// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2020 SiFive, Inc * For SiFive's PWM IP block documentation please refer Chapter 14 of * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf * * Limitations: * - When changing both duty cycle and period, we cannot prevent in * software that the output might produce a period with mixed * settings (new period length and old duty cycle). * - The hardware cannot generate a 100% duty cycle. * - The hardware generates only inverted output. */ #include #include #include #include #include #include #include #include #include #include /* PWMCFG fields */ #define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0) #define PWM_SIFIVE_PWMCFG_STICKY BIT(8) #define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9) #define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10) #define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12) #define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13) #define PWM_SIFIVE_PWMCFG_CENTER BIT(16) #define PWM_SIFIVE_PWMCFG_GANG BIT(24) #define PWM_SIFIVE_PWMCFG_IP BIT(28) /* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */ #define PWM_SIFIVE_SIZE_PWMCMP 4 #define PWM_SIFIVE_CMPWIDTH 16 #define PWM_SIFIVE_CHANNEL_ENABLE_VAL 0 #define PWM_SIFIVE_CHANNEL_DISABLE_VAL 0xffff DECLARE_GLOBAL_DATA_PTR; struct pwm_sifive_regs { unsigned long cfg; unsigned long cnt; unsigned long pwms; unsigned long cmp0; }; struct pwm_sifive_data { struct pwm_sifive_regs regs; }; struct pwm_sifive_priv { void __iomem *base; ulong freq; const struct pwm_sifive_data *data; }; static int pwm_sifive_set_config(struct udevice *dev, uint channel, uint period_ns, uint duty_ns) { struct pwm_sifive_priv *priv = dev_get_priv(dev); const struct pwm_sifive_regs *regs = &priv->data->regs; unsigned long scale_pow; unsigned long long num; u32 scale, val = 0, frac; debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns); /* * The PWM unit is used with pwmzerocmp=0, so the only way to modify the * period length is using pwmscale which provides the number of bits the * counter is shifted before being feed to the comparators. A period * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks. * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period */ scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000); scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf); val |= (FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale) | PWM_SIFIVE_PWMCFG_EN_ALWAYS); /* * The problem of output producing mixed setting as mentioned at top, * occurs here. To minimize the window for this problem, we are * calculating the register values first and then writing them * consecutively */ num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH); frac = DIV_ROUND_CLOSEST_ULL(num, period_ns); frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); frac = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac; writel(val, priv->base + regs->cfg); writel(frac, priv->base + regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP); return 0; } static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable) { struct pwm_sifive_priv *priv = dev_get_priv(dev); const struct pwm_sifive_regs *regs = &priv->data->regs; debug("%s: Enable '%s'\n", __func__, dev->name); if (enable) writel(PWM_SIFIVE_CHANNEL_ENABLE_VAL, priv->base + regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP); else writel(PWM_SIFIVE_CHANNEL_DISABLE_VAL, priv->base + regs->cmp0 + channel * PWM_SIFIVE_SIZE_PWMCMP); return 0; } static int pwm_sifive_of_to_plat(struct udevice *dev) { struct pwm_sifive_priv *priv = dev_get_priv(dev); priv->base = dev_read_addr_ptr(dev); return 0; } static int pwm_sifive_probe(struct udevice *dev) { struct pwm_sifive_priv *priv = dev_get_priv(dev); struct clk clk; int ret = 0; ret = clk_get_by_index(dev, 0, &clk); if (ret < 0) { debug("%s get clock fail!\n", __func__); return -EINVAL; } priv->freq = clk_get_rate(&clk); priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev); return 0; } static const struct pwm_ops pwm_sifive_ops = { .set_config = pwm_sifive_set_config, .set_enable = pwm_sifive_set_enable, }; static const struct pwm_sifive_data pwm_data = { .regs = { .cfg = 0x00, .cnt = 0x08, .pwms = 0x10, .cmp0 = 0x20, }, }; static const struct udevice_id pwm_sifive_ids[] = { { .compatible = "sifive,pwm0", .data = (ulong)&pwm_data}, { } }; U_BOOT_DRIVER(pwm_sifive) = { .name = "pwm_sifive", .id = UCLASS_PWM, .of_match = pwm_sifive_ids, .ops = &pwm_sifive_ops, .of_to_plat = pwm_sifive_of_to_plat, .probe = pwm_sifive_probe, .priv_auto = sizeof(struct pwm_sifive_priv), };