Re: [PATCH 1/2] drivers: pwm: pwm-atmel: switch to atomic PWM
From: m18063
Date: Wed Mar 01 2017 - 08:59:36 EST
Hi Boris,
Thank you for the closer review. Please see my answers
inline.
Thank you,
Claudiu
On 28.02.2017 23:07, Boris Brezillon wrote:
> Hi Claudiu,
>
> On Tue, 28 Feb 2017 12:40:14 +0200
> Claudiu Beznea <claudiu.beznea@xxxxxxxxxxxxx> wrote:
>
>> The currently Atmel PWM controllers supported by this driver
>> could change period and duty factor without channel disable
>> (sama5d3 supports this by using period and duty factor update
>> registers, sam9rl support this by writing channel update
>> register and select the corresponding update: period or duty
>> factor).
> Hm, I had a closer a look at the datasheet, and I'm not sure this is
> possible in a safe way. AFAICT (I might be wrong), there's no way to
> atomically update both the period and duty cycles. So, imagine you're
> just at the end of the current period and you update one of the 2 params
> (either the period or the duty cycle) before the end of the period, but
> the other one is updated just after the beginning of a new period.
> During one cycle you'll have a bad config.
I was also think at this scenario. I thought that this should be
good for most of the cases.
>
> While this should be acceptable in most cases, there are a few cases
> where glitches are not permitted (when the PWM drives a critical
> device). Also, I don't know what happens if we have duty > period.
duty > period is checked by the PWM core before calling apply method.
>
>> The chip doesn't support run time changings of signal
>> polarity. This has been adapted by first disabling the channel,
>> update registers and the enabling the channel.
> Yep. I agree with that one.
>
>> Signed-off-by: Claudiu Beznea <claudiu.beznea@xxxxxxxxxxxxx>
>> ---
>> drivers/pwm/pwm-atmel.c | 217 ++++++++++++++++++++++++++----------------------
>> 1 file changed, 118 insertions(+), 99 deletions(-)
>>
>> diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
>> index 67a7023..014b86c 100644
>> --- a/drivers/pwm/pwm-atmel.c
>> +++ b/drivers/pwm/pwm-atmel.c
>> @@ -68,7 +68,7 @@ struct atmel_pwm_chip {
>> struct mutex isr_lock;
>>
>> void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
>> - unsigned long dty, unsigned long prd);
>> + unsigned long dty, unsigned long prd, bool enabled);
>> };
>>
>> static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip)
>> @@ -105,99 +105,120 @@ static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip,
>> writel_relaxed(val, chip->base + base + offset);
>> }
>>
>> -static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
>> - int duty_ns, int period_ns)
>> +static int atmel_pwm_config_prepare(struct pwm_chip *chip,
>> + struct pwm_state *state, unsigned long *prd,
>> + unsigned long *dty, unsigned int *pres)
> I'd rename the function atmel_pwm_calculate_params(). Also, when the
> period does not change we don't have to calculate prd and pres, so
> maybe we should have one function to calculate prd+pres and another one
> to calculate dty:
I agree. I didn't want to change the current implementation from
this point of view.
> static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip,
> const struct pwm_state *state,
> u32 *cprd, u32 *pres)
> {
> struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
> unsigned long long cycles = state->period;
>
> /* Calculate the period cycles and prescale value */
> cycles *= clk_get_rate(atmel_pwm->clk);
> do_div(cycles, NSEC_PER_SEC);
>
> for (*pres = 0; cycles > PWM_MAX_PRD; cycles >>= 1)
> (*pres)++;
>
> if (*pres > PRD_MAX_PRES) {
> dev_err(chip->dev, "pres exceeds the maximum value\n");
> return -EINVAL;
> }
>
> *cprd = cycles;
>
> return 0;
> }
>
> static void atmel_pwm_calculate_cdty(const struct pwm_state *state,
> u32 cprd, u32 *cdty)
> {
> unsigned long long cycles = state->duty_cycle;
>
> cycles *= cprd;
> do_div(cycles, state->period);
>
> *cdty = cycles;
> }
>
>
>> {
>> struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> - unsigned long prd, dty;
>> unsigned long long div;
>> - unsigned int pres = 0;
>> - u32 val;
>> - int ret;
>> -
>> - if (pwm_is_enabled(pwm) && (period_ns != pwm_get_period(pwm))) {
>> - dev_err(chip->dev, "cannot change PWM period while enabled\n");
>> - return -EBUSY;
>> - }
>>
>> /* Calculate the period cycles and prescale value */
>> - div = (unsigned long long)clk_get_rate(atmel_pwm->clk) * period_ns;
>> + div = (unsigned long long)clk_get_rate(atmel_pwm->clk) * state->period;
>> do_div(div, NSEC_PER_SEC);
>>
>> + *pres = 0;
>> while (div > PWM_MAX_PRD) {
>> div >>= 1;
>> - pres++;
>> + (*pres)++;
>> }
>>
>> - if (pres > PRD_MAX_PRES) {
>> + if (*pres > PRD_MAX_PRES) {
>> dev_err(chip->dev, "pres exceeds the maximum value\n");
>> return -EINVAL;
>> }
>>
>> /* Calculate the duty cycles */
>> - prd = div;
>> - div *= duty_ns;
>> - do_div(div, period_ns);
>> - dty = prd - div;
>> + *prd = div;
>> + div *= state->duty_cycle;
>> + do_div(div, state->period);
>> + *dty = *prd - div;
> Not sure this subtraction is needed, you just need to revert the
> polarity selection (if polarity == NORMAL => set CPOL, otherwise, clear
> it).
I didn't want to change the existing implementation.
>
>>
>> - ret = clk_enable(atmel_pwm->clk);
>> - if (ret) {
>> - dev_err(chip->dev, "failed to enable PWM clock\n");
>> - return ret;
>> - }
>> + return 0;
>> +}
>> +
>> +static void atmel_pwm_config_set(struct pwm_chip *chip, struct pwm_device *pwm,
>> + unsigned long dty, unsigned long prd,
>> + unsigned long pres, enum pwm_polarity polarity,
>> + bool enabled)
>> +{
>> + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> + u32 val;
>>
>> /* It is necessary to preserve CPOL, inside CMR */
>> val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
>> val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
>> + if (polarity == PWM_POLARITY_NORMAL)
>> + val &= ~PWM_CMR_CPOL;
>> + else
>> + val |= PWM_CMR_CPOL;
>> atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
>> - atmel_pwm->config(chip, pwm, dty, prd);
>> + atmel_pwm->config(chip, pwm, dty, prd, enabled);
>> mutex_lock(&atmel_pwm->isr_lock);
>> atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
>> atmel_pwm->updated_pwms &= ~(1 << pwm->hwpwm);
>> mutex_unlock(&atmel_pwm->isr_lock);
>> -
>> - clk_disable(atmel_pwm->clk);
>> - return ret;
>> }
>>
> You can move the code in atmel_pwm_config_set() directly in
> atmel_pwm_apply().
Ok. I will.
>
>> static void atmel_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm,
>> - unsigned long dty, unsigned long prd)
>> + unsigned long dty, unsigned long prd,
>> + bool enabled)
>> {
>> struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> unsigned int val;
>> + unsigned long timeout;
>>
>> + if (pwm_is_enabled(pwm) && enabled) {
>> + /* Update duty factor. */
>> + val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
>> + val &= ~PWM_CMR_UPD_CDTY;
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
>>
>> - atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
>> -
>> - val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
>> - val &= ~PWM_CMR_UPD_CDTY;
>> - atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
>> -
>> - /*
>> - * If the PWM channel is enabled, only update CDTY by using the update
>> - * register, it needs to set bit 10 of CMR to 0
>> - */
>> - if (pwm_is_enabled(pwm))
>> - return;
>> - /*
>> - * If the PWM channel is disabled, write value to duty and period
>> - * registers directly.
>> - */
>> - atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
>> - atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
>> + /*
>> + * Wait for the duty factor update.
>> + */
>> + mutex_lock(&atmel_pwm->isr_lock);
>> + atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR) &
>> + ~(1 << pwm->hwpwm);
>> +
>> + timeout = jiffies + 2 * HZ;
>> + while (!(atmel_pwm->updated_pwms & (1 << pwm->hwpwm)) &&
>> + time_before(jiffies, timeout)) {
>> + usleep_range(10, 100);
>> + atmel_pwm->updated_pwms |=
>> + atmel_pwm_readl(atmel_pwm, PWM_ISR);
>> + }
>> +
>> + mutex_unlock(&atmel_pwm->isr_lock);
>> +
>> + /* Update period. */
>> + val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
>> + val |= PWM_CMR_UPD_CDTY;
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, prd);
> As I said above, I'm almost sure it's not 100% safe to update both
> parameters. We'd better stick to the existing implementation and see if
> new IPs provide an atomic period+duty update feature.
There is such approach only for synchronous channels, which I didn't cover
in this implementation.
>
>> + } else {
>> + /*
>> + * If the PWM channel is disabled, write value to duty and
>> + * period registers directly.
>> + */
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
>> + }
>> }
> There are 2 different cases in this _config() function, and
> atmel_pwm_apply() is already doing different things when the PWM is
> enabled than when it's disabled, so maybe it's worth creating 2
> different hooks, one for the update-while-running case, and another for
> for the initialization case.
>
> How about having the following hooks in atmel_pwm_data?
>
> void (*update_cdty)(struct pwm_chip *chip,
> struct pwm_device *pwm,
> u32 cdty);
> void (*set_cprd_cdty)(struct pwm_chip *chip,
> struct pwm_device *pwm,
> u32 cprd, u32 cdty);
I agree.
>
>>
>> static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
>> - unsigned long dty, unsigned long prd)
>> + unsigned long dty, unsigned long prd,
>> + bool enabled)
>> {
>> struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>>
>> - if (pwm_is_enabled(pwm)) {
>> + if (pwm_is_enabled(pwm) && enabled) {
>> /*
>> - * If the PWM channel is enabled, using the duty update register
>> - * to update the value.
>> + * If the PWM channel is enabled, use update registers
>> + * to update the duty and period.
>> */
>> atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTYUPD, dty);
>> + atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CPRDUPD, prd);
> Same here, I'm not convinced we are guaranteed that both parameters will
> be applied atomically. There's a sync mechanism described in the
> datasheet, but I'm not sure I understand how it works, and anyway,
> you're not using it here, so let's stick to the "update duty only"
> approach for now.
Only for synchronous channels the atomicity is granted. I didn't cover that
in this commit. With this approach the dty, period will be changed at the
next period but there is no guarantee that they will be synchronously
updated.
>
>> } else {
>> /*
>> * If the PWM channel is disabled, write value to duty and
>> @@ -208,49 +229,6 @@ static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
>> }
>> }
>>
>> -static int atmel_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
>> - enum pwm_polarity polarity)
>> -{
>> - struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> - u32 val;
>> - int ret;
>> -
>> - val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
>> -
>> - if (polarity == PWM_POLARITY_NORMAL)
>> - val &= ~PWM_CMR_CPOL;
>> - else
>> - val |= PWM_CMR_CPOL;
>> -
>> - ret = clk_enable(atmel_pwm->clk);
>> - if (ret) {
>> - dev_err(chip->dev, "failed to enable PWM clock\n");
>> - return ret;
>> - }
>> -
>> - atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
>> -
>> - clk_disable(atmel_pwm->clk);
>> -
>> - return 0;
>> -}
>> -
>> -static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
>> -{
>> - struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> - int ret;
>> -
>> - ret = clk_enable(atmel_pwm->clk);
>> - if (ret) {
>> - dev_err(chip->dev, "failed to enable PWM clock\n");
>> - return ret;
>> - }
>> -
>> - atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm);
>> -
>> - return 0;
>> -}
>> -
>> static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
>> {
>> struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> @@ -285,17 +263,58 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
>> clk_disable(atmel_pwm->clk);
>> }
>>
>> +static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>> + struct pwm_state *state)
>> +{
>> + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
>> + struct pwm_state cstate;
>> + unsigned long prd, dty;
>> + unsigned int pres;
>> + bool enabled = true;
>> + int ret;
>> +
>> + pwm_get_state(pwm, &cstate);
>> +
>> + if (state->enabled) {
>> + ret = atmel_pwm_config_prepare(chip, state, &prd, &dty,
>> + &pres);
>> + if (ret) {
>> + dev_err(chip->dev, "failed to prepare config\n");
>> + return ret;
>> + }
>> +
>> + if (cstate.polarity != state->polarity) {
> Hm, you seem to unconditionally disable the PWM. What if it was already
> disabled? The atmel_pwm->clk refcounting is likely to be wrong after
> that.
I agree. I should take care of the current PWM state before disabling it.
>
> Moreover, if you follow my previous suggestions, you should have
>
> if (cstate.enabled &&
> (cstate.polarity != state->polarity ||
> cstate.period != state->period))
>
>> + atmel_pwm_disable(chip, pwm);
>> + enabled = false;
> Just set cstate.enabled to false here, no need for an extra variable.
I agree with you.
>
>> + }
>> +
>> + if (!cstate.enabled || !enabled) {
> if (!cstate.enabled) {
>
>> + ret = clk_enable(atmel_pwm->clk);
>> + if (ret) {
>> + dev_err(chip->dev, "failed to enable clock\n");
>> + return ret;
>> + }
>> + }
>> +
>> + atmel_pwm_config_set(chip, pwm, dty, prd, pres,
>> + state->polarity, enabled);
>> + if (!cstate.enabled || !enabled)
>> + atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm);
> I would differentiate the update CDTY only and set CDTY+CPRD+CMR cases
> here. Something like:
>
> if (cstate.enabled) {
> /*
> * 1/ read CPRD
> * 2/ call atmel_pwm_calculate_cdty()
> * 3/ call ->update_cdty() hook
> */
> } else {
> /*
> * 1/ enable the clk
> * 2/ read CPRD
> * 3/ call atmel_pwm_calculate_cprd_and_pres()
> * 4/ call atmel_pwm_calculate_cdty()
> * 3/ call ->set_cprd_cdty() hook
> * 4/ write CMR (PRES + polarity)
> * 5/ start the PWM (PWM_ENA)
> */
> }
>
>> + } else if (cstate.enabled) {
>> + atmel_pwm_disable(chip, pwm);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> static const struct pwm_ops atmel_pwm_ops = {
>> - .config = atmel_pwm_config,
>> - .set_polarity = atmel_pwm_set_polarity,
>> - .enable = atmel_pwm_enable,
>> - .disable = atmel_pwm_disable,
>> + .apply = atmel_pwm_apply,
>> .owner = THIS_MODULE,
>> };
>>
>> struct atmel_pwm_data {
>> void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
>> - unsigned long dty, unsigned long prd);
>> + unsigned long dty, unsigned long prd, bool enabled);
>> };
>>
>> static const struct atmel_pwm_data atmel_pwm_data_v1 = {