pwm: Changes for v5.19-rc1

Quite a large number of conversions this time around, courtesy of Uwe
 who has been working tirelessly on these. No drivers of the legacy API
 are left at this point, so as a next step the old API can be removed.
 
 Support is added for a few new devices such as the Xilinx AXI timer-
 based PWMs and the PWM IP found on Sunplus SoCs.
 
 Other than that, there's a number of fixes, cleanups and optimizations.
 -----BEGIN PGP SIGNATURE-----
 
 iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAmKXeUoZHHRoaWVycnku
 cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zoQ44D/9ZtpQ30tKUQS4o55iiQZBy
 7cfeHK++6oCrcIp57j5TEJaMh+DRz23ga+5/Sp2YeqCCswifrM/Lxm/ys34045oa
 YVHF1Yz9NbyE7d5W+Jzoo3cFBcne6lFgwcN8qVfpc2O35PwmzWSbU3iBlIq0qxbC
 etWbDxBzFbsNsvH6TSNP7xYxfRVct+KYBIl4ySswqryHry2Bl9mTV2P9wS3CkZne
 4y6RWyYeaNBmqLxCOiw98pXr70uJKRAy1qoEUvFBZGzYsb9kwOG9J7RQJxgtwVFK
 YJ+l/c/cq1U3LtQQImcPhx8y+jdRpFK3RlcA6cRBHP3PWuTUsKPKYf2DFEee3bYv
 NhNxkuJhuXhvqpB3Bb5xrnOE34tjF4tLPz74UxaU/DlLqeveyvaqsVuGK7IVbxcU
 YVsceNJHdTJdkKaomB/OkmNrKEf8XNmCmF+x2FPAMKBnJ+E43jKuPk7TTUJrJLn0
 O57VjKmiA3ZsztgWudSOiOvTxmsfRnlHTcPvMO8MbYO9tXVFOBGuzV3RgxKvwpDT
 EWp5VAQp8JMoLvG3fdB4wbZ8isVooYdYYw/jiUG5MA3MGxW+4zmRR/+BglsKpMU7
 AujYBUeInXg1bwezBUW4EieslFVQ/8ctsXj84bbIg3gA5SRSgp/ccH/2ollnAsPU
 Dw6E+STJiQM95NqfUUb8IQ==
 =CwnJ
 -----END PGP SIGNATURE-----

Merge tag 'pwm/for-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm

Pull pwm updates from Thierry Reding:
 "Quite a large number of conversions this time around, courtesy of Uwe
  who has been working tirelessly on these. No drivers of the legacy API
  are left at this point, so as a next step the old API can be removed.

  Support is added for a few new devices such as the Xilinx AXI timer-
  based PWMs and the PWM IP found on Sunplus SoCs.

  Other than that, there's a number of fixes, cleanups and optimizations"

* tag 'pwm/for-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (43 commits)
  pwm: pwm-cros-ec: Add channel type support
  dt-bindings: google,cros-ec-pwm: Add the new -type compatible
  dt-bindings: Add mfd/cros_ec definitions
  pwm: Document that the pinstate of a disabled PWM isn't reliable
  pwm: twl-led: Implement .apply() callback
  pwm: lpc18xx: Implement .apply() callback
  pwm: mediatek: Implement .apply() callback
  pwm: lpc32xx: Implement .apply() callback
  pwm: tegra: Implement .apply() callback
  pwm: stmpe: Implement .apply() callback
  pwm: sti: Implement .apply() callback
  pwm: pwm-mediatek: Add support for MediaTek Helio X10 MT6795
  dt-bindings: pwm: pwm-mediatek: Add documentation for MT6795 SoC
  pwm: tegra: Optimize period calculation
  pwm: renesas-tpu: Improve precision of period and duty_cycle calculation
  pwm: renesas-tpu: Improve maths to compute register settings
  pwm: renesas-tpu: Rename variables to match the usual naming
  pwm: renesas-tpu: Implement .apply() callback
  pwm: renesas-tpu: Make use of devm functions
  pwm: renesas-tpu: Make use of dev_err_probe()
  ...
This commit is contained in:
Linus Torvalds 2022-06-01 10:49:11 -07:00
commit 8eca6b0a64
33 changed files with 1562 additions and 371 deletions

View File

@ -0,0 +1,47 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) 2022 Microchip Technology, Inc. and its subsidiaries
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/atmel,at91sam-pwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Atmel/Microchip PWM controller
maintainers:
- Claudiu Beznea <claudiu.beznea@microchip.com>
allOf:
- $ref: "pwm.yaml#"
properties:
compatible:
oneOf:
- items:
- enum:
- atmel,at91sam9rl-pwm
- atmel,sama5d3-pwm
- atmel,sama5d2-pwm
- microchip,sam9x60-pwm
- items:
- const: microchip,sama7g5-pwm
- const: atmel,sama5d2-pwm
reg:
maxItems: 1
"#pwm-cells":
const: 3
required:
- compatible
- reg
unevaluatedProperties: false
examples:
- |
pwm0: pwm@f8034000 {
compatible = "atmel,at91sam9rl-pwm";
reg = <0xf8034000 0x400>;
#pwm-cells = <3>;
};

View File

@ -1,35 +0,0 @@
Atmel PWM controller
Required properties:
- compatible: should be one of:
- "atmel,at91sam9rl-pwm"
- "atmel,sama5d3-pwm"
- "atmel,sama5d2-pwm"
- "microchip,sam9x60-pwm"
- 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.
Example:
pwm0: pwm@f8034000 {
compatible = "atmel,at91sam9rl-pwm";
reg = <0xf8034000 0x400>;
#pwm-cells = <3>;
};
pwmleds {
compatible = "pwm-leds";
d1 {
label = "d1";
pwms = <&pwm0 3 5000 0>
max-brightness = <255>;
};
d2 {
label = "d2";
pwms = <&pwm0 1 5000 1>
max-brightness = <255>;
};
};

View File

@ -21,7 +21,14 @@ allOf:
properties:
compatible:
const: google,cros-ec-pwm
oneOf:
- description: PWM controlled using EC_PWM_TYPE_GENERIC channels.
items:
- const: google,cros-ec-pwm
- description: PWM controlled using CROS_EC_PWM_DT_<...> types.
items:
- const: google,cros-ec-pwm-type
"#pwm-cells":
description: The cell specifies the PWM index.
const: 1

View File

@ -0,0 +1,75 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/mediatek,pwm-disp.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: MediaTek DISP_PWM Controller Device Tree Bindings
maintainers:
- Jitao Shi <jitao.shi@mediatek.com>
- Xinlei Lee <xinlei.lee@mediatek.com>
allOf:
- $ref: pwm.yaml#
properties:
compatible:
oneOf:
- enum:
- mediatek,mt2701-disp-pwm
- mediatek,mt6595-disp-pwm
- mediatek,mt8173-disp-pwm
- mediatek,mt8183-disp-pwm
- items:
- const: mediatek,mt8167-disp-pwm
- const: mediatek,mt8173-disp-pwm
- items:
- enum:
- mediatek,mt8186-disp-pwm
- mediatek,mt8192-disp-pwm
- mediatek,mt8195-disp-pwm
- const: mediatek,mt8183-disp-pwm
reg:
maxItems: 1
"#pwm-cells":
const: 2
interrupts:
maxItems: 1
clocks:
items:
- description: Main Clock
- description: Mm Clock
clock-names:
items:
- const: main
- const: mm
required:
- compatible
- reg
- "#pwm-cells"
- clocks
- clock-names
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/mt8173-clk.h>
#include <dt-bindings/interrupt-controller/irq.h>
pwm0: pwm@1401e000 {
compatible = "mediatek,mt8173-disp-pwm";
reg = <0x1401e000 0x1000>;
#pwm-cells = <2>;
clocks = <&mmsys CLK_MM_DISP_PWM026M>,
<&mmsys CLK_MM_DISP_PWM0MM>;
clock-names = "main", "mm";
};

View File

@ -3,6 +3,7 @@ MediaTek PWM controller
Required properties:
- compatible: should be "mediatek,<name>-pwm":
- "mediatek,mt2712-pwm": found on mt2712 SoC.
- "mediatek,mt6795-pwm": found on mt6795 SoC.
- "mediatek,mt7622-pwm": found on mt7622 SoC.
- "mediatek,mt7623-pwm": found on mt7623 SoC.
- "mediatek,mt7628-pwm": found on mt7628 SoC.

View File

