pwm: Changes for v6.11-rc1

This contains the usual mix of fixes, cleanups, two new drivers and
 several dt binding updates. The fixes are for minor issues that are
 already old (4.11-rc1 and 3.9-rc1) and were found by code review and not
 during usage, so I didn't sent them for earlier inclusion.
 
 The changes to include/linux/mfd/stm32-timers.h and
 drivers/counter/stm32-timer-cnt.c are part of an immutable branch that
 will also be included in the mfd and counter PR. It changes some
 register definitions and affects the pwm-stm32 driver.
 
 Thanks go to Andy Shevchenko, AngeloGioacchino Del Regno, Conor Dooley,
 David Lechner, Dhruva Gole, Drew Fustini, Frank Li, Jeff Johnson, Junyi
 Zhao, Kelvin Zhang, Krzysztof Kozlowski, Lee Jones, Linus Walleij, Linus
 Walleij, Michael Hennerich, Nicola Di Lieto, Nicolas Ferre, Nuno Sa,
 Paul Cercueil, Raag Jadav, Rob Herring, Sean Anderson, Sean Young,
 Shenwei Wang, Stefan Wahren, Trevor Gamblin, Tzung-Bi Shih, Vincent
 Whitchurch and William Breathitt Gray for their contributions to this
 pull request; they authored changes, spend time reviewing changes and
 coordinated the above mentioned immutable branch.
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEP4GsaTp6HlmJrf7Tj4D7WH0S/k4FAmaUYxcACgkQj4D7WH0S
 /k7p9AgAsmKo97xC3XoXbWE2gvgK5LuqIoNfjFGGmcYZV6xsyfte2ZEoED6r6W63
 l/GwbBUKhGPZM/y+VL8QoXxWRRx/XmTjsawCX0d+jHJjw+nluSIVUKMDYmQpaCPg
 UehGaJKjAUqczPytxbTGrHEevArHPN1GieAcayfyOI7iqQomLklZ2VX3PKbgVcjC
 3EMfyAK9JPJ3x6IvMVZQeZa4/OhLv78p2p1mLWaXjpmp5GErPHujBz4ByPtqUAgt
 YWGFWlFOvy1EQsGwJ2qsUa/ZAfe5AkcmaJ4HwhBHe5Vv4pm8c9oC66DUUeKbyNMP
 O5QPPEN3gr9EWL9VQaGOyfY7JAfvaQ==
 =/dpI
 -----END PGP SIGNATURE-----

Merge tag 'pwm/for-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux

Pull pwm updates from Uwe Kleine-König:
 "This contains the usual mix of fixes, cleanups, two new drivers and
  several dt binding updates. The fixes are for minor issues that are
  already old (4.11-rc1 and 3.9-rc1) and were found by code review and
  not during usage, so I didn't sent them for earlier inclusion.

  The changes to include/linux/mfd/stm32-timers.h and
  drivers/counter/stm32-timer-cnt.c are part of an immutable branch that
  will also be included in the mfd and counter pulls. It changes some
  register definitions and affects the pwm-stm32 driver.

  Thanks go to Andy Shevchenko, AngeloGioacchino Del Regno, Conor
  Dooley, David Lechner, Dhruva Gole, Drew Fustini, Frank Li, Jeff
  Johnson, Junyi Zhao, Kelvin Zhang, Krzysztof Kozlowski, Lee Jones,
  Linus Walleij, Linus Walleij, Michael Hennerich, Nicola Di Lieto,
  Nicolas Ferre, Nuno Sa, Paul Cercueil, Raag Jadav, Rob Herring, Sean
  Anderson, Sean Young, Shenwei Wang, Stefan Wahren, Trevor Gamblin,
  Tzung-Bi Shih, Vincent Whitchurch and William Breathitt Gray for their
  contributions to this pull request; they authored changes, spend time
  reviewing changes and coordinated the above mentioned immutable
  branch"

* tag 'pwm/for-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux: (38 commits)
  pwm: axi-pwmgen: add .max_register to regmap
  dt-bindings: pwm: at91: Add sama7d65 compatible string
  pwm: atmel-tcb: Make private data variable naming consistent
  pwm: atmel-tcb: Simplify checking the companion output
  pwm: Allow pwm state transitions from an invalid state
  pwm: xilinx: Simplify using devm_ functions
  pwm: Use guards for pwm_lookup_lock instead of explicity mutex_lock + mutex_unlock
  pwm: Use guards for export->lock instead of explicity mutex_lock + mutex_unlock
  pwm: Use guards for pwm_lock instead of explicity mutex_lock + mutex_unlock
  pwm: Register debugfs operations after the pwm class
  pwm: imx-tpm: Enable pinctrl setting for sleep state
  pwm: lpss: drop redundant runtime PM handles
  pwm: lpss: use devm_pm_runtime_enable() helper
  pwm-stm32: Make use of parametrised register definitions
  dt-bindings: pwm: imx: remove interrupt property from required
  pwm: meson: Add support for Amlogic S4 PWM
  pwm: Add GPIO PWM driver
  dt-bindings: pwm: Add pwm-gpio
  pwm: Drop pwm_apply_state()
  bus: ts-nbus: Use pwm_apply_might_sleep()
  ...
This commit is contained in:
Linus Torvalds 2024-07-15 17:42:28 -07:00
commit c6e63a9882
35 changed files with 1068 additions and 442 deletions

View File

@ -0,0 +1,48 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/adi,axi-pwmgen.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Analog Devices AXI PWM generator
maintainers:
- Michael Hennerich <Michael.Hennerich@analog.com>
- Nuno Sá <nuno.sa@analog.com>
description:
The Analog Devices AXI PWM generator can generate PWM signals
with variable pulse width and period.
https://wiki.analog.com/resources/fpga/docs/axi_pwm_gen
allOf:
- $ref: pwm.yaml#
properties:
compatible:
const: adi,axi-pwmgen-2.00.a
reg:
maxItems: 1
"#pwm-cells":
const: 2
clocks:
maxItems: 1
required:
- reg
- clocks
unevaluatedProperties: false
examples:
- |
pwm@44b00000 {
compatible = "adi,axi-pwmgen-2.00.a";
reg = <0x44b00000 0x1000>;
clocks = <&spi_clk>;
#pwm-cells = <2>;
};

View File

@ -23,7 +23,9 @@ properties:
- atmel,sama5d2-pwm
- microchip,sam9x60-pwm
- items:
- const: microchip,sama7g5-pwm
- enum:
- microchip,sama7d65-pwm
- microchip,sama7g5-pwm
- const: atmel,sama5d2-pwm
- items:
- const: microchip,sam9x7-pwm

View File

@ -0,0 +1,92 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/fsl,vf610-ftm-pwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Freescale FlexTimer Module (FTM) PWM controller
description: |
The same FTM PWM device can have a different endianness on different SoCs. The
device tree provides a property to describing this so that an operating system
device driver can handle all variants of the device. Refer to the table below
for the endianness of the FTM PWM block as integrated into the existing SoCs:
SoC | FTM-PWM endianness
--------+-------------------
Vybrid | LE
LS1 | BE
LS2 | LE
Please see ../regmap/regmap.txt for more detail about how to specify endian
modes in device tree.
maintainers:
- Frank Li <Frank.Li@nxp.com>
properties:
compatible:
enum:
- fsl,vf610-ftm-pwm
- fsl,imx8qm-ftm-pwm
reg:
maxItems: 1
"#pwm-cells":
const: 3
clocks:
minItems: 4
maxItems: 4
clock-names:
items:
- const: ftm_sys
- const: ftm_ext
- const: ftm_fix
- const: ftm_cnt_clk_en
pinctrl-0: true
pinctrl-1: true
pinctrl-names:
minItems: 1
items:
- const: default
- const: sleep
big-endian:
$ref: /schemas/types.yaml#/definitions/flag
description:
Boolean property, required if the FTM PWM registers use a big-
endian rather than little-endian layout.
required:
- compatible
- reg
- clocks
- clock-names
allOf:
- $ref: pwm.yaml#
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/clock/vf610-clock.h>
pwm@40038000 {
compatible = "fsl,vf610-ftm-pwm";
reg = <0x40038000 0x1000>;
#pwm-cells = <3>;
clocks = <&clks VF610_CLK_FTM0>,
<&clks VF610_CLK_FTM0_EXT_SEL>,
<&clks VF610_CLK_FTM0_FIX_SEL>,
<&clks VF610_CLK_FTM0_EXT_FIX_EN>;
clock-names = "ftm_sys", "ftm_ext", "ftm_fix", "ftm_cnt_clk_en";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm0_1>;
big-endian;
};

View File

@ -68,7 +68,6 @@ required:
- reg
- clocks
- clock-names
- interrupts
additionalProperties: false

View File

@ -1,55 +0,0 @@
Freescale FlexTimer Module (FTM) PWM controller
The same FTM PWM device can have a different endianness on different SoCs. The
device tree provides a property to describing this so that an operating system
device driver can handle all variants of the device. Refer to the table below
for the endianness of the FTM PWM block as integrated into the existing SoCs:
SoC | FTM-PWM endianness
--------+-------------------
Vybrid | LE
LS1 | BE
LS2 | LE
Please see ../regmap/regmap.txt for more detail about how to specify endian
modes in device tree.
Required properties:
- compatible : should be "fsl,<soc>-ftm-pwm" and one of the following
compatible strings:
- "fsl,vf610-ftm-pwm" for PWM compatible with the one integrated on VF610
- "fsl,imx8qm-ftm-pwm" for PWM compatible with the one integrated on i.MX8QM
- reg: Physical base address and length of the controller's registers
- #pwm-cells: Should be 3. See pwm.yaml in this directory for a description of
the cells format.
- clock-names: Should include the following module clock source entries:
"ftm_sys" (module clock, also can be used as counter clock),
"ftm_ext" (external counter clock),
"ftm_fix" (fixed counter clock),
"ftm_cnt_clk_en" (external and fixed counter clock enable/disable).
- clocks: Must contain a phandle and clock specifier for each entry in
clock-names, please see clock/clock-bindings.txt for details of the property
values.
- pinctrl-names: Must contain a "default" entry.
- pinctrl-NNN: One property must exist for each entry in pinctrl-names.
See pinctrl/pinctrl-bindings.txt for details of the property values.
- big-endian: Boolean property, required if the FTM PWM registers use a big-
endian rather than little-endian layout.
Example:
pwm0: pwm@40038000 {
compatible = "fsl,vf610-ftm-pwm";
reg = <0x40038000 0x1000>;
#pwm-cells = <3>;
clock-names = "ftm_sys", "ftm_ext",
"ftm_fix", "ftm_cnt_clk_en";
clocks = <&clks VF610_CLK_FTM0>,
<&clks VF610_CLK_FTM0_EXT_SEL>,
<&clks VF610_CLK_FTM0_FIX_SEL>,
<&clks VF610_CLK_FTM0_EXT_FIX_EN>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm0_1>;
big-endian;
};

View File

@ -0,0 +1,46 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/pwm-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Generic software PWM for modulating GPIOs
maintainers:
- Stefan Wahren <wahrenst@gmx.net>
allOf:
- $ref: pwm.yaml#
properties:
compatible:
const: pwm-gpio
"#pwm-cells":
const: 3
description:
See pwm.yaml in this directory for a description of the cells format.
The first cell which represents the PWM instance number must always
be zero.
gpios:
description:
GPIO to be modulated
maxItems: 1
required:
- compatible
- "#pwm-cells"
- gpios
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
pwm {
#pwm-cells = <3>;
compatible = "pwm-gpio";
gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
};

View File

@ -16,8 +16,10 @@ properties:
pattern: "^pwm(@.*|-([0-9]|[1-9][0-9]+))?$"
"#pwm-cells":
description:
Number of cells in a PWM specifier.
description: |
Number of cells in a PWM specifier. Typically the cells represent, in
order: the chip-relative PWM number, the PWM period in nanoseconds and
optionally a number of flags (defined in <dt-bindings/pwm/pwm.h>).
required:
- "#pwm-cells"

View File

@ -27,7 +27,12 @@ hardware descriptions such as device tree or ACPI:
to the lines for a more permanent solution of this type.
- gpio-beeper: drivers/input/misc/gpio-beeper.c is used to provide a beep from
an external speaker connected to a GPIO line.
an external speaker connected to a GPIO line. (If the beep is controlled by
off/on, for an actual PWM waveform, see pwm-gpio below.)
- pwm-gpio: drivers/pwm/pwm-gpio.c is used to toggle a GPIO with a high
resolution timer producing a PWM waveform on the GPIO line, as well as
Linux high resolution timers can do.
- extcon-gpio: drivers/extcon/extcon-gpio.c is used when you need to read an
external connector status, such as a headset line for an audio driver or an

View File

@ -3549,6 +3549,15 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/spi/adi,axi-spi-engine.yaml
F: drivers/spi/spi-axi-spi-engine.c
AXI PWM GENERATOR
M: Michael Hennerich <michael.hennerich@analog.com>
M: Nuno Sá <nuno.sa@analog.com>
L: linux-pwm@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
F: drivers/pwm/pwm-axi-pwmgen.c
AXXIA I2C CONTROLLER
M: Krzysztof Adamski <krzysztof.adamski@nokia.com>
L: linux-i2c@vger.kernel.org

View File

@ -294,7 +294,7 @@ static int ts_nbus_probe(struct platform_device *pdev)
state.duty_cycle = state.period;
state.enabled = true;
ret = pwm_apply_state(pwm, &state);
ret = pwm_apply_might_sleep(pwm, &state);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to configure PWM\n");

View File

@ -465,7 +465,7 @@ static int stm32_count_events_configure(struct counter_device *counter)
ret = stm32_count_capture_configure(counter, event_node->channel, true);
if (ret)
return ret;
dier |= TIM_DIER_CC_IE(event_node->channel);
dier |= TIM_DIER_CCxIE(event_node->channel + 1);
break;
default:
/* should never reach this path */
@ -478,7 +478,7 @@ static int stm32_count_events_configure(struct counter_device *counter)
/* check for disabled capture events */
for (i = 0 ; i < priv->nchannels; i++) {
if (!(dier & TIM_DIER_CC_IE(i))) {
if (!(dier & TIM_DIER_CCxIE(i + 1))) {
ret = stm32_count_capture_configure(counter, i, false);
if (ret)
return ret;

View File

@ -94,6 +94,19 @@ config PWM_ATMEL_TCB
To compile this driver as a module, choose M here: the module
will be called pwm-atmel-tcb.
config PWM_AXI_PWMGEN
tristate "Analog Devices AXI PWM generator"
depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST
select REGMAP_MMIO
help
This enables support for the Analog Devices AXI PWM generator.
This is a configurable PWM generator with variable pulse width and
period.
To compile this driver as a module, choose M here: the module will be
called pwm-axi-pwmgen.
config PWM_BCM_IPROC
tristate "iProc PWM support"
depends on ARCH_BCM_IPROC || COMPILE_TEST
@ -223,6 +236,17 @@ config PWM_FSL_FTM
To compile this driver as a module, choose M here: the module
will be called pwm-fsl-ftm.
config PWM_GPIO
tristate "GPIO PWM support"
depends on GPIOLIB
depends on HIGH_RES_TIMERS
help
Generic PWM framework driver for software PWM toggling a GPIO pin
from kernel high-resolution timers.
To compile this driver as a module, choose M here: the module
will be called pwm-gpio.
config PWM_HIBVT
tristate "HiSilicon BVT PWM support"
depends on ARCH_HISI || COMPILE_TEST

View File

@ -5,6 +5,7 @@ obj-$(CONFIG_PWM_APPLE) += pwm-apple.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
obj-$(CONFIG_PWM_AXI_PWMGEN) += pwm-axi-pwmgen.o
obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o
obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
@ -18,6 +19,7 @@ obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o
obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o
obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o
obj-$(CONFIG_PWM_IMG) += pwm-img.o
obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o

View File

@ -6,6 +6,8 @@
* Copyright (C) 2011-2012 Avionic Design GmbH
*/
#define DEFAULT_SYMBOL_NAMESPACE PWM
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/idr.h>
@ -135,6 +137,25 @@ static void pwm_apply_debug(struct pwm_device *pwm,
}
}
static bool pwm_state_valid(const struct pwm_state *state)
{
/*
* For a disabled state all other state description is irrelevant and
* and supposed to be ignored. So also ignore any strange values and
* consider the state ok.
*/
if (state->enabled)
return true;
if (!state->period)
return false;
if (state->duty_cycle > state->period)
return false;
return true;
}
/**
* __pwm_apply() - atomically apply a new state to a PWM device
* @pwm: PWM device
@ -145,10 +166,26 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
struct pwm_chip *chip;
int err;
if (!pwm || !state || !state->period ||
state->duty_cycle > state->period)
if (!pwm || !state)
return -EINVAL;
if (!pwm_state_valid(state)) {
/*
* Allow to transition from one invalid state to another.
* This ensures that you can e.g. change the polarity while
* the period is zero. (This happens on stm32 when the hardware
* is in its poweron default state.) This greatly simplifies
* working with the sysfs API where you can only change one
* parameter at a time.
*/
if (!pwm_state_valid(&pwm->state)) {
pwm->state = *state;
return 0;
}
return -EINVAL;
}
chip = pwm->chip;
if (state->period == pwm->state.period &&
@ -291,19 +328,15 @@ EXPORT_SYMBOL_GPL(pwm_adjust_config);
int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
unsigned long timeout)
{
int err;
if (!pwm || !pwm->chip->ops)
return -EINVAL;
if (!pwm->chip->ops->capture)
return -ENOSYS;
mutex_lock(&pwm_lock);
err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout);
mutex_unlock(&pwm_lock);
guard(mutex)(&pwm_lock);
return err;
return pwm->chip->ops->capture(pwm->chip, pwm, result, timeout);
}
EXPORT_SYMBOL_GPL(pwm_capture);
@ -315,19 +348,15 @@ static struct pwm_chip *pwmchip_find_by_name(const char *name)
if (!name)
return NULL;
mutex_lock(&pwm_lock);
guard(mutex)(&pwm_lock);
idr_for_each_entry_ul(&pwm_chips, chip, tmp, id) {
const char *chip_name = dev_name(pwmchip_parent(chip));
if (chip_name && strcmp(chip_name, name) == 0) {
mutex_unlock(&pwm_lock);
if (chip_name && strcmp(chip_name, name) == 0)
return chip;
}
}
mutex_unlock(&pwm_lock);
return NULL;
}
@ -394,9 +423,9 @@ err_get_device:
* chip. A negative error code is returned if the index is not valid for the
* specified PWM chip or if the PWM device cannot be requested.
*/
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label)
static struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label)
{
struct pwm_device *pwm;
int err;
@ -404,18 +433,16 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
if (!chip || index >= chip->npwm)
return ERR_PTR(-EINVAL);
mutex_lock(&pwm_lock);
guard(mutex)(&pwm_lock);
pwm = &chip->pwms[index];
err = pwm_device_request(pwm, label);
if (err < 0)
pwm = ERR_PTR(err);
return ERR_PTR(err);
mutex_unlock(&pwm_lock);
return pwm;
}
EXPORT_SYMBOL_GPL(pwm_request_from_chip);
struct pwm_device *
of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *args)
@ -511,11 +538,11 @@ static ssize_t period_store(struct device *pwm_dev,
if (ret)
return ret;
mutex_lock(&export->lock);
guard(mutex)(&export->lock);
pwm_get_state(pwm, &state);
state.period = val;
ret = pwm_apply_might_sleep(pwm, &state);
mutex_unlock(&export->lock);
return ret ? : size;
}
@ -546,11 +573,11 @@ static ssize_t duty_cycle_store(struct device *pwm_dev,
if (ret)
return ret;
mutex_lock(&export->lock);
guard(mutex)(&export->lock);
pwm_get_state(pwm, &state);
state.duty_cycle = val;
ret = pwm_apply_might_sleep(pwm, &state);
mutex_unlock(&export->lock);
return ret ? : size;
}
@ -580,7 +607,7 @@ static ssize_t enable_store(struct device *pwm_dev,
if (ret)
return ret;
mutex_lock(&export->lock);
guard(mutex)(&export->lock);
pwm_get_state(pwm, &state);
@ -592,14 +619,11 @@ static ssize_t enable_store(struct device *pwm_dev,
state.enabled = true;
break;
default:
ret = -EINVAL;
goto unlock;
return -EINVAL;
}
ret = pwm_apply_might_sleep(pwm, &state);
unlock:
mutex_unlock(&export->lock);
return ret ? : size;
}
@ -643,11 +667,11 @@ static ssize_t polarity_store(struct device *pwm_dev,
else
return -EINVAL;
mutex_lock(&export->lock);
guard(mutex)(&export->lock);
pwm_get_state(pwm, &state);
state.polarity = polarity;
ret = pwm_apply_might_sleep(pwm, &state);
mutex_unlock(&export->lock);
return ret ? : size;
}
@ -1102,11 +1126,11 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
chip->owner = owner;
mutex_lock(&pwm_lock);
guard(mutex)(&pwm_lock);
ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_idr_alloc;
return ret;
chip->id = ret;
@ -1119,8 +1143,6 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
if (ret)
goto err_device_add;
mutex_unlock(&pwm_lock);
return 0;
err_device_add:
@ -1128,9 +1150,6 @@ err_device_add:
of_pwmchip_remove(chip);
idr_remove(&pwm_chips, chip->id);
err_idr_alloc:
mutex_unlock(&pwm_lock);
return ret;
}
@ -1149,11 +1168,8 @@ void pwmchip_remove(struct pwm_chip *chip)
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_remove(chip);
mutex_lock(&pwm_lock);
idr_remove(&pwm_chips, chip->id);
mutex_unlock(&pwm_lock);
scoped_guard(mutex, &pwm_lock)
idr_remove(&pwm_chips, chip->id);
device_del(&chip->dev);
}
@ -1209,15 +1225,11 @@ static struct pwm_chip *fwnode_to_pwmchip(struct fwnode_handle *fwnode)
struct pwm_chip *chip;
unsigned long id, tmp;
mutex_lock(&pwm_lock);
guard(mutex)(&pwm_lock);
idr_for_each_entry_ul(&pwm_chips, chip, tmp, id)
if (pwmchip_parent(chip) && device_match_fwnode(pwmchip_parent(chip), fwnode)) {
mutex_unlock(&pwm_lock);
if (pwmchip_parent(chip) && device_match_fwnode(pwmchip_parent(chip), fwnode))
return chip;
}
mutex_unlock(&pwm_lock);
return ERR_PTR(-EPROBE_DEFER);
}
@ -1366,14 +1378,12 @@ static LIST_HEAD(pwm_lookup_list);
*/
void pwm_add_table(struct pwm_lookup *table, size_t num)
{
mutex_lock(&pwm_lookup_lock);
guard(mutex)(&pwm_lookup_lock);
while (num--) {
list_add_tail(&table->list, &pwm_lookup_list);
table++;
}
mutex_unlock(&pwm_lookup_lock);
}
/**
@ -1383,14 +1393,12 @@ void pwm_add_table(struct pwm_lookup *table, size_t num)
*/
void pwm_remove_table(struct pwm_lookup *table, size_t num)
{
mutex_lock(&pwm_lookup_lock);
guard(mutex)(&pwm_lookup_lock);
while (num--) {
list_del(&table->list);
table++;
}
mutex_unlock(&pwm_lookup_lock);
}
/**
@ -1451,37 +1459,34 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
* Then we take the most specific entry - with the following order
* of precedence: dev+con > dev only > con only.
*/
mutex_lock(&pwm_lookup_lock);
scoped_guard(mutex, &pwm_lookup_lock)
list_for_each_entry(p, &pwm_lookup_list, list) {
match = 0;
list_for_each_entry(p, &pwm_lookup_list, list) {
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
match += 2;
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
if (match > best) {
chosen = p;
if (match != 3)
best = match;
else
break;
}
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
if (match > best) {
chosen = p;
if (match != 3)
best = match;
else
break;
}
}
mutex_unlock(&pwm_lookup_lock);
if (!chosen)
return ERR_PTR(-ENODEV);
@ -1532,11 +1537,11 @@ void pwm_put(struct pwm_device *pwm)
chip = pwm->chip;
mutex_lock(&pwm_lock);
guard(mutex)(&pwm_lock);
if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
pr_warn("PWM device already freed\n");
goto out;
return;
}
if (chip->ops->free)
@ -1547,8 +1552,6 @@ void pwm_put(struct pwm_device *pwm)
put_device(&chip->dev);
module_put(chip->owner);
out:
mutex_unlock(&pwm_lock);
}
EXPORT_SYMBOL_GPL(pwm_put);
@ -1705,9 +1708,17 @@ DEFINE_SEQ_ATTRIBUTE(pwm_debugfs);
static int __init pwm_init(void)
{
int ret;
ret = class_register(&pwm_class);
if (ret) {
pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret));
return ret;
}
if (IS_ENABLED(CONFIG_DEBUG_FS))
debugfs_create_file("pwm", 0444, NULL, NULL, &pwm_debugfs_fops);
return class_register(&pwm_class);
return 0;
}
subsys_initcall(pwm_init);

