[PATCH] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()

Rafael V. Volkmer posted 1 patch 10 months, 1 week ago
drivers/pwm/pwm-tiehrpwm.c | 151 ++++++++++++++++++++++++++++++++++++-
1 file changed, 150 insertions(+), 1 deletion(-)
[PATCH] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Posted by Rafael V. Volkmer 10 months, 1 week ago
Fixes potential desynchronization of state.enabled in the .probe()
method by suggesting proper handling of hardware state initialization.
Adds considerations for implementing .get_hw_state() to check the
current state of the module by checking physical registers.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 151 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 150 insertions(+), 1 deletion(-)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 0125e73b98df..5de213bc3ef5 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -91,6 +91,20 @@
 #define AQCSFRC_CSFA_FRCHIGH	BIT(1)
 #define AQCSFRC_CSFA_DISSWFRC	(BIT(1) | BIT(0))
 
+#define AQCTLA_CAU_MASK   (BIT(5) | BIT(4))
+#define AQCTLA_CAU_SHIFT  4
+#define AQCTLA_CAD_MASK   (BIT(9) | BIT(8))
+#define AQCTLA_CAD_SHIFT  8
+
+/* The ePWM hardware encodes compare actions with two bits each:
+ *   00 = Do nothing
+ *   01 = Clear
+ *   10 = Set
+ *   11 = Toggle
+ */
+#define AQ_CLEAR  1
+#define AQ_SET    2
+
 #define NUM_PWM_CHANNEL		2	/* EHRPWM channels */
 
 struct ehrpwm_context {
@@ -353,6 +367,118 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 	return 0;
 }
 
+static bool ehrpwm_is_enabled(struct pwm_chip *chip)
+{
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+	bool ret;
+	u16 aqcsfrc_reg;
+	u8 csfa_bits;
+	u16 aqctla_reg;
+
+	if(chip == NULL) {
+		return -EINVAL;
+	}
+
+	aqcsfrc_reg	= readw(pc->mmio_base + AQCSFRC);
+	csfa_bits	= (u8)(aqcsfrc_reg & AQCSFRC_CSFA_MASK);
+	
+	aqctla_reg	= readw(pc->mmio_base + AQCTLA);
+
+	ret = (csfa_bits != 0u)	 ? false :
+	      (aqctla_reg == 0u) ? false : true;
+
+	return ret;
+}
+
+static u64 ehrpwm_read_period(struct pwm_chip *chip)
+{
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+	u64 ret;
+	unsigned long tbclk_rate;
+	u16 tbprd_reg;
+	u64 period_cycles;
+	u64 period_ns;
+
+	if(chip == NULL) {
+		return -EINVAL;
+	}
+
+	tbprd_reg = readw(pc->mmio_base + TBPRD);
+	tbclk_rate = clk_get_rate(pc->tbclk);
+	period_cycles = tbprd_reg + 1u;
+	
+	/* period_ns = (period_cycles * 1e9) / tblck_rate */
+	period_ns = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, tbclk_rate);
+	
+	ret = period_ns;
+	return ret;
+}
+
+static u64 ehrpwm_read_duty_cycle(struct pwm_chip *chip)
+{
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+	u64 ret;
+	u16 cmpa_reg;
+	u64 duty_cycles;
+	u64 duty_ns;
+	unsigned long tbclk_rate;
+
+	if(chip == NULL) {
+		return -EINVAL;
+	}
+
+	cmpa_reg = readw(pc->mmio_base + CMPA);
+	tbclk_rate = clk_get_rate(pc->tbclk);
+	duty_cycles = cmpa_reg;
+	duty_ns = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, tbclk_rate);
+	ret = duty_ns;
+
+	return ret;
+}
+
+static enum pwm_polarity ehrpwm_read_polarity(struct pwm_chip *chip)
+{
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+	enum pwm_polarity ret;
+	u16 aqctla_reg;
+	u8 cau_action;
+	u8 cad_action;
+
+	if(chip == NULL) {
+		return -EINVAL;
+	}
+
+	aqctla_reg	= readw(pc->mmio_base + AQCTLA);
+	cau_action = (aqctla_reg & AQCTLA_CAU_MASK) >> AQCTLA_CAU_SHIFT;
+	cad_action = (aqctla_reg & AQCTLA_CAD_MASK) >> AQCTLA_CAD_SHIFT;
+	
+	if (cau_action == AQ_SET && cad_action == AQ_CLEAR) {
+		ret = PWM_POLARITY_NORMAL;
+	}
+	else if (cau_action == AQ_CLEAR && cad_action == AQ_SET) {
+		ret = PWM_POLARITY_INVERSED;
+	}
+
+	return ret;
+}
+
+static int ehrpwm_get_hw_state(struct pwm_chip *chip, struct pwm_device *pwm, 
+								struct pwm_state *state)
+{
+	int ret;
+
+	if(chip == NULL || pwm == NULL || state == NULL){
+		return -EINVAL;
+	}
+
+	state->enabled = ehrpwm_is_enabled(chip);
+	state->period = ehrpwm_read_period(chip);
+    state->duty_cycle = ehrpwm_read_duty_cycle(chip);
+    state->polarity = ehrpwm_read_polarity(chip);
+
+	return ret;
+}
+
 static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
@@ -449,8 +575,10 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
 	struct ehrpwm_pwm_chip *pc;
+	struct pwm_state state;
 	struct pwm_chip *chip;
 	struct clk *clk;
+	bool tbclk_enabled;
 	int ret;
 
 	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
@@ -501,10 +629,31 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, chip);
 	pm_runtime_enable(&pdev->dev);
 
+	ehrpwm_get_hw_state(chip, &chip->pwms[0], &state);
+	if(state.enabled == true) {
+		ret = clk_prepare_enable(pc->tbclk);
+		if (ret) {     
+			dev_err(&pdev->dev, "clk_prepare_enable() failed: %d\n", ret);
+			goto err_pwmchip_remove;
+		}
+            
+		tbclk_enabled = true;
+		ret = pm_runtime_get_sync(&pdev->dev);
+		if(ret < 0) {
+			dev_err(&pdev->dev, "pm_runtime_get_sync() failed: %d\n", ret);
+			clk_disable_unprepare(pc->tbclk);
+ 			goto err_pwmchip_remove;
+		}
+ 	}
+
 	return 0;
 
+err_pwmchip_remove:
+	pwmchip_remove(chip);
+
 err_clk_unprepare:
-	clk_unprepare(pc->tbclk);
+	if(tbclk_enabled)
+		clk_unprepare(pc->tbclk);
 
 	return ret;
 }
-- 
2.25.1
Re: [PATCH] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Posted by Uwe Kleine-König 10 months, 1 week ago
Hello Rafael,

first of all: There are several issues regarding code style, checkpatch
reports

	total: 20 errors, 13 warnings, 180 lines checked

Please fix them all for v2 (or mention unfixed ones in the cover text,
ideally with a reasoning). (In case you don't know:

	scripts/checkpatch.pl -g @

; for bonus points add --strict.)

On Tue, Feb 04, 2025 at 03:55:40PM -0300, Rafael V. Volkmer wrote:
> Fixes potential desynchronization of state.enabled in the .probe()
> method by suggesting proper handling of hardware state initialization.
> Adds considerations for implementing .get_hw_state() to check the
> current state of the module by checking physical registers.

I don't understand that. What is .get_hw_state()?

Returning here after reading through the complete patch, I suggest
something like:

	If the hardware is already active during probe, it's not
	asserted that the clock is enabled. So check in
	ehrpwm_pwm_probe() if the enable bit is set and call
	clk_enable() and pm_runtime_get_sync() if yes.

> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
> ---
>  drivers/pwm/pwm-tiehrpwm.c | 151 ++++++++++++++++++++++++++++++++++++-
>  1 file changed, 150 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
> index 0125e73b98df..5de213bc3ef5 100644
> --- a/drivers/pwm/pwm-tiehrpwm.c
> +++ b/drivers/pwm/pwm-tiehrpwm.c
> @@ -91,6 +91,20 @@
>  #define AQCSFRC_CSFA_FRCHIGH	BIT(1)
>  #define AQCSFRC_CSFA_DISSWFRC	(BIT(1) | BIT(0))
>  
> +#define AQCTLA_CAU_MASK   (BIT(5) | BIT(4))
> +#define AQCTLA_CAU_SHIFT  4
> +#define AQCTLA_CAD_MASK   (BIT(9) | BIT(8))
> +#define AQCTLA_CAD_SHIFT  8

Please use

	#define AQCTLA_CAD	GENMASK(9, 8)

and for the usage then instead of

	cad_action = (aqctla_reg & AQCTLA_CAD_MASK) >> AQCTLA_CAD_SHIFT;

better:

	cad_action = FIELD_GET(AQCTLA_CAD, aqctla_reg);

> +
> +/* The ePWM hardware encodes compare actions with two bits each:
> + *   00 = Do nothing
> + *   01 = Clear
> + *   10 = Set
> + *   11 = Toggle
> + */
> +#define AQ_CLEAR  1
> +#define AQ_SET    2
> +
>  #define NUM_PWM_CHANNEL		2	/* EHRPWM channels */
>  
>  struct ehrpwm_context {
> @@ -353,6 +367,118 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
>  	return 0;
>  }
>  
> +static bool ehrpwm_is_enabled(struct pwm_chip *chip)
> +{
> +	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
> +	bool ret;
> +	u16 aqcsfrc_reg;
> +	u8 csfa_bits;
> +	u16 aqctla_reg;
> +
> +	if(chip == NULL) {
> +		return -EINVAL;
> +	}

return -EINVAL is bogus for a function returning bool. The solution is
easy: chip is never NULL here, so please drop. 

> +
> +	aqcsfrc_reg	= readw(pc->mmio_base + AQCSFRC);
> +	csfa_bits	= (u8)(aqcsfrc_reg & AQCSFRC_CSFA_MASK);

Single space before = please.

> +	aqctla_reg	= readw(pc->mmio_base + AQCTLA);
> +
> +	ret = (csfa_bits != 0u)	 ? false :
> +	      (aqctla_reg == 0u) ? false : true;

I didn't understand what these values mean, but this is unreadable. I
prefer:

	if (csfa_bits)
		return false;

	if (aqctla_reg)
		return true;

	return false;

Maybe also add code comments about the semantic to the respective
blocks.

> +
> +	return ret;
> +}
> +
> +static u64 ehrpwm_read_period(struct pwm_chip *chip)
> +{
> +	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
> +	u64 ret;
> +	unsigned long tbclk_rate;
> +	u16 tbprd_reg;
> +	u64 period_cycles;
> +	u64 period_ns;
> +
> +	if(chip == NULL) {
> +		return -EINVAL;
> +	}
> +
> +	tbprd_reg = readw(pc->mmio_base + TBPRD);
> +	tbclk_rate = clk_get_rate(pc->tbclk);
> +	period_cycles = tbprd_reg + 1u;
> +	
> +	/* period_ns = (period_cycles * 1e9) / tblck_rate */
> +	period_ns = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, tbclk_rate);
> +	
> +	ret = period_ns;
> +	return ret;

return period_ns;

Given that ehrpwm_read_period() and ehrpwm_read_duty_cycle() are called
directly after another, I suggest to rework the code that clk_get_rate()
is only called once. (Maybe even put it into ehrpwm_pwm_chip.)

> +}
> +
> +static u64 ehrpwm_read_duty_cycle(struct pwm_chip *chip)
> +{
> +	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
> +	u64 ret;
> +	u16 cmpa_reg;
> +	u64 duty_cycles;
> +	u64 duty_ns;
> +	unsigned long tbclk_rate;
> +
> +	if(chip == NULL) {
> +		return -EINVAL;
> +	}
> +
> +	cmpa_reg = readw(pc->mmio_base + CMPA);
> +	tbclk_rate = clk_get_rate(pc->tbclk);
> +	duty_cycles = cmpa_reg;
> +	duty_ns = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, tbclk_rate);
> +	ret = duty_ns;
> +
> +	return ret;
> +}
> +
> +static enum pwm_polarity ehrpwm_read_polarity(struct pwm_chip *chip)
> +{
> +	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
> +	enum pwm_polarity ret;
> +	u16 aqctla_reg;
> +	u8 cau_action;
> +	u8 cad_action;
> +
> +	if(chip == NULL) {
> +		return -EINVAL;
> +	}
> +
> +	aqctla_reg	= readw(pc->mmio_base + AQCTLA);
> +	cau_action = (aqctla_reg & AQCTLA_CAU_MASK) >> AQCTLA_CAU_SHIFT;
> +	cad_action = (aqctla_reg & AQCTLA_CAD_MASK) >> AQCTLA_CAD_SHIFT;
> +	
> +	if (cau_action == AQ_SET && cad_action == AQ_CLEAR) {
> +		ret = PWM_POLARITY_NORMAL;
> +	}
> +	else if (cau_action == AQ_CLEAR && cad_action == AQ_SET) {
> +		ret = PWM_POLARITY_INVERSED;
> +	}

I would expect the compiler to issue a warning here about ret being
uninitialized in some situations.

Not sure checkpatch points that out: } and "else" go on the same line.

> +
> +	return ret;
> +}
> +
> +static int ehrpwm_get_hw_state(struct pwm_chip *chip, struct pwm_device *pwm, 
> +								struct pwm_state *state)

The canonical name for this function is ehrpwm_pwm_get_state. And I
would have expected an assignment to .get_state in ehrpwm_pwm_ops.

> +{
> +	int ret;
> +
> +	if(chip == NULL || pwm == NULL || state == NULL){
> +		return -EINVAL;
> +	}
> +
> +	state->enabled = ehrpwm_is_enabled(chip);
> +	state->period = ehrpwm_read_period(chip);
> +    state->duty_cycle = ehrpwm_read_duty_cycle(chip);
> +    state->polarity = ehrpwm_read_polarity(chip);
> +
> +	return ret;
> +}
> +
>  static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
>  {
>  	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
> @@ -449,8 +575,10 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  {
>  	struct device_node *np = pdev->dev.of_node;
>  	struct ehrpwm_pwm_chip *pc;
> +	struct pwm_state state;
>  	struct pwm_chip *chip;
>  	struct clk *clk;
> +	bool tbclk_enabled;
>  	int ret;
>  
>  	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
> @@ -501,10 +629,31 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  	platform_set_drvdata(pdev, chip);
>  	pm_runtime_enable(&pdev->dev);
>  
> +	ehrpwm_get_hw_state(chip, &chip->pwms[0], &state);
> +	if(state.enabled == true) {

Given that you only use state.enabled here, maybe shorten that to

	if (ehrpwm_is_enabled(...))

> +		ret = clk_prepare_enable(pc->tbclk);

While it's a good idea to enable the clock if the PWM is already running
at probe time, this must happen before pwmchip_add().

> +		if (ret) {     
> +			dev_err(&pdev->dev, "clk_prepare_enable() failed: %d\n", ret);

Please use dev_err_probe() here.

> +			goto err_pwmchip_remove;
> +		}
> +            
> +		tbclk_enabled = true;
> +		ret = pm_runtime_get_sync(&pdev->dev);
> +		if(ret < 0) {
> +			dev_err(&pdev->dev, "pm_runtime_get_sync() failed: %d\n", ret);
> +			clk_disable_unprepare(pc->tbclk);
> + 			goto err_pwmchip_remove;
> +		}
> + 	}
> +
>  	return 0;
>  
> +err_pwmchip_remove:
> +	pwmchip_remove(chip);
> +
>  err_clk_unprepare:
> -	clk_unprepare(pc->tbclk);
> +	if(tbclk_enabled)
> +		clk_unprepare(pc->tbclk);
>  
>  	return ret;
>  }

Best regards
Uwe
[PATCH v2 0/1] pwm: tiehrpwm: ensures that state.enabled is
Posted by Rafael V. Volkmer 10 months, 1 week ago
Hi Uwe.

Thanks for the feedback! It was very constructive for improving the code!

I fixed the points you mentioned and hope you can review v2 of my patch!

Changes in v2:
- Adjusted the indentation reported by checkpatch.pl;
- Added the bitfield header to replace the register bit defines with GEN_MASK 
and FIELD_GET;
- Concentrated the use of clk_get_rate() only once, inside ehrpwm_get_state();
- Added the ehrpwm_get_state() function as part of the driver's fops, directly 
linked to .get_state;
- Now, the activation of clk_prepare_enable() when the pwm is already running 
is done before pwmchip_add(), and runtime_get_sync() after;
- Added comments for each new function;
- Added comments in specific areas of register manipulation, in order to make 
understanding how they work more intuitive;
- Removed unnecessary null pointer checks in functions and also improper 
returns; 
- I improved the commit message, as suggested.

Rafael V. Volkmer (1):
  pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()

 drivers/pwm/pwm-tiehrpwm.c | 202 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 201 insertions(+), 1 deletion(-)

-- 
2.25.1
[PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Posted by Rafael V. Volkmer 10 months, 1 week ago
If the hardware is already active during probe, it's not asserted
that the clock is enabled. To address this, added ehrpwm_get_state()
to verify the enable bit and update the pwm_state struct with the
correct hardware parameters.

In ehrpwm_pwm_probe(), the function checks if the enable bit is set
and calls clk_enable() and pm_runtime_get_sync() if needed.

Additionally, ehrpwm_get_state() was integrated as .get_state in
the ehrpwm_pwm_ops struct, incorporating it into the driver's pwm_ops
for consistent state management.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 202 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 201 insertions(+), 1 deletion(-)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 0125e73b98df..cbddbbd48a75 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -13,6 +13,7 @@
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
+#include <linux/bitfield.h>
 
 /* EHRPWM registers and bits definitions */
 
@@ -91,6 +92,19 @@
 #define AQCSFRC_CSFA_FRCHIGH	BIT(1)
 #define AQCSFRC_CSFA_DISSWFRC	(BIT(1) | BIT(0))
 
+#define AQCTLA_CAU      GENMASK(5, 4)
+#define AQCTLA_CAD      GENMASK(9, 8)
+
+/**
+ * The ePWM hardware encodes compare actions with two bits each:
+ *   00 = Do nothing
+ *   01 = Clear
+ *   10 = Set
+ *   11 = Toggle
+ */
+#define AQ_CLEAR  1
+#define AQ_SET    2
+
 #define NUM_PWM_CHANNEL		2	/* EHRPWM channels */
 
 struct ehrpwm_context {
@@ -353,6 +367,165 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 	return 0;
 }
 
+/**
+ * ehrpwm_is_enabled - Checks if the eHRPWM channel is enabled
+ * @chip:	pointer to the PWM chip structure
+ *
+ * @return:	true if the channel is enabled, false otherwise
+ */
+static bool ehrpwm_is_enabled(struct pwm_chip *chip)
+{
+	bool ret = false;
+
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
+	u16 aqcsfrc_reg = 0u;
+	u8 csfa_bits = 0u;
+	u16 aqctla_reg = 0u;
+
+	aqcsfrc_reg	= readw(pc->mmio_base + AQCSFRC);
+	csfa_bits = (u8)(aqcsfrc_reg & AQCSFRC_CSFA_MASK);
+	aqctla_reg = readw(pc->mmio_base + AQCTLA);
+	/*
+	 * If the CSFA value is non-zero,
+	 * it means channel A is being forced via software
+	 * (override), so we consider the PWM "disabled".
+	 */
+	if (csfa_bits)
+		ret = false;
+
+	/*
+	 * If the control register (AQCTLA) is configured
+	 * (non-zero), it means channel A has qualified actions
+	 * and is therefore enabled to generate PWM.
+	 */
+	if (aqctla_reg)
+		ret = true;
+
+	return ret;
+}
+
+/**
+ * ehrpwm_read_period - Reads the period of the eHRPWM channel (in ns)
+ * @chip:		pointer to the PWM chip structure
+ * @tbclk_rate:	time-base clock rate in Hz
+ *
+ * @return:		period in nanoseconds
+ */
+static u64 ehrpwm_read_period(struct pwm_chip *chip, unsigned long tbclk_rate)
+{
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
+	u16 tbprd_reg = 0u;
+	u64 period_cycles = 0u;
+	u64 period_ns = 0u;
+
+	tbprd_reg = readw(pc->mmio_base + TBPRD);
+	period_cycles = tbprd_reg + 1u;
+
+	/*
+	 * period_ns = (period_cycles * 1e9) / tbclk_rate
+	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
+	 */
+	period_ns = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, tbclk_rate);
+
+	return period_ns;
+}
+
+/**
+ * ehrpwm_read_duty_cycle - Reads the duty cycle of the eHRPWM channel (in ns)
+ * @chip:		pointer to the PWM chip structure
+ * @tbclk_rate:	time-base clock rate in Hz
+ *
+ * @return:		duty cycle in nanoseconds
+ */
+static u64 ehrpwm_read_duty_cycle(struct pwm_chip *chip, unsigned long tbclk_rate)
+{
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
+	u16 cmpa_reg = 0u;
+	u64 duty_cycles = 0u;
+	u64 duty_ns = 0u;
+
+	cmpa_reg = readw(pc->mmio_base + CMPA);
+
+	duty_cycles = cmpa_reg;
+
+	/*
+	 * duty_ns = (duty_cycles * 1e9) / tbclk_rate
+	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
+	 */
+	duty_ns = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, tbclk_rate);
+
+	return duty_ns;
+}
+
+/**
+ * ehrpwm_read_polarity - Reads the polarity of the eHRPWM channel
+ * @chip:	pointer to the PWM chip structure
+ *
+ * @return:	the polarity of the PWM (PWM_POLARITY_NORMAL or PWM_POLARITY_INVERSED)
+ */
+static enum pwm_polarity ehrpwm_read_polarity(struct pwm_chip *chip)
+{
+	enum pwm_polarity ret = PWM_POLARITY_NORMAL;
+
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+
+	u16 aqctla_reg = 0u;
+	u8 cau_action = 0u;
+	u8 cad_action = 0u;
+
+	aqctla_reg = readw(pc->mmio_base + AQCTLA);
+	cau_action = FIELD_GET(AQCTLA_CAU, aqctla_reg);
+	cad_action = FIELD_GET(AQCTLA_CAD, aqctla_reg);
+
+	/*
+	 * Evaluate the actions to determine the PWM polarity:
+	 *  - If an up-count event sets the output (AQ_SET) and a down-count
+	 *    event clears it (AQ_CLEAR), then polarity is NORMAL.
+	 *  - If an up-count event clears the output (AQ_CLEAR) and a down-count
+	 *    event sets it (AQ_SET), then polarity is INVERSED.
+	 */
+	if (cau_action == AQ_SET && cad_action == AQ_CLEAR)
+		ret = PWM_POLARITY_NORMAL;
+
+	else if (cau_action == AQ_CLEAR && cad_action == AQ_SET)
+		ret = PWM_POLARITY_INVERSED;
+
+	return ret;
+}
+
+/**
+ * ehrpwm_get_state - Retrieves the current state of the eHRPWM channel
+ * @chip:	pointer to the PWM chip structure
+ * @pwm:	pointer to the PWM device structure
+ * @state:	pointer to the pwm_state structure to be filled
+ *
+ * @return:	0 on success or a negative error code on failure
+ */
+static int ehrpwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+							struct pwm_state *state)
+{
+	int ret = 0u;
+	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
+	unsigned long tbclk_rate = 0u;
+
+	if (chip == NULL || pwm == NULL || state == NULL)
+		return -EINVAL;
+
+	tbclk_rate = clk_get_rate(pc->tbclk);
+	if (tbclk_rate <= 0)
+		return -EINVAL;
+
+	state->enabled = ehrpwm_is_enabled(chip);
+	state->period = ehrpwm_read_period(chip, tbclk_rate);
+	state->duty_cycle = ehrpwm_read_duty_cycle(chip, tbclk_rate);
+	state->polarity = ehrpwm_read_polarity(chip);
+
+	return ret;
+}
+
 static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
@@ -436,6 +609,7 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 static const struct pwm_ops ehrpwm_pwm_ops = {
 	.free = ehrpwm_pwm_free,
 	.apply = ehrpwm_pwm_apply,
+	.get_state = ehrpwm_get_state,
 };
 
 static const struct of_device_id ehrpwm_of_match[] = {
@@ -449,8 +623,10 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
 	struct ehrpwm_pwm_chip *pc;
+	struct pwm_state state;
 	struct pwm_chip *chip;
 	struct clk *clk;
+	bool tbclk_enabled;
 	int ret;
 
 	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
@@ -492,6 +668,18 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	ehrpwm_get_state(chip, &chip->pwms[0], &state);
+
+	if (state.enabled == true) {
+		ret = clk_prepare_enable(pc->tbclk);
+		if (ret) {
+			dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
+			goto err_pwmchip_remove;
+		}
+
+		tbclk_enabled = true;
+	}
+
 	ret = pwmchip_add(chip);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
@@ -501,10 +689,22 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, chip);
 	pm_runtime_enable(&pdev->dev);
 
+	if (state.enabled == true) {
+		ret = pm_runtime_get_sync(&pdev->dev);
+		if (ret < 0) {
+			dev_err_probe(&pdev->dev, ret, "pm_runtime_get_sync() failed");
+			clk_disable_unprepare(pc->tbclk);
+			goto err_pwmchip_remove;
+		}
+	}
+
 	return 0;
 
+err_pwmchip_remove:
+	pwmchip_remove(chip);
 err_clk_unprepare:
-	clk_unprepare(pc->tbclk);
+	if (tbclk_enabled)
+		clk_unprepare(pc->tbclk);
 
 	return ret;
 }
