diff mbox series

[2/2] pwm: sophgo: add driver for SG2044

Message ID 20250407072056.8629-3-looong.bin@gmail.com (mailing list archive)
State Superseded
Headers show
Series riscv: pwm: sophgo: add pwm support for SG2044 | expand

Checks

Context Check Description
bjorn/pre-ci_am success Success
bjorn/build-rv32-defconfig success build-rv32-defconfig
bjorn/build-rv64-clang-allmodconfig fail build-rv64-clang-allmodconfig
bjorn/build-rv64-gcc-allmodconfig fail build-rv64-gcc-allmodconfig
bjorn/build-rv64-nommu-k210-defconfig success build-rv64-nommu-k210-defconfig
bjorn/build-rv64-nommu-k210-virt success build-rv64-nommu-k210-virt
bjorn/checkpatch warning checkpatch
bjorn/dtb-warn-rv64 success dtb-warn-rv64
bjorn/header-inline success header-inline
bjorn/kdoc fail kdoc
bjorn/module-param success module-param
bjorn/verify-fixes success verify-fixes
bjorn/verify-signedoff fail verify-signedoff

Commit Message

Longbin Li April 7, 2025, 7:20 a.m. UTC
From: ghost <2990955050@qq.com>

Add PWM controller for SG2044.

Signed-off-by: Longbin Li <looong.bin@gmail.com>
---
 drivers/pwm/pwm-sophgo-sg2042.c | 162 +++++++++++++++++++++++++++-----
 1 file changed, 138 insertions(+), 24 deletions(-)

--
2.48.1

Comments

Uwe Kleine-König April 7, 2025, 9:38 a.m. UTC | #1
On Mon, Apr 07, 2025 at 03:20:39PM +0800, Longbin Li wrote:
> From: ghost <2990955050@qq.com>

Huh, is that a real name?

> Add PWM controller for SG2044.
> 
> Signed-off-by: Longbin Li <looong.bin@gmail.com>
> ---
>  drivers/pwm/pwm-sophgo-sg2042.c | 162 +++++++++++++++++++++++++++-----
>  1 file changed, 138 insertions(+), 24 deletions(-)
> 
> diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c
> index ff4639d849ce..c62e8c758d87 100644
> --- a/drivers/pwm/pwm-sophgo-sg2042.c
> +++ b/drivers/pwm/pwm-sophgo-sg2042.c

The Limitations paragraph needs updating. E.g. SG2044 seems to support
polarity while SG2042 doesn't.

> @@ -26,20 +26,22 @@
>  #include <linux/pwm.h>
>  #include <linux/reset.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 SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
> -#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4)
> +#define REG_HLPERIOD		0x0
> +#define REG_PERIOD		0x4
> +#define REG_GROUP		0x8

REG_GROUP belongs to a different category than REG_PERIOD. So please use
a different schema to name it (or drop it, see below).

> +#define REG_POLARITY		0x40
> +
> +#define REG_PWMSTART		0x44
> +#define REG_PWMUPDATE		0x4C
> +#define REG_SHIFTCOUNT		0x80
> +#define REG_SHIFTSTART		0x90

REG_SHIFTCOUNT and REG_SHIFTSTART are unused.

> +#define REG_PWM_OE		0xD0

Actually I liked the old prefix better. E.g. "REG_POLARITY" looks more
generic that it actually is.

> +
> +#define PWM_REG_NUM		0x80

This is unused?

> +
> +#define PWM_POLARITY_MASK(n) BIT(n)
> +#define PWM_HLPERIOD(chan) ((chan) * REG_GROUP + REG_HLPERIOD)
> +#define PWM_PERIOD(chan) ((chan) * REG_GROUP + REG_PERIOD)

((chan) * 8 + 0) is IMHO better. I guess this is subjective because at
least the *8 is repeated several times, but the advantage of not using a
define for 8 (and 0 and 4) is that by looking at

	#define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)

you immediatly see the offsets of the HLPERIOD register, while for

	#define PWM_HLPERIOD(chan) ((chan) * REG_GROUP + REG_HLPERIOD)

you have to lookup two additional symbols.

Also PWM is a prefix that is too generic.