@ -1,45 +0,0 @@
MediaTek display PWM controller
Required properties:
- compatible: should be "mediatek,<name>-disp-pwm":
- "mediatek,mt2701-disp-pwm": found on mt2701 SoC.
- "mediatek,mt6595-disp-pwm": found on mt6595 SoC.
- "mediatek,mt8167-disp-pwm", "mediatek,mt8173-disp-pwm": found on mt8167 SoC.
- "mediatek,mt8173-disp-pwm": found on mt8173 SoC.
- "mediatek,mt8183-disp-pwm": found on mt8183 SoC.$
- reg: physical base address and length of the controller's registers.
- #pwm-cells: must be 2. See pwm.yaml in this directory for a description of
the cell format.
- clocks: phandle and clock specifier of the PWM reference clock.
- clock-names: must contain the following:
- "main": clock used to generate PWM signals.
- "mm": sync signals from the modules of mmsys.
- pinctrl-names: Must contain a "default" entry.
- pinctrl-0: One property must exist for each entry in pinctrl-names.
See pinctrl/pinctrl-bindings.txt for details of the property values.
Example:
pwm0: pwm@1401e000 {
compatible = "mediatek,mt8173-disp-pwm",
"mediatek,mt6595-disp-pwm";
reg = <0 0x1401e000 0 0x1000>;
#pwm-cells = <2>;
clocks = <&mmsys CLK_MM_DISP_PWM026M>,
<&mmsys CLK_MM_DISP_PWM0MM>;
clock-names = "main", "mm";
pinctrl-names = "default";
pinctrl-0 = <&disp_pwm0_pins>;
};
backlight_lcd: backlight_lcd {
compatible = "pwm-backlight";
pwms = <&pwm0 0 1000000>;
brightness-levels = <
0 16 32 48 64 80 96 112
128 144 160 176 192 208 224 240
255
>;
default-brightness-level = <9>;
power-supply = <&mt6397_vio18_reg>;
enable-gpios = <&pio 95 GPIO_ACTIVE_HIGH>;
};

View File

@ -0,0 +1,42 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) Sunplus Co., Ltd. 2021
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/sunplus,sp7021-pwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Sunplus SoC SP7021 PWM Controller
maintainers:
- Hammer Hsieh <hammerh0314@gmail.com>
allOf:
- $ref: pwm.yaml#
properties:
compatible:
const: sunplus,sp7021-pwm
reg:
maxItems: 1
clocks:
maxItems: 1
'#pwm-cells':
const: 2
unevaluatedProperties: false
required:
- reg
- clocks
examples:
- |
pwm: pwm@9c007a00 {
compatible = "sunplus,sp7021-pwm";
reg = <0x9c007a00 0x80>;
clocks = <&clkc 0xa2>;
#pwm-cells = <2>;
};

View File

@ -0,0 +1,92 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/timer/xlnx,xps-timer.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
maintainers:
- Sean Anderson <sean.anderson@seco.com>
properties:
compatible:
contains:
const: xlnx,xps-timer-1.00.a
clocks:
maxItems: 1
clock-names:
const: s_axi_aclk
interrupts:
maxItems: 1
reg:
maxItems: 1
'#pwm-cells': true
xlnx,count-width:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [8, 16, 32]
default: 32
description:
The width of the counter(s), in bits.
xlnx,one-timer-only:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [ 0, 1 ]
description:
Whether only one timer is present in this block.
required:
- compatible
- reg
- xlnx,one-timer-only
allOf:
- if:
required:
- '#pwm-cells'
then:
allOf:
- required:
- clocks
- properties:
xlnx,one-timer-only:
const: 0
else:
required:
- interrupts
- if:
required:
- clocks
then:
required:
- clock-names
additionalProperties: false
examples:
- |
timer@800e0000 {
clock-names = "s_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,xps-timer-1.00.a";
reg = <0x800e0000 0x10000>;
interrupts = <0 39 2>;
xlnx,count-width = <16>;
xlnx,one-timer-only = <0x0>;
};
timer@800f0000 {
#pwm-cells = <0>;
clock-names = "s_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,xps-timer-1.00.a";
reg = <0x800e0000 0x10000>;
xlnx,count-width = <32>;
xlnx,one-timer-only = <0x0>;
};

View File

@ -49,6 +49,12 @@ After being requested, a PWM has to be configured using::
This API controls both the PWM period/duty_cycle config and the
enable/disable state.
As a consumer, don't rely on the output's state for a disabled PWM. If it's
easily possible, drivers are supposed to emit the inactive state, but some
drivers cannot. If you rely on getting the inactive state, use .duty_cycle=0,
.enabled=true.
There is also a usage_power setting: If set, the PWM driver is only required to
maintain the power output but has more freedom regarding signal form.
If supported by the driver, the signal can be optimized, for example to improve

View File

@ -13062,7 +13062,7 @@ M: Claudiu Beznea <claudiu.beznea@microchip.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
L: linux-pwm@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/pwm/atmel-pwm.txt
F: Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml
F: drivers/pwm/pwm-atmel.c
MICROCHIP SAMA5D2-COMPATIBLE ADC DRIVER
@ -19031,6 +19031,12 @@ S: Maintained
F: Documentation/devicetree/bindings/nvmem/sunplus,sp7021-ocotp.yaml
F: drivers/nvmem/sunplus-ocotp.c
SUNPLUS PWM DRIVER
M: Hammer Hsieh <hammerh0314@gmail.com>
S: Maintained
F: Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml
F: drivers/pwm/pwm-sunplus.c
SUNPLUS RTC DRIVER
M: Vincent Shih <vincent.sunplus@gmail.com>
L: linux-rtc@vger.kernel.org
@ -21820,6 +21826,12 @@ F: drivers/misc/Makefile
F: drivers/misc/xilinx_sdfec.c
F: include/uapi/misc/xilinx_sdfec.h
XILINX PWM DRIVER
M: Sean Anderson <sean.anderson@seco.com>
S: Maintained
F: drivers/pwm/pwm-xilinx.c
F: include/clocksource/timer-xilinx.h
XILINX UARTLITE SERIAL DRIVER
M: Peter Korsgaard <jacmet@sunsite.dk>
L: linux-serial@vger.kernel.org

View File

@ -251,6 +251,10 @@ static int __init xilinx_timer_init(struct device_node *timer)
u32 timer_num = 1;
int ret;
/* If this property is present, the device is a PWM and not a timer */
if (of_property_read_bool(timer, "#pwm-cells"))
return 0;
if (initialized)
return -EINVAL;

View File

@ -572,6 +572,17 @@ config PWM_SUN4I
To compile this driver as a module, choose M here: the module
will be called pwm-sun4i.
config PWM_SUNPLUS
tristate "Sunplus PWM support"
depends on ARCH_SUNPLUS || COMPILE_TEST
depends on HAS_IOMEM && OF
help
Generic PWM framework driver for the PWM controller on
Sunplus SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-sunplus.
config PWM_TEGRA
tristate "NVIDIA Tegra PWM support"
depends on ARCH_TEGRA || COMPILE_TEST
@ -640,4 +651,18 @@ config PWM_VT8500
To compile this driver as a module, choose M here: the module
will be called pwm-vt8500.
config PWM_XILINX
tristate "Xilinx AXI Timer PWM support"
depends on OF_ADDRESS
depends on COMMON_CLK
select REGMAP_MMIO
help
PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
typically a soft core which may be present in Xilinx FPGAs.
This device may also be present in Microblaze soft processors.
If you don't have this IP in your design, choose N.
To compile this driver as a module, choose M here: the module
will be called pwm-xilinx.
endif

View File

@ -53,6 +53,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
@ -60,3 +61,4 @@ obj-$(CONFIG_PWM_TWL) += pwm-twl.o
obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o

View File

@ -61,7 +61,7 @@ struct atmel_tcb_pwm_chip {
struct atmel_tcb_channel bkup;
};
const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128, 0, };
static const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128, 0, };
static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip)
{
@ -72,7 +72,8 @@ static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
tcbpwm->polarity = polarity;
@ -97,7 +98,6 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
return ret;
}
pwm_set_chip_data(pwm, tcbpwm);
tcbpwm->polarity = PWM_POLARITY_NORMAL;
tcbpwm->duty = 0;
tcbpwm->period = 0;
@ -139,7 +139,7 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
clk_disable_unprepare(tcbpwmc->clk);
tcbpwmc->pwms[pwm->hwpwm] = NULL;
@ -149,7 +149,7 @@ static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
unsigned cmr;
enum pwm_polarity polarity = tcbpwm->polarity;
@ -206,7 +206,7 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
u32 cmr;
enum pwm_polarity polarity = tcbpwm->polarity;
@ -291,7 +291,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
struct atmel_tcb_pwm_device *atcbpwm = NULL;
int i = 0;
int slowclk = 0;

View File

