[PATCH 2/2] pwm: sophgo: add driver for Sophgo SG2042 PWM

Uwe Kleine-König u.kleine-koenig at baylibre.com
Thu Sep 5 09:03:01 PDT 2024


Hello,

On Thu, Sep 05, 2024 at 08:10:42PM +0800, Chen Wang wrote:
> From: Chen Wang <unicorn_wang at outlook.com>
> 
> Add a PWM driver for PWM controller in Sophgo SG2042 SoC.
> 
> Signed-off-by: Chen Wang <unicorn_wang at outlook.com>
> ---
>  drivers/pwm/Kconfig             |   9 ++
>  drivers/pwm/Makefile            |   1 +
>  drivers/pwm/pwm-sophgo-sg2042.c | 148 ++++++++++++++++++++++++++++++++
>  3 files changed, 158 insertions(+)
>  create mode 100644 drivers/pwm/pwm-sophgo-sg2042.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 3e53838990f5..6287d63a84fd 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -577,6 +577,15 @@ config PWM_SL28CPLD
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-sl28cpld.
>  
> +config PWM_SOPHGO_SG2042
> +	tristate "Sophgo SG2042 PWM support"
> +	depends on ARCH_SOPHGO || COMPILE_TEST
> +	help
> +	  PWM driver for Sophgo SG2042 PWM controller.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm_sophgo_sg2042.
> +
>  config PWM_SPEAR
>  	tristate "STMicroelectronics SPEAr PWM support"
>  	depends on PLAT_SPEAR || COMPILE_TEST
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 0be4f3e6dd43..ef2555e83183 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -52,6 +52,7 @@ obj-$(CONFIG_PWM_RZ_MTU3)	+= pwm-rz-mtu3.o
>  obj-$(CONFIG_PWM_SAMSUNG)	+= pwm-samsung.o
>  obj-$(CONFIG_PWM_SIFIVE)	+= pwm-sifive.o
>  obj-$(CONFIG_PWM_SL28CPLD)	+= pwm-sl28cpld.o
> +obj-$(CONFIG_PWM_SOPHGO_SG2042)	+= pwm-sophgo-sg2042.o
>  obj-$(CONFIG_PWM_SPEAR)		+= pwm-spear.o
>  obj-$(CONFIG_PWM_SPRD)		+= pwm-sprd.o
>  obj-$(CONFIG_PWM_STI)		+= pwm-sti.o
> diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c
> new file mode 100644
> index 000000000000..cf11ad54b4de
> --- /dev/null
> +++ b/drivers/pwm/pwm-sophgo-sg2042.c
> @@ -0,0 +1,148 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sophgo SG2042 PWM Controller Driver
> + *
> + * Copyright (C) 2024 Sophgo Technology Inc.
> + * Copyright (C) 2024 Chen Wang <unicorn_wang at outlook.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +
> +#include <asm/div64.h>
> +
> +/*
> + * Offset RegisterName
> + * 0x0000 HLPERIOD0
> + * 0x0004 PERIOD0
> + * 0x0008 HLPERIOD1
> + * 0x000C PERIOD1
> + * 0x0010 HLPERIOD2
> + * 0x0014 PERIOD2
> + * 0x0018 HLPERIOD3
> + * 0x001C PERIOD3
> + * Four groups and every group is composed of HLPERIOD & PERIOD
> + */
> +#define REG_HLPERIOD	0x0
> +#define REG_PERIOD	0x4
> +
> +#define REG_GROUP	0x8
> +
> +#define SG2042_PWM_CHANNELNUM	4
> +
> +/**
> + * struct sg2042_pwm_chip - private data of PWM chip
> + * @base:		base address of mapped PWM registers
> + * @base_clk:		base clock used to drive the pwm controller
> + */
> +struct sg2042_pwm_chip {
> +	void __iomem *base;
> +	struct clk *base_clk;
> +};
> +
> +static inline
> +struct sg2042_pwm_chip *to_sg2042_pwm_chip(struct pwm_chip *chip)
> +{
> +	return pwmchip_get_drvdata(chip);
> +}
> +
> +static void pwm_sg2042_config(void __iomem *base, unsigned int channo, u32 period, u32 hlperiod)
> +{
> +	writel(period, base + REG_GROUP * channo + REG_PERIOD);
> +	writel(hlperiod, base + REG_GROUP * channo + REG_HLPERIOD);
> +}

I suggest to use the following instead:

	#define SG2042_HLPERIOD(chan) ((chan) * 8 + 0)
	#define SG2042_PERIOD(chan) ((chan) * 8 + 4)

	...

	static void pwm_sg2042_config(void __iomem *base, unsigned int chan, u32 period, u32 hlperiod)
	{
		writel(period, base + SG2042_PERIOD(chan));
		writel(hlperiod, base + SG2042_HLPERIOD(chan));
	}

The (subjective?) advantage is that the definition of SG2042_HLPERIOD
contains information about all channel's HLPERIOD register and the usage
in pwm_sg2042_config is obviously(?) right.

