Skip to content

Commit 24ec563

Browse files
ukleinekUwe Kleine-König
authored andcommitted
pwm: mediatek: Convert to waveform API
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>
1 parent 0251fa8 commit 24ec563

1 file changed

Lines changed: 176 additions & 109 deletions

File tree

drivers/pwm/pwm-mediatek.c

Lines changed: 176 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -135,50 +135,51 @@ static inline u32 pwm_mediatek_readl(struct pwm_mediatek_chip *chip,
135135
num * chip->soc->chanreg_width + offset);
136136
}
137137

138-
static void pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm)
139-
{
140-
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
141-
u32 value;
142-
143-
value = readl(pc->regs);
144-
value |= BIT(pwm->hwpwm);
145-
writel(value, pc->regs);
146-
}
147-
148-
static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm)
149-
{
150-
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
151-
u32 value;
152-
153-
value = readl(pc->regs);
154-
value &= ~BIT(pwm->hwpwm);
155-
writel(value, pc->regs);
156-
}
138+
struct pwm_mediatek_waveform {
139+
u32 enable;
140+
u32 con;
141+
u32 width;
142+
u32 thres;
143+
};
157144

158-
static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
159-
u64 duty_ns, u64 period_ns)
145+
static int pwm_mediatek_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,
146+
const struct pwm_waveform *wf, void *_wfhw)
160147
{
148+
struct pwm_mediatek_waveform *wfhw = _wfhw;
161149
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
162150
u32 clkdiv, enable;
163-
u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
164151
u64 cnt_period, cnt_duty;
165152
unsigned long clk_rate;
166-
int ret;
153+
int ret = 0;
167154

168-
ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
169-
if (ret < 0)
170-
return ret;
155+
if (wf->period_length_ns == 0) {
156+
*wfhw = (typeof(*wfhw)){
157+
.enable = 0,
158+
};
171159

172-
clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
160+
return 0;
161+
}
162+
163+
if (!pc->clk_pwms[pwm->hwpwm].rate) {
164+
struct clk *clk = pc->clk_pwms[pwm->hwpwm].clk;
165+
166+
ret = clk_prepare_enable(clk);
167+
if (ret)
168+
return ret;
173169

174-
/* Make sure we use the bus clock and not the 26MHz clock */
175-
if (pc->soc->pwm_ck_26m_sel_reg)
176-
writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg);
170+
pc->clk_pwms[pwm->hwpwm].rate = clk_get_rate(clk);
177171

178-
cnt_period = mul_u64_u64_div_u64(period_ns, clk_rate, NSEC_PER_SEC);
172+
clk_disable_unprepare(clk);
173+
}
174+
175+
clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
176+
if (clk_rate == 0 || clk_rate > 1000000000)
177+
return -EINVAL;
178+
179+
cnt_period = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);
179180
if (cnt_period == 0) {
180-
ret = -ERANGE;
181-
goto out;
181+
cnt_period = 1;
182+
ret = 1;
182183
}
183184

184185
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,
193194
clkdiv = 0;
194195
}
195196

196-
cnt_duty = mul_u64_u64_div_u64(duty_ns, clk_rate, NSEC_PER_SEC) >> clkdiv;
197+
cnt_duty = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC) >> clkdiv;
197198
if (cnt_duty > cnt_period)
198199
cnt_duty = cnt_period;
199200

@@ -206,121 +207,187 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
206207

207208
cnt_period -= 1;
208209

209-
dev_dbg(&chip->dev, "pwm#%u: %lld/%lld @%lu -> CON: %x, PERIOD: %llx, DUTY: %llx\n",
210-
pwm->hwpwm, duty_ns, period_ns, clk_rate, clkdiv, cnt_period, cnt_duty);
210+
dev_dbg(&chip->dev, "pwm#%u: %lld/%lld @%lu -> ENABLE: %x, CON: %x, PERIOD: %llx, DUTY: %llx\n",
211+
pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, clk_rate,
212+
enable, clkdiv, cnt_period, cnt_duty);
211213

212-
if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
213-
/*
214-
* PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
215-
* from the other PWMs on MT7623.
216-
*/
217-
reg_width = PWM45DWIDTH_FIXUP;
218-
reg_thres = PWM45THRES_FIXUP;
219-
}
214+
*wfhw = (typeof(*wfhw)){
215+
.enable = enable,
216+
.con = clkdiv,
217+
.width = cnt_period,
218+
.thres = cnt_duty,
219+
};
220220

221-
pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
222-
pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period);
221+
return ret;
222+
}
223223

224-
if (enable) {
225-
pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty);
226-
pwm_mediatek_enable(chip, pwm);
224+
static int pwm_mediatek_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
225+
const void *_wfhw, struct pwm_waveform *wf)
226+
{
227+
const struct pwm_mediatek_waveform *wfhw = _wfhw;
228+
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
229+
u32 clkdiv, cnt_period, cnt_duty;
230+
unsigned long clk_rate;
231+
232+
/*
233+
* When _wfhw was populated, the clock was on, so .rate is
234+
* already set appropriately.
235+
*/
236+
clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
237+
238+
if (wfhw->enable) {
239+
clkdiv = FIELD_GET(PWMCON_CLKDIV, wfhw->con);
240+
cnt_period = FIELD_GET(PWMDWIDTH_PERIOD, wfhw->width);
241+
cnt_duty = FIELD_GET(PWMTHRES_DUTY, wfhw->thres);
242+
243+
/*
244+
* cnt_period is a 13 bit value, NSEC_PER_SEC is 30 bits wide
245+
* and clkdiv is less than 8, so the multiplication doesn't
246+
* overflow an u64.
247+
*/
248+
*wf = (typeof(*wf)){
249+
.period_length_ns =
250+
DIV_ROUND_UP_ULL((u64)(cnt_period + 1) * NSEC_PER_SEC << clkdiv, clk_rate),
251+
.duty_length_ns =
252+
DIV_ROUND_UP_ULL((u64)(cnt_duty + 1) * NSEC_PER_SEC << clkdiv, clk_rate),
253+
};
227254
} else {
228-
pwm_mediatek_disable(chip, pwm);
229-
}
255+
clkdiv = 0;
256+
cnt_period = 0;
257+
cnt_duty = 0;
230258

231-
out:
232-
pwm_mediatek_clk_disable(pc, pwm->hwpwm);
259+
/*
260+
* .enable = 0 is also used for too small duty_cycle values, so
261+
* report the HW as being enabled to communicate the minimal
262+
* period.
263+
*/
264+
*wf = (typeof(*wf)){
265+
.period_length_ns =
266+
DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate),
267+
.duty_length_ns = 0,
268+
};
269+
};
233270

234-
return ret;
271+
dev_dbg(&chip->dev, "pwm#%u: ENABLE: %x, CLKDIV: %x, PERIOD: %x, DUTY: %x @%lu -> %lld/%lld\n",
272+
pwm->hwpwm, wfhw->enable, clkdiv, cnt_period, cnt_duty, clk_rate,
273+
wf->duty_length_ns, wf->period_length_ns);
274+
275+
return 0;
235276
}
236277

237-
static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm,
238-
const struct pwm_state *state)
278+
static int pwm_mediatek_read_waveform(struct pwm_chip *chip,
279+
struct pwm_device *pwm, void *_wfhw)
239280
{
281+
struct pwm_mediatek_waveform *wfhw = _wfhw;
240282
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
241-
int err;
283+
u32 enable, clkdiv, cnt_period, cnt_duty;
284+
u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
285+
int ret;
242286

243-
if (state->polarity != PWM_POLARITY_NORMAL)
244-
return -EINVAL;
287+
ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
288+
if (ret < 0)
289+
return ret;
245290

246-
if (!state->enabled) {
247-
if (pwm->state.enabled) {
248-
pwm_mediatek_disable(chip, pwm);
249-
pwm_mediatek_clk_disable(pc, pwm->hwpwm);
291+
enable = readl(pc->regs) & BIT(pwm->hwpwm);
292+
293+
if (enable) {
294+
if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
295+
/*
296+
* PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
297+
* from the other PWMs on MT7623.
298+
*/
299+
reg_width = PWM45DWIDTH_FIXUP;
300+
reg_thres = PWM45THRES_FIXUP;
250301
}
251302

252-
return 0;
253-
}
303+
clkdiv = FIELD_GET(PWMCON_CLKDIV, pwm_mediatek_readl(pc, pwm->hwpwm, PWMCON));
304+
cnt_period = FIELD_GET(PWMDWIDTH_PERIOD, pwm_mediatek_readl(pc, pwm->hwpwm, reg_width));
305+
cnt_duty = FIELD_GET(PWMTHRES_DUTY, pwm_mediatek_readl(pc, pwm->hwpwm, reg_thres));
254306

255-
err = pwm_mediatek_config(chip, pwm, state->duty_cycle, state->period);
256-
if (err)
257-
return err;
307+
*wfhw = (typeof(*wfhw)){
308+
.enable = enable,
309+
.con = BIT(15) | clkdiv,
310+
.width = cnt_period,
311+
.thres = cnt_duty,
312+
};
313+
} else {
314+
*wfhw = (typeof(*wfhw)){
315+
.enable = 0,
316+
};
317+
}
258318

259-
if (!pwm->state.enabled)
260-
err = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
319+
pwm_mediatek_clk_disable(pc, pwm->hwpwm);
261320

262-
return err;
321+
return ret;
263322
}
264323

