// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2018-2019 NXP. * * Limitations: * - The TPM counter and period counter are shared between * multiple channels, so all channels should use same period * settings. * - Changes to polarity cannot be latched at the time of the * next period start. * - Changing period and duty cycle together isn't atomic, * with the wrong timing it might happen that a period is * produced with old duty cycle but new period settings.
*/
/* * The reference manual describes this field as two separate bits. The * semantic of the two bits isn't orthogonal though, so they are treated * together as a 2-bit field here.
*/ #define PWM_IMX_TPM_CnSC_ELS GENMASK(3, 2) #define PWM_IMX_TPM_CnSC_ELS_INVERSED FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 1) #define PWM_IMX_TPM_CnSC_ELS_NORMAL FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 2)
/* * This function determines for a given pwm_state *state that a consumer * might request the pwm_state *real_state that eventually is implemented * by the hardware and the necessary register values (in *p) to achieve * this.
*/ staticint pwm_imx_tpm_round_state(struct pwm_chip *chip, struct imx_tpm_pwm_param *p, struct pwm_state *real_state, conststruct pwm_state *state)
{ struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
u32 rate, prescale, period_count, clock_unit;
u64 tmp;
/* calculate real period HW can support */
tmp = (u64)period_count << prescale;
tmp *= NSEC_PER_SEC;
real_state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
/* * if eventually the PWM output is inactive, either * duty cycle is 0 or status is disabled, need to * make sure the output pin is inactive.
*/ if (!state->enabled)
real_state->duty_cycle = 0; else
real_state->duty_cycle = state->duty_cycle;
/* get polarity */
val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); if ((val & PWM_IMX_TPM_CnSC_ELS) == PWM_IMX_TPM_CnSC_ELS_INVERSED)
state->polarity = PWM_POLARITY_INVERSED; else /* * Assume reserved values (2b00 and 2b11) to yield * normal polarity.
*/
state->polarity = PWM_POLARITY_NORMAL;
/* get channel status */
state->enabled = FIELD_GET(PWM_IMX_TPM_CnSC_ELS, val) ? true : false;
return 0;
}
/* this function is supposed to be called with mutex hold */ staticint pwm_imx_tpm_apply_hw(struct pwm_chip *chip, struct imx_tpm_pwm_param *p, struct pwm_state *state, struct pwm_device *pwm)
{ struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); bool period_update = false; bool duty_update = false;
u32 val, cmod, cur_prescale; unsignedlong timeout; struct pwm_state c;
if (state->period != tpm->real_period) { /* * TPM counter is shared by multiple channels, so * prescale and period can NOT be modified when * there are multiple channels in use with different * period settings.
*/ if (tpm->user_count > 1) return -EBUSY;
/* set TPM counter prescale */
val &= ~PWM_IMX_TPM_SC_PS;
val |= FIELD_PREP(PWM_IMX_TPM_SC_PS, p->prescale);
writel(val, tpm->base + PWM_IMX_TPM_SC);
/* * if the counter is disabled (CMOD == 0), programming the new * period length (MOD) will not reset the counter (CNT). If * CNT.COUNT happens to be bigger than the new MOD value then * the counter will end up being reset way too late. Therefore, * manually reset it to 0.
*/ if (!cmod)
writel(0x0, tpm->base + PWM_IMX_TPM_CNT); /* * set period count: * if the PWM is disabled (CMOD[1:0] = 2b00), then MOD register * is updated when MOD register is written. * * if the PWM is enabled (CMOD[1:0] ≠ 2b00), the period length * is latched into hardware when the next period starts.
*/
writel(p->mod, tpm->base + PWM_IMX_TPM_MOD);
tpm->real_period = state->period;
period_update = true;
}
pwm_imx_tpm_get_state(chip, pwm, &c);
/* polarity is NOT allowed to be changed if PWM is active */ if (c.enabled && c.polarity != state->polarity) return -EBUSY;
if (state->duty_cycle != c.duty_cycle) { /* * set channel value: * if the PWM is disabled (CMOD[1:0] = 2b00), then CnV register * is updated when CnV register is written. * * if the PWM is enabled (CMOD[1:0] ≠ 2b00), the duty length * is latched into hardware when the next period starts.
*/
writel(p->val, tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm));
duty_update = true;
}
/* make sure MOD & CnV registers are updated */ if (period_update || duty_update) {
timeout = jiffies + msecs_to_jiffies(tpm->real_period /
NSEC_PER_MSEC + 1); while (readl(tpm->base + PWM_IMX_TPM_MOD) != p->mod
|| readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm))
!= p->val) { if (time_after(jiffies, timeout)) return -ETIME;
cpu_relax();
}
}
/* * polarity settings will enabled/disable output status * immediately, so if the channel is disabled, need to * make sure MSA/MSB/ELS are set to 0 which means channel * disabled.
*/
val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm));
val &= ~(PWM_IMX_TPM_CnSC_ELS | PWM_IMX_TPM_CnSC_MSA |
PWM_IMX_TPM_CnSC_MSB); if (state->enabled) { /* * set polarity (for edge-aligned PWM modes) * * ELS[1:0] = 2b10 yields normal polarity behaviour, * ELS[1:0] = 2b01 yields inversed polarity. * The other values are reserved.
*/
val |= PWM_IMX_TPM_CnSC_MSB;
val |= (state->polarity == PWM_POLARITY_NORMAL) ?
PWM_IMX_TPM_CnSC_ELS_NORMAL :
PWM_IMX_TPM_CnSC_ELS_INVERSED;
}
writel(val, tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm));
/* control the counter status */ if (state->enabled != c.enabled) {
val = readl(tpm->base + PWM_IMX_TPM_SC); if (state->enabled) { if (++tpm->enable_count == 1)
val |= PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK;
} else { if (--tpm->enable_count == 0)
val &= ~PWM_IMX_TPM_SC_CMOD;
}
writel(val, tpm->base + PWM_IMX_TPM_SC);
}
/* * Force 'real_period' to be zero to force period update code * can be executed after system resume back, since suspend causes * the period related registers to become their reset values.
*/
tpm->real_period = 0;
clk_disable_unprepare(tpm->clk);
ret = pinctrl_pm_select_sleep_state(dev); if (ret)
clk_prepare_enable(tpm->clk);
return ret;
}
staticint pwm_imx_tpm_resume(struct device *dev)
{ struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev); int ret = 0;
ret = pinctrl_pm_select_default_state(dev); if (ret) return ret;
ret = clk_prepare_enable(tpm->clk); if (ret) {
dev_err(dev, "failed to prepare or enable clock: %d\n", ret);
pinctrl_pm_select_sleep_state(dev);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.