From patchwork Thu Apr 23 11:27:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yash Shah X-Patchwork-Id: 238397 List-Id: U-Boot discussion From: yash.shah at sifive.com (Yash Shah) Date: Thu, 23 Apr 2020 16:57:15 +0530 Subject: [PATCH v2 1/2] pwm: Add DT documentation for SiFive PWM Controller In-Reply-To: <1587641236-5026-1-git-send-email-yash.shah@sifive.com> References: <1587641236-5026-1-git-send-email-yash.shah@sifive.com> Message-ID: <1587641236-5026-2-git-send-email-yash.shah@sifive.com> DT documentation for PWM controller added from Linux v5.6 commit: daa78cc3408e ("pwm: sifive: Add DT documentation for SiFive PWM Controller") Signed-off-by: Yash Shah Reviewed-by: Heiko Schocher --- doc/device-tree-bindings/pwm/pwm-sifive.txt | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 doc/device-tree-bindings/pwm/pwm-sifive.txt diff --git a/doc/device-tree-bindings/pwm/pwm-sifive.txt b/doc/device-tree-bindings/pwm/pwm-sifive.txt new file mode 100644 index 0000000..9a98837 --- /dev/null +++ b/doc/device-tree-bindings/pwm/pwm-sifive.txt @@ -0,0 +1,31 @@ +SiFive PWM controller + +Unlike most other PWM controllers, the SiFive PWM controller currently only +supports one period for all channels in the PWM. All PWMs need to run at +the same period. The period also has significant restrictions on the values +it can achieve, which the driver rounds to the nearest achievable period. +PWM RTL that corresponds to the IP block version numbers can be found +here: + +https://github.com/sifive/sifive-blocks/tree/master/src/main/scala/devices/pwm + +Required properties: +- compatible: Should be "sifive,-pwm" and "sifive,pwm". + Supported compatible strings are: "sifive,fu540-c000-pwm" for the SiFive + PWM v0 as integrated onto the SiFive FU540 chip, and "sifive,pwm0" for the + SiFive PWM v0 IP block with no chip integration tweaks. +- reg: physical base address and length of the controller's registers +- clocks: Should contain a clock identifier for the PWM's parent clock. +- #pwm-cells: Should be 3. +- interrupts: one interrupt per PWM channel + +Examples: + +pwm: pwm at 10020000 { + compatible = "sifive,fu540-c000-pwm", "sifive,pwm0"; + reg = <0x0 0x10020000 0x0 0x1000>; + clocks = <&tlclk>; + interrupt-parent = <&plic>; + interrupts = <42 43 44 45>; + #pwm-cells = <3>; +}; From patchwork Thu Apr 23 11:27:16 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yash Shah X-Patchwork-Id: 238398 List-Id: U-Boot discussion From: yash.shah at sifive.com (Yash Shah) Date: Thu, 23 Apr 2020 16:57:16 +0530 Subject: [PATCH v2 2/2] pwm: Add PWM driver for SiFive SoC In-Reply-To: <1587641236-5026-1-git-send-email-yash.shah@sifive.com> References: <1587641236-5026-1-git-send-email-yash.shah@sifive.com> Message-ID: <1587641236-5026-3-git-send-email-yash.shah@sifive.com> Adds a PWM driver for PWM chip present in SiFive's HiFive Unleashed SoC This driver is simple port of Linux pwm sifive driver from Linux v5.6 commit: 9e37a53eb051 ("pwm: sifive: Add a driver for SiFive SoC PWM") Signed-off-by: Yash Shah Reviewed-by: Heiko Schocher --- drivers/pwm/Kconfig | 6 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-sifive.c | 172 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 drivers/pwm/pwm-sifive.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 1f36fc7..d6ea9ee 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -40,6 +40,12 @@ config PWM_SANDBOX useful. The PWM can be enabled but is not connected to any outputs so this is not very useful. +config PWM_SIFIVE + bool "Enable support for SiFive PWM" + depends on DM_PWM + help + This PWM is found SiFive's FU540 and other SoCs. + config PWM_TEGRA bool "Enable support for the Tegra PWM" depends on DM_PWM diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index a837c35..c2c7fce 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -14,5 +14,6 @@ obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o +obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c new file mode 100644 index 0000000..77bc659 --- /dev/null +++ b/drivers/pwm/pwm-sifive.c @@ -0,0 +1,172 @@ +// 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 + +/* 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_ofdata_to_platdata(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, + .ofdata_to_platdata = pwm_sifive_ofdata_to_platdata, + .probe = pwm_sifive_probe, + .priv_auto_alloc_size = sizeof(struct pwm_sifive_priv), +};