All of lore.kernel.org
 help / color / mirror / Atom feed
From: Conor Dooley <conor.dooley@microchip.com>
To: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
Cc: Conor Dooley <conor@kernel.org>,
	Thierry Reding <thierry.reding@gmail.com>,
	Daire McNamara <daire.mcnamara@microchip.com>,
	<linux-kernel@vger.kernel.org>, <linux-pwm@vger.kernel.org>,
	<linux-riscv@lists.infradead.org>
Subject: Re: [PATCH v12 1/2] pwm: add microchip soft ip corePWM driver
Date: Mon, 5 Dec 2022 15:21:55 +0000	[thread overview]
Message-ID: <Y44Mk2nGu1Zeq7QQ@wendy> (raw)
In-Reply-To: <20221130103755.lhil2jaw3oufr2sf@pengutronix.de>

[-- Attachment #1: Type: text/plain, Size: 16414 bytes --]

Hey Uwe,

Preserving the context..

On Wed, Nov 30, 2022 at 11:37:55AM +0100, Uwe Kleine-König wrote:
> On Wed, Nov 30, 2022 at 09:53:51AM +0000, Conor Dooley wrote:
> > On Mon, Nov 21, 2022 at 03:29:39PM +0000, Conor Dooley wrote:
> > > On Thu, Nov 17, 2022 at 10:03:13PM +0000, Conor Dooley wrote:
> > > > On Thu, Nov 17, 2022 at 10:04:33PM +0100, Uwe Kleine-König wrote:
> > > > > On Thu, Nov 17, 2022 at 05:38:26PM +0000, Conor Dooley wrote:
> > > > > > On Thu, Nov 17, 2022 at 05:49:50PM +0100, Uwe Kleine-König wrote:
> > > > > > > Hello Conor,
> > > > > > 
> > > > > > Hello Uwe,
> > > > > > 
> > > > > > > On Thu, Nov 10, 2022 at 09:35:12AM +0000, Conor Dooley wrote:
> > > > > > > > [...]
> > > > > > > > +
> > > > > > > > +static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > > > > > +				 bool enable, u64 period)
> > > > > > > > +{
> > > > > > > > +	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
> > > > > > > > +	u8 channel_enable, reg_offset, shift;
> > > > > > > > +
> > > > > > > > +	/*
> > > > > > > > +	 * There are two adjacent 8 bit control regs, the lower reg controls
> > > > > > > > +	 * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg
> > > > > > > > +	 * and if so, offset by the bus width.
> > > > > > > > +	 */
> > > > > > > > +	reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3);
> > > > > > > > +	shift = pwm->hwpwm & 7;
> > > > > > > > +
> > > > > > > > +	channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset);
> > > > > > > > +	channel_enable &= ~(1 << shift);
> > > > > > > > +	channel_enable |= (enable << shift);
> > > > > > > > +
> > > > > > > > +	writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset);
> > > > > > > > +	mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm);
> > > > > > > > +	mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm;
> > > > > > > > +
> > > > > > > > +	/*
> > > > > > > > +	 * Notify the block to update the waveform from the shadow registers.
> > > > > > > > +	 * The updated values will not appear on the bus until they have been
> > > > > > > > +	 * applied to the waveform at the beginning of the next period. We must
> > > > > > > > +	 * write these registers and wait for them to be applied before
> > > > > > > > +	 * considering the channel enabled.
> > > > > > > > +	 * If the delay is under 1 us, sleep for at least 1 us anyway.
> > > > > > > > +	 */
> > > > > > > > +	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
> > > > > > > > +		u64 delay;
> > > > > > > > +
> > > > > > > > +		delay = div_u64(period, 1000u) ? : 1u;
> > > > > > > > +		writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > > > > > > +		usleep_range(delay, delay * 2);
> > > > > > > > +	}
> > > > > > > 
> > > > > > > In some cases the delay could be prevented. e.g. when going from one
> > > > > > > disabled state to another. If you don't want to complicate the driver
> > > > > > > here, maybe point it out in a comment at least?
> > > > > > 
> > > > > > Maybe this is my naivity talking, but I'd rather wait. Is there not the
> > > > > > chance that we re-enter pwm_apply() before the update has actually gone
> > > > > > through?
> > > > > 
> > > > > My idea was to do something like that:
> > > > > 
> > > > > 	int mchp_core_pwm_apply(....)
> > > > > 	{
> > > > > 		if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
> > > > > 			/*
> > > > > 			 * We're still waiting for an update, don't
> > > > > 			 * interfer until it's completed.
> > > > > 			 */
> > > > > 			while (readl_relaxed(mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD)) {
> > > > > 				cpu_relax();
> > > > > 				if (waited_unreasonably_long())
> > > > > 					return -ETIMEOUT;
> > > > > 			}
> > > > > 		}
> > > > > 
> > > > > 		update_period_and_duty(...);
> > > > > 		return 0;
> > > > > 	}
> > > 
> > > So I was doing some fiddling, and the following works reasonably well:
> > > 	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
> > > 		u32 delay = MCHPCOREPWM_TIMEOUT_US;
> > > 		u32 sync_upd;
> > > 		int ret;
> > > 
> > > 		writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 
> > > 		ret = read_poll_timeout(readl, sync_upd, !sync_upd, delay/100, delay,
> > > 					false, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 		if (ret)
> > > 			dev_dbg(mchp_core_pwm->chip.dev,
> > > 				"timed out waiting for shadow register sync\n");
> > > 	}
> > > 
> > > but...
> > > 
> > > > > This way you don't have to wait at all if the calls to pwm_apply() are
> > > > > infrequent. Of course this only works this way, if you can determine if
> > > > > there is a pending update.
> > > > 
> > > > Ah I think I get what you mean now about waiting for completion &
> > > > reading the bit. I don't know off the top of my head if that bit is
> > > > readable. Docs say that they're R/W but I don't know if that means that
> > > > an AXI read works or if the value is actually readable. I'll try
> > > > something like this if I can.
> > > 
> > > ...it does not implement what I think you suggested & comes with the
> > > drawback of inconsistent behaviour depending on whether the timeout is
> > > hit or not.
> > > 
> > > Instead, waiting in apply(), as you suggested, & get_state() looks to be the
> > > better option, using the same sort of logic as above, say:
> > > static int mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
> > > 					      unsigned int channel)
> > > {
> > > 	int ret;
> > > 
> > > 	/*
> > > 	 * If a shadow register is used for this PWM channel, and iff there is
> > > 	 * a pending update to the waveform, we must wait for it to be applied
> > > 	 * before attempting to read its state, as reading the registers yields
> > > 	 * the currently implemented settings, the new ones are only readable
> > > 	 * once the current period has ended.
> > > 	 *
> > > 	 * Rather large delays are possible, in the seconds, so to avoid waiting
> > > 	 * around for **too** long - cap the wait at 100 ms.
> > > 	 */
> > > 	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
> > > 		u32 delay = MCHPCOREPWM_TIMEOUT_US;
> > > 		u32 sync_upd;
> > > 
> > > 		writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 
> > > 		ret = read_poll_timeout(readl, sync_upd, !sync_upd, delay/100, delay,
> > > 					false, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 		if (ret)
> > > 			return -ETIMEDOUT;
> > > 	}
> > > 
> > > 	return 0;
> > > }
> > > 
> > > I think that strikes a good balance? We return quickly & don't blocker
> > > the caller, but simultaneously try to prevent them from either trying to
> > > apply new settings or get the current settings until the last request
> > > has gone though?
> > > 
> > > get_state() returns void though, is it valid behaviour to wait for the
> > > timeout there?
> 
> There was an approach to change that, see
> https://lore.kernel.org/linux-pwm/20220916151506.298488-1-u.kleine-koenig@pengutronix.de
> 
> I need to send a v2.
> 
> > > I had a check in the core code and found some places where the call in
> > > looks like:
> > > 	struct pwm_state s1, s2; 
> > > 	chip->ops->get_state(chip, pwm, &s1);
> > > In this case, exiting early would leave us with a completely wrong
> > > idead of the state, if it was to time out.
> > > 
> > > Either way, it seems like either way we would be misleading the caller
> > > of get_state() - perhaps the way around that is to do the wait & then
> > > just carry on with get_state()?
> > > In that scenario, you'd get the new settings where possible and the old ones
> > > otherwise.
> > > Returning if the timeout is hit would give you the new settings where possible
> > > & otherwise you'd get whatever was passed to get_state().
> > > I'm not really sure which of those two situations would be preferred?
> 
> Hmm, .get_state should not return the old state. We really want
> .get_state to return an error code. Maybe postpone that question until
> we have that?

