| // 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 <common.h> |
| #include <clk.h> |
| #include <div64.h> |
| #include <dm.h> |
| #include <pwm.h> |
| #include <regmap.h> |
| #include <linux/io.h> |
| #include <linux/log2.h> |
| #include <linux/bitfield.h> |
| |
| /* 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 |
| |
| 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); |
| |
| /* |
| * 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); |
| |
| 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; |
| u32 val; |
| |
| debug("%s: Enable '%s'\n", __func__, dev->name); |
| |
| if (enable) { |
| val = readl(priv->base + regs->cfg); |
| val |= PWM_SIFIVE_PWMCFG_EN_ALWAYS; |
| writel(val, priv->base + regs->cfg); |
| } else { |
| writel(0, 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), |
| }; |