* [PATCH] pwm: Add PWM driver for SiFive SoC
@ 2020-04-15 6:32 Yash Shah
2020-04-15 17:04 ` Heiko Schocher
0 siblings, 1 reply; 3+ messages in thread
From: Yash Shah @ 2020-04-15 6:32 UTC (permalink / raw)
To: u-boot
Adds a PWM driver for PWM chip present in SiFive's HiFive Unleashed SoC
This driver is simple port of Linux pwm sifive driver.
Signed-off-by: Yash Shah <yash.shah@sifive.com>
---
drivers/pwm/Kconfig | 6 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-sifive.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 188 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..e2d4185 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_DM_PWM) += pwm-uclass.o
obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o
obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
+obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o
obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o
obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o
diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
new file mode 100644
index 0000000..c54297e
--- /dev/null
+++ b/drivers/pwm/pwm-sifive.c
@@ -0,0 +1,181 @@
+// 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 {
+ fdt_addr_t base;
+ ulong freq;
+ const struct pwm_sifive_data *data;
+};
+
+static int pwm_sifive_set_invert(struct udevice *dev, uint channel,
+ bool polarity)
+{
+ debug("%s: Do not support polarity\n", __func__);
+
+ return 0;
+}
+
+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, (void __iomem *)priv->base + regs->cfg);
+ writel(frac, (void __iomem *)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((void __iomem *)priv->base + regs->cfg);
+ val |= PWM_SIFIVE_PWMCFG_EN_ALWAYS;
+ writel(val, (void __iomem *)priv->base + regs->cfg);
+ } else {
+ writel(0, (void __iomem *)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(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_invert = pwm_sifive_set_invert,
+ .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),
+};
--
2.7.4
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH] pwm: Add PWM driver for SiFive SoC
2020-04-15 6:32 [PATCH] pwm: Add PWM driver for SiFive SoC Yash Shah
@ 2020-04-15 17:04 ` Heiko Schocher
2020-04-17 11:52 ` Yash Shah
0 siblings, 1 reply; 3+ messages in thread
From: Heiko Schocher @ 2020-04-15 17:04 UTC (permalink / raw)
To: u-boot
Hello Yash Shah,
Am 15.04.2020 um 08:32 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.
So please add more information from exatly which linux version
this patch is from, see:
https://www.denx.de/wiki/U-Boot/Patches#Attributing_Code_Copyrights_Sign
> Signed-off-by: Yash Shah <yash.shah@sifive.com>
> ---
> drivers/pwm/Kconfig | 6 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-sifive.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 188 insertions(+)
> create mode 100644 drivers/pwm/pwm-sifive.c
You introduce a new devicetree node, please add a documentation in
doc/device-tree-bindings/pwm/
>
> 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..e2d4185 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_DM_PWM) += pwm-uclass.o
>
> obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o
> obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
> +obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
Please keep this list sorted.
> obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o
> obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o
> obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o
> diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
> new file mode 100644
> index 0000000..c54297e
> --- /dev/null
> +++ b/drivers/pwm/pwm-sifive.c
> @@ -0,0 +1,181 @@
> +// 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 {
> + fdt_addr_t base;
> + ulong freq;
> + const struct pwm_sifive_data *data;
> +};
> +
> +static int pwm_sifive_set_invert(struct udevice *dev, uint channel,
> + bool polarity)
> +{
> + debug("%s: Do not support polarity\n", __func__);
> +
> + return 0;
> +}
You don;t need this function.
> +
> +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, (void __iomem *)priv->base + regs->cfg);
Do you really need this cast on each readl/writel call?
> + writel(frac, (void __iomem *)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((void __iomem *)priv->base + regs->cfg);
> + val |= PWM_SIFIVE_PWMCFG_EN_ALWAYS;
> + writel(val, (void __iomem *)priv->base + regs->cfg);
> + } else {
> + writel(0, (void __iomem *)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(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_invert = pwm_sifive_set_invert,
> + .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),
> +};
>
Thanks!
bye,
Heiko
--
DENX Software Engineering GmbH, Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-52 Fax: +49-8142-66989-80 Email: hs at denx.de
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] pwm: Add PWM driver for SiFive SoC
2020-04-15 17:04 ` Heiko Schocher
@ 2020-04-17 11:52 ` Yash Shah
0 siblings, 0 replies; 3+ messages in thread
From: Yash Shah @ 2020-04-17 11:52 UTC (permalink / raw)
To: u-boot
> -----Original Message-----
> From: Heiko Schocher <hs@denx.de>
> Sent: 15 April 2020 22:34
> To: Yash Shah <yash.shah@sifive.com>
> Cc: martyn.welch at collabora.co.uk; u-boot at lists.denx.de
> Subject: Re: [PATCH] pwm: Add PWM driver for SiFive SoC
>
> [External Email] Do not click links or attachments unless you recognize the
> sender and know the content is safe
>
> Hello Yash Shah,
>
> Am 15.04.2020 um 08:32 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.
>
> So please add more information from exatly which linux version this patch is
> from, see:
>
> https://www.denx.de/wiki/U-
> Boot/Patches#Attributing_Code_Copyrights_Sign
Sure, will refer this doc and send a v2 accordingly.
>
> > Signed-off-by: Yash Shah <yash.shah@sifive.com>
> > ---
> > drivers/pwm/Kconfig | 6 ++
> > drivers/pwm/Makefile | 1 +
> > drivers/pwm/pwm-sifive.c | 181
> +++++++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 188 insertions(+)
> > create mode 100644 drivers/pwm/pwm-sifive.c
>
> You introduce a new devicetree node, please add a documentation in
> doc/device-tree-bindings/pwm/
Will add the dt documentation.
>
> >
> > 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..e2d4185 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -12,6 +12,7 @@ obj-$(CONFIG_DM_PWM) += pwm-uclass.o
> >
> > obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o
> > obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
> > +obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
>
> Please keep this list sorted.
Sure. My bad.
>
> > obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o
> > obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o
> > obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o
> > diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c new
> > file mode 100644 index 0000000..c54297e
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-sifive.c
> > @@ -0,0 +1,181 @@
> > +// 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 {
> > + fdt_addr_t base;
> > + ulong freq;
> > + const struct pwm_sifive_data *data; };
> > +
> > +static int pwm_sifive_set_invert(struct udevice *dev, uint channel,
> > + bool polarity) {
> > + debug("%s: Do not support polarity\n", __func__);
> > +
> > + return 0;
> > +}
>
> You don;t need this function.
>
> > +
> > +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, (void __iomem *)priv->base + regs->cfg);
>
> Do you really need this cast on each readl/writel call?
Will fix this in v2.
Thanks for your comments.
- Yash
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2020-04-17 11:52 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-15 6:32 [PATCH] pwm: Add PWM driver for SiFive SoC Yash Shah
2020-04-15 17:04 ` Heiko Schocher
2020-04-17 11:52 ` Yash Shah
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.