I came into work today thinking that I could just rebase on top of your
patchset and send out a v13, but that was unfortunately not the case :/

So uh, it turns out that I was wrong about the behaviour of the
sync_update register's bit.
It turns out that that bit holds it's value until the IP block is reset,
and /does not/ get cleared at the start of the next period.
I'm really not sure how it worked when I tested the other week [0], so I
spent the first half of the day trying to figure out what on earth had
happened to my FPGA image. I must've picked the wrong image when I went
to test it the other week that had the wrong configuration somehow.

As a result, I've gone and hacked up another way of transferring the
burden of waiting - setting a timer for the period, backed by a
completion. get_state() and apply() now both check for the completion
and time out otherwise. I'm half tempted to tack RFC back onto the
series as I have not really messed with timers at all before and may
have done something off the wall.

I pushed it out (see [1] in case you'd like to look) so that the bots
can have a play with it, since it'll be a few weeks before I'll have a
chance to properly test that I've broken nothing with this.

It's not nearly as neat or contained, but still benefits from the
non-void get_state() & doesn't "confuse" a caller of get_state() with
some potential garbage.

The diff on top of the read_poll_timeout() approach from above is pasted
here. Hopefully I'll refine it a bit before sending a v13, checkpatch
may have an aneurysm with what's below. Or have a better idea and throw
it out..

Thanks again for sending that v2 ~immediately,
Conor.

0 - https://lore.kernel.org/linux-riscv/Y3uZY5mt%2FZIWk3sS@wendy/
1 - https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git/tree/drivers/pwm/pwm-microchip-core.c?h=pwm-dev-v13&id=ddbd59fb5480b1be74645f0a84d934b1f91d833d

diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c
index c88fa8f8d96d..f565e8be46b3 100644
--- a/drivers/pwm/pwm-microchip-core.c
+++ b/drivers/pwm/pwm-microchip-core.c
@@ -34,6 +34,7 @@
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
+#include <linux/timer.h>
 
 #define PREG_TO_VAL(PREG) ((PREG) + 1)
 
@@ -47,12 +48,14 @@
 #define MCHPCOREPWM_POSEDGE(i)	(0x10 + 0x08 * (i)) /* 0x10, 0x18, ..., 0x88 */
 #define MCHPCOREPWM_NEGEDGE(i)	(0x14 + 0x08 * (i)) /* 0x14, 0x1c, ..., 0x8c */
 #define MCHPCOREPWM_SYNC_UPD	0xe4
-#define MCHPCOREPWM_TIMEOUT_US	100000u
+#define MCHPCOREPWM_TIMEOUT_MS	100u
 
 struct mchp_core_pwm_chip {
 	struct pwm_chip chip;
 	struct clk *clk;
 	struct mutex lock; /* protect the shared period */
+	struct completion sync_update_complete;
+	struct timer_list sync_update_timer;
 	void __iomem *base;
 	u32 sync_update_mask;
 	u16 channel_enabled;
@@ -91,9 +94,54 @@ static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
 	 * applied to the waveform at the beginning of the next period.
 	 * This is a NO-OP if the channel does not have shadow registers.
 	 */
+	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
+		u64 delay;
+
+		/* Have to convert to jiffies... */
+		delay = div_u64(period, 1000000u) ? : 1u;
+		reinit_completion(&mchp_core_pwm->sync_update_complete);
+		mchp_core_pwm->sync_update_timer.expires = jiffies + delay;
+		add_timer(&mchp_core_pwm->sync_update_timer);
+	}
+}
+
+static int mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
+					      unsigned int channel)
+{
+	/*
+	 * If a shadow register is used for this PWM channel, and iff there is
+	 * a pending update to the waveform, we must wait for it to be applied
+	 * before attempting to read its state, as reading the registers yields
+	 * the currently implemented settings, the new ones are only readable
+	 * once the current period has ended.
+	 *
+	 * Rather large delays are possible, in the seconds, so to avoid waiting
+	 * around for **too** long - cap the wait at 100 ms.
+	 */
+	if (!timer_pending(&mchp_core_pwm->sync_update_timer))
+		return 0;
+
+	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
+		int ret;
+		ret = wait_for_completion_interruptible_timeout(&mchp_core_pwm->sync_update_complete,
+								msecs_to_jiffies(MCHPCOREPWM_TIMEOUT_MS));
+		if (!ret)
+			return -ETIMEDOUT;
+
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
 
-	writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
+static void mchp_core_pwm_complete_sync_update(struct timer_list *t)
+{
+	struct mchp_core_pwm_chip *mchp_core_pwm = from_timer(mchp_core_pwm,
+							      t, sync_update_timer);
 
+	complete(&mchp_core_pwm->sync_update_complete);
+	del_timer(&mchp_core_pwm->sync_update_timer);
 }
 
 static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate,
@@ -181,35 +229,6 @@ static inline void mchp_core_pwm_apply_period(struct mchp_core_pwm_chip *mchp_co
 	writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
 }
 
-static int mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
-					      unsigned int channel)
-{
-	/*
-	 * If a shadow register is used for this PWM channel, and iff there is
-	 * a pending update to the waveform, we must wait for it to be applied
-	 * before attempting to read its state, as reading the registers yields
-	 * the currently implemented settings, the new ones are only readable
-	 * once the current period has ended.
-	 *
-	 * Rather large delays are possible, in the seconds, so to avoid waiting
-	 * around for **too** long - cap the wait at 100 ms.
-	 */
-	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
-		u32 delay = MCHPCOREPWM_TIMEOUT_US;
-		u32 sync_upd;
-		int ret;
-
-		writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
-
-		ret = read_poll_timeout(readl, sync_upd, !sync_upd, delay/100, delay,
-					false, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
-		if (ret)
-			return -ETIMEDOUT;
-	}
-
-	return 0;
-}
-
 static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
 				      const struct pwm_state *state)
 {
@@ -297,31 +316,37 @@ static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
 	int ret;
 
+	mutex_lock(&mchp_core_pwm->lock);
+
 	ret = mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
 	if (ret)
-		return ret;
-
-	mutex_lock(&mchp_core_pwm->lock);
+		goto exit_unlock;
 
 	ret = mchp_core_pwm_apply_locked(chip, pwm, state);
 
+exit_unlock:
 	mutex_unlock(&mchp_core_pwm->lock);
 
 	return ret;
 }
 
