Commit 7d756066 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull pwm updates from Uwe Kleine-König:
 "This contains a new abstraction for PWM waveforms that is more
  expressive that the legacy one. Compared to the old abstraction it
  contains a duty_offset member instead of polarity. This new
  abstraction is already used in an ADC driver merged into the iio tree.

  The new API requires changes to the lowlevel drivers. For now there
  are two drivers that are converted to the new API (axi-pwmgen and
  stm32). Converted drivers continue to work with the old API. Drivers
  not yet converted only work with the older API.

  Otherwise it's the usual collection of fixes, cleanups and dt doc
  updates.

  This time around thanks go to Andy Shevchenko, Clark Wang, Conor
  Dooley, David Lechner, Dimitri Fedrau, Frank Li, Jun Li, Kelvin Zhang,
  Krzysztof Kozlowski, Nuno Sa, Shen Lichuan and Trevor Gamblin for code
  contributions, testing and review"

* tag 'pwm/for-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux:
  pwm: Assume a disabled PWM to emit a constant inactive output
  pwm: core: export pwm_get_state_hw()
  pwm: core: use device_match_name() instead of strcmp(dev_name(...
  dt-bindings: pwm: adi,axi-pwmgen: Increase #pwm-cells to 3
  pwm: imx27: Use clk_bulk_*() API to simplify clock handling
  pwm: imx27: Workaround of the pwm output bug when decrease the duty cycle
  pwm: axi-pwmgen: Enable FORCE_ALIGN by default
  pwm: axi-pwmgen: Rename 0x10 register
  dt-bindings: pwm: amlogic: Document C3 PWM
  pwm: axi-pwmgen: Create a dedicated function for getting driver data from a chip
  pwm: atmel-tcb: Use min() macro
  pwm: stm32: Fix error checking for a regmap_read() call
  pwm: Add kernel doc for members added to pwm_ops recently
  pwm: Reorder symbols in core.c
  pwm: stm32: Implementation of the waveform callbacks
  pwm: axi-pwmgen: Implementation of the waveform callbacks
  pwm: Add tracing for waveform callbacks
  pwm: Provide new consumer API functions for waveforms
  pwm: New abstraction for PWM waveforms
  pwm: Add more locking
parents f2ef3972 b2eaa117
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ properties:
    maxItems: 1

  "#pwm-cells":
    const: 2
    const: 3

  clocks:
    maxItems: 1
@@ -44,5 +44,5 @@ examples:
       compatible = "adi,axi-pwmgen-2.00.a";
       reg = <0x44b00000 0x1000>;
       clocks = <&spi_clk>;
       #pwm-cells = <2>;
       #pwm-cells = <3>;
    };
+1 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ properties:
          - amlogic,meson-s4-pwm
      - items:
          - enum:
              - amlogic,c3-pwm
              - amlogic,meson-a1-pwm
          - const: amlogic,meson-s4-pwm
      - items:
+768 −199

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -342,8 +342,8 @@ static int atmel_tcb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
		return 0;
	}

	period = state->period < INT_MAX ? state->period : INT_MAX;
	duty_cycle = state->duty_cycle < INT_MAX ? state->duty_cycle : INT_MAX;
	period = min(state->period, INT_MAX);
	duty_cycle = min(state->duty_cycle, INT_MAX);

	ret = atmel_tcb_pwm_config(chip, pwm, duty_cycle, period);
	if (ret)
+129 −50
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@
 *
 * Limitations:
 * - The writes to registers for period and duty are shadowed until
 *   LOAD_CONFIG is written to AXI_PWMGEN_REG_CONFIG, at which point
 *   LOAD_CONFIG is written to AXI_PWMGEN_REG_RSTN, at which point
 *   they take effect.
 * - Writing LOAD_CONFIG also has the effect of re-synchronizing all
 *   enabled channels, which could cause glitching on other channels. It
@@ -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>
@@ -32,14 +33,16 @@
#define AXI_PWMGEN_REG_ID		0x04
#define AXI_PWMGEN_REG_SCRATCHPAD	0x08
#define AXI_PWMGEN_REG_CORE_MAGIC	0x0C
#define AXI_PWMGEN_REG_CONFIG		0x10
#define AXI_PWMGEN_REG_RSTN		0x10
#define   AXI_PWMGEN_REG_RSTN_LOAD_CONFIG	BIT(1)
#define   AXI_PWMGEN_REG_RSTN_RESET		BIT(0)
#define AXI_PWMGEN_REG_NPWM		0x14
#define AXI_PWMGEN_REG_CONFIG		0x18
#define   AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN	BIT(1)
#define AXI_PWMGEN_CHX_PERIOD(ch)	(0x40 + (4 * (ch)))
#define AXI_PWMGEN_CHX_DUTY(ch)		(0x80 + (4 * (ch)))
#define AXI_PWMGEN_CHX_OFFSET(ch)	(0xC0 + (4 * (ch)))
#define AXI_PWMGEN_REG_CORE_MAGIC_VAL	0x601A3471 /* Identification number to test during setup */
#define AXI_PWMGEN_LOAD_CONFIG		BIT(1)
#define AXI_PWMGEN_REG_CONFIG_RESET	BIT(0)

struct axi_pwmgen_ddata {
	struct regmap *regmap;
@@ -53,81 +56,147 @@ 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 struct axi_pwmgen_ddata *axi_pwmgen_ddata_from_chip(struct pwm_chip *chip)
{
	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;
	return pwmchip_get_drvdata(chip);
}

	if (state->polarity != PWM_POLARITY_NORMAL)
		return -EINVAL;
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 = axi_pwmgen_ddata_from_chip(chip);

	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 (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;
		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 (period_cnt == 0)
			return -EINVAL;
	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);

		ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt);
		if (ret)
			return ret;
	return 0;
}

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 = axi_pwmgen_ddata_from_chip(chip);

	wf->period_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC,
					ddata->clk_rate_hz);

		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->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), duty_cnt);
	wf->duty_offset_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC,
					     ddata->clk_rate_hz);

	return 0;
}

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 = axi_pwmgen_ddata_from_chip(chip);
	struct regmap *regmap = ddata->regmap;
	unsigned int ch = pwm->hwpwm;
	int ret;

	ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt);
	if (ret)
		return ret;
	} else {
		ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0);

	ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt);
	if (ret)
		return ret;

		ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0);
	ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt);
	if (ret)
		return ret;
	}

	return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
	return regmap_write(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_LOAD_CONFIG);
}

static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
				struct pwm_state *state)
static int axi_pwmgen_read_waveform(struct pwm_chip *chip,
				    struct pwm_device *pwm,
				    void *_wfhw)
{
	struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
	struct axi_pwmgen_waveform *wfhw = _wfhw;
	struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(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_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt);
	if (ret)
		return ret;

	state->enabled = cnt != 0;

	state->period = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
	ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt);
	if (ret)
		return ret;

	ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &cnt);
	ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt);
	if (ret)
		return ret;

	state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
	if (wfhw->duty_cycle_cnt > wfhw->period_cnt)
		wfhw->duty_cycle_cnt = wfhw->period_cnt;

	state->polarity = PWM_POLARITY_NORMAL;
	/* 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)
@@ -156,7 +225,17 @@ static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)
	}

	/* Enable the core */
	ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_RESET);
	ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_RESET);
	if (ret)
		return ret;

	/*
	 * Enable force align so that changes to PWM period and duty cycle take
	 * effect immediately. Otherwise, the effect of the change is delayed
	 * until the period of all channels run out, which can be long after the
	 * apply function returns.
	 */
	ret = regmap_set_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN);
	if (ret)
		return ret;

Loading