Re: [PATCH v3 2/2] pwm: add support for NXPs high-side switch MC33XS2410

From: Dimitri Fedrau
Date: Wed Jul 31 2024 - 04:47:07 EST


Am Mon, Jul 29, 2024 at 11:28:56PM +0200 schrieb Uwe Kleine-König:
Hi Uwe,

[...]

> > +static int mc33xs2410_xfer_regs(struct spi_device *spi, bool read, u8 *reg,
> > + u16 *val, bool *ctrl, int len)
> > +{
> > + struct spi_transfer t[MC33XS2410_MAX_TRANSFERS] = { { 0 } };
> > + u8 tx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
> > + u8 rx[MC33XS2410_MAX_TRANSFERS * MC33XS2410_WORD_LEN];
> > + int i, ret, reg_i, val_i;
> > +
> > + if (!len)
> > + return 0;
> > +
> > + if (read)
> > + len++;
> > +
> > + if (len > MC33XS2410_MAX_TRANSFERS)
> > + return -EINVAL;
> > +
> > + for (i = 0; i < len; i++) {
> > + reg_i = i * MC33XS2410_WORD_LEN;
> > + val_i = reg_i + 1;
> > + if (read) {
> > + if (i < len - 1) {
> > + tx[reg_i] = reg[i];
> > + tx[val_i] = ctrl[i] ? MC33XS2410_RD_CTRL : 0;
> > + t[i].tx_buf = &tx[reg_i];
> > + }
> > +
> > + if (i > 0)
> > + t[i].rx_buf = &rx[reg_i - MC33XS2410_WORD_LEN];
> > + } else {
> > + tx[reg_i] = reg[i] | MC33XS2410_WR;
> > + tx[val_i] = val[i];
> > + t[i].tx_buf = &tx[reg_i];
> > + }
> > +
> > + t[i].len = MC33XS2410_WORD_LEN;
> > + t[i].cs_change = 1;
> > + }
> > +
> > + t[len - 1].cs_change = 0;
> > +
> > + ret = spi_sync_transfer(spi, &t[0], len);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (read) {
> > + for (i = 0; i < len - 1; i++) {
> > + reg_i = i * MC33XS2410_WORD_LEN;
> > + val[i] = FIELD_GET(MC33XS2410_RD_DATA_MASK,
> > + get_unaligned_be16(&rx[reg_i]));
> > + }
> > + }
> > +
> > + return 0;
>
> Huh, this is complicated. Isn't that covered by regmap somehow?
>

AFAIK it isn't supported. The main reason why regmap-spi doesn't work for
this device is that the device needs a CS change after transmitting 16
bits. This is not covered by regmap-spi. So I would end up implementing
reg_read, regmap_write should be fine in regmap-spi. Besides that if I
want to come as close as possible to an atomic configuration, which is not
possible, I would have to implement some bulk read/write operations and
end up with a similar implementation. I would stick to the current
implementation if you agree.

> > +}
> > +
> > [...]
> > +
> > +static u8 mc33xs2410_pwm_get_freq(u64 period)
> > +{
> > + u8 step, count;
> > +
> > + /*
> > + * Check if period is within the limits of each of the four frequency
> > + * ranges, starting with the highest frequency(lowest period). Higher
> > + * frequencies are represented with better resolution by the device.
> > + * Therefore favor frequency range with the better resolution to
> > + * minimize error introduced by the frequency steps.
>
> I'm not a native English speaker, but I find that misleading. That
> period is in the "possible" range is already asserted by the caller. So
> the switch is about "Check which step is appropriate for the given
> period", right?
>

Yes, you are right. Will fix it in the next version.

> > + */
> > +
> > + switch (period) {
> > + case MC33XS2410_MIN_PERIOD_STEP(3) + 1 ... MC33XS2410_MAX_PERIOD_STEP(3):
> > + step = 3;
> > + break;
> > + case MC33XS2410_MAX_PERIOD_STEP(3) + 1 ... MC33XS2410_MAX_PERIOD_STEP(2):
> > + step = 2;
> > + break;
> > + case MC33XS2410_MAX_PERIOD_STEP(2) + 1 ... MC33XS2410_MAX_PERIOD_STEP(1):
> > + step = 1;
> > + break;
> > + case MC33XS2410_MAX_PERIOD_STEP(1) + 1 ... MC33XS2410_MAX_PERIOD_STEP(0):
> > + step = 0;
> > + break;
> > + }
> > +
> > + count = DIV_ROUND_UP(MC33XS2410_MAX_PERIOD_STEP(step), period) - 1;
> > +
> > + return FIELD_PREP(MC33XS2410_PWM_FREQ_STEP_MASK, step) |
> > + FIELD_PREP(MC33XS2410_PWM_FREQ_COUNT_MASK, count);
> > +}
> > +
> > [...]
> > +
> > +static int mc33xs2410_pwm_get_relative_duty_cycle(u64 period, u64 duty_cycle)
> > +{
> > + if (!period)
> > + return 0;
> > +
> > + duty_cycle *= 256;
>
> This might overflow.
>

How ? Max period and duty_cycle is checked by the caller and can be
maximum 2000000000, 2000000000 * 256 = 512000000000, fits in u64. Did I
miss anything ?

> > + duty_cycle = DIV_ROUND_CLOSEST_ULL(duty_cycle, period);
>
> round-closest is most probably wrong. Please test your driver with
> PWM_DEBUG enabled and increasing and decreasing series of duty_cycle and
> period.
>

Yes, I should probably round it down. But I tested with PWM_DEBUG enabled
and it gave me the best results so far. There are still few cases where
there are complaints. I try to fix it.

> > +
> > + /* Device is not able to generate 0% duty cycle */
> > + if (!duty_cycle)
> > + return -ERANGE;
>
> Given that the hardware emits a low level when disabled, please disable
> if duty_cycle = 0 is requested.
>

Ok.

> > + return duty_cycle - 1;
> > +}
> > +
> > [...]
> > +static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
> > + struct pwm_device *pwm,
> > + struct pwm_state *state)
> > +{
> > + struct mc33xs2410_pwm *mc33xs2410 = to_pwm_mc33xs2410_chip(chip);
> > + struct spi_device *spi = mc33xs2410->spi;
> > + u8 reg[4] = {
> > + MC33XS2410_PWM_FREQ(pwm->hwpwm + 1),
> > + MC33XS2410_PWM_DC(pwm->hwpwm + 1),
> > + MC33XS2410_PWM_CTRL1,
> > + MC33XS2410_PWM_CTRL3,
> > + };
> > + bool ctrl[4] = { true, true, true, true };
> > + u16 val[4];
> > + int ret;
> > +
> > + ret = mc33xs2410_read_regs(spi, reg, ctrl, val, 4);
> > + if (ret < 0)
> > + return ret;
> > +
> > + state->period = mc33xs2410_pwm_get_period(val[0]);
> > + pwm_set_relative_duty_cycle(state, val[1] + 1, 256);
>
> pwm_set_relative_duty_cycle doesn't use the right rounding for
> .get_state().
>

As mentioned above, will try to fix it.

> > + state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm)) ?
> > + PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
> > +
> > + state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm));
> > +
> > + return 0;
> > +}
> > +
> > [...]
> > +static int mc33xs2410_probe(struct spi_device *spi)
> > +{
> > [...]
> > + /* Disable watchdog */
> > + ret = mc33xs2410_write_reg(spi, MC33XS2410_WDT, 0x0);
> > + if (ret < 0)
> > + return dev_err_probe(dev, ret, "Failed to disable watchdog\n");
>
> Wouldn't the watchdog functionality better be handled by a dedicated
> watchdog driver? Disabling it here unconditionally looks wrong.
>

Yes, would be better. I planned this after this patchset is accepted.
Without disabling the watchdog, the device is not able to operate. So I
would stick to it for now and come up with a patch later on.

> > + /* Transition to normal mode */
> > + ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL,
> > + MC33XS2410_GLB_CTRL_MODE_MASK,
> > + MC33XS2410_GLB_CTRL_NORMAL_MODE);
> > [...]
>
> Best regards
> Uwe

Best regards
Dimitri