Commit ecd5d67a authored by Linus Torvalds's avatar Linus Torvalds
Browse files

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()
parents 3bde70a2 a85e08a0
Loading
Loading
Loading
Loading
+7 −6
Original line number Diff line number Diff line
@@ -322,7 +322,7 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
	const struct pwm_ops *ops = chip->ops;
	char wfhw[WFHWSIZE];
	struct pwm_waveform wf_rounded;
	int err;
	int err, ret_tohw;

	BUG_ON(WFHWSIZE < ops->sizeof_wfhw);

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

	err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
	if (err)
		return err;
	ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
	if (ret_tohw < 0)
		return ret_tohw;

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

		if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded))
		if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded))
			dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
				wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
				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,
				wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
				wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
	}
	return 0;

	return ret_tohw;
}

/**
+7 −3
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
{
	struct axi_pwmgen_waveform *wfhw = _wfhw;
	struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
	int ret = 0;

	if (wf->period_length_ns == 0) {
		*wfhw = (struct axi_pwmgen_waveform){
@@ -91,12 +92,15 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
		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.
			 * So round up .period_cnt to 1 (i.e. the smallest
			 * possible period). With .duty_cycle and .duty_offset
			 * being less than or equal to .period, their rounded
			 * value must be 0.
			 */
			wfhw->period_cnt = 1;
			wfhw->duty_cycle_cnt = 0;
			wfhw->duty_offset_cnt = 0;
			ret = 1;
		} else {
			wfhw->duty_cycle_cnt = min_t(u64,
						     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,
		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);

	return 0;
	return ret;
}

static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+6 −0
Original line number Diff line number Diff line
@@ -118,6 +118,9 @@ static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc,
	unsigned long long exval;

	rate = clk_get_rate(fpc->clk[fpc->period.clk_select]);
	if (rate >> fpc->period.clk_ps == 0)
		return 0;

	exval = ticks;
	exval *= 1000000000UL;
	do_div(exval, rate >> fpc->period.clk_ps);
@@ -190,6 +193,9 @@ static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc,
	unsigned int period = fpc->period.mod_period + 1;
	unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period);

	if (!period_ns)
		return 0;

	duty = (unsigned long long)duty_ns * period;
	do_div(duty, period_ns);

+6 −2
Original line number Diff line number Diff line
@@ -121,21 +121,25 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
	struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
	u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH,
	    reg_thres = PWMTHRES;
	unsigned long clk_rate;
	u64 resolution;
	int ret;

	ret = pwm_mediatek_clk_enable(chip, pwm);

	if (ret < 0)
		return ret;

	clk_rate = clk_get_rate(pc->clk_pwms[pwm->hwpwm]);
	if (!clk_rate)
		return -EINVAL;

	/* Make sure we use the bus clock and not the 26MHz clock */
	if (pc->soc->has_ck_26m_sel)
		writel(0, pc->regs + PWM_CK_26M_SEL);

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

	cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
	while (cnt_period > 8191) {
+13 −11
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
 * - The hardware cannot generate a 0% duty cycle.
 */

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

static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
				int period_ns)
static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, u64 duty_ns,
				u64 period_ns)
{
	unsigned long long one_cycle, tmp;	/* 0.01 nanoseconds */
	unsigned long long tmp;
	unsigned long clk_rate = clk_get_rate(rp->clk);
	u32 cyc, ph;

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

	tmp = period_ns * 100ULL;
	do_div(tmp, one_cycle);
	cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK;
	cyc = FIELD_PREP(RCAR_PWMCNT_CYC0_MASK, tmp);

	tmp = duty_ns * 100ULL;
	do_div(tmp, one_cycle);
	ph = tmp & RCAR_PWMCNT_PH0_MASK;
	tmp = mul_u64_u64_div_u64(duty_ns, clk_rate, (u64)NSEC_PER_SEC << div);
	if (tmp > FIELD_MAX(RCAR_PWMCNT_PH0_MASK))
		tmp = FIELD_MAX(RCAR_PWMCNT_PH0_MASK);
	ph = FIELD_PREP(RCAR_PWMCNT_PH0_MASK, tmp);

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