@ -23,29 +23,6 @@ static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip)
return container_of(chip, struct clps711x_chip, chip);
}
static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v)
{
/* PWM0 - bits 4..7, PWM1 - bits 8..11 */
u32 shift = (n + 1) * 4;
unsigned long flags;
u32 tmp;
spin_lock_irqsave(&priv->lock, flags);
tmp = readl(priv->pmpcon);
tmp &= ~(0xf << shift);
tmp |= v << shift;
writel(tmp, priv->pmpcon);
spin_unlock_irqrestore(&priv->lock, flags);
}
static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v)
{
/* Duty cycle 0..15 max */
return DIV64_U64_ROUND_CLOSEST(v * 0xf, pwm->args.period);
}
static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
@ -60,44 +37,41 @@ static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
return 0;
}
static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;
/* PWM0 - bits 4..7, PWM1 - bits 8..11 */
u32 shift = (pwm->hwpwm + 1) * 4;
unsigned long flags;
u32 pmpcon, val;
if (period_ns != pwm->args.period)
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
duty = clps711x_get_duty(pwm, duty_ns);
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
if (state->period != pwm->args.period)
return -EINVAL;
if (state->enabled)
val = mul_u64_u64_div_u64(state->duty_cycle, 0xf, state->period);
else
val = 0;
spin_lock_irqsave(&priv->lock, flags);
pmpcon = readl(priv->pmpcon);
pmpcon &= ~(0xf << shift);
pmpcon |= val << shift;
writel(pmpcon, priv->pmpcon);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;
duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm));
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
return 0;
}
static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
clps711x_pwm_update_val(priv, pwm->hwpwm, 0);
}
static const struct pwm_ops clps711x_pwm_ops = {
.request = clps711x_pwm_request,
.config = clps711x_pwm_config,
.enable = clps711x_pwm_enable,
.disable = clps711x_pwm_disable,
.apply = clps711x_pwm_apply,
.owner = THIS_MODULE,
};

View File

@ -12,17 +12,21 @@
#include <linux/pwm.h>
#include <linux/slab.h>
#include <dt-bindings/mfd/cros_ec.h>
/**
* struct cros_ec_pwm_device - Driver data for EC PWM
*
* @dev: Device node
* @ec: Pointer to EC device
* @chip: PWM controller chip
* @use_pwm_type: Use PWM types instead of generic channels
*/
struct cros_ec_pwm_device {
struct device *dev;
struct cros_ec_device *ec;
struct pwm_chip chip;
bool use_pwm_type;
};
/**
@ -58,14 +62,31 @@ static void cros_ec_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
kfree(channel);
}
static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
static int cros_ec_dt_type_to_pwm_type(u8 dt_index, u8 *pwm_type)
{
switch (dt_index) {
case CROS_EC_PWM_DT_KB_LIGHT:
*pwm_type = EC_PWM_TYPE_KB_LIGHT;
return 0;
case CROS_EC_PWM_DT_DISPLAY_LIGHT:
*pwm_type = EC_PWM_TYPE_DISPLAY_LIGHT;
return 0;
default:
return -EINVAL;
}
}
static int cros_ec_pwm_set_duty(struct cros_ec_pwm_device *ec_pwm, u8 index,
u16 duty)
{
struct cros_ec_device *ec = ec_pwm->ec;
struct {
struct cros_ec_command msg;
struct ec_params_pwm_set_duty params;
} __packed buf;
struct ec_params_pwm_set_duty *params = &buf.params;
struct cros_ec_command *msg = &buf.msg;
int ret;
memset(&buf, 0, sizeof(buf));
@ -75,14 +96,25 @@ static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
msg->outsize = sizeof(*params);
params->duty = duty;
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
if (ec_pwm->use_pwm_type) {
ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
if (ret) {
dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
return ret;
}
params->index = 0;
} else {
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
}
return cros_ec_cmd_xfer_status(ec, msg);
}
static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index)
static int cros_ec_pwm_get_duty(struct cros_ec_pwm_device *ec_pwm, u8 index)
{
struct cros_ec_device *ec = ec_pwm->ec;
struct {
struct cros_ec_command msg;
union {
@ -102,8 +134,17 @@ static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index)
msg->insize = sizeof(*resp);
msg->outsize = sizeof(*params);
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
if (ec_pwm->use_pwm_type) {
ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
if (ret) {
dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
return ret;
}
params->index = 0;
} else {
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
}
ret = cros_ec_cmd_xfer_status(ec, msg);
if (ret < 0)
@ -133,7 +174,7 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
*/
duty_cycle = state->enabled ? state->duty_cycle : 0;
ret = cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle);
ret = cros_ec_pwm_set_duty(ec_pwm, pwm->hwpwm, duty_cycle);
if (ret < 0)
return ret;
@ -149,7 +190,7 @@ static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
int ret;
ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm);
ret = cros_ec_pwm_get_duty(ec_pwm, pwm->hwpwm);
if (ret < 0) {
dev_err(chip->dev, "error getting initial duty: %d\n", ret);
return;
@ -204,13 +245,13 @@ static const struct pwm_ops cros_ec_pwm_ops = {
* of PWMs it supports directly, so we have to read the pwm duty cycle for
* subsequent channels until we get an error.
*/
static int cros_ec_num_pwms(struct cros_ec_device *ec)
static int cros_ec_num_pwms(struct cros_ec_pwm_device *ec_pwm)
{
int i, ret;
/* The index field is only 8 bits */
for (i = 0; i <= U8_MAX; i++) {
ret = cros_ec_pwm_get_duty(ec, i);
ret = cros_ec_pwm_get_duty(ec_pwm, i);
/*
* We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM
* responses; everything else is treated as an error.
@ -236,6 +277,7 @@ static int cros_ec_pwm_probe(struct platform_device *pdev)
{
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct cros_ec_pwm_device *ec_pwm;
struct pwm_chip *chip;
int ret;
@ -251,17 +293,26 @@ static int cros_ec_pwm_probe(struct platform_device *pdev)
chip = &ec_pwm->chip;
ec_pwm->ec = ec;
if (of_device_is_compatible(np, "google,cros-ec-pwm-type"))
ec_pwm->use_pwm_type = true;
/* PWM chip */
chip->dev = dev;
chip->ops = &cros_ec_pwm_ops;
chip->of_xlate = cros_ec_pwm_xlate;
chip->of_pwm_n_cells = 1;
ret = cros_ec_num_pwms(ec);
if (ret < 0) {
dev_err(dev, "Couldn't find PWMs: %d\n", ret);
return ret;
if (ec_pwm->use_pwm_type) {
chip->npwm = CROS_EC_PWM_DT_COUNT;
} else {
ret = cros_ec_num_pwms(ec_pwm);
if (ret < 0) {
dev_err(dev, "Couldn't find PWMs: %d\n", ret);
return ret;
}
chip->npwm = ret;
}
chip->npwm = ret;
dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);
ret = pwmchip_add(chip);
@ -288,6 +339,7 @@ static int cros_ec_pwm_remove(struct platform_device *dev)
#ifdef CONFIG_OF
static const struct of_device_id cros_ec_pwm_of_match[] = {
{ .compatible = "google,cros-ec-pwm" },
{ .compatible = "google,cros-ec-pwm-type" },
{},
};
MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);

View File

@ -93,7 +93,7 @@ static void lp3943_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
}
static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
u64 duty_ns, u64 period_ns)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
@ -118,14 +118,20 @@ static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
reg_duty = LP3943_REG_PWM1;
}
period_ns = clamp(period_ns, LP3943_MIN_PERIOD, LP3943_MAX_PERIOD);
val = (u8)(period_ns / LP3943_MIN_PERIOD - 1);
/*
* Note that after this clamping, period_ns fits into an int. This is
* helpful because we can resort to integer division below instead of
* the (more expensive) 64 bit division.
*/
period_ns = clamp(period_ns, (u64)LP3943_MIN_PERIOD, (u64)LP3943_MAX_PERIOD);
val = (u8)((int)period_ns / LP3943_MIN_PERIOD - 1);
err = lp3943_write_byte(lp3943, reg_prescale, val);
if (err)
return err;
val = (u8)(duty_ns * LP3943_MAX_DUTY / period_ns);
duty_ns = min(duty_ns, period_ns);
val = (u8)((int)duty_ns * LP3943_MAX_DUTY / (int)period_ns);
return lp3943_write_byte(lp3943, reg_duty, val);
}
@ -182,12 +188,34 @@ static void lp3943_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
lp3943_pwm_set_mode(lp3943_pwm, pwm_map, LP3943_GPIO_OUT_HIGH);
}
static int lp3943_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
lp3943_pwm_disable(chip, pwm);
return 0;
}
err = lp3943_pwm_config(chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = lp3943_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops lp3943_pwm_ops = {
.request = lp3943_pwm_request,
.free = lp3943_pwm_free,
.config = lp3943_pwm_config,
.enable = lp3943_pwm_enable,
.disable = lp3943_pwm_disable,
.apply = lp3943_pwm_apply,
.owner = THIS_MODULE,
};

View File

@ -226,14 +226,7 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
static int lpc18xx_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
return 0;
}
static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity)
{
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
@ -249,7 +242,7 @@ static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
LPC18XX_PWM_EVSTATEMSK(lpc18xx_data->duty_event),
LPC18XX_PWM_EVSTATEMSK_ALL);
if (pwm_get_polarity(pwm) == PWM_POLARITY_NORMAL) {
if (polarity == PWM_POLARITY_NORMAL) {
set_event = lpc18xx_pwm->period_event;
clear_event = lpc18xx_data->duty_event;
res_action = LPC18XX_PWM_RES_SET;
@ -308,11 +301,35 @@ static void lpc18xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
clear_bit(lpc18xx_data->duty_event, &lpc18xx_pwm->event_map);
}
static int lpc18xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
bool enabled = pwm->state.enabled;
if (state->polarity != pwm->state.polarity && pwm->state.enabled) {
lpc18xx_pwm_disable(chip, pwm);
enabled = false;
}
if (!state->enabled) {
if (enabled)
lpc18xx_pwm_disable(chip, pwm);
return 0;
}
err = lpc18xx_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!enabled)
err = lpc18xx_pwm_enable(chip, pwm, state->polarity);
return err;
}
static const struct pwm_ops lpc18xx_pwm_ops = {
.config = lpc18xx_pwm_config,
.set_polarity = lpc18xx_pwm_set_polarity,
.enable = lpc18xx_pwm_enable,
.disable = lpc18xx_pwm_disable,
.apply = lpc18xx_pwm_apply,
.request = lpc18xx_pwm_request,
.free = lpc18xx_pwm_free,
.owner = THIS_MODULE,

View File

@ -88,10 +88,33 @@ static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable_unprepare(lpc32xx->clk);
}
static int lpc32xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
lpc32xx_pwm_disable(chip, pwm);
return 0;
}
err = lpc32xx_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = lpc32xx_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops lpc32xx_pwm_ops = {
.config = lpc32xx_pwm_config,
.enable = lpc32xx_pwm_enable,
.disable = lpc32xx_pwm_disable,
.apply = lpc32xx_pwm_apply,
.owner = THIS_MODULE,
};

View File

@ -198,10 +198,33 @@ static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm)
pwm_mediatek_clk_disable(chip, pwm);
}
static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
pwm_mediatek_disable(chip, pwm);
return 0;
}
err = pwm_mediatek_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = pwm_mediatek_enable(chip, pwm);
return err;
}
static const struct pwm_ops pwm_mediatek_ops = {
.config = pwm_mediatek_config,
.enable = pwm_mediatek_enable,
.disable = pwm_mediatek_disable,
.apply = pwm_mediatek_apply,
.owner = THIS_MODULE,
};
@ -264,6 +287,12 @@ static const struct pwm_mediatek_of_data mt2712_pwm_data = {
.has_ck_26m_sel = false,
};
static const struct pwm_mediatek_of_data mt6795_pwm_data = {
.num_pwms = 7,
.pwm45_fixup = false,
.has_ck_26m_sel = false,
};
static const struct pwm_mediatek_of_data mt7622_pwm_data = {
.num_pwms = 6,
.pwm45_fixup = false,
@ -302,6 +331,7 @@ static const struct pwm_mediatek_of_data mt8516_pwm_data = {
static const struct of_device_id pwm_mediatek_of_match[] = {
{ .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data },
{ .compatible = "mediatek,mt6795-pwm", .data = &mt6795_pwm_data },
{ .compatible = "mediatek,mt7622-pwm", .data = &mt7622_pwm_data },
{ .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data },
{ .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data },

View File

@ -66,7 +66,7 @@ static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware,
u32 reg, u32 *val)
{
struct raspberrypi_pwm_prop msg = {
.reg = reg
.reg = cpu_to_le32(reg),
};
int ret;

View File

@ -89,71 +89,71 @@ struct tpu_device {
#define to_tpu_device(c) container_of(c, struct tpu_device, chip)
static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value)
static void tpu_pwm_write(struct tpu_pwm_device *tpd, int reg_nr, u16 value)
{
void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET
+ pwm->channel * TPU_CHANNEL_SIZE;
void __iomem *base = tpd->tpu->base + TPU_CHANNEL_OFFSET
+ tpd->channel * TPU_CHANNEL_SIZE;
iowrite16(value, base + reg_nr);
}
static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm,
static void tpu_pwm_set_pin(struct tpu_pwm_device *tpd,
enum tpu_pin_state state)
{
static const char * const states[] = { "inactive", "PWM", "active" };
dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n",
pwm->channel, states[state]);
dev_dbg(&tpd->tpu->pdev->dev, "%u: configuring pin as %s\n",
tpd->channel, states[state]);
switch (state) {
case TPU_PIN_INACTIVE:
tpu_pwm_write(pwm, TPU_TIORn,
pwm->polarity == PWM_POLARITY_INVERSED ?
tpu_pwm_write(tpd, TPU_TIORn,
tpd->polarity == PWM_POLARITY_INVERSED ?
TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0);
break;
case TPU_PIN_PWM:
tpu_pwm_write(pwm, TPU_TIORn,
pwm->polarity == PWM_POLARITY_INVERSED ?
tpu_pwm_write(tpd, TPU_TIORn,
tpd->polarity == PWM_POLARITY_INVERSED ?
TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR);
break;
case TPU_PIN_ACTIVE:
tpu_pwm_write(pwm, TPU_TIORn,
pwm->polarity == PWM_POLARITY_INVERSED ?
tpu_pwm_write(tpd, TPU_TIORn,
tpd->polarity == PWM_POLARITY_INVERSED ?
TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1);
break;
}
}
static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start)
static void tpu_pwm_start_stop(struct tpu_pwm_device *tpd, int start)
{
unsigned long flags;
u16 value;
spin_lock_irqsave(&pwm->tpu->lock, flags);
value = ioread16(pwm->tpu->base + TPU_TSTR);
spin_lock_irqsave(&tpd->tpu->lock, flags);
value = ioread16(tpd->tpu->base + TPU_TSTR);
if (start)
value |= 1 << pwm->channel;
value |= 1 << tpd->channel;
else
value &= ~(1 << pwm->channel);
value &= ~(1 << tpd->channel);
iowrite16(value, pwm->tpu->base + TPU_TSTR);
spin_unlock_irqrestore(&pwm->tpu->lock, flags);
iowrite16(value, tpd->tpu->base + TPU_TSTR);
spin_unlock_irqrestore(&tpd->tpu->lock, flags);
}
static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
static int tpu_pwm_timer_start(struct tpu_pwm_device *tpd)
{
int ret;
if (!pwm->timer_on) {
if (!tpd->timer_on) {
/* Wake up device and enable clock. */
pm_runtime_get_sync(&pwm->tpu->pdev->dev);
ret = clk_prepare_enable(pwm->tpu->clk);
pm_runtime_get_sync(&tpd->tpu->pdev->dev);
ret = clk_prepare_enable(tpd->tpu->clk);
if (ret) {
dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n");
dev_err(&tpd->tpu->pdev->dev, "cannot enable clock\n");
return ret;
}
pwm->timer_on = true;
tpd->timer_on = true;
}
/*
@ -161,8 +161,8 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
* completely. First drive the pin to the inactive state to avoid
* glitches.
*/
tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
tpu_pwm_start_stop(pwm, false);
tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
tpu_pwm_start_stop(tpd, false);
/*
* - Clear TCNT on TGRB match
@ -172,142 +172,168 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
* - Output 1 until TGRA, output 0 until TGRB (active high polarity
* - PWM mode
*/
tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
pwm->prescaler);
tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM);
tpu_pwm_set_pin(pwm, TPU_PIN_PWM);
tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
tpu_pwm_write(pwm, TPU_TGRBn, pwm->period);
tpu_pwm_write(tpd, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
tpd->prescaler);
tpu_pwm_write(tpd, TPU_TMDRn, TPU_TMDR_MD_PWM);
tpu_pwm_set_pin(tpd, TPU_PIN_PWM);
tpu_pwm_write(tpd, TPU_TGRAn, tpd->duty);
tpu_pwm_write(tpd, TPU_TGRBn, tpd->period);
dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
pwm->channel, pwm->duty, pwm->period);
dev_dbg(&tpd->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
tpd->channel, tpd->duty, tpd->period);
/* Start the channel. */
tpu_pwm_start_stop(pwm, true);
tpu_pwm_start_stop(tpd, true);
return 0;
}
static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm)
static void tpu_pwm_timer_stop(struct tpu_pwm_device *tpd)
{
if (!pwm->timer_on)
if (!tpd->timer_on)
return;
/* Disable channel. */
tpu_pwm_start_stop(pwm, false);
tpu_pwm_start_stop(tpd, false);
/* Stop clock and mark device as idle. */
clk_disable_unprepare(pwm->tpu->clk);
pm_runtime_put(&pwm->tpu->pdev->dev);
clk_disable_unprepare(tpd->tpu->clk);
pm_runtime_put(&tpd->tpu->pdev->dev);
pwm->timer_on = false;
tpd->timer_on = false;
}
/* -----------------------------------------------------------------------------
* PWM API
*/
static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm)
static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tpu_device *tpu = to_tpu_device(chip);
struct tpu_pwm_device *pwm;
struct tpu_pwm_device *tpd;
if (_pwm->hwpwm >= TPU_CHANNEL_MAX)
if (pwm->hwpwm >= TPU_CHANNEL_MAX)
return -EINVAL;
pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
if (pwm == NULL)
tpd = kzalloc(sizeof(*tpd), GFP_KERNEL);
if (tpd == NULL)
return -ENOMEM;
pwm->tpu = tpu;
pwm->channel = _pwm->hwpwm;
pwm->polarity = PWM_POLARITY_NORMAL;
pwm->prescaler = 0;
pwm->period = 0;
pwm->duty = 0;
tpd->tpu = tpu;
tpd->channel = pwm->hwpwm;
tpd->polarity = PWM_POLARITY_NORMAL;
tpd->prescaler = 0;
tpd->period = 0;
tpd->duty = 0;
pwm->timer_on = false;
tpd->timer_on = false;
pwm_set_chip_data(_pwm, pwm);
pwm_set_chip_data(pwm, tpd);
return 0;
}
static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm)
static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
tpu_pwm_timer_stop(pwm);
kfree(pwm);
tpu_pwm_timer_stop(tpd);
kfree(tpd);
}
static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
int duty_ns, int period_ns)
static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
u64 duty_ns, u64 period_ns, bool enabled)
{
static const unsigned int prescalers[] = { 1, 4, 16, 64 };
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
struct tpu_device *tpu = to_tpu_device(chip);
unsigned int prescaler;
bool duty_only = false;
u32 clk_rate;
u32 period;
u64 period;
u32 duty;
int ret;
/*
* Pick a prescaler to avoid overflowing the counter.
* TODO: Pick the highest acceptable prescaler.
*/
clk_rate = clk_get_rate(tpu->clk);
for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
period = clk_rate / prescalers[prescaler]
/ (NSEC_PER_SEC / period_ns);
if (period <= 0xffff)
break;
if (unlikely(clk_rate > NSEC_PER_SEC)) {
/*
* This won't happen in the nearer future, so this is only a
* safeguard to prevent the following calculation from
* overflowing. With this clk_rate * period_ns / NSEC_PER_SEC is
* not greater than period_ns and so fits into an u64.
*/
return -EINVAL;
}
if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
return -ENOTSUPP;
period = mul_u64_u64_div_u64(clk_rate, period_ns, NSEC_PER_SEC);
/*
* Find the minimal prescaler in [0..3] such that
*
* period >> (2 * prescaler) < 0x10000
*
* This could be calculated using something like:
*
* prescaler = max(ilog2(period) / 2, 7) - 7;
*
* but given there are only four allowed results and that ilog2 isn't
* cheap on all platforms using a switch statement is more effective.
*/
switch (period) {
case 1 ... 0xffff:
prescaler = 0;
break;
case 0x10000 ... 0x3ffff:
prescaler = 1;
break;
case 0x40000 ... 0xfffff:
prescaler = 2;
break;
case 0x100000 ... 0x3fffff:
prescaler = 3;
break;
default:
return -EINVAL;
}
if (duty_ns) {
duty = clk_rate / prescalers[prescaler]
/ (NSEC_PER_SEC / duty_ns);
if (duty > period)
return -EINVAL;
} else {
period >>= 2 * prescaler;
if (duty_ns)
duty = mul_u64_u64_div_u64(clk_rate, duty_ns,
(u64)NSEC_PER_SEC << (2 * prescaler));
else
duty = 0;
}
dev_dbg(&tpu->pdev->dev,
"rate %u, prescaler %u, period %u, duty %u\n",
clk_rate, prescalers[prescaler], period, duty);
clk_rate, 1 << (2 * prescaler), (u32)period, duty);
if (pwm->prescaler == prescaler && pwm->period == period)
if (tpd->prescaler == prescaler && tpd->period == period)
duty_only = true;
pwm->prescaler = prescaler;
pwm->period = period;
pwm->duty = duty;
tpd->prescaler = prescaler;
tpd->period = period;
tpd->duty = duty;
/* If the channel is disabled we're done. */
if (!pwm_is_enabled(_pwm))
if (!enabled)
return 0;
if (duty_only && pwm->timer_on) {
if (duty_only && tpd->timer_on) {
/*
* If only the duty cycle changed and the timer is already
* running, there's no need to reconfigure it completely, Just
* modify the duty cycle.
*/
tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel,
pwm->duty);
tpu_pwm_write(tpd, TPU_TGRAn, tpd->duty);
dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", tpd->channel,
tpd->duty);
} else {
/* Otherwise perform a full reconfiguration. */
ret = tpu_pwm_timer_start(pwm);
ret = tpu_pwm_timer_start(tpd);
if (ret < 0)
return ret;
}
@ -317,29 +343,29 @@ static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
* To avoid running the timer when not strictly required, handle
* 0% and 100% duty cycles as fixed levels and stop the timer.
*/
tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(pwm);
tpu_pwm_set_pin(tpd, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(tpd);
}
return 0;
}
static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm,
static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
pwm->polarity = polarity;
tpd->polarity = polarity;
return 0;
}
static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
int ret;
ret = tpu_pwm_timer_start(pwm);
ret = tpu_pwm_timer_start(tpd);
if (ret < 0)
return ret;
@ -347,32 +373,64 @@ static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
* To avoid running the timer when not strictly required, handle 0% and
* 100% duty cycles as fixed levels and stop the timer.
*/
if (pwm->duty == 0 || pwm->duty == pwm->period) {
tpu_pwm_set_pin(pwm, pwm->duty ?
if (tpd->duty == 0 || tpd->duty == tpd->period) {
tpu_pwm_set_pin(tpd, tpd->duty ?
TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(pwm);
tpu_pwm_timer_stop(tpd);
}
return 0;
}
static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm)
static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
/* The timer must be running to modify the pin output configuration. */
tpu_pwm_timer_start(pwm);
tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(pwm);
tpu_pwm_timer_start(tpd);
tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(tpd);
}
static int tpu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
bool enabled = pwm->state.enabled;
if (state->polarity != pwm->state.polarity) {
if (enabled) {
tpu_pwm_disable(chip, pwm);
enabled = false;
}
err = tpu_pwm_set_polarity(chip, pwm, state->polarity);
if (err)
return err;
}
if (!state->enabled) {
if (enabled)
tpu_pwm_disable(chip, pwm);
return 0;
}
err = tpu_pwm_config(pwm->chip, pwm,
state->duty_cycle, state->period, enabled);
if (err)
return err;
if (!enabled)
err = tpu_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops tpu_pwm_ops = {
.request = tpu_pwm_request,
.free = tpu_pwm_free,
.config = tpu_pwm_config,
.set_polarity = tpu_pwm_set_polarity,
.enable = tpu_pwm_enable,
.disable = tpu_pwm_disable,
.apply = tpu_pwm_apply,
.owner = THIS_MODULE,
};
@ -398,10 +456,8 @@ static int tpu_probe(struct platform_device *pdev)
return PTR_ERR(tpu->base);
tpu->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(tpu->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return PTR_ERR(tpu->clk);
}
if (IS_ERR(tpu->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(tpu->clk), "Failed to get clock\n");
/* Initialize and register the device. */
platform_set_drvdata(pdev, tpu);
@ -410,25 +466,13 @@ static int tpu_probe(struct platform_device *pdev)
tpu->chip.ops = &tpu_pwm_ops;
tpu->chip.npwm = TPU_CHANNEL_MAX;
pm_runtime_enable(&pdev->dev);
ret = devm_pm_runtime_enable(&pdev->dev);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to enable runtime PM\n");
ret = pwmchip_add(&tpu->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register PWM chip\n");
pm_runtime_disable(&pdev->dev);
return ret;
}
return 0;
}
static int tpu_remove(struct platform_device *pdev)
{
struct tpu_device *tpu = platform_get_drvdata(pdev);
pwmchip_remove(&tpu->chip);
pm_runtime_disable(&pdev->dev);
ret = devm_pwmchip_add(&pdev->dev, &tpu->chip);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to register PWM chip\n");
return 0;
}
@ -447,7 +491,6 @@ MODULE_DEVICE_TABLE(of, tpu_of_table);
static struct platform_driver tpu_driver = {
.probe = tpu_probe,
.remove = tpu_remove,
.driver = {
.name = "renesas-tpu-pwm",
.of_match_table = of_match_ptr(tpu_of_table),

View File

@ -321,14 +321,6 @@ static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
u32 tin_ns = chan->tin_ns, tcnt, tcmp, oldtcmp;
/*
* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits.
*/
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm));
oldtcmp = readl(our_chip->base + REG_TCMPB(pwm->hwpwm));
@ -438,13 +430,51 @@ static int pwm_samsung_set_polarity(struct pwm_chip *chip,
return 0;
}
static int pwm_samsung_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err, enabled = pwm->state.enabled;
if (state->polarity != pwm->state.polarity) {
if (enabled) {
pwm_samsung_disable(chip, pwm);
enabled = false;
}
err = pwm_samsung_set_polarity(chip, pwm, state->polarity);
if (err)
return err;
}
if (!state->enabled) {
if (enabled)
pwm_samsung_disable(chip, pwm);
return 0;
}
/*
* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits.
*/
if (state->period > NSEC_PER_SEC)
return -ERANGE;
err = pwm_samsung_config(chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = pwm_samsung_enable(chip, pwm);
return err;
}
static const struct pwm_ops pwm_samsung_ops = {
.request = pwm_samsung_request,
.free = pwm_samsung_free,
.enable = pwm_samsung_enable,
.disable = pwm_samsung_disable,
.config = pwm_samsung_config,
.set_polarity = pwm_samsung_set_polarity,
.apply = pwm_samsung_apply,
.owner = THIS_MODULE,
};

View File

@ -138,10 +138,9 @@ static int pwm_sifive_enable(struct pwm_chip *chip, bool enable)
dev_err(ddata->chip.dev, "Enable clk failed\n");
return ret;
}
}
if (!enable)
} else {
clk_disable(ddata->clk);
}
return 0;
}

View File

@ -391,11 +391,34 @@ out:
return ret;
}
static int sti_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
sti_pwm_disable(chip, pwm);
return 0;
}
err = sti_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = sti_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops sti_pwm_ops = {
.capture = sti_pwm_capture,
.config = sti_pwm_config,
.enable = sti_pwm_enable,
.disable = sti_pwm_disable,
.apply = sti_pwm_apply,
.free = sti_pwm_free,
.owner = THIS_MODULE,
};

View File

@ -259,10 +259,33 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
static int stmpe_24xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
stmpe_24xx_pwm_disable(chip, pwm);
return 0;
}
err = stmpe_24xx_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = stmpe_24xx_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops stmpe_24xx_pwm_ops = {
.config = stmpe_24xx_pwm_config,
.enable = stmpe_24xx_pwm_enable,
.disable = stmpe_24xx_pwm_disable,
.apply = stmpe_24xx_pwm_apply,
.owner = THIS_MODULE,
};

View File

@ -89,7 +89,6 @@ struct sun4i_pwm_chip {
void __iomem *base;
spinlock_t ctrl_lock;
const struct sun4i_pwm_data *data;
unsigned long next_period[2];
};
static inline struct sun4i_pwm_chip *to_sun4i_pwm_chip(struct pwm_chip *chip)
@ -236,7 +235,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
u32 ctrl, duty = 0, period = 0, val;
int ret;
unsigned int delay_us, prescaler = 0;
unsigned long now;
bool bypass;
pwm_get_state(pwm, &cstate);
@ -284,8 +282,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
val = (duty & PWM_DTY_MASK) | PWM_PRD(period);
sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm));
sun4i_pwm->next_period[pwm->hwpwm] = jiffies +
nsecs_to_jiffies(cstate.period + 1000);
if (state->polarity != PWM_POLARITY_NORMAL)
ctrl &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
@ -305,15 +301,11 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
/* We need a full period to elapse before disabling the channel. */
now = jiffies;
if (time_before(now, sun4i_pwm->next_period[pwm->hwpwm])) {
delay_us = jiffies_to_usecs(sun4i_pwm->next_period[pwm->hwpwm] -
now);
if ((delay_us / 500) > MAX_UDELAY_MS)
msleep(delay_us / 1000 + 1);
else
usleep_range(delay_us, delay_us * 2);
}
delay_us = DIV_ROUND_UP_ULL(cstate.period, NSEC_PER_USEC);
if ((delay_us / 500) > MAX_UDELAY_MS)
msleep(delay_us / 1000 + 1);
else
usleep_range(delay_us, delay_us * 2);
spin_lock(&sun4i_pwm->ctrl_lock);
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);

