]> git.ipfire.org Git - people/ms/linux.git/commitdiff
Merge tag 'pwm/for-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 1 Jun 2022 17:49:11 +0000 (10:49 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 1 Jun 2022 17:49:11 +0000 (10:49 -0700)
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()
  ...

33 files changed:
Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/atmel-pwm.txt [deleted file]
Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.yaml
Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/pwm/pwm-mediatek.txt
Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt [deleted file]
Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/timer/xlnx,xps-timer.yaml [new file with mode: 0644]
Documentation/driver-api/pwm.rst
MAINTAINERS
arch/microblaze/kernel/timer.c
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/pwm-atmel-tcb.c
drivers/pwm/pwm-clps711x.c
drivers/pwm/pwm-cros-ec.c
drivers/pwm/pwm-lp3943.c
drivers/pwm/pwm-lpc18xx-sct.c
drivers/pwm/pwm-lpc32xx.c
drivers/pwm/pwm-mediatek.c
drivers/pwm/pwm-raspberrypi-poe.c
drivers/pwm/pwm-renesas-tpu.c
drivers/pwm/pwm-samsung.c
drivers/pwm/pwm-sifive.c
drivers/pwm/pwm-sti.c
drivers/pwm/pwm-stmpe.c
drivers/pwm/pwm-sun4i.c
drivers/pwm/pwm-sunplus.c [new file with mode: 0644]
drivers/pwm/pwm-tegra.c
drivers/pwm/pwm-twl-led.c
drivers/pwm/pwm-xilinx.c [new file with mode: 0644]
include/clocksource/timer-xilinx.h [new file with mode: 0644]
include/dt-bindings/mfd/cros_ec.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml b/Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml
new file mode 100644 (file)
index 0000000..ab45df8
--- /dev/null
@@ -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 (file)
index fbb5325..0000000
+++ /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>;
-               };
-       };
index 7ab6912a845fcbf8a4fdefcde7fa96320d2761f8..c8577bdf6c946fb9db852c72e0d386f3b4a30099 100644 (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
diff --git a/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml b/Documentation/devicetree/bindings/pwm/mediatek,pwm-disp.yaml
new file mode 100644 (file)
index 0000000..e4fe2d1
--- /dev/null
@@ -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";
+    };
index 25ed214473d7aed75a10914456f72af3d4eca2a9..033d1fc0f405618ccb5d585e0816bf2f8857b750 100644 (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.
diff --git a/Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt b/Documentation/devicetree/bindings/pwm/pwm-mtk-disp.txt
deleted file mode 100644 (file)
index 691e58b..0000000
+++ /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 (file)
index 0000000..d4fc9e8
--- /dev/null
@@ -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 (file)
index 0000000..dd168d4
--- /dev/null
@@ -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>;
+    };
index ccb06e485756863267daa9804c11de0a9aabd2bb..fd26c3d895b67619e13e1d6ddb79458ef4931f9d 100644 (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
index f1b4b77daa5f39ae3025cd9a40e2c4f0435520d1..3577be9c7089ccdc76a79264fb6d3f2d41bca35f 100644 (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
index f8832cf49384cadb07b02e4b71ab9250eaaef618..26c385582c3bbca319698a73de29d8b633f61b0f 100644 (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;
 
index 21e3b05a5153a9ba1f7e59bd623bee0a3f397f80..904de8d61828a6b620ce7b9760913df5d3db0c21 100644 (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
index 708840b7fba8d84a7723e4e22a429b430d4241c8..5c08bdb817b4c58f3ff5bcfaa4c5aced9c7412a4 100644 (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
index 36f7ea381838d2abdbb9ead17a103ff8b681b85b..3977a0f9d132ff0c7c818ae8bcbf533862205b72 100644 (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;
index d7ad8868583041f27c8876f09ae332d1ba57361d..b0d91142da8d07a2792f9ca6542a67ac7a0ed8be 100644 (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);
-
-       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,
 };
 
index 5e29d9c682c34fb18e7f5e43a3c4ffc96b2d9aed..7f10f56c3eb66cc4e030290171d59c5320b169e1 100644 (file)
 #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);
index ea17d446a62767fc2cccff8f2de48b2dfd2479df..215ef90691144601be0777490a761aa390fd2242 100644 (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,
 };
 
index b909096dba2fd4b67fbcc7150c4d4ddcb367cd43..272e0b5d01b89b17eac2aad1aeee920f19884d20 100644 (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,
index ddeab5687cb81440916988f043ff7ff27bc57daa..86a0ea0f6955c47e906c54e947455ca282c6fb7f 100644 (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,
 };
 
index 568b13a48717e76d6bda917c86b24ce3824d3b46..d28c0874c7f2a8cb60d368918df6298f5cedea04 100644 (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 },
index e52e29fc8231f34ff20bfae3123bcea42e3851fc..6ff73029f367f53f372ff1b02e315ad497df320e 100644 (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;
 
index 4381df90a5270affbd4b5259bf2f0bf504bfa79d..d7311614c846dda20dbf7696618d628bff7a3ef0 100644 (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;
 
+       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),
index 0a4ff55fad0407716a7c63e22a57c7e263b43d17..9c5b4f515641bfe6882fae72435f9f47e1535823 100644 (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,
 };
 
index 253c4a17d25530ee30859dbc0853bc689c36a03f..e6d05a329002694bc50d3e05ee631a34f53487d4 100644 (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;
 }
index f491d56254d7cda9a1162988a6bd513b281b86ff..44b1f93256b3683d86e5e45290e381bbb194cbc6 100644 (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,
 };
index c4336d3bace326f91b04124c681a9f3c85dc419b..5d4a4762ce0cdb1475bc345b19151d89e5a98756 100644 (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,
 };
 
index 16d75f9aa36a9f03c7a523713a5987f1aff14f45..c8445b0a333926aaa33f05c5eb1dce6bf60837b7 100644 (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);
diff --git a/drivers/pwm/pwm-sunplus.c b/drivers/pwm/pwm-sunplus.c
new file mode 100644 (file)
index 0000000..e776fd1
--- /dev/null
@@ -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");
index e5a9ffef4a71e5a127af129d3111098bacff2d36..dad9978c9186104eb0abb2af7eabf4b2d168141e 100644 (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,
 };
 
index 49d9f7a780121ea71a188865b7972b2662386c8c..ed0b63dd38f1a45b9d43a3f95337e80eb4a4d231 100644 (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,
diff --git a/drivers/pwm/pwm-xilinx.c b/drivers/pwm/pwm-xilinx.c
new file mode 100644 (file)
index 0000000..4dab2b8
--- /dev/null
@@ -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 (file)
index 0000000..c0f56fe
--- /dev/null
@@ -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 (file)
index 0000000..3b29cd0
--- /dev/null
@@ -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