All of lore.kernel.org
 help / color / mirror / Atom feed
From: Conor Dooley <conor.dooley@microchip.com>
To: Conor Dooley <conor@kernel.org>, <u.kleine-koenig@pengutronix.de>
Cc: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>,
	"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: Wed, 30 Nov 2022 09:53:51 +0000	[thread overview]
Message-ID: <Y4coL74qQX80TNaT@wendy> (raw)
In-Reply-To: <Y3uZY5mt/ZIWk3sS@wendy>

Hey Uwe,

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?
> 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?

Apologies for bumping this, I was wondering if any thoughts on the
above? I'm not sure which is the lesser evil here (or if I have
misunderstood something).

Thanks,
Conor.


WARNING: multiple messages have this Message-ID (diff)
From: Conor Dooley <conor.dooley@microchip.com>
To: Conor Dooley <conor@kernel.org>, <u.kleine-koenig@pengutronix.de>
Cc: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>,
	"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: Wed, 30 Nov 2022 09:53:51 +0000	[thread overview]
Message-ID: <Y4coL74qQX80TNaT@wendy> (raw)
In-Reply-To: <Y3uZY5mt/ZIWk3sS@wendy>

Hey Uwe,

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?
> 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?

Apologies for bumping this, I was wondering if any thoughts on the
above? I'm not sure which is the lesser evil here (or if I have
misunderstood something).

Thanks,
Conor.


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

  reply	other threads:[~2022-11-30  9:54 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 [this message]
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
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=Y4coL74qQX80TNaT@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.