mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Rafael V. Volkmer" <rafael.v.volkmer@gmail.com>
To: rafael.v.volkmer@gmail.com
Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
	ukleinek@kernel.org
Subject: [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe()
Date: Thu,  6 Feb 2025 00:18:52 -0300	[thread overview]
Message-ID: <20250206031852.64853-1-rafael.v.volkmer@gmail.com> (raw)
In-Reply-To: <20250206031347.63700-1-rafael.v.volkmer@gmail.com>

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


  reply	other threads:[~2025-02-06  3:19 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-04 18:55 [PATCH] " Rafael V. Volkmer
2025-02-05 11:12 ` Uwe Kleine-König
2025-02-06  3:13   ` [PATCH v2 0/1] pwm: tiehrpwm: ensures that state.enabled is Rafael V. Volkmer
2025-02-06  3:18     ` Rafael V. Volkmer [this message]
2025-02-07  8:49       ` [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe() Uwe Kleine-König
2025-02-07 21:29         ` [PATCH v3 1/3] pwm: tiehrpwm: replace manual bit definitions with bitfield.h macros Rafael V. Volkmer
2025-02-07 21:32           ` [PATCH v3 2/3] pwm: ehrpwm: add get_state function to retrieve PWM channel state Rafael V. Volkmer
2025-02-07 21:34             ` [PATCH v3 3/3] pwm: ehrpwm: ensure clock and runtime PM are enabled if hardware is active Rafael V. Volkmer
2025-03-26 11:45               ` Uwe Kleine-König
2025-04-19 19:48                 ` [PATCH v4 1/4] pwm: tiehrpwm: replace manual bit definitions with bitfield.h macros Rafael V. Volkmer
2025-04-19 19:55                   ` [PATCH v4 2/4] pwm: tiehrpwm: add get_state function to retrieve PWM channel state Rafael V. Volkmer
2025-04-19 19:58                     ` [PATCH v4 3/4] pwm: tiehrpwm: ensure clock and runtime PM are enabled if hardware is active Rafael V. Volkmer
2025-04-19 20:01                       ` [PATCH v4 4/4] pwm: tiehrpwm: drop unnecessary parentheses and fix spacing Rafael V. Volkmer
2025-05-13  9:36                         ` Uwe Kleine-König
2025-05-13  9:33                       ` [PATCH v4 3/4] pwm: tiehrpwm: ensure clock and runtime PM are enabled if hardware is active Uwe Kleine-König
2025-05-13  9:27                     ` [PATCH v4 2/4] pwm: tiehrpwm: add get_state function to retrieve PWM channel state Uwe Kleine-König
2025-05-13  9:18                   ` [PATCH v4 1/4] pwm: tiehrpwm: replace manual bit definitions with bitfield.h macros Uwe Kleine-König
2025-03-26 11:32             ` [PATCH v3 2/3] pwm: ehrpwm: add get_state function to retrieve PWM channel state Uwe Kleine-König
2025-04-20  2:21       ` [PATCH v2 1/1] pwm: tiehrpwm: ensures that state.enabled is synchronized in .probe() kernel test robot
2025-04-20  5:17       ` kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250206031852.64853-1-rafael.v.volkmer@gmail.com \
    --to=rafael.v.volkmer@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=ukleinek@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®