diff mbox series

[v2,2/4] pwm: meson: Support constant and polarity bits

Message ID 20241016152553.2321992-3-gnstark@salutedevices.com (mailing list archive)
State Superseded
Headers show
Series pwm: meson: Support constant and polarity bits | expand

Commit Message

George Stark Oct. 16, 2024, 3:25 p.m. UTC
Newer meson PWM IPs support constant and polarity bits. Support them to
correctly implement constant and inverted output levels.

Using constant bit allows to have truly stable low or high output level.
Since hi and low regs internally increment its values by 1 just writing
zero to any of them gives 1 clock count impulse. If constant bit is set
zero value in hi and low regs is not incremented.

Using polarity bit instead of swapping hi and low reg values allows to
correctly identify inversion in .get_state().

Signed-off-by: George Stark <gnstark@salutedevices.com>
---
 drivers/pwm/pwm-meson.c | 63 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 56 insertions(+), 7 deletions(-)

Comments

Uwe Kleine-König Nov. 4, 2024, 9:32 a.m. UTC | #1
Hello George,

there are two minor things I dislike in this patch/driver. But I'm not
sure the alternatives are objectively considerably better. See below and
judge yourself.

On Wed, Oct 16, 2024 at 06:25:51PM +0300, George Stark wrote:
> Newer meson PWM IPs support constant and polarity bits. Support them to
> correctly implement constant and inverted output levels.
> 
> Using constant bit allows to have truly stable low or high output level.
> Since hi and low regs internally increment its values by 1 just writing
> zero to any of them gives 1 clock count impulse. If constant bit is set
> zero value in hi and low regs is not incremented.
> 
> Using polarity bit instead of swapping hi and low reg values allows to
> correctly identify inversion in .get_state().
> 
> Signed-off-by: George Stark <gnstark@salutedevices.com>
> ---
>  drivers/pwm/pwm-meson.c | 63 ++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 56 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c
> index 2ef632caebcc..974c3c74768c 100644
> --- a/drivers/pwm/pwm-meson.c
> +++ b/drivers/pwm/pwm-meson.c
> @@ -6,7 +6,7 @@
>   * PWM output is achieved by calculating a clock that permits calculating
>   * two periods (low and high). The counter then has to be set to switch after
>   * N cycles for the first half period.
> - * The hardware has no "polarity" setting. This driver reverses the period
> + * Partly the hardware has no "polarity" setting. This driver reverses the period
>   * cycles (the low length is inverted with the high length) for
>   * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity
>   * from the hardware.
> @@ -56,6 +56,10 @@
>  #define MISC_B_CLK_SEL_SHIFT	6
>  #define MISC_A_CLK_SEL_SHIFT	4
>  #define MISC_CLK_SEL_MASK	0x3
> +#define MISC_B_CONSTANT_EN	BIT(29)
> +#define MISC_A_CONSTANT_EN	BIT(28)
> +#define MISC_B_INVERT_EN	BIT(27)
> +#define MISC_A_INVERT_EN	BIT(26)
>  #define MISC_B_EN		BIT(1)
>  #define MISC_A_EN		BIT(0)
>  
> @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data {
>  	u8		clk_div_shift;
>  	u8		clk_en_shift;
>  	u32		pwm_en_mask;
> +	u32		const_en_mask;
> +	u32		inv_en_mask;
>  } meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
>  	{
>  		.reg_offset	= REG_PWM_A,
> @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data {
>  		.clk_div_shift	= MISC_A_CLK_DIV_SHIFT,
>  		.clk_en_shift	= MISC_A_CLK_EN_SHIFT,
>  		.pwm_en_mask	= MISC_A_EN,
> +		.const_en_mask	= MISC_A_CONSTANT_EN,
> +		.inv_en_mask	= MISC_A_INVERT_EN,
>  	},
>  	{
>  		.reg_offset	= REG_PWM_B,
> @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data {
>  		.clk_div_shift	= MISC_B_CLK_DIV_SHIFT,
>  		.clk_en_shift	= MISC_B_CLK_EN_SHIFT,
>  		.pwm_en_mask	= MISC_B_EN,
> +		.const_en_mask	= MISC_B_CONSTANT_EN,
> +		.inv_en_mask	= MISC_B_INVERT_EN,
>  	}
>  };

So the generic register description describes the const and invert bits,
but it doesn't apply to all IPs. Thinking about that, I wonder why this
struct exists at all. I would have done this as follows:

	#define MESON_PWM_REG_PWM(chan)		(0 + 4 * (chan))

	#define MESON_PWM_REG_MISC		(8)
	#define MESON_PWM_REG_MISC_EN(chan)		BIT(chan)
	#define MESON_PWM_REG_MISC_CLK_SEL(chan)	GENMASK(5 + 2 * (chan), 4 + 2 * (chan))
	....

and then use these constants directly (with pwm->hwpwm as parameter if
needed) in the code. I would expect this to result in more efficient and
smaller code.

> @@ -227,6 +252,15 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
>  
>  	value = readl(meson->base + REG_MISC_AB);
>  	value |= channel_data->pwm_en_mask;
> +
> +	if (meson->data->has_constant)
> +		meson_pwm_assign_bit(&value, channel_data->const_en_mask,
> +				     channel->constant);

Personally I'd prefer:

	value &= ~MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
	if (meson->data->has_constant && channel->constant)
		value |= MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);

