Commit c6e63a98 authored by Linus Torvalds's avatar Linus Torvalds

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()
  ...
parents 500a711d 240b129d
# 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>;
};
......@@ -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
......
# 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;
};
......@@ -68,7 +68,6 @@ required:
- reg
- clocks
- clock-names
- interrupts
additionalProperties: false
......
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;
};
# 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>;
};
......@@ -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"
......
......@@ -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
......
......@@ -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
......
......@@ -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");
......
......@@ -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;
......
......@@ -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
......
......@@ -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
......
......@@ -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,9 +166,25 @@ 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;
......@@ -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 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
* 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 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
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,36 +1459,33 @@ 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);
list_for_each_entry(p, &pwm_lookup_list, list) {
match = 0;
scoped_guard(mutex, &pwm_lookup_lock)
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;
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
match += 1;
}
if (match > best) {
chosen = p;
if (match > best) {
chosen = p;
if (match != 3)
best = match;
else
break;
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);
......@@ -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 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev)
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));
......
// 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");
......@@ -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);
......
// 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");
......@@ -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;
}
......
......@@ -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>");
......@@ -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>");
......@@ -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");
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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");
......@@ -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);
......
......@@ -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");
......@@ -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");
......@@ -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>");
......
......@@ -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);
......
......@@ -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");
......@@ -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),
......
This diff is collapsed.
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment