Hello, On Fri, Mar 04, 2022 at 02:20:12PM +0800, Hammer Hsieh wrote: > Add Sunplus SoC PWM Driver > > Signed-off-by: Hammer Hsieh <hammerh0314@xxxxxxxxx> > --- > Changes in v2: > - Addressed all comments from Uwe Kleine-König. > - rebase kernel to 5.17 rc5 > > MAINTAINERS | 1 + > drivers/pwm/Kconfig | 11 +++ > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-sunplus.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 242 insertions(+) > create mode 100644 drivers/pwm/pwm-sunplus.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 825b714..8710c8e 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -18535,6 +18535,7 @@ SUNPLUS PWM DRIVER > M: Hammer Hsieh <hammerh0314@xxxxxxxxx> > S: Maintained > F: Documentation/devicetree/bindings/pwm/pwm-sunplus.yaml > +F: drivers/pwm/pwm-sunplus.c > > SUNPLUS RTC DRIVER > M: Vincent Shih <vincent.sunplus@xxxxxxxxx> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index 21e3b05..54cfb50 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -572,6 +572,17 @@ config PWM_SUN4I > To compile this driver as a module, choose M here: the module > will be called pwm-sun4i. > > +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_TEGRA > tristate "NVIDIA Tegra PWM support" > depends on ARCH_TEGRA || 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..170534f > --- /dev/null > +++ b/drivers/pwm/pwm-sunplus.c > @@ -0,0 +1,229 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * PWM device driver for SUNPLUS SoCs Is there a public manual available for this hardware? If yes, please add a link here. > + * > + * Limitations: > + * - Only supports normal polarity. How does the HW behave when it's disabled? Usual candidates are: - It freezes at where it currently is - It outputs low - It becomes tristate Please note this in the Limitations section, too. Another thing to mention is if running periods are completed when the parameters change. > + * > + * Author: Hammer Hsieh <hammerh0314@xxxxxxxxx> > + */ > +#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)) I'd not give PWM_SUP_FREQ_BASE and PWM_SUP_DUTY_BASE a name here, just #define PWM_SUP_FREQ(ch) (0x008 + 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 Please use a consistent prefix for the driver specific defines. > +struct sunplus_pwm { > + struct pwm_chip chip; > + void __iomem *base; > + struct clk *clk; > + u32 approx_period[PWM_SUP_NUM]; > + u32 approx_duty_cycle[PWM_SUP_NUM]; > +}; > + > +static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip) > +{ > + return container_of(chip, struct sunplus_pwm, chip); > +} > + > +static void sunplus_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct sunplus_pwm *priv = to_sunplus_pwm(chip); > + u32 value; > + > + /* disable pwm channel output */ > + value = readl(priv->base + PWM_SUP_CONTROL0); > + value &= ~BIT(pwm->hwpwm); > + writel(value, priv->base + PWM_SUP_CONTROL0); > + /* disable pwm channel clk source */ > + value = readl(priv->base + PWM_SUP_CONTROL1); > + value &= ~BIT(pwm->hwpwm); > + writel(value, priv->base + PWM_SUP_CONTROL1); the .free callback isn't supposed to modify the hardware. > +} > + > +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 dd_freq, duty, value, value1; As value and value1 hold register values for PWM_SUP_CONTROL0 and PWM_SUP_CONTROL1, I'd call them control0 and control1 respectively. > + u32 tmp, rate; > + u64 max_period, period_ns, duty_ns, clk_rate; > + > + if (state->polarity != pwm->state.polarity) > + return -EINVAL; > + > + if (!state->enabled) { > + /* disable pwm channel output */ > + value = readl(priv->base + PWM_SUP_CONTROL0); > + value &= ~BIT(pwm->hwpwm); I'd give this one a name. Something like: #define PWM_SUP_CONTROL_EN(ch) BIT(ch) (Pick the right name from the manual.) > + writel(value, priv->base + PWM_SUP_CONTROL0); > + /* disable pwm channel clk source */ > + value = readl(priv->base + PWM_SUP_CONTROL1); > + value &= ~BIT(pwm->hwpwm); > + writel(value, priv->base + PWM_SUP_CONTROL1); > + return 0; > + } > + > + clk_rate = clk_get_rate(priv->clk); > + rate = (u32)clk_rate / 100000; This cast doesn't change anything, does it? > + max_period = PWM_SUP_FREQ_MAX * (PWM_SUP_FREQ_SCALER * 10000 / rate); Here you have rounding issues. Consider rate = 3329. Then you get max_period = 0xffff * (2560000 / 3329) = 0xffff * 768 = 50330880. However the actual result is 50396395.31... Also dividing by the result of a division looses precision. > + > + if (state->period > max_period) > + return -EINVAL; No, you're supposed to implement the biggest period possible not bigger than the requested period. So the right thing here is: > + period_ns = state->period; period = min(state->period, max_period); > + duty_ns = state->duty_cycle; > + > + priv->approx_period[pwm->hwpwm] = (u32)period_ns / 100; > + priv->approx_duty_cycle[pwm->hwpwm] = (u32)duty_ns / 100; Tracking period_ns / 100 seems strange and vulnerable to rounding issues. > + /* cal pwm freq and check value under range */ > + dd_freq = rate * priv->approx_period[pwm->hwpwm] / (PWM_SUP_FREQ_SCALER * 100); This is all too complicated, you just need: dd_freq = mul_u64_u64_div_u64(clk_rate, period, (u64)PWM_SUP_FREQ_SCALER * NSEC_PER_SEC) > + 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); > + value1 = readl(priv->base + PWM_SUP_CONTROL1); > + value1 |= 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 = priv->approx_duty_cycle[pwm->hwpwm] * PWM_SUP_FREQ_SCALER; > + tmp /= priv->approx_period[pwm->hwpwm]; Please use the exact values available. > + duty = (u32)tmp; > + duty |= (pwm->hwpwm << PWM_DD_SEL_BIT_SHIFT); > + } > + writel(duty, priv->base + PWM_SUP_DUTY(pwm->hwpwm)); > + writel(value1, priv->base + PWM_SUP_CONTROL1); > + writel(value, priv->base + PWM_SUP_CONTROL0); What is the difference between CONTROL1 and CONTROL0? > + 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, freq, duty, rate, freq_tmp, duty_tmp; > + u64 tmp, clk_rate; > + > + value = readl(priv->base + PWM_SUP_CONTROL0); > + > + if (value & BIT(pwm->hwpwm)) { > + clk_rate = clk_get_rate(priv->clk); > + rate = (u32)clk_rate / 100000; > + freq = readl(priv->base + PWM_SUP_FREQ(pwm->hwpwm)); > + duty = readl(priv->base + PWM_SUP_DUTY(pwm->hwpwm)); > + duty &= ~GENMASK(9, 8); > + > + freq_tmp = rate * priv->approx_period[pwm->hwpwm] / (PWM_SUP_FREQ_SCALER * 100); > + duty_tmp = priv->approx_duty_cycle[pwm->hwpwm] * PWM_SUP_FREQ_SCALER / > + priv->approx_period[pwm->hwpwm]; > + > + if (freq == freq_tmp && duty == duty_tmp) { > + state->period = priv->approx_period[pwm->hwpwm] * 100; > + state->duty_cycle = priv->approx_duty_cycle[pwm->hwpwm] * 100; > + } else { > + tmp = (u64)freq * PWM_SUP_FREQ_SCALER * 10000; > + state->period = div64_u64(tmp, rate); > + tmp = (u64)freq * (u64)duty * 10000; > + state->duty_cycle = div64_u64(tmp, rate); > + } > + state->enabled = true; > + } else { > + state->enabled = false; > + } > + > + state->polarity = PWM_POLARITY_NORMAL; > +} When .get_state() is first called, .apply wasn't called yet. Then priv->approx_period[pwm->hwpwm] is zero and the returned result is wrong. Please read the register values and calculate the implemented output without caching. > +static const struct pwm_ops sunplus_pwm_ops = { > + .free = sunplus_pwm_free, > + .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"); If priv->clk is the dummy clk, clk_get_rate returns 0. This is bad as (at lease up to now) you divide by rate in .apply(). > + > + ret = clk_prepare_enable(priv->clk); > + if (ret) missing error message > + return ret; > + > + ret = devm_add_action_or_reset(dev, > + (void(*)(void *))clk_disable_unprepare, Without checking my C book I'm unsure if this is save on all platforms. I'd implement a oneline function for this. > + priv->clk); > + if (ret) missing error message > + return ret; > + > + priv->chip.dev = dev; > + priv->chip.ops = &sunplus_pwm_ops; > + priv->chip.npwm = PWM_SUP_NUM; > + > + 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 <hammerh0314@xxxxxxxxx>"); > +MODULE_LICENSE("GPL"); > -- > 2.7.4 > > -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | https://www.pengutronix.de/ |
Attachment:
signature.asc
Description: PGP signature