[PATCH] pwm: meson: Convert to waveform API
Alexandre Mergnat
amergnat at baylibre.com
Sun Aug 9 01:51:08 PDT 2026
Hi, a gentle ping on this patch. It has been around three weeks since
the submission. Any feedback would be appreciated.
Regards,
Alexandre
On Fri, Jul 17, 2026 at 9:18 PM Alexandre Mergnat <amergnat at baylibre.com> wrote:
>
> The PWM subsystem introduced a new waveform abstraction that
> describes a PWM signal by period length, duty length and duty offset
> instead of period, duty cycle and polarity. The meson driver still
> uses the legacy .apply() and .get_state() callbacks and does not
> implement the waveform callbacks, so consumers cannot use the
> pwm_*_waveform() helper functions with it.
>
> Adopt the new API by implementing .round_waveform_tohw(),
> .round_waveform_fromhw(), .read_waveform() and .write_waveform()
> with a private meson_pwm_waveform struct holding the hardware
> representation. The cached channel state fields (rate, hi, lo,
> constant, inverted) are removed; counter values are now computed in
> .round_waveform_tohw() via clk_round_rate() without register writes,
> then committed in .write_waveform().
>
> The hardware invert bit places the duty active phase at the end of the
> period on revisions that have it. Input clock rates are kept at or
> below 1 GHz so each counter cycle remains representable in nanoseconds
> by .round_waveform_fromhw(). Requests for which clk_round_rate() cannot
> provide such a rate are rejected. Otherwise, period counts above the
> 16-bit range are clamped, while a zero count is rounded up to one and
> signalled by .round_waveform_tohw() returning 1.
>
> Signed-off-by: Alexandre Mergnat <amergnat at baylibre.com>
> ---
> The PWM core gained a new waveform abstraction that lets drivers
> describe the hardware more directly and gives consumers access to
> rounding and read-back without touching registers. The meson driver
> still uses the legacy .apply() and .get_state() callbacks, so
> consumers of its PWMs cannot benefit from the new pwm_*_waveform()
> helpers.
>
> This series implements the four waveform callbacks and removes the
> cached channel state that the legacy .apply() path relied on.
> Settings are now computed without side effects and committed in a
> separate step, matching the split the waveform API requires.
>
> Behavioural changes visible to consumers:
>
> - On revisions without the invert bit, a nonzero duty offset is
> rounded down to zero. An inverted polarity request through the
> legacy path produces the same register settings as the previous
> "duty = period - duty" emulation, so the generated signal is
> unchanged for those consumers.
>
> - Disabling a PWM no longer emulates an inactive-high output on
> revisions without the invert bit. The PWM API defines the output
> of a disabled PWM as undefined (this was already the case for
> .apply()), so affected consumers were relying on unspecified
> behaviour. Consumers that need an inactive high output should keep
> the PWM enabled with inverted polarity and zero duty cycle.
>
> - Input clock rates are kept at or below 1 GHz so counter cycles remain
> representable in nanoseconds. Requests for which clk_round_rate()
> cannot provide such a rate are rejected. Otherwise, period counts
> exceeding the 16-bit range are clamped. A zero count is rounded up to
> one, signalled by .round_waveform_tohw() returning 1.
>
> - Reported period and duty cycle values are rounded up instead of
> down, so a returned setting reproduces the same counter values
> when applied again. The constant output bit is now derived from
> the converted counter values, which also covers cases where the
> ns-to-counter conversion yields an empty high or low phase.
> clk_set_rate() failures now propagate as write errors instead of
> being logged and ignored, and .write_waveform() now also fails if
> the input clock rate changed between rounding and writing.
>
> Testing
> =======
>
> Tested on a Libre Computer Le Potato (AML-S905X-CC, Amlogic S905X /
> GXL SoC) with CONFIG_PWM_DEBUG=y, built in tree. Three PWM nodes were
> enabled in the board device tree (pwm_ab, pwm_ef, pwm_AO_ab), exposing
> 6 channels across the EE and AO clock domains.
>
> The libpwm tools drive the /dev/pwmchipN character device directly, so
> the new waveform callbacks are exercised through their own ioctls. All
> 6 channels went through pwmround for rounding queries (ROUNDWF:
> standard, too-short and too-long periods, zero and full duty, duty
> offsets, non-representable values), pwmset for rounded and exact
> settings (SETROUNDEDWF / SETEXACTWF) with hardware read-back through
> debugfs (.read_waveform()), and pwmtestperf duty sweeps. pwmtestperf
> was run in all four direction/polarity combinations at 100 us, 1 ms and
> 10 ms (72 runs), then with a 1 ns step at a non-tick-aligned period so
> every duty and duty-offset rounding boundary is crossed (about 24000
> boundary checks). PWM_DEBUG's rounding and read-back checks stayed
> silent throughout.
>
> ROUNDWF and SETEXACTWF agree as specified: SETEXACTWF applies exactly
> representable waveforms and returns -EDOM otherwise. 1 ms is exact on
> the EE chips and rounds to 999990 ns on the AO chip, reflecting
> each PWM's clock granularity. A 1 ns period request is
> rounded up to the shortest representable period with zero duty,
> signalled by .round_waveform_tohw() returning 1. Periods too long for
> the source clock to represent (349.52 ms on this board) are rejected
> with -EINVAL when clk_round_rate() cannot provide a low enough rate.
> GXL has no invert bit (has_polarity = false), so nonzero duty offsets
> are rounded down to zero and the axg/g12/s4 invert paths are not
> exercisable on this board.
>
> The sysfs interface (50%/25%/0%/100% duty, polarity inversion,
> enable/disable on all 6 channels) was also exercised and reads back
> consistently, confirming no regression for legacy consumers.
> ---
> drivers/pwm/pwm-meson.c | 372 ++++++++++++++++++++++++++++--------------------
> 1 file changed, 215 insertions(+), 157 deletions(-)
>
> diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c
> index 8c6bf3d49753..be17aae0941b 100644
> --- a/drivers/pwm/pwm-meson.c
> +++ b/drivers/pwm/pwm-meson.c
> @@ -6,11 +6,11 @@
> * PWM output is achieved by calculating a clock that permits calculating
> * two periods (low and high). The counter then has to be set to switch after
> * N cycles for the first half period.
> - * Partly the hardware has no "polarity" setting. This driver reverses the period
> - * cycles (the low length is inverted with the high length) for
> - * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity
> - * from the hardware.
> - * Setting the duty cycle will disable and re-enable the PWM output.
> + * Some of the IP block revisions have an invert bit that swaps the high and
> + * low parts of the output. This is used to implement waveforms with
> + * duty_offset_ns + duty_length_ns == period_length_ns, i.e. what the legacy
> + * API calls inverted polarity. On the other revisions a nonzero duty_offset_ns
> + * is rounded down to zero.
> * Disabling the PWM stops the output immediately (without waiting for the
> * current period to complete first).
> *
> @@ -35,6 +35,7 @@
> #include <linux/io.h>
> #include <linux/kernel.h>
> #include <linux/math64.h>
> +#include <linux/minmax.h>
> #include <linux/module.h>
> #include <linux/of.h>
> #include <linux/platform_device.h>
> @@ -96,12 +97,6 @@ static struct meson_pwm_channel_data {
> };
>
> struct meson_pwm_channel {
> - unsigned long rate;
> - unsigned int hi;
> - unsigned int lo;
> - bool constant;
> - bool inverted;
> -
> struct clk_mux mux;
> struct clk_divider div;
> struct clk_gate gate;
> @@ -156,30 +151,44 @@ static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
> clk_disable_unprepare(channel->clk);
> }
>
> -static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
> - const struct pwm_state *state)
> +struct meson_pwm_waveform {
> + unsigned long rate;
> + u16 hi;
> + u16 lo;
> + bool enabled;
> + bool inverted;
> +};
> +
> +static int meson_pwm_round_waveform_tohw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const struct pwm_waveform *wf,
> + void *_wfhw)
> {
> + struct meson_pwm_waveform *wfhw = _wfhw;
> struct meson_pwm *meson = to_meson_pwm(chip);
> struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
> - unsigned int cnt, duty_cnt;
> + u64 cnt, duty_cnt, freq;
> long fin_freq;
> - u64 duty, period, freq;
> + int ret = 0;
>
> - duty = state->duty_cycle;
> - period = state->period;
> + if (wf->period_length_ns == 0) {
> + *wfhw = (struct meson_pwm_waveform){
> + .enabled = false,
> + };
> +
> + return 0;
> + }
>
> /*
> - * Note this is wrong. The result is an output wave that isn't really
> - * inverted and so is wrongly identified by .get_state as normal.
> - * Fixing this needs some care however as some machines might rely on
> - * this.
> + * Get the highest input clock rate that makes the requested period
> + * representable with the 16 bit wide counters, but at most 1 GHz so
> + * one counter cycle stays at least a nanosecond long, which
> + * .round_waveform_fromhw() relies on. clk_round_rate() may round up
> + * past the requested rate, so reject a too-high result below.
> */
> - if (state->polarity == PWM_POLARITY_INVERSED && !meson->data->has_polarity)
> - duty = period - duty;
> -
> - freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period);
> - if (freq > ULONG_MAX)
> - freq = ULONG_MAX;
> + freq = div64_u64((u64)NSEC_PER_SEC * FIELD_MAX(PWM_LOW_MASK),
> + wf->period_length_ns);
> + freq = min_t(u64, freq, NSEC_PER_SEC);
>
> fin_freq = clk_round_rate(channel->clk, freq);
> if (fin_freq <= 0) {
> @@ -188,180 +197,226 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
> return fin_freq ? fin_freq : -EINVAL;
> }
>
> - dev_dbg(pwmchip_parent(chip), "fin_freq: %ld Hz\n", fin_freq);
> -
> - cnt = mul_u64_u64_div_u64(fin_freq, period, NSEC_PER_SEC);
> - if (cnt > 0xffff) {
> - dev_err(pwmchip_parent(chip), "unable to get period cnt\n");
> + if (fin_freq > NSEC_PER_SEC) {
> + dev_err(pwmchip_parent(chip),
> + "source clock frequency %ld too high\n", fin_freq);
> return -EINVAL;
> }
>
> - dev_dbg(pwmchip_parent(chip), "period=%llu cnt=%u\n", period, cnt);
> -
> - if (duty == period) {
> - channel->hi = cnt;
> - channel->lo = 0;
> - channel->constant = true;
> - } else if (duty == 0) {
> - channel->hi = 0;
> - channel->lo = cnt;
> - channel->constant = true;
> - } else {
> - duty_cnt = mul_u64_u64_div_u64(fin_freq, duty, NSEC_PER_SEC);
> -
> - dev_dbg(pwmchip_parent(chip), "duty=%llu duty_cnt=%u\n", duty, duty_cnt);
> + /*
> + * If the requested period is too long to be counted even at the
> + * lowest possible input clock rate, implement the longest possible
> + * period instead.
> + */
> + cnt = mul_u64_u64_div_u64(fin_freq, wf->period_length_ns, NSEC_PER_SEC);
> + cnt = min_t(u64, cnt, FIELD_MAX(PWM_LOW_MASK));
> +
> + if (cnt == 0) {
> + /*
> + * The requested period is shorter than one cycle of the
> + * fastest available input clock, implement the minimal
> + * period with zero duty cycle (the general calculation below
> + * yields that as duty_length_ns and duty_offset_ns cannot
> + * exceed the requested period) and signal the rounding up
> + * in the return value.
> + */
> + cnt = 1;
> + ret = 1;
> + }
>
> - channel->hi = duty_cnt;
> - channel->lo = cnt - duty_cnt;
> - channel->constant = false;
> + duty_cnt = mul_u64_u64_div_u64(fin_freq, wf->duty_length_ns, NSEC_PER_SEC);
> + duty_cnt = min(duty_cnt, cnt);
> +
> + *wfhw = (struct meson_pwm_waveform){
> + .rate = fin_freq,
> + .hi = duty_cnt,
> + .lo = cnt - duty_cnt,
> + .enabled = true,
> + };
> +
> + if (meson->data->has_polarity && duty_cnt) {
> + u64 offset_cnt = mul_u64_u64_div_u64(fin_freq, wf->duty_offset_ns,
> + NSEC_PER_SEC);
> +
> + /*
> + * Waveforms with duty_offset_ns + duty_length_ns ==
> + * period_length_ns are implemented by inverting the output:
> + * The signal then is low for hi counts (the offset) followed
> + * by high for lo counts (the duty cycle). Other nonzero
> + * duty_offset_ns values are rounded down to 0.
> + */
> + if (offset_cnt && duty_cnt + offset_cnt >= cnt) {
> + swap(wfhw->hi, wfhw->lo);
> + wfhw->inverted = true;
> + }
> }
>
> - channel->rate = fin_freq;
> + dev_dbg(pwmchip_parent(chip),
> + "pwm#%u: %llu/%llu [+%llu] @%lu -> hi: %u, lo: %u, inverted: %d\n",
> + pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns,
> + wf->duty_offset_ns, wfhw->rate, wfhw->hi, wfhw->lo,
> + wfhw->inverted);
>
> - return 0;
> + return ret;
> }
>
> -static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt)
> {
> - struct meson_pwm *meson = to_meson_pwm(chip);
> - struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
> - struct meson_pwm_channel_data *channel_data;
> - unsigned long flags;
> - u32 value;
> - int err;
> -
> - channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
> -
> - err = clk_set_rate(channel->clk, channel->rate);
> - if (err)
> - dev_err(pwmchip_parent(chip), "setting clock rate failed\n");
> -
> - spin_lock_irqsave(&meson->lock, flags);
> + return DIV64_U64_ROUND_UP(NSEC_PER_SEC * (u64)cnt, fin_freq);
> +}
>
> - value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) |
> - FIELD_PREP(PWM_LOW_MASK, channel->lo);
> - writel(value, meson->base + channel_data->reg_offset);
> +static int meson_pwm_round_waveform_fromhw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const void *_wfhw,
> + struct pwm_waveform *wf)
> +{
> + const struct meson_pwm_waveform *wfhw = _wfhw;
>
> - value = readl(meson->base + REG_MISC_AB);
> - value |= channel_data->pwm_en_mask;
> + if (!wfhw->enabled || !wfhw->rate) {
> + *wf = (struct pwm_waveform){
> + .period_length_ns = 0,
> + };
>
> - if (meson->data->has_constant) {
> - value &= ~channel_data->const_en_mask;
> - if (channel->constant)
> - value |= channel_data->const_en_mask;
> + return 0;
> }
>
> - if (meson->data->has_polarity) {
> - value &= ~channel_data->inv_en_mask;
> - if (channel->inverted)
> - value |= channel_data->inv_en_mask;
> + /*
> + * wfhw->hi and wfhw->lo are u16, so their sum doesn't overflow and
> + * NSEC_PER_SEC * (hi + lo) fits into the u64 dividend in
> + * meson_pwm_cnt_to_ns().
> + */
> + wf->period_length_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->hi + wfhw->lo);
> +
> + if (wfhw->inverted && wfhw->lo == 0) {
> + /*
> + * The output is constant low. Report a normal constant-low
> + * waveform instead of duty_offset_ns == period_length_ns
> + * which wouldn't be a valid waveform description. This
> + * setting is only reachable when the hardware was programmed
> + * by the bootloader as .round_waveform_tohw() never produces
> + * it.
> + */
> + wf->duty_length_ns = 0;
> + wf->duty_offset_ns = 0;
> + } else if (wfhw->inverted) {
> + wf->duty_length_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->lo);
> + wf->duty_offset_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->hi);
> + } else {
> + wf->duty_length_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->hi);
> + wf->duty_offset_ns = 0;
> }
>
> - writel(value, meson->base + REG_MISC_AB);
> -
> - spin_unlock_irqrestore(&meson->lock, flags);
> + return 0;
> }
>
> -static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +static int meson_pwm_read_waveform(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + void *_wfhw)
> {
> + struct meson_pwm_waveform *wfhw = _wfhw;
> struct meson_pwm *meson = to_meson_pwm(chip);
> struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
> struct meson_pwm_channel_data *channel_data;
> - unsigned long flags;
> - u32 value;
> + u32 value, pwm_reg;
>
> channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
>
> - spin_lock_irqsave(&meson->lock, flags);
> -
> value = readl(meson->base + REG_MISC_AB);
> - value &= ~channel_data->pwm_en_mask;
> + if (!(value & channel_data->pwm_en_mask)) {
> + *wfhw = (struct meson_pwm_waveform){
> + .enabled = false,
> + };
>
> - if (meson->data->has_polarity) {
> - value &= ~channel_data->inv_en_mask;
> - if (channel->inverted)
> - value |= channel_data->inv_en_mask;
> + return 0;
> }
>
> - writel(value, meson->base + REG_MISC_AB);
> + pwm_reg = readl(meson->base + channel_data->reg_offset);
>
> - spin_unlock_irqrestore(&meson->lock, flags);
> + *wfhw = (struct meson_pwm_waveform){
> + .rate = clk_get_rate(channel->clk),
> + .hi = FIELD_GET(PWM_HIGH_MASK, pwm_reg),
> + .lo = FIELD_GET(PWM_LOW_MASK, pwm_reg),
> + .enabled = true,
> + .inverted = meson->data->has_polarity &&
> + (value & channel_data->inv_en_mask),
> + };
> +
> + return 0;
> }
>
> -static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> - const struct pwm_state *state)
> +static int meson_pwm_write_waveform(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const void *_wfhw)
> {
> + const struct meson_pwm_waveform *wfhw = _wfhw;
> struct meson_pwm *meson = to_meson_pwm(chip);
> struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
> - int err = 0;
> -
> - channel->inverted = (state->polarity == PWM_POLARITY_INVERSED);
> -
> - if (!state->enabled) {
> - if (channel->inverted && !meson->data->has_polarity) {
> - /*
> - * Some of IP block revisions don't have an "always high"
> - * setting which we can use for "inverted disabled".
> - * Instead we achieve this by setting mux parent with
> - * highest rate and minimum divider value, resulting
> - * in the shortest possible duration for one "count"
> - * and "period == duty_cycle". This results in a signal
> - * which is LOW for one "count", while being HIGH for
> - * the rest of the (so the signal is HIGH for slightly
> - * less than 100% of the period, but this is the best
> - * we can achieve).
> - */
> - channel->rate = ULONG_MAX;
> - channel->hi = ~0;
> - channel->lo = 0;
> - channel->constant = true;
> -
> - meson_pwm_enable(chip, pwm);
> - } else {
> - meson_pwm_disable(chip, pwm);
> - }
> - } else {
> - err = meson_pwm_calc(chip, pwm, state);
> - if (err < 0)
> + struct meson_pwm_channel_data *channel_data;
> + unsigned long flags;
> + u32 value;
> + int err;
> +
> + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
> +
> + /*
> + * Channel clock operations also modify REG_MISC_AB under meson->lock,
> + * including from .request()/.free() outside the pwmchip lock. Set the
> + * rate before taking meson->lock to avoid recursive locking. Skip
> + * clk_set_rate() when unchanged because even a no-op re-evaluates the
> + * mux parent and reparses the device tree.
> + */
> + if (wfhw->enabled && clk_get_rate(channel->clk) != wfhw->rate) {
> + err = clk_set_rate(channel->clk, wfhw->rate);
> + if (err) {
> + dev_err(pwmchip_parent(chip),
> + "setting clock rate failed: %d\n", err);
> return err;
> + }
>
> - meson_pwm_enable(chip, pwm);
> + /*
> + * The rate computed by .round_waveform_tohw() might not be
> + * hit if an input clock changed its rate in between; the
> + * counter values only implement the promised waveform at
> + * exactly wfhw->rate.
> + */
> + if (clk_get_rate(channel->clk) != wfhw->rate) {
> + dev_err(pwmchip_parent(chip),
> + "clock rate changed since rounding\n");
> + return -EINVAL;
> + }
> }
>
> - return 0;
> -}
> + spin_lock_irqsave(&meson->lock, flags);
>
> -static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt)
> -{
> - return fin_freq ? div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq) : 0;
> -}
> + if (wfhw->enabled) {
> + value = FIELD_PREP(PWM_HIGH_MASK, wfhw->hi) |
> + FIELD_PREP(PWM_LOW_MASK, wfhw->lo);
> + writel(value, meson->base + channel_data->reg_offset);
>
> -static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> - struct pwm_state *state)
> -{
> - struct meson_pwm *meson = to_meson_pwm(chip);
> - struct meson_pwm_channel_data *channel_data;
> - unsigned long fin_freq;
> - unsigned int hi, lo;
> - u32 value;
> -
> - channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
> - fin_freq = clk_get_rate(meson->channels[pwm->hwpwm].clk);
> + value = readl(meson->base + REG_MISC_AB);
> + value |= channel_data->pwm_en_mask;
>
> - value = readl(meson->base + REG_MISC_AB);
> - state->enabled = value & channel_data->pwm_en_mask;
> + if (meson->data->has_constant) {
> + value &= ~channel_data->const_en_mask;
> + /* The signal is constant when one phase is empty */
> + if (!wfhw->hi || !wfhw->lo)
> + value |= channel_data->const_en_mask;
> + }
>
> - if (meson->data->has_polarity && (value & channel_data->inv_en_mask))
> - state->polarity = PWM_POLARITY_INVERSED;
> - else
> - state->polarity = PWM_POLARITY_NORMAL;
> + if (meson->data->has_polarity) {
> + value &= ~channel_data->inv_en_mask;
> + if (wfhw->inverted)
> + value |= channel_data->inv_en_mask;
> + }
>
> - value = readl(meson->base + channel_data->reg_offset);
> - lo = FIELD_GET(PWM_LOW_MASK, value);
> - hi = FIELD_GET(PWM_HIGH_MASK, value);
> + writel(value, meson->base + REG_MISC_AB);
> + } else {
> + value = readl(meson->base + REG_MISC_AB);
> + value &= ~channel_data->pwm_en_mask;
> + writel(value, meson->base + REG_MISC_AB);
> + }
>
> - state->period = meson_pwm_cnt_to_ns(fin_freq, lo + hi);
> - state->duty_cycle = meson_pwm_cnt_to_ns(fin_freq, hi);
> + spin_unlock_irqrestore(&meson->lock, flags);
>
> return 0;
> }
> @@ -369,8 +424,11 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> static const struct pwm_ops meson_pwm_ops = {
> .request = meson_pwm_request,
> .free = meson_pwm_free,
> - .apply = meson_pwm_apply,
> - .get_state = meson_pwm_get_state,
> + .sizeof_wfhw = sizeof(struct meson_pwm_waveform),
> + .round_waveform_tohw = meson_pwm_round_waveform_tohw,
> + .round_waveform_fromhw = meson_pwm_round_waveform_fromhw,
> + .read_waveform = meson_pwm_read_waveform,
> + .write_waveform = meson_pwm_write_waveform,
> };
>
> static int meson_pwm_init_clocks_meson8b(struct pwm_chip *chip,
>
> ---
> base-commit: 2e0a43a9e6f2e846f370aa63ea274f5403628b79
> change-id: 20260710-pwm_meson_convert_to_waveform_api-8711b7f8c755
>
> Best regards,
> --
> Alexandre Mergnat <amergnat at baylibre.com>
>
More information about the linux-amlogic
mailing list