Re: [PATCH v3 4/7] pwm: ntxec: Add driver for PWM function in Netronix EC

From: Jonathan Neuschäfer
Date: Sun Sep 27 2020 - 17:11:51 EST


On Fri, Sep 25, 2020 at 08:30:37AM +0200, Uwe Kleine-König wrote:
> Hello Jonathan,
[...]
> > +config PWM_NTXEC
> > + tristate "Netronix embedded controller PWM support"
> > + depends on MFD_NTXEC
> > + help
> > + Say yes here if you want to support the PWM output of the embedded
> > + controller found in certain e-book readers designed by the ODM
> > + Netronix.
>
> Is it only me who had to look up what ODM means? If not, maybe spell it
> out?

I'm sure other readers will have the same problem. I'll spell it out.

> > +/*
> > + * The maximum input value (in nanoseconds) is determined by the time base and
> > + * the range of the hardware registers that hold the converted value.
> > + * It fits into 32 bits, so we can do our calculations in 32 bits as well.
> > + */
> > +#define MAX_PERIOD_NS (TIME_BASE_NS * 0x10000 - 1)
>
> The maximal configurable period length is 0xffff, so I would have
> expected MAX_PERIOD_NS to be 0xffff * TIME_BASE_NS?

Due to the division rounding down, TIME_BASE_NS * 0x10000 - 1 would be
the highest input that results in a representable value after the
division, but I'm not sure it otherwise makes sense.

>
> > +static int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
> > + const struct pwm_state *state)
> > +{
> > + struct ntxec_pwm *pwm = pwmchip_to_pwm(pwm_dev->chip);
> > + unsigned int duty = state->duty_cycle;
> > + unsigned int period = state->period;
> > + int res = 0;
> > +
>
> I assume your device only supports normal polarity? If so, please check
> for it here and point out this limitation in the header (in the format
> that is for example used in pwm-sifive.c to make it easy to grep for
> that).

I haven't seen any indication that it supports inverted polarity. I'll
point it out in the header comment, and add a check.

>
> > + if (period > MAX_PERIOD_NS) {
> > + dev_warn(pwm->dev,
> > + "Period is not representable in 16 bits after division by %u: %u\n",
> > + TIME_BASE_NS, period);
>
> No error messages in .apply() please; this might spam the kernel log.
>
> Also the expectation when a too big period is requested is to configure
> for the biggest possible period. So just do:
>
> if (period > MAX_PERIOD_NS) {
> period = MAX_PERIOD_NS;
>
> if (duty > period)
> duty = period;
> }
>
> (or something equivalent).

Okay, I'll adjust it.

> > + /*
> > + * Writing a duty cycle of zone puts the device into a state where
>
> What is "zone"? A mixture of zero and one and so approximately 0.5?

Oops, that's a typo. I just meant "zero".

> > + * writing a higher duty cycle doesn't result in the brightness that it
> > + * usually results in. This can be fixed by cycling the ENABLE register.
> > + *
> > + * As a workaround, write ENABLE=0 when the duty cycle is zero.
> > + */
> > + if (state->enabled && duty != 0) {
> > + res = regmap_write(pwm->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
> > + if (res)
> > + return res;
> > +
> > + /* Disable the auto-off timer */
> > + res = regmap_write(pwm->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
> > + if (res)
> > + return res;
> > +
> > + return regmap_write(pwm->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
> > + } else {
> > + return regmap_write(pwm->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
> > + }
>
> This code is wrong for state->enabled = false.

Why?

> How does the PWM behave when .apply is called? Does it complete the
> currently running period? Can it happen that when you switch from say
>
> .duty_cycle = 900 * TIME_BASE_NS (0x384)
> .period = 1800 * TIME_BASE_NS (0x708)
>
> to
>
> .duty_cycle = 300 * TIME_BASE_NS (0x12c)
> .period = 600 * TIME_BASE_NS (0x258)
>
> that a period with
>
> .duty_cycle = 388 * TIME_BASE_NS (0x184)
> .period = 1800 * TIME_BASE_NS (0x708)
>
> (because only NTXEC_REG_PERIOD_HIGH was written when the new period
> started) or something similar is emitted?

Changes take effect after the low byte is written, so a result like 0x184
in the above example should not happen.

When the period and duty cycle are both changed, it temporarily results
in an inconsistent state:

- period = 1800ns, duty cycle = 900ns
- period = 600ns, duty cycle = 900ns (!)
- period = 600ns, duty cycle = 300ns

The inconsistent state of duty cycle > period is handled gracefully by
the EC and it outputs a 100% duty cycle, as far as I can tell.

I currently don't have a logic analyzer / oscilloscope to measure
whether we get full PWM periods, or some kind of glitch when the new
period starts in the middle of the last one.

> > +}
> > +
> > +static struct pwm_ops ntxec_pwm_ops = {
> > + .apply = ntxec_pwm_apply,
>
> Please implement a .get_state() callback. And enable PWM_DEBUG during
> your tests.

The device doesn't support reading back the PWM state. What should a
driver do in this case?

> > + .owner = THIS_MODULE,
> > +};
> > +
> > +static int ntxec_pwm_probe(struct platform_device *pdev)
> > +{
> > + struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
> > + struct ntxec_pwm *pwm;
>
> Please don't call this variable pwm. I would expect that a variable with
> this name is of type pwm_device. I would have called it "ddata" (and the
> type would be named ntxec_pwm_ddata for me); another usual name is "priv".

Ok, I'll rename it.

> > + chip->npwm = 1;
> > +
> > + res = pwmchip_add(chip);
> > + if (res < 0)
> > + return res;
> > +
> > + platform_set_drvdata(pdev, pwm);
>
> If you do the platform_set_drvdata earlier you can just do
>
> return pwmchip_add(chip);

Good idea, I'll do that.


Thanks,
Jonathan Neuschäfer

Attachment: signature.asc
Description: PGP signature