even though your variant only mentions the mask once. While it has this
repetition, it's clear what happens without having to know what
meson_pwm_assign_bit() does. Maybe that's subjective?

Best regards
Uwe
George Stark Nov. 6, 2024, 1:54 p.m. UTC | #2
Hello Uwe

Thanks for the review.

On 11/4/24 12:32, Uwe Kleine-König wrote:
> Hello George,
> 
> there are two minor things I dislike in this patch/driver. But I'm not
> sure the alternatives are objectively considerably better. See below and
> judge yourself.

...

>> @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data {
>>   	u8		clk_div_shift;
>>   	u8		clk_en_shift;
>>   	u32		pwm_en_mask;
>> +	u32		const_en_mask;
>> +	u32		inv_en_mask;
>>   } meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
>>   	{
>>   		.reg_offset	= REG_PWM_A,
>> @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data {
>>   		.clk_div_shift	= MISC_A_CLK_DIV_SHIFT,
>>   		.clk_en_shift	= MISC_A_CLK_EN_SHIFT,
>>   		.pwm_en_mask	= MISC_A_EN,
>> +		.const_en_mask	= MISC_A_CONSTANT_EN,
>> +		.inv_en_mask	= MISC_A_INVERT_EN,
>>   	},
>>   	{
>>   		.reg_offset	= REG_PWM_B,
>> @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data {
>>   		.clk_div_shift	= MISC_B_CLK_DIV_SHIFT,
>>   		.clk_en_shift	= MISC_B_CLK_EN_SHIFT,
>>   		.pwm_en_mask	= MISC_B_EN,
>> +		.const_en_mask	= MISC_B_CONSTANT_EN,
>> +		.inv_en_mask	= MISC_B_INVERT_EN,
>>   	}
>>   };
> 
> So the generic register description describes the const and invert bits,
> but it doesn't apply to all IPs. Thinking about that, I wonder why this
> struct exists at all. I would have done this as follows:
> 
> 	#define MESON_PWM_REG_PWM(chan)		(0 + 4 * (chan))
> 
> 	#define MESON_PWM_REG_MISC		(8)
> 	#define MESON_PWM_REG_MISC_EN(chan)		BIT(chan)
> 	#define MESON_PWM_REG_MISC_CLK_SEL(chan)	GENMASK(5 + 2 * (chan), 4 + 2 * (chan))
> 	....
> 
> and then use these constants directly (with pwm->hwpwm as parameter if
> needed) in the code. I would expect this to result in more efficient and
> smaller code.

I've been looking into this driver for more than a year and got used to
it so much so never thought about changing the foundations :) Although 
it's an interesting thought.

1. I took meson_pwm_enable() without
const patches and reimplemented it using only defines (e.g. w/o local
var channel_data) and objdumped current and new versions. New version
turned out to be one instruction longer (arm64, gcc, default -O2). So 
total difference in executable code may be not that significant although
we can win in C-code line count.

2. Things like
#define MISC_B_EN		BIT(1)
#define MISC_A_EN		BIT(0)
is more straightforward and can be matched to the datasheet easier
comparing to (a + b * (chan)) things.

So I'm not sure either.

>> @@ -227,6 +252,15 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
>>   
>>   	value = readl(meson->base + REG_MISC_AB);
>>   	value |= channel_data->pwm_en_mask;
>> +
>> +	if (meson->data->has_constant)
>> +		meson_pwm_assign_bit(&value, channel_data->const_en_mask,
>> +				     channel->constant);
> 
> Personally I'd prefer:
> 
> 	value &= ~MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
> 	if (meson->data->has_constant && channel->constant)
> 		value |= MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
> 
> even though your variant only mentions the mask once. While it has this
> repetition, it's clear what happens without having to know what
> meson_pwm_assign_bit() does. Maybe that's subjective?
> 

Actually I also don't like meson_pwm_assign_bit() too match and I'm
surprised there's no something like this in the kernel already.
I again objdumped versions meson_pwm_assign_bit() vs double mask 
repetition. Unconditional bit clearing takes only a single instruction:

// value &= ~channel_data->const_en_mask;
9ac:	0a250040 	bic	w0, w2, w5

So in the current series I could drop meson_pwm_assign_bit() and use:

value &= ~channel_data->const_en_mask;
if (meson->data->has_constant && channel->constant)
	value |= channel_data->const_en_mask;

If it's decided now or later to drop meson_pwm_channel_data then
w\o meson_pwm_assign_bit() future patch will be line-to-line change.

What you think?

> Best regards
> Uwe
Uwe Kleine-König Nov. 7, 2024, 8:41 a.m. UTC | #3
On Wed, Nov 06, 2024 at 04:54:41PM +0300, George Stark wrote:
> On 11/4/24 12:32, Uwe Kleine-König wrote:
> > > @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data {
> > >   	u8		clk_div_shift;
> > >   	u8		clk_en_shift;
> > >   	u32		pwm_en_mask;
> > > +	u32		const_en_mask;
> > > +	u32		inv_en_mask;
> > >   } meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
> > >   	{
> > >   		.reg_offset	= REG_PWM_A,
> > > @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data {
> > >   		.clk_div_shift	= MISC_A_CLK_DIV_SHIFT,
> > >   		.clk_en_shift	= MISC_A_CLK_EN_SHIFT,
> > >   		.pwm_en_mask	= MISC_A_EN,
> > > +		.const_en_mask	= MISC_A_CONSTANT_EN,
> > > +		.inv_en_mask	= MISC_A_INVERT_EN,
> > >   	},
> > >   	{
> > >   		.reg_offset	= REG_PWM_B,
> > > @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data {
> > >   		.clk_div_shift	= MISC_B_CLK_DIV_SHIFT,
> > >   		.clk_en_shift	= MISC_B_CLK_EN_SHIFT,
> > >   		.pwm_en_mask	= MISC_B_EN,
> > > +		.const_en_mask	= MISC_B_CONSTANT_EN,
> > > +		.inv_en_mask	= MISC_B_INVERT_EN,
> > >   	}
> > >   };
> > 
> > So the generic register description describes the const and invert bits,
> > but it doesn't apply to all IPs. Thinking about that, I wonder why this
> > struct exists at all. I would have done this as follows:
> > 
> > 	#define MESON_PWM_REG_PWM(chan)		(0 + 4 * (chan))
> > 
> > 	#define MESON_PWM_REG_MISC		(8)
> > 	#define MESON_PWM_REG_MISC_EN(chan)		BIT(chan)
> > 	#define MESON_PWM_REG_MISC_CLK_SEL(chan)	GENMASK(5 + 2 * (chan), 4 + 2 * (chan))
> > 	....
> > 
> > and then use these constants directly (with pwm->hwpwm as parameter if
> > needed) in the code. I would expect this to result in more efficient and
> > smaller code.
> 
> I've been looking into this driver for more than a year and got used to
> it so much so never thought about changing the foundations :) Although it's
> an interesting thought.
> 
> 1. I took meson_pwm_enable() without
> const patches and reimplemented it using only defines (e.g. w/o local
> var channel_data) and objdumped current and new versions. New version
> turned out to be one instruction longer (arm64, gcc, default -O2). So total
> difference in executable code may be not that significant although
> we can win in C-code line count.

Oh, I indeed would have expected a slight advantage size-wise. Maybe the
compiler already optimizes out the meson_pwm_per_channel_data variable.

