diff mbox series

[v2,2/2] pwm: Add PWM driver for SiFive SoC

Message ID 1587641236-5026-3-git-send-email-yash.shah@sifive.com
State Accepted
Commit 7239a610b796b0bb8f85c5c21798596c2768cb50
Headers show
Series Add support for PWM SiFive | expand

Commit Message

Yash Shah April 23, 2020, 11:27 a.m. UTC
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 <yash.shah at sifive.com>
---
 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

Comments

Heiko Schocher July 8, 2020, 4:30 a.m. UTC | #1
Hello Yash,

Am 23.04.2020 um 13:27 schrieb Yash Shah:
> 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 <yash.shah at sifive.com>
> ---
>   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

Reviewed-by: Heiko Schocher <hs at denx.de>

@Tom: Do you want to pick up this patch, or I can take it into
u-boot-i2c tree (as I currently plan to send a pull request)?

bye,
Heiko
Tom Rini July 8, 2020, 2:40 p.m. UTC | #2
On Wed, Jul 08, 2020 at 06:30:28AM +0200, Heiko Schocher wrote:
> Hello Yash,
> 
> Am 23.04.2020 um 13:27 schrieb Yash Shah:
> > 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 <yash.shah at sifive.com>
> > ---
> >   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
> 
> Reviewed-by: Heiko Schocher <hs at denx.de>
> 
> @Tom: Do you want to pick up this patch, or I can take it into
> u-boot-i2c tree (as I currently plan to send a pull request)?

Take it via your tree, thanks.
Heiko Schocher July 9, 2020, 8:27 a.m. UTC | #3
Hello Yash,

Am 23.04.2020 um 13:27 schrieb Yash Shah:
> 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 <yash.shah at sifive.com>
> ---
>   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

Applied to u-boot-i2c.git master

Thanks!

bye,
Heiko
diff mbox series

Patch

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 <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_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),
+};