View File

@ -81,7 +81,8 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
tcbpwm->period = 0;
tcbpwm->div = 0;
spin_lock(&tcbpwmc->lock);
guard(spinlock)(&tcbpwmc->lock);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr);
/*
* Get init config from Timer Counter registers if
@ -107,7 +108,6 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0;
regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), cmr);
spin_unlock(&tcbpwmc->lock);
return 0;
}
@ -137,7 +137,6 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm,
if (tcbpwm->duty == 0)
polarity = !polarity;
spin_lock(&tcbpwmc->lock);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr);
/* flush old setting and set the new one */
@ -172,8 +171,6 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm,
ATMEL_TC_SWTRG);
tcbpwmc->bkup.enabled = 0;
}
spin_unlock(&tcbpwmc->lock);
}
static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
@ -194,7 +191,6 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
if (tcbpwm->duty == 0)
polarity = !polarity;
spin_lock(&tcbpwmc->lock);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CMR), &cmr);
/* flush old setting and set the new one */
@ -256,7 +252,6 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(tcbpwmc->channel, CCR),
ATMEL_TC_SWTRG | ATMEL_TC_CLKEN);
tcbpwmc->bkup.enabled = 1;
spin_unlock(&tcbpwmc->lock);
return 0;
}
@ -265,7 +260,8 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = &tcbpwmc->pwms[pwm->hwpwm];
struct atmel_tcb_pwm_device *atcbpwm = NULL;
/* companion PWM sharing register values period and div */
struct atmel_tcb_pwm_device *atcbpwm = &tcbpwmc->pwms[pwm->hwpwm ^ 1];
int i = 0;
int slowclk = 0;
unsigned period;
@ -310,11 +306,6 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
duty = div_u64(duty_ns, min);
period = div_u64(period_ns, min);
if (pwm->hwpwm == 0)
atcbpwm = &tcbpwmc->pwms[1];
else
atcbpwm = &tcbpwmc->pwms[0];
/*
* PWM devices provided by the TCB driver are grouped by 2.
* PWM devices in a given group must be configured with the
@ -323,8 +314,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* We're checking the period value of the second PWM device
* in this group before applying the new config.
*/
if ((atcbpwm && atcbpwm->duty > 0 &&
atcbpwm->duty != atcbpwm->period) &&
if ((atcbpwm->duty > 0 && atcbpwm->duty != atcbpwm->period) &&
(atcbpwm->div != i || atcbpwm->period != period)) {
dev_err(pwmchip_parent(chip),
"failed to configure period_ns: PWM group already configured with a different value\n");
@ -341,9 +331,12 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
static int atmel_tcb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
int duty_cycle, period;
int ret;
guard(spinlock)(&tcbpwmc->lock);
if (!state->enabled) {
atmel_tcb_pwm_disable(chip, pwm, state->polarity);
return 0;
@ -389,17 +382,17 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
{
struct pwm_chip *chip;
const struct of_device_id *match;
struct atmel_tcb_pwm_chip *tcbpwm;
struct atmel_tcb_pwm_chip *tcbpwmc;
const struct atmel_tcb_config *config;
struct device_node *np = pdev->dev.of_node;
char clk_name[] = "t0_clk";
int err;
int channel;
chip = devm_pwmchip_alloc(&pdev->dev, NPWM, sizeof(*tcbpwm));
chip = devm_pwmchip_alloc(&pdev->dev, NPWM, sizeof(*tcbpwmc));
if (IS_ERR(chip))
return PTR_ERR(chip);
tcbpwm = to_tcb_chip(chip);
tcbpwmc = to_tcb_chip(chip);
err = of_property_read_u32(np, "reg", &channel);
if (err < 0) {
@ -409,20 +402,20 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
return err;
}
tcbpwm->regmap = syscon_node_to_regmap(np->parent);
if (IS_ERR(tcbpwm->regmap))
return PTR_ERR(tcbpwm->regmap);
tcbpwmc->regmap = syscon_node_to_regmap(np->parent);
if (IS_ERR(tcbpwmc->regmap))
return PTR_ERR(tcbpwmc->regmap);
tcbpwm->slow_clk = of_clk_get_by_name(np->parent, "slow_clk");
if (IS_ERR(tcbpwm->slow_clk))
return PTR_ERR(tcbpwm->slow_clk);
tcbpwmc->slow_clk = of_clk_get_by_name(np->parent, "slow_clk");
if (IS_ERR(tcbpwmc->slow_clk))
return PTR_ERR(tcbpwmc->slow_clk);
clk_name[1] += channel;
tcbpwm->clk = of_clk_get_by_name(np->parent, clk_name);
if (IS_ERR(tcbpwm->clk))
tcbpwm->clk = of_clk_get_by_name(np->parent, "t0_clk");
if (IS_ERR(tcbpwm->clk)) {
err = PTR_ERR(tcbpwm->clk);
tcbpwmc->clk = of_clk_get_by_name(np->parent, clk_name);
if (IS_ERR(tcbpwmc->clk))
tcbpwmc->clk = of_clk_get_by_name(np->parent, "t0_clk");
if (IS_ERR(tcbpwmc->clk)) {
err = PTR_ERR(tcbpwmc->clk);
goto err_slow_clk;
}
@ -430,22 +423,22 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
config = match->data;
if (config->has_gclk) {
tcbpwm->gclk = of_clk_get_by_name(np->parent, "gclk");
if (IS_ERR(tcbpwm->gclk)) {
err = PTR_ERR(tcbpwm->gclk);
tcbpwmc->gclk = of_clk_get_by_name(np->parent, "gclk");
if (IS_ERR(tcbpwmc->gclk)) {
err = PTR_ERR(tcbpwmc->gclk);
goto err_clk;
}
}
chip->ops = &atmel_tcb_pwm_ops;
tcbpwm->channel = channel;
tcbpwm->width = config->counter_width;
tcbpwmc->channel = channel;
tcbpwmc->width = config->counter_width;
err = clk_prepare_enable(tcbpwm->slow_clk);
err = clk_prepare_enable(tcbpwmc->slow_clk);
if (err)
goto err_gclk;
spin_lock_init(&tcbpwm->lock);
spin_lock_init(&tcbpwmc->lock);
err = pwmchip_add(chip);
if (err < 0)
@ -456,16 +449,16 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
return 0;
err_disable_clk:
clk_disable_unprepare(tcbpwm->slow_clk);
clk_disable_unprepare(tcbpwmc->slow_clk);
err_gclk:
clk_put(tcbpwm->gclk);
clk_put(tcbpwmc->gclk);
err_clk:
clk_put(tcbpwm->clk);
clk_put(tcbpwmc->clk);
err_slow_clk:
clk_put(tcbpwm->slow_clk);
clk_put(tcbpwmc->slow_clk);
return err;
}
@ -473,14 +466,14 @@ err_slow_clk:
static void atmel_tcb_pwm_remove(struct platform_device *pdev)
{
struct pwm_chip *chip = platform_get_drvdata(pdev);
struct atmel_tcb_pwm_chip *tcbpwm = to_tcb_chip(chip);
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
pwmchip_remove(chip);
clk_disable_unprepare(tcbpwm->slow_clk);
clk_put(tcbpwm->gclk);
clk_put(tcbpwm->clk);
clk_put(tcbpwm->slow_clk);
clk_disable_unprepare(tcbpwmc->slow_clk);
clk_put(tcbpwmc->gclk);
clk_put(tcbpwmc->clk);
clk_put(tcbpwmc->slow_clk);
}
static const struct of_device_id atmel_tcb_pwm_dt_ids[] = {
@ -492,14 +485,14 @@ MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids);
static int atmel_tcb_pwm_suspend(struct device *dev)
{
struct pwm_chip *chip = dev_get_drvdata(dev);
struct atmel_tcb_pwm_chip *tcbpwm = to_tcb_chip(chip);
struct atmel_tcb_channel *chan = &tcbpwm->bkup;
unsigned int channel = tcbpwm->channel;
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_channel *chan = &tcbpwmc->bkup;
unsigned int channel = tcbpwmc->channel;
regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, CMR), &chan->cmr);
regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, RA), &chan->ra);
regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, RB), &chan->rb);
regmap_read(tcbpwm->regmap, ATMEL_TC_REG(channel, RC), &chan->rc);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, CMR), &chan->cmr);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RA), &chan->ra);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RB), &chan->rb);
regmap_read(tcbpwmc->regmap, ATMEL_TC_REG(channel, RC), &chan->rc);
return 0;
}
@ -507,17 +500,17 @@ static int atmel_tcb_pwm_suspend(struct device *dev)
static int atmel_tcb_pwm_resume(struct device *dev)
{
struct pwm_chip *chip = dev_get_drvdata(dev);
struct atmel_tcb_pwm_chip *tcbpwm = to_tcb_chip(chip);
struct atmel_tcb_channel *chan = &tcbpwm->bkup;
unsigned int channel = tcbpwm->channel;
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_channel *chan = &tcbpwmc->bkup;
unsigned int channel = tcbpwmc->channel;
regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, CMR), chan->cmr);
regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, RA), chan->ra);
regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, RB), chan->rb);
regmap_write(tcbpwm->regmap, ATMEL_TC_REG(channel, RC), chan->rc);
regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, CMR), chan->cmr);
regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RA), chan->ra);
regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RB), chan->rb);
regmap_write(tcbpwmc->regmap, ATMEL_TC_REG(channel, RC), chan->rc);
if (chan->enabled)
regmap_write(tcbpwm->regmap,
regmap_write(tcbpwmc->regmap,
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
ATMEL_TC_REG(channel, CCR));

View File

@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Analog Devices AXI PWM generator
*
* Copyright 2024 Analog Devices Inc.
* Copyright 2024 Baylibre SAS
*
* Device docs: https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html
*
* Limitations:
* - The writes to registers for period and duty are shadowed until
* LOAD_CONFIG is written to AXI_PWMGEN_REG_CONFIG, 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
* is therefore expected that channels are assigned harmonic periods
* and all have a single user coordinating this.
* - Supports normal polarity. Does not support changing polarity.
* - On disable, the PWM output becomes low (inactive).
*/
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/fpga/adi-axi-common.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define AXI_PWMGEN_REG_CORE_VERSION 0x00
#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_NPWM 0x14
#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;
unsigned long clk_rate_hz;
};
static const struct regmap_config axi_pwmgen_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = 0xFC,
};
static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
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;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
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 (period_cnt == 0)
return -EINVAL;
ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt);
if (ret)
return ret;
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;
ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), duty_cnt);
if (ret)
return ret;
} else {
ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0);
if (ret)
return ret;
ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0);
if (ret)
return ret;
}
return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
}
static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(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);
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), &cnt);
if (ret)
return ret;
state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
state->polarity = PWM_POLARITY_NORMAL;
return 0;
}
static const struct pwm_ops axi_pwmgen_pwm_ops = {
.apply = axi_pwmgen_apply,
.get_state = axi_pwmgen_get_state,
};
static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)
{
int ret;
u32 val;
ret = regmap_read(regmap, AXI_PWMGEN_REG_CORE_MAGIC, &val);
if (ret)
return ret;
if (val != AXI_PWMGEN_REG_CORE_MAGIC_VAL)
return dev_err_probe(dev, -ENODEV,
"failed to read expected value from register: got %08x, expected %08x\n",
val, AXI_PWMGEN_REG_CORE_MAGIC_VAL);
ret = regmap_read(regmap, AXI_PWMGEN_REG_CORE_VERSION, &val);
if (ret)
return ret;
if (ADI_AXI_PCORE_VER_MAJOR(val) != 2) {
return dev_err_probe(dev, -ENODEV, "Unsupported peripheral version %u.%u.%u\n",
ADI_AXI_PCORE_VER_MAJOR(val),
ADI_AXI_PCORE_VER_MINOR(val),
ADI_AXI_PCORE_VER_PATCH(val));
}
/* Enable the core */
ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_RESET);
if (ret)
return ret;
ret = regmap_read(regmap, AXI_PWMGEN_REG_NPWM, &val);
if (ret)
return ret;
/* Return the number of PWMs */
return val;
}
static int axi_pwmgen_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct regmap *regmap;
struct pwm_chip *chip;
struct axi_pwmgen_ddata *ddata;
struct clk *clk;
void __iomem *io_base;
int ret;
io_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(io_base))
return PTR_ERR(io_base);
regmap = devm_regmap_init_mmio(dev, io_base, &axi_pwmgen_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap),
"failed to init register map\n");
ret = axi_pwmgen_setup(regmap, dev);
if (ret < 0)
return ret;
chip = devm_pwmchip_alloc(dev, ret, sizeof(*ddata));
if (IS_ERR(chip))
return PTR_ERR(chip);
ddata = pwmchip_get_drvdata(chip);
ddata->regmap = regmap;
clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(clk))
return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
ret = devm_clk_rate_exclusive_get(dev, clk);
if (ret)
return dev_err_probe(dev, ret, "failed to get exclusive rate\n");
ddata->clk_rate_hz = clk_get_rate(clk);
if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC)
return dev_err_probe(dev, -EINVAL,
"Invalid clock rate: %lu\n", ddata->clk_rate_hz);
chip->ops = &axi_pwmgen_pwm_ops;
chip->atomic = true;
ret = devm_pwmchip_add(dev, chip);
if (ret)
return dev_err_probe(dev, ret, "could not add PWM chip\n");
return 0;
}
static const struct of_device_id axi_pwmgen_ids[] = {
{ .compatible = "adi,axi-pwmgen-2.00.a" },
{ }
};
MODULE_DEVICE_TABLE(of, axi_pwmgen_ids);
static struct platform_driver axi_pwmgen_driver = {
.driver = {
.name = "axi-pwmgen",
.of_match_table = axi_pwmgen_ids,
},
.probe = axi_pwmgen_probe,
};
module_platform_driver(axi_pwmgen_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@analog.com>");
MODULE_AUTHOR("Trevor Gamblin <tgamblin@baylibre.com>");
MODULE_DESCRIPTION("Driver for the Analog Devices AXI PWM generator");

View File

@ -20,20 +20,10 @@
*
* @ec: Pointer to EC device
* @use_pwm_type: Use PWM types instead of generic channels
* @channel: array with per-channel data
*/
struct cros_ec_pwm_device {
struct cros_ec_device *ec;
bool use_pwm_type;
struct cros_ec_pwm *channel;
};
/**
* struct cros_ec_pwm - per-PWM driver data
* @duty_cycle: cached duty cycle
*/
struct cros_ec_pwm {
u16 duty_cycle;
};
static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *chip)
@ -135,7 +125,6 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
struct cros_ec_pwm *channel = &ec_pwm->channel[pwm->hwpwm];
u16 duty_cycle;
int ret;
@ -156,8 +145,6 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (ret < 0)
return ret;
channel->duty_cycle = state->duty_cycle;
return 0;
}
@ -165,7 +152,6 @@ static int cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
struct cros_ec_pwm *channel = &ec_pwm->channel[pwm->hwpwm];
int ret;
ret = cros_ec_pwm_get_duty(ec_pwm->ec, ec_pwm->use_pwm_type, pwm->hwpwm);
@ -175,44 +161,13 @@ static int cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
}
state->enabled = (ret > 0);
state->duty_cycle = ret;
state->period = EC_PWM_MAX_DUTY;
state->polarity = PWM_POLARITY_NORMAL;
/*
* Note that "disabled" and "duty cycle == 0" are treated the same. If
* the cached duty cycle is not zero, used the cached duty cycle. This
* ensures that the configured duty cycle is kept across a disable and
* enable operation and avoids potentially confusing consumers.
*
* For the case of the initial hardware readout, channel->duty_cycle
* will be 0 and the actual duty cycle read from the EC is used.
*/
if (ret == 0 && channel->duty_cycle > 0)
state->duty_cycle = channel->duty_cycle;
else
state->duty_cycle = ret;
return 0;
}
static struct pwm_device *
cros_ec_pwm_xlate(struct pwm_chip *chip, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
if (args->args[0] >= chip->npwm)
return ERR_PTR(-EINVAL);
pwm = pwm_request_from_chip(chip, args->args[0], NULL);
if (IS_ERR(pwm))
return pwm;
/* The EC won't let us change the period */
pwm->args.period = EC_PWM_MAX_DUTY;
return pwm;
}
static const struct pwm_ops cros_ec_pwm_ops = {
.get_state = cros_ec_pwm_get_state,
.apply = cros_ec_pwm_apply,
@ -263,7 +218,7 @@ static int cros_ec_pwm_probe(struct platform_device *pdev)
struct cros_ec_pwm_device *ec_pwm;
struct pwm_chip *chip;
bool use_pwm_type = false;
unsigned int npwm;
unsigned int i, npwm;
int ret;
if (!ec)
@ -289,12 +244,17 @@ static int cros_ec_pwm_probe(struct platform_device *pdev)
/* PWM chip */
chip->ops = &cros_ec_pwm_ops;
chip->of_xlate = cros_ec_pwm_xlate;
ec_pwm->channel = devm_kcalloc(dev, chip->npwm, sizeof(*ec_pwm->channel),
GFP_KERNEL);
if (!ec_pwm->channel)
return -ENOMEM;
/*
* The device tree binding for this device is special as it only uses a
* single cell (for the hwid) and so doesn't provide a default period.
* This isn't a big problem though as the hardware only supports a
* single period length, it's just a bit ugly to make this fit into the
* pwm core abstractions. So initialize the period here, as
* of_pwm_xlate_with_flags() won't do that for us.
*/
for (i = 0; i < npwm; ++i)
chip->pwms[i].args.period = EC_PWM_MAX_DUTY;
dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);

