aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml47
-rw-r--r--Documentation/devicetree/bindings/pwm/atmel-pwm.txt35
-rw-r--r--Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.yaml9
-rw-r--r--Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml75
-rw-r--r--Documentation/devicetree/bindings/pwm/pwm-mediatek.txt1
-rw-r--r--Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt45
-rw-r--r--Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml42
-rw-r--r--Documentation/devicetree/bindings/timer/xlnx,xps-timer.yaml92
-rw-r--r--Documentation/driver-api/pwm.rst6
-rw-r--r--MAINTAINERS14
-rw-r--r--arch/microblaze/kernel/timer.c4
-rw-r--r--drivers/pwm/Kconfig25
-rw-r--r--drivers/pwm/Makefile2
-rw-r--r--drivers/pwm/pwm-atmel-tcb.c14
-rw-r--r--drivers/pwm/pwm-clps711x.c68
-rw-r--r--drivers/pwm/pwm-cros-ec.c82
-rw-r--r--drivers/pwm/pwm-lp3943.c42
-rw-r--r--drivers/pwm/pwm-lpc18xx-sct.c43
-rw-r--r--drivers/pwm/pwm-lpc32xx.c29
-rw-r--r--drivers/pwm/pwm-mediatek.c36
-rw-r--r--drivers/pwm/pwm-raspberrypi-poe.c2
-rw-r--r--drivers/pwm/pwm-renesas-tpu.c317
-rw-r--r--drivers/pwm/pwm-samsung.c54
-rw-r--r--drivers/pwm/pwm-sifive.c5
-rw-r--r--drivers/pwm/pwm-sti.c29
-rw-r--r--drivers/pwm/pwm-stmpe.c29
-rw-r--r--drivers/pwm/pwm-sun4i.c18
-rw-r--r--drivers/pwm/pwm-sunplus.c232
-rw-r--r--drivers/pwm/pwm-tegra.c40
-rw-r--r--drivers/pwm/pwm-twl-led.c76
-rw-r--r--drivers/pwm/pwm-xilinx.c321
-rw-r--r--include/clocksource/timer-xilinx.h73
-rw-r--r--include/dt-bindings/mfd/cros_ec.h18
33 files changed, 1558 insertions, 367 deletions
diff --git a/Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml b/Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml
new file mode 100644
index 000000000000..ab45df80345d
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml
@@ -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>;
+ };
diff --git a/Documentation/devicetree/bindings/pwm/atmel-pwm.txt b/Documentation/devicetree/bindings/pwm/atmel-pwm.txt
deleted file mode 100644
index fbb5325be1f0..000000000000
--- a/Documentation/devicetree/bindings/pwm/atmel-pwm.txt
+++ /dev/null
@@ -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>;
- };
- };
diff --git a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.yaml b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.yaml
index 7ab6912a845f..c8577bdf6c94 100644
--- a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.yaml
@@ -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
diff --git a/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml
new file mode 100644
index 000000000000..e4fe2d1bfef5
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml
@@ -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";
+ };
diff --git a/Documentation/devicetree/bindings/pwm/pwm-mediatek.txt b/Documentation/devicetree/bindings/pwm/pwm-mediatek.txt
index 25ed214473d7..033d1fc0f405 100644
--- a/Documentation/devicetree/bindings/pwm/pwm-mediatek.txt
+++ b/Documentation/devicetree/bindings/pwm/pwm-mediatek.txt
@@ -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.
diff --git a/Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt b/Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt
deleted file mode 100644
index 691e58b6c223..000000000000
--- a/Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt
+++ /dev/null
@@ -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>;
- };
diff --git a/Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml b/Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml
new file mode 100644
index 000000000000..d4fc9e8db1d1
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml
@@ -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>;
+ };
diff --git a/Documentation/devicetree/bindings/timer/xlnx,xps-timer.yaml b/Documentation/devicetree/bindings/timer/xlnx,xps-timer.yaml
new file mode 100644
index 000000000000..dd168d41d2e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/xlnx,xps-timer.yaml
@@ -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>;
+ };
diff --git a/Documentation/driver-api/pwm.rst b/Documentation/driver-api/pwm.rst
index ccb06e485756..fd26c3d895b6 100644
--- a/Documentation/driver-api/pwm.rst
+++ b/Documentation/driver-api/pwm.rst
@@ -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
diff --git a/MAINTAINERS b/MAINTAINERS
index f1b4b77daa5f..3577be9c7089 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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
diff --git a/arch/microblaze/kernel/timer.c b/arch/microblaze/kernel/timer.c
index f8832cf49384..26c385582c3b 100644
--- a/arch/microblaze/kernel/timer.c
+++ b/arch/microblaze/kernel/timer.c
@@ -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;
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 21e3b05a5153..904de8d61828 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -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
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 708840b7fba8..5c08bdb817b4 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -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
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c
index 36f7ea381838..3977a0f9d132 100644
--- a/drivers/pwm/pwm-atmel-tcb.c
+++ b/drivers/pwm/pwm-atmel-tcb.c
@@ -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;
diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c
index d7ad88685830..b0d91142da8d 100644
--- a/drivers/pwm/pwm-clps711x.c
+++ b/drivers/pwm/pwm-clps711x.c
@@ -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);
-
- return 0;
-}
+ if (state->period != pwm->args.period)
+ return -EINVAL;
-static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct clps711x_chip *priv = to_clps711x_chip(chip);
- unsigned int duty;
+ if (state->enabled)
+ val = mul_u64_u64_div_u64(state->duty_cycle, 0xf, state->period);
+ else
+ val = 0;
- duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm));
- clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
+ spin_lock_irqsave(&priv->lock, flags);
- return 0;
-}
+ pmpcon = readl(priv->pmpcon);
+ pmpcon &= ~(0xf << shift);
+ pmpcon |= val << shift;
+ writel(pmpcon, priv->pmpcon);
-static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
-{
- struct clps711x_chip *priv = to_clps711x_chip(chip);
+ spin_unlock_irqrestore(&priv->lock, flags);
- clps711x_pwm_update_val(priv, pwm->hwpwm, 0);
+ return 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,
};
diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c
index 5e29d9c682c3..7f10f56c3eb6 100644
--- a/drivers/pwm/pwm-cros-ec.c
+++ b/drivers/pwm/pwm-cros-ec.c
@@ -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);
diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c
index ea17d446a627..215ef9069114 100644
--- a/drivers/pwm/pwm-lp3943.c
+++ b/drivers/pwm/pwm-lp3943.c
@@ -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,
};
diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
index b909096dba2f..272e0b5d01b8 100644
--- a/drivers/pwm/pwm-lpc18xx-sct.c
+++ b/drivers/pwm/pwm-lpc18xx-sct.c
@@ -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,
diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c
index ddeab5687cb8..86a0ea0f6955 100644
--- a/drivers/pwm/pwm-lpc32xx.c
+++ b/drivers/pwm/pwm-lpc32xx.c
@@ -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,
};
diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c
index 568b13a48717..d28c0874c7f2 100644
--- a/drivers/pwm/pwm-mediatek.c
+++ b/drivers/pwm/pwm-mediatek.c
@@ -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 },
diff --git a/drivers/pwm/pwm-raspberrypi-poe.c b/drivers/pwm/pwm-raspberrypi-poe.c
index e52e29fc8231..6ff73029f367 100644
--- a/drivers/pwm/pwm-raspberrypi-poe.c
+++ b/drivers/pwm/pwm-raspberrypi-poe.c
@@ -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;
diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c
index 4381df90a527..d7311614c846 100644
--- a/drivers/pwm/pwm-renesas-tpu.c
+++ b/drivers/pwm/pwm-renesas-tpu.c
@@ -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;
+ clk_rate = clk_get_rate(tpu->clk);
+ 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;
+ }
+
+ period = mul_u64_u64_div_u64(clk_rate, period_ns, NSEC_PER_SEC);
+
/*
- * Pick a prescaler to avoid overflowing the counter.
- * TODO: Pick the highest acceptable prescaler.
+ * 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.
*/
- clk_rate = clk_get_rate(tpu->clk);
+ switch (period) {
+ case 1 ... 0xffff:
+ prescaler = 0;
+ break;
- for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
- period = clk_rate / prescalers[prescaler]
- / (NSEC_PER_SEC / period_ns);
- if (period <= 0xffff)
- break;
- }
+ case 0x10000 ... 0x3ffff:
+ prescaler = 1;
+ break;
- if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
- dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
- return -ENOTSUPP;
+ 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 = 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);
+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to enable runtime PM\n");
- 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),
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c
index 0a4ff55fad04..9c5b4f515641 100644
--- a/drivers/pwm/pwm-samsung.c
+++ b/drivers/pwm/pwm-samsung.c
@@ -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,
};
diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
index 253c4a17d255..e6d05a329002 100644
--- a/drivers/pwm/pwm-sifive.c
+++ b/drivers/pwm/pwm-sifive.c
@@ -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;
}
diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c
index f491d56254d7..44b1f93256b3 100644
--- a/drivers/pwm/pwm-sti.c
+++ b/drivers/pwm/pwm-sti.c
@@ -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,
};
diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c
index c4336d3bace3..5d4a4762ce0c 100644
--- a/drivers/pwm/pwm-stmpe.c
+++ b/drivers/pwm/pwm-stmpe.c
@@ -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,
};
diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c
index 16d75f9aa36a..c8445b0a3339 100644
--- a/drivers/pwm/pwm-sun4i.c
+++ b/drivers/pwm/pwm-sun4i.c
@@ -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);
diff --git a/drivers/pwm/pwm-sunplus.c b/drivers/pwm/pwm-sunplus.c
new file mode 100644
index 000000000000..e776fd16512d
--- /dev/null
+++ b/drivers/pwm/pwm-sunplus.c
@@ -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");
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index e5a9ffef4a71..dad9978c9186 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -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,
};
diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c
index 49d9f7a78012..ed0b63dd38f1 100644
--- a/drivers/pwm/pwm-twl-led.c
+++ b/drivers/pwm/pwm-twl-led.c
@@ -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,
diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
new file mode 100644
index 000000000000..4dab2b86c427
--- /dev/null
+++ b/drivers/pwm/pwm-xilinx.c
@@ -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");
diff --git a/include/clocksource/timer-xilinx.h b/include/clocksource/timer-xilinx.h
new file mode 100644
index 000000000000..c0f56fe6d22a
--- /dev/null
+++ b/include/clocksource/timer-xilinx.h
@@ -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 */
diff --git a/include/dt-bindings/mfd/cros_ec.h b/include/dt-bindings/mfd/cros_ec.h
new file mode 100644
index 000000000000..3b29cd049578
--- /dev/null
+++ b/include/dt-bindings/mfd/cros_ec.h
@@ -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