[PATCH v13 1/2] pwm: add microchip soft ip corePWM driver
Conor Dooley
conor at kernel.org
Tue Jan 10 16:15:29 PST 2023
On Tue, Jan 10, 2023 at 11:48:05PM +0100, Uwe Kleine-König wrote:
> On Wed, Dec 21, 2022 at 11:29:12AM +0000, Conor Dooley wrote:
> > From: Conor Dooley <conor.dooley at microchip.com>
> > + delay_us = DIV_ROUND_UP_ULL(remaining_ns, NSEC_PER_USEC);
> > + if ((delay_us / 1000) > MAX_UDELAY_MS)
> > + msleep(delay_us / 1000 + 1);
>
> Is this better than
>
> msleep(DIV_ROUND_UP(delay_us, 1000);
>
> ? Also I wonder about your usage of MAX_UDELAY_MS. This is about
I probably started hacking on the example you gave and didn't notice
the U. What I have here is ~what you suggested last time.
> udelay() but you're using usleep_range()?
>
> > + else
> > + usleep_range(delay_us, delay_us * 2);
>
> I wonder if there isn't a function that implements something like
>
> wait_until(mchp_core_pwm->update_timestamp);
>
> which would be a bit nicer than doing this by hand. Maybe fsleep()?
That'd be fsleep(delay_us), but does at least clean up some of the
messing.
> > +static void mchp_core_pwm_apply_duty(struct pwm_chip *chip, struct pwm_device *pwm,
> > + const struct pwm_state *state, u64 duty_steps,
> > + u8 period_steps)
> > +{
> > + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
> > + u8 posedge, negedge;
> > + u8 period_steps_val = PREG_TO_VAL(period_steps);
> > +
> > + /*
> > + * Setting posedge == negedge doesn't yield a constant output,
> > + * so that's an unsuitable setting to model duty_steps = 0.
> > + * In that case set the unwanted edge to a value that never
> > + * triggers.
> > + */
> > + if (state->polarity == PWM_POLARITY_INVERSED) {
> > + negedge = !duty_steps ? period_steps_val : 0u;
>
> IMHO
>
> negedge = duty_steps ? 0 : period_steps_val;
>
> is a bit easier to parse.
>
> > + posedge = duty_steps;
> > + } else {
> > + posedge = !duty_steps ? period_steps_val : 0u;
> > + negedge = duty_steps;
> > + }
>
> The following code is equivalent:
>
> u8 first_edge = 0, second_edge = duty_steps;
>
> /*
> * Setting posedge == negedge doesn't yield a constant output,
> * so that's an unsuitable setting to model duty_steps = 0.
> * In that case set the unwanted edge to a value that never
> * triggers.
> */
> if (duty_steps == 0)
> first_edge = period_steps_val;
>
> if (state->polarity == PWM_POLARITY_INVERSED) {
> negedge = first_edge;
> posedge = second_edge;
> } else {
> posedge = first_edge;
> negedge = second_edge;
> }
>
> I'm not sure if it's easier to understand. What do you think?
Despite having used them, I dislike ternary statements.
> > + writel_relaxed(posedge, mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
> > + writel_relaxed(negedge, mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
> > +}
> > +
> > +static void mchp_core_pwm_calc_period(const struct pwm_state *state, unsigned long clk_rate,
> > + u16 *prescale, u8 *period_steps)
> > +{
> > + u64 tmp;
> > +
> > + /*
> > + * Calculate the period cycles and prescale values.
> > + * The registers are each 8 bits wide & multiplied to compute the period
> > + * using the formula:
> > + * (clock_period) * (prescale + 1) * (period_steps + 1)
> > + * so the maximum period that can be generated is 0x10000 times the
> > + * period of the input clock.
> > + * However, due to the design of the "hardware", it is not possible to
> > + * attain a 100% duty cycle if the full range of period_steps is used.
> > + * Therefore period_steps is restricted to 0xFE and the maximum multiple
> > + * of the clock period attainable is 0xFF00.
> > + */
> > + tmp = mul_u64_u64_div_u64(state->period, clk_rate, NSEC_PER_SEC);
> > +
> > + /*
> > + * The hardware adds one to the register value, so decrement by one to
> > + * account for the offset
> > + */
> > + if (tmp >= MCHPCOREPWM_PERIOD_MAX) {
> > + *prescale = MCHPCOREPWM_PRESCALE_MAX - 1;
> > + *period_steps = MCHPCOREPWM_PERIOD_STEPS_MAX - 1;
> > +
> > + return;
> > + }
> > +
> > + *prescale = div_u64(tmp, MCHPCOREPWM_PERIOD_STEPS_MAX);
> > + /* PREG_TO_VAL() can produce a value larger than UINT8_MAX */
> > + *period_steps = div_u64(tmp, PREG_TO_VAL(*prescale)) - 1;
>
> This looks wrong, but I didn't think long about that. Did we discuss
> this already and/or are you sure this is correct?
We did discuss it previously AFAICT;
https://lore.kernel.org/linux-pwm/896d73ac-05af-8673-8379-29011800be83@microchip.com/
In that version of the code, prescale_val meant the mathematical value
used for calculations & "prescale" was the value written into the
register.
Now, we have ditched prescale_val and operate directly with what gets
written into the register.
I ran a test case through the calculation, and it seemed to work out?
> (We have:
> (prescale + 1) * (period_steps + 1)
> period = ------------------------------------
> clk_rate
>
> You calculate
> period * clk_rate
> prescale = -------------------
> NSEC_PER_SEC * 0xff
Say period = 2000 ns, clk_rate = 62.5 Mhz, giving a register value for
prescale of 0.49019...
> period * clk_rate
> period_steps = ----------------------------- - 1
> NSEC_PER_SEC * (prescale + 1)
Same numbers, but we use the PREG_TO_VAL() macro so the mathematical value
is 1.49019.
2000 * 62.5E6
--------------------- - 1 = 82.88360....
1E9 * (0.49016 + 1)
>
> assuming exact arithmetic putting these into the above equation we get:
>
>
> period * clk_rate period * clk_rate
> (------------------- + 1) * (-----------------------------) / clk_rate
> NSEC_PER_SEC * 0xff NSEC_PER_SEC * (prescale + 1)
>
> and then substituting prescale this doesn't resolve to period, does it?
> Correct me if I'm wrong.)
(0.49016 + 1) * (82.88360 + 1) 124.99...
------------------------------ = ------------- = 0.00000199999
62.5E6 62.5E6
And then accounting for that fact that 2000 was really 2000E-9,
we arrive back where we started, give or take some rounding?
Doing that with integer maths works out more cleanly since 0.49016
becomes 0.
2000 * 62.5E6
------------------ - 1 = 124
1E9 * (0 + 1)
(0 + 1) * (124 + 1) 125
------------------- = --------- = 0.000002
62.5E6 62.5E6
Unfortunately, I don't think I am seeing what you're seeing.
> period * clk_rate period * clk_rate
> (------------------- + 1) * (-----------------------------) / clk_rate
> NSEC_PER_SEC * 0xff NSEC_PER_SEC * (prescale + 1)
^
It may be this + 1, which I don't seem to have accounted for in my quick
run through a calculation?
*prescale = div_u64(tmp, MCHPCOREPWM_PERIOD_STEPS_MAX);
*period_steps = div_u64(tmp, PREG_TO_VAL(*prescale)) - 1;
The code does not add a 1 when it calculates prescale, only when it uses
the result to calculate period_steps, since prescale & period_steps are
the register values, not the "mathematical" ones.
Hopefully I've not gone and made a fool of myself...
> > +static inline void mchp_core_pwm_apply_period(struct mchp_core_pwm_chip *mchp_core_pwm,
> > + u8 prescale, u8 period_steps)
> > +{
> > + writel_relaxed(prescale, mchp_core_pwm->base + MCHPCOREPWM_PRESCALE);
> > + writel_relaxed(period_steps, mchp_core_pwm->base + MCHPCOREPWM_PERIOD);
> > +}
>
> There is only one caller for this two-line function. I suggest to unroll it?
Sure.
> > + ret = devm_pwmchip_add(&pdev->dev, &mchp_core_pwm->chip);
> > + if (ret < 0)
> > + return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
> > +
> > + /*
> > + * Enabled synchronous update for channels with shadow registers
> > + * enabled. For channels without shadow registers, this has no effect
> > + * at all so is unconditionally enabled.
> > + */
> > + writel_relaxed(1U, mchp_core_pwm->base + MCHPCOREPWM_SYNC_UPD);
> > + mchp_core_pwm->update_timestamp = ktime_get();
>
> This needs to be done before devm_pwmchip_add().
Makes sense, woops. I think I've revised this to the point that my
blinkers have turned on & I'll wait a while before resubmitting in order
to hopefully reset that.
Perhaps I need to watch a lecture on how to write a PWM driver since I
am clearly no good at it, given the 15 revisions. Do you know of any?
Thanks,
Conor.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-riscv/attachments/20230111/756f4325/attachment.sig>
More information about the linux-riscv
mailing list