-- 
2.25.1
Re: [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Posted by kernel test robot 7 months, 4 weeks ago
Hi Rafael,

kernel test robot noticed the following build warnings:

[auto build test WARNING on linus/master]
[also build test WARNING on v6.15-rc2 next-20250417]
[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/Rafael-V-Volkmer/pwm-tiehrpwm-ensures-that-state-enabled-is-synchronized-in-probe/20250420-075200
base:   linus/master
patch link:    https://lore.kernel.org/r/20250206031852.64853-1-rafael.v.volkmer%40gmail.com
patch subject: [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
config: um-randconfig-002-20250420 (https://download.01.org/0day-ci/archive/20250420/202504201347.OiWigSUq-lkp@intel.com/config)
compiler: clang version 21.0.0git (https://github.com/llvm/llvm-project f819f46284f2a79790038e1f6649172789734ae8)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250420/202504201347.OiWigSUq-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/202504201347.OiWigSUq-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from drivers/pwm/pwm-tiehrpwm.c:11:
   In file included from include/linux/io.h:12:
   In file included from arch/um/include/asm/io.h:24:
   include/asm-generic/io.h:549:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     549 |         val = __raw_readb(PCI_IOBASE + addr);
         |                           ~~~~~~~~~~ ^
   include/asm-generic/io.h:567:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     567 |         val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
         |                                                         ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu'
      37 | #define __le16_to_cpu(x) ((__force __u16)(__le16)(x))
         |                                                   ^
   In file included from drivers/pwm/pwm-tiehrpwm.c:11:
   In file included from include/linux/io.h:12:
   In file included from arch/um/include/asm/io.h:24:
   include/asm-generic/io.h:585:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     585 |         val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
         |                                                         ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu'
      35 | #define __le32_to_cpu(x) ((__force __u32)(__le32)(x))
         |                                                   ^
   In file included from drivers/pwm/pwm-tiehrpwm.c:11:
   In file included from include/linux/io.h:12:
   In file included from arch/um/include/asm/io.h:24:
   include/asm-generic/io.h:601:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     601 |         __raw_writeb(value, PCI_IOBASE + addr);
         |                             ~~~~~~~~~~ ^
   include/asm-generic/io.h:616:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     616 |         __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
         |                                                       ~~~~~~~~~~ ^
   include/asm-generic/io.h:631:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     631 |         __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
         |                                                       ~~~~~~~~~~ ^
   include/asm-generic/io.h:724:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     724 |         readsb(PCI_IOBASE + addr, buffer, count);
         |                ~~~~~~~~~~ ^
   include/asm-generic/io.h:737:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     737 |         readsw(PCI_IOBASE + addr, buffer, count);
         |                ~~~~~~~~~~ ^
   include/asm-generic/io.h:750:20: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     750 |         readsl(PCI_IOBASE + addr, buffer, count);
         |                ~~~~~~~~~~ ^
   include/asm-generic/io.h:764:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     764 |         writesb(PCI_IOBASE + addr, buffer, count);
         |                 ~~~~~~~~~~ ^
   include/asm-generic/io.h:778:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     778 |         writesw(PCI_IOBASE + addr, buffer, count);
         |                 ~~~~~~~~~~ ^
   include/asm-generic/io.h:792:21: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
     792 |         writesl(PCI_IOBASE + addr, buffer, count);
         |                 ~~~~~~~~~~ ^
>> drivers/pwm/pwm-tiehrpwm.c:675:7: warning: variable 'tbclk_enabled' is used uninitialized whenever 'if' condition is true [-Wsometimes-uninitialized]
     675 |                 if (ret) {
         |                     ^~~
   drivers/pwm/pwm-tiehrpwm.c:706:6: note: uninitialized use occurs here
     706 |         if (tbclk_enabled)
         |             ^~~~~~~~~~~~~
   drivers/pwm/pwm-tiehrpwm.c:675:3: note: remove the 'if' if its condition is always false
     675 |                 if (ret) {
         |                 ^~~~~~~~~~
     676 |                         dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
         |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     677 |                         goto err_pwmchip_remove;
         |                         ~~~~~~~~~~~~~~~~~~~~~~~~
     678 |                 }
         |                 ~
   drivers/pwm/pwm-tiehrpwm.c:629:20: note: initialize the variable 'tbclk_enabled' to silence this warning
     629 |         bool tbclk_enabled;
         |                           ^
         |                            = 0
   13 warnings generated.


vim +675 drivers/pwm/pwm-tiehrpwm.c

   621	
   622	static int ehrpwm_pwm_probe(struct platform_device *pdev)
   623	{
   624		struct device_node *np = pdev->dev.of_node;
   625		struct ehrpwm_pwm_chip *pc;
   626		struct pwm_state state;
   627		struct pwm_chip *chip;
   628		struct clk *clk;
   629		bool tbclk_enabled;
   630		int ret;
   631	
   632		chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
   633		if (IS_ERR(chip))
   634			return PTR_ERR(chip);
   635		pc = to_ehrpwm_pwm_chip(chip);
   636	
   637		clk = devm_clk_get(&pdev->dev, "fck");
   638		if (IS_ERR(clk)) {
   639			if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
   640				dev_warn(&pdev->dev, "Binding is obsolete.\n");
   641				clk = devm_clk_get(pdev->dev.parent, "fck");
   642			}
   643		}
   644	
   645		if (IS_ERR(clk))
   646			return dev_err_probe(&pdev->dev, PTR_ERR(clk), "Failed to get fck\n");
   647	
   648		pc->clk_rate = clk_get_rate(clk);
   649		if (!pc->clk_rate) {
   650			dev_err(&pdev->dev, "failed to get clock rate\n");
   651			return -EINVAL;
   652		}
   653	
   654		chip->ops = &ehrpwm_pwm_ops;
   655	
   656		pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);
   657		if (IS_ERR(pc->mmio_base))
   658			return PTR_ERR(pc->mmio_base);
   659	
   660		/* Acquire tbclk for Time Base EHRPWM submodule */
   661		pc->tbclk = devm_clk_get(&pdev->dev, "tbclk");
   662		if (IS_ERR(pc->tbclk))
   663			return dev_err_probe(&pdev->dev, PTR_ERR(pc->tbclk), "Failed to get tbclk\n");
   664	
   665		ret = clk_prepare(pc->tbclk);
   666		if (ret < 0) {
   667			dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret);
   668			return ret;
   669		}
   670	
   671		ehrpwm_get_state(chip, &chip->pwms[0], &state);
   672	
   673		if (state.enabled == true) {
   674			ret = clk_prepare_enable(pc->tbclk);
 > 675			if (ret) {
   676				dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
   677				goto err_pwmchip_remove;
   678			}
   679	
   680			tbclk_enabled = true;
   681		}
   682	
   683		ret = pwmchip_add(chip);
   684		if (ret < 0) {
   685			dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
   686			goto err_clk_unprepare;
   687		}
   688	
   689		platform_set_drvdata(pdev, chip);
   690		pm_runtime_enable(&pdev->dev);
   691	
   692		if (state.enabled == true) {
   693			ret = pm_runtime_get_sync(&pdev->dev);
   694			if (ret < 0) {
   695				dev_err_probe(&pdev->dev, ret, "pm_runtime_get_sync() failed");
   696				clk_disable_unprepare(pc->tbclk);
   697				goto err_pwmchip_remove;
   698			}
   699		}
   700	
   701		return 0;
   702	
   703	err_pwmchip_remove:
   704		pwmchip_remove(chip);
   705	err_clk_unprepare:
   706		if (tbclk_enabled)
   707			clk_unprepare(pc->tbclk);
   708	
   709		return ret;
   710	}
   711	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
Re: [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Posted by kernel test robot 7 months, 4 weeks ago
Hi Rafael,

kernel test robot noticed the following build warnings:

[auto build test WARNING on linus/master]
[also build test WARNING on v6.15-rc2 next-20250417]
[cannot apply to thierry-reding-pwm/for-next]
[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/Rafael-V-Volkmer/pwm-tiehrpwm-ensures-that-state-enabled-is-synchronized-in-probe/20250420-075200
base:   linus/master
patch link:    https://lore.kernel.org/r/20250206031852.64853-1-rafael.v.volkmer%40gmail.com
patch subject: [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
config: arc-randconfig-001-20250420 (https://download.01.org/0day-ci/archive/20250420/202504201050.o3m04RP6-lkp@intel.com/config)
compiler: arc-linux-gcc (GCC) 12.4.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250420/202504201050.o3m04RP6-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/202504201050.o3m04RP6-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/pwm/pwm-tiehrpwm.c:105: warning: expecting prototype for The ePWM hardware encodes compare actions with two bits each(). Prototype was for AQ_CLEAR() instead


vim +105 drivers/pwm/pwm-tiehrpwm.c

    97	
    98	/**
    99	 * The ePWM hardware encodes compare actions with two bits each:
   100	 *   00 = Do nothing
   101	 *   01 = Clear
   102	 *   10 = Set
   103	 *   11 = Toggle
   104	 */
 > 105	#define AQ_CLEAR  1
   106	#define AQ_SET    2
   107	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
Re: [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Posted by Uwe Kleine-König 10 months, 1 week ago
Hello Rafael,

On Thu, Feb 06, 2025 at 12:18:52AM -0300, Rafael V. Volkmer wrote:
> If the hardware is already active during probe, it's not asserted
> that the clock is enabled. To address this, added ehrpwm_get_state()
> to verify the enable bit and update the pwm_state struct with the
> correct hardware parameters.
> 
> In ehrpwm_pwm_probe(), the function checks if the enable bit is set
> and calls clk_enable() and pm_runtime_get_sync() if needed.
> 
> Additionally, ehrpwm_get_state() was integrated as .get_state in
> the ehrpwm_pwm_ops struct, incorporating it into the driver's pwm_ops
> for consistent state management.

No in depth review here. One thing I just noticed when skimming your
patch: There are still whitespace style issues (that checkpatch might
not notice), please only use only a single space after =. Also please
split the patch in two: One to introduce .get_state() and the other to
fix clock enable accounting.

I would also consider inlining ehrpwm_read_period(),
ehrpwm_read_duty_cycle() and ehrpwm_read_polarity() into
ehrpwm_get_state() to not have to jump around to understand the register
usage.

For a more detailed review I have to ask for your patience. There are
still some more driver reviews in my list, but I come to your driver
eventually.

Best regards
Uwe
[PATCH v3 1/3] pwm: tiehrpwm: replace manual bit definitions with bitfield.h macros
Posted by Rafael V. Volkmer 10 months, 1 week ago
Simplify bit manipulation by replacing manual BIT(x) definitions with
GENMASK() and FIELD_PREP() from <linux/bitfield.h>. This improves
readability, consistency, and aligns with modern kernel practices
while preserving existing functionality.

Additionally, update set_prescale_div() to use FIELD_PREP() for
TBCTL_CLKDIV_MASK and TBCTL_HSPCLKDIV_MASK instead of manually
shifting bits. This makes the code more maintainable and avoids
potential errors in bit field assignments.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 222 ++++++++++++++++++++++++-------------
 1 file changed, 142 insertions(+), 80 deletions(-)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 0125e73b98df..50516f46ab04 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -13,85 +13,147 @@
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
+#include <linux/bitfield.h>
 
-/* EHRPWM registers and bits definitions */
-
-/* Time base module registers */
-#define TBCTL			0x00
-#define TBPRD			0x0A
-
-#define TBCTL_PRDLD_MASK	BIT(3)
-#define TBCTL_PRDLD_SHDW	0
-#define TBCTL_PRDLD_IMDT	BIT(3)
-#define TBCTL_CLKDIV_MASK	(BIT(12) | BIT(11) | BIT(10) | BIT(9) | \
-				BIT(8) | BIT(7))
-#define TBCTL_CTRMODE_MASK	(BIT(1) | BIT(0))
-#define TBCTL_CTRMODE_UP	0
-#define TBCTL_CTRMODE_DOWN	BIT(0)
-#define TBCTL_CTRMODE_UPDOWN	BIT(1)
-#define TBCTL_CTRMODE_FREEZE	(BIT(1) | BIT(0))
-
-#define TBCTL_HSPCLKDIV_SHIFT	7
-#define TBCTL_CLKDIV_SHIFT	10
-
-#define CLKDIV_MAX		7
-#define HSPCLKDIV_MAX		7
-#define PERIOD_MAX		0xFFFF
-
-/* compare module registers */
-#define CMPA			0x12
-#define CMPB			0x14
-
-/* Action qualifier module registers */
-#define AQCTLA			0x16
-#define AQCTLB			0x18
-#define AQSFRC			0x1A
-#define AQCSFRC			0x1C
-
-#define AQCTL_CBU_MASK		(BIT(9) | BIT(8))
-#define AQCTL_CBU_FRCLOW	BIT(8)
-#define AQCTL_CBU_FRCHIGH	BIT(9)
-#define AQCTL_CBU_FRCTOGGLE	(BIT(9) | BIT(8))
-#define AQCTL_CAU_MASK		(BIT(5) | BIT(4))
-#define AQCTL_CAU_FRCLOW	BIT(4)
-#define AQCTL_CAU_FRCHIGH	BIT(5)
-#define AQCTL_CAU_FRCTOGGLE	(BIT(5) | BIT(4))
-#define AQCTL_PRD_MASK		(BIT(3) | BIT(2))
-#define AQCTL_PRD_FRCLOW	BIT(2)
-#define AQCTL_PRD_FRCHIGH	BIT(3)
-#define AQCTL_PRD_FRCTOGGLE	(BIT(3) | BIT(2))
-#define AQCTL_ZRO_MASK		(BIT(1) | BIT(0))
-#define AQCTL_ZRO_FRCLOW	BIT(0)
-#define AQCTL_ZRO_FRCHIGH	BIT(1)
-#define AQCTL_ZRO_FRCTOGGLE	(BIT(1) | BIT(0))
-
-#define AQCTL_CHANA_POLNORMAL	(AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \
-				AQCTL_ZRO_FRCHIGH)
-#define AQCTL_CHANA_POLINVERSED	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \
-				AQCTL_ZRO_FRCLOW)
-#define AQCTL_CHANB_POLNORMAL	(AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \
-				AQCTL_ZRO_FRCHIGH)
-#define AQCTL_CHANB_POLINVERSED	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \
-				AQCTL_ZRO_FRCLOW)
-
-#define AQSFRC_RLDCSF_MASK	(BIT(7) | BIT(6))
-#define AQSFRC_RLDCSF_ZRO	0
-#define AQSFRC_RLDCSF_PRD	BIT(6)
-#define AQSFRC_RLDCSF_ZROPRD	BIT(7)
-#define AQSFRC_RLDCSF_IMDT	(BIT(7) | BIT(6))
-
-#define AQCSFRC_CSFB_MASK	(BIT(3) | BIT(2))
-#define AQCSFRC_CSFB_FRCDIS	0
-#define AQCSFRC_CSFB_FRCLOW	BIT(2)
-#define AQCSFRC_CSFB_FRCHIGH	BIT(3)
-#define AQCSFRC_CSFB_DISSWFRC	(BIT(3) | BIT(2))
-#define AQCSFRC_CSFA_MASK	(BIT(1) | BIT(0))
-#define AQCSFRC_CSFA_FRCDIS	0
-#define AQCSFRC_CSFA_FRCLOW	BIT(0)
-#define AQCSFRC_CSFA_FRCHIGH	BIT(1)
-#define AQCSFRC_CSFA_DISSWFRC	(BIT(1) | BIT(0))
-
-#define NUM_PWM_CHANNEL		2	/* EHRPWM channels */
+/*
+ * --------------------------------------
+ * Time base module registers
+ * --------------------------------------
+ */
+#define TBCTL					0x00
+#define TBPRD					0x0A
+
+/* TBCTL: CTRMODE field (bits [1:0]) */
+#define TBCTL_CTRMODE_MASK		GENMASK(1, 0)
+
+/* The possible values for that field */
+#define TBCTL_CTRMODE_UP		FIELD_PREP(TBCTL_CTRMODE_MASK, 0U)
+#define TBCTL_CTRMODE_DOWN		FIELD_PREP(TBCTL_CTRMODE_MASK, 1U)
+#define TBCTL_CTRMODE_UPDOWN	FIELD_PREP(TBCTL_CTRMODE_MASK, 2U)
+#define TBCTL_CTRMODE_FREEZE	FIELD_PREP(TBCTL_CTRMODE_MASK, 3U)
+
+/* TBCTL: PRDLD bit (bit [3]) */
+#define TBCTL_PRDLD_MASK		GENMASK(3, 3)
+
+/* Possible values for PRDLD */
+#define TBCTL_PRDLD_SHDW		FIELD_PREP(TBCTL_PRDLD_MASK, 0U) /* 0 => shadow */
+#define TBCTL_PRDLD_IMDT		FIELD_PREP(TBCTL_PRDLD_MASK, 1U) /* 1 => immediate */
+
+/* TBCTL: Combined bits [12:7] actually split into two subfields:
+ *    - TBCTL_HSPCLKDIV => bits [9:7]
+ *    - TBCTL_CLKDIV    => bits [12:10]
+ */
+#define TBCTL_HSPCLKDIV_MASK	GENMASK(9, 7)
+#define TBCTL_CLKDIV_MASK		GENMASK(12, 10)
+
+/* Max values if needed: */
+#define CLKDIV_MAX				7U
+#define HSPCLKDIV_MAX			7U
+#define PERIOD_MAX				0xFFFF
+
+/*
+ * --------------------------------------
+ * Compare module registers
+ * --------------------------------------
+ */
+#define CMPA					0x12
+#define CMPB					0x14
+
+/*
+ * --------------------------------------
+ * Action qualifier (AQ) module registers
+ * --------------------------------------
+ */
+#define AQCTLA					0x16
+#define AQCTLB					0x18
+#define AQSFRC					0x1A
+#define AQCSFRC					0x1C
+
+/*
+ * AQCTLA and AQCTLB share similar 2-bit fields for
+ * various events (ZRO, PRD, CAU, CBU, etc.).
+ *
+ * For instance, bits [1:0] => ZRO,
+ *               bits [3:2] => PRD,
+ *               bits [5:4] => CAU,
+ *               bits [9:8] => CBU, etc.
+ */
+#define AQCTL_ZRO_MASK			GENMASK(1, 0)
+#define AQCTL_PRD_MASK			GENMASK(3, 2)
+#define AQCTL_CAU_MASK			GENMASK(5, 4)
+#define AQCTL_CBU_MASK			GENMASK(9, 8)
+
+/*
+ * FORCE LOW	=> 0b01
+ * FORCE HIGH	=> 0b01
+ * FORCE TOGGLE	=> 0b01
+ */
+#define AQCTL_FRCLOW			1U
+#define AQCTL_FRCHIGH			2U
+#define AQCTL_FRCTOGGLE			3U
+
+/* These values in CAU: */
+#define AQCTL_CAU_FRCLOW		FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCLOW)
+#define AQCTL_CAU_FRCHIGH		FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCHIGH)
+#define AQCTL_CAU_FRCTOGGLE	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCTOGGLE)
+
+/* These values in PRD: */
+#define AQCTL_PRD_FRCLOW		FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCLOW)
+#define AQCTL_PRD_FRCHIGH		FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCHIGH)
+#define AQCTL_PRD_FRCTOGGLE		FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCTOGGLE)
+
+/* These values in ZRO: */
+#define AQCTL_ZRO_FRCLOW		FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCLOW)
+#define AQCTL_ZRO_FRCHIGH		FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCHIGH)
+#define AQCTL_ZRO_FRCTOGGLE		FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCTOGGLE)
+
+/* These values in CBU: */
+#define AQCTL_CBU_FRCLOW		FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCLOW)
+#define AQCTL_CBU_FRCHIGH		FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCHIGH)
+#define AQCTL_CBU_FRCTOGGLE		FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCTOGGLE)
+
+/* Predefined combinations for channel polarity */
+#define AQCTL_CHANA_POLNORMAL	\
+	(AQCTL_CAU_FRCLOW  | AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH)
+#define AQCTL_CHANA_POLINVERSED	\
+	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW  | AQCTL_ZRO_FRCLOW)
+#define AQCTL_CHANB_POLNORMAL	\
+	(AQCTL_CBU_FRCLOW  | AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH)
+#define AQCTL_CHANB_POLINVERSED	\
+	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW  | AQCTL_ZRO_FRCLOW)
+
+/* AQSFRC: RLDCSF => bits [7:6] */
+#define AQSFRC_RLDCSF_MASK		GENMASK(7, 6)
+
+#define AQSFRC_RLDCSF_ZRO		FIELD_PREP(AQSFRC_RLDCSF_MASK, 0U)
+#define AQSFRC_RLDCSF_PRD		FIELD_PREP(AQSFRC_RLDCSF_MASK, 1U)
+#define AQSFRC_RLDCSF_ZROPRD	FIELD_PREP(AQSFRC_RLDCSF_MASK, 2U)
+#define AQSFRC_RLDCSF_IMDT		FIELD_PREP(AQSFRC_RLDCSF_MASK, 3U)
+
+/* AQCSFRC: CSFB => bits [3:2], CSFA => bits [1:0] */
+#define AQCSFRC_CSFB_MASK		GENMASK(3, 2)
+#define AQCSFRC_CSFA_MASK		GENMASK(1, 0)
+
+/* The possible 2-bit values for CSFB or CSFA fields */
+#define AQCSFRC_FRCDIS			0U
+#define AQCSFRC_FRCLOW			1U
+#define AQCSFRC_FRCHIGH			2U
+#define AQCSFRC_DISSWFRC		3U
+
+/* These values in CSFB: */
+#define AQCSFRC_CSFB_FRCDIS		FIELD_PREP(AQCSFRC_CSFB_MASK, AQCSFRC_FRCDIS)
+#define AQCSFRC_CSFB_FRCLOW		FIELD_PREP(AQCSFRC_CSFB_MASK, AQCSFRC_FRCLOW)
+#define AQCSFRC_CSFB_FRCHIGH	FIELD_PREP(AQCSFRC_CSFB_MASK, AQCSFRC_FRCHIGH)
+#define AQCSFRC_CSFB_DISSWFRC	FIELD_PREP(AQCSFRC_CSFB_MASK, AQCSFRC_DISSWFRC)
+
+/* These values in CSFA: */
+#define AQCSFRC_CSFA_FRCDIS		FIELD_PREP(AQCSFRC_CSFA_MASK, AQCSFRC_FRCDIS)
+#define AQCSFRC_CSFA_FRCLOW		FIELD_PREP(AQCSFRC_CSFA_MASK, AQCSFRC_FRCLOW)
+#define AQCSFRC_CSFA_FRCHIGH	FIELD_PREP(AQCSFRC_CSFA_MASK, AQCSFRC_FRCHIGH)
+#define AQCSFRC_CSFA_DISSWFRC	FIELD_PREP(AQCSFRC_CSFA_MASK, AQCSFRC_DISSWFRC)
+
+/* EHRPWM channels */
+#define NUM_PWM_CHANNEL			2U
 
 struct ehrpwm_context {
 	u16 tbctl;
@@ -167,8 +229,8 @@ static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
 			*prescale_div = (1 << clkdiv) *
 					(hspclkdiv ? (hspclkdiv * 2) : 1);
 			if (*prescale_div > rqst_prescaler) {
-				*tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) |
-					(hspclkdiv << TBCTL_HSPCLKDIV_SHIFT);
+				*tb_clk_div = FIELD_PREP(TBCTL_CLKDIV_MASK, clkdiv) |
+								FIELD_PREP(TBCTL_HSPCLKDIV_MASK, hspclkdiv);
 				return 0;
 			}
 		}
-- 
2.25.1
[PATCH v3 2/3] pwm: ehrpwm: add get_state function to retrieve PWM channel state
Posted by Rafael V. Volkmer 10 months, 1 week ago
The ehrpwm driver was missing a get_state function, which is required
to properly retrieve the current state of the PWM channel. This commit
adds the ehrpwm_get_state() function, allowing users to query the
enabled state, period, duty cycle, and polarity of the PWM output.

The function reads the relevant registers (AQCSFRC, AQCTLx, TBPRD, CMPA)
to determine:
- Whether the PWM is enabled or disabled
- The configured period and duty cycle
- The polarity based on action-qualifier configurations

Additionally, this commit updates the pwm_ops structure to include
.get_state, ensuring proper integration with the PWM subsystem.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 122 +++++++++++++++++++++++++++++++++++++
 1 file changed, 122 insertions(+)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 50516f46ab04..52527136c507 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -81,7 +81,9 @@
 #define AQCTL_ZRO_MASK			GENMASK(1, 0)
 #define AQCTL_PRD_MASK			GENMASK(3, 2)
 #define AQCTL_CAU_MASK			GENMASK(5, 4)
+#define AQCTL_CAD_MASK			GENMASK(7, 6)
 #define AQCTL_CBU_MASK			GENMASK(9, 8)
+#define AQCTL_CBD_MASK			GENMASK(11, 10)
 
 /*
  * FORCE LOW	=> 0b01
@@ -495,9 +497,129 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	return err;
 }
 
+/**
+ * ehrpwm_get_state - Retrieves the current state of the eHRPWM channel
+ * @chip:	pointer to the PWM chip structure
+ * @pwm:	pointer to the PWM device structure
+ * @state:	pointer to the pwm_state structure to be filled
+ *
+ * @return:	0 on success or a negative error code on failure
+ */
+static int ehrpwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+							struct pwm_state *state)
+{
+	int ret = 0u;
+
+	struct ehrpwm_pwm_chip *pc = NULL;
+
+	unsigned long long tbclk_rate = 0u;
+
+	/* Registers */
+	u16 aqcsfrc_reg = 0u;
+	u16 aqctl_reg = 0u;
+	u16 tbprd_reg = 0u;
+	u16 cmpa_reg = 0u;
+
+	/* Bits */
+	u8 csf_bits = 0u;
+
+	/* Values */
+	u64 period_cycles = 0u;
+	u64 duty_cycles = 0u;
+
+	/* Actions */
+	u8 up_action = 0u;
+	u8 down_action = 0u;
+
+	if (chip == NULL || pwm == NULL || state == NULL)
+		return -EINVAL;
+
+	pc = to_ehrpwm_pwm_chip(chip);
+	if (pc == NULL || pwm == NULL || state == NULL)
+		return -EINVAL;
+
+	tbclk_rate = clk_get_rate(pc->tbclk);
+	if (tbclk_rate <= 0)
+		return -EINVAL;
+
+	/*< Get if EHRPWM is enable by checking registers values >*/
+
+	/*
+	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
+	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
+	 */
+	if (pwm->hwpwm == 0) {
+		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
+		csf_bits = FIELD_GET(AQCSFRC_CSFA_MASK, aqcsfrc_reg);
+		aqctl_reg = readw(pc->mmio_base + AQCTLA);
+	} else {
+		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
+		csf_bits = FIELD_GET(AQCSFRC_CSFB_MASK, aqcsfrc_reg);
+		aqctl_reg = readw(pc->mmio_base + AQCTLB);
+	}
+
+	if (csf_bits)
+		state->enabled = false;
+
+	else if (aqctl_reg)
+		state->enabled = true;
+
+	/*< Get EHRPWM Period by checking registers values >*/
+	tbprd_reg = readw(pc->mmio_base + TBPRD);
+	period_cycles = (u64)(tbprd_reg + 1u);
+
+	/*
+	 * period (in ns) = (period_cycles * 1e9) / tbclk_rate
+	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
+	 */
+	state->period = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, tbclk_rate);
+
+	/*< Get EHRPWM Duty Cycle by checking registers values >*/
+	cmpa_reg = readw(pc->mmio_base + CMPA);
+	duty_cycles = cmpa_reg;
+
+	/*
+	 * duty_cycle (in ns) = (duty_cycles * 1e9) / tbclk_rate
+	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
+	 */
+	state->duty_cycle  = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, tbclk_rate);
+
+	/*< Get EHRPWM polarity by checking registers values >*/
+
+	/*
+	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
+	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
+	 */
+	if (pwm->hwpwm == 0) {
+		aqctl_reg = readw(pc->mmio_base + AQCTLA);
+		up_action = FIELD_GET(AQCTL_CAU_MASK, aqctl_reg);
+		down_action = FIELD_GET(AQCTL_CAD_MASK, aqctl_reg);
+	} else {
+		aqctl_reg = readw(pc->mmio_base + AQCTLB);
+		up_action = FIELD_GET(AQCTL_CBU_MASK, aqctl_reg);
+		down_action = FIELD_GET(AQCTL_CBD_MASK, aqctl_reg);
+	}
+
+	/*
+	 * Evaluate the actions to determine the PWM polarity:
+	 *  - If an up-count event sets the output (AQCTL_FRCHIGH) and a down-count
+	 *    event clears it (AQ_CLEAR), then polarity is NORMAL.
+	 *  - If an up-count event clears the output (AQ_CLEAR) and a down-count
+	 *    event sets it (AQCTL_FRCLOW), then polarity is INVERSED.
+	 */
+	if (up_action == AQCTL_FRCHIGH && down_action == AQCTL_FRCLOW)
+		state->polarity = PWM_POLARITY_NORMAL;
+
+	else if (up_action == AQCTL_FRCLOW && down_action == AQCTL_FRCHIGH)
+		state->polarity = PWM_POLARITY_INVERSED;
+
+	return ret;
+}
+
 static const struct pwm_ops ehrpwm_pwm_ops = {
 	.free = ehrpwm_pwm_free,
 	.apply = ehrpwm_pwm_apply,
+	.get_state = ehrpwm_get_state,
 };
 
 static const struct of_device_id ehrpwm_of_match[] = {
-- 
2.25.1
Re: [PATCH v3 2/3] pwm: ehrpwm: add get_state function to retrieve PWM channel state
Posted by Uwe Kleine-König 8 months, 3 weeks ago
Hello Rafael,

On Fri, Feb 07, 2025 at 06:32:34PM -0300, Rafael V. Volkmer wrote:
> The ehrpwm driver was missing a get_state function, which is required
> to properly retrieve the current state of the PWM channel. This commit
> adds the ehrpwm_get_state() function, allowing users to query the
> enabled state, period, duty cycle, and polarity of the PWM output.

s/This commit adds/Add/

> The function reads the relevant registers (AQCSFRC, AQCTLx, TBPRD, CMPA)
> to determine:
> - Whether the PWM is enabled or disabled
> - The configured period and duty cycle
> - The polarity based on action-qualifier configurations
> 
> Additionally, this commit updates the pwm_ops structure to include
> .get_state, ensuring proper integration with the PWM subsystem.

The last two paragraphs are too verbose. I'd drop them.

> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
> ---
>  drivers/pwm/pwm-tiehrpwm.c | 122 +++++++++++++++++++++++++++++++++++++
>  1 file changed, 122 insertions(+)
> 
> diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
> index 50516f46ab04..52527136c507 100644
> --- a/drivers/pwm/pwm-tiehrpwm.c
> +++ b/drivers/pwm/pwm-tiehrpwm.c
> @@ -81,7 +81,9 @@
>  #define AQCTL_ZRO_MASK			GENMASK(1, 0)
>  #define AQCTL_PRD_MASK			GENMASK(3, 2)
>  #define AQCTL_CAU_MASK			GENMASK(5, 4)
> +#define AQCTL_CAD_MASK			GENMASK(7, 6)
>  #define AQCTL_CBU_MASK			GENMASK(9, 8)
> +#define AQCTL_CBD_MASK			GENMASK(11, 10)
>  
>  /*
>   * FORCE LOW	=> 0b01
> @@ -495,9 +497,129 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>  	return err;
>  }
>  
> +/**
> + * ehrpwm_get_state - Retrieves the current state of the eHRPWM channel
> + * @chip:	pointer to the PWM chip structure
> + * @pwm:	pointer to the PWM device structure
> + * @state:	pointer to the pwm_state structure to be filled
> + *
> + * @return:	0 on success or a negative error code on failure
> + */
> +static int ehrpwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +							struct pwm_state *state)
> +{
> +	int ret = 0u;
> +
> +	struct ehrpwm_pwm_chip *pc = NULL;
> +
> +	unsigned long long tbclk_rate = 0u;
> +
> +	/* Registers */
> +	u16 aqcsfrc_reg = 0u;
> +	u16 aqctl_reg = 0u;
> +	u16 tbprd_reg = 0u;
> +	u16 cmpa_reg = 0u;

No need to initialize these. Also please drop the u suffix (which is
also inconsistant as you used U in patch #1). And then they can group
them in single code lines (i.e.

	u16 aqcsfrc_reg, aqctl_reg, tbprd_reg, cmpa_reg;

).

> +	/* Bits */
> +	u8 csf_bits = 0u;
> +
> +	/* Values */
> +	u64 period_cycles = 0u;
> +	u64 duty_cycles = 0u;
> +
> +	/* Actions */
> +	u8 up_action = 0u;
> +	u8 down_action = 0u;
> +
> +	if (chip == NULL || pwm == NULL || state == NULL)
> +		return -EINVAL;

Drop this check, the core makes sure these are all valid.

> +	pc = to_ehrpwm_pwm_chip(chip);
> +	if (pc == NULL || pwm == NULL || state == NULL)
> +		return -EINVAL;

Some of these you already checked above (so drop these, too). In my eyes
you can assume that to_ehrpwm_pwm_chip() doesn't return NULL.

> +	tbclk_rate = clk_get_rate(pc->tbclk);
> +	if (tbclk_rate <= 0)
> +		return -EINVAL;

tbclk_rate is long long while the return value of clk_get_rate is long
only. Also given that tbclk_rate is unsigned <= doesn't make much sense.

Also note that clk_get_rate() is only supposed to be called for an
enabled clock.

> +	/*< Get if EHRPWM is enable by checking registers values >*/

s/enable/enabled/

> +	/*
> +	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
> +	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
> +	 */
> +	if (pwm->hwpwm == 0) {
> +		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
> +		csf_bits = FIELD_GET(AQCSFRC_CSFA_MASK, aqcsfrc_reg);
> +		aqctl_reg = readw(pc->mmio_base + AQCTLA);
> +	} else {
> +		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
> +		csf_bits = FIELD_GET(AQCSFRC_CSFB_MASK, aqcsfrc_reg);
> +		aqctl_reg = readw(pc->mmio_base + AQCTLB);
> +	}
> +
> +	if (csf_bits)
> +		state->enabled = false;
> +
> +	else if (aqctl_reg)
> +		state->enabled = true;

	else
		state->enable is uninitialized :-\

> +	/*< Get EHRPWM Period by checking registers values >*/

This is kind of obvious. What is that /*< style? Looks like a bird :-)

> +	tbprd_reg = readw(pc->mmio_base + TBPRD);
> +	period_cycles = (u64)(tbprd_reg + 1u);

Maybe you need to cast first and then add 1?

> +	/*
> +	 * period (in ns) = (period_cycles * 1e9) / tbclk_rate
> +	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
> +	 */
> +	state->period = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, tbclk_rate);
> +
> +	/*< Get EHRPWM Duty Cycle by checking registers values >*/
> +	cmpa_reg = readw(pc->mmio_base + CMPA);
> +	duty_cycles = cmpa_reg;
> +
> +	/*
> +	 * duty_cycle (in ns) = (duty_cycles * 1e9) / tbclk_rate
> +	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
> +	 */
> +	state->duty_cycle  = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, tbclk_rate);