241
drivers/pwm/pwm-gpio.c Normal file
View File

@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic software PWM for modulating GPIOs
*
* Copyright (C) 2020 Axis Communications AB
* Copyright (C) 2020 Nicola Di Lieto
* Copyright (C) 2024 Stefan Wahren
* Copyright (C) 2024 Linus Walleij
*/
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/hrtimer.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include <linux/types.h>
struct pwm_gpio {
struct hrtimer gpio_timer;
struct gpio_desc *gpio;
struct pwm_state state;
struct pwm_state next_state;
/* Protect internal state between pwm_ops and hrtimer */
spinlock_t lock;
bool changing;
bool running;
bool level;
};
static void pwm_gpio_round(struct pwm_state *dest, const struct pwm_state *src)
{
u64 dividend;
u32 remainder;
*dest = *src;
/* Round down to hrtimer resolution */
dividend = dest->period;
remainder = do_div(dividend, hrtimer_resolution);
dest->period -= remainder;
dividend = dest->duty_cycle;
remainder = do_div(dividend, hrtimer_resolution);
dest->duty_cycle -= remainder;
}
static u64 pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level)
{
const struct pwm_state *state = &gpwm->state;
bool invert = state->polarity == PWM_POLARITY_INVERSED;
gpwm->level = level;
gpiod_set_value(gpwm->gpio, gpwm->level ^ invert);
if (!state->duty_cycle || state->duty_cycle == state->period) {
gpwm->running = false;
return 0;
}
gpwm->running = true;
return level ? state->duty_cycle : state->period - state->duty_cycle;
}
static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer)
{
struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio,
gpio_timer);
u64 next_toggle;
bool new_level;
guard(spinlock_irqsave)(&gpwm->lock);
/* Apply new state at end of current period */
if (!gpwm->level && gpwm->changing) {
gpwm->changing = false;
gpwm->state = gpwm->next_state;
new_level = !!gpwm->state.duty_cycle;
} else {
new_level = !gpwm->level;
}
next_toggle = pwm_gpio_toggle(gpwm, new_level);
if (next_toggle)
hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer),
ns_to_ktime(next_toggle));
return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART;
}
static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip);
bool invert = state->polarity == PWM_POLARITY_INVERSED;
if (state->duty_cycle && state->duty_cycle < hrtimer_resolution)
return -EINVAL;
if (state->duty_cycle != state->period &&
(state->period - state->duty_cycle < hrtimer_resolution))
return -EINVAL;
if (!state->enabled) {
hrtimer_cancel(&gpwm->gpio_timer);
} else if (!gpwm->running) {
int ret;
/*
* This just enables the output, but pwm_gpio_toggle()
* really starts the duty cycle.
*/
ret = gpiod_direction_output(gpwm->gpio, invert);
if (ret)
return ret;
}
guard(spinlock_irqsave)(&gpwm->lock);
if (!state->enabled) {
pwm_gpio_round(&gpwm->state, state);
gpwm->running = false;
gpwm->changing = false;
gpiod_set_value(gpwm->gpio, invert);
} else if (gpwm->running) {
pwm_gpio_round(&gpwm->next_state, state);
gpwm->changing = true;
} else {
unsigned long next_toggle;
pwm_gpio_round(&gpwm->state, state);
gpwm->changing = false;
next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle);
if (next_toggle)
hrtimer_start(&gpwm->gpio_timer, next_toggle,
HRTIMER_MODE_REL);
}
return 0;
}
static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip);
guard(spinlock_irqsave)(&gpwm->lock);
if (gpwm->changing)
*state = gpwm->next_state;
else
*state = gpwm->state;
return 0;
}
static const struct pwm_ops pwm_gpio_ops = {
.apply = pwm_gpio_apply,
.get_state = pwm_gpio_get_state,
};
static void pwm_gpio_disable_hrtimer(void *data)
{
struct pwm_gpio *gpwm = data;
hrtimer_cancel(&gpwm->gpio_timer);
}
static int pwm_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pwm_chip *chip;
struct pwm_gpio *gpwm;
int ret;
chip = devm_pwmchip_alloc(dev, 1, sizeof(*gpwm));
if (IS_ERR(chip))
return PTR_ERR(chip);
gpwm = pwmchip_get_drvdata(chip);
spin_lock_init(&gpwm->lock);
gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
if (IS_ERR(gpwm->gpio))
return dev_err_probe(dev, PTR_ERR(gpwm->gpio),
"%pfw: could not get gpio\n",
dev_fwnode(dev));
if (gpiod_cansleep(gpwm->gpio))
return dev_err_probe(dev, -EINVAL,
"%pfw: sleeping GPIO not supported\n",
dev_fwnode(dev));
chip->ops = &pwm_gpio_ops;
chip->atomic = true;
hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ret = devm_add_action_or_reset(dev, pwm_gpio_disable_hrtimer, gpwm);
if (ret)
return ret;
gpwm->gpio_timer.function = pwm_gpio_timer;
ret = pwmchip_add(chip);
if (ret < 0)
return dev_err_probe(dev, ret, "could not add pwmchip\n");
return 0;
}
static const struct of_device_id pwm_gpio_dt_ids[] = {
{ .compatible = "pwm-gpio" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pwm_gpio_dt_ids);
static struct platform_driver pwm_gpio_driver = {
.driver = {
.name = "pwm-gpio",
.of_match_table = pwm_gpio_dt_ids,
},
.probe = pwm_gpio_probe,
};
module_platform_driver(pwm_gpio_driver);
MODULE_DESCRIPTION("PWM GPIO driver");
MODULE_AUTHOR("Vincent Whitchurch");
MODULE_LICENSE("GPL");

View File

@ -20,6 +20,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
@ -380,6 +381,7 @@ static int pwm_imx_tpm_probe(struct platform_device *pdev)
static int pwm_imx_tpm_suspend(struct device *dev)
{
struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
int ret;
if (tpm->enable_count > 0)
return -EBUSY;
@ -393,7 +395,11 @@ static int pwm_imx_tpm_suspend(struct device *dev)
clk_disable_unprepare(tpm->clk);
return 0;
ret = pinctrl_pm_select_sleep_state(dev);
if (ret)
clk_prepare_enable(tpm->clk);
return ret;
}
static int pwm_imx_tpm_resume(struct device *dev)
@ -401,9 +407,15 @@ static int pwm_imx_tpm_resume(struct device *dev)
struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
int ret = 0;
ret = clk_prepare_enable(tpm->clk);
ret = pinctrl_pm_select_default_state(dev);
if (ret)
return ret;
ret = clk_prepare_enable(tpm->clk);
if (ret) {
dev_err(dev, "failed to prepare or enable clock: %d\n", ret);
pinctrl_pm_select_sleep_state(dev);
}
return ret;
}