> 2. Things like
> #define MISC_B_EN		BIT(1)
> #define MISC_A_EN		BIT(0)
> is more straightforward and can be matched to the datasheet easier
> comparing to (a + b * (chan)) things.

In my (subjective) view that comparison isn't hard with the
parametrised definition.

> So I'm not sure either.
> 
> > > @@ -227,6 +252,15 @@ static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> > >   	value = readl(meson->base + REG_MISC_AB);
> > >   	value |= channel_data->pwm_en_mask;
> > > +
> > > +	if (meson->data->has_constant)
> > > +		meson_pwm_assign_bit(&value, channel_data->const_en_mask,
> > > +				     channel->constant);
> > 
> > Personally I'd prefer:
> > 
> > 	value &= ~MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
> > 	if (meson->data->has_constant && channel->constant)
> > 		value |= MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
> > 
> > even though your variant only mentions the mask once. While it has this
> > repetition, it's clear what happens without having to know what
> > meson_pwm_assign_bit() does. Maybe that's subjective?
> 
> Actually I also don't like meson_pwm_assign_bit() too match and I'm
> surprised there's no something like this in the kernel already.
> I again objdumped versions meson_pwm_assign_bit() vs double mask repetition.
> Unconditional bit clearing takes only a single instruction:
> 
> // value &= ~channel_data->const_en_mask;
> 9ac:	0a250040 	bic	w0, w2, w5
> 
> So in the current series I could drop meson_pwm_assign_bit() and use:
> 
> value &= ~channel_data->const_en_mask;
> if (meson->data->has_constant && channel->constant)
> 	value |= channel_data->const_en_mask;
> 
> If it's decided now or later to drop meson_pwm_channel_data then
> w\o meson_pwm_assign_bit() future patch will be line-to-line change.
> 
> What you think?

Sounds sensible.

Best regards
Uwe
George Stark Nov. 19, 2024, 12:51 p.m. UTC | #4
Hello Uwe