s/  / /

> +
> +	/*< Get EHRPWM polarity by checking registers values >*/
> +
> +	/*
> +	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
> +	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
> +	 */
> +	if (pwm->hwpwm == 0) {
> +		aqctl_reg = readw(pc->mmio_base + AQCTLA);
> +		up_action = FIELD_GET(AQCTL_CAU_MASK, aqctl_reg);
> +		down_action = FIELD_GET(AQCTL_CAD_MASK, aqctl_reg);
> +	} else {
> +		aqctl_reg = readw(pc->mmio_base + AQCTLB);
> +		up_action = FIELD_GET(AQCTL_CBU_MASK, aqctl_reg);
> +		down_action = FIELD_GET(AQCTL_CBD_MASK, aqctl_reg);
> +	}
> +
> +	/*
> +	 * Evaluate the actions to determine the PWM polarity:
> +	 *  - If an up-count event sets the output (AQCTL_FRCHIGH) and a down-count
> +	 *    event clears it (AQ_CLEAR), then polarity is NORMAL.
> +	 *  - If an up-count event clears the output (AQ_CLEAR) and a down-count
> +	 *    event sets it (AQCTL_FRCLOW), then polarity is INVERSED.
> +	 */
> +	if (up_action == AQCTL_FRCHIGH && down_action == AQCTL_FRCLOW)
> +		state->polarity = PWM_POLARITY_NORMAL;
> +
> +	else if (up_action == AQCTL_FRCLOW && down_action == AQCTL_FRCHIGH)
> +		state->polarity = PWM_POLARITY_INVERSED;

	else ??? (maybe return an error?)

> +	return ret;
> +}
> +

Best regards
Uwe
[PATCH v3 3/3] pwm: ehrpwm: ensure clock and runtime PM are enabled if hardware is active
Posted by Rafael V. Volkmer 10 months, 1 week ago
During probe, if the hardware is already active, it is not guaranteed
that the clock is enabled. To address this, ehrpwm_pwm_probe() now
checks whether the PWM is enabled and ensures that the necessary
resources are initialized.

Changes:
- Call ehrpwm_get_state() during probe to check if the PWM is active.
- If the PWM is enabled, call clk_prepare_enable() to ensure the clock
is active.
- If the clock is successfully enabled, call pm_runtime_get_sync() to
manage power state.
- Handle failure cases by properly disabling and unpreparing the clock.

This ensures that the driver correctly handles cases where the hardware
is already in use at the time of initialization, preventing potential
failures due to uninitialized resources.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 52527136c507..30beaf7d1721 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -633,8 +633,10 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
 	struct ehrpwm_pwm_chip *pc;
+	struct pwm_state state;
 	struct pwm_chip *chip;
 	struct clk *clk;
+	bool tbclk_enabled;
 	int ret;
 
 	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
@@ -676,6 +678,18 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	ehrpwm_get_state(chip, &chip->pwms[0], &state);
+
+	if (state.enabled == true) {
+		ret = clk_prepare_enable(pc->tbclk);
+		if (ret) {
+			dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
+			goto err_pwmchip_remove;
+		}
+
+		tbclk_enabled = true;
+	}
+
 	ret = pwmchip_add(chip);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
@@ -685,10 +699,22 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, chip);
 	pm_runtime_enable(&pdev->dev);
 
+	if (state.enabled == true) {
+		ret = pm_runtime_get_sync(&pdev->dev);
+		if (ret < 0) {
+			dev_err_probe(&pdev->dev, ret, "pm_runtime_get_sync() failed");
+			clk_disable_unprepare(pc->tbclk);
+			goto err_pwmchip_remove;
+		}
+	}
+
 	return 0;
 
+err_pwmchip_remove:
+	pwmchip_remove(chip);
 err_clk_unprepare:
-	clk_unprepare(pc->tbclk);
+	if (tbclk_enabled)
+		clk_unprepare(pc->tbclk);
 
 	return ret;
 }
-- 
2.25.1
Re: [PATCH v3 3/3] pwm: ehrpwm: ensure clock and runtime PM are enabled if hardware is active
Posted by Uwe Kleine-König 8 months, 3 weeks ago
Hello Rafael,

On Fri, Feb 07, 2025 at 06:34:24PM -0300, Rafael V. Volkmer wrote:
> During probe, if the hardware is already active, it is not guaranteed
> that the clock is enabled. To address this, ehrpwm_pwm_probe() now
> checks whether the PWM is enabled and ensures that the necessary
> resources are initialized.
> 
> Changes:
> - Call ehrpwm_get_state() during probe to check if the PWM is active.
> - If the PWM is enabled, call clk_prepare_enable() to ensure the clock
> is active.
> - If the clock is successfully enabled, call pm_runtime_get_sync() to
> manage power state.
> - Handle failure cases by properly disabling and unpreparing the clock.

This is too detailed, just drop the changes list.

> This ensures that the driver correctly handles cases where the hardware
> is already in use at the time of initialization, preventing potential
> failures due to uninitialized resources.
> 
> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
> ---
>  drivers/pwm/pwm-tiehrpwm.c | 28 +++++++++++++++++++++++++++-
>  1 file changed, 27 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
> index 52527136c507..30beaf7d1721 100644
> --- a/drivers/pwm/pwm-tiehrpwm.c
> +++ b/drivers/pwm/pwm-tiehrpwm.c
> @@ -633,8 +633,10 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  {
>  	struct device_node *np = pdev->dev.of_node;
>  	struct ehrpwm_pwm_chip *pc;
> +	struct pwm_state state;
>  	struct pwm_chip *chip;
>  	struct clk *clk;
> +	bool tbclk_enabled;
>  	int ret;
>  
>  	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
> @@ -676,6 +678,18 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  		return ret;
>  	}
>  
> +	ehrpwm_get_state(chip, &chip->pwms[0], &state);

ehrpwm_get_state() does some things that are not needed here given that
you only evaluate state.enabled. I suggest to just read the one (or
two?) registers you need to determine if the PWM is on.

> +	if (state.enabled == true) {
> +		ret = clk_prepare_enable(pc->tbclk);

pc->tbclk is already prepared, so clk_enable() should be enough. After
all this should match what ehrpwm_pwm_enable() does.

> +		if (ret) {
> +			dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
> +			goto err_pwmchip_remove;
> +		}
> +
> +		tbclk_enabled = true;
> +	}
> +
>  	ret = pwmchip_add(chip);
>  	if (ret < 0) {
>  		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
> @@ -685,10 +699,22 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  	platform_set_drvdata(pdev, chip);
>  	pm_runtime_enable(&pdev->dev);
>  
> +	if (state.enabled == true) {
> +		ret = pm_runtime_get_sync(&pdev->dev);
> +		if (ret < 0) {
> +			dev_err_probe(&pdev->dev, ret, "pm_runtime_get_sync() failed");
> +			clk_disable_unprepare(pc->tbclk);
> +			goto err_pwmchip_remove;
> +		}

It feels a bit strange to do this here. I think technically it's fine
here, but doing pm_runtime_get_sync() before pwmchip_add() would make
that a bit clearer.

> +	}
> +
>  	return 0;
>  
> +err_pwmchip_remove:
> +	pwmchip_remove(chip);
>  err_clk_unprepare:
> -	clk_unprepare(pc->tbclk);
> +	if (tbclk_enabled)
> +		clk_unprepare(pc->tbclk);

I think this is wrong an keeping the unconditional clk_unprepare() is
right. Might be easier to convert the driver to use
devm_clk_get_enabled().

Best regards
Uwe
[PATCH v4 1/4] pwm: tiehrpwm: replace manual bit definitions with bitfield.h macros
Posted by Rafael V. Volkmer 7 months, 4 weeks ago
Simplify bit manipulation by replacing manual BIT(x) definitions with
GENMASK() and FIELD_PREP() from <linux/bitfield.h>. This improves
readability, consistency, and aligns with modern kernel practices
while preserving existing functionality.

Additionally, update set_prescale_div() to use FIELD_PREP() for
TBCTL_CLKDIV_MASK and TBCTL_HSPCLKDIV_MASK instead of manually
shifting bits. This makes the code more maintainable and avoids
potential errors in bit field assignments.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 191 ++++++++++++++++++++++---------------
 1 file changed, 114 insertions(+), 77 deletions(-)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 0125e73b98df..1ead1aa91a1a 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -13,85 +13,122 @@
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
+#include <linux/bitfield.h>
 
 /* EHRPWM registers and bits definitions */
 
 /* Time base module registers */
-#define TBCTL			0x00
-#define TBPRD			0x0A
-
-#define TBCTL_PRDLD_MASK	BIT(3)
-#define TBCTL_PRDLD_SHDW	0
-#define TBCTL_PRDLD_IMDT	BIT(3)
-#define TBCTL_CLKDIV_MASK	(BIT(12) | BIT(11) | BIT(10) | BIT(9) | \
-				BIT(8) | BIT(7))
-#define TBCTL_CTRMODE_MASK	(BIT(1) | BIT(0))
-#define TBCTL_CTRMODE_UP	0
-#define TBCTL_CTRMODE_DOWN	BIT(0)
-#define TBCTL_CTRMODE_UPDOWN	BIT(1)
-#define TBCTL_CTRMODE_FREEZE	(BIT(1) | BIT(0))
-
-#define TBCTL_HSPCLKDIV_SHIFT	7
-#define TBCTL_CLKDIV_SHIFT	10
-
-#define CLKDIV_MAX		7
-#define HSPCLKDIV_MAX		7
-#define PERIOD_MAX		0xFFFF
-
-/* compare module registers */
-#define CMPA			0x12
-#define CMPB			0x14
-
-/* Action qualifier module registers */
-#define AQCTLA			0x16
-#define AQCTLB			0x18
-#define AQSFRC			0x1A
-#define AQCSFRC			0x1C
-
-#define AQCTL_CBU_MASK		(BIT(9) | BIT(8))
-#define AQCTL_CBU_FRCLOW	BIT(8)
-#define AQCTL_CBU_FRCHIGH	BIT(9)
-#define AQCTL_CBU_FRCTOGGLE	(BIT(9) | BIT(8))
-#define AQCTL_CAU_MASK		(BIT(5) | BIT(4))
-#define AQCTL_CAU_FRCLOW	BIT(4)
-#define AQCTL_CAU_FRCHIGH	BIT(5)
-#define AQCTL_CAU_FRCTOGGLE	(BIT(5) | BIT(4))
-#define AQCTL_PRD_MASK		(BIT(3) | BIT(2))
-#define AQCTL_PRD_FRCLOW	BIT(2)
-#define AQCTL_PRD_FRCHIGH	BIT(3)
-#define AQCTL_PRD_FRCTOGGLE	(BIT(3) | BIT(2))
-#define AQCTL_ZRO_MASK		(BIT(1) | BIT(0))
-#define AQCTL_ZRO_FRCLOW	BIT(0)
-#define AQCTL_ZRO_FRCHIGH	BIT(1)
-#define AQCTL_ZRO_FRCTOGGLE	(BIT(1) | BIT(0))
-
-#define AQCTL_CHANA_POLNORMAL	(AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \
-				AQCTL_ZRO_FRCHIGH)
-#define AQCTL_CHANA_POLINVERSED	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \
-				AQCTL_ZRO_FRCLOW)
-#define AQCTL_CHANB_POLNORMAL	(AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \
-				AQCTL_ZRO_FRCHIGH)
-#define AQCTL_CHANB_POLINVERSED	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \
-				AQCTL_ZRO_FRCLOW)
-
-#define AQSFRC_RLDCSF_MASK	(BIT(7) | BIT(6))
-#define AQSFRC_RLDCSF_ZRO	0
-#define AQSFRC_RLDCSF_PRD	BIT(6)
-#define AQSFRC_RLDCSF_ZROPRD	BIT(7)
-#define AQSFRC_RLDCSF_IMDT	(BIT(7) | BIT(6))
-
-#define AQCSFRC_CSFB_MASK	(BIT(3) | BIT(2))
-#define AQCSFRC_CSFB_FRCDIS	0
-#define AQCSFRC_CSFB_FRCLOW	BIT(2)
-#define AQCSFRC_CSFB_FRCHIGH	BIT(3)
-#define AQCSFRC_CSFB_DISSWFRC	(BIT(3) | BIT(2))
-#define AQCSFRC_CSFA_MASK	(BIT(1) | BIT(0))
-#define AQCSFRC_CSFA_FRCDIS	0
-#define AQCSFRC_CSFA_FRCLOW	BIT(0)
-#define AQCSFRC_CSFA_FRCHIGH	BIT(1)
-#define AQCSFRC_CSFA_DISSWFRC	(BIT(1) | BIT(0))
-
-#define NUM_PWM_CHANNEL		2	/* EHRPWM channels */
+#define TBCTL	0x00
+#define TBPRD	0x0A
+
+/* TBCTL: CTRMODE field (bits [1:0]) */
+#define TBCTL_CTRMODE_MASK	GENMASK(1, 0)
+#define TBCTL_CTRMODE_UP	FIELD_PREP(TBCTL_CTRMODE_MASK, 0)
+#define TBCTL_CTRMODE_DOWN	FIELD_PREP(TBCTL_CTRMODE_MASK, 1)
+#define TBCTL_CTRMODE_UPDOWN	FIELD_PREP(TBCTL_CTRMODE_MASK, 2)
+#define TBCTL_CTRMODE_FREEZE	FIELD_PREP(TBCTL_CTRMODE_MASK, 3)
+
+/* TBCTL: PRDLD bit (bit [3]) */
+#define TBCTL_PRDLD_MASK	GENMASK(3, 3)
+#define TBCTL_PRDLD_SHDW	FIELD_PREP(TBCTL_PRDLD_MASK, 0)  /* shadow load */
+#define TBCTL_PRDLD_IMDT	FIELD_PREP(TBCTL_PRDLD_MASK, 1)  /* immediate */
+
+/* TBCTL: high‑speed prescale (bits [9:7]) */
+#define TBCTL_HSPCLKDIV_MASK	GENMASK(9, 7)
+/* TBCTL: clock prescale (bits [12:10]) */
+#define TBCTL_CLKDIV_MASK	GENMASK(12, 10)
+
+#define CLKDIV_MAX	7
+#define HSPCLKDIV_MAX	7
+#define PERIOD_MAX	0xFFFF
+
+/*
+ * ----------------------------------------------------------------
+ * Compare module registers
+ * ----------------------------------------------------------------
+ */
+#define CMPA	0x12
+#define CMPB	0x14
+
+/*
+ * ----------------------------------------------------------------
+ * Action Qualifier (AQ) module registers
+ * ----------------------------------------------------------------
+ */
+#define AQCTLA	0x16
+#define AQCTLB	0x18
+#define AQSFRC	0x1A
+#define AQCSFRC	0x1
+
+/* AQCTL: event-action fields */
+/*   ZRO  = bits [1:0] */
+/*   PRD  = bits [3:2] */
+/*   CAU  = bits [5:4] */
+/*   CBU  = bits [9:8] */
+#define AQCTL_ZRO_MASK	GENMASK(1, 0)
+#define AQCTL_PRD_MASK	GENMASK(3, 2)
+#define AQCTL_CAU_MASK	GENMASK(5, 4)
+#define AQCTL_CBU_MASK	GENMASK(9, 8)
+
+/* common action codes (2‑bit) */
+#define AQCTL_FRCLOW	1
+#define AQCTL_FRCHIGH	2
+#define AQCTL_FRCTOGGLE	3
+
+/* ZRO actions */
+#define AQCTL_ZRO_FRCLOW	FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCLOW)
+#define AQCTL_ZRO_FRCHIGH	FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCHIGH)
+#define AQCTL_ZRO_FRCTOGGLE	FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCTOGGLE)
+
+/* PRD actions */
+#define AQCTL_PRD_FRCLOW	FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCLOW)
+#define AQCTL_PRD_FRCHIGH	FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCHIGH)
+#define AQCTL_PRD_FRCTOGGLE	FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCTOGGLE)
+
+/* CAU actions */
+#define AQCTL_CAU_FRCLOW	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCLOW)
+#define AQCTL_CAU_FRCHIGH	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCHIGH)
+#define AQCTL_CAU_FRCTOGGLE	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCTOGGLE)
+
+/* CBU actions */
+#define AQCTL_CBU_FRCLOW	FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCLOW)
+#define AQCTL_CBU_FRCHIGH	FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCHIGH)
+#define AQCTL_CBU_FRCTOGGLE	FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCTOGGLE)
+
+/* predefined channel‑polarity combos */
+#define AQCTL_CHANA_POLNORMAL	\
+	(AQCTL_CAU_FRCLOW  | AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH)
+#define AQCTL_CHANA_POLINVERSED	\
+	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW  | AQCTL_ZRO_FRCLOW)
+
+#define AQCTL_CHANB_POLNORMAL	\
+	(AQCTL_CBU_FRCLOW  | AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH)
+#define AQCTL_CHANB_POLINVERSED	\
+	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW  | AQCTL_ZRO_FRCLOW)
+
+/* AQSFRC: RLDCSF (bits [7:6]) */
+#define AQSFRC_RLDCSF_MASK	GENMASK(7, 6)
+#define AQSFRC_RLDCSF_ZRO	FIELD_PREP(AQSFRC_RLDCSF_MASK, 0)
+#define AQSFRC_RLDCSF_PRD	FIELD_PREP(AQSFRC_RLDCSF_MASK, 1)
+#define AQSFRC_RLDCSF_ZROPRD	FIELD_PREP(AQSFRC_RLDCSF_MASK, 2)
+#define AQSFRC_RLDCSF_IMDT	FIELD_PREP(AQSFRC_RLDCSF_MASK, 3)
+
+/* AQCSFRC: CSFB (bits [3:2]), CSFA (bits [1:0]) */
+#define AQCSFRC_CSFB_MASK	GENMASK(3, 2)
+#define AQCSFRC_CSFA_MASK	GENMASK(1, 0)
+
+#define AQCSFRC_CSFB_FRCDIS	FIELD_PREP(AQCSFRC_CSFB_MASK, 0)
+#define AQCSFRC_CSFB_FRCLOW	FIELD_PREP(AQCSFRC_CSFB_MASK, 1)
+#define AQCSFRC_CSFB_FRCHIGH	FIELD_PREP(AQCSFRC_CSFB_MASK, 2)
+#define AQCSFRC_CSFB_DISSWFRC	FIELD_PREP(AQCSFRC_CSFB_MASK, 3)
+
+#define AQCSFRC_CSFA_FRCDIS	FIELD_PREP(AQCSFRC_CSFA_MASK, 0)
+#define AQCSFRC_CSFA_FRCLOW	FIELD_PREP(AQCSFRC_CSFA_MASK, 1)
+#define AQCSFRC_CSFA_FRCHIGH	FIELD_PREP(AQCSFRC_CSFA_MASK, 2)
+#define AQCSFRC_CSFA_DISSWFRC	FIELD_PREP(AQCSFRC_CSFA_MASK, 3)
+
+/* Number of EHRPWM channels */
+#define NUM_PWM_CHANNEL	2U
 
 struct ehrpwm_context {
 	u16 tbctl;
@@ -167,8 +204,8 @@ static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
 			*prescale_div = (1 << clkdiv) *
 					(hspclkdiv ? (hspclkdiv * 2) : 1);
 			if (*prescale_div > rqst_prescaler) {
-				*tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) |
-					(hspclkdiv << TBCTL_HSPCLKDIV_SHIFT);
+				*tb_clk_div = FIELD_PREP(TBCTL_CLKDIV_MASK, clkdiv) |
+							FIELD_PREP(TBCTL_HSPCLKDIV_MASK, hspclkdiv);
 				return 0;
 			}
 		}
-- 
2.25.1

Re: [PATCH v4 1/4] pwm: tiehrpwm: replace manual bit definitions with bitfield.h macros
Posted by Uwe Kleine-König 7 months ago
On Sat, Apr 19, 2025 at 04:48:35PM -0300, Rafael V. Volkmer wrote:
> Simplify bit manipulation by replacing manual BIT(x) definitions with
> GENMASK() and FIELD_PREP() from <linux/bitfield.h>. This improves
> readability, consistency, and aligns with modern kernel practices
> while preserving existing functionality.
> 
> Additionally, update set_prescale_div() to use FIELD_PREP() for
> TBCTL_CLKDIV_MASK and TBCTL_HSPCLKDIV_MASK instead of manually
> shifting bits. This makes the code more maintainable and avoids
> potential errors in bit field assignments.
> 
> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
> ---
>  drivers/pwm/pwm-tiehrpwm.c | 191 ++++++++++++++++++++++---------------
>  1 file changed, 114 insertions(+), 77 deletions(-)

I reorder the patch a bit with the intend to not change semantics but to
make my points a bit clearer.

> diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
> index 0125e73b98df..1ead1aa91a1a 100644
> --- a/drivers/pwm/pwm-tiehrpwm.c
> +++ b/drivers/pwm/pwm-tiehrpwm.c
> @@ -13,85 +13,122 @@
>  #include <linux/clk.h>
>  #include <linux/pm_runtime.h>
>  #include <linux/of.h>
> +#include <linux/bitfield.h>
>  
>  /* EHRPWM registers and bits definitions */
>  
>  /* Time base module registers */
> -#define TBCTL			0x00
> -#define TBPRD			0x0A
> +#define TBCTL	0x00
> +#define TBPRD	0x0A

It's unclear to me why you changed the indention here. While you might
consider that inconsistent as I hate indenting struct initializers, for
#defines I think it greatly improves readability. I'd also keep the
whitespace changes to a minimum to make the patch better readable.

> -#define TBCTL_PRDLD_MASK	BIT(3)
> -#define TBCTL_PRDLD_SHDW	0
> -#define TBCTL_PRDLD_IMDT	BIT(3)
> -#define TBCTL_CLKDIV_MASK	(BIT(12) | BIT(11) | BIT(10) | BIT(9) | \
> -				BIT(8) | BIT(7))
> -#define TBCTL_CTRMODE_MASK	(BIT(1) | BIT(0))
> -#define TBCTL_CTRMODE_UP	0
> -#define TBCTL_CTRMODE_DOWN	BIT(0)
> -#define TBCTL_CTRMODE_UPDOWN	BIT(1)
> -#define TBCTL_CTRMODE_FREEZE	(BIT(1) | BIT(0))

Huh, you reordered these?