View File

@ -194,5 +194,6 @@ static struct platform_driver pwm_imx1_driver = {
};
module_platform_driver(pwm_imx1_driver);
MODULE_DESCRIPTION("i.MX1 and i.MX21 Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");

View File

@ -352,5 +352,6 @@ static struct platform_driver imx_pwm_driver = {
};
module_platform_driver(imx_pwm_driver);
MODULE_DESCRIPTION("i.MX27 and later i.MX SoCs Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");

View File

@ -230,4 +230,5 @@ static struct platform_driver lgm_pwm_driver = {
};
module_platform_driver(lgm_pwm_driver);
MODULE_DESCRIPTION("Intel LGM Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");

View File

@ -201,12 +201,11 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
* state instead of its inactive state.
*/
if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled)
regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
TCU_TCSR_PWM_INITL_HIGH, 0);
regmap_clear_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
TCU_TCSR_PWM_INITL_HIGH);
else
regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
TCU_TCSR_PWM_INITL_HIGH,
TCU_TCSR_PWM_INITL_HIGH);
regmap_set_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
TCU_TCSR_PWM_INITL_HIGH);
if (state->enabled)
jz4740_pwm_enable(chip, pwm);

View File

@ -46,25 +46,6 @@ static void pwm_lpss_remove_pci(struct pci_dev *pdev)
pm_runtime_get_sync(&pdev->dev);
}
static int pwm_lpss_runtime_suspend_pci(struct device *dev)
{
/*
* The PCI core will handle transition to D3 automatically. We only
* need to provide runtime PM hooks for that to happen.
*/
return 0;
}
static int pwm_lpss_runtime_resume_pci(struct device *dev)
{
return 0;
}
static DEFINE_RUNTIME_DEV_PM_OPS(pwm_lpss_pci_pm,
pwm_lpss_runtime_suspend_pci,
pwm_lpss_runtime_resume_pci,
NULL);
static const struct pci_device_id pwm_lpss_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info},
{ PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info},
@ -84,9 +65,6 @@ static struct pci_driver pwm_lpss_driver_pci = {
.id_table = pwm_lpss_pci_ids,
.probe = pwm_lpss_probe_pci,
.remove = pwm_lpss_remove_pci,
.driver = {
.pm = pm_ptr(&pwm_lpss_pci_pm),
},
};
module_pci_driver(pwm_lpss_driver_pci);