232
drivers/pwm/pwm-sunplus.c Normal file
View File

@ -0,0 +1,232 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PWM device driver for SUNPLUS SP7021 SoC
*
* Links:
* Reference Manual:
* https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
*
* Reference Manual(PWM module):
* https://sunplus.atlassian.net/wiki/spaces/doc/pages/461144198/12.+Pulse+Width+Modulation+PWM
*
* Limitations:
* - Only supports normal polarity.
* - It output low when PWM channel disabled.
* - When the parameters change, current running period will not be completed
* and run new settings immediately.
* - In .apply() PWM output need to write register FREQ and DUTY. When first write FREQ
* done and not yet write DUTY, it has short timing gap use new FREQ and old DUTY.
*
* Author: Hammer Hsieh <hammerh0314@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define SP7021_PWM_MODE0 0x000
#define SP7021_PWM_MODE0_PWMEN(ch) BIT(ch)
#define SP7021_PWM_MODE0_BYPASS(ch) BIT(8 + (ch))
#define SP7021_PWM_MODE1 0x004
#define SP7021_PWM_MODE1_CNT_EN(ch) BIT(ch)
#define SP7021_PWM_FREQ(ch) (0x008 + 4 * (ch))
#define SP7021_PWM_FREQ_MAX GENMASK(15, 0)
#define SP7021_PWM_DUTY(ch) (0x018 + 4 * (ch))
#define SP7021_PWM_DUTY_DD_SEL(ch) FIELD_PREP(GENMASK(9, 8), ch)
#define SP7021_PWM_DUTY_MAX GENMASK(7, 0)
#define SP7021_PWM_DUTY_MASK SP7021_PWM_DUTY_MAX
#define SP7021_PWM_FREQ_SCALER 256
#define SP7021_PWM_NUM 4
struct sunplus_pwm {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct sunplus_pwm, chip);
}
static int sunplus_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct sunplus_pwm *priv = to_sunplus_pwm(chip);
u32 dd_freq, duty, mode0, mode1;
u64 clk_rate;
if (state->polarity != pwm->state.polarity)
return -EINVAL;
if (!state->enabled) {
/* disable pwm channel output */
mode0 = readl(priv->base + SP7021_PWM_MODE0);
mode0 &= ~SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
writel(mode0, priv->base + SP7021_PWM_MODE0);
/* disable pwm channel clk source */
mode1 = readl(priv->base + SP7021_PWM_MODE1);
mode1 &= ~SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
writel(mode1, priv->base + SP7021_PWM_MODE1);
return 0;
}
clk_rate = clk_get_rate(priv->clk);
/*
* The following calculations might overflow if clk is bigger
* than 256 GHz. In practise it's 202.5MHz, so this limitation
* is only theoretic.
*/
if (clk_rate > (u64)SP7021_PWM_FREQ_SCALER * NSEC_PER_SEC)
return -EINVAL;
/*
* With clk_rate limited above we have dd_freq <= state->period,
* so this cannot overflow.
*/
dd_freq = mul_u64_u64_div_u64(clk_rate, state->period, (u64)SP7021_PWM_FREQ_SCALER
* NSEC_PER_SEC);
if (dd_freq == 0)
return -EINVAL;
if (dd_freq > SP7021_PWM_FREQ_MAX)
dd_freq = SP7021_PWM_FREQ_MAX;
writel(dd_freq, priv->base + SP7021_PWM_FREQ(pwm->hwpwm));
/* cal and set pwm duty */
mode0 = readl(priv->base + SP7021_PWM_MODE0);
mode0 |= SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
mode1 = readl(priv->base + SP7021_PWM_MODE1);
mode1 |= SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
if (state->duty_cycle == state->period) {
/* PWM channel output = high */
mode0 |= SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | SP7021_PWM_DUTY_MAX;
} else {
mode0 &= ~SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
/*
* duty_ns <= period_ns 27 bits, clk_rate 28 bits, won't overflow.
*/
duty = mul_u64_u64_div_u64(state->duty_cycle, clk_rate,
(u64)dd_freq * NSEC_PER_SEC);
duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | duty;
}
writel(duty, priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
writel(mode1, priv->base + SP7021_PWM_MODE1);
writel(mode0, priv->base + SP7021_PWM_MODE0);
return 0;
}
static void sunplus_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct sunplus_pwm *priv = to_sunplus_pwm(chip);
u32 mode0, dd_freq, duty;
u64 clk_rate;
mode0 = readl(priv->base + SP7021_PWM_MODE0);
if (mode0 & BIT(pwm->hwpwm)) {
clk_rate = clk_get_rate(priv->clk);
dd_freq = readl(priv->base + SP7021_PWM_FREQ(pwm->hwpwm));
duty = readl(priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
duty = FIELD_GET(SP7021_PWM_DUTY_MASK, duty);
/*
* dd_freq 16 bits, SP7021_PWM_FREQ_SCALER 8 bits
* NSEC_PER_SEC 30 bits, won't overflow.
*/
state->period = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)SP7021_PWM_FREQ_SCALER
* NSEC_PER_SEC, clk_rate);
/*
* dd_freq 16 bits, duty 8 bits, NSEC_PER_SEC 30 bits, won't overflow.
*/
state->duty_cycle = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)duty * NSEC_PER_SEC,
clk_rate);
state->enabled = true;
} else {
state->enabled = false;
}
state->polarity = PWM_POLARITY_NORMAL;
}
static const struct pwm_ops sunplus_pwm_ops = {
.apply = sunplus_pwm_apply,
.get_state = sunplus_pwm_get_state,
.owner = THIS_MODULE,
};
static void sunplus_pwm_clk_release(void *data)
{
struct clk *clk = data;
clk_disable_unprepare(clk);
}
static int sunplus_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sunplus_pwm *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
priv->clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->clk))
return dev_err_probe(dev, PTR_ERR(priv->clk),
"get pwm clock failed\n");
ret = clk_prepare_enable(priv->clk);
if (ret < 0) {
dev_err(dev, "failed to enable clock: %d\n", ret);
return ret;
}
ret = devm_add_action_or_reset(dev, sunplus_pwm_clk_release, priv->clk);
if (ret < 0) {
dev_err(dev, "failed to release clock: %d\n", ret);
return ret;
}
priv->chip.dev = dev;
priv->chip.ops = &sunplus_pwm_ops;
priv->chip.npwm = SP7021_PWM_NUM;
ret = devm_pwmchip_add(dev, &priv->chip);
if (ret < 0)
return dev_err_probe(dev, ret, "Cannot register sunplus PWM\n");
return 0;
}
static const struct of_device_id sunplus_pwm_of_match[] = {
{ .compatible = "sunplus,sp7021-pwm", },
{}
};
MODULE_DEVICE_TABLE(of, sunplus_pwm_of_match);
static struct platform_driver sunplus_pwm_driver = {
.probe = sunplus_pwm_probe,
.driver = {
.name = "sunplus-pwm",
.of_match_table = sunplus_pwm_of_match,
},
};
module_platform_driver(sunplus_pwm_driver);
MODULE_DESCRIPTION("Sunplus SoC PWM Driver");
MODULE_AUTHOR("Hammer Hsieh <hammerh0314@gmail.com>");
MODULE_LICENSE("GPL");

