Re: [PATCH v3 2/2] drm/bridge: ti-sn65dsi86: Implement the pwm_chip

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello Bjorn,

On Tue, Jun 22, 2021 at 08:12:48PM -0500, Bjorn Andersson wrote:
> On Tue 22 Jun 15:29 CDT 2021, Uwe Kleine-K?nig wrote:
> > On Mon, Jun 21, 2021 at 10:09:48PM -0500, Bjorn Andersson wrote:
> > > +		/*
> > > +		 * PWM duty cycle is given as:
> > > +		 *
> > > +		 *   duty = BACKLIGHT / (BACKLIGHT_SCALE + 1)
> > > +		 *
> > > +		 * The documentation is however inconsistent in its examples,
> > > +		 * so the interpretation used here is that the duty cycle is
> > > +		 * the period of BACKLIGHT * PRE_DIV / REFCLK_FREQ.
> > 
> > I don't understand this.
> > 
> > > +		 *
> > > +		 * The ratio PRE_DIV / REFCLK_FREQ is rounded up to whole
> > > +		 * nanoseconds in order to ensure that the calculations are
> > > +		 * idempotent and gives results that are smaller than the
> > > +		 * requested value.
> > > +		 */
> > > +		tick = DIV_ROUND_UP(NSEC_PER_SEC * pre_div, pdata->pwm_refclk_freq);
> > > +		backlight = state->duty_cycle / tick;
> > 
> > You're loosing precision here by dividing by the result of a division.
> 
> The actual period is also a result of a division and after spending too
> many hours scratching my head I reached to conclusion that this was the
> reason why I wasn't able to get the duty cycle calculation idempotent
> over the ranges I tested.

How did you test? Using the sysfs interface?
 
> But in my effort to describe this to you here, I finally spotted the
> error and will follow up with a new version that does:
> 
>                 actual = NSEC_PER_SEC * (pre_div * scale + 1) / pdata->pwm_refclk_freq);
>                 backlight = state->duty_cycle * (scale + 1) / actual;

So the first term ("actual") is the period that you get for a given
pre_div, scale and pwm_refclk_freq, right? And the 2nd ("backlight")
defines the register value to configure the duty_cycle, right?

I wonder: Is it possible to configure a 100% relative duty cycle? Then
backlight would be scale + 1 which (at least if scale is 0xffff) would
overflow the 16 bit register width?!

> > > +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(NSEC_PER_SEC * (pre_div * scale + 1), pdata->pwm_refclk_freq);
> > > +	state->duty_cycle = backlight * DIV_ROUND_UP(NSEC_PER_SEC * pre_div, pdata->pwm_refclk_freq);
> > 
> > If you use
> > 
> > 	state->duty_cycle = DIV_ROUND_UP(backlight * NSEC_PER_SEC * pre_div, pdata->pwm_refclk_freq);
> > 
> > instead (with a cast to u64 to not yield an overflow) the result is more
> > exact.
> > 
> 
> The problem with this is that it sometimes yields duty_cycles larger
> than what was requested... But going back to describing this as a ratio
> of the period this becomes:
> 
>         state->duty_cycle = DIV_ROUND_UP_ULL(state->period * backlight, scale + 1);

I saw your next iteration of this patch set, but didn't look into it
yet. Note that if it uses this formula it sill looses precision.
Consider:

	pwm_refclk_freq = 1333333
	pre_div = 4
	scale = 60000
	backlight = 59999

then you calculate:

	state->period = 180000796 (exact value: 180000795.00019875)
	state->duty_cycle = 179994797 (exact value: 179994795.0736975)

so duty_cycle should actually be reported as 179994796. That happens
because state->period is already the result of a division, you get the
right value when doing:

	state->duty_cycle = round_up(NSEC_PER_SEC * (pre_div * scale + 1) * backlight, (scale + 1) * pdata->pwm_refclk_freq)

> > I still find this surprising, I'd expect that SCALE also matters for the
> > duty_cycle. With the assumption implemented here modifying SCALE only
> > affects the period. This should be easy to verify?! I would expect that
> > changing SCALE doesn't affect the relative duty_cycle, so the brightness
> > of an LED is unaffected (unless the period gets too big of course).
> > 
> 
> I think the hardware is two nested counters, one (A) ticking at REFCLK_FREQ
> and as that hits PRE_DIV, it kicks the second counter (B) (and resets).
> 
> As counter A is reset the output signal goes high, when A hits BACKLIGHT the
> signal goes low and when A hits BACKLIGHT_SCALE it resets.

then we would probably have:

	period = (scale + 1) * pre_div / refclk

but not

	period = (scale * pre_div + 1) / refclk

. The former would be nicer because then in the calculation for
duty_cycle the factor (scale + 1) would cancel.

> Per this implementation the actual length of the duty cycle would indeed
> be independent of the BACKLIGHT_SCALE,

In your formula for duty_cycle scale actually does matter. So I think
we're not there yet?

> but the length of the low signal (and hence the ratio, which results
> in the actual brightness) does depend on BACKLIGHT_SCALE.

Best regards
Uwe

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

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux