]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pwm: axi-pwmgen: Implementation of the waveform callbacks
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Fri, 20 Sep 2024 08:58:01 +0000 (10:58 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Sat, 28 Sep 2024 13:13:56 +0000 (15:13 +0200)
Convert the axi-pwmgen driver to use the new callbacks for hardware
programming.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Tested-by: Trevor Gamblin <tgamblin@baylibre.com>
Link: https://lore.kernel.org/r/922277f07b1d1fb9c9cd915b1ec3fdeec888a916.1726819463.git.u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-axi-pwmgen.c

index b5477659ba186cd87dcfd28ec395f76d87f36111..39d184417c7c0830f0eb5c1a865946a9b65a7d62 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/err.h>
 #include <linux/fpga/adi-axi-common.h>
 #include <linux/io.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
@@ -53,81 +54,142 @@ static const struct regmap_config axi_pwmgen_regmap_config = {
        .max_register = 0xFC,
 };
 
-static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm,
-                           const struct pwm_state *state)
+/* This represents a hardware configuration for one channel */
+struct axi_pwmgen_waveform {
+       u32 period_cnt;
+       u32 duty_cycle_cnt;
+       u32 duty_offset_cnt;
+};
+
+static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
+                                         struct pwm_device *pwm,
+                                         const struct pwm_waveform *wf,
+                                         void *_wfhw)
 {
+       struct axi_pwmgen_waveform *wfhw = _wfhw;
        struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
-       unsigned int ch = pwm->hwpwm;
-       struct regmap *regmap = ddata->regmap;
-       u64 period_cnt, duty_cnt;
-       int ret;
 
-       if (state->polarity != PWM_POLARITY_NORMAL)
-               return -EINVAL;
+       if (wf->period_length_ns == 0) {
+               *wfhw = (struct axi_pwmgen_waveform){
+                       .period_cnt = 0,
+                       .duty_cycle_cnt = 0,
+                       .duty_offset_cnt = 0,
+               };
+       } else {
+               /* With ddata->clk_rate_hz < NSEC_PER_SEC this won't overflow. */
+               wfhw->period_cnt = min_t(u64,
+                                        mul_u64_u32_div(wf->period_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+                                        U32_MAX);
+
+               if (wfhw->period_cnt == 0) {
+                       /*
+                        * The specified period is too short for the hardware.
+                        * Let's round .duty_cycle down to 0 to get a (somewhat)
+                        * valid result.
+                        */
+                       wfhw->period_cnt = 1;
+                       wfhw->duty_cycle_cnt = 0;
+                       wfhw->duty_offset_cnt = 0;
+               } else {
+                       wfhw->duty_cycle_cnt = min_t(u64,
+                                                    mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+                                                    U32_MAX);
+                       wfhw->duty_offset_cnt = min_t(u64,
+                                                     mul_u64_u32_div(wf->duty_offset_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+                                                     U32_MAX);
+               }
+       }
 
-       if (state->enabled) {
-               period_cnt = mul_u64_u64_div_u64(state->period, ddata->clk_rate_hz, NSEC_PER_SEC);
-               if (period_cnt > UINT_MAX)
-                       period_cnt = UINT_MAX;
+       dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> PERIOD: %08x, DUTY: %08x, OFFSET: %08x\n",
+               pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+               ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt);
 
-               if (period_cnt == 0)
-                       return -EINVAL;
+       return 0;
+}
 
-               ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt);
-               if (ret)
-                       return ret;
+static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+                                            const void *_wfhw, struct pwm_waveform *wf)
+{
+       const struct axi_pwmgen_waveform *wfhw = _wfhw;
+       struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
 
-               duty_cnt = mul_u64_u64_div_u64(state->duty_cycle, ddata->clk_rate_hz, NSEC_PER_SEC);
-               if (duty_cnt > UINT_MAX)
-                       duty_cnt = UINT_MAX;
+       wf->period_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC,
+                                       ddata->clk_rate_hz);
 
-               ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), duty_cnt);
-               if (ret)
-                       return ret;
-       } else {
-               ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0);
-               if (ret)
-                       return ret;
+       wf->duty_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_cycle_cnt * NSEC_PER_SEC,
+                                           ddata->clk_rate_hz);
 
-               ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0);
-               if (ret)
-                       return ret;
-       }
+       wf->duty_offset_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC,
+                                            ddata->clk_rate_hz);
 
-       return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
+       return 0;
 }
 
-static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
-                               struct pwm_state *state)
+static int axi_pwmgen_write_waveform(struct pwm_chip *chip,
+                                    struct pwm_device *pwm,
+                                    const void *_wfhw)
 {
+       const struct axi_pwmgen_waveform *wfhw = _wfhw;
        struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
        struct regmap *regmap = ddata->regmap;
        unsigned int ch = pwm->hwpwm;
-       u32 cnt;
        int ret;
 
-       ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &cnt);
+       ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt);
        if (ret)
                return ret;
 
-       state->enabled = cnt != 0;
+       ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt);
+       if (ret)
+               return ret;
 
-       state->period = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
+       ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt);
+       if (ret)
+               return ret;
 
-       ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &cnt);
+       return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
+}
+
+static int axi_pwmgen_read_waveform(struct pwm_chip *chip,
+                                   struct pwm_device *pwm,
+                                   void *_wfhw)
+{
+       struct axi_pwmgen_waveform *wfhw = _wfhw;
+       struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
+       struct regmap *regmap = ddata->regmap;
+       unsigned int ch = pwm->hwpwm;
+       int ret;
+
+       ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt);
        if (ret)
                return ret;
 
-       state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
+       ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt);
+       if (ret)
+               return ret;
 
-       state->polarity = PWM_POLARITY_NORMAL;
+       if (wfhw->duty_cycle_cnt > wfhw->period_cnt)
+               wfhw->duty_cycle_cnt = wfhw->period_cnt;
+
+       /* XXX: is this the actual behaviour of the hardware? */
+       if (wfhw->duty_offset_cnt >= wfhw->period_cnt) {
+               wfhw->duty_cycle_cnt = 0;
+               wfhw->duty_offset_cnt = 0;
+       }
 
        return 0;
 }
 
 static const struct pwm_ops axi_pwmgen_pwm_ops = {
-       .apply = axi_pwmgen_apply,
-       .get_state = axi_pwmgen_get_state,
+       .sizeof_wfhw = sizeof(struct axi_pwmgen_waveform),
+       .round_waveform_tohw = axi_pwmgen_round_waveform_tohw,
+       .round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw,
+       .read_waveform = axi_pwmgen_read_waveform,
+       .write_waveform = axi_pwmgen_write_waveform,
 };
 
 static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)