View File

@ -99,7 +99,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
unsigned long long c = duty_ns, hz;
unsigned long long c = duty_ns;
unsigned long rate, required_clk_rate;
u32 val = 0;
int err;
@ -156,11 +156,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
pc->clk_rate = clk_get_rate(pc->clk);
}
rate = pc->clk_rate >> PWM_DUTY_WIDTH;
/* Consider precision in PWM_SCALE_WIDTH rate calculation */
hz = DIV_ROUND_CLOSEST_ULL(100ULL * NSEC_PER_SEC, period_ns);
rate = DIV_ROUND_CLOSEST_ULL(100ULL * rate, hz);
rate = mul_u64_u64_div_u64(pc->clk_rate, period_ns,
(u64)NSEC_PER_SEC << PWM_DUTY_WIDTH);
/*
* Since the actual PWM divider is the register's frequency divider
@ -169,6 +167,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
if (rate > 0)
rate--;
else
return -EINVAL;
/*
* Make sure that the rate will fit in the register's frequency
@ -230,10 +230,34 @@ static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
pm_runtime_put_sync(pc->dev);
}
static int tegra_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
bool enabled = pwm->state.enabled;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (enabled)
tegra_pwm_disable(chip, pwm);
return 0;
}
err = tegra_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!enabled)
err = tegra_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops tegra_pwm_ops = {
.config = tegra_pwm_config,
.enable = tegra_pwm_enable,
.disable = tegra_pwm_disable,
.apply = tegra_pwm_apply,
.owner = THIS_MODULE,
};

View File

@ -137,6 +137,45 @@ out:
mutex_unlock(&twl->mutex);
}
static int twl4030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int ret;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
twl4030_pwmled_disable(chip, pwm);
return 0;
}
/*
* We cannot skip calling ->config even if state->period ==
* pwm->state.period && state->duty_cycle == pwm->state.duty_cycle
* because we might have exited early in the last call to
* pwm_apply_state because of !state->enabled and so the two values in
* pwm->state might not be configured in hardware.
*/
ret = twl4030_pwmled_config(pwm->chip, pwm,
state->duty_cycle, state->period);
if (ret)
return ret;
if (!pwm->state.enabled)
ret = twl4030_pwmled_enable(chip, pwm);
return ret;
}
static const struct pwm_ops twl4030_pwmled_ops = {
.apply = twl4030_pwmled_apply,
.owner = THIS_MODULE,
};
static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
@ -206,6 +245,32 @@ out:
mutex_unlock(&twl->mutex);
}
static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != pwm->state.polarity)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
twl6030_pwmled_disable(chip, pwm);
return 0;
}
err = twl6030_pwmled_config(pwm->chip, pwm,
state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = twl6030_pwmled_enable(chip, pwm);
return err;
}
static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
@ -257,17 +322,8 @@ out:
mutex_unlock(&twl->mutex);
}
static const struct pwm_ops twl4030_pwmled_ops = {
.enable = twl4030_pwmled_enable,
.disable = twl4030_pwmled_disable,
.config = twl4030_pwmled_config,
.owner = THIS_MODULE,
};
static const struct pwm_ops twl6030_pwmled_ops = {
.enable = twl6030_pwmled_enable,
.disable = twl6030_pwmled_disable,
.config = twl6030_pwmled_config,
.apply = twl6030_pwmled_apply,
.request = twl6030_pwmled_request,
.free = twl6030_pwmled_free,
.owner = THIS_MODULE,

321
drivers/pwm/pwm-xilinx.c Normal file
View File

@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
*
* Limitations:
* - When changing both duty cycle and period, we may end up with one cycle
* with the old duty cycle and the new period. This is because the counters
* may only be reloaded by first stopping them, or by letting them be
* automatically reloaded at the end of a cycle. If this automatic reload
* happens after we set TLR0 but before we set TLR1 then we will have a
* bad cycle. This could probably be fixed by reading TCR0 just before
* reprogramming, but I think it would add complexity for little gain.
* - Cannot produce 100% duty cycle by configuring the TLRs. This might be
* possible by stopping the counters at an appropriate point in the cycle,
* but this is not (yet) implemented.
* - Only produces "normal" output.
* - Always produces low output if disabled.
*/
#include <clocksource/timer-xilinx.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
/*
* The following functions are "common" to drivers for this device, and may be
* exported at a future date.
*/
u32 xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 tcsr,
u64 cycles)
{
WARN_ON(cycles < 2 || cycles - 2 > priv->max);
if (tcsr & TCSR_UDT)
return cycles - 2;
return priv->max - cycles + 2;
}
unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
u32 tlr, u32 tcsr)
{
u64 cycles;
if (tcsr & TCSR_UDT)
cycles = tlr + 2;
else
cycles = (u64)priv->max - tlr + 2;
/* cycles has a max of 2^32 + 2, so we can't overflow */
return DIV64_U64_ROUND_UP(cycles * NSEC_PER_SEC,
clk_get_rate(priv->clk));
}
/*
* The idea here is to capture whether the PWM is actually running (e.g.
* because we or the bootloader set it up) and we need to be careful to ensure
* we don't cause a glitch. According to the data sheet, to enable the PWM we
* need to
*
* - Set both timers to generate mode (MDT=1)
* - Set both timers to PWM mode (PWMA=1)
* - Enable the generate out signals (GENT=1)
*
* In addition,
*
* - The timer must be running (ENT=1)
* - The timer must auto-reload TLR into TCR (ARHT=1)
* - We must not be in the process of loading TLR into TCR (LOAD=0)
* - Cascade mode must be disabled (CASC=0)
*
* If any of these differ from usual, then the PWM is either disabled, or is
* running in a mode that this driver does not support.
*/
#define TCSR_PWM_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA)
#define TCSR_PWM_CLEAR (TCSR_MDT | TCSR_LOAD)
#define TCSR_PWM_MASK (TCSR_PWM_SET | TCSR_PWM_CLEAR)
struct xilinx_pwm_device {
struct pwm_chip chip;
struct xilinx_timer_priv priv;
};
static inline struct xilinx_timer_priv
*xilinx_pwm_chip_to_priv(struct pwm_chip *chip)
{
return &container_of(chip, struct xilinx_pwm_device, chip)->priv;
}
static bool xilinx_timer_pwm_enabled(u32 tcsr0, u32 tcsr1)
{
return ((TCSR_PWM_MASK | TCSR_CASC) & tcsr0) == TCSR_PWM_SET &&
(TCSR_PWM_MASK & tcsr1) == TCSR_PWM_SET;
}
static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
const struct pwm_state *state)
{
struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
u32 tlr0, tlr1, tcsr0, tcsr1;
u64 period_cycles, duty_cycles;
unsigned long rate;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
/*
* To be representable by TLR, cycles must be between 2 and
* priv->max + 2. To enforce this we can reduce the cycles, but we may
* not increase them. Caveat emptor: while this does result in more
* predictable rounding, it may also result in a completely different
* duty cycle (% high time) than what was requested.
*/
rate = clk_get_rate(priv->clk);
/* Avoid overflow */
period_cycles = min_t(u64, state->period, U32_MAX * NSEC_PER_SEC);
period_cycles = mul_u64_u32_div(period_cycles, rate, NSEC_PER_SEC);
period_cycles = min_t(u64, period_cycles, priv->max + 2);
if (period_cycles < 2)
return -ERANGE;
/* Same thing for duty cycles */
duty_cycles = min_t(u64, state->duty_cycle, U32_MAX * NSEC_PER_SEC);
duty_cycles = mul_u64_u32_div(duty_cycles, rate, NSEC_PER_SEC);
duty_cycles = min_t(u64, duty_cycles, priv->max + 2);
/*
* If we specify 100% duty cycle, we will get 0% instead, so decrease
* the duty cycle count by one.
*/
if (duty_cycles >= period_cycles)
duty_cycles = period_cycles - 1;
/* Round down to 0% duty cycle for unrepresentable duty cycles */
if (duty_cycles < 2)
duty_cycles = period_cycles;
regmap_read(priv->map, TCSR0, &tcsr0);
regmap_read(priv->map, TCSR1, &tcsr1);
tlr0 = xilinx_timer_tlr_cycles(priv, tcsr0, period_cycles);
tlr1 = xilinx_timer_tlr_cycles(priv, tcsr1, duty_cycles);
regmap_write(priv->map, TLR0, tlr0);
regmap_write(priv->map, TLR1, tlr1);
if (state->enabled) {
/*
* If the PWM is already running, then the counters will be
* reloaded at the end of the current cycle.
*/
if (!xilinx_timer_pwm_enabled(tcsr0, tcsr1)) {
/* Load TLR into TCR */
regmap_write(priv->map, TCSR0, tcsr0 | TCSR_LOAD);
regmap_write(priv->map, TCSR1, tcsr1 | TCSR_LOAD);
/* Enable timers all at once with ENALL */
tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
regmap_write(priv->map, TCSR0, tcsr0);
regmap_write(priv->map, TCSR1, tcsr1);
}
} else {
regmap_write(priv->map, TCSR0, 0);
regmap_write(priv->map, TCSR1, 0);
}
return 0;
}
static void xilinx_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *unused,
struct pwm_state *state)
{
struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
u32 tlr0, tlr1, tcsr0, tcsr1;
regmap_read(priv->map, TLR0, &tlr0);
regmap_read(priv->map, TLR1, &tlr1);
regmap_read(priv->map, TCSR0, &tcsr0);
regmap_read(priv->map, TCSR1, &tcsr1);
state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
state->polarity = PWM_POLARITY_NORMAL;
/*
* 100% duty cycle results in constant low output. This may be (very)
* wrong if rate > 1 GHz, so fix this if you have such hardware :)
*/
if (state->period == state->duty_cycle)
state->duty_cycle = 0;
}
static const struct pwm_ops xilinx_pwm_ops = {
.apply = xilinx_pwm_apply,
.get_state = xilinx_pwm_get_state,
.owner = THIS_MODULE,
};
static const struct regmap_config xilinx_pwm_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.max_register = TCR1,
};
static int xilinx_pwm_probe(struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct xilinx_timer_priv *priv;
struct xilinx_pwm_device *xilinx_pwm;
u32 pwm_cells, one_timer, width;
void __iomem *regs;
/* If there are no PWM cells, this binding is for a timer */
ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
if (ret == -EINVAL)
return -ENODEV;
if (ret)
return dev_err_probe(dev, ret, "could not read #pwm-cells\n");
xilinx_pwm = devm_kzalloc(dev, sizeof(*xilinx_pwm), GFP_KERNEL);
if (!xilinx_pwm)
return -ENOMEM;
platform_set_drvdata(pdev, xilinx_pwm);
priv = &xilinx_pwm->priv;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
priv->map = devm_regmap_init_mmio(dev, regs,
&xilinx_pwm_regmap_config);
if (IS_ERR(priv->map))
return dev_err_probe(dev, PTR_ERR(priv->map),
"Could not create regmap\n");
ret = of_property_read_u32(np, "xlnx,one-timer-only", &one_timer);
if (ret)
return dev_err_probe(dev, ret,
"Could not read xlnx,one-timer-only\n");
if (one_timer)
return dev_err_probe(dev, -EINVAL,
"Two timers required for PWM mode\n");
ret = of_property_read_u32(np, "xlnx,count-width", &width);
if (ret == -EINVAL)
width = 32;
else if (ret)
return dev_err_probe(dev, ret,
"Could not read xlnx,count-width\n");
if (width != 8 && width != 16 && width != 32)
return dev_err_probe(dev, -EINVAL,
"Invalid counter width %d\n", width);
priv->max = BIT_ULL(width) - 1;
/*
* The polarity of the Generate Out signals must be active high for PWM
* mode to work. We could determine this from the device tree, but
* alas, such properties are not allowed to be used.
*/
priv->clk = devm_clk_get(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);
if (ret)
return dev_err_probe(dev, ret, "Clock enable failed\n");
clk_rate_exclusive_get(priv->clk);
xilinx_pwm->chip.dev = dev;
xilinx_pwm->chip.ops = &xilinx_pwm_ops;
xilinx_pwm->chip.npwm = 1;
ret = pwmchip_add(&xilinx_pwm->chip);
if (ret) {
clk_rate_exclusive_put(priv->clk);
clk_disable_unprepare(priv->clk);
return dev_err_probe(dev, ret, "Could not register PWM chip\n");
}
return 0;
}
static int xilinx_pwm_remove(struct platform_device *pdev)
{
struct xilinx_pwm_device *xilinx_pwm = platform_get_drvdata(pdev);
pwmchip_remove(&xilinx_pwm->chip);
clk_rate_exclusive_put(xilinx_pwm->priv.clk);
clk_disable_unprepare(xilinx_pwm->priv.clk);
return 0;
}
static const struct of_device_id xilinx_pwm_of_match[] = {
{ .compatible = "xlnx,xps-timer-1.00.a", },
{},
};
MODULE_DEVICE_TABLE(of, xilinx_pwm_of_match);
static struct platform_driver xilinx_pwm_driver = {
.probe = xilinx_pwm_probe,
.remove = xilinx_pwm_remove,
.driver = {
.name = "xilinx-pwm",
.of_match_table = of_match_ptr(xilinx_pwm_of_match),
},
};
module_platform_driver(xilinx_pwm_driver);
MODULE_ALIAS("platform:xilinx-pwm");
MODULE_DESCRIPTION("PWM driver for Xilinx LogiCORE IP AXI Timer");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
*/
#ifndef XILINX_TIMER_H
#define XILINX_TIMER_H
#include <linux/compiler.h>
#define TCSR0 0x00
#define TLR0 0x04
#define TCR0 0x08
#define TCSR1 0x10
#define TLR1 0x14
#define TCR1 0x18
#define TCSR_MDT BIT(0)
#define TCSR_UDT BIT(1)
#define TCSR_GENT BIT(2)
#define TCSR_CAPT BIT(3)
#define TCSR_ARHT BIT(4)
#define TCSR_LOAD BIT(5)
#define TCSR_ENIT BIT(6)
#define TCSR_ENT BIT(7)
#define TCSR_TINT BIT(8)
#define TCSR_PWMA BIT(9)
#define TCSR_ENALL BIT(10)
#define TCSR_CASC BIT(11)
struct clk;
struct device_node;
struct regmap;
/**
* struct xilinx_timer_priv - Private data for Xilinx AXI timer drivers
* @map: Regmap of the device, possibly with an offset
* @clk: Parent clock
* @max: Maximum value of the counters
*/
struct xilinx_timer_priv {
struct regmap *map;
struct clk *clk;
u32 max;
};
/**
* xilinx_timer_tlr_cycles() - Calculate the TLR for a period specified
* in clock cycles
* @priv: The timer's private data
* @tcsr: The value of the TCSR register for this counter
* @cycles: The number of cycles in this period
*
* Callers of this function MUST ensure that @cycles is representable as
* a TLR.
*
* Return: The calculated value for TLR
*/
u32 xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 tcsr,
u64 cycles);
/**
* xilinx_timer_get_period() - Get the current period of a counter
* @priv: The timer's private data
* @tlr: The value of TLR for this counter
* @tcsr: The value of TCSR for this counter
*
* Return: The period, in ns
*/
unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
u32 tlr, u32 tcsr);
#endif /* XILINX_TIMER_H */

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* DTS binding definitions used for the Chromium OS Embedded Controller.
*
* Copyright (c) 2022 The Chromium OS Authors. All rights reserved.
*/
#ifndef _DT_BINDINGS_MFD_CROS_EC_H
#define _DT_BINDINGS_MFD_CROS_EC_H
/* Typed channel for keyboard backlight. */
#define CROS_EC_PWM_DT_KB_LIGHT 0
/* Typed channel for display backlight. */
#define CROS_EC_PWM_DT_DISPLAY_LIGHT 1
/* Number of typed channels. */
#define CROS_EC_PWM_DT_COUNT 2
#endif