-static void mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
-				    struct pwm_state *state)
+static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+				   struct pwm_state *state)
 {
 	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
 	u64 rate;
 	u16 prescale;
 	u8 period_steps, duty_steps, posedge, negedge;
-
-	mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
+	int ret;
 
 	mutex_lock(&mchp_core_pwm->lock);
 
+	ret = mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
+	if (ret) {
+		mutex_unlock(&mchp_core_pwm->lock);
+		return ret;
+	}
+
 	if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm))
 		state->enabled = true;
 	else
@@ -351,6 +376,8 @@ static void mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pw
 	}
 
 	state->polarity = negedge < posedge ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+
+	return 0;
 }
 
 static const struct pwm_ops mchp_core_pwm_ops = {
@@ -403,6 +430,10 @@ static int mchp_core_pwm_probe(struct platform_device *pdev)
 	if (ret < 0)
 		return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
 
+	init_completion(&mchp_pwm->sync_update_complete);
+	timer_setup(&mchp_pwm->sync_update_timer, mchp_core_pwm_complete_sync_update, 0);
+	writel_relaxed(1U, mchp_pwm->base + MCHPCOREPWM_SYNC_UPD);
+
 	return 0;
 }



[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

WARNING: multiple messages have this Message-ID (diff)
From: Conor Dooley <conor.dooley@microchip.com>
To: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
Cc: Conor Dooley <conor@kernel.org>,
	Thierry Reding <thierry.reding@gmail.com>,
	Daire McNamara <daire.mcnamara@microchip.com>,
	<linux-kernel@vger.kernel.org>, <linux-pwm@vger.kernel.org>,
	<linux-riscv@lists.infradead.org>
Subject: Re: [PATCH v12 1/2] pwm: add microchip soft ip corePWM driver
Date: Mon, 5 Dec 2022 15:21:55 +0000	[thread overview]
Message-ID: <Y44Mk2nGu1Zeq7QQ@wendy> (raw)
In-Reply-To: <20221130103755.lhil2jaw3oufr2sf@pengutronix.de>


[-- Attachment #1.1: Type: text/plain, Size: 16414 bytes --]

Hey Uwe,

Preserving the context..

On Wed, Nov 30, 2022 at 11:37:55AM +0100, Uwe Kleine-König wrote:
> On Wed, Nov 30, 2022 at 09:53:51AM +0000, Conor Dooley wrote:
> > On Mon, Nov 21, 2022 at 03:29:39PM +0000, Conor Dooley wrote:
> > > On Thu, Nov 17, 2022 at 10:03:13PM +0000, Conor Dooley wrote:
> > > > On Thu, Nov 17, 2022 at 10:04:33PM +0100, Uwe Kleine-König wrote:
> > > > > On Thu, Nov 17, 2022 at 05:38:26PM +0000, Conor Dooley wrote:
> > > > > > On Thu, Nov 17, 2022 at 05:49:50PM +0100, Uwe Kleine-König wrote:
> > > > > > > Hello Conor,
> > > > > > 
> > > > > > Hello Uwe,
> > > > > > 
> > > > > > > On Thu, Nov 10, 2022 at 09:35:12AM +0000, Conor Dooley wrote:
> > > > > > > > [...]
> > > > > > > > +
> > > > > > > > +static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > > > > > +				 bool enable, u64 period)
> > > > > > > > +{
> > > > > > > > +	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
> > > > > > > > +	u8 channel_enable, reg_offset, shift;
> > > > > > > > +
> > > > > > > > +	/*
> > > > > > > > +	 * There are two adjacent 8 bit control regs, the lower reg controls
> > > > > > > > +	 * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg
> > > > > > > > +	 * and if so, offset by the bus width.
> > > > > > > > +	 */
> > > > > > > > +	reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3);
> > > > > > > > +	shift = pwm->hwpwm & 7;
> > > > > > > > +
> > > > > > > > +	channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset);
> > > > > > > > +	channel_enable &= ~(1 << shift);
> > > > > > > > +	channel_enable |= (enable << shift);
> > > > > > > > +
> > > > > > > > +	writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset);
> > > > > > > > +	mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm);
> > > > > > > > +	mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm;
> > > > > > > > +
> > > > > > > > +	/*
> > > > > > > > +	 * Notify the block to update the waveform from the shadow registers.
> > > > > > > > +	 * The updated values will not appear on the bus until they have been
> > > > > > > > +	 * applied to the waveform at the beginning of the next period. We must
> > > > > > > > +	 * write these registers and wait for them to be applied before
> > > > > > > > +	 * considering the channel enabled.
> > > > > > > > +	 * If the delay is under 1 us, sleep for at least 1 us anyway.
> > > > > > > > +	 */
> > > > > > > > +	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
> > > > > > > > +		u64 delay;
> > > > > > > > +
> > > > > > > > +		delay = div_u64(period, 1000u) ? : 1u;
> > > > > > > > +		writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > > > > > > +		usleep_range(delay, delay * 2);
> > > > > > > > +	}
> > > > > > > 
> > > > > > > In some cases the delay could be prevented. e.g. when going from one
> > > > > > > disabled state to another. If you don't want to complicate the driver
> > > > > > > here, maybe point it out in a comment at least?
> > > > > > 
> > > > > > Maybe this is my naivity talking, but I'd rather wait. Is there not the
> > > > > > chance that we re-enter pwm_apply() before the update has actually gone
> > > > > > through?
> > > > > 
> > > > > My idea was to do something like that:
> > > > > 
> > > > > 	int mchp_core_pwm_apply(....)
> > > > > 	{
> > > > > 		if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
> > > > > 			/*
> > > > > 			 * We're still waiting for an update, don't
> > > > > 			 * interfer until it's completed.
> > > > > 			 */
> > > > > 			while (readl_relaxed(mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD)) {
> > > > > 				cpu_relax();
> > > > > 				if (waited_unreasonably_long())
> > > > > 					return -ETIMEOUT;
> > > > > 			}
> > > > > 		}
> > > > > 
> > > > > 		update_period_and_duty(...);
> > > > > 		return 0;
> > > > > 	}
> > > 
> > > So I was doing some fiddling, and the following works reasonably well:
> > > 	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
> > > 		u32 delay = MCHPCOREPWM_TIMEOUT_US;
> > > 		u32 sync_upd;
> > > 		int ret;
> > > 
> > > 		writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 
> > > 		ret = read_poll_timeout(readl, sync_upd, !sync_upd, delay/100, delay,
> > > 					false, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 		if (ret)
> > > 			dev_dbg(mchp_core_pwm->chip.dev,
> > > 				"timed out waiting for shadow register sync\n");
> > > 	}
> > > 
> > > but...
> > > 
> > > > > This way you don't have to wait at all if the calls to pwm_apply() are
> > > > > infrequent. Of course this only works this way, if you can determine if
> > > > > there is a pending update.
> > > > 
> > > > Ah I think I get what you mean now about waiting for completion &
> > > > reading the bit. I don't know off the top of my head if that bit is
> > > > readable. Docs say that they're R/W but I don't know if that means that
> > > > an AXI read works or if the value is actually readable. I'll try
> > > > something like this if I can.
> > > 
> > > ...it does not implement what I think you suggested & comes with the
> > > drawback of inconsistent behaviour depending on whether the timeout is
> > > hit or not.
> > > 
> > > Instead, waiting in apply(), as you suggested, & get_state() looks to be the
> > > better option, using the same sort of logic as above, say:
> > > static int mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
> > > 					      unsigned int channel)
> > > {
> > > 	int ret;
> > > 
> > > 	/*
> > > 	 * If a shadow register is used for this PWM channel, and iff there is
> > > 	 * a pending update to the waveform, we must wait for it to be applied
> > > 	 * before attempting to read its state, as reading the registers yields
> > > 	 * the currently implemented settings, the new ones are only readable
> > > 	 * once the current period has ended.
> > > 	 *
> > > 	 * Rather large delays are possible, in the seconds, so to avoid waiting
> > > 	 * around for **too** long - cap the wait at 100 ms.
> > > 	 */
> > > 	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
> > > 		u32 delay = MCHPCOREPWM_TIMEOUT_US;
> > > 		u32 sync_upd;
> > > 
> > > 		writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 
> > > 		ret = read_poll_timeout(readl, sync_upd, !sync_upd, delay/100, delay,
> > > 					false, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > > 		if (ret)
> > > 			return -ETIMEDOUT;
> > > 	}
> > > 
> > > 	return 0;
> > > }
> > > 
> > > I think that strikes a good balance? We return quickly & don't blocker
> > > the caller, but simultaneously try to prevent them from either trying to
> > > apply new settings or get the current settings until the last request
> > > has gone though?
> > > 
> > > get_state() returns void though, is it valid behaviour to wait for the
> > > timeout there?
> 
> There was an approach to change that, see
> https://lore.kernel.org/linux-pwm/20220916151506.298488-1-u.kleine-koenig@pengutronix.de
> 
> I need to send a v2.
> 
> > > I had a check in the core code and found some places where the call in
> > > looks like:
> > > 	struct pwm_state s1, s2; 
> > > 	chip->ops->get_state(chip, pwm, &s1);
> > > In this case, exiting early would leave us with a completely wrong
> > > idead of the state, if it was to time out.
> > > 
> > > Either way, it seems like either way we would be misleading the caller
> > > of get_state() - perhaps the way around that is to do the wait & then
> > > just carry on with get_state()?
> > > In that scenario, you'd get the new settings where possible and the old ones
> > > otherwise.
> > > Returning if the timeout is hit would give you the new settings where possible
> > > & otherwise you'd get whatever was passed to get_state().
> > > I'm not really sure which of those two situations would be preferred?
> 
> Hmm, .get_state should not return the old state. We really want
> .get_state to return an error code. Maybe postpone that question until
> we have that?

I came into work today thinking that I could just rebase on top of your
patchset and send out a v13, but that was unfortunately not the case :/

So uh, it turns out that I was wrong about the behaviour of the
sync_update register's bit.
It turns out that that bit holds it's value until the IP block is reset,
and /does not/ get cleared at the start of the next period.
I'm really not sure how it worked when I tested the other week [0], so I
spent the first half of the day trying to figure out what on earth had
happened to my FPGA image. I must've picked the wrong image when I went
to test it the other week that had the wrong configuration somehow.

As a result, I've gone and hacked up another way of transferring the
burden of waiting - setting a timer for the period, backed by a
completion. get_state() and apply() now both check for the completion
and time out otherwise. I'm half tempted to tack RFC back onto the
series as I have not really messed with timers at all before and may
have done something off the wall.

I pushed it out (see [1] in case you'd like to look) so that the bots
can have a play with it, since it'll be a few weeks before I'll have a
chance to properly test that I've broken nothing with this.

It's not nearly as neat or contained, but still benefits from the
non-void get_state() & doesn't "confuse" a caller of get_state() with
some potential garbage.

The diff on top of the read_poll_timeout() approach from above is pasted
here. Hopefully I'll refine it a bit before sending a v13, checkpatch
may have an aneurysm with what's below. Or have a better idea and throw
it out..

Thanks again for sending that v2 ~immediately,
Conor.

0 - https://lore.kernel.org/linux-riscv/Y3uZY5mt%2FZIWk3sS@wendy/
1 - https://git.kernel.org/pub/scm/linux/kernel/git/conor/linux.git/tree/drivers/pwm/pwm-microchip-core.c?h=pwm-dev-v13&id=ddbd59fb5480b1be74645f0a84d934b1f91d833d

diff --git a/drivers/pwm/pwm-microchip-core.c b/drivers/pwm/pwm-microchip-core.c
index c88fa8f8d96d..f565e8be46b3 100644
--- a/drivers/pwm/pwm-microchip-core.c
+++ b/drivers/pwm/pwm-microchip-core.c
@@ -34,6 +34,7 @@
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
+#include <linux/timer.h>
 
 #define PREG_TO_VAL(PREG) ((PREG) + 1)
 
@@ -47,12 +48,14 @@
 #define MCHPCOREPWM_POSEDGE(i)	(0x10 + 0x08 * (i)) /* 0x10, 0x18, ..., 0x88 */
 #define MCHPCOREPWM_NEGEDGE(i)	(0x14 + 0x08 * (i)) /* 0x14, 0x1c, ..., 0x8c */
 #define MCHPCOREPWM_SYNC_UPD	0xe4
-#define MCHPCOREPWM_TIMEOUT_US	100000u
+#define MCHPCOREPWM_TIMEOUT_MS	100u
 
 struct mchp_core_pwm_chip {
 	struct pwm_chip chip;
 	struct clk *clk;
 	struct mutex lock; /* protect the shared period */
+	struct completion sync_update_complete;
+	struct timer_list sync_update_timer;
 	void __iomem *base;
 	u32 sync_update_mask;
 	u16 channel_enabled;
@@ -91,9 +94,54 @@ static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
 	 * applied to the waveform at the beginning of the next period.
 	 * This is a NO-OP if the channel does not have shadow registers.
 	 */
+	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm)) {
+		u64 delay;
+
+		/* Have to convert to jiffies... */
+		delay = div_u64(period, 1000000u) ? : 1u;
+		reinit_completion(&mchp_core_pwm->sync_update_complete);
+		mchp_core_pwm->sync_update_timer.expires = jiffies + delay;
+		add_timer(&mchp_core_pwm->sync_update_timer);
+	}
+}
+
+static int mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
+					      unsigned int channel)
+{
+	/*
+	 * If a shadow register is used for this PWM channel, and iff there is
+	 * a pending update to the waveform, we must wait for it to be applied
+	 * before attempting to read its state, as reading the registers yields
+	 * the currently implemented settings, the new ones are only readable
+	 * once the current period has ended.
+	 *
+	 * Rather large delays are possible, in the seconds, so to avoid waiting
+	 * around for **too** long - cap the wait at 100 ms.
+	 */
+	if (!timer_pending(&mchp_core_pwm->sync_update_timer))
+		return 0;
+
+	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
+		int ret;
+		ret = wait_for_completion_interruptible_timeout(&mchp_core_pwm->sync_update_complete,
+								msecs_to_jiffies(MCHPCOREPWM_TIMEOUT_MS));
+		if (!ret)
+			return -ETIMEDOUT;
+
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
 
-	writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
+static void mchp_core_pwm_complete_sync_update(struct timer_list *t)
+{
+	struct mchp_core_pwm_chip *mchp_core_pwm = from_timer(mchp_core_pwm,
+							      t, sync_update_timer);
 
+	complete(&mchp_core_pwm->sync_update_complete);
+	del_timer(&mchp_core_pwm->sync_update_timer);
 }
 
 static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate,
@@ -181,35 +229,6 @@ static inline void mchp_core_pwm_apply_period(struct mchp_core_pwm_chip *mchp_co
 	writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
 }
 
-static int mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
-					      unsigned int channel)
-{
-	/*
-	 * If a shadow register is used for this PWM channel, and iff there is
-	 * a pending update to the waveform, we must wait for it to be applied
-	 * before attempting to read its state, as reading the registers yields
-	 * the currently implemented settings, the new ones are only readable
-	 * once the current period has ended.
-	 *
-	 * Rather large delays are possible, in the seconds, so to avoid waiting
-	 * around for **too** long - cap the wait at 100 ms.
-	 */
-	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
-		u32 delay = MCHPCOREPWM_TIMEOUT_US;
-		u32 sync_upd;
-		int ret;
-
-		writel_relaxed(1u, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
-
-		ret = read_poll_timeout(readl, sync_upd, !sync_upd, delay/100, delay,
-					false, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
-		if (ret)
-			return -ETIMEDOUT;
-	}
-
-	return 0;
-}
-
 static int mchp_core_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
 				      const struct pwm_state *state)
 {
@@ -297,31 +316,37 @@ static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
 	int ret;
 
+	mutex_lock(&mchp_core_pwm->lock);
+
 	ret = mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
 	if (ret)
-		return ret;
-
-	mutex_lock(&mchp_core_pwm->lock);
+		goto exit_unlock;
 
 	ret = mchp_core_pwm_apply_locked(chip, pwm, state);
 
+exit_unlock:
 	mutex_unlock(&mchp_core_pwm->lock);
 
 	return ret;
 }
 
-static void mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
-				    struct pwm_state *state)
+static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+				   struct pwm_state *state)
 {
 	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
 	u64 rate;
 	u16 prescale;
 	u8 period_steps, duty_steps, posedge, negedge;
-
-	mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
+	int ret;
 
 	mutex_lock(&mchp_core_pwm->lock);
 
+	ret = mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
+	if (ret) {
+		mutex_unlock(&mchp_core_pwm->lock);
+		return ret;
+	}
+
 	if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm))
 		state->enabled = true;
 	else
@@ -351,6 +376,8 @@ static void mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pw
 	}
 
 	state->polarity = negedge < posedge ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+
+	return 0;
 }
 
 static const struct pwm_ops mchp_core_pwm_ops = {
@@ -403,6 +430,10 @@ static int mchp_core_pwm_probe(struct platform_device *pdev)
 	if (ret < 0)
 		return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
 
+	init_completion(&mchp_pwm->sync_update_complete);
+	timer_setup(&mchp_pwm->sync_update_timer, mchp_core_pwm_complete_sync_update, 0);
+	writel_relaxed(1U, mchp_pwm->base + MCHPCOREPWM_SYNC_UPD);
+
 	return 0;
 }



[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

[-- Attachment #2: Type: text/plain, Size: 161 bytes --]

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

  parent reply	other threads:[~2022-12-05 15:27 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-10  9:35 [PATCH v12 0/2] Hey Uwe, all, Conor Dooley
2022-11-10  9:35 ` Conor Dooley
2022-11-10  9:35 ` [PATCH v12 1/2] pwm: add microchip soft ip corePWM driver Conor Dooley
2022-11-10  9:35   ` Conor Dooley
2022-11-17 16:49   ` Uwe Kleine-König
2022-11-17 16:49     ` Uwe Kleine-König
2022-11-17 17:38     ` Conor Dooley
2022-11-17 17:38       ` Conor Dooley
2022-11-17 21:04       ` Uwe Kleine-König
2022-11-17 21:04         ` Uwe Kleine-König
2022-11-17 22:03         ` Conor Dooley
2022-11-17 22:03           ` Conor Dooley
2022-11-21 15:29           ` Conor Dooley
2022-11-21 15:29             ` Conor Dooley
2022-11-30  9:53             ` Conor Dooley
2022-11-30  9:53               ` Conor Dooley
2022-11-30 10:37               ` Uwe Kleine-König
2022-11-30 10:37                 ` Uwe Kleine-König
2022-11-30 11:15                 ` Conor Dooley
2022-11-30 11:15                   ` Conor Dooley
2022-12-05 15:21                 ` Conor Dooley [this message]
2022-12-05 15:21                   ` Conor Dooley
2022-12-05 16:03                   ` Uwe Kleine-König
2022-12-05 16:03                     ` Uwe Kleine-König
2022-12-05 17:13                     ` Conor Dooley
2022-12-05 17:13                       ` Conor Dooley
2022-12-05 18:13                       ` Uwe Kleine-König
2022-12-05 18:13                         ` Uwe Kleine-König
2022-11-10  9:35 ` [PATCH v12 2/2] MAINTAINERS: add pwm to PolarFire SoC entry Conor Dooley
2022-11-10  9:35   ` Conor Dooley
2022-11-10  9:38 ` [PATCH v12 0/2] Hey Uwe, all, Conor.Dooley
2022-11-10  9:38   ` Conor.Dooley

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=Y44Mk2nGu1Zeq7QQ@wendy \
    --to=conor.dooley@microchip.com \
    --cc=conor@kernel.org \
    --cc=daire.mcnamara@microchip.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=thierry.reding@gmail.com \
    --cc=u.kleine-koenig@pengutronix.de \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.