Skip to content

Commit ecd5d67

Browse files
committed
Merge tag 'pwm/for-6.15-rc2-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux
Pull pwm fixes from Uwe Kleine-König: "A set of fixes for pwm core and various drivers The first three patches handle clk_get_rate() returning 0 (which might happen for example if the CCF is disabled). The first of these was found because this triggered a warning with clang, the two others by looking for similar issues in other drivers. The remaining three fixes address issues in the new waveform pwm API. Now that I worked on this a bit more, the finer details and corner cases are better understood and the code is fixed accordingly" * tag 'pwm/for-6.15-rc2-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux: pwm: axi-pwmgen: Let .round_waveform_tohw() signal when request was rounded up pwm: stm32: Search an appropriate duty_cycle if period cannot be modified pwm: Let pwm_set_waveform() succeed even if lowlevel driver rounded up pwm: fsl-ftm: Handle clk_get_rate() returning 0 pwm: rcar: Improve register calculation pwm: mediatek: Prevent divide-by-zero in pwm_mediatek_config()
2 parents 3bde70a + a85e08a commit ecd5d67

File tree

6 files changed

+42
-31
lines changed

6 files changed

+42
-31
lines changed

drivers/pwm/core.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
322322
const struct pwm_ops *ops = chip->ops;
323323
char wfhw[WFHWSIZE];
324324
struct pwm_waveform wf_rounded;
325-
int err;
325+
int err, ret_tohw;
326326

327327
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
328328

@@ -332,16 +332,16 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
332332
if (!pwm_wf_valid(wf))
333333
return -EINVAL;
334334

335-
err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
336-
if (err)
337-
return err;
335+
ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
336+
if (ret_tohw < 0)
337+
return ret_tohw;
338338

339339
if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {
340340
err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
341341
if (err)
342342
return err;
343343

344-
if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded))
344+
if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded))
345345
dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
346346
wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
347347
wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
@@ -382,7 +382,8 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
382382
wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
383383
wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
384384
}
385-
return 0;
385+
386+
return ret_tohw;
386387
}
387388

388389
/**

drivers/pwm/pwm-axi-pwmgen.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
7575
{
7676
struct axi_pwmgen_waveform *wfhw = _wfhw;
7777
struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
78+
int ret = 0;
7879

7980
if (wf->period_length_ns == 0) {
8081
*wfhw = (struct axi_pwmgen_waveform){
@@ -91,12 +92,15 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
9192
if (wfhw->period_cnt == 0) {
9293
/*
9394
* The specified period is too short for the hardware.
94-
* Let's round .duty_cycle down to 0 to get a (somewhat)
95-
* valid result.
95+
* So round up .period_cnt to 1 (i.e. the smallest
96+
* possible period). With .duty_cycle and .duty_offset
97+
* being less than or equal to .period, their rounded
98+
* value must be 0.
9699
*/
97100
wfhw->period_cnt = 1;
98101
wfhw->duty_cycle_cnt = 0;
99102
wfhw->duty_offset_cnt = 0;
103+
ret = 1;
100104
} else {
101105
wfhw->duty_cycle_cnt = min_t(u64,
102106
mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
@@ -111,7 +115,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
111115
pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
112116
ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt);
113117

114-
return 0;
118+
return ret;
115119
}
116120

117121
static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,

drivers/pwm/pwm-fsl-ftm.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc,
118118
unsigned long long exval;
119119

120120
rate = clk_get_rate(fpc->clk[fpc->period.clk_select]);
121+
if (rate >> fpc->period.clk_ps == 0)
122+
return 0;
123+
121124
exval = ticks;
122125
exval *= 1000000000UL;
123126
do_div(exval, rate >> fpc->period.clk_ps);
@@ -190,6 +193,9 @@ static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc,
190193
unsigned int period = fpc->period.mod_period + 1;
191194
unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period);
192195

196+
if (!period_ns)
197+
return 0;
198+
193199
duty = (unsigned long long)duty_ns * period;
194200
do_div(duty, period_ns);
195201

drivers/pwm/pwm-mediatek.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,25 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
121121
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
122122
u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH,
123123
reg_thres = PWMTHRES;
124+
unsigned long clk_rate;
124125
u64 resolution;
125126
int ret;
126127