>  #define SG2042_PWM_CHANNELNUM	4
> 
> @@ -51,6 +53,12 @@
>  struct sg2042_pwm_ddata {
>  	void __iomem *base;
>  	unsigned long clk_rate_hz;
> +	struct mutex lock;

What does this lock protect? Note that there is a chip lock that is held
when .apply() is called, to serialize apply calls for a single chip. I
guess this can and should be dropped.

> +};
> +
> +struct sg2042_chip_data {
> +	const struct pwm_ops ops;
> +	bool atomic;
>  };
> 
>  /*
> @@ -62,8 +70,8 @@ static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan,
>  {
>  	void __iomem *base = ddata->base;
> 
> -	writel(period_ticks, base + SG2042_PWM_PERIOD(chan));
> -	writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan));
> +	writel(period_ticks, base + PWM_PERIOD(chan));
> +	writel(hlperiod_ticks, base + PWM_HLPERIOD(chan));

The register renaming adds really quite some noise that is actually
unrelated to this patch. If you really think the register defines need
renaming, do that in a separate patch (and justify it well).

>  }
> 
>  static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> @@ -104,8 +112,8 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>  	u32 hlperiod_ticks;
>  	u32 period_ticks;
> 
> -	period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan));
> -	hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan));
> +	period_ticks = readl(ddata->base + PWM_PERIOD(chan));
> +	hlperiod_ticks = readl(ddata->base + PWM_HLPERIOD(chan));
> 
>  	if (!period_ticks) {
>  		state->enabled = false;
> @@ -123,13 +131,112 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>  	return 0;
>  }
> 
> -static const struct pwm_ops pwm_sg2042_ops = {
> -	.apply = pwm_sg2042_apply,
> -	.get_state = pwm_sg2042_get_state,
> +static void pwm_sg2044_config(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, bool enabled)
> +{
> +	u32 pwm_value;
> +
> +	pwm_value = readl(ddata->base + REG_PWMSTART);
> +
> +	if (enabled)
> +		writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
> +	else
> +		writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
> +}
> +
> +static void pwm_sg2044_set_outputenable(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
> +					bool enabled)
> +{
> +	u32 pwm_value;
> +
> +	pwm_value = readl(ddata->base + REG_PWM_OE);
> +
> +	if (enabled)
> +		writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
> +	else
> +		writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
> +}
> +
> +static int pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
> +				   const struct pwm_state *state)
> +{
> +	enum pwm_polarity polarity;
> +	u32 pwm_value;
> +
> +	pwm_value = readl(ddata->base + REG_POLARITY);
> +
> +	polarity = state->polarity;
> +
> +	if (polarity == PWM_POLARITY_NORMAL)
> +		pwm_value &= ~BIT(pwm->hwpwm);
> +	else
> +		pwm_value |= BIT(pwm->hwpwm);
> +
> +	writel(pwm_value, ddata->base + REG_POLARITY);

I like this idiom better than the one used in
pwm_sg2044_set_outputenable() and pwm_sg2044_config(). However drop the
local variable polarity.

> +	return 0;
> +}
> +
> +static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			    const struct pwm_state *state)
> +{
> +	struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
> +	u32 hlperiod_ticks;
> +	u32 period_ticks;
> +
> +	if (!state->enabled) {
> +		pwm_sg2044_config(ddata, pwm, false);
> +		return 0;
> +	}
> +
> +	pwm_sg2044_set_polarity(ddata, pwm, state);
> +
> +	/*
> +	 * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
> +	 * Duration of One Cycle (period) = PERIOD x Period_of_input_clk
> +	 */
> +	period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period,
> +					       NSEC_PER_SEC), U32_MAX);
> +	hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle,
> +						 NSEC_PER_SEC), U32_MAX);

This is the same calculation as for sg2042. I think I'd put that in a
function that is used by both variants.

> +	dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n",
> +		pwm->hwpwm, period_ticks, hlperiod_ticks);

Now that there are more register values, please add them all to the
debug output.

> +	pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
> +
> +	guard(mutex)(&ddata->lock);
> +
> +	/*
> +	 * re-enable PWMSTART to refresh the register period
> +	 */
> +	pwm_sg2044_config(ddata, pwm, false);

pwm_sg2044_config() is conceptually different to pwm_sg2042_config().
This is irritating, so please find a better name.

> +	pwm_sg2044_set_outputenable(ddata, pwm, true);
> +	pwm_sg2044_config(ddata, pwm, true);
> +
> +	return 0;
> +}
> +
> +static const struct sg2042_chip_data sg2042_chip_data = {
> +	.ops = {
> +		.apply = pwm_sg2042_apply,
> +		.get_state = pwm_sg2042_get_state,
> +	},
> +	.atomic = true,
> +};
> +
> +static const struct sg2042_chip_data sg2044_chip_data = {
> +	.ops = {
> +		.apply = pwm_sg2044_apply,
> +		.get_state = pwm_sg2042_get_state,
> +	},
> +	.atomic = false,

If you drop the mutex don't forget to drop this one, too.

>  };

