]> Gentwo Git Trees - linux/.git/commitdiff
pwm: mediatek: Convert to waveform API
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Mon, 13 Oct 2025 11:42:56 +0000 (13:42 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Fri, 14 Nov 2025 10:56:14 +0000 (11:56 +0100)
Implement the new waveform callbacks which makes the usage of this
hardware more flexible and allows to use it via the pwm character
device.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://patch.msgid.link/20251013114258.149260-2-u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-mediatek.c

index 4291072a13a7f6e50e1c46905925de17c9639dc4..f2c918c0d26ab48c6111b417baf382faed871251 100644 (file)
@@ -135,50 +135,51 @@ static inline u32 pwm_mediatek_readl(struct pwm_mediatek_chip *chip,
                     num * chip->soc->chanreg_width + offset);
 }
 
-static void pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
-       struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
-       u32 value;
-
-       value = readl(pc->regs);
-       value |= BIT(pwm->hwpwm);
-       writel(value, pc->regs);
-}
-
-static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
-       struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
-       u32 value;
-
-       value = readl(pc->regs);
-       value &= ~BIT(pwm->hwpwm);
-       writel(value, pc->regs);
-}
+struct pwm_mediatek_waveform {
+       u32 enable;
+       u32 con;
+       u32 width;
+       u32 thres;
+};
 
-static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
-                              u64 duty_ns, u64 period_ns)
+static int pwm_mediatek_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,
+                                           const struct pwm_waveform *wf, void *_wfhw)
 {
+       struct pwm_mediatek_waveform *wfhw = _wfhw;
        struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
        u32 clkdiv, enable;
-       u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
        u64 cnt_period, cnt_duty;
        unsigned long clk_rate;
-       int ret;
+       int ret = 0;
 
-       ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
-       if (ret < 0)
-               return ret;
+       if (wf->period_length_ns == 0) {
+               *wfhw = (typeof(*wfhw)){
+                       .enable = 0,
+               };
 
-       clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
+               return 0;
+       }
+
+       if (!pc->clk_pwms[pwm->hwpwm].rate) {
+               struct clk *clk = pc->clk_pwms[pwm->hwpwm].clk;
+
+               ret = clk_prepare_enable(clk);
+               if (ret)
+                       return ret;
 
-       /* Make sure we use the bus clock and not the 26MHz clock */
-       if (pc->soc->pwm_ck_26m_sel_reg)
-               writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg);
+               pc->clk_pwms[pwm->hwpwm].rate = clk_get_rate(clk);
 
-       cnt_period = mul_u64_u64_div_u64(period_ns, clk_rate, NSEC_PER_SEC);
+               clk_disable_unprepare(clk);
+       }
+
+       clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
+       if (clk_rate == 0 || clk_rate > 1000000000)
+               return -EINVAL;
+
+       cnt_period = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);
        if (cnt_period == 0) {
-               ret = -ERANGE;
-               goto out;
+               cnt_period = 1;
+               ret = 1;
        }
 
        if (cnt_period > FIELD_MAX(PWMDWIDTH_PERIOD) + 1) {
@@ -193,7 +194,7 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
                clkdiv = 0;
        }
 
-       cnt_duty = mul_u64_u64_div_u64(duty_ns, clk_rate, NSEC_PER_SEC) >> clkdiv;
+       cnt_duty = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC) >> clkdiv;
        if (cnt_duty > cnt_period)
                cnt_duty = cnt_period;
 
@@ -206,121 +207,187 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
 
        cnt_period -= 1;
 
-       dev_dbg(&chip->dev, "pwm#%u: %lld/%lld @%lu -> CON: %x, PERIOD: %llx, DUTY: %llx\n",
-               pwm->hwpwm, duty_ns, period_ns, clk_rate, clkdiv, cnt_period, cnt_duty);
+       dev_dbg(&chip->dev, "pwm#%u: %lld/%lld @%lu -> ENABLE: %x, CON: %x, PERIOD: %llx, DUTY: %llx\n",
+               pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, clk_rate,
+               enable, clkdiv, cnt_period, cnt_duty);
 
-       if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
-               /*
-                * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
-                * from the other PWMs on MT7623.
-                */
-               reg_width = PWM45DWIDTH_FIXUP;
-               reg_thres = PWM45THRES_FIXUP;
-       }
+       *wfhw = (typeof(*wfhw)){
+               .enable = enable,
+               .con = clkdiv,
+               .width = cnt_period,
+               .thres = cnt_duty,
+       };
 
