diff options
Diffstat (limited to 'roms/u-boot/drivers/pwm/pwm-sifive.c')
-rw-r--r-- | roms/u-boot/drivers/pwm/pwm-sifive.c | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/pwm/pwm-sifive.c b/roms/u-boot/drivers/pwm/pwm-sifive.c new file mode 100644 index 000000000..b9813a3b6 --- /dev/null +++ b/roms/u-boot/drivers/pwm/pwm-sifive.c @@ -0,0 +1,174 @@ +// 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), +}; |