265-
static int pwm_mediatek_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
266-
struct pwm_state *state)
324+
static int pwm_mediatek_write_waveform(struct pwm_chip *chip,
325+
struct pwm_device *pwm, const void *_wfhw)
267326
{
327+
const struct pwm_mediatek_waveform *wfhw = _wfhw;
268328
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
329+
u32 ctrl;
269330
int ret;
270-
u32 enable;
271-
u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
272-
273-
if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
274-
/*
275-
* PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
276-
* from the other PWMs on MT7623.
277-
*/
278-
reg_width = PWM45DWIDTH_FIXUP;
279-
reg_thres = PWM45THRES_FIXUP;
280-
}
281331

282332
ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
283333
if (ret < 0)
284334
return ret;
285335

286-
enable = readl(pc->regs);
287-
if (enable & BIT(pwm->hwpwm)) {
288-
u32 clkdiv, cnt_period, cnt_duty;
289-
unsigned long clk_rate;
336+
ctrl = readl(pc->regs);
290337

291-
clk_rate = pc->clk_pwms[pwm->hwpwm].rate;
338+
if (wfhw->enable) {
339+
u32 reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
292340

293-
state->enabled = true;
294-
state->polarity = PWM_POLARITY_NORMAL;
341+
if (pc->soc->pwm45_fixup && pwm->hwpwm > 2) {
342+
/*
343+
* PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
344+
* from the other PWMs on MT7623.
345+
*/
346+
reg_width = PWM45DWIDTH_FIXUP;
347+
reg_thres = PWM45THRES_FIXUP;
348+
}
295349

296-
clkdiv = FIELD_GET(PWMCON_CLKDIV,
297-
pwm_mediatek_readl(pc, pwm->hwpwm, PWMCON));
298-
cnt_period = FIELD_GET(PWMDWIDTH_PERIOD,
299-
pwm_mediatek_readl(pc, pwm->hwpwm, reg_width));
300-
cnt_duty = FIELD_GET(PWMTHRES_DUTY,
301-
pwm_mediatek_readl(pc, pwm->hwpwm, reg_thres));
350+
if (!(ctrl & BIT(pwm->hwpwm))) {
351+
/*
352+
* The clks are already on, just increasing the usage
353+
* counter doesn't fail.
354+
*/
355+
ret = pwm_mediatek_clk_enable(pc, pwm->hwpwm);
356+
if (unlikely(ret < 0))
357+
goto out;
358+
359+
ctrl |= BIT(pwm->hwpwm);
360+
writel(ctrl, pc->regs);
361+
}
302362

303-
/*
304-
* cnt_period is a 13 bit value, NSEC_PER_SEC is 30 bits wide
305-
* and clkdiv is less than 8, so the multiplication doesn't
306-
* overflow an u64.
307-
*/
308-
state->period =
309-
DIV_ROUND_UP_ULL((u64)cnt_period * NSEC_PER_SEC << clkdiv, clk_rate);
310-
state->duty_cycle =
311-
DIV_ROUND_UP_ULL((u64)cnt_duty * NSEC_PER_SEC << clkdiv, clk_rate);
363+
/* Make sure we use the bus clock and not the 26MHz clock */
364+
if (pc->soc->pwm_ck_26m_sel_reg)
365+
writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg);
366+
367+
pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | wfhw->con);
368+
pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, wfhw->width);
369+
pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, wfhw->thres);
312370
} else {
313-
state->enabled = false;
371+
if (ctrl & BIT(pwm->hwpwm)) {
372+
ctrl &= ~BIT(pwm->hwpwm);
373+
writel(ctrl, pc->regs);
374+
375+
pwm_mediatek_clk_disable(pc, pwm->hwpwm);
376+
}
314377
}
315378

379+
out:
316380
pwm_mediatek_clk_disable(pc, pwm->hwpwm);
317381

318382
return ret;
319383
}
320384

321385
static const struct pwm_ops pwm_mediatek_ops = {
322-
.apply = pwm_mediatek_apply,
323-
.get_state = pwm_mediatek_get_state,
386+
.sizeof_wfhw = sizeof(struct pwm_mediatek_waveform),
387+
.round_waveform_tohw = pwm_mediatek_round_waveform_tohw,
388+
.round_waveform_fromhw = pwm_mediatek_round_waveform_fromhw,
389+
.read_waveform = pwm_mediatek_read_waveform,
390+
.write_waveform = pwm_mediatek_write_waveform,
324391
};
325392

326393
static int pwm_mediatek_init_used_clks(struct pwm_mediatek_chip *pc)

0 commit comments

Comments
 (0)