Skip to content

Commit eb18504

Browse files
ukleinekUwe Kleine-König
authored andcommitted
pwm: axi-pwmgen: Implementation of the waveform callbacks
Convert the axi-pwmgen driver to use the new callbacks for hardware programming. Signed-off-by: Uwe Kleine-König <[email protected]> Tested-by: Trevor Gamblin <[email protected]> Link: https://lore.kernel.org/r/922277f07b1d1fb9c9cd915b1ec3fdeec888a916.1726819463.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König <[email protected]>
1 parent 1afd01d commit eb18504

File tree

1 file changed

+105
-43
lines changed

1 file changed

+105
-43
lines changed

drivers/pwm/pwm-axi-pwmgen.c

Lines changed: 105 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <linux/err.h>
2424
#include <linux/fpga/adi-axi-common.h>
2525
#include <linux/io.h>
26+
#include <linux/minmax.h>
2627
#include <linux/module.h>
2728
#include <linux/platform_device.h>
2829
#include <linux/pwm.h>
@@ -53,81 +54,142 @@ static const struct regmap_config axi_pwmgen_regmap_config = {
5354
.max_register = 0xFC,
5455
};
5556

56-
static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm,
57-
const struct pwm_state *state)
57+
/* This represents a hardware configuration for one channel */
58+
struct axi_pwmgen_waveform {
59+
u32 period_cnt;
60+
u32 duty_cycle_cnt;
61+
u32 duty_offset_cnt;
62+
};
63+
64+
static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
65+
struct pwm_device *pwm,
66+
const struct pwm_waveform *wf,
67+
void *_wfhw)
5868
{
69+
struct axi_pwmgen_waveform *wfhw = _wfhw;
5970
struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
60-
unsigned int ch = pwm->hwpwm;
61-
struct regmap *regmap = ddata->regmap;
62-
u64 period_cnt, duty_cnt;
63-
int ret;
6471

65-
if (state->polarity != PWM_POLARITY_NORMAL)
66-
return -EINVAL;
72+
if (wf->period_length_ns == 0) {
73+
*wfhw = (struct axi_pwmgen_waveform){
74+
.period_cnt = 0,
75+
.duty_cycle_cnt = 0,
76+
.duty_offset_cnt = 0,
77+
};
78+
} else {
79+
/* With ddata->clk_rate_hz < NSEC_PER_SEC this won't overflow. */
80+
wfhw->period_cnt = min_t(u64,
81+
mul_u64_u32_div(wf->period_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
82+
U32_MAX);
83+
84+
if (wfhw->period_cnt == 0) {
85+
/*
86+
* The specified period is too short for the hardware.
87+
* Let's round .duty_cycle down to 0 to get a (somewhat)
88+
* valid result.
89+
*/
90+
wfhw->period_cnt = 1;
91+
wfhw->duty_cycle_cnt = 0;
92+
wfhw->duty_offset_cnt = 0;
93+
} else {
94+
wfhw->duty_cycle_cnt = min_t(u64,
95+
mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
96+
U32_MAX);
97+
wfhw->duty_offset_cnt = min_t(u64,
98+
mul_u64_u32_div(wf->duty_offset_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
99+
U32_MAX);
100+
}
101+
}
67102

68-
if (state->enabled) {
69-
period_cnt = mul_u64_u64_div_u64(state->period, ddata->clk_rate_hz, NSEC_PER_SEC);
70-
if (period_cnt > UINT_MAX)
71-
period_cnt = UINT_MAX;
103+
dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> PERIOD: %08x, DUTY: %08x, OFFSET: %08x\n",
104+
pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
105+
ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt);
72106

73-
if (period_cnt == 0)
74-
return -EINVAL;
107+
return 0;
108+
}
75109

76-
ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt);
77-
if (ret)
78-
return ret;
110+
static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
111+
const void *_wfhw, struct pwm_waveform *wf)
112+
{
113+
const struct axi_pwmgen_waveform *wfhw = _wfhw;
114+
struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
79115

80-
duty_cnt = mul_u64_u64_div_u64(state->duty_cycle, ddata->clk_rate_hz, NSEC_PER_SEC);
81-
if (duty_cnt > UINT_MAX)
82-
duty_cnt = UINT_MAX;
116+
wf->period_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC,
117+
ddata->clk_rate_hz);
83118

84-
ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), duty_cnt);
85-
if (ret)
86-
return ret;
87-
} else {
88-
ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0);
89-
if (ret)
90-
return ret;
119+
wf->duty_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_cycle_cnt * NSEC_PER_SEC,
120+
ddata->clk_rate_hz);
91121

92-
ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0);
93-
if (ret)
94-
return ret;
95-
}
122+
wf->duty_offset_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC,
123+
ddata->clk_rate_hz);
96124

97-
return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
125+
return 0;
98126
}
99127

100-
static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
101-
struct pwm_state *state)
128+
static int axi_pwmgen_write_waveform(struct pwm_chip *chip,
129+
struct pwm_device *pwm,
130+
const void *_wfhw)
102131
{
132+
const struct axi_pwmgen_waveform *wfhw = _wfhw;
103133
struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
104134
struct regmap *regmap = ddata->regmap;
105135
unsigned int ch = pwm->hwpwm;
106-
u32 cnt;
107136
int ret;
108137

109-
ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &cnt);
138+
ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt);
110139
if (ret)
111140
return ret;
112141

113-
state->enabled = cnt != 0;
142+
ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt);
143+
if (ret)
144+
return ret;
114145

115-
state->period = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
146+
ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt);
147+
if (ret)
148+
return ret;
116149

117-
ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &cnt);
150+
return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
151+
}
152+
153+
static int axi_pwmgen_read_waveform(struct pwm_chip *chip,
154+
struct pwm_device *pwm,
155+
void *_wfhw)
156+
{
157+
struct axi_pwmgen_waveform *wfhw = _wfhw;
158+
struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
159+
struct regmap *regmap = ddata->regmap;
160+
unsigned int ch = pwm->hwpwm;
161+
int ret;
162+
163+
ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt);
164+
if (ret)
165+
return ret;
166+
167+
ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt);
118168
if (ret)
119169
return ret;
120170

121-
state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
171+
ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt);
172+
if (ret)
173+
return ret;
122174

123-
state->polarity = PWM_POLARITY_NORMAL;
175+
if (wfhw->duty_cycle_cnt > wfhw->period_cnt)
176+
wfhw->duty_cycle_cnt = wfhw->period_cnt;
177+
178+
/* XXX: is this the actual behaviour of the hardware? */
179+
if (wfhw->duty_offset_cnt >= wfhw->period_cnt) {
180+
wfhw->duty_cycle_cnt = 0;
181+
wfhw->duty_offset_cnt = 0;
182+
}
124183

125184
return 0;
126185
}
127186

128187
static const struct pwm_ops axi_pwmgen_pwm_ops = {
129-
.apply = axi_pwmgen_apply,
130-
.get_state = axi_pwmgen_get_state,
188+
.sizeof_wfhw = sizeof(struct axi_pwmgen_waveform),
189+
.round_waveform_tohw = axi_pwmgen_round_waveform_tohw,
190+
.round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw,
191+
.read_waveform = axi_pwmgen_read_waveform,
192+
.write_waveform = axi_pwmgen_write_waveform,
131193
};
132194

133195
static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)

0 commit comments

Comments
 (0)