Best regards
Uwe
Longbin Li April 7, 2025, 10:02 a.m. UTC | #2
On Mon, Apr 07, 2025 at 11:38:27AM +0200, Uwe Kleine-König wrote:
> On Mon, Apr 07, 2025 at 03:20:39PM +0800, Longbin Li wrote:
> > From: ghost <2990955050@qq.com>
> 
> Huh, is that a real name?
>

My fault,i will change it.
 
> > Add PWM controller for SG2044.
> > 
> > Signed-off-by: Longbin Li <looong.bin@gmail.com>
> > ---
> >  drivers/pwm/pwm-sophgo-sg2042.c | 162 +++++++++++++++++++++++++++-----
> >  1 file changed, 138 insertions(+), 24 deletions(-)
> > 
> > diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c
> > index ff4639d849ce..c62e8c758d87 100644
> > --- a/drivers/pwm/pwm-sophgo-sg2042.c
> > +++ b/drivers/pwm/pwm-sophgo-sg2042.c
> 
> The Limitations paragraph needs updating. E.g. SG2044 seems to support
> polarity while SG2042 doesn't.
> 

Thanks,i will add that.

> > @@ -26,20 +26,22 @@
> >  #include <linux/pwm.h>
> >  #include <linux/reset.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 SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
> > -#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4)
> > +#define REG_HLPERIOD		0x0
> > +#define REG_PERIOD		0x4
> > +#define REG_GROUP		0x8
> 
> REG_GROUP belongs to a different category than REG_PERIOD. So please use
> a different schema to name it (or drop it, see below).
> 

I will improve it.

> > +#define REG_POLARITY		0x40
> > +
> > +#define REG_PWMSTART		0x44
> > +#define REG_PWMUPDATE		0x4C
> > +#define REG_SHIFTCOUNT		0x80
> > +#define REG_SHIFTSTART		0x90
> 
> REG_SHIFTCOUNT and REG_SHIFTSTART are unused.
> 

I will drop it.

> > +#define REG_PWM_OE		0xD0
> 
> Actually I liked the old prefix better. E.g. "REG_POLARITY" looks more
> generic that it actually is.
> 

I will reconsider that.

> > +
> > +#define PWM_REG_NUM		0x80
> 
> This is unused?
> 

I will drop it,thanks!

> > +
> > +#define PWM_POLARITY_MASK(n) BIT(n)
> > +#define PWM_HLPERIOD(chan) ((chan) * REG_GROUP + REG_HLPERIOD)
> > +#define PWM_PERIOD(chan) ((chan) * REG_GROUP + REG_PERIOD)
> 
> ((chan) * 8 + 0) is IMHO better. I guess this is subjective because at
> least the *8 is repeated several times, but the advantage of not using a
> define for 8 (and 0 and 4) is that by looking at
> 
> 	#define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
> 
> you immediatly see the offsets of the HLPERIOD register, while for
> 
> 	#define PWM_HLPERIOD(chan) ((chan) * REG_GROUP + REG_HLPERIOD)
> 
> you have to lookup two additional symbols.
> 
> Also PWM is a prefix that is too generic.
> 

I will reconsider that.

> >  #define SG2042_PWM_CHANNELNUM	4
> > 
> > @@ -51,6 +53,12 @@
> >  struct sg2042_pwm_ddata {
> >  	void __iomem *base;
> >  	unsigned long clk_rate_hz;
> > +	struct mutex lock;
> 
> What does this lock protect? Note that there is a chip lock that is held
> when .apply() is called, to serialize apply calls for a single chip. I
> guess this can and should be dropped.
> 

I will retest it, and if possible i will drop it, thanks.

> > +};
> > +
> > +struct sg2042_chip_data {
> > +	const struct pwm_ops ops;
> > +	bool atomic;
> >  };
> > 
> >  /*
> > @@ -62,8 +70,8 @@ static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan,
> >  {
> >  	void __iomem *base = ddata->base;
> > 
> > -	writel(period_ticks, base + SG2042_PWM_PERIOD(chan));
> > -	writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan));
> > +	writel(period_ticks, base + PWM_PERIOD(chan));
> > +	writel(hlperiod_ticks, base + PWM_HLPERIOD(chan));
> 
> The register renaming adds really quite some noise that is actually
> unrelated to this patch. If you really think the register defines need
> renaming, do that in a separate patch (and justify it well).
> 

Sorry, i will modify here.

> >  }
> > 
> >  static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > @@ -104,8 +112,8 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> >  	u32 hlperiod_ticks;
> >  	u32 period_ticks;
> > 
> > -	period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan));
> > -	hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan));
> > +	period_ticks = readl(ddata->base + PWM_PERIOD(chan));
> > +	hlperiod_ticks = readl(ddata->base + PWM_HLPERIOD(chan));
> > 
> >  	if (!period_ticks) {
> >  		state->enabled = false;
> > @@ -123,13 +131,112 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> >  	return 0;
> >  }
> > 
> > -static const struct pwm_ops pwm_sg2042_ops = {
> > -	.apply = pwm_sg2042_apply,
> > -	.get_state = pwm_sg2042_get_state,
> > +static void pwm_sg2044_config(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, bool enabled)
> > +{
> > +	u32 pwm_value;
> > +
> > +	pwm_value = readl(ddata->base + REG_PWMSTART);
> > +
> > +	if (enabled)
> > +		writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
> > +	else
> > +		writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
> > +}
> > +
> > +static void pwm_sg2044_set_outputenable(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
> > +					bool enabled)
> > +{
> > +	u32 pwm_value;
> > +
> > +	pwm_value = readl(ddata->base + REG_PWM_OE);
> > +
> > +	if (enabled)
> > +		writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
> > +	else
> > +		writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
> > +}
> > +
> > +static int pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
> > +				   const struct pwm_state *state)
> > +{
> > +	enum pwm_polarity polarity;
> > +	u32 pwm_value;
> > +
> > +	pwm_value = readl(ddata->base + REG_POLARITY);
> > +
> > +	polarity = state->polarity;
> > +
> > +	if (polarity == PWM_POLARITY_NORMAL)
> > +		pwm_value &= ~BIT(pwm->hwpwm);
> > +	else
> > +		pwm_value |= BIT(pwm->hwpwm);
> > +
> > +	writel(pwm_value, ddata->base + REG_POLARITY);
> 
> I like this idiom better than the one used in
> pwm_sg2044_set_outputenable() and pwm_sg2044_config(). However drop the
> local variable polarity.
> 

I will modify that, thanks.

> > +	return 0;
> > +}
> > +
> > +static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > +			    const struct pwm_state *state)
> > +{
> > +	struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
> > +	u32 hlperiod_ticks;
> > +	u32 period_ticks;
> > +
> > +	if (!state->enabled) {
> > +		pwm_sg2044_config(ddata, pwm, false);
> > +		return 0;
> > +	}
> > +
> > +	pwm_sg2044_set_polarity(ddata, pwm, state);
> > +
> > +	/*
> > +	 * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
> > +	 * Duration of One Cycle (period) = PERIOD x Period_of_input_clk
> > +	 */
> > +	period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period,
> > +					       NSEC_PER_SEC), U32_MAX);
> > +	hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle,
> > +						 NSEC_PER_SEC), U32_MAX);
> 
> This is the same calculation as for sg2042. I think I'd put that in a
> function that is used by both variants.
> 

Thanks, i will modify that.

> > +	dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n",
> > +		pwm->hwpwm, period_ticks, hlperiod_ticks);
> 
> Now that there are more register values, please add them all to the
> debug output.
> 

I will, thanks.

> > +	pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
> > +
> > +	guard(mutex)(&ddata->lock);
> > +
> > +	/*
> > +	 * re-enable PWMSTART to refresh the register period
> > +	 */
> > +	pwm_sg2044_config(ddata, pwm, false);
> 
> pwm_sg2044_config() is conceptually different to pwm_sg2042_config().
> This is irritating, so please find a better name.
> 

I will modify that, thanks.

> > +	pwm_sg2044_set_outputenable(ddata, pwm, true);
> > +	pwm_sg2044_config(ddata, pwm, true);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct sg2042_chip_data sg2042_chip_data = {
> > +	.ops = {
> > +		.apply = pwm_sg2042_apply,
> > +		.get_state = pwm_sg2042_get_state,
> > +	},
> > +	.atomic = true,
> > +};
> > +
> > +static const struct sg2042_chip_data sg2044_chip_data = {
> > +	.ops = {
> > +		.apply = pwm_sg2044_apply,
> > +		.get_state = pwm_sg2042_get_state,
> > +	},
> > +	.atomic = false,
> 
> If you drop the mutex don't forget to drop this one, too.
> 

I will, thanks for your reminder.

> >  };
> 
> Best regards
> Uwe