> -#define TBCTL_HSPCLKDIV_SHIFT	7
> -#define TBCTL_CLKDIV_SHIFT	10
> -
> -#define CLKDIV_MAX		7
> -#define HSPCLKDIV_MAX		7
> -#define PERIOD_MAX		0xFFFF
> -
> -/* compare module registers */
> -#define CMPA			0x12
> -#define CMPB			0x14
> -
> -/* Action qualifier module registers */
> -#define AQCTLA			0x16
> -#define AQCTLB			0x18
> -#define AQSFRC			0x1A
> -#define AQCSFRC			0x1C
> -
> -#define AQCTL_CBU_MASK		(BIT(9) | BIT(8))
> -#define AQCTL_CBU_FRCLOW	BIT(8)
> -#define AQCTL_CBU_FRCHIGH	BIT(9)
> -#define AQCTL_CBU_FRCTOGGLE	(BIT(9) | BIT(8))
> -#define AQCTL_CAU_MASK		(BIT(5) | BIT(4))
> -#define AQCTL_CAU_FRCLOW	BIT(4)
> -#define AQCTL_CAU_FRCHIGH	BIT(5)
> -#define AQCTL_CAU_FRCTOGGLE	(BIT(5) | BIT(4))
> -#define AQCTL_PRD_MASK		(BIT(3) | BIT(2))
> -#define AQCTL_PRD_FRCLOW	BIT(2)
> -#define AQCTL_PRD_FRCHIGH	BIT(3)
> -#define AQCTL_PRD_FRCTOGGLE	(BIT(3) | BIT(2))
> -#define AQCTL_ZRO_MASK		(BIT(1) | BIT(0))
> -#define AQCTL_ZRO_FRCLOW	BIT(0)
> -#define AQCTL_ZRO_FRCHIGH	BIT(1)
> -#define AQCTL_ZRO_FRCTOGGLE	(BIT(1) | BIT(0))
> -
> -#define AQCTL_CHANA_POLNORMAL	(AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \
> -				AQCTL_ZRO_FRCHIGH)
> -#define AQCTL_CHANA_POLINVERSED	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \
> -				AQCTL_ZRO_FRCLOW)
> -#define AQCTL_CHANB_POLNORMAL	(AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \
> -				AQCTL_ZRO_FRCHIGH)
> -#define AQCTL_CHANB_POLINVERSED	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \
> -				AQCTL_ZRO_FRCLOW)
> -
> -#define AQSFRC_RLDCSF_MASK	(BIT(7) | BIT(6))
> -#define AQSFRC_RLDCSF_ZRO	0
> -#define AQSFRC_RLDCSF_PRD	BIT(6)
> -#define AQSFRC_RLDCSF_ZROPRD	BIT(7)
> -#define AQSFRC_RLDCSF_IMDT	(BIT(7) | BIT(6))
> -
> -#define AQCSFRC_CSFB_MASK	(BIT(3) | BIT(2))
> -#define AQCSFRC_CSFB_FRCDIS	0
> -#define AQCSFRC_CSFB_FRCLOW	BIT(2)
> -#define AQCSFRC_CSFB_FRCHIGH	BIT(3)
> -#define AQCSFRC_CSFB_DISSWFRC	(BIT(3) | BIT(2))
> -#define AQCSFRC_CSFA_MASK	(BIT(1) | BIT(0))
> -#define AQCSFRC_CSFA_FRCDIS	0
> -#define AQCSFRC_CSFA_FRCLOW	BIT(0)
> -#define AQCSFRC_CSFA_FRCHIGH	BIT(1)
> -#define AQCSFRC_CSFA_DISSWFRC	(BIT(1) | BIT(0))
> -
> -#define NUM_PWM_CHANNEL		2	/* EHRPWM channels */
> +/* TBCTL: CTRMODE field (bits [1:0]) */
> +#define TBCTL_CTRMODE_MASK	GENMASK(1, 0)
> +#define TBCTL_CTRMODE_UP	FIELD_PREP(TBCTL_CTRMODE_MASK, 0)
> +#define TBCTL_CTRMODE_DOWN	FIELD_PREP(TBCTL_CTRMODE_MASK, 1)
> +#define TBCTL_CTRMODE_UPDOWN	FIELD_PREP(TBCTL_CTRMODE_MASK, 2)
> +#define TBCTL_CTRMODE_FREEZE	FIELD_PREP(TBCTL_CTRMODE_MASK, 3)
> +
> +/* TBCTL: PRDLD bit (bit [3]) */
> +#define TBCTL_PRDLD_MASK	GENMASK(3, 3)
> +#define TBCTL_PRDLD_SHDW	FIELD_PREP(TBCTL_PRDLD_MASK, 0)  /* shadow load */
> +#define TBCTL_PRDLD_IMDT	FIELD_PREP(TBCTL_PRDLD_MASK, 1)  /* immediate */
> +
> +/* TBCTL: high‑speed prescale (bits [9:7]) */
> +#define TBCTL_HSPCLKDIV_MASK	GENMASK(9, 7)
> +/* TBCTL: clock prescale (bits [12:10]) */
> +#define TBCTL_CLKDIV_MASK	GENMASK(12, 10)

This is different than before. Please only change the way the constants
are defined and not their values.

> +
> +#define CLKDIV_MAX	7
> +#define HSPCLKDIV_MAX	7
> +#define PERIOD_MAX	0xFFFF
> +
> +/*
> + * ----------------------------------------------------------------
> + * Compare module registers
> + * ----------------------------------------------------------------
> + */
> +#define CMPA	0x12
> +#define CMPB	0x14
> +
> +/*
> + * ----------------------------------------------------------------
> + * Action Qualifier (AQ) module registers
> + * ----------------------------------------------------------------
> + */
> +#define AQCTLA	0x16
> +#define AQCTLB	0x18
> +#define AQSFRC	0x1A
> +#define AQCSFRC	0x1
> +
> +/* AQCTL: event-action fields */
> +/*   ZRO  = bits [1:0] */
> +/*   PRD  = bits [3:2] */
> +/*   CAU  = bits [5:4] */
> +/*   CBU  = bits [9:8] */
> +#define AQCTL_ZRO_MASK	GENMASK(1, 0)
> +#define AQCTL_PRD_MASK	GENMASK(3, 2)
> +#define AQCTL_CAU_MASK	GENMASK(5, 4)
> +#define AQCTL_CBU_MASK	GENMASK(9, 8)
> +
> +/* common action codes (2‑bit) */
> +#define AQCTL_FRCLOW	1
> +#define AQCTL_FRCHIGH	2
> +#define AQCTL_FRCTOGGLE	3
> +
> +/* ZRO actions */
> +#define AQCTL_ZRO_FRCLOW	FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCLOW)
> +#define AQCTL_ZRO_FRCHIGH	FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCHIGH)
> +#define AQCTL_ZRO_FRCTOGGLE	FIELD_PREP(AQCTL_ZRO_MASK, AQCTL_FRCTOGGLE)
> +
> +/* PRD actions */
> +#define AQCTL_PRD_FRCLOW	FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCLOW)
> +#define AQCTL_PRD_FRCHIGH	FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCHIGH)
> +#define AQCTL_PRD_FRCTOGGLE	FIELD_PREP(AQCTL_PRD_MASK, AQCTL_FRCTOGGLE)
> +
> +/* CAU actions */
> +#define AQCTL_CAU_FRCLOW	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCLOW)
> +#define AQCTL_CAU_FRCHIGH	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCHIGH)
> +#define AQCTL_CAU_FRCTOGGLE	FIELD_PREP(AQCTL_CAU_MASK, AQCTL_FRCTOGGLE)
> +
> +/* CBU actions */
> +#define AQCTL_CBU_FRCLOW	FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCLOW)
> +#define AQCTL_CBU_FRCHIGH	FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCHIGH)
> +#define AQCTL_CBU_FRCTOGGLE	FIELD_PREP(AQCTL_CBU_MASK, AQCTL_FRCTOGGLE)
> +
> +/* predefined channel‑polarity combos */
> +#define AQCTL_CHANA_POLNORMAL	\
> +	(AQCTL_CAU_FRCLOW  | AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH)
> +#define AQCTL_CHANA_POLINVERSED	\
> +	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW  | AQCTL_ZRO_FRCLOW)
> +
> +#define AQCTL_CHANB_POLNORMAL	\
> +	(AQCTL_CBU_FRCLOW  | AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH)
> +#define AQCTL_CHANB_POLINVERSED	\
> +	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW  | AQCTL_ZRO_FRCLOW)
> +
> +/* AQSFRC: RLDCSF (bits [7:6]) */
> +#define AQSFRC_RLDCSF_MASK	GENMASK(7, 6)
> +#define AQSFRC_RLDCSF_ZRO	FIELD_PREP(AQSFRC_RLDCSF_MASK, 0)
> +#define AQSFRC_RLDCSF_PRD	FIELD_PREP(AQSFRC_RLDCSF_MASK, 1)
> +#define AQSFRC_RLDCSF_ZROPRD	FIELD_PREP(AQSFRC_RLDCSF_MASK, 2)
> +#define AQSFRC_RLDCSF_IMDT	FIELD_PREP(AQSFRC_RLDCSF_MASK, 3)
> +
> +/* AQCSFRC: CSFB (bits [3:2]), CSFA (bits [1:0]) */
> +#define AQCSFRC_CSFB_MASK	GENMASK(3, 2)
> +#define AQCSFRC_CSFA_MASK	GENMASK(1, 0)
> +
> +#define AQCSFRC_CSFB_FRCDIS	FIELD_PREP(AQCSFRC_CSFB_MASK, 0)
> +#define AQCSFRC_CSFB_FRCLOW	FIELD_PREP(AQCSFRC_CSFB_MASK, 1)
> +#define AQCSFRC_CSFB_FRCHIGH	FIELD_PREP(AQCSFRC_CSFB_MASK, 2)
> +#define AQCSFRC_CSFB_DISSWFRC	FIELD_PREP(AQCSFRC_CSFB_MASK, 3)
> +
> +#define AQCSFRC_CSFA_FRCDIS	FIELD_PREP(AQCSFRC_CSFA_MASK, 0)
> +#define AQCSFRC_CSFA_FRCLOW	FIELD_PREP(AQCSFRC_CSFA_MASK, 1)
> +#define AQCSFRC_CSFA_FRCHIGH	FIELD_PREP(AQCSFRC_CSFA_MASK, 2)
> +#define AQCSFRC_CSFA_DISSWFRC	FIELD_PREP(AQCSFRC_CSFA_MASK, 3)
> +
> +/* Number of EHRPWM channels */
> +#define NUM_PWM_CHANNEL	2U

That U is quite useless, a plain 2 does well.
 
>  struct ehrpwm_context {
>  	u16 tbctl;
> @@ -167,8 +204,8 @@ static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
>  			*prescale_div = (1 << clkdiv) *
>  					(hspclkdiv ? (hspclkdiv * 2) : 1);
>  			if (*prescale_div > rqst_prescaler) {
> -				*tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) |
> -					(hspclkdiv << TBCTL_HSPCLKDIV_SHIFT);
> +				*tb_clk_div = FIELD_PREP(TBCTL_CLKDIV_MASK, clkdiv) |
> +							FIELD_PREP(TBCTL_HSPCLKDIV_MASK, hspclkdiv);

I'd like to have this change in a separate patch. Is it obvious that the
new variant has the same semantic as the old? Or does this introduce
changes for some inputs?

Best regards
Uwe
[PATCH v4 2/4] pwm: tiehrpwm: add get_state function to retrieve PWM channel state
Posted by Rafael V. Volkmer 7 months, 4 weeks ago
The ehrpwm driver was missing a get_state function, which is required
to properly retrieve the current state of the PWM channel. Add the
ehrpwm_get_state() function, allowing users to query the enabled state,
period, duty cycle, and polarity of the PWM output.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 97 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 1ead1aa91a1a..cde331a73696 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -68,7 +68,9 @@
 #define AQCTL_ZRO_MASK	GENMASK(1, 0)
 #define AQCTL_PRD_MASK	GENMASK(3, 2)
 #define AQCTL_CAU_MASK	GENMASK(5, 4)
+#define AQCTL_CAD_MASK	GENMASK(7, 6)
 #define AQCTL_CBU_MASK	GENMASK(9, 8)
+#define AQCTL_CBD_MASK	GENMASK(11, 10)
 
 /* common action codes (2‑bit) */
 #define AQCTL_FRCLOW	1
@@ -470,9 +472,104 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	return err;
 }
 