-       pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
-       pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period);
+       return ret;
+}
 
-       if (enable) {
-               pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty);
-               pwm_mediatek_enable(chip, pwm);
+static int pwm_mediatek_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+                                             const void *_wfhw, struct pwm_waveform *wf)
+{
+       const struct pwm_mediatek_waveform *wfhw = _wfhw;
+       struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
+       u32 clkdiv, cnt_period, cnt_duty;
+       unsigned long clk_rate;
+
+       /*
+        * When _wfhw was populated, the clock was on, so .rate is
+        * already set appropriately.
+        */
+       clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
+
+       if (wfhw->enable) {
+               clkdiv = FIELD_GET(PWMCON_CLKDIV, wfhw->con);
+               cnt_period = FIELD_GET(PWMDWIDTH_PERIOD, wfhw->width);
+               cnt_duty = FIELD_GET(PWMTHRES_DUTY, wfhw->thres);
+
+               /*
+                * cnt_period is a 13 bit value, NSEC_PER_SEC is 30 bits wide
+                * and clkdiv is less than 8, so the multiplication doesn't
+                * overflow an u64.
+                */
+               *wf = (typeof(*wf)){
+                       .period_length_ns =
+                               DIV_ROUND_UP_ULL((u64)(cnt_period + 1) * NSEC_PER_SEC << clkdiv, clk_rate),
+                       .duty_length_ns =
+                               DIV_ROUND_UP_ULL((u64)(cnt_duty + 1) * NSEC_PER_SEC << clkdiv, clk_rate),
+               };
        } else {
-               pwm_mediatek_disable(chip, pwm);
-       }
+               clkdiv = 0;
+               cnt_period = 0;
+               cnt_duty = 0;
 
-out:
-       pwm_mediatek_clk_disable(pc, pwm->hwpwm);
+               /*
+                * .enable = 0 is also used for too small duty_cycle values, so
+                * report the HW as being enabled to communicate the minimal
+                * period.
+                */
+               *wf = (typeof(*wf)){
+                       .period_length_ns =
+                               DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate),
+                       .duty_length_ns = 0,
+               };
+       };
 
-       return ret;
+       dev_dbg(&chip->dev, "pwm#%u: ENABLE: %x, CLKDIV: %x, PERIOD: %x, DUTY: %x @%lu -> %lld/%lld\n",
+               pwm->hwpwm, wfhw->enable, clkdiv, cnt_period, cnt_duty, clk_rate,
+               wf->duty_length_ns, wf->period_length_ns);
+
+       return 0;
 }
 
-static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm,
-                             const struct pwm_state *state)
+static int pwm_mediatek_read_waveform(struct pwm_chip *chip,
+                                     struct pwm_device *pwm, void *_wfhw)
 {
+       struct pwm_mediatek_waveform *wfhw = _wfhw;
        struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
-       int err;
+       u32 enable, clkdiv, cnt_period, cnt_duty;
+       u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
+       int ret;
 
-       if (state->polarity != PWM_POLARITY_NORMAL)
-               return -EINVAL;
+       ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
+       if (ret < 0)
+               return ret;
 
-       if (!state->enabled) {
-               if (pwm->state.enabled) {
-                       pwm_mediatek_disable(chip, pwm);
-                       pwm_mediatek_clk_disable(pc, pwm->hwpwm);
+       enable = readl(pc->regs) & BIT(pwm->hwpwm);
+
+       if (enable) {
+               if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
+                       /*
+                        * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
+                        * from the other PWMs on MT7623.
+                        */
+                       reg_width = PWM45DWIDTH_FIXUP;
+                       reg_thres = PWM45THRES_FIXUP;
                }
 
-               return 0;
-       }
+               clkdiv = FIELD_GET(PWMCON_CLKDIV, pwm_mediatek_readl(pc, pwm->hwpwm, PWMCON));
+               cnt_period = FIELD_GET(PWMDWIDTH_PERIOD, pwm_mediatek_readl(pc, pwm->hwpwm, reg_width));
+               cnt_duty = FIELD_GET(PWMTHRES_DUTY, pwm_mediatek_readl(pc, pwm->hwpwm, reg_thres));
 
-       err = pwm_mediatek_config(chip, pwm, state->duty_cycle, state->period);
-       if (err)
-               return err;
+               *wfhw = (typeof(*wfhw)){
+                       .enable = enable,
+                       .con = BIT(15) | clkdiv,
+                       .width = cnt_period,
+                       .thres = cnt_duty,
+               };
+       } else {
+               *wfhw = (typeof(*wfhw)){
+                       .enable = 0,
+               };
+       }
 
-       if (!pwm->state.enabled)
-               err = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
+       pwm_mediatek_clk_disable(pc, pwm->hwpwm);
 
-       return err;
+       return ret;
 }
 
-static int pwm_mediatek_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
-                                 struct pwm_state *state)
+static int pwm_mediatek_write_waveform(struct pwm_chip *chip,
+                                      struct pwm_device *pwm, const void *_wfhw)
 {
+       const struct pwm_mediatek_waveform *wfhw = _wfhw;
        struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
+       u32 ctrl;
        int ret;
-       u32 enable;
-       u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
-
-       if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
-               /*
-                * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
-                * from the other PWMs on MT7623.
-                */
-               reg_width = PWM45DWIDTH_FIXUP;
-               reg_thres = PWM45THRES_FIXUP;
-       }
 
        ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
        if (ret < 0)
                return ret;
 
-       enable = readl(pc->regs);
-       if (enable & BIT(pwm->hwpwm)) {
-               u32 clkdiv, cnt_period, cnt_duty;
-               unsigned long clk_rate;
+       ctrl = readl(pc->regs);
 
-               clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
+       if (wfhw->enable) {
+               u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
 
-               state->enabled = true;
-               state->polarity = PWM_POLARITY_NORMAL;
+               if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
+                       /*
+                        * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
+                        * from the other PWMs on MT7623.
+                        */
+                       reg_width = PWM45DWIDTH_FIXUP;
+                       reg_thres = PWM45THRES_FIXUP;
+               }
 
-               clkdiv = FIELD_GET(PWMCON_CLKDIV,
-                                  pwm_mediatek_readl(pc, pwm->hwpwm, PWMCON));
-               cnt_period = FIELD_GET(PWMDWIDTH_PERIOD,
-                                      pwm_mediatek_readl(pc, pwm->hwpwm, reg_width));
-               cnt_duty = FIELD_GET(PWMTHRES_DUTY,
-                                    pwm_mediatek_readl(pc, pwm->hwpwm, reg_thres));
+               if (!(ctrl & BIT(pwm->hwpwm))) {
+                       /*
+                        * The clks are already on, just increasing the usage
+                        * counter doesn't fail.
+                        */
+                       ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
+                       if (unlikely(ret < 0))
+                               goto out;
+
+                       ctrl |= BIT(pwm->hwpwm);
+                       writel(ctrl, pc->regs);
+               }
 
-               /*
-                * cnt_period is a 13 bit value, NSEC_PER_SEC is 30 bits wide
-                * and clkdiv is less than 8, so the multiplication doesn't
-                * overflow an u64.
-                */
-               state->period =
-                       DIV_ROUND_UP_ULL((u64)cnt_period * NSEC_PER_SEC << clkdiv, clk_rate);
-               state->duty_cycle =
-                       DIV_ROUND_UP_ULL((u64)cnt_duty * NSEC_PER_SEC << clkdiv, clk_rate);
+               /* Make sure we use the bus clock and not the 26MHz clock */
+               if (pc->soc->pwm_ck_26m_sel_reg)
+                       writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg);
+
+               pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | wfhw->con);
+               pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, wfhw->width);
+               pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, wfhw->thres);
        } else {
-               state->enabled = false;
+               if (ctrl & BIT(pwm->hwpwm)) {
+                       ctrl &= ~BIT(pwm->hwpwm);
+                       writel(ctrl, pc->regs);
+
+                       pwm_mediatek_clk_disable(pc, pwm->hwpwm);
+               }
        }
 
+out:
        pwm_mediatek_clk_disable(pc, pwm->hwpwm);
 
        return ret;
 }
 
 static const struct pwm_ops pwm_mediatek_ops = {
-       .apply = pwm_mediatek_apply,
-       .get_state = pwm_mediatek_get_state,
+       .sizeof_wfhw = sizeof(struct pwm_mediatek_waveform),
+       .round_waveform_tohw = pwm_mediatek_round_waveform_tohw,
+       .round_waveform_fromhw = pwm_mediatek_round_waveform_fromhw,
+       .read_waveform = pwm_mediatek_read_waveform,
+       .write_waveform = pwm_mediatek_write_waveform,
 };
 
 static int pwm_mediatek_init_used_clks(struct pwm_mediatek_chip *pc)