Hello, On Fri, Dec 17, 2021 at 07:46:08PM +0800, Hammer Hsieh wrote: > Add Sunplus SoC PWM Driver > > Signed-off-by: Hammer Hsieh <hammer.hsieh@xxxxxxxxxxx> > --- > MAINTAINERS | 1 + > drivers/pwm/Kconfig | 11 +++ > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-sunplus.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 205 insertions(+) > create mode 100644 drivers/pwm/pwm-sunplus.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 721ed79..1c9e3c5 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -18246,6 +18246,7 @@ SUNPLUS PWM DRIVER > M: Hammer Hsieh <hammer.hsieh@xxxxxxxxxxx> > S: Maintained > F: Documentation/devicetree/bindings/pwm/pwm-sunplus.yaml > +F: drivers/pwm/pwm-sunplus.c > > SUPERH > M: Yoshinori Sato <ysato@xxxxxxxxxxxxxxxxxxxx> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index 21e3b05..9df5d5f 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -526,6 +526,17 @@ config PWM_SPRD > To compile this driver as a module, choose M here: the module > will be called pwm-sprd. > > +config PWM_SUNPLUS > + tristate "Sunplus PWM support" > + depends on ARCH_SUNPLUS || COMPILE_TEST > + depends on HAS_IOMEM && OF > + help > + Generic PWM framework driver for the PWM controller on > + Sunplus SoCs. > + > + To compile this driver as a module, choose M here: the module > + will be called pwm-sunplus. > + > config PWM_STI > tristate "STiH4xx PWM support" > depends on ARCH_STI || COMPILE_TEST > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index 708840b..be58616 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -53,6 +53,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o > obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o > obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o > obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o > +obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o > obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o > obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o > obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o > diff --git a/drivers/pwm/pwm-sunplus.c b/drivers/pwm/pwm-sunplus.c > new file mode 100644 > index 0000000..0ae59fc > --- /dev/null > +++ b/drivers/pwm/pwm-sunplus.c > @@ -0,0 +1,192 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * PWM device driver for SUNPLUS SoCs > + * > + * Author: Hammer Hsieh <hammer.hsieh@xxxxxxxxxxx> > + */ Please add a section here about your hardware limitations. Please stick to the format used in e.g. pwm-sifive.c. That is a block starting with * Limitations: and then a list of issues. One such item is: Only supports normal polarity. > +#include <linux/clk.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/pwm.h> > + > +#define PWM_SUP_CONTROL0 0x000 > +#define PWM_SUP_CONTROL1 0x004 > +#define PWM_SUP_FREQ_BASE 0x008 > +#define PWM_SUP_DUTY_BASE 0x018 > +#define PWM_SUP_FREQ(ch) (PWM_SUP_FREQ_BASE + 4 * (ch)) > +#define PWM_SUP_DUTY(ch) (PWM_SUP_DUTY_BASE + 4 * (ch)) > +#define PWM_SUP_FREQ_MAX GENMASK(15, 0) > +#define PWM_SUP_DUTY_MAX GENMASK(7, 0) > + > +#define PWM_SUP_NUM 4 > +#define PWM_BYPASS_BIT_SHIFT 8 > +#define PWM_DD_SEL_BIT_SHIFT 8 > +#define PWM_SUP_FREQ_SCALER 256 > + > +struct sunplus_pwm { > + struct pwm_chip chip; > + void __iomem *base; > + struct clk *clk; > +}; > + > +static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip) > +{ > + return container_of(chip, struct sunplus_pwm, chip); > +} > + > +static void sunplus_reg_init(void __iomem *base) > +{ > + u32 i, value; > + > + /* turn off all pwm channel output */ > + value = readl(base + PWM_SUP_CONTROL0); > + value &= ~GENMASK((PWM_SUP_NUM - 1), 0); > + writel(value, base + PWM_SUP_CONTROL0); > + > + /* init all pwm channel clock source */ > + value = readl(base + PWM_SUP_CONTROL1); > + value |= GENMASK((PWM_SUP_NUM - 1), 0); > + writel(value, base + PWM_SUP_CONTROL1); > + > + /* init all freq and duty setting */ > + for (i = 0; i < PWM_SUP_NUM; i++) { > + writel(0, base + PWM_SUP_FREQ(i)); > + writel(0, base + PWM_SUP_DUTY(i)); > + } Please keep the PWM in their boot-up state. That is, if the bootloader enabled a display with a bootsplash, don't disable the backlight when the PWM driver loads. > +} > + > +static int sunplus_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, > + const struct pwm_state *state) > +{ > + struct sunplus_pwm *priv = to_sunplus_pwm(chip); > + u32 period_ns, duty_ns, value; > + u32 dd_freq, duty; > + u64 tmp; > + if (state->polarity != PWM_POLARITY_NORMAL) return -EINVAL; > + if (!state->enabled) { > + value = readl(priv->base + PWM_SUP_CONTROL0); > + value &= ~BIT(pwm->hwpwm); > + writel(value, priv->base + PWM_SUP_CONTROL0); > + return 0; > + } > + > + period_ns = state->period; state->period is an u64, so you might loose precision here. > + duty_ns = state->duty_cycle; ditto > + > + /* cal pwm freq and check value under range */ > + tmp = clk_get_rate(priv->clk) * (u64)period_ns; This might overflow? > + tmp = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC); > + tmp = DIV_ROUND_CLOSEST_ULL(tmp, PWM_SUP_FREQ_SCALER); In general you should pick the highest period that isn't bigger than the requested period. I didn't check in detail, but using round-closest is a strong hint that you get that wrong. > + dd_freq = (u32)tmp; > + > + if (dd_freq == 0) > + return -EINVAL; > + > + if (dd_freq > PWM_SUP_FREQ_MAX) > + dd_freq = PWM_SUP_FREQ_MAX; > + > + writel(dd_freq, priv->base + PWM_SUP_FREQ(pwm->hwpwm)); > + > + /* cal and set pwm duty */ > + value = readl(priv->base + PWM_SUP_CONTROL0); > + value |= BIT(pwm->hwpwm); > + if (duty_ns == period_ns) { > + value |= BIT(pwm->hwpwm + PWM_BYPASS_BIT_SHIFT); > + duty = PWM_SUP_DUTY_MAX; > + } else { > + value &= ~BIT(pwm->hwpwm + PWM_BYPASS_BIT_SHIFT); > + tmp = (u64)duty_ns * PWM_SUP_FREQ_SCALER + (period_ns >> 1); > + tmp = DIV_ROUND_CLOSEST_ULL(tmp, (u64)period_ns); > + duty = (u32)tmp; > + duty |= (pwm->hwpwm << PWM_DD_SEL_BIT_SHIFT); This is also more inexact than necessary. In general don't use period_ns in the calculation of duty register settings. As with period you're supposed to pick the biggest possible dutycycle not bigger than the requested value. Consider a PWM that with register P = P and register D = D implements a PWM output with period = 1000 * P ns and duty_cycle = 1000 * D ns For a request of period = 39900 and duty_cycle = 12100, you have to pick P = 39 and D = 12. However P * duty_ns / period_ns = 11.82 ... > + } > + writel(value, priv->base + PWM_SUP_CONTROL0); > + writel(duty, priv->base + PWM_SUP_DUTY(pwm->hwpwm)); > + > + return 0; > +} > + > +static void sunplus_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, > + struct pwm_state *state) > +{ > + struct sunplus_pwm *priv = to_sunplus_pwm(chip); > + u32 value; > + > + value = readl(priv->base + PWM_SUP_CONTROL0); > + > + if (value & BIT(pwm->hwpwm)) > + state->enabled = true; > + else > + state->enabled = false; This looks incomplete. Please enable PWM_DEBUG during your tests and address all output generated by that. As the general idea is that passing the result from .get_state() to .apply shouldn't modify the output, you have (in general) round up divisions in .get_state(). > +} > + > +static const struct pwm_ops sunplus_pwm_ops = { > + .apply = sunplus_pwm_apply, > + .get_state = sunplus_pwm_get_state, > + .owner = THIS_MODULE, > +}; > + > +static int sunplus_pwm_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct sunplus_pwm *priv; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(priv->base)) > + return PTR_ERR(priv->base); > + > + priv->clk = devm_clk_get_optional(dev, NULL); > + if (IS_ERR(priv->clk)) > + return dev_err_probe(dev, PTR_ERR(priv->clk), > + "get pwm clock failed\n"); > + > + ret = clk_prepare_enable(priv->clk); > + if (ret) > + return ret; > + > + ret = devm_add_action_or_reset(dev, > + (void(*)(void *))clk_disable_unprepare, > + priv->clk); > + if (ret) > + return ret; > + > + priv->chip.dev = dev; > + priv->chip.ops = &sunplus_pwm_ops; > + priv->chip.npwm = PWM_SUP_NUM; > + > + sunplus_reg_init(priv->base); > + > + platform_set_drvdata(pdev, priv); This is unused, so please drop this. > + > + ret = devm_pwmchip_add(dev, &priv->chip); > + if (ret < 0) > + return dev_err_probe(dev, ret, "Cannot register sunplus PWM\n"); > + > + return 0; > +} > + > +static const struct of_device_id sunplus_pwm_of_match[] = { > + { .compatible = "sunplus,sp7021-pwm", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, sunplus_pwm_of_match); > + > +static struct platform_driver sunplus_pwm_driver = { > + .probe = sunplus_pwm_probe, > + .driver = { > + .name = "sunplus-pwm", > + .of_match_table = sunplus_pwm_of_match, > + }, > +}; > +module_platform_driver(sunplus_pwm_driver); > + > +MODULE_DESCRIPTION("Sunplus SoC PWM Driver"); > +MODULE_AUTHOR("Hammer Hsieh <hammer.hsieh@xxxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); "GPL" has the same semantic and is the more usual, so I suggest to use that one. Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | https://www.pengutronix.de/ |
Attachment:
signature.asc
Description: PGP signature