+static int ehrpwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+			    struct pwm_state *state)
+{
+	int ret = 0;
+
+	struct ehrpwm_pwm_chip *pc = NULL;
+
+	/* Registers */
+	u16 aqcsfrc_reg, aqctl_reg, tbprd_reg, cmpa_reg;
+
+	/* Bits */
+	u8 csf_bits;
+
+	/* Values */
+	u64 period_cycles, duty_cycles;
+
+	/* Actions */
+	u8 up_action, down_action;
+
+	pc = to_ehrpwm_pwm_chip(chip);
+
+	/*
+	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
+	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
+	 */
+	if (pwm->hwpwm == 0) {
+		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
+		csf_bits = FIELD_GET(AQCSFRC_CSFA_MASK, aqcsfrc_reg);
+		aqctl_reg = readw(pc->mmio_base + AQCTLA);
+	} else {
+		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
+		csf_bits = FIELD_GET(AQCSFRC_CSFB_MASK, aqcsfrc_reg);
+		aqctl_reg = readw(pc->mmio_base + AQCTLB);
+	}
+
+	if (csf_bits)
+		state->enabled = false;
+	else if (aqctl_reg)
+		state->enabled = true;
+	else
+		state->enabled = false;
+
+	tbprd_reg = readw(pc->mmio_base + TBPRD);
+	period_cycles = (u64)tbprd_reg + 1u;
+
+	/*
+	 * period (in ns) = (period_cycles * 1e9) / clk_rate
+	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
+	 */
+	state->period = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, pc->clk_rate);
+
+	cmpa_reg = readw(pc->mmio_base + CMPA);
+	duty_cycles = cmpa_reg;
+
+	/*
+	 * duty_cycle (in ns) = (duty_cycles * 1e9) / clk_rate
+	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
+	 */
+	state->duty_cycle = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, pc->clk_rate);
+
+	/*
+	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
+	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
+	 */
+	if (pwm->hwpwm == 0) {
+		aqctl_reg = readw(pc->mmio_base + AQCTLA);
+		up_action = FIELD_GET(AQCTL_CAU_MASK, aqctl_reg);
+		down_action = FIELD_GET(AQCTL_CAD_MASK, aqctl_reg);
+	} else {
+		aqctl_reg = readw(pc->mmio_base + AQCTLB);
+		up_action = FIELD_GET(AQCTL_CBU_MASK, aqctl_reg);
+		down_action = FIELD_GET(AQCTL_CBD_MASK, aqctl_reg);
+	}
+
+	/*
+	 * Evaluate the actions to determine the PWM polarity:
+	 *  - If an up-count event sets the output (AQCTL_FRCHIGH) and a down-count
+	 *    event clears it (AQ_CLEAR), then polarity is NORMAL.
+	 *  - If an up-count event clears the output (AQ_CLEAR) and a down-count
+	 *    event sets it (AQCTL_FRCLOW), then polarity is INVERSED.
+	 */
+	if (up_action == AQCTL_FRCHIGH && down_action == AQCTL_FRCLOW) {
+		state->polarity = PWM_POLARITY_NORMAL;
+	} else if (up_action == AQCTL_FRCLOW && down_action == AQCTL_FRCHIGH) {
+		state->polarity = PWM_POLARITY_INVERSED;
+	} else {
+		state->polarity = PWM_POLARITY_NORMAL;
+		dev_dbg(&chip->dev, "ehrpwm: unknown polarity bits (0x%x/0x%x), defaulting to NORMAL\n",
+			up_action, down_action);
+	}
+
+	return ret;
+}
+
 static const struct pwm_ops ehrpwm_pwm_ops = {
 	.free = ehrpwm_pwm_free,
 	.apply = ehrpwm_pwm_apply,
+	.get_state = ehrpwm_get_state,
 };
 
 static const struct of_device_id ehrpwm_of_match[] = {
-- 
2.25.1

Re: [PATCH v4 2/4] pwm: tiehrpwm: add get_state function to retrieve PWM channel state
Posted by Uwe Kleine-König 7 months ago
Hello Rafael,

On Sat, Apr 19, 2025 at 04:55:55PM -0300, Rafael V. Volkmer wrote:
> The ehrpwm driver was missing a get_state function, which is required
> to properly retrieve the current state of the PWM channel. Add the
> ehrpwm_get_state() function, allowing users to query the enabled state,
> period, duty cycle, and polarity of the PWM output.
> 
> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
> ---
>  drivers/pwm/pwm-tiehrpwm.c | 97 ++++++++++++++++++++++++++++++++++++++
>  1 file changed, 97 insertions(+)
> 
> diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
> index 1ead1aa91a1a..cde331a73696 100644
> --- a/drivers/pwm/pwm-tiehrpwm.c
> +++ b/drivers/pwm/pwm-tiehrpwm.c
> @@ -68,7 +68,9 @@
>  #define AQCTL_ZRO_MASK	GENMASK(1, 0)
>  #define AQCTL_PRD_MASK	GENMASK(3, 2)
>  #define AQCTL_CAU_MASK	GENMASK(5, 4)
> +#define AQCTL_CAD_MASK	GENMASK(7, 6)
>  #define AQCTL_CBU_MASK	GENMASK(9, 8)
> +#define AQCTL_CBD_MASK	GENMASK(11, 10)
>  
>  /* common action codes (2‑bit) */
>  #define AQCTL_FRCLOW	1
> @@ -470,9 +472,104 @@ static int ehrpwm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>  	return err;
>  }
>  
> +static int ehrpwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +			    struct pwm_state *state)
> +{
> +	int ret = 0;
> +
> +	struct ehrpwm_pwm_chip *pc = NULL;
> +
> +	/* Registers */
> +	u16 aqcsfrc_reg, aqctl_reg, tbprd_reg, cmpa_reg;
> +
> +	/* Bits */
> +	u8 csf_bits;
> +
> +	/* Values */
> +	u64 period_cycles, duty_cycles;
> +
> +	/* Actions */
> +	u8 up_action, down_action;
> +
> +	pc = to_ehrpwm_pwm_chip(chip);
> +
> +	/*
> +	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
> +	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
> +	 */
> +	if (pwm->hwpwm == 0) {
> +		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
> +		csf_bits = FIELD_GET(AQCSFRC_CSFA_MASK, aqcsfrc_reg);
> +		aqctl_reg = readw(pc->mmio_base + AQCTLA);
> +	} else {
> +		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
> +		csf_bits = FIELD_GET(AQCSFRC_CSFB_MASK, aqcsfrc_reg);
> +		aqctl_reg = readw(pc->mmio_base + AQCTLB);
> +	}
> +
> +	if (csf_bits)
> +		state->enabled = false;
> +	else if (aqctl_reg)
> +		state->enabled = true;
> +	else
> +		state->enabled = false;

if (csf_bits || !aqctl_reg) {
	state->enabled = false;
	return 0;
}

?
	
> +
> +	tbprd_reg = readw(pc->mmio_base + TBPRD);
> +	period_cycles = (u64)tbprd_reg + 1u;
> +
> +	/*
> +	 * period (in ns) = (period_cycles * 1e9) / clk_rate
> +	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
> +	 */
> +	state->period = DIV_ROUND_UP_ULL(period_cycles * NSEC_PER_SEC, pc->clk_rate);
> +
> +	cmpa_reg = readw(pc->mmio_base + CMPA);
> +	duty_cycles = cmpa_reg;

duty_cycles = readw(pc->mmio_base + CMPA);

and drop the otherwise unused cmpa_reg.

I would expect that you need to read CMPB for hwpwm == 1?

I see the naming is consistent with ehrpwm_pwm_config, but I'd prefer
period_ticks and duty_ticks over period_cycles and duty_cycles. Then
it's clear that the unit is clock ticks and not ns.

> +	/*
> +	 * duty_cycle (in ns) = (duty_cycles * 1e9) / clk_rate
> +	 * Using DIV_ROUND_UP_ULL to avoid floating-point operations.
> +	 */
> +	state->duty_cycle = DIV_ROUND_UP_ULL(duty_cycles * NSEC_PER_SEC, pc->clk_rate);
> +
> +	/*
> +	 * The 'hwpwm' field identifies which hardware output channel (e.g.,
> +	 * 0 for channel A and 1 for channel B) of the eHRPWM module is in use.
> +	 */
> +	if (pwm->hwpwm == 0) {
> +		aqctl_reg = readw(pc->mmio_base + AQCTLA);
> +		up_action = FIELD_GET(AQCTL_CAU_MASK, aqctl_reg);
> +		down_action = FIELD_GET(AQCTL_CAD_MASK, aqctl_reg);
> +	} else {
> +		aqctl_reg = readw(pc->mmio_base + AQCTLB);
> +		up_action = FIELD_GET(AQCTL_CBU_MASK, aqctl_reg);
> +		down_action = FIELD_GET(AQCTL_CBD_MASK, aqctl_reg);
> +	}

When you send a new revision, please start a new thread.

Best regards
Uwe
[PATCH v4 3/4] pwm: tiehrpwm: ensure clock and runtime PM are enabled if hardware is active
Posted by Rafael V. Volkmer 7 months, 4 weeks ago
During probe, if the hardware is already active, it is not guaranteed
that the clock is enabled. To address this, ehrpwm_pwm_probe() now
checks whether the PWM is enabled and ensures that the necessary
resources are initialized.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 46 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index cde331a73696..23530d53e177 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -583,15 +583,50 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
 	struct ehrpwm_pwm_chip *pc;
+	struct pwm_device *pwm;
+	struct pwm_state state;
 	struct pwm_chip *chip;
 	struct clk *clk;
+	bool tbclk_enabled;
 	int ret;
 
+	u16 aqcsfrc_reg, aqctl_reg;
+
+	u8 csf_bits;
+
 	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
 	if (IS_ERR(chip))
 		return PTR_ERR(chip);
 	pc = to_ehrpwm_pwm_chip(chip);
 
+	pwm = &chip->pwms[0];
+
+	if (pwm->hwpwm == 0) {
+		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
+		csf_bits = FIELD_GET(AQCSFRC_CSFA_MASK, aqcsfrc_reg);
+		aqctl_reg = readw(pc->mmio_base + AQCTLA);
+	} else {
+		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
+		csf_bits = FIELD_GET(AQCSFRC_CSFB_MASK, aqcsfrc_reg);
+		aqctl_reg = readw(pc->mmio_base + AQCTLB);
+	}
+
+	if (csf_bits)
+		state.enabled = false;
+	else if (aqctl_reg)
+		state.enabled = true;
+	else
+		state.enabled = false;
+
+	if (state.enabled) {
+		ret = clk_enable(pc->tbclk);
+		if (ret) {
+			dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
+			goto err_pwmchip_remove;
+		}
+		tbclk_enabled = true;
+	}
+
 	clk = devm_clk_get(&pdev->dev, "fck");
 	if (IS_ERR(clk)) {
 		if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
@@ -626,6 +661,15 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	if (state.enabled) {
+		ret = pm_runtime_get_sync(&pdev->dev);
+		if (ret < 0) {
+			dev_err_probe(&pdev->dev, ret, "pm_runtime_get_sync() failed");
+			clk_disable_unprepare(pc->tbclk);
+			goto err_pwmchip_remove;
+			}
+	}
+
 	ret = pwmchip_add(chip);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
@@ -637,6 +681,8 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
 
 	return 0;
 
+err_pwmchip_remove:
+		pwmchip_remove(chip);
 err_clk_unprepare:
 	clk_unprepare(pc->tbclk);
 
-- 
2.25.1
Re: [PATCH v4 3/4] pwm: tiehrpwm: ensure clock and runtime PM are enabled if hardware is active
Posted by Uwe Kleine-König 7 months ago
Hello Rafael,

On Sat, Apr 19, 2025 at 04:58:30PM -0300, Rafael V. Volkmer wrote:
> During probe, if the hardware is already active, it is not guaranteed
> that the clock is enabled. To address this, ehrpwm_pwm_probe() now
> checks whether the PWM is enabled and ensures that the necessary
> resources are initialized.
> 
> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
> ---
>  drivers/pwm/pwm-tiehrpwm.c | 46 ++++++++++++++++++++++++++++++++++++++
>  1 file changed, 46 insertions(+)
> 
> diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
> index cde331a73696..23530d53e177 100644
> --- a/drivers/pwm/pwm-tiehrpwm.c
> +++ b/drivers/pwm/pwm-tiehrpwm.c
> @@ -583,15 +583,50 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  {
>  	struct device_node *np = pdev->dev.of_node;
>  	struct ehrpwm_pwm_chip *pc;
> +	struct pwm_device *pwm;
> +	struct pwm_state state;
>  	struct pwm_chip *chip;
>  	struct clk *clk;
> +	bool tbclk_enabled;
>  	int ret;
>  
> +	u16 aqcsfrc_reg, aqctl_reg;
> +
> +	u8 csf_bits;
> +
>  	chip = devm_pwmchip_alloc(&pdev->dev, NUM_PWM_CHANNEL, sizeof(*pc));
>  	if (IS_ERR(chip))
>  		return PTR_ERR(chip);
>  	pc = to_ehrpwm_pwm_chip(chip);
>  
> +	pwm = &chip->pwms[0];
> +
> +	if (pwm->hwpwm == 0) {

This is always true. And actually a lowlevel driver shouldn't need 
chip->pwms at all.

> +		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
> +		csf_bits = FIELD_GET(AQCSFRC_CSFA_MASK, aqcsfrc_reg);
> +		aqctl_reg = readw(pc->mmio_base + AQCTLA);
> +	} else {
> +		aqcsfrc_reg = readw(pc->mmio_base + AQCSFRC);
> +		csf_bits = FIELD_GET(AQCSFRC_CSFB_MASK, aqcsfrc_reg);
> +		aqctl_reg = readw(pc->mmio_base + AQCTLB);
> +	}
> +
> +	if (csf_bits)
> +		state.enabled = false;
> +	else if (aqctl_reg)
> +		state.enabled = true;
> +	else
> +		state.enabled = false;
> +
> +	if (state.enabled) {
> +		ret = clk_enable(pc->tbclk);
> +		if (ret) {
> +			dev_err_probe(&pdev->dev, ret, "clk_prepare_enable() failed");
> +			goto err_pwmchip_remove;
> +		}
> +		tbclk_enabled = true;
> +	}

You don't check the 2nd PWM, this is needed however. You even need to
call pm_runtime_get_sync() twice if both PWMs are on.

>  	clk = devm_clk_get(&pdev->dev, "fck");
>  	if (IS_ERR(clk)) {
>  		if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
> @@ -626,6 +661,15 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  		return ret;
>  	}
>  
> +	if (state.enabled) {
> +		ret = pm_runtime_get_sync(&pdev->dev);
> +		if (ret < 0) {
> +			dev_err_probe(&pdev->dev, ret, "pm_runtime_get_sync() failed");
> +			clk_disable_unprepare(pc->tbclk);
> +			goto err_pwmchip_remove;
> +			}

indention inconsistency.

> +	}
> +
>  	ret = pwmchip_add(chip);
>  	if (ret < 0) {
>  		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
> @@ -637,6 +681,8 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
>  
>  	return 0;
>  
> +err_pwmchip_remove:
> +		pwmchip_remove(chip);

That's wrong. When the `goto err_pwmchip_remove` is taken, pwmchip_add
wasn't called yet.

>  err_clk_unprepare:
>  	clk_unprepare(pc->tbclk);
>  

Best regards
Uwe
[PATCH v4 4/4] pwm: tiehrpwm: drop unnecessary parentheses and fix spacing
Posted by Rafael V. Volkmer 7 months, 4 weeks ago
The comparison in pwm-tiehrpwm.c triggered “UNNECESSARY_PARENTHESES” and
“PARENTHESIS_ALIGNMENT” when wrapping the second clause in extra parens.
This removes superfluous parentheses, aligns continued lines under the ‘if’,
and ensures operators are properly spaced.

Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>
---
 drivers/pwm/pwm-tiehrpwm.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c
index 23530d53e177..73c3dd57a50b 100644
--- a/drivers/pwm/pwm-tiehrpwm.c
+++ b/drivers/pwm/pwm-tiehrpwm.c
@@ -285,8 +285,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 	 * same period register for multiple channels.
 	 */
 	for (i = 0; i < NUM_PWM_CHANNEL; i++) {
-		if (pc->period_cycles[i] &&
-				(pc->period_cycles[i] != period_cycles)) {
+		if (pc->period_cycles[i] && pc->period_cycles[i] != period_cycles) {
 			/*
 			 * Allow channel to reconfigure period if no other
 			 * channels being configured.
@@ -304,7 +303,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 	pc->period_cycles[pwm->hwpwm] = period_cycles;
 
 	/* Configure clock prescaler to support Low frequency PWM wave */
-	if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval,
+	if (set_prescale_div(period_cycles / PERIOD_MAX, &ps_divval,
 			     &tb_divval)) {
 		dev_err(pwmchip_parent(chip), "Unsupported values\n");
 		return -EINVAL;
-- 
2.25.1

Re: [PATCH v4 4/4] pwm: tiehrpwm: drop unnecessary parentheses and fix spacing
Posted by Uwe Kleine-König 7 months ago
On Sat, Apr 19, 2025 at 05:01:00PM -0300, Rafael V. Volkmer wrote:
> The comparison in pwm-tiehrpwm.c triggered “UNNECESSARY_PARENTHESES” and
> “PARENTHESIS_ALIGNMENT” when wrapping the second clause in extra parens.

What is the source of "UNNECESSARY_PARENTHESES" and
"PARENTHESIS_ALIGNMENT"?

> This removes superfluous parentheses, aligns continued lines under the ‘if’,

The "aligns continued lines under the ‘if’" part is wrong?

> and ensures operators are properly spaced.
> 
> Signed-off-by: Rafael V. Volkmer <rafael.v.volkmer@gmail.com>

Best regards
Uwe