View File

@ -55,14 +55,7 @@ static int pwm_lpss_probe_platform(struct platform_device *pdev)
DPM_FLAG_SMART_SUSPEND);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
static void pwm_lpss_remove_platform(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return devm_pm_runtime_enable(&pdev->dev);
}
static const struct acpi_device_id pwm_lpss_acpi_match[] = {
@ -80,7 +73,6 @@ static struct platform_driver pwm_lpss_driver_platform = {
.acpi_match_table = pwm_lpss_acpi_match,
},
.probe = pwm_lpss_probe_platform,
.remove_new = pwm_lpss_remove_platform,
};
module_platform_driver(pwm_lpss_driver_platform);

View File

@ -395,4 +395,5 @@ static struct platform_driver pwm_mediatek_driver = {
module_platform_driver(pwm_mediatek_driver);
MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
MODULE_DESCRIPTION("MediaTek general purpose Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");

View File

@ -460,6 +460,37 @@ static int meson_pwm_init_channels_meson8b_v2(struct pwm_chip *chip)
return meson_pwm_init_clocks_meson8b(chip, mux_parent_data);
}
static void meson_pwm_s4_put_clk(void *data)
{
struct clk *clk = data;
clk_put(clk);
}
static int meson_pwm_init_channels_s4(struct pwm_chip *chip)
{
struct device *dev = pwmchip_parent(chip);
struct device_node *np = dev->of_node;
struct meson_pwm *meson = to_meson_pwm(chip);
int i, ret;
for (i = 0; i < MESON_NUM_PWMS; i++) {
meson->channels[i].clk = of_clk_get(np, i);
if (IS_ERR(meson->channels[i].clk))
return dev_err_probe(dev,
PTR_ERR(meson->channels[i].clk),
"Failed to get clk\n");
ret = devm_add_action_or_reset(dev, meson_pwm_s4_put_clk,
meson->channels[i].clk);
if (ret)
return dev_err_probe(dev, ret,
"Failed to add clk_put action\n");
}
return 0;
}
static const struct meson_pwm_data pwm_meson8b_data = {
.parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" },
.channels_init = meson_pwm_init_channels_meson8b_legacy,
@ -498,6 +529,10 @@ static const struct meson_pwm_data pwm_meson8_v2_data = {
.channels_init = meson_pwm_init_channels_meson8b_v2,
};
static const struct meson_pwm_data pwm_s4_data = {
.channels_init = meson_pwm_init_channels_s4,
};
static const struct of_device_id meson_pwm_matches[] = {
{
.compatible = "amlogic,meson8-pwm-v2",
@ -536,6 +571,10 @@ static const struct of_device_id meson_pwm_matches[] = {
.compatible = "amlogic,meson-g12a-ao-pwm-cd",
.data = &pwm_g12a_ao_cd_data
},
{
.compatible = "amlogic,meson-s4-pwm",
.data = &pwm_s4_data
},
{},
};
MODULE_DEVICE_TABLE(of, meson_pwm_matches);

View File

@ -208,4 +208,5 @@ static struct platform_driver pwm_driver = {
module_platform_driver(pwm_driver);
MODULE_DESCRIPTION("PXA Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");

View File

@ -644,6 +644,7 @@ static struct platform_driver pwm_samsung_driver = {
};
module_platform_driver(pwm_samsung_driver);
MODULE_DESCRIPTION("Samsung Pulse Width Modulator driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>");
MODULE_ALIAS("platform:samsung-pwm");

View File

@ -255,6 +255,7 @@ static struct platform_driver spear_pwm_driver = {
module_platform_driver(spear_pwm_driver);
MODULE_DESCRIPTION("ST Microelectronics SPEAr Pulse Width Modulator driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>");
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");

View File

@ -368,7 +368,7 @@ static int stm32_pwm_config(struct stm32_pwm *priv, unsigned int ch,
dty = mul_u64_u64_div_u64(duty_ns, clk_get_rate(priv->clk),
(u64)NSEC_PER_SEC * (prescaler + 1));
regmap_write(priv->regmap, TIM_CCR1 + 4 * ch, dty);
regmap_write(priv->regmap, TIM_CCRx(ch + 1), dty);
/* Configure output mode */
shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
@ -390,9 +390,9 @@ static int stm32_pwm_set_polarity(struct stm32_pwm *priv, unsigned int ch,
{
u32 mask;
mask = TIM_CCER_CC1P << (ch * 4);
mask = TIM_CCER_CCxP(ch + 1);
if (priv->have_complementary_output)
mask |= TIM_CCER_CC1NP << (ch * 4);
mask |= TIM_CCER_CCxNP(ch + 1);
regmap_update_bits(priv->regmap, TIM_CCER, mask,
polarity == PWM_POLARITY_NORMAL ? 0 : mask);
@ -410,9 +410,9 @@ static int stm32_pwm_enable(struct stm32_pwm *priv, unsigned int ch)
return ret;
/* Enable channel */
mask = TIM_CCER_CC1E << (ch * 4);
mask = TIM_CCER_CCxE(ch + 1);
if (priv->have_complementary_output)
mask |= TIM_CCER_CC1NE << (ch * 4);
mask |= TIM_CCER_CCxNE(ch);
regmap_set_bits(priv->regmap, TIM_CCER, mask);
@ -430,9 +430,9 @@ static void stm32_pwm_disable(struct stm32_pwm *priv, unsigned int ch)
u32 mask;
/* Disable channel */
mask = TIM_CCER_CC1E << (ch * 4);
mask = TIM_CCER_CCxE(ch + 1);
if (priv->have_complementary_output)
mask |= TIM_CCER_CC1NE << (ch * 4);
mask |= TIM_CCER_CCxNE(ch + 1);
regmap_clear_bits(priv->regmap, TIM_CCER, mask);
@ -452,8 +452,9 @@ static int stm32_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
enabled = pwm->state.enabled;
if (enabled && !state->enabled) {
stm32_pwm_disable(priv, pwm->hwpwm);
if (!state->enabled) {
if (enabled)
stm32_pwm_disable(priv, pwm->hwpwm);
return 0;
}
@ -501,8 +502,8 @@ static int stm32_pwm_get_state(struct pwm_chip *chip,
if (ret)
goto out;
state->enabled = ccer & (TIM_CCER_CC1E << (ch * 4));
state->polarity = (ccer & (TIM_CCER_CC1P << (ch * 4))) ?
state->enabled = ccer & TIM_CCER_CCxE(ch + 1);
state->polarity = (ccer & TIM_CCER_CCxP(ch + 1)) ?
PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
ret = regmap_read(priv->regmap, TIM_PSC, &psc);
if (ret)
@ -510,7 +511,7 @@ static int stm32_pwm_get_state(struct pwm_chip *chip,
ret = regmap_read(priv->regmap, TIM_ARR, &arr);
if (ret)
goto out;
ret = regmap_read(priv->regmap, TIM_CCR1 + 4 * ch, &ccr);
ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &ccr);
if (ret)
goto out;
@ -711,7 +712,7 @@ static int stm32_pwm_suspend(struct device *dev)
ccer = active_channels(priv);
for (i = 0; i < chip->npwm; i++) {
mask = TIM_CCER_CC1E << (i * 4);
mask = TIM_CCER_CCxE(i + 1);
if (ccer & mask) {
dev_err(dev, "PWM %u still in use by consumer %s\n",
i, chip->pwms[i].label);

View File

@ -170,6 +170,7 @@ static struct platform_driver visconti_pwm_driver = {
};
module_platform_driver(visconti_pwm_driver);
MODULE_DESCRIPTION("Toshiba Visconti Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>");
MODULE_ALIAS("platform:pwm-visconti");

View File

@ -224,7 +224,6 @@ static int xilinx_pwm_probe(struct platform_device *pdev)
if (IS_ERR(chip))
return PTR_ERR(chip);
priv = xilinx_pwm_chip_to_priv(chip);
platform_set_drvdata(pdev, chip);
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
@ -263,37 +262,24 @@ static int xilinx_pwm_probe(struct platform_device *pdev)
* alas, such properties are not allowed to be used.
*/
priv->clk = devm_clk_get(dev, "s_axi_aclk");
priv->clk = devm_clk_get_enabled(dev, "s_axi_aclk");
if (IS_ERR(priv->clk))
return dev_err_probe(dev, PTR_ERR(priv->clk),
"Could not get clock\n");
ret = clk_prepare_enable(priv->clk);
ret = devm_clk_rate_exclusive_get(dev, priv->clk);
if (ret)
return dev_err_probe(dev, ret, "Clock enable failed\n");
clk_rate_exclusive_get(priv->clk);
return dev_err_probe(dev, ret,
"Failed to lock clock rate\n");
chip->ops = &xilinx_pwm_ops;
ret = pwmchip_add(chip);
if (ret) {
clk_rate_exclusive_put(priv->clk);
clk_disable_unprepare(priv->clk);
ret = devm_pwmchip_add(dev, chip);
if (ret)
return dev_err_probe(dev, ret, "Could not register PWM chip\n");
}
return 0;
}
static void xilinx_pwm_remove(struct platform_device *pdev)
{
struct pwm_chip *chip = platform_get_drvdata(pdev);
struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
pwmchip_remove(chip);
clk_rate_exclusive_put(priv->clk);
clk_disable_unprepare(priv->clk);
}
static const struct of_device_id xilinx_pwm_of_match[] = {
{ .compatible = "xlnx,xps-timer-1.00.a", },
{},
@ -302,7 +288,6 @@ MODULE_DEVICE_TABLE(of, xilinx_pwm_of_match);
static struct platform_driver xilinx_pwm_driver = {
.probe = xilinx_pwm_probe,
.remove_new = xilinx_pwm_remove,
.driver = {
.name = "xilinx-pwm",
.of_match_table = of_match_ptr(xilinx_pwm_of_match),

View File

@ -12,97 +12,106 @@
#include <linux/dma-mapping.h>
#include <linux/regmap.h>
#define TIM_CR1 0x00 /* Control Register 1 */
#define TIM_CR2 0x04 /* Control Register 2 */
#define TIM_SMCR 0x08 /* Slave mode control reg */
#define TIM_DIER 0x0C /* DMA/interrupt register */
#define TIM_SR 0x10 /* Status register */
#define TIM_EGR 0x14 /* Event Generation Reg */
#define TIM_CCMR1 0x18 /* Capt/Comp 1 Mode Reg */
#define TIM_CCMR2 0x1C /* Capt/Comp 2 Mode Reg */
#define TIM_CCER 0x20 /* Capt/Comp Enable Reg */
#define TIM_CNT 0x24 /* Counter */
#define TIM_PSC 0x28 /* Prescaler */
#define TIM_ARR 0x2c /* Auto-Reload Register */
#define TIM_CCR1 0x34 /* Capt/Comp Register 1 */
#define TIM_CCR2 0x38 /* Capt/Comp Register 2 */
#define TIM_CCR3 0x3C /* Capt/Comp Register 3 */
#define TIM_CCR4 0x40 /* Capt/Comp Register 4 */
#define TIM_BDTR 0x44 /* Break and Dead-Time Reg */
#define TIM_DCR 0x48 /* DMA control register */
#define TIM_DMAR 0x4C /* DMA register for transfer */
#define TIM_TISEL 0x68 /* Input Selection */
#define TIM_CR1 0x00 /* Control Register 1 */
#define TIM_CR2 0x04 /* Control Register 2 */
#define TIM_SMCR 0x08 /* Slave mode control reg */
#define TIM_DIER 0x0C /* DMA/interrupt register */
#define TIM_SR 0x10 /* Status register */
#define TIM_EGR 0x14 /* Event Generation Reg */
#define TIM_CCMR1 0x18 /* Capt/Comp 1 Mode Reg */
#define TIM_CCMR2 0x1C /* Capt/Comp 2 Mode Reg */
#define TIM_CCER 0x20 /* Capt/Comp Enable Reg */
#define TIM_CNT 0x24 /* Counter */
#define TIM_PSC 0x28 /* Prescaler */
#define TIM_ARR 0x2c /* Auto-Reload Register */
#define TIM_CCRx(x) (0x34 + 4 * ((x) - 1)) /* Capt/Comp Register x (x ∈ {1, .. 4}) */
#define TIM_CCR1 TIM_CCRx(1) /* Capt/Comp Register 1 */
#define TIM_CCR2 TIM_CCRx(2) /* Capt/Comp Register 2 */
#define TIM_CCR3 TIM_CCRx(3) /* Capt/Comp Register 3 */
#define TIM_CCR4 TIM_CCRx(4) /* Capt/Comp Register 4 */
#define TIM_BDTR 0x44 /* Break and Dead-Time Reg */
#define TIM_DCR 0x48 /* DMA control register */
#define TIM_DMAR 0x4C /* DMA register for transfer */
#define TIM_TISEL 0x68 /* Input Selection */
#define TIM_CR1_CEN BIT(0) /* Counter Enable */
#define TIM_CR1_DIR BIT(4) /* Counter Direction */
#define TIM_CR1_ARPE BIT(7) /* Auto-reload Preload Ena */
#define TIM_CR2_MMS (BIT(4) | BIT(5) | BIT(6)) /* Master mode selection */
#define TIM_CR2_MMS2 GENMASK(23, 20) /* Master mode selection 2 */
#define TIM_SMCR_SMS (BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */
#define TIM_SMCR_TS (BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */
#define TIM_DIER_UIE BIT(0) /* Update interrupt */
#define TIM_DIER_CC1IE BIT(1) /* CC1 Interrupt Enable */
#define TIM_DIER_CC2IE BIT(2) /* CC2 Interrupt Enable */
#define TIM_DIER_CC3IE BIT(3) /* CC3 Interrupt Enable */
#define TIM_DIER_CC4IE BIT(4) /* CC4 Interrupt Enable */
#define TIM_DIER_CC_IE(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt enable */
#define TIM_DIER_UDE BIT(8) /* Update DMA request Enable */
#define TIM_DIER_CC1DE BIT(9) /* CC1 DMA request Enable */
#define TIM_DIER_CC2DE BIT(10) /* CC2 DMA request Enable */
#define TIM_DIER_CC3DE BIT(11) /* CC3 DMA request Enable */
#define TIM_DIER_CC4DE BIT(12) /* CC4 DMA request Enable */
#define TIM_DIER_COMDE BIT(13) /* COM DMA request Enable */
#define TIM_DIER_TDE BIT(14) /* Trigger DMA request Enable */
#define TIM_SR_UIF BIT(0) /* Update interrupt flag */
#define TIM_SR_CC_IF(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt flag */
#define TIM_EGR_UG BIT(0) /* Update Generation */
#define TIM_CCMR_PE BIT(3) /* Channel Preload Enable */
#define TIM_CCMR_M1 (BIT(6) | BIT(5)) /* Channel PWM Mode 1 */
#define TIM_CCMR_CC1S (BIT(0) | BIT(1)) /* Capture/compare 1 sel */
#define TIM_CCMR_IC1PSC GENMASK(3, 2) /* Input capture 1 prescaler */
#define TIM_CCMR_CC2S (BIT(8) | BIT(9)) /* Capture/compare 2 sel */
#define TIM_CCMR_IC2PSC GENMASK(11, 10) /* Input capture 2 prescaler */
#define TIM_CCMR_CC1S_TI1 BIT(0) /* IC1/IC3 selects TI1/TI3 */
#define TIM_CCMR_CC1S_TI2 BIT(1) /* IC1/IC3 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI2 BIT(8) /* IC2/IC4 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI1 BIT(9) /* IC2/IC4 selects TI1/TI3 */
#define TIM_CCMR_CC3S (BIT(0) | BIT(1)) /* Capture/compare 3 sel */
#define TIM_CCMR_CC4S (BIT(8) | BIT(9)) /* Capture/compare 4 sel */
#define TIM_CCMR_CC3S_TI3 BIT(0) /* IC3 selects TI3 */
#define TIM_CCMR_CC4S_TI4 BIT(8) /* IC4 selects TI4 */
#define TIM_CCER_CC1E BIT(0) /* Capt/Comp 1 out Ena */
#define TIM_CCER_CC1P BIT(1) /* Capt/Comp 1 Polarity */
#define TIM_CCER_CC1NE BIT(2) /* Capt/Comp 1N out Ena */
#define TIM_CCER_CC1NP BIT(3) /* Capt/Comp 1N Polarity */
#define TIM_CCER_CC2E BIT(4) /* Capt/Comp 2 out Ena */
#define TIM_CCER_CC2P BIT(5) /* Capt/Comp 2 Polarity */
#define TIM_CCER_CC2NP BIT(7) /* Capt/Comp 2N Polarity */
#define TIM_CCER_CC3E BIT(8) /* Capt/Comp 3 out Ena */
#define TIM_CCER_CC3P BIT(9) /* Capt/Comp 3 Polarity */
#define TIM_CCER_CC3NP BIT(11) /* Capt/Comp 3N Polarity */
#define TIM_CCER_CC4E BIT(12) /* Capt/Comp 4 out Ena */
#define TIM_CCER_CC4P BIT(13) /* Capt/Comp 4 Polarity */
#define TIM_CCER_CC4NP BIT(15) /* Capt/Comp 4N Polarity */
#define TIM_CCER_CCXE (BIT(0) | BIT(4) | BIT(8) | BIT(12))
#define TIM_BDTR_BKE(x) BIT(12 + (x) * 12) /* Break input enable */
#define TIM_BDTR_BKP(x) BIT(13 + (x) * 12) /* Break input polarity */
#define TIM_BDTR_AOE BIT(14) /* Automatic Output Enable */
#define TIM_BDTR_MOE BIT(15) /* Main Output Enable */
#define TIM_BDTR_BKF(x) (0xf << (16 + (x) * 4))
#define TIM_DCR_DBA GENMASK(4, 0) /* DMA base addr */
#define TIM_DCR_DBL GENMASK(12, 8) /* DMA burst len */
#define TIM_CR1_CEN BIT(0) /* Counter Enable */
#define TIM_CR1_DIR BIT(4) /* Counter Direction */
#define TIM_CR1_ARPE BIT(7) /* Auto-reload Preload Ena */
#define TIM_CR2_MMS (BIT(4) | BIT(5) | BIT(6)) /* Master mode selection */
#define TIM_CR2_MMS2 GENMASK(23, 20) /* Master mode selection 2 */
#define TIM_SMCR_SMS (BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */
#define TIM_SMCR_TS (BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */
#define TIM_DIER_UIE BIT(0) /* Update interrupt */
#define TIM_DIER_CCxIE(x) BIT(1 + ((x) - 1)) /* CCx Interrupt Enable (x ∈ {1, .. 4}) */
#define TIM_DIER_CC1IE TIM_DIER_CCxIE(1) /* CC1 Interrupt Enable */
#define TIM_DIER_CC2IE TIM_DIER_CCxIE(2) /* CC2 Interrupt Enable */
#define TIM_DIER_CC3IE TIM_DIER_CCxIE(3) /* CC3 Interrupt Enable */
#define TIM_DIER_CC4IE TIM_DIER_CCxIE(4) /* CC4 Interrupt Enable */
#define TIM_DIER_UDE BIT(8) /* Update DMA request Enable */
#define TIM_DIER_CCxDE(x) BIT(9 + ((x) - 1)) /* CCx DMA request Enable (x ∈ {1, .. 4}) */
#define TIM_DIER_CC1DE TIM_DIER_CCxDE(1) /* CC1 DMA request Enable */
#define TIM_DIER_CC2DE TIM_DIER_CCxDE(2) /* CC2 DMA request Enable */
#define TIM_DIER_CC3DE TIM_DIER_CCxDE(3) /* CC3 DMA request Enable */
#define TIM_DIER_CC4DE TIM_DIER_CCxDE(4) /* CC4 DMA request Enable */
#define TIM_DIER_COMDE BIT(13) /* COM DMA request Enable */
#define TIM_DIER_TDE BIT(14) /* Trigger DMA request Enable */
#define TIM_SR_UIF BIT(0) /* Update interrupt flag */
#define TIM_SR_CC_IF(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt flag */
#define TIM_EGR_UG BIT(0) /* Update Generation */
#define TIM_CCMR_PE BIT(3) /* Channel Preload Enable */
#define TIM_CCMR_M1 (BIT(6) | BIT(5)) /* Channel PWM Mode 1 */
#define TIM_CCMR_CC1S (BIT(0) | BIT(1)) /* Capture/compare 1 sel */
#define TIM_CCMR_IC1PSC GENMASK(3, 2) /* Input capture 1 prescaler */
#define TIM_CCMR_CC2S (BIT(8) | BIT(9)) /* Capture/compare 2 sel */
#define TIM_CCMR_IC2PSC GENMASK(11, 10) /* Input capture 2 prescaler */
#define TIM_CCMR_CC1S_TI1 BIT(0) /* IC1/IC3 selects TI1/TI3 */
#define TIM_CCMR_CC1S_TI2 BIT(1) /* IC1/IC3 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI2 BIT(8) /* IC2/IC4 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI1 BIT(9) /* IC2/IC4 selects TI1/TI3 */
#define TIM_CCMR_CC3S (BIT(0) | BIT(1)) /* Capture/compare 3 sel */
#define TIM_CCMR_CC4S (BIT(8) | BIT(9)) /* Capture/compare 4 sel */
#define TIM_CCMR_CC3S_TI3 BIT(0) /* IC3 selects TI3 */
#define TIM_CCMR_CC4S_TI4 BIT(8) /* IC4 selects TI4 */
#define TIM_CCER_CCxE(x) BIT(0 + 4 * ((x) - 1)) /* Capt/Comp x out Ena (x ∈ {1, .. 4}) */
#define TIM_CCER_CCxP(x) BIT(1 + 4 * ((x) - 1)) /* Capt/Comp x Polarity (x ∈ {1, .. 4}) */
#define TIM_CCER_CCxNE(x) BIT(2 + 4 * ((x) - 1)) /* Capt/Comp xN out Ena (x ∈ {1, .. 4}) */
#define TIM_CCER_CCxNP(x) BIT(3 + 4 * ((x) - 1)) /* Capt/Comp xN Polarity (x ∈ {1, .. 4}) */
#define TIM_CCER_CC1E TIM_CCER_CCxE(1) /* Capt/Comp 1 out Ena */
#define TIM_CCER_CC1P TIM_CCER_CCxP(1) /* Capt/Comp 1 Polarity */
#define TIM_CCER_CC1NE TIM_CCER_CCxNE(1) /* Capt/Comp 1N out Ena */
#define TIM_CCER_CC1NP TIM_CCER_CCxNP(1) /* Capt/Comp 1N Polarity */
#define TIM_CCER_CC2E TIM_CCER_CCxE(2) /* Capt/Comp 2 out Ena */
#define TIM_CCER_CC2P TIM_CCER_CCxP(2) /* Capt/Comp 2 Polarity */
#define TIM_CCER_CC2NE TIM_CCER_CCxNE(2) /* Capt/Comp 2N out Ena */
#define TIM_CCER_CC2NP TIM_CCER_CCxNP(2) /* Capt/Comp 2N Polarity */
#define TIM_CCER_CC3E TIM_CCER_CCxE(3) /* Capt/Comp 3 out Ena */
#define TIM_CCER_CC3P TIM_CCER_CCxP(3) /* Capt/Comp 3 Polarity */
#define TIM_CCER_CC3NE TIM_CCER_CCxNE(3) /* Capt/Comp 3N out Ena */
#define TIM_CCER_CC3NP TIM_CCER_CCxNP(3) /* Capt/Comp 3N Polarity */
#define TIM_CCER_CC4E TIM_CCER_CCxE(4) /* Capt/Comp 4 out Ena */
#define TIM_CCER_CC4P TIM_CCER_CCxP(4) /* Capt/Comp 4 Polarity */
#define TIM_CCER_CC4NE TIM_CCER_CCxNE(4) /* Capt/Comp 4N out Ena */
#define TIM_CCER_CC4NP TIM_CCER_CCxNP(4) /* Capt/Comp 4N Polarity */
#define TIM_CCER_CCXE (BIT(0) | BIT(4) | BIT(8) | BIT(12))
#define TIM_BDTR_BKE(x) BIT(12 + (x) * 12) /* Break input enable */
#define TIM_BDTR_BKP(x) BIT(13 + (x) * 12) /* Break input polarity */
#define TIM_BDTR_AOE BIT(14) /* Automatic Output Enable */
#define TIM_BDTR_MOE BIT(15) /* Main Output Enable */
#define TIM_BDTR_BKF(x) (0xf << (16 + (x) * 4))
#define TIM_DCR_DBA GENMASK(4, 0) /* DMA base addr */
#define TIM_DCR_DBL GENMASK(12, 8) /* DMA burst len */
#define MAX_TIM_PSC 0xFFFF
#define MAX_TIM_ICPSC 0x3
#define TIM_CR2_MMS_SHIFT 4
#define TIM_CR2_MMS2_SHIFT 20
#define MAX_TIM_PSC 0xFFFF
#define MAX_TIM_ICPSC 0x3
#define TIM_CR2_MMS_SHIFT 4
#define TIM_CR2_MMS2_SHIFT 20
#define TIM_SMCR_SMS_SLAVE_MODE_DISABLED 0 /* counts on internal clock when CEN=1 */
#define TIM_SMCR_SMS_ENCODER_MODE_1 1 /* counts TI1FP1 edges, depending on TI2FP2 level */
#define TIM_SMCR_SMS_ENCODER_MODE_2 2 /* counts TI2FP2 edges, depending on TI1FP1 level */
#define TIM_SMCR_SMS_ENCODER_MODE_3 3 /* counts on both TI1FP1 and TI2FP2 edges */
#define TIM_SMCR_TS_SHIFT 4
#define TIM_BDTR_BKF_MASK 0xF
#define TIM_BDTR_BKF_SHIFT(x) (16 + (x) * 4)
#define TIM_SMCR_TS_SHIFT 4
#define TIM_BDTR_BKF_MASK 0xF
#define TIM_BDTR_BKF_SHIFT(x) (16 + (x) * 4)
enum stm32_timers_dmas {
STM32_TIMERS_DMA_CH1,

View File

@ -4,9 +4,12 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
MODULE_IMPORT_NS(PWM);
struct pwm_chip;
/**
@ -249,9 +252,7 @@ struct pwm_capture {
* @free: optional hook for freeing a PWM
* @capture: capture and report PWM signal
* @apply: atomically apply a new PWM config
* @get_state: get the current PWM state. This function is only
* called once per PWM device when the PWM chip is
* registered.
* @get_state: get the current PWM state.
*/
struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
@ -407,10 +408,6 @@ void pwmchip_remove(struct pwm_chip *chip);
int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner);
#define devm_pwmchip_add(dev, chip) __devm_pwmchip_add(dev, chip, THIS_MODULE)
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label);
struct pwm_device *of_pwm_xlate_with_flags(struct pwm_chip *chip,
const struct of_phandle_args *args);
struct pwm_device *of_pwm_single_xlate(struct pwm_chip *chip,
@ -505,14 +502,6 @@ static inline int devm_pwmchip_add(struct device *dev, struct pwm_chip *chip)
return -EINVAL;
}
static inline struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label)
{
might_sleep();
return ERR_PTR(-ENODEV);
}
static inline struct pwm_device *pwm_get(struct device *dev,
const char *consumer)
{
@ -574,13 +563,6 @@ static inline void pwm_apply_args(struct pwm_device *pwm)
pwm_apply_might_sleep(pwm, &state);
}
/* only for backwards-compatibility, new code should not use this */
static inline int pwm_apply_state(struct pwm_device *pwm,
const struct pwm_state *state)
{
return pwm_apply_might_sleep(pwm, state);
}
struct pwm_lookup {
struct list_head list;
const char *provider;