(Another advantage is that the register names have a prefix unique to
the driver, so there is no danger of mixing it up with some other
hardware's driver that might also have a register named "PERIOD".)

Is this racy? i.e. can it happen that between the two writel the output
is defined by the new period and old duty_cycle?

How does the hardware behave on reconfiguration? Does it complete the
currently running period? Please document that in a comment at the start
of the driver like many other drivers do. (See 

	sed -rn '/Limitations:/,/\*\/?$/p' drivers/pwm/*.c

)

> +static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			    const struct pwm_state *state)
> +{
> +	struct sg2042_pwm_chip *sg2042_pwm = to_sg2042_pwm_chip(chip);
> +	u32 hlperiod;
> +	u32 period;
> +	u64 f_clk;
> +	u64 p;
> +
> +	if (!state->enabled) {
> +		pwm_sg2042_config(sg2042_pwm->base, pwm->hwpwm, 0, 0);
> +		return 0;
> +	}

Here you're missing (I guess):

	if (state->polarity == PWM_POLARITY_INVERSED)
		return -EINVAL;

> +	/*
> +	 * Period of High level (duty_cycle) = HLPERIOD x Period_clk
> +	 * Period of One Cycle (period) = PERIOD x Period_clk
> +	 */
> +	f_clk = clk_get_rate(sg2042_pwm->base_clk);
> +
> +	p = f_clk * state->period;

This might overflow.

> +	do_div(p, NSEC_PER_SEC);
> +	period = (u32)p;

This gets very wrong if p happens to be bigger than U32_MAX.

If you ensure f_clk <= NSEC_PER_SEC in .probe() (in combination with a
call to clk_rate_exclusive_get()), you can do:

	period_cycles = min(mul_u64_u64_div_u64(f_clk, state->period, NSEC_PER_SEC), U32_MAX);
	duty_cycles = min(mul_u64_u64_div_u64(f_clk, state->duty_cycle, NSEC_PER_SEC), U32_MAX);

This would also allow to store the clkrate in the driver private struct
and drop the pointer to the clk from there.

> +	p = f_clk * state->duty_cycle;
> +	do_div(p, NSEC_PER_SEC);
> +	hlperiod = (u32)p;
> +
> +	dev_dbg(pwmchip_parent(chip), "chan[%d]: period=%u, hlperiod=%u\n",
> +		pwm->hwpwm, period, hlperiod);

pwm->hwpwm is an unsigned int, so use %u for it.

> +	pwm_sg2042_config(sg2042_pwm->base, pwm->hwpwm, period, hlperiod);
> +
> +	return 0;
> +}
> +
> +static const struct pwm_ops pwm_sg2042_ops = {
> +	.apply		= pwm_sg2042_apply,

No .get_state() possible? Please use a single space before =.

> +};
> +
> +static const struct of_device_id sg2042_pwm_match[] = {
> +	{ .compatible = "sophgo,sg2042-pwm" },
> +	{ },

Please drop the , after the sentinel entry.

> +};
> +MODULE_DEVICE_TABLE(of, sg2042_pwm_match);
> +
> +static int pwm_sg2042_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct sg2042_pwm_chip *sg2042_pwm;
> +	struct pwm_chip *chip;
> +	int ret;
> +
> +	chip = devm_pwmchip_alloc(&pdev->dev, SG2042_PWM_CHANNELNUM, sizeof(*sg2042_pwm));
> +	if (IS_ERR(chip))
> +		return PTR_ERR(chip);
> +	sg2042_pwm = to_sg2042_pwm_chip(chip);
> +
> +	chip->ops = &pwm_sg2042_ops;
> +
> +	sg2042_pwm->base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(sg2042_pwm->base))
> +		return PTR_ERR(sg2042_pwm->base);
> +
> +	sg2042_pwm->base_clk = devm_clk_get_enabled(&pdev->dev, "apb");
> +	if (IS_ERR(sg2042_pwm->base_clk))
> +		return dev_err_probe(dev, PTR_ERR(sg2042_pwm->base_clk),
> +				     "failed to get base clk\n");
> +
> +	ret = devm_pwmchip_add(&pdev->dev, chip);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "failed to register PWM chip\n");
> +
> +	platform_set_drvdata(pdev, chip);

This is unused and should/can be dropped.

> +
> +	return 0;
> +}
> +
> +static struct platform_driver pwm_sg2042_driver = {
> +	.driver	= {
> +		.name	= "sg2042-pwm",
> +		.of_match_table = of_match_ptr(sg2042_pwm_match),
> +	},
> +	.probe = pwm_sg2042_probe,
> +};
> +module_platform_driver(pwm_sg2042_driver);

Please use a single space before =.

> +MODULE_AUTHOR("Chen Wang");
> +MODULE_DESCRIPTION("Sophgo SG2042 PWM driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.34.1
> 

Best regards
Uwe
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-riscv/attachments/20240905/ba7f841e/attachment.sig>


More information about the linux-riscv mailing list