On 11/7/24 11:41, Uwe Kleine-König wrote:
> On Wed, Nov 06, 2024 at 04:54:41PM +0300, George Stark wrote:
>> On 11/4/24 12:32, Uwe Kleine-König wrote:
>>>> @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data {
>>>>    	u8		clk_div_shift;
>>>>    	u8		clk_en_shift;
>>>>    	u32		pwm_en_mask;
>>>> +	u32		const_en_mask;
>>>> +	u32		inv_en_mask;
>>>>    } meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
>>>>    	{
>>>>    		.reg_offset	= REG_PWM_A,
>>>> @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data {
>>>>    		.clk_div_shift	= MISC_A_CLK_DIV_SHIFT,
>>>>    		.clk_en_shift	= MISC_A_CLK_EN_SHIFT,
>>>>    		.pwm_en_mask	= MISC_A_EN,
>>>> +		.const_en_mask	= MISC_A_CONSTANT_EN,
>>>> +		.inv_en_mask	= MISC_A_INVERT_EN,
>>>>    	},
>>>>    	{
>>>>    		.reg_offset	= REG_PWM_B,
>>>> @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data {
>>>>    		.clk_div_shift	= MISC_B_CLK_DIV_SHIFT,
>>>>    		.clk_en_shift	= MISC_B_CLK_EN_SHIFT,
>>>>    		.pwm_en_mask	= MISC_B_EN,
>>>> +		.const_en_mask	= MISC_B_CONSTANT_EN,
>>>> +		.inv_en_mask	= MISC_B_INVERT_EN,
>>>>    	}
>>>>    };

...

>>> Personally I'd prefer:
>>>
>>> 	value &= ~MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
>>> 	if (meson->data->has_constant && channel->constant)
>>> 		value |= MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
>>>
>>> even though your variant only mentions the mask once. While it has this
>>> repetition, it's clear what happens without having to know what
>>> meson_pwm_assign_bit() does. Maybe that's subjective?
>>
>> Actually I also don't like meson_pwm_assign_bit() too match and I'm
>> surprised there's no something like this in the kernel already.
>> I again objdumped versions meson_pwm_assign_bit() vs double mask repetition.
>> Unconditional bit clearing takes only a single instruction:
>>
>> // value &= ~channel_data->const_en_mask;
>> 9ac:	0a250040 	bic	w0, w2, w5
>>
>> So in the current series I could drop meson_pwm_assign_bit() and use:
>>
>> value &= ~channel_data->const_en_mask;
>> if (meson->data->has_constant && channel->constant)
>> 	value |= channel_data->const_en_mask;
>>
>> If it's decided now or later to drop meson_pwm_channel_data then
>> w\o meson_pwm_assign_bit() future patch will be line-to-line change.
>>
>> What you think?
> 
> Sounds sensible.

While changing the patch to drop meson_pwm_assign_bit() one thing
concerned me on the approach:

value &= ~channel_data->const_en_mask;
if (meson->data->has_constant && channel->constant)
	value |= channel_data->const_en_mask;

that we reset bit in the value var even if that bit is not supported on
the current SoC. I checked several datasheets for old SoCs and those 
bits are marked as unused (not even reserved) and I've never seen those
bits set. Still I'd offer to use precise condition for changing those 
bit. I'll send v3 let's discuss it again if you think I bother too much.

> 
> Best regards
> Uwe
Uwe Kleine-König Nov. 19, 2024, 3:19 p.m. UTC | #5
Hello George,

On Tue, Nov 19, 2024 at 03:51:34PM +0300, George Stark wrote:
> On 11/7/24 11:41, Uwe Kleine-König wrote:
> > On Wed, Nov 06, 2024 at 04:54:41PM +0300, George Stark wrote:
> > > On 11/4/24 12:32, Uwe Kleine-König wrote:
> > > > > @@ -68,6 +72,8 @@ static struct meson_pwm_channel_data {
> > > > >    	u8		clk_div_shift;
> > > > >    	u8		clk_en_shift;
> > > > >    	u32		pwm_en_mask;
> > > > > +	u32		const_en_mask;
> > > > > +	u32		inv_en_mask;
> > > > >    } meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
> > > > >    	{
> > > > >    		.reg_offset	= REG_PWM_A,
> > > > > @@ -75,6 +81,8 @@ static struct meson_pwm_channel_data {
> > > > >    		.clk_div_shift	= MISC_A_CLK_DIV_SHIFT,
> > > > >    		.clk_en_shift	= MISC_A_CLK_EN_SHIFT,
> > > > >    		.pwm_en_mask	= MISC_A_EN,
> > > > > +		.const_en_mask	= MISC_A_CONSTANT_EN,
> > > > > +		.inv_en_mask	= MISC_A_INVERT_EN,
> > > > >    	},
> > > > >    	{
> > > > >    		.reg_offset	= REG_PWM_B,
> > > > > @@ -82,6 +90,8 @@ static struct meson_pwm_channel_data {
> > > > >    		.clk_div_shift	= MISC_B_CLK_DIV_SHIFT,
> > > > >    		.clk_en_shift	= MISC_B_CLK_EN_SHIFT,
> > > > >    		.pwm_en_mask	= MISC_B_EN,
> > > > > +		.const_en_mask	= MISC_B_CONSTANT_EN,
> > > > > +		.inv_en_mask	= MISC_B_INVERT_EN,
> > > > >    	}
> > > > >    };
> 
> ...
> 
> > > > Personally I'd prefer:
> > > > 
> > > > 	value &= ~MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
> > > > 	if (meson->data->has_constant && channel->constant)
> > > > 		value |= MESON_PWM_REG_MISC_CONST_EN(pwm->hwpwm);
> > > > 
> > > > even though your variant only mentions the mask once. While it has this
> > > > repetition, it's clear what happens without having to know what
> > > > meson_pwm_assign_bit() does. Maybe that's subjective?
> > > 
> > > Actually I also don't like meson_pwm_assign_bit() too match and I'm
> > > surprised there's no something like this in the kernel already.
> > > I again objdumped versions meson_pwm_assign_bit() vs double mask repetition.
> > > Unconditional bit clearing takes only a single instruction:
> > > 
> > > // value &= ~channel_data->const_en_mask;
> > > 9ac:	0a250040 	bic	w0, w2, w5
> > > 
> > > So in the current series I could drop meson_pwm_assign_bit() and use:
> > > 
> > > value &= ~channel_data->const_en_mask;
> > > if (meson->data->has_constant && channel->constant)
> > > 	value |= channel_data->const_en_mask;
> > > 
> > > If it's decided now or later to drop meson_pwm_channel_data then
> > > w\o meson_pwm_assign_bit() future patch will be line-to-line change.
> > > 
> > > What you think?
> > 
> > Sounds sensible.
> 
> While changing the patch to drop meson_pwm_assign_bit() one thing
> concerned me on the approach:
> 
> value &= ~channel_data->const_en_mask;
> if (meson->data->has_constant && channel->constant)
> 	value |= channel_data->const_en_mask;
> 
> that we reset bit in the value var even if that bit is not supported on
> the current SoC. I checked several datasheets for old SoCs and those bits
> are marked as unused (not even reserved) and I've never seen those
> bits set. Still I'd offer to use precise condition for changing those bit.
> I'll send v3 let's discuss it again if you think I bother too much.

Usually writing zeros to unused bits is a safe procedure. If the
hardware we're talking to is a newer variant of the supported stuff, the
hardware engineer did something wrong if keeping the read bits or
writing them as zero is incompatible. So either way is fine for me.

Best regards
Uwe
diff mbox series

Patch

diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c
index 2ef632caebcc..974c3c74768c 100644
--- a/drivers/pwm/pwm-meson.c
+++ b/drivers/pwm/pwm-meson.c
@@ -6,7 +6,7 @@ 
  * PWM output is achieved by calculating a clock that permits calculating
  * two periods (low and high). The counter then has to be set to switch after
  * N cycles for the first half period.
- * The hardware has no "polarity" setting. This driver reverses the period
+ * Partly the hardware has no "polarity" setting. This driver reverses the period
  * cycles (the low length is inverted with the high length) for
  * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity
  * from the hardware.
@@ -56,6 +56,10 @@ 
 #define MISC_B_CLK_SEL_SHIFT	6
 #define MISC_A_CLK_SEL_SHIFT	4
 #define MISC_CLK_SEL_MASK	0x3
+#define MISC_B_CONSTANT_EN	BIT(29)
+#define MISC_A_CONSTANT_EN	BIT(28)
+#define MISC_B_INVERT_EN	BIT(27)
+#define MISC_A_INVERT_EN	BIT(26)
 #define MISC_B_EN		BIT(1)
 #define MISC_A_EN		BIT(0)
 
@@ -68,6 +72,8 @@  static struct meson_pwm_channel_data {
 	u8		clk_div_shift;
 	u8		clk_en_shift;
 	u32		pwm_en_mask;
+	u32		const_en_mask;
+	u32		inv_en_mask;
 } meson_pwm_per_channel_data[MESON_NUM_PWMS] = {
 	{
 		.reg_offset	= REG_PWM_A,
@@ -75,6 +81,8 @@  static struct meson_pwm_channel_data {
 		.clk_div_shift	= MISC_A_CLK_DIV_SHIFT,
 		.clk_en_shift	= MISC_A_CLK_EN_SHIFT,
 		.pwm_en_mask	= MISC_A_EN,
+		.const_en_mask	= MISC_A_CONSTANT_EN,
+		.inv_en_mask	= MISC_A_INVERT_EN,
 	},
 	{
 		.reg_offset	= REG_PWM_B,
@@ -82,6 +90,8 @@  static struct meson_pwm_channel_data {
 		.clk_div_shift	= MISC_B_CLK_DIV_SHIFT,
 		.clk_en_shift	= MISC_B_CLK_EN_SHIFT,
 		.pwm_en_mask	= MISC_B_EN,
+		.const_en_mask	= MISC_B_CONSTANT_EN,
+		.inv_en_mask	= MISC_B_INVERT_EN,
 	}
 };
 
@@ -89,6 +99,8 @@  struct meson_pwm_channel {
 	unsigned long rate;
 	unsigned int hi;
 	unsigned int lo;
+	bool constant;
+	bool inverted;
 
 	struct clk_mux mux;
 	struct clk_divider div;
@@ -99,6 +111,8 @@  struct meson_pwm_channel {
 struct meson_pwm_data {
 	const char *const parent_names[MESON_NUM_MUX_PARENTS];
 	int (*channels_init)(struct pwm_chip *chip);
+	bool has_constant;
+	bool has_polarity;
 };
 
 struct meson_pwm {
@@ -160,7 +174,7 @@  static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
 	 * Fixing this needs some care however as some machines might rely on
 	 * this.
 	 */
-	if (state->polarity == PWM_POLARITY_INVERSED)
+	if (state->polarity == PWM_POLARITY_INVERSED && !meson->data->has_polarity)
 		duty = period - duty;
 
 	freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period);
@@ -187,9 +201,11 @@  static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
 	if (duty == period) {
 		channel->hi = cnt;
 		channel->lo = 0;
+		channel->constant = true;
 	} else if (duty == 0) {
 		channel->hi = 0;
 		channel->lo = cnt;
+		channel->constant = true;
 	} else {
 		duty_cnt = mul_u64_u64_div_u64(fin_freq, duty, NSEC_PER_SEC);
 
@@ -197,6 +213,7 @@  static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
 
 		channel->hi = duty_cnt;
 		channel->lo = cnt - duty_cnt;
+		channel->constant = false;
 	}
 
 	channel->rate = fin_freq;
@@ -204,6 +221,14 @@  static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
 	return 0;
 }
 
+static inline void meson_pwm_assign_bit(u32 *data, u32 mask, bool value)
+{
+	if (value)
+		*data |= mask;
+	else
+		*data &= ~mask;
+}
+
 static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct meson_pwm *meson = to_meson_pwm(chip);
@@ -227,6 +252,15 @@  static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 
 	value = readl(meson->base + REG_MISC_AB);
 	value |= channel_data->pwm_en_mask;
+
+	if (meson->data->has_constant)
+		meson_pwm_assign_bit(&value, channel_data->const_en_mask,
+				     channel->constant);
+
+	if (meson->data->has_polarity)
+		meson_pwm_assign_bit(&value, channel_data->inv_en_mask,
+				     channel->inverted);
+
 	writel(value, meson->base + REG_MISC_AB);
 
 	spin_unlock_irqrestore(&meson->lock, flags);
@@ -235,13 +269,22 @@  static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct meson_pwm *meson = to_meson_pwm(chip);
+	struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
+	struct meson_pwm_channel_data *channel_data;
 	unsigned long flags;
 	u32 value;
 
+	channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
+
 	spin_lock_irqsave(&meson->lock, flags);
 
 	value = readl(meson->base + REG_MISC_AB);
-	value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask;
+	value &= ~channel_data->pwm_en_mask;
+
+	if (meson->data->has_polarity)
+		meson_pwm_assign_bit(&value, channel_data->inv_en_mask,
+				     channel->inverted);
+
 	writel(value, meson->base + REG_MISC_AB);
 
 	spin_unlock_irqrestore(&meson->lock, flags);
@@ -254,10 +297,12 @@  static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
 	int err = 0;
 
+	channel->inverted = (state->polarity == PWM_POLARITY_INVERSED);
+
 	if (!state->enabled) {
-		if (state->polarity == PWM_POLARITY_INVERSED) {
+		if (channel->inverted && !meson->data->has_polarity) {
 			/*
-			 * This IP block revision doesn't have an "always high"
+			 * Some of IP block revisions don't have an "always high"
 			 * setting which we can use for "inverted disabled".
 			 * Instead we achieve this by setting mux parent with
 			 * highest rate and minimum divider value, resulting
@@ -271,6 +316,7 @@  static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 			channel->rate = ULONG_MAX;
 			channel->hi = ~0;
 			channel->lo = 0;
+			channel->constant = true;
 
 			meson_pwm_enable(chip, pwm);
 		} else {
@@ -319,6 +365,11 @@  static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
 	value = readl(meson->base + REG_MISC_AB);
 	state->enabled = value & channel_data->pwm_en_mask;
 
+	if (meson->data->has_polarity && (value & channel_data->inv_en_mask))
+		state->polarity = PWM_POLARITY_INVERSED;
+	else
+		state->polarity = PWM_POLARITY_NORMAL;
+
 	value = readl(meson->base + channel_data->reg_offset);
 	lo = FIELD_GET(PWM_LOW_MASK, value);
 	hi = FIELD_GET(PWM_HIGH_MASK, value);
@@ -326,8 +377,6 @@  static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
 	state->period = meson_pwm_cnt_to_ns(chip, pwm, lo + hi);
 	state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, hi);
 
-	state->polarity = PWM_POLARITY_NORMAL;
-
 	return 0;
 }