127128
ret = pwm_mediatek_clk_enable(chip, pwm);
128-
129129
if (ret < 0)
130130
return ret;
131131

132+
clk_rate = clk_get_rate(pc->clk_pwms[pwm->hwpwm]);
133+
if (!clk_rate)
134+
return -EINVAL;
135+
132136
/* Make sure we use the bus clock and not the 26MHz clock */
133137
if (pc->soc->has_ck_26m_sel)
134138
writel(0, pc->regs + PWM_CK_26M_SEL);
135139

136140
/* Using resolution in picosecond gets accuracy higher */
137141
resolution = (u64)NSEC_PER_SEC * 1000;
138-
do_div(resolution, clk_get_rate(pc->clk_pwms[pwm->hwpwm]));
142+
do_div(resolution, clk_rate);
139143

140144
cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
141145
while (cnt_period > 8191) {

drivers/pwm/pwm-rcar.c

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* - The hardware cannot generate a 0% duty cycle.
99
*/
1010

11+
#include <linux/bitfield.h>
1112
#include <linux/clk.h>
1213
#include <linux/err.h>
1314
#include <linux/io.h>
@@ -102,23 +103,24 @@ static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp,
102103
rcar_pwm_write(rp, value, RCAR_PWMCR);
103104
}
104105

105-
static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
106-
int period_ns)
106+
static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, u64 duty_ns,
107+
u64 period_ns)
107108
{
108-
unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */
109+
unsigned long long tmp;
109110
unsigned long clk_rate = clk_get_rate(rp->clk);
110111
u32 cyc, ph;
111112

112-
one_cycle = NSEC_PER_SEC * 100ULL << div;
113-
do_div(one_cycle, clk_rate);
113+
/* div <= 24 == RCAR_PWM_MAX_DIVISION, so the shift doesn't overflow. */
114+
tmp = mul_u64_u64_div_u64(period_ns, clk_rate, (u64)NSEC_PER_SEC << div);
115+
if (tmp > FIELD_MAX(RCAR_PWMCNT_CYC0_MASK))
116+
tmp = FIELD_MAX(RCAR_PWMCNT_CYC0_MASK);
114117

115-
tmp = period_ns * 100ULL;
116-
do_div(tmp, one_cycle);
117-
cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK;
118+
cyc = FIELD_PREP(RCAR_PWMCNT_CYC0_MASK, tmp);
118119

119-
tmp = duty_ns * 100ULL;
120-
do_div(tmp, one_cycle);
121-
ph = tmp & RCAR_PWMCNT_PH0_MASK;
120+
tmp = mul_u64_u64_div_u64(duty_ns, clk_rate, (u64)NSEC_PER_SEC << div);
121+
if (tmp > FIELD_MAX(RCAR_PWMCNT_PH0_MASK))
122+
tmp = FIELD_MAX(RCAR_PWMCNT_PH0_MASK);
123+
ph = FIELD_PREP(RCAR_PWMCNT_PH0_MASK, tmp);
122124

123125
/* Avoid prohibited setting */
124126
if (cyc == 0 || ph == 0)

drivers/pwm/pwm-stm32.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,16 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip,
103103
if (ret)
104104
goto out;
105105

106-
/*
107-
* calculate the best value for ARR for the given PSC, refuse if
108-
* the resulting period gets bigger than the requested one.
109-
*/
110106
arr = mul_u64_u64_div_u64(wf->period_length_ns, rate,
111107
(u64)NSEC_PER_SEC * (wfhw->psc + 1));
112108
if (arr <= wfhw->arr) {
113109
/*
114-
* requested period is small than the currently
110+
* requested period is smaller than the currently
115111
* configured and unchangable period, report back the smallest
116-
* possible period, i.e. the current state; Initialize
117-
* ccr to anything valid.
112+
* possible period, i.e. the current state and return 1
113+
* to indicate the wrong rounding direction.
118114
*/
119-
wfhw->ccr = 0;
120115
ret = 1;
121-
goto out;
122116
}
123117

124118
} else {

0 commit comments

Comments
 (0)