Best regards
Longbin Li
kernel test robot April 7, 2025, 3:30 p.m. UTC | #3
Hi Longbin,

kernel test robot noticed the following build warnings:

[auto build test WARNING on krzk-dt/for-next]
[also build test WARNING on linus/master v6.15-rc1 next-20250407]
[cannot apply to robh/for-next sophgo/for-next sophgo/fixes]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Longbin-Li/dt-bindings-pwm-sophgo-add-pwm-controller-for-SG2044/20250407-153334
base:   https://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-dt.git for-next
patch link:    https://lore.kernel.org/r/20250407072056.8629-3-looong.bin%40gmail.com
patch subject: [PATCH 2/2] pwm: sophgo: add driver for SG2044
config: arm-randconfig-003-20250407 (https://download.01.org/0day-ci/archive/20250407/202504072300.KCXJcmJK-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250407/202504072300.KCXJcmJK-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202504072300.KCXJcmJK-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/pwm/pwm-sophgo-sg2042.c:57: warning: Function parameter or struct member 'lock' not described in 'sg2042_pwm_ddata'


vim +57 drivers/pwm/pwm-sophgo-sg2042.c

    47	
    48	/**
    49	 * struct sg2042_pwm_ddata - private driver data
    50	 * @base:		base address of mapped PWM registers
    51	 * @clk_rate_hz:	rate of base clock in HZ
    52	 */
    53	struct sg2042_pwm_ddata {
    54		void __iomem *base;
    55		unsigned long clk_rate_hz;
    56		struct mutex lock;
  > 57	};
    58
Chen Wang April 8, 2025, 1:02 a.m. UTC | #4
On 2025/4/7 15:20, Longbin Li wrote:
> From: ghost <2990955050@qq.com>
>
> Add PWM controller for SG2044.
>
> Signed-off-by: Longbin Li <looong.bin@gmail.com>

Consider updating "MODULE_AUTHOR" (add your name) and 
"MODULE_DESCRIPTION"(add SG2044).

Thanks,

Chen

[......]
diff mbox series

Patch

diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c
index ff4639d849ce..c62e8c758d87 100644
--- a/drivers/pwm/pwm-sophgo-sg2042.c
+++ b/drivers/pwm/pwm-sophgo-sg2042.c
@@ -26,20 +26,22 @@ 
 #include <linux/pwm.h>
 #include <linux/reset.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 SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
-#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4)
+#define REG_HLPERIOD		0x0
+#define REG_PERIOD		0x4
+#define REG_GROUP		0x8
+#define REG_POLARITY		0x40
+
+#define REG_PWMSTART		0x44
+#define REG_PWMUPDATE		0x4C
+#define REG_SHIFTCOUNT		0x80
+#define REG_SHIFTSTART		0x90
+#define REG_PWM_OE		0xD0
+
+#define PWM_REG_NUM		0x80
+
+#define PWM_POLARITY_MASK(n) BIT(n)
+#define PWM_HLPERIOD(chan) ((chan) * REG_GROUP + REG_HLPERIOD)
+#define PWM_PERIOD(chan) ((chan) * REG_GROUP + REG_PERIOD)

 #define SG2042_PWM_CHANNELNUM	4

@@ -51,6 +53,12 @@ 
 struct sg2042_pwm_ddata {
 	void __iomem *base;
 	unsigned long clk_rate_hz;
+	struct mutex lock;
+};
+
+struct sg2042_chip_data {
+	const struct pwm_ops ops;
+	bool atomic;
 };

 /*
@@ -62,8 +70,8 @@  static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan,
 {
 	void __iomem *base = ddata->base;

-	writel(period_ticks, base + SG2042_PWM_PERIOD(chan));
-	writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan));
+	writel(period_ticks, base + PWM_PERIOD(chan));
+	writel(hlperiod_ticks, base + PWM_HLPERIOD(chan));
 }

 static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -104,8 +112,8 @@  static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
 	u32 hlperiod_ticks;
 	u32 period_ticks;

-	period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan));
-	hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan));
+	period_ticks = readl(ddata->base + PWM_PERIOD(chan));
+	hlperiod_ticks = readl(ddata->base + PWM_HLPERIOD(chan));

 	if (!period_ticks) {
 		state->enabled = false;
@@ -123,13 +131,112 @@  static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
 	return 0;
 }

-static const struct pwm_ops pwm_sg2042_ops = {
-	.apply = pwm_sg2042_apply,
-	.get_state = pwm_sg2042_get_state,
+static void pwm_sg2044_config(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, bool enabled)
+{
+	u32 pwm_value;
+
+	pwm_value = readl(ddata->base + REG_PWMSTART);
+
+	if (enabled)
+		writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
+	else
+		writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
+}
+
+static void pwm_sg2044_set_outputenable(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+					bool enabled)
+{
+	u32 pwm_value;
+
+	pwm_value = readl(ddata->base + REG_PWM_OE);
+
+	if (enabled)
+		writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
+	else
+		writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
+}
+
+static int pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+				   const struct pwm_state *state)
+{
+	enum pwm_polarity polarity;
+	u32 pwm_value;
+
+	pwm_value = readl(ddata->base + REG_POLARITY);
+
+	polarity = state->polarity;
+
+	if (polarity == PWM_POLARITY_NORMAL)
+		pwm_value &= ~BIT(pwm->hwpwm);
+	else
+		pwm_value |= BIT(pwm->hwpwm);
+
+	writel(pwm_value, ddata->base + REG_POLARITY);
+
+	return 0;
+}
+
+static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			    const struct pwm_state *state)
+{
+	struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
+	u32 hlperiod_ticks;
+	u32 period_ticks;
+
+	if (!state->enabled) {
+		pwm_sg2044_config(ddata, pwm, false);
+		return 0;
+	}
+
+	pwm_sg2044_set_polarity(ddata, pwm, state);
+
+	/*
+	 * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
+	 * Duration of One Cycle (period) = PERIOD x Period_of_input_clk
+	 */
+	period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period,
+					       NSEC_PER_SEC), U32_MAX);
+	hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle,
+						 NSEC_PER_SEC), U32_MAX);
+
+	dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n",
+		pwm->hwpwm, period_ticks, hlperiod_ticks);
+
+	pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
+
+	guard(mutex)(&ddata->lock);
+
+	/*
+	 * re-enable PWMSTART to refresh the register period
+	 */
+	pwm_sg2044_config(ddata, pwm, false);
+	pwm_sg2044_set_outputenable(ddata, pwm, true);
+	pwm_sg2044_config(ddata, pwm, true);
+
+	return 0;
+}
+
+static const struct sg2042_chip_data sg2042_chip_data = {
+	.ops = {
+		.apply = pwm_sg2042_apply,
+		.get_state = pwm_sg2042_get_state,
+	},
+	.atomic = true,
+};
+
+static const struct sg2042_chip_data sg2044_chip_data = {
+	.ops = {
+		.apply = pwm_sg2044_apply,
+		.get_state = pwm_sg2042_get_state,
+	},
+	.atomic = false,
 };

 static const struct of_device_id sg2042_pwm_ids[] = {
-	{ .compatible = "sophgo,sg2042-pwm" },
+	{ .compatible = "sophgo,sg2042-pwm",
+	  .data = &sg2042_chip_data },
+	{ .compatible = "sophgo,sg2044-pwm",
+	  .data = &sg2044_chip_data },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
@@ -137,17 +244,24 @@  MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
 static int pwm_sg2042_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
+	const struct sg2042_chip_data *chip_data;
 	struct sg2042_pwm_ddata *ddata;
 	struct reset_control *rst;
 	struct pwm_chip *chip;
 	struct clk *clk;
 	int ret;

+	chip_data = device_get_match_data(dev);
+	if (!chip_data)
+		return -ENODEV;
+
 	chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata));
 	if (IS_ERR(chip))
 		return PTR_ERR(chip);
 	ddata = pwmchip_get_drvdata(chip);

+	mutex_init(&ddata->lock);
+
 	ddata->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(ddata->base))
 		return PTR_ERR(ddata->base);
@@ -170,8 +284,8 @@  static int pwm_sg2042_probe(struct platform_device *pdev)
 	if (IS_ERR(rst))
 		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");

-	chip->ops = &pwm_sg2042_ops;
-	chip->atomic = true;
+	chip->ops = &chip_data->ops;
+	chip->atomic = chip_data->atomic;

 	ret = devm_pwmchip_add(dev, chip);
 	if (ret < 0)