123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- // 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 <asm/global_data.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
- #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),
- };
|