Re: [PATCH v11 3/4] pwm: add microchip soft ip corePWM driver

From: Uwe Kleine-König
Date: Tue Nov 08 2022 - 10:51:05 EST


Hello,

On Fri, Oct 07, 2022 at 12:35:12PM +0100, Conor Dooley wrote:
> [...]
> +static u64 mchp_core_pwm_calc_duty(struct pwm_chip *chip, struct pwm_device *pwm,
> + const struct pwm_state *state, u8 prescale, u8 period_steps)
> +{
> + struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
> + u64 duty_steps, tmp;
> + u16 prescale_val = PREG_TO_VAL(prescale);
> +
> + /*
> + * Calculate the duty cycle in multiples of the prescaled period:
> + * duty_steps = duty_in_ns / step_in_ns
> + * step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate
> + * The code below is rearranged slightly to only divide once.
> + */
> + duty_steps = state->duty_cycle * clk_get_rate(mchp_core_pwm->clk);
> + tmp = prescale_val * NSEC_PER_SEC;
> + return div64_u64(duty_steps, tmp);

The assignment to duty_steps can overflow. So you have to use
mul_u64_u64_div_u64 here, too.

> +}
> +
> [...]
> +
> +static int mchp_core_pwm_apply_locked(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;
> +
> + if (!state->enabled) {
> + mchp_core_pwm_enable(chip, pwm, false, current_state.period);
> + 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, &prescale, &period_steps);
> + 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))
> + return -EINVAL;
> +
> + /*
> + * It is possible that something could have set the period_steps
> + * register to 0xff, which would prevent us from setting a 100%
> + * or 0% relative duty cycle, as explained above in
> + * mchp_core_pwm_calc_period().
> + * The period is locked and we cannot change this, so we abort.
> + */
> + if (hw_period_steps == MCHPCOREPWM_PERIOD_STEPS_MAX)
> + return -EINVAL;
> +
> + prescale = hw_prescale;
> + period_steps = hw_period_steps;
> + } else {
> + int ret;
> +
> + ret = mchp_core_pwm_calc_period(chip, state, &prescale, &period_steps);
> + if (ret)
> + return ret;
> +
> + mchp_core_pwm_apply_period(mchp_core_pwm, prescale, period_steps);
> + }
> +
> + duty_steps = mchp_core_pwm_calc_duty(chip, pwm, state, prescale, period_steps);

Both mchp_core_pwm_calc_period and mchp_core_pwm_calc_duty call
clk_get_rate(), I suggest call this only once and pass the rate to these
two functions.

Both branches of the if above start with calling
mchp_core_pwm_calc_period, this could be simplified, too. (Hmm, in
exactly one of them you check the return code, wouldn't that be sensible
for both callers?)

> +
> + /*
> + * Because the period is per channel, it is possible that the requested
> + * duty cycle is longer than the period, in which case cap it to the
> + * period, IOW a 100% duty cycle.
> + */
> + if (duty_steps > period_steps)
> + duty_steps = period_steps + 1;
> +
> + mchp_core_pwm_apply_duty(chip, pwm, state, duty_steps, period_steps);
> +
> + mchp_core_pwm_enable(chip, pwm, true, state->period);
> +
> + return 0;
> +}

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |

Attachment: signature.asc
Description: PGP signature