// SPDX-License-Identifier: GPL-2.0-only /* * sl28cpld PWM driver * * Copyright (c) 2020 Michael Walle <michael@walle.cc> * * There is no public datasheet available for this PWM core. But it is easy * enough to be briefly explained. It consists of one 8-bit counter. The PWM * supports four distinct frequencies by selecting when to reset the counter. * With the prescaler setting you can select which bit of the counter is used * to reset it. This implies that the higher the frequency the less remaining * bits are available for the actual counter. * * Let cnt[7:0] be the counter, clocked at 32kHz: * +-----------+--------+--------------+-----------+---------------+ * | prescaler | reset | counter bits | frequency | period length | * +-----------+--------+--------------+-----------+---------------+ * | 0 | cnt[7] | cnt[6:0] | 250 Hz | 4000000 ns | * | 1 | cnt[6] | cnt[5:0] | 500 Hz | 2000000 ns | * | 2 | cnt[5] | cnt[4:0] | 1 kHz | 1000000 ns | * | 3 | cnt[4] | cnt[3:0] | 2 kHz | 500000 ns | * +-----------+--------+--------------+-----------+---------------+ * * Limitations: * - The hardware cannot generate a 100% duty cycle if the prescaler is 0. * - The hardware cannot atomically set the prescaler and the counter value, * which might lead to glitches and inconsistent states if a write fails. * - The counter is not reset if you switch the prescaler which leads * to glitches, too. * - The duty cycle will switch immediately and not after a complete cycle. * - Depending on the actual implementation, disabling the PWM might have * side effects. For example, if the output pin is shared with a GPIO pin * it will automatically switch back to GPIO mode.
*/
/* * Sanitize values for the PWM core. Depending on the prescaler it * might happen that we calculate a duty_cycle greater than the actual * period. This might happen if someone (e.g. the bootloader) sets an * invalid combination of values. The behavior of the hardware is * undefined in this case. But we need to report sane values back to * the PWM core.
*/
state->duty_cycle = min(state->duty_cycle, state->period);
/* Polarity inversion is not supported */ if (state->polarity != PWM_POLARITY_NORMAL) return -EINVAL;
/* * Calculate the prescaler. Pick the biggest period that isn't * bigger than the requested period.
*/
prescaler = DIV_ROUND_UP_ULL(SL28CPLD_PWM_PERIOD(0), state->period);
prescaler = order_base_2(prescaler);
if (prescaler > field_max(SL28CPLD_PWM_CTRL_PRESCALER_MASK)) return -ERANGE;
ctrl = FIELD_PREP(SL28CPLD_PWM_CTRL_PRESCALER_MASK, prescaler); if (state->enabled)
ctrl |= SL28CPLD_PWM_CTRL_ENABLE;
/* * Work around the hardware limitation. See also above. Trap 100% duty * cycle if the prescaler is 0. Set prescaler to 1 instead. We don't * care about the frequency because its "all-one" in either case. * * We don't need to check the actual prescaler setting, because only * if the prescaler is 0 we can have this particular value.
*/ if (cycle == SL28CPLD_PWM_MAX_DUTY_CYCLE(0)) {
ctrl &= ~SL28CPLD_PWM_CTRL_PRESCALER_MASK;
ctrl |= FIELD_PREP(SL28CPLD_PWM_CTRL_PRESCALER_MASK, 1);
cycle = SL28CPLD_PWM_MAX_DUTY_CYCLE(1);
}
/* * To avoid glitches when we switch the prescaler, we have to make sure * we have a valid duty cycle for the new mode. * * Take the current prescaler (or the current period length) into * account to decide whether we have to write the duty cycle or the new * prescaler first. If the period length is decreasing we have to * write the duty cycle first.
*/
write_duty_cycle_first = pwm->state.period > state->period;
if (write_duty_cycle_first) {
ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CYCLE, cycle); if (ret) return ret;
}
ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CTRL, ctrl); if (ret) return ret;
if (!write_duty_cycle_first) {
ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CYCLE, cycle); if (ret) return ret;
}
priv->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!priv->regmap) {
dev_err(&pdev->dev, "could not get parent regmap\n"); return -ENODEV;
}
ret = device_property_read_u32(&pdev->dev, "reg", &priv->offset); if (ret) {
dev_err(&pdev->dev, "no 'reg' property found (%pe)\n",
ERR_PTR(ret)); return -EINVAL;
}
/* Initialize the pwm_chip structure */
chip->ops = &sl28cpld_pwm_ops;
ret = devm_pwmchip_add(&pdev->dev, chip); if (ret) {
dev_err(&pdev->dev, "failed to add PWM chip (%pe)",
ERR_PTR(ret)); return ret;
}
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.