On Sat 25 Sep 16:25 CDT 2021, Uwe Kleine-K?nig wrote:

> Hello Bjorn,
> 
> On Fri, Sep 24, 2021 at 05:04:41PM -0500, Bjorn Andersson wrote:
> > On Fri 24 Sep 02:54 CDT 2021, Uwe Kleine-K?nig wrote:
> > > > +static int ti_sn65dsi86_read_u16(struct ti_sn65dsi86 *pdata,
> > > > +                                unsigned int reg, u16 *val)
> > > > +{
> > > > +       unsigned int tmp;
> > > > +       int ret;
> > > > +
> > > > +       ret = regmap_read(pdata->regmap, reg, &tmp);
> > > > +       if (ret)
> > > > +               return ret;
> > > > +       *val = tmp;
> > > > +
> > > > +       ret = regmap_read(pdata->regmap, reg + 1, &tmp);
> > > > +       if (ret)
> > > > +               return ret;
> > > > +       *val |= tmp << 8;
> > > > +
> > > > +       return 0;
> > > > +}
> > > > +
> > > >  static void ti_sn65dsi86_write_u16(struct ti_sn65dsi86 *pdata,
> > > >                                    unsigned int reg, u16 val)
> > > >  {
> > > > -       regmap_write(pdata->regmap, reg, val & 0xFF);
> > > > -       regmap_write(pdata->regmap, reg + 1, val >> 8);
> > > > +       u8 buf[2] = { val & 0xff, val >> 8 };
> > > > +
> > > > +       regmap_bulk_write(pdata->regmap, reg, &buf, ARRAY_SIZE(buf));
> > > 
> > > Given that ti_sn65dsi86_write_u16 uses regmap_bulk_write I wonder why
> > > ti_sn65dsi86_read_u16 doesn't use regmap_bulk_read.
> > 
> > Seems reasonable to make that use the bulk interface as well and this
> > would look better in its own patch.
> 
> Not sure I understand you here. My position here would be to introduce
> these two functions with a consistent behaviour. If both use bulk or
> both don't use it (and you introduce it later in a separate patch)
> doesn't matter to me.
> 

We're in agreement :)

> > > >  static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn65dsi86 *pdata)
> > > > @@ -253,6 +298,12 @@ static void ti_sn_bridge_set_refclk_freq(struct 
> > > > ti_sn65dsi86 *pdata)
> > > >  
> > > >         regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, 
> > > > REFCLK_FREQ_MASK,
> > > >                            REFCLK_FREQ(i));
> > > > +
> > > > +       /*
> > > > +        * The PWM refclk is based on the value written to 
> > > > SN_DPPLL_SRC_REG,
> > > > +        * regardless of its actual sourcing.
> > > > +        */
> > > > +       pdata->pwm_refclk_freq = ti_sn_bridge_refclk_lut[i];
> > > >  }
> > > >  
> > > >  static void ti_sn65dsi86_enable_comms(struct ti_sn65dsi86 *pdata)
> > > > @@ -1259,9 +1310,283 @@ static struct auxiliary_driver 
> > > > ti_sn_bridge_driver = {
> > > >  };
> > > >  
> > > >  /* 
> > > > -----------------------------------------------------------------------------
> > > > - * GPIO Controller
> > > > + * PWM Controller
> > > > + */
> > > > +#if defined(CONFIG_PWM)
> > > > +static int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata)
> > > > +{
> > > > +       return atomic_xchg(&pdata->pwm_pin_busy, 1) ? -EBUSY : 0;
> > > > +}
> > > > +
> > > > +static void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata)
> > > > +{
> > > > +       atomic_set(&pdata->pwm_pin_busy, 0);
> > > > +}
> > > > +
> > > > +static struct ti_sn65dsi86 *pwm_chip_to_ti_sn_bridge(struct pwm_chip 
> > > > *chip)
> > > > +{
> > > > +       return container_of(chip, struct ti_sn65dsi86, pchip);
> > > > +}
> > > > +
> > > > +static int ti_sn_pwm_request(struct pwm_chip *chip, struct pwm_device 
> > > > *pwm)
> > > > +{
> > > > +       struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
> > > > +
> > > > +       return ti_sn_pwm_pin_request(pdata);
> > > > +}
> > > > +
> > > > +static void ti_sn_pwm_free(struct pwm_chip *chip, struct pwm_device 
> > > > *pwm)
> > > > +{
> > > > +       struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
> > > > +
> > > > +       ti_sn_pwm_pin_release(pdata);
> > > > +}
> > > > +
> > > > +/*
> > > > + * Limitations:
> > > > + * - The PWM signal is not driven when the chip is powered down, or in 
> > > > its
> > > > + *   reset state and the driver does not implement the "suspend state"
> > > > + *   described in the documentation. In order to save power, 
> > > > state->enabled is
> > > > + *   interpreted as denoting if the signal is expected to be valid, 
> > > > and is used
> > > > + *   to determine if the chip needs to be kept powered.
> > > > + * - Changing both period and duty_cycle is not done atomically, 
> > > > neither is the
> > > > + *   multi-byte register updates, so the output might briefly be 
> > > > undefined
> > > > + *   during update.
> > > >   */
> > > > +static int ti_sn_pwm_apply(struct pwm_chip *chip, struct pwm_device 
> > > > *pwm,
> > > > +                          const struct pwm_state *state)
> > > > +{
> > > > +       struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
> > > > +       unsigned int pwm_en_inv;
> > > > +       unsigned int backlight;
> > > > +       unsigned int pre_div;
> > > > +       unsigned int scale;
> > > > +       u64 period_max;
> > > > +       u64 period;
> > > > +       int ret;
> > > > +
> > > > +       if (!pdata->pwm_enabled) {
> > > > +               ret = pm_runtime_get_sync(pdata->dev);
> > > > +               if (ret < 0) {
> > > > +                       pm_runtime_put_sync(pdata->dev);
> > > > +                       return ret;
> > > > +               }
> > > > +       }
> > > > +
> > > > +       if (state->enabled) {
> > > > +               if (!pdata->pwm_enabled) {
> > > > +                       /*
> > > > +                        * The chip might have been powered down while 
> > > > we
> > > > +                        * didn't hold a PM runtime reference, so mux 
> > > > in the
> > > > +                        * PWM function on the GPIO pin again.
> > > > +                        */
> > > > +                       ret = regmap_update_bits(pdata->regmap, 
> > > > SN_GPIO_CTRL_REG,
> > > > +                                                SN_GPIO_MUX_MASK << (2 
> > > > * SN_PWM_GPIO_IDX),
> > > > +                                                SN_GPIO_MUX_SPECIAL << 
> > > > (2 * SN_PWM_GPIO_IDX));
> > > > +                       if (ret) {
> > > > +                               dev_err(pdata->dev, "failed to mux in 
> > > > PWM function\n");
> > > > +                               goto out;
> > > > +                       }
> > > > +               }
> > > > +
> > > > +               /*
> > > > +                * Per the datasheet the PWM frequency is given by:
> > > > +                *
> > > > +                *                          REFCLK_FREQ
> > > > +                *   PWM_FREQ = -----------------------------------
> > > > +                *               PWM_PRE_DIV * BACKLIGHT_SCALE + 1
> > > > +                *
> > > > +                * Unfortunately the math becomes overly complex unless 
> > > > we
> > > > +                * assume that this formula is missing parenthesis 
> > > > around
> > > > +                * BACKLIGHT_SCALE + 1.
> > > 
> > > We shouldn't assume a different formula than the reference manual
> > > describes because it's complex. The reasoning should be that the
> > > reference manual is wrong and that someone has convinced themselves the
> > > assumed formula is the right one instead.
> > 
> > Given the effort put in to conclude that there must be some parenthesis
> > there, I agree that "assume" might be to weak of a word...
> > 
> > > With the assumed formula: What happens if PWM_PRE_DIV is 0?
> > 
> > Looking at the specification again and I don't see anything prohibiting
> > from writing 0 in the PRE_DIV register, and writing a 0 to the register
> > causes no visual on my backlight from writing 1.
> 
> So the backlight behaves identically for PRE_DIV = 0 and PRE_DIV = 1 as
> far as you can tell?
> 

Flipping between them I see no difference on my screen.

> > Per our new understanding this should probably have resulted in a period
> > of 0.
> 
> ... but a period of zero doesn't make sense.
> 

Right, that's what I meant. If the hardware really did operate with a
zero period (or a period of 1 refclock cycle) that would be visually
noticeable.

> > That said, if the formula from the datasheet was correct then we'd
> > have a period T_pwm (given T_ref as refclk pulse length) of:
> > 
> >   T_pwm = T_ref * (div * scale + 1)
> > 
> > And with div = 0 we'd have:
> > 
> >   T_pwm = T_ref * 1
> > 
> > Which wouldn't give any room for adjusting the duty cycle, and I can
> > still do that. So that's not correct either.
> 
> I don't see a problem here (just the hardware would be unusual and
> complicated and so more expensive than the simple straigt forward way).
> 
> One way to find out if
> 
>        PWM_PRE_DIV * BACKLIGHT_SCALE + 1
>       -----------------------------------
>                   REFCLK_FREQ
> 
> or
> 
>        PWM_PRE_DIV * (BACKLIGHT_SCALE + 1)
>       -------------------------------------
>                    REFCLK_FREQ
> 
> is the right formula is to program PWM_PRE_DIV=1 and BACKLIGHT_SCALE=1
> and then check if the backlight is fully on with SN_BACKLIGHT_REG=2 (or
> only with SN_BACKLIGHT_REG=3).
> 
> > Unfortunately I still don't want to dismantle my working laptop, so I
> > don't know if I can do any better than this. Perhaps they do the typical
> > trick of encoding prediv off-by-1 and the PWM duty is off by a factor
> > of 2. Perhaps the comparator for the prediv counter trips at 1 even
> > though the register is configured at 0...
> 
> I would guess the latter.
> 

I will do some more experiments, but as we concluded previously, the
first formula (without parenthesis) doesn't make sense.

> > > > +               /*
> > > > +                * Maximum T_pwm is 255 * (65535 + 1) / REFCLK_FREQ
> > > > +                * Limit period to this to avoid overflows
> > > > +                */
> > > > +               period_max = div_u64((u64)NSEC_PER_SEC * 255 * (65535 + 
> > > > 1), pdata->pwm_refclk_freq);
> > > > +               if (period > period_max)
> > > > +                       period = period_max;
> > > > +               else
> > > > +                       period = state->period;
> > > > +
> > > > +               pre_div = DIV64_U64_ROUND_UP(period * 
> > > > pdata->pwm_refclk_freq,
> > > > +                                            (u64)NSEC_PER_SEC * 
> > > > (BACKLIGHT_SCALE_MAX + 1));
> > > > +               scale = div64_u64(period * pdata->pwm_refclk_freq, 
> > > > (u64)NSEC_PER_SEC * pre_div) - 1;
> > > 
> > > You're loosing precision here as you divide by the result of a division.
> > 
> > As described above, I'm trying to find suitable values for PRE_DIV and
> > BACKLIGHT_SCALE in:
> > 
> >   T_pwm * refclk_freq
> >  --------------------- = PRE_DIV * (BACKLIGHT_SCALE + 1)
> >      NSEC_PER_SEC
> > 
> > BACKLIGHT_SCALE must fit in 16 bits and in order to be useful for
> > driving the backlight control be large enough that the resolution
> > (BACKLIGTH = [0..BACKLIGHT_SCALE]) covers whatever resolution
> > pwm-backlight might expose.
> > 
> > For the intended use case this seems pretty good, but I presume you
> > could pick better values to get closer to the requested period.
> > 
> > Do you have a better suggestion for how to pick PRE_DIV and
> > BACKLIGHT_SCALE?
> 
> My motivation is limited to spend brain cycles here if we're not sure
> we're using the right formula. Maybe my concern is wrong given that
> pre_div isn't only an interim result but an actual register value. I
> will have to think about that when I'm not tired.
> 

The alternative to this approach, afaict, would be to search for PRE_DIV
and BACKLIGHT_SCALE that gives us the closest period to the requested
one.

But that would come at the expense of duty cycle resolution, which is
what this currently optimizes for.

> > > > +static void ti_sn_pwm_get_state(struct pwm_chip *chip, struct 
> > > > pwm_device *pwm,
> > > > +                               struct pwm_state *state)
> > > > +{
> > > > +       struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
> > > > +       unsigned int pwm_en_inv;
> > > > +       unsigned int pre_div;
> > > > +       u16 backlight;
> > > > +       u16 scale;
> > > > +       int ret;
> > > > +
> > > > +       ret = regmap_read(pdata->regmap, SN_PWM_EN_INV_REG, 
> > > > &pwm_en_inv);
> > > > +       if (ret)
> > > > +               return;
> > > > +
> > > > +       ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_SCALE_REG, 
> > > > &scale);
> > > > +       if (ret)
> > > > +               return;
> > > > +
> > > > +       ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_REG, 
> > > > &backlight);
> > > > +       if (ret)
> > > > +               return;
> > > > +
> > > > +       ret = regmap_read(pdata->regmap, SN_PWM_PRE_DIV_REG, &pre_div);
> > > > +       if (ret)
> > > > +               return;
> > > > +
> > > > +       state->enabled = FIELD_GET(SN_PWM_EN_MASK, pwm_en_inv);
> > > > +       if (FIELD_GET(SN_PWM_INV_MASK, pwm_en_inv))
> > > > +               state->polarity = PWM_POLARITY_INVERSED;
> > > > +       else
> > > > +               state->polarity = PWM_POLARITY_NORMAL;
> > > > +
> > > > +       state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * 
> > > > (scale + 1),
> > > > +                                        pdata->pwm_refclk_freq);
> > > > +       state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * 
> > > > pre_div * backlight,
> > > > +                                            pdata->pwm_refclk_freq);
> > > 
> > > What happens if scale + 1 < backlight?
> > 
> > When we write the backlight register above, we cap it at scale, so for
> > the typical case that shouldn't happen.
> 
> .get_state() is called before the first .apply(), to the values to
> interpret here originate from the bootloader which might write strange
> settings that the linux driver would never do. So being prudent here is
> IMHO a good idea.
> 

Didn't notice that as I traced things, I will add some sanity nudging
here.

> It's a shame that .get_state() cannot return an error.
> 
> > So I guess the one case when this could happen is if we get_state()
> > before pwm_apply(), when someone else has written broken values
> 
> ack.
> 

Thanks,
Bjorn

Reply via email to