public inbox for devicetree@vger.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: Thierry Reding <thierry.reding@gmail.com>,
	Rob Herring <robh+dt@kernel.org>,
	Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
	Daire McNamara <daire.mcnamara@microchip.com>,
	<devicetree@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<linux-pwm@vger.kernel.org>, <linux-riscv@lists.infradead.org>
Subject: Re: [PATCH v10 3/4] pwm: add microchip soft ip corePWM driver
Date: Mon, 19 Sep 2022 15:29:19 +0100	[thread overview]
Message-ID: <Yyh8v+MtHuc0LLf0@wendy> (raw)
In-Reply-To: <20220919135008.sahwmwbfwvgplji4@pengutronix.de>

Hey Uwe,

On Mon, Sep 19, 2022 at 03:50:08PM +0200, Uwe Kleine-König wrote:
> On Mon, Sep 19, 2022 at 01:53:56PM +0100, Conor Dooley wrote:
> > Hey Uwe,
> > Thanks (as always). I've switched up my email setup a bit so I hope
> > that I've not mangled anything here.
> > 
> > On Thu, Sep 15, 2022 at 09:21:52AM +0200, Uwe Kleine-König wrote:
> > > Hello,
> > > 
> > > On Wed, Aug 24, 2022 at 10:12:14AM +0100, Conor Dooley wrote:
> > > > Add a driver that supports the Microchip FPGA "soft" PWM IP core.
> > > > 
> > > > Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> > > > ---
> > > >  drivers/pwm/Kconfig              |  10 +
> > > >  drivers/pwm/Makefile             |   1 +
> > > >  drivers/pwm/pwm-microchip-core.c | 402 +++++++++++++++++++++++++++++++
> > > >  3 files changed, 413 insertions(+)
> > > >  create mode 100644 drivers/pwm/pwm-microchip-core.c
> > > > 
> > 
> > > > +static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > > > +			       const struct pwm_state *state)
> > > > +{
> > > > +	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
> > > > +	struct pwm_state current_state = pwm->state;
> > > > +	bool period_locked;
> > > > +	u64 duty_steps;
> > > > +	u16 prescale;
> > > > +	u8 period_steps;
> > > > +	int ret;
> > > > +
> > > > +	mutex_lock(&mchp_core_pwm->lock);
> > > > +
> > > > +	if (!state->enabled) {
> > > > +		mchp_core_pwm_enable(chip, pwm, false, current_state.period);
> > > > +		mutex_unlock(&mchp_core_pwm->lock);
> > > > +		return 0;
> > > > +	}
> > > > +
> > > > +	/*
> > > > +	 * If the only thing that has changed is the duty cycle or the polarity,
> > > > +	 * we can shortcut the calculations and just compute/apply the new duty
> > > > +	 * cycle pos & neg edges
> > > > +	 * As all the channels share the same period, do not allow it to be
> > > > +	 * changed if any other channels are enabled.
> > > > +	 * If the period is locked, it may not be possible to use a period
> > > > +	 * less than that requested. In that case, we just abort.
> > > > +	 */
> > > > +	period_locked = mchp_core_pwm->channel_enabled & ~(1 << pwm->hwpwm);
> > > > +
> > > > +	if (period_locked) {
> > > > +		u16 hw_prescale;
> > > > +		u8 hw_period_steps;
> > > > +
> > > > +		mchp_core_pwm_calc_period(chip, state, (u8 *)&prescale, &period_steps);
> > > 
> > > Huh, if (u8 *)&prescale works depends on endianness.
> > 
> > Big endian? What's that? ;)
> > I think the cast can just be dropped and the u16 used directly instead.
> > 
> > > 
> > > > +		hw_prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
> > > > +		hw_period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
> > > > +
> > > > +		if ((period_steps + 1) * (prescale + 1) <
> > > > +		    (hw_period_steps + 1) * (hw_prescale + 1)) {
> > > > +			mutex_unlock(&mchp_core_pwm->lock);
> > > > +			return -EINVAL;
> > > > +		}
> > > > +
> > > > +		/*
> > > > +		 * It is possible that something could have set the period_steps
> > > 
> > > My German feel for the English language says s/could have/has/
> > 
> > What I wrote is _fine_ but the could is redudant given the possible.
> > I'll change it over.
> > 
> > > > +		 * register to 0xff, which would prevent us from setting a 100%
> > > 
> > > For my understanding: It would also prevent a 0% relative duty, right?
> > 
> > Yeah, I guess the comment could reflect that.
> > 
> > > 
> > > > +		 * duty cycle, as explained in the mchp_core_pwm_calc_period()
> > > 
> > > s/duty/relative duty/; s/the //
> > > 
> > > > +		 * above.
> > > > +		 * The period is locked and we cannot change this, so we abort.
> > > > +		 */
> > > > +		if (period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX) {
> > > 
> > > Don't you need to check hw_period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX
> > > here?
> > 
> > D'oh.
> > 
> > > 
> > > > +			mutex_unlock(&mchp_core_pwm->lock);
> > > > +			return -EINVAL;
> > > > +		}
> > > > +
> > > > +		prescale = hw_prescale;
> > > > +		period_steps = hw_period_steps;
> > > > +	} else if (!current_state.enabled || current_state.period != state->period) {
> > > > +		ret = mchp_core_pwm_calc_period(chip, state, (u8 *)&prescale, &period_steps);
> > > 
> > > ret is only used in this block, so the declaration can go into here,
> > > too.
> > > 
> > > > +		if (ret) {
> > > > +			mutex_unlock(&mchp_core_pwm->lock);
> > > > +			return ret;
> > > > +		}
> > > > +		mchp_core_pwm_apply_period(mchp_core_pwm, prescale, period_steps);
> > > > +	} else {
> > > > +		prescale = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
> > > > +		period_steps = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
> > > > +		/*
> > > > +		 * As above, it is possible that something could have set the
> > > > +		 * period_steps register to 0xff, which would prevent us from
> > > > +		 * setting a 100% duty cycle, as explained above.
> > > > +		 * As the period is not locked, we are free to fix this.
> > > > +		 */
> > > 
> > > Are you sure this is safe? I think it isn't. Consider:
> > > 
> > > 	pwm_apply_state(mypwm, { .duty = 0, .period = A, .enabled = true, });
> > > 	pwm_apply_state(mypwm, { .duty = 0, .period = B, .enabled = false, });
> > > 	pwm_apply_state(mypwm, { .duty = 0, .period = B, .enabled = true, });
> > > 
> > > Then you have in the third call prescale and period_steps still
> > > corresponding to A because you didn't update these registers in the 2nd
> > > call as you exited early.
> > 
> > Riiight. I think I am a little confused here - this comment does not
> > refer to my comment but rather to the whole logic I have?
> > 
> > As in, what you're concerned about is the early exit if the state is
> > disabled & that I take the values in the hardware as accurate?
> 
> No, the thing I'm concerned about is assuming MCHPCOREPWM_PRESCALE and
> MCHPCOREPWM_PERIOD correspond to state->period. So I'd drop the last
> block use the 2nd last instead without further condition.

So, if the period isn't locked always re-configure it. Makes life easier
for me.

> 
> > What makes sense to me to do here (assuming I understood correctly)
> > is to compare state->period against what is in the hardare rather than
> > against what the pwm core thinks?
> > Or else I could stop exiting early if the pwm is to be disabled &
> > instead allow the period and duty to be set so that the state of the
> > hardware is as close to the pwm core's representation of it as possible.
> 
> exiting early is fine.
>  
> > > > [...]
> > > > +	period_steps = PREG_TO_VAL(readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_PERIOD));
> > > > +	state->period = period_steps * prescale * NSEC_PER_SEC;
> > > 
> > > This is broken on 32 bit archs (here: arm):
> > > 
> > > $ cat test.c
> > > #include <inttypes.h>
> > > #include <stdio.h>
> > > #include <stdlib.h>
> > > 
> > > int main(int argc, char *argv[])
> > > {
> > > 	uint8_t period_steps = atoi(argv[1]);
> > > 	uint16_t prescale = atoi(argv[2]);
> > > 	uint64_t period;
> > > 
> > > 	period = period_steps * prescale * 1000000000L;
> > > 
> > > 	printf("period_steps = %" PRIu8 "\n", period_steps);
> > > 	printf("prescale = %" PRIu16 "\n", prescale);
> > > 	printf("period = %" PRIu64 "\n", period);
> > > 
> > > 	return 0;
> > > }
> > > 
> > > $ make test
> > > cc     test.c   -o test
> > > 
> > > $ ./test 255 65535
> > > period_steps = 255
> > > prescale = 65535
> > > period = 18446744073018591744
> > > 
> > > The problem is that the result of 16711425 * 1000000000L isn't affected
> > > by the type of period and so it's promoted to L which isn't big enough
> > > to hold 16711425000000000 where longs are only 32 bit wide.
> > 
> > I don't think this is ever going to be hit in the wild, since prescale
> > comes from the hardware where it is limited to 255 - but preventing the
> > issue seems trivially done by splitting the multiplication so no reason
> > not to. Thanks for providing the test program btw :)
> 
> Even 255 * 255 * 1000000000 overflows. With a maintainer's hat on, it is
> very valuable to prevent such issues because your driver might be used
> as a template for the next driver.
> 
> > > > +	state->period = DIV64_U64_ROUND_UP(state->period, clk_get_rate(mchp_core_pwm->clk));
> > > > +
> > > > +	posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
> > > > +	negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
> > > > +
> > > > +	if ((negedge == posedge) && state->enabled) {
> > > 
> > > Why do you need that state->enabled?
> > 
> > Because I was running into conflicts between the reporting here and some
> > of the checks that I have added to prevent the PWM being put into an
> > invalid state. On boot both negedge and posedge will be zero & this was
> > preventing me from setting the period at all.
> 
> I don't understood that.

On startup, (negedge == posedge) is true as both are zero, but the reset
values for prescale and period are actually 0x8. If on reset I try to
set a small period, say "echo 1000 > period" apply() returns -EINVAL
because of a check in the pwm core in pwm_apply_state() as I am
attempting to set the period to lower than the out-of-reset duty cycle.

I considered zeroing the registers, but if something below Linux had
been using the PWM I felt that may not be the right thing to do. Can I
continue to check for the enablement here or would you rather I did
something different?

Thanks,
Conor.


  reply	other threads:[~2022-09-19 14:29 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-08-24  9:12 [PATCH v10 0/4] Microchip soft ip corePWM driver Conor Dooley
2022-08-24  9:12 ` [PATCH v10 1/4] dt-bindings: pwm: fix microchip corePWM's pwm-cells Conor Dooley
2022-08-24  9:12 ` [PATCH v10 2/4] riscv: dts: fix the icicle's #pwm-cells Conor Dooley
2022-09-14 19:59   ` Uwe Kleine-König
2022-09-15  7:03     ` Conor.Dooley
2022-08-24  9:12 ` [PATCH v10 3/4] pwm: add microchip soft ip corePWM driver Conor Dooley
2022-09-15  7:21   ` Uwe Kleine-König
2022-09-19 12:53     ` Conor Dooley
2022-09-19 13:50       ` Uwe Kleine-König
2022-09-19 14:29         ` Conor Dooley [this message]
2022-09-30  7:11           ` Conor Dooley
2022-09-30  9:13           ` Uwe Kleine-König
2022-09-30  9:45             ` Conor Dooley
2022-09-30 13:39               ` Uwe Kleine-König
2022-09-30 13:49                 ` Conor Dooley
2022-09-30 14:06                   ` Uwe Kleine-König
2022-08-24  9:12 ` [PATCH v10 4/4] MAINTAINERS: add pwm to PolarFire SoC entry Conor Dooley
2022-09-14 20:01   ` Uwe Kleine-König

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=Yyh8v+MtHuc0LLf0@wendy \
    --to=conor.dooley@microchip.com \
    --cc=daire.mcnamara@microchip.com \
    --cc=devicetree@vger.kernel.org \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=robh+dt@kernel.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox