linux-watchdog.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v9 00/27] Ingenic TCU patchset v9
@ 2018-12-27 18:12 Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 01/27] dt-bindings: ingenic: Add DT bindings for TCU clocks Paul Cercueil
                   ` (26 more replies)
  0 siblings, 27 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk

Hi,

This is the v9 of my patchset to add support for the Timer/Counter Unit
present on Ingenic JZ47xx SoCs.

Changes from v8 mainly include:

- The system timer and clocksource sub-nodes of the ingenic-timer driver
  are gone. Now, the ingenic-timer will use the (optional) property
  named "ingenic,pwm-channels-mask" to know which TCU channels are
  reserved for PWM use.

- New patch [11/27] makes the PWM driver implement the .apply callback,
  which is cleaner and incidentally fixes a long-standing bug.

- The patch in V8 that converted the PWM driver to use the regmap and
  clocks provided by the ingenic-timer driver has been splitted in three
  patches, [12,13,14/27]. The algorithm in [14/27] has been slightly
  improved.

- The patch that adds support for the JZ4725B SoC to the PWM driver has
  been removed from the patchset, as it's been suggested that the core
  could use a "npwms" device property to override the number of PWMs set
  in the driver.

Thanks,
-Paul Cercueil


^ permalink raw reply	[flat|nested] 43+ messages in thread

* [PATCH v9 01/27] dt-bindings: ingenic: Add DT bindings for TCU clocks
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 02/27] doc: Add doc for the Ingenic TCU hardware Paul Cercueil
                   ` (25 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

This header provides clock numbers for the ingenic,tcu
DT binding.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Reviewed-by: Rob Herring <robh@kernel.org>
Acked-by: Stephen Boyd <sboyd@kernel.org>
---

Notes:
     v2: Use SPDX identifier for the license
    
     v3: No change
    
     v4: No change
    
     v5: s/JZ47*_/TCU_/ and dropped *_CLK_LAST defines
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 include/dt-bindings/clock/ingenic,tcu.h | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 include/dt-bindings/clock/ingenic,tcu.h

diff --git a/include/dt-bindings/clock/ingenic,tcu.h b/include/dt-bindings/clock/ingenic,tcu.h
new file mode 100644
index 000000000000..d569650a7945
--- /dev/null
+++ b/include/dt-bindings/clock/ingenic,tcu.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header provides clock numbers for the ingenic,tcu DT binding.
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_INGENIC_TCU_H__
+#define __DT_BINDINGS_CLOCK_INGENIC_TCU_H__
+
+#define TCU_CLK_TIMER0	0
+#define TCU_CLK_TIMER1	1
+#define TCU_CLK_TIMER2	2
+#define TCU_CLK_TIMER3	3
+#define TCU_CLK_TIMER4	4
+#define TCU_CLK_TIMER5	5
+#define TCU_CLK_TIMER6	6
+#define TCU_CLK_TIMER7	7
+#define TCU_CLK_WDT	8
+#define TCU_CLK_OST	9
+
+#endif /* __DT_BINDINGS_CLOCK_INGENIC_TCU_H__ */
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 02/27] doc: Add doc for the Ingenic TCU hardware
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 01/27] dt-bindings: ingenic: Add DT bindings for TCU clocks Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 03/27] dt-bindings: Add doc for the Ingenic TCU drivers Paul Cercueil
                   ` (24 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Add a documentation file about the Timer/Counter Unit (TCU) present in
the Ingenic JZ47xx SoCs.

The Timer/Counter Unit (TCU) in Ingenic JZ47xx SoCs is a multi-function
hardware block. It features up to to eight channels, that can be used as
counters, timers, or PWM.

- JZ4725B, JZ4750, JZ4755 only have six TCU channels. The other SoCs all
  have eight channels.

- JZ4725B introduced a separate channel, called Operating System Timer
  (OST). It is a 32-bit programmable timer. On JZ4770 and above, it is
  64-bit.

- Each one of the TCU channels has its own clock, which can be reparented
  to three different clocks (pclk, ext, rtc), gated, and reclocked, through
  their TCSR register.
  * The watchdog and OST hardware blocks also feature a TCSR register with
    the same format in their register space.
  * The TCU registers used to gate/ungate can also gate/ungate the watchdog
    and OST clocks.

- Each TCU channel works in one of two modes:
  * mode TCU1: channels cannot work in sleep mode, but are easier to
    operate.
  * mode TCU2: channels can work in sleep mode, but the operation is a bit
    more complicated than with TCU1 channels.

- The mode of each TCU channel depends on the SoC used:
  * On the oldest SoCs (up to JZ4740), all of the eight channels operate in
    TCU1 mode.
  * On JZ4725B, channel 5 operates as TCU2, the others operate as TCU1.
  * On newest SoCs (JZ4750 and above), channels 1-2 operate as TCU2, the
    others operate as TCU1.

- Each channel can generate an interrupt. Some channels share an interrupt
  line, some don't, and this changes between SoC versions:
  * on older SoCs (JZ4740 and below), channel 0 and channel 1 have their
    own interrupt line; channels 2-7 share the last interrupt line.
  * On JZ4725B, channel 0 has its own interrupt; channels 1-5 share one
    interrupt line; the OST uses the last interrupt line.
  * on newer SoCs (JZ4750 and above), channel 5 has its own interrupt;
    channels 0-4 and (if eight channels) 6-7 all share one interrupt line;
    the OST uses the last interrupt line.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v4: New patch in this series
    
     v5: Added information about number of channels, and improved
         documentation about channel modes
    
     v6: Add info about OST (can be 32-bit on older SoCs)
    
     v7: No change
    
     v8: No change

     v9: No change

 Documentation/mips/ingenic-tcu.txt | 60 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 Documentation/mips/ingenic-tcu.txt

diff --git a/Documentation/mips/ingenic-tcu.txt b/Documentation/mips/ingenic-tcu.txt
new file mode 100644
index 000000000000..0ea35b2a46da
--- /dev/null
+++ b/Documentation/mips/ingenic-tcu.txt
@@ -0,0 +1,60 @@
+Ingenic JZ47xx SoCs Timer/Counter Unit hardware
+-----------------------------------------------
+
+The Timer/Counter Unit (TCU) in Ingenic JZ47xx SoCs is a multi-function
+hardware block. It features up to to eight channels, that can be used as
+counters, timers, or PWM.
+
+- JZ4725B, JZ4750, JZ4755 only have six TCU channels. The other SoCs all
+  have eight channels.
+
+- JZ4725B introduced a separate channel, called Operating System Timer
+  (OST). It is a 32-bit programmable timer. On JZ4770 and above, it is
+  64-bit.
+
+- Each one of the TCU channels has its own clock, which can be reparented
+  to three different clocks (pclk, ext, rtc), gated, and reclocked, through
+  their TCSR register.
+  * The watchdog and OST hardware blocks also feature a TCSR register with
+    the same format in their register space.
+  * The TCU registers used to gate/ungate can also gate/ungate the watchdog
+    and OST clocks.
+
+- Each TCU channel works in one of two modes:
+  * mode TCU1: channels cannot work in sleep mode, but are easier to
+    operate.
+  * mode TCU2: channels can work in sleep mode, but the operation is a bit
+    more complicated than with TCU1 channels.
+
+- The mode of each TCU channel depends on the SoC used:
+  * On the oldest SoCs (up to JZ4740), all of the eight channels operate in
+    TCU1 mode.
+  * On JZ4725B, channel 5 operates as TCU2, the others operate as TCU1.
+  * On newest SoCs (JZ4750 and above), channels 1-2 operate as TCU2, the
+    others operate as TCU1.
+
+- Each channel can generate an interrupt. Some channels share an interrupt
+  line, some don't, and this changes between SoC versions:
+  * on older SoCs (JZ4740 and below), channel 0 and channel 1 have their
+    own interrupt line; channels 2-7 share the last interrupt line.
+  * On JZ4725B, channel 0 has its own interrupt; channels 1-5 share one
+    interrupt line; the OST uses the last interrupt line.
+  * on newer SoCs (JZ4750 and above), channel 5 has its own interrupt;
+    channels 0-4 and (if eight channels) 6-7 all share one interrupt line;
+    the OST uses the last interrupt line.
+
+Implementation
+--------------
+
+The functionalities of the TCU hardware are spread across multiple drivers:
+- clocks/irq/timer: drivers/clocksource/ingenic-timer.c
+- PWM:              drivers/pwm/pwm-jz4740.c
+- watchdog:         drivers/watchdog/jz4740_wdt.c
+- OST:              drivers/clocksource/ingenic-ost.c
+
+Because various functionalities of the TCU that belong to different drivers
+and frameworks can be controlled from the same registers, all of these
+drivers access their registers through the same regmap.
+
+For more information regarding the devicetree bindings of the TCU drivers,
+have a look at Documentation/devicetree/bindings/mfd/ingenic,tcu.txt.
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 03/27] dt-bindings: Add doc for the Ingenic TCU drivers
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 01/27] dt-bindings: ingenic: Add DT bindings for TCU clocks Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 02/27] doc: Add doc for the Ingenic TCU hardware Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2018-12-28 21:48   ` Rob Herring
  2018-12-27 18:12 ` [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver Paul Cercueil
                   ` (23 subsequent siblings)
  26 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Add documentation about how to properly use the Ingenic TCU
(Timer/Counter Unit) drivers from devicetree.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v4: New patch in this series. Corresponds to V2 patches 3-4-5 with
         added content.
    
     v5: - Edited PWM/watchdog DT bindings documentation to point to the new
           document.
         - Moved main document to
           Documentation/devicetree/bindings/timer/ingenic,tcu.txt
         - Updated documentation to reflect the new devicetree bindings.
    
     v6: - Removed PWM/watchdog documentation files as asked by upstream
         - Removed doc about properties that should be implicit
         - Removed doc about ingenic,timer-channel /
           ingenic,clocksource-channel as they are gone
         - Fix WDT clock name in the binding doc
         - Fix lengths of register areas in watchdog/pwm nodes
    
     v7: No change

     v8: - Fix address of the PWM node
         - Added doc about system timer and clocksource children nodes

     v9: - Remove doc about system timer and clocksource children
           nodes...
	 - Add doc about ingenic,pwm-channels-mask property

 .../devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt |  25 ----
 .../devicetree/bindings/timer/ingenic,tcu.txt      | 139 +++++++++++++++++++++
 .../bindings/watchdog/ingenic,jz4740-wdt.txt       |  17 ---
 3 files changed, 139 insertions(+), 42 deletions(-)
 delete mode 100644 Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt
 create mode 100644 Documentation/devicetree/bindings/timer/ingenic,tcu.txt
 delete mode 100644 Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt

diff --git a/Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt b/Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt
deleted file mode 100644
index 7d9d3f90641b..000000000000
--- a/Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-Ingenic JZ47xx PWM Controller
-=============================
-
-Required properties:
-- compatible: One of:
-  * "ingenic,jz4740-pwm"
-  * "ingenic,jz4770-pwm"
-  * "ingenic,jz4780-pwm"
-- #pwm-cells: Should be 3. See pwm.txt in this directory for a description
-  of the cells format.
-- clocks : phandle to the external clock.
-- clock-names : Should be "ext".
-
-
-Example:
-
-	pwm: pwm@10002000 {
-		compatible = "ingenic,jz4740-pwm";
-		reg = <0x10002000 0x1000>;
-
-		#pwm-cells = <3>;
-
-		clocks = <&ext>;
-		clock-names = "ext";
-	};
diff --git a/Documentation/devicetree/bindings/timer/ingenic,tcu.txt b/Documentation/devicetree/bindings/timer/ingenic,tcu.txt
new file mode 100644
index 000000000000..ec9639700115
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/ingenic,tcu.txt
@@ -0,0 +1,139 @@
+Ingenic JZ47xx SoCs Timer/Counter Unit devicetree bindings
+==========================================================
+
+For a description of the TCU hardware and drivers, have a look at
+Documentation/mips/ingenic-tcu.txt.
+
+Required properties:
+
+- compatible: Must be one of:
+  * ingenic,jz4740-tcu
+  * ingenic,jz4725b-tcu
+  * ingenic,jz4770-tcu
+- reg: Should be the offset/length value corresponding to the TCU registers
+- clocks: List of phandle & clock specifiers for clocks external to the TCU.
+  The "pclk", "rtc", "ext" and "tcu" clocks should be provided.
+- clock-names: List of name strings for the external clocks.
+- #clock-cells: Should be <1>;
+  Clock consumers specify this argument to identify a clock. The valid values
+  may be found in <dt-bindings/clock/ingenic,tcu.h>.
+- interrupt-controller : Identifies the node as an interrupt controller
+- #interrupt-cells : Specifies the number of cells needed to encode an
+  interrupt source. The value should be 1.
+- interrupt-parent : phandle of the interrupt controller.
+- interrupts : Specifies the interrupt the controller is connected to.
+
+Optional properties:
+
+- ingenic,pwm-channels-mask: Bitmask of TCU channels reserved for PWM use.
+  Default value is 0xfe or 0xfc if the SoC does not have the OS Timer.
+
+
+Children nodes
+==========================================================
+
+
+PWM node:
+---------
+
+Required properties:
+
+- compatible: Must be one of:
+  * ingenic,jz4740-pwm
+  * ingenic,jz4725b-pwm
+- #pwm-cells: Should be 3. See ../pwm/pwm.txt for a description of the cell
+  format.
+- clocks: List of phandle & clock specifiers for the TCU clocks.
+- clock-names: List of name strings for the TCU clocks.
+
+
+Watchdog node:
+--------------
+
+Required properties:
+
+- compatible: Must be one of:
+  * ingenic,jz4740-watchdog
+  * ingenic,jz4780-watchdog
+- clocks: phandle to the WDT clock
+- clock-names: should be "wdt"
+
+
+OS Timer node:
+---------
+
+Required properties:
+
+- compatible: Must be one of:
+  * ingenic,jz4725b-ost
+  * ingenic,jz4770-ost
+- clocks: phandle to the OST clock
+- clock-names: should be "ost"
+- interrupts : Specifies the interrupt the OST is connected to.
+
+
+Example
+==========================================================
+
+#include <dt-bindings/clock/jz4770-cgu.h>
+#include <dt-bindings/clock/ingenic,tcu.h>
+
+/ {
+	tcu: timer@10002000 {
+		compatible = "ingenic,jz4770-tcu";
+		reg = <0x10002000 0x1000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges = <0x0 0x10002000 0x1000>;
+
+		#clock-cells = <1>;
+
+		clocks = <&cgu JZ4770_CLK_RTC
+			  &cgu JZ4770_CLK_EXT
+			  &cgu JZ4770_CLK_PCLK
+			  &cgu JZ4770_CLK_EXT>;
+		clock-names = "rtc", "ext", "pclk", "tcu";
+
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		interrupt-parent = <&intc>;
+		interrupts = <27 26 25>;
+
+		watchdog: watchdog@0 {
+			compatible = "ingenic,jz4740-watchdog";
+			reg = <0x0 0xc>;
+
+			clocks = <&tcu TCU_CLK_WDT>;
+			clock-names = "wdt";
+		};
+
+		pwm: pwm@40 {
+			compatible = "ingenic,jz4740-pwm";
+			reg = <0x40 0x80>;
+
+			#pwm-cells = <3>;
+
+			clocks = <&tcu TCU_CLK_TIMER0
+				  &tcu TCU_CLK_TIMER1
+				  &tcu TCU_CLK_TIMER2
+				  &tcu TCU_CLK_TIMER3
+				  &tcu TCU_CLK_TIMER4
+				  &tcu TCU_CLK_TIMER5
+				  &tcu TCU_CLK_TIMER6
+				  &tcu TCU_CLK_TIMER7>;
+			clock-names = "timer0", "timer1", "timer2", "timer3",
+				      "timer4", "timer5", "timer6", "timer7";
+		};
+
+		ost: timer@e0 {
+			compatible = "ingenic,jz4770-ost";
+			reg = <0xe0 0x20>;
+
+			clocks = <&tcu TCU_CLK_OST>;
+			clock-names = "ost";
+
+			interrupts = <15>;
+		};
+	};
+};
diff --git a/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt b/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt
deleted file mode 100644
index ce1cb72d5345..000000000000
--- a/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Ingenic Watchdog Timer (WDT) Controller for JZ4740 & JZ4780
-
-Required properties:
-compatible: "ingenic,jz4740-watchdog" or "ingenic,jz4780-watchdog"
-reg: Register address and length for watchdog registers
-clocks: phandle to the RTC clock
-clock-names: should be "rtc"
-
-Example:
-
-watchdog: jz4740-watchdog@10002000 {
-	compatible = "ingenic,jz4740-watchdog";
-	reg = <0x10002000 0x10>;
-
-	clocks = <&cgu JZ4740_CLK_RTC>;
-	clock-names = "rtc";
-};
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (2 preceding siblings ...)
  2018-12-27 18:12 ` [PATCH v9 03/27] dt-bindings: Add doc for the Ingenic TCU drivers Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2019-02-25 15:55   ` Daniel Lezcano
  2018-12-27 18:12 ` [PATCH v9 05/27] clocksource: Add driver for the Ingenic JZ47xx OST Paul Cercueil
                   ` (22 subsequent siblings)
  26 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

This driver handles the TCU (Timer Counter Unit) present on the Ingenic
JZ47xx SoCs, and provides the kernel with a system timer, and optionally
with a clocksource and a sched_clock.

It also provides clocks and interrupt handling to client drivers.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v2: Use SPDX identifier for the license
    
     v3: - Move documentation to its own patch
         - Search the devicetree for PWM clients, and use all the TCU
    	   channels that won't be used for PWM
    
     v4: - Add documentation about why we search for PWM clients
         - Verify that the PWM clients are for the TCU PWM driver
    
     v5: Major overhaul. Too many changes to list. Consider it's a new
         patch.
    
     v6: - Add two API functions ingenic_tcu_request_channel and
           ingenic_tcu_release_channel. To be used by the PWM driver to
           request the use of a TCU channel. The driver will now dynamically
           move away the system timer or clocksource to a new TCU channel.
         - The system timer now defaults to channel 0, the clocksource now
           defaults to channel 1 and is no more optional. The
           ingenic,timer-channel and ingenic,clocksource-channel devicetree
           properties are now gone.
         - Fix round_rate / set_rate not calculating the prescale divider
           the same way. This caused problems when (parent_rate / div) would
           give a non-integer result. The behaviour is correct now.
         - The clocksource clock is turned off on suspend now.
    
     v7: Fix section mismatch by using builtin_platform_driver_probe()

     v8: - Removed ingenic_tcu_[request,release]_channel, and the mechanism
           to dynamically change the TCU channel of the system timer or
	   the clocksource.
	 - The driver's devicetree node can now have two more children
	   nodes, that correspond to the system timer and clocksource.
	   For these two, the driver will use the TCU timer that
	   correspond to the memory resource supplied in their
	   respective node.

     v9: - Removed support for clocksource / timer children devicetree
           nodes. Now, we use a property "ingenic,pwm-channels-mask" to
	   know which PWM channels are reserved for PWM use and should
	   not be used as OS timers.

 drivers/clocksource/Kconfig         |  10 +
 drivers/clocksource/Makefile        |   1 +
 drivers/clocksource/ingenic-timer.c | 901 ++++++++++++++++++++++++++++++++++++
 drivers/clocksource/ingenic-timer.h |  15 +
 include/linux/mfd/ingenic-tcu.h     |   2 +
 5 files changed, 929 insertions(+)
 create mode 100644 drivers/clocksource/ingenic-timer.c
 create mode 100644 drivers/clocksource/ingenic-timer.h

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 55c77e44bb2d..4e69af15c3e7 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -638,4 +638,14 @@ config GX6605S_TIMER
 	help
 	  This option enables support for gx6605s SOC's timer.
 
+config INGENIC_TIMER
+	bool "Clocksource/timer using the TCU in Ingenic JZ SoCs"
+	depends on MIPS || COMPILE_TEST
+	depends on COMMON_CLK
+	select TIMER_OF
+	select IRQ_DOMAIN
+	select REGMAP
+	help
+	  Support for the timer/counter unit of the Ingenic JZ SoCs.
+
 endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index dd9138104568..7c8f790dcf67 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_ASM9260_TIMER)		+= asm9260_timer.o
 obj-$(CONFIG_H8300_TMR8)		+= h8300_timer8.o
 obj-$(CONFIG_H8300_TMR16)		+= h8300_timer16.o
 obj-$(CONFIG_H8300_TPU)			+= h8300_tpu.o
+obj-$(CONFIG_INGENIC_TIMER)		+= ingenic-timer.o
 obj-$(CONFIG_CLKSRC_ST_LPC)		+= clksrc_st_lpc.o
 obj-$(CONFIG_X86_NUMACHIP)		+= numachip.o
 obj-$(CONFIG_ATCPIT100_TIMER)		+= timer-atcpit100.o
diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c
new file mode 100644
index 000000000000..81faa120cfee
--- /dev/null
+++ b/drivers/clocksource/ingenic-timer.c
@@ -0,0 +1,901 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * JZ47xx SoCs TCU IRQ driver
+ * Copyright (C) 2018 Paul Cercueil <paul@crapouillou.net>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/mfd/ingenic-tcu.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sched_clock.h>
+
+#include <dt-bindings/clock/ingenic,tcu.h>
+
+#include "ingenic-timer.h"
+
+/* 8 channels max + watchdog + OST */
+#define TCU_CLK_COUNT	10
+
+enum tcu_clk_parent {
+	TCU_PARENT_PCLK,
+	TCU_PARENT_RTC,
+	TCU_PARENT_EXT,
+};
+
+struct ingenic_soc_info {
+	unsigned char num_channels;
+	bool has_ost;
+};
+
+struct ingenic_tcu_clk_info {
+	struct clk_init_data init_data;
+	u8 gate_bit;
+	u8 tcsr_reg;
+};
+
+struct ingenic_tcu_clk {
+	struct clk_hw hw;
+
+	struct regmap *map;
+	const struct ingenic_tcu_clk_info *info;
+
+	unsigned int idx;
+};
+
+#define to_tcu_clk(_hw) container_of(_hw, struct ingenic_tcu_clk, hw)
+
+struct ingenic_tcu {
+	const struct ingenic_soc_info *soc_info;
+	struct regmap *map;
+	struct clk *clk, *timer_clk, *cs_clk;
+
+	struct irq_domain *domain;
+	unsigned int nb_parent_irqs;
+	u32 parent_irqs[3];
+
+	struct clk_hw_onecell_data *clocks;
+
+	unsigned int timer_channel, cs_channel;
+	struct clock_event_device cevt;
+	struct clocksource cs;
+	char name[4];
+
+	unsigned long pwm_channels_mask;
+};
+
+static struct ingenic_tcu *ingenic_tcu;
+
+void __iomem *ingenic_tcu_base;
+EXPORT_SYMBOL_GPL(ingenic_tcu_base);
+
+static int ingenic_tcu_enable(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+	regmap_write(tcu_clk->map, TCU_REG_TSCR, BIT(info->gate_bit));
+	return 0;
+}
+
+static void ingenic_tcu_disable(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+
+	regmap_write(tcu_clk->map, TCU_REG_TSSR, BIT(info->gate_bit));
+}
+
+static int ingenic_tcu_is_enabled(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	unsigned int value;
+
+	regmap_read(tcu_clk->map, TCU_REG_TSR, &value);
+
+	return !(value & BIT(info->gate_bit));
+}
+
+static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	unsigned int val = 0;
+	int ret;
+
+	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &val);
+	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+	return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
+}
+
+static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	struct regmap *map = tcu_clk->map;
+	int ret;
+
+	/*
+	 * Our clock provider has the CLK_SET_PARENT_GATE flag set, so we know
+	 * that the clk is in unprepared state. To be able to access TCSR
+	 * we must ungate the clock supply and we gate it again when done.
+	 */
+
+	regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
+
+	ret = regmap_update_bits(map, info->tcsr_reg,
+				TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
+	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
+
+	regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
+
+	return 0;
+}
+
+static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
+		unsigned long parent_rate)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	unsigned int prescale;
+	int ret;
+
+	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &prescale);
+	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
+
+	prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> TCU_TCSR_PRESCALE_LSB;
+
+	return parent_rate >> (prescale * 2);
+}
+
+static u8 ingenic_tcu_get_prescale(unsigned long rate, unsigned long req_rate)
+{
+	u8 prescale;
+
+	for (prescale = 0; prescale < 5; prescale++)
+		if ((rate >> (prescale * 2)) <= req_rate)
+			return prescale;
+
+	return 5; /* /1024 divider */
+}
+
+static long ingenic_tcu_round_rate(struct clk_hw *hw, unsigned long req_rate,
+		unsigned long *parent_rate)
+{
+	unsigned long rate = *parent_rate;
+	u8 prescale;
+
+	if (req_rate > rate)
+		return -EINVAL;
+
+	prescale = ingenic_tcu_get_prescale(rate, req_rate);
+	return rate >> (prescale * 2);
+}
+
+static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long req_rate,
+		unsigned long parent_rate)
+{
+	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
+	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
+	struct regmap *map = tcu_clk->map;
+	u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
+	int ret;
+
+	/*
+	 * Our clock provider has the CLK_SET_RATE_GATE flag set, so we know
+	 * that the clk is in unprepared state. To be able to access TCSR
+	 * we must ungate the clock supply and we gate it again when done.
+	 */
+
+	regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
+
+	ret = regmap_update_bits(map, info->tcsr_reg,
+				TCU_TCSR_PRESCALE_MASK,
+				prescale << TCU_TCSR_PRESCALE_LSB);
+	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
+
+	regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
+
+	return 0;
+}
+
+static const struct clk_ops ingenic_tcu_clk_ops = {
+	.get_parent	= ingenic_tcu_get_parent,
+	.set_parent	= ingenic_tcu_set_parent,
+
+	.recalc_rate	= ingenic_tcu_recalc_rate,
+	.round_rate	= ingenic_tcu_round_rate,
+	.set_rate	= ingenic_tcu_set_rate,
+
+	.enable		= ingenic_tcu_enable,
+	.disable	= ingenic_tcu_disable,
+	.is_enabled	= ingenic_tcu_is_enabled,
+};
+
+static const char * const ingenic_tcu_timer_parents[] = {
+	[TCU_PARENT_PCLK] = "pclk",
+	[TCU_PARENT_RTC]  = "rtc",
+	[TCU_PARENT_EXT]  = "ext",
+};
+
+#define DEF_TIMER(_name, _gate_bit, _tcsr)				\
+	{								\
+		.init_data = {						\
+			.name = _name,					\
+			.parent_names = ingenic_tcu_timer_parents,	\
+			.num_parents = ARRAY_SIZE(ingenic_tcu_timer_parents),\
+			.ops = &ingenic_tcu_clk_ops,			\
+			.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE,\
+		},							\
+		.gate_bit = _gate_bit,					\
+		.tcsr_reg = _tcsr,					\
+	}
+static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
+	[TCU_CLK_TIMER0] = DEF_TIMER("timer0", 0, TCU_REG_TCSRc(0)),
+	[TCU_CLK_TIMER1] = DEF_TIMER("timer1", 1, TCU_REG_TCSRc(1)),
+	[TCU_CLK_TIMER2] = DEF_TIMER("timer2", 2, TCU_REG_TCSRc(2)),
+	[TCU_CLK_TIMER3] = DEF_TIMER("timer3", 3, TCU_REG_TCSRc(3)),
+	[TCU_CLK_TIMER4] = DEF_TIMER("timer4", 4, TCU_REG_TCSRc(4)),
+	[TCU_CLK_TIMER5] = DEF_TIMER("timer5", 5, TCU_REG_TCSRc(5)),
+	[TCU_CLK_TIMER6] = DEF_TIMER("timer6", 6, TCU_REG_TCSRc(6)),
+	[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
+};
+
+static const struct ingenic_tcu_clk_info ingenic_tcu_watchdog_clk_info =
+				DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
+static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
+				DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
+#undef DEF_TIMER
+
+static void ingenic_tcu_intc_cascade(struct irq_desc *desc)
+{
+	struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
+	struct irq_domain *domain = irq_desc_get_handler_data(desc);
+	struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
+	struct regmap *map = gc->private;
+	uint32_t irq_reg, irq_mask;
+	unsigned int i;
+
+	regmap_read(map, TCU_REG_TFR, &irq_reg);
+	regmap_read(map, TCU_REG_TMR, &irq_mask);
+
+	chained_irq_enter(irq_chip, desc);
+
+	irq_reg &= ~irq_mask;
+
+	for_each_set_bit(i, (unsigned long *)&irq_reg, 32)
+		generic_handle_irq(irq_linear_revmap(domain, i));
+
+	chained_irq_exit(irq_chip, desc);
+}
+
+static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	struct irq_chip_type *ct = irq_data_get_chip_type(d);
+	struct regmap *map = gc->private;
+	u32 mask = d->mask;
+
+	irq_gc_lock(gc);
+	regmap_write(map, ct->regs.ack, mask);
+	regmap_write(map, ct->regs.enable, mask);
+	*ct->mask_cache |= mask;
+	irq_gc_unlock(gc);
+}
+
+static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	struct irq_chip_type *ct = irq_data_get_chip_type(d);
+	struct regmap *map = gc->private;
+	u32 mask = d->mask;
+
+	irq_gc_lock(gc);
+	regmap_write(map, ct->regs.disable, mask);
+	*ct->mask_cache &= ~mask;
+	irq_gc_unlock(gc);
+}
+
+static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct irq_data *d)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+	struct irq_chip_type *ct = irq_data_get_chip_type(d);
+	struct regmap *map = gc->private;
+	u32 mask = d->mask;
+
+	irq_gc_lock(gc);
+	regmap_write(map, ct->regs.ack, mask);
+	regmap_write(map, ct->regs.disable, mask);
+	irq_gc_unlock(gc);
+}
+
+static u64 notrace ingenic_tcu_timer_read(void)
+{
+	unsigned int channel = ingenic_tcu->cs_channel;
+	u16 count;
+
+	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
+
+	return count;
+}
+
+static inline struct ingenic_tcu *to_ingenic_tcu(struct clock_event_device *evt)
+{
+	return container_of(evt, struct ingenic_tcu, cevt);
+}
+
+static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt)
+{
+	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
+
+	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
+	return 0;
+}
+
+static int ingenic_tcu_cevt_set_next(unsigned long next,
+				     struct clock_event_device *evt)
+{
+	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
+
+	if (next > 0xffff)
+		return -EINVAL;
+
+	regmap_write(tcu->map, TCU_REG_TDFRc(tcu->timer_channel), next);
+	regmap_write(tcu->map, TCU_REG_TCNTc(tcu->timer_channel), 0);
+	regmap_write(tcu->map, TCU_REG_TESR, BIT(tcu->timer_channel));
+
+	return 0;
+}
+
+static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id)
+{
+	struct clock_event_device *evt = dev_id;
+	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
+
+	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
+
+	if (evt->event_handler)
+		evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
+			unsigned int idx, enum tcu_clk_parent parent,
+			const struct ingenic_tcu_clk_info *info,
+			struct clk_hw_onecell_data *clocks)
+{
+	struct ingenic_tcu_clk *tcu_clk;
+	int err;
+
+	tcu_clk = kzalloc(sizeof(*tcu_clk), GFP_KERNEL);
+	if (!tcu_clk)
+		return -ENOMEM;
+
+	tcu_clk->hw.init = &info->init_data;
+	tcu_clk->idx = idx;
+	tcu_clk->info = info;
+	tcu_clk->map = tcu->map;
+
+	/* Reset channel and clock divider, set default parent */
+	ingenic_tcu_enable(&tcu_clk->hw);
+	regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
+	ingenic_tcu_disable(&tcu_clk->hw);
+
+	err = clk_hw_register(NULL, &tcu_clk->hw);
+	if (err)
+		goto err_free_tcu_clk;
+
+	err = clk_hw_register_clkdev(&tcu_clk->hw, info->init_data.name, NULL);
+	if (err)
+		goto err_clk_unregister;
+
+	clocks->hws[idx] = &tcu_clk->hw;
+	return 0;
+
+err_clk_unregister:
+	clk_hw_unregister(&tcu_clk->hw);
+err_free_tcu_clk:
+	kfree(tcu_clk);
+	return err;
+}
+
+static int __init ingenic_tcu_clk_init(struct ingenic_tcu *tcu,
+				       struct device_node *np)
+{
+	size_t i;
+	int ret;
+
+	tcu->clocks = kzalloc(sizeof(*tcu->clocks) +
+			 sizeof(*tcu->clocks->hws) * TCU_CLK_COUNT,
+			 GFP_KERNEL);
+	if (!tcu->clocks)
+		return -ENOMEM;
+
+	tcu->clocks->num = TCU_CLK_COUNT;
+
+	for (i = 0; i < tcu->soc_info->num_channels; i++) {
+		ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
+				&ingenic_tcu_clk_info[i], tcu->clocks);
+		if (ret) {
+			pr_err("ingenic-timer: cannot register clock %i\n", i);
+			goto err_unregister_timer_clocks;
+		}
+	}
+
+	/*
+	 * We set EXT as the default parent clock for all the TCU clocks
+	 * except for the watchdog one, where we set the RTC clock as the
+	 * parent. Since the EXT and PCLK are much faster than the RTC clock,
+	 * the watchdog would kick after a maximum time of 5s, and we might
+	 * want a slower kicking time.
+	 */
+	ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
+				&ingenic_tcu_watchdog_clk_info, tcu->clocks);
+	if (ret) {
+		pr_err("ingenic-timer: cannot register watchdog clock\n");
+		goto err_unregister_timer_clocks;
+	}
+
+	if (tcu->soc_info->has_ost) {
+		ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
+					TCU_PARENT_EXT,
+					&ingenic_tcu_ost_clk_info,
+					tcu->clocks);
+		if (ret) {
+			pr_err("ingenic-timer: cannot register ost clock\n");
+			goto err_unregister_watchdog_clock;
+		}
+	}
+
+	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, tcu->clocks);
+	if (ret) {
+		pr_err("ingenic-timer: cannot add OF clock provider\n");
+		goto err_unregister_ost_clock;
+	}
+
+	return 0;
+
+err_unregister_ost_clock:
+	if (tcu->soc_info->has_ost)
+		clk_hw_unregister(tcu->clocks->hws[i + 1]);
+err_unregister_watchdog_clock:
+	clk_hw_unregister(tcu->clocks->hws[i]);
+err_unregister_timer_clocks:
+	for (i = 0; i < tcu->clocks->num; i++)
+		if (tcu->clocks->hws[i])
+			clk_hw_unregister(tcu->clocks->hws[i]);
+	kfree(tcu->clocks);
+	return ret;
+}
+
+static void __init ingenic_tcu_clk_cleanup(struct ingenic_tcu *tcu,
+					   struct device_node *np)
+{
+	unsigned int i;
+
+	of_clk_del_provider(np);
+
+	for (i = 0; i < tcu->clocks->num; i++)
+		clk_hw_unregister(tcu->clocks->hws[i]);
+	kfree(tcu->clocks);
+}
+
+static int __init ingenic_tcu_intc_init(struct ingenic_tcu *tcu,
+					struct device_node *np)
+{
+	struct irq_chip_generic *gc;
+	struct irq_chip_type *ct;
+	int err, i, irqs;
+
+	irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32));
+	if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs))
+		return -EINVAL;
+
+	tcu->nb_parent_irqs = irqs;
+
+	tcu->domain = irq_domain_add_linear(np, 32,
+			&irq_generic_chip_ops, NULL);
+	if (!tcu->domain)
+		return -ENOMEM;
+
+	err = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU",
+			handle_level_irq, 0, IRQ_NOPROBE | IRQ_LEVEL, 0);
+	if (err)
+		goto out_domain_remove;
+
+	gc = irq_get_domain_generic_chip(tcu->domain, 0);
+	ct = gc->chip_types;
+
+	gc->wake_enabled = IRQ_MSK(32);
+	gc->private = tcu->map;
+
+	ct->regs.disable = TCU_REG_TMSR;
+	ct->regs.enable = TCU_REG_TMCR;
+	ct->regs.ack = TCU_REG_TFCR;
+	ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg;
+	ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg;
+	ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack;
+	ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
+
+	/* Mask all IRQs by default */
+	regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32));
+
+	/* On JZ4740, timer 0 and timer 1 have their own interrupt line;
+	 * timers 2-7 share one interrupt.
+	 * On SoCs >= JZ4770, timer 5 has its own interrupt line;
+	 * timers 0-4 and 6-7 share one single interrupt.
+	 *
+	 * To keep things simple, we just register the same handler to
+	 * all parent interrupts. The handler will properly detect which
+	 * channel fired the interrupt.
+	 */
+	for (i = 0; i < irqs; i++) {
+		tcu->parent_irqs[i] = irq_of_parse_and_map(np, i);
+		if (!tcu->parent_irqs[i]) {
+			err = -EINVAL;
+			goto out_unmap_irqs;
+		}
+
+		irq_set_chained_handler_and_data(tcu->parent_irqs[i],
+				ingenic_tcu_intc_cascade, tcu->domain);
+	}
+
+	return 0;
+
+out_unmap_irqs:
+	for (; i > 0; i--)
+		irq_dispose_mapping(tcu->parent_irqs[i - 1]);
+out_domain_remove:
+	irq_domain_remove(tcu->domain);
+	return err;
+}
+
+static void __init ingenic_tcu_intc_cleanup(struct ingenic_tcu *tcu)
+{
+	unsigned int i;
+
+	for (i = 0; i < tcu->nb_parent_irqs; i++)
+		irq_dispose_mapping(tcu->parent_irqs[i]);
+
+	irq_domain_remove(tcu->domain);
+}
+
+static int __init ingenic_tcu_timer_init(struct ingenic_tcu *tcu)
+{
+	unsigned int timer_virq;
+	unsigned long rate;
+	int err;
+
+	tcu->timer_clk = tcu->clocks->hws[tcu->timer_channel]->clk;
+
+	err = clk_prepare_enable(tcu->timer_clk);
+	if (err)
+		return err;
+
+	rate = clk_get_rate(tcu->timer_clk);
+	if (!rate) {
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	timer_virq = irq_create_mapping(tcu->domain, tcu->timer_channel);
+	if (!timer_virq) {
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	snprintf(tcu->name, sizeof(tcu->name), "TCU");
+
+	err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
+			  tcu->name, &tcu->cevt);
+	if (err)
+		goto err_irq_dispose_mapping;
+
+	tcu->cevt.cpumask = cpumask_of(smp_processor_id());
+	tcu->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
+	tcu->cevt.name = tcu->name;
+	tcu->cevt.rating = 200;
+	tcu->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown;
+	tcu->cevt.set_next_event = ingenic_tcu_cevt_set_next;
+
+	clockevents_config_and_register(&tcu->cevt, rate, 10, 0xffff);
+
+	return 0;
+
+err_irq_dispose_mapping:
+	irq_dispose_mapping(timer_virq);
+err_clk_disable:
+	clk_disable_unprepare(tcu->timer_clk);
+	return err;
+}
+
+static int __init ingenic_tcu_clocksource_init(struct ingenic_tcu *tcu)
+{
+	unsigned int channel = tcu->cs_channel;
+	struct clocksource *cs = &tcu->cs;
+	unsigned long rate;
+	int err;
+
+	tcu->cs_clk = tcu->clocks->hws[channel]->clk;
+
+	err = clk_prepare_enable(tcu->cs_clk);
+	if (err)
+		return err;
+
+	rate = clk_get_rate(tcu->cs_clk);
+	if (!rate) {
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	/* Reset channel */
+	regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel),
+			   0xffff & ~TCU_TCSR_RESERVED_BITS, 0);
+
+	/* Reset counter */
+	regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff);
+	regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0);
+
+	/* Enable channel */
+	regmap_write(tcu->map, TCU_REG_TESR, BIT(channel));
+
+	cs->name = "ingenic-timer";
+	cs->rating = 200;
+	cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
+	cs->mask = CLOCKSOURCE_MASK(16);
+	cs->read = (u64 (*)(struct clocksource *))ingenic_tcu_timer_read;
+
+	err = clocksource_register_hz(cs, rate);
+	if (err)
+		goto err_clk_disable;
+
+	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
+
+	return 0;
+
+err_clk_disable:
+	clk_disable_unprepare(tcu->cs_clk);
+	return err;
+}
+
+static void __init ingenic_tcu_clocksource_cleanup(struct ingenic_tcu *tcu)
+{
+	clocksource_unregister(&tcu->cs);
+	clk_disable_unprepare(tcu->cs_clk);
+}
+
+static const struct regmap_config ingenic_tcu_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+};
+
+static const struct ingenic_soc_info jz4740_soc_info = {
+	.num_channels = 8,
+	.has_ost = false,
+};
+
+static const struct ingenic_soc_info jz4725b_soc_info = {
+	.num_channels = 6,
+	.has_ost = true,
+};
+
+static const struct ingenic_soc_info jz4770_soc_info = {
+	.num_channels = 8,
+	.has_ost = true,
+};
+
+static const struct of_device_id ingenic_tcu_of_match[] = {
+	{ .compatible = "ingenic,jz4740-tcu",  .data = &jz4740_soc_info, },
+	{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
+	{ .compatible = "ingenic,jz4770-tcu",  .data = &jz4770_soc_info, },
+	{ }
+};
+
+static int __init ingenic_tcu_init(struct device_node *np)
+{
+	const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
+	const struct ingenic_soc_info *soc_info = id->data;
+	unsigned int max_pwm_num;
+	struct ingenic_tcu *tcu;
+	struct resource res;
+	void __iomem *base;
+	int ret;
+
+	of_node_clear_flag(np, OF_POPULATED);
+
+	tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
+	if (!tcu)
+		return -ENOMEM;
+
+	/*
+	 * Enable all TCU channels for PWM use by default except channel 0
+	 * and, if the OS Timer is present, channel 1.
+	 */
+	tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1,
+					 2 - !!soc_info->has_ost);
+	of_property_read_u32(np, "ingenic,pwm-channels-mask",
+			     (u32 *)&tcu->pwm_channels_mask);
+
+	max_pwm_num = soc_info->num_channels - !soc_info->has_ost - 1;
+
+	if (hweight8(tcu->pwm_channels_mask) > max_pwm_num) {
+		pr_crit("ingenic-tcu: Invalid PWM channel mask: 0x%02lx\n",
+					tcu->pwm_channels_mask);
+		return -EINVAL;
+	}
+
+	tcu->soc_info = soc_info;
+	ingenic_tcu = tcu;
+
+	tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
+						 soc_info->num_channels);
+	if (soc_info->has_ost) {
+		tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
+						     soc_info->num_channels,
+						     tcu->timer_channel + 1);
+	}
+
+	base = of_io_request_and_map(np, 0, NULL);
+	if (IS_ERR(base)) {
+		ret = PTR_ERR(base);
+		goto err_free_ingenic_tcu;
+	}
+
+	of_address_to_resource(np, 0, &res);
+
+	ingenic_tcu_base = base;
+
+	tcu->map = regmap_init_mmio(NULL, base, &ingenic_tcu_regmap_config);
+	if (IS_ERR(tcu->map)) {
+		ret = PTR_ERR(tcu->map);
+		goto err_iounmap;
+	}
+
+	tcu->clk = of_clk_get_by_name(np, "tcu");
+	if (IS_ERR(tcu->clk)) {
+		ret = PTR_ERR(tcu->clk);
+		pr_crit("ingenic-tcu: Unable to find TCU clock: %i\n", ret);
+		goto err_free_regmap;
+	}
+
+	ret = clk_prepare_enable(tcu->clk);
+	if (ret) {
+		pr_crit("ingenic-tcu: Unable to enable TCU clock: %i\n", ret);
+		goto err_clk_put;
+	}
+
+	ret = ingenic_tcu_intc_init(tcu, np);
+	if (ret)
+		goto err_clk_disable;
+
+	ret = ingenic_tcu_clk_init(tcu, np);
+	if (ret)
+		goto err_tcu_intc_cleanup;
+
+	if (!soc_info->has_ost) {
+		ret = ingenic_tcu_clocksource_init(tcu);
+		if (ret)
+			goto err_tcu_clk_cleanup;
+	}
+
+	ret = ingenic_tcu_timer_init(tcu);
+	if (ret)
+		goto err_tcu_clocksource_cleanup;
+
+
+	return 0;
+
+err_tcu_clocksource_cleanup:
+	if (!soc_info->has_ost)
+		ingenic_tcu_clocksource_cleanup(tcu);
+err_tcu_clk_cleanup:
+	ingenic_tcu_clk_cleanup(tcu, np);
+err_tcu_intc_cleanup:
+	ingenic_tcu_intc_cleanup(tcu);
+err_clk_disable:
+	clk_disable_unprepare(tcu->clk);
+err_clk_put:
+	clk_put(tcu->clk);
+err_free_regmap:
+	regmap_exit(tcu->map);
+err_iounmap:
+	iounmap(base);
+	release_mem_region(res.start, resource_size(&res));
+err_free_ingenic_tcu:
+	kfree(tcu);
+	return ret;
+}
+
+TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",  ingenic_tcu_init);
+TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init);
+TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",  ingenic_tcu_init);
+
+
+static int __init ingenic_tcu_probe(struct platform_device *pdev)
+{
+	platform_set_drvdata(pdev, ingenic_tcu);
+
+	regmap_attach_dev(&pdev->dev, ingenic_tcu->map,
+			  &ingenic_tcu_regmap_config);
+
+	return devm_of_platform_populate(&pdev->dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ingenic_tcu_suspend(struct device *dev)
+{
+	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
+
+	clk_disable(tcu->cs_clk);
+	clk_disable(tcu->timer_clk);
+	clk_disable(tcu->clk);
+	return 0;
+}
+
+static int ingenic_tcu_resume(struct device *dev)
+{
+	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_enable(tcu->clk);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(tcu->timer_clk);
+	if (ret)
+		goto err_tcu_clk_disable;
+
+	ret = clk_enable(tcu->cs_clk);
+	if (ret)
+		goto err_tcu_timer_clk_disable;
+
+	return 0;
+
+err_tcu_timer_clk_disable:
+	clk_disable(tcu->timer_clk);
+err_tcu_clk_disable:
+	clk_disable(tcu->clk);
+	return ret;
+}
+
+static const struct dev_pm_ops ingenic_tcu_pm_ops = {
+	/* _noirq: We want the TCU clock to be gated last / ungated first */
+	.suspend_noirq = ingenic_tcu_suspend,
+	.resume_noirq  = ingenic_tcu_resume,
+};
+#define INGENIC_TCU_PM_OPS (&ingenic_tcu_pm_ops)
+
+#else
+#define INGENIC_TCU_PM_OPS NULL
+#endif /* CONFIG_PM_SUSPEND */
+
+static struct platform_driver ingenic_tcu_driver = {
+	.driver = {
+		.name	= "ingenic-tcu",
+		.pm	= INGENIC_TCU_PM_OPS,
+		.of_match_table = ingenic_tcu_of_match,
+	},
+};
+builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe);
+
+bool ingenic_tcu_pwm_can_use_chn(unsigned int channel)
+{
+	return !!(ingenic_tcu->pwm_channels_mask & BIT(channel));
+}
+EXPORT_SYMBOL_GPL(ingenic_tcu_pwm_can_use_chn);
diff --git a/drivers/clocksource/ingenic-timer.h b/drivers/clocksource/ingenic-timer.h
new file mode 100644
index 000000000000..fa43da836ab6
--- /dev/null
+++ b/drivers/clocksource/ingenic-timer.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
+#define __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
+
+#include <linux/compiler_types.h>
+
+/*
+ * README: For use *ONLY* by the ingenic-ost driver.
+ * Regular drivers which want to access the TCU registers
+ * must have ingenic-timer as parent and retrieve the regmap
+ * doing dev_get_regmap(pdev->dev.parent);
+ */
+extern void __iomem *ingenic_tcu_base;
+
+#endif /* __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__ */
diff --git a/include/linux/mfd/ingenic-tcu.h b/include/linux/mfd/ingenic-tcu.h
index ab16ad283def..dac3fac35c1e 100644
--- a/include/linux/mfd/ingenic-tcu.h
+++ b/include/linux/mfd/ingenic-tcu.h
@@ -53,4 +53,6 @@
 #define TCU_REG_TCNTc(c)	(TCU_REG_TCNT0 + ((c) * TCU_CHANNEL_STRIDE))
 #define TCU_REG_TCSRc(c)	(TCU_REG_TCSR0 + ((c) * TCU_CHANNEL_STRIDE))
 
+bool ingenic_tcu_pwm_can_use_chn(unsigned int channel);
+
 #endif /* __LINUX_MFD_INGENIC_TCU_H_ */
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 05/27] clocksource: Add driver for the Ingenic JZ47xx OST
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (3 preceding siblings ...)
  2018-12-27 18:12 ` [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 06/27] MAINTAINERS: Add myself as maintainer for Ingenic TCU drivers Paul Cercueil
                   ` (21 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Maarten ter Huurne, Paul Cercueil

From: Maarten ter Huurne <maarten@treewalker.org>

OST is the OS Timer, a 64-bit timer/counter with buffered reading.

SoCs before the JZ4770 had (if any) a 32-bit OST; the JZ4770 and
JZ4780 have a 64-bit OST.

This driver will register both a clocksource and a sched_clock to the
system.

Signed-off-by: Maarten ter Huurne <maarten@treewalker.org>
Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: - Get rid of SoC IDs; pass pointer to ingenic_ost_soc_info as
           devicetree match data instead.
         - Use device_get_match_data() instead of the of_* variant
         - Handle error of dev_get_regmap() properly
    
     v7: Fix section mismatch by using builtin_platform_driver_probe()

     v8: builtin_platform_driver_probe() does not work anymore in
         4.20-rc6? The probe function won't be called. Work around this
	 for now by using late_initcall.

     v9: No change

 drivers/clocksource/Kconfig       |   8 ++
 drivers/clocksource/Makefile      |   1 +
 drivers/clocksource/ingenic-ost.c | 215 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 224 insertions(+)
 create mode 100644 drivers/clocksource/ingenic-ost.c

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 4e69af15c3e7..e0646878b0de 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -648,4 +648,12 @@ config INGENIC_TIMER
 	help
 	  Support for the timer/counter unit of the Ingenic JZ SoCs.
 
+config INGENIC_OST
+	bool "Ingenic JZ47xx Operating System Timer"
+	depends on MIPS || COMPILE_TEST
+	depends on COMMON_CLK
+	select INGENIC_TIMER
+	help
+	  Support for the OS Timer of the Ingenic JZ4770 or similar SoC.
+
 endmenu
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 7c8f790dcf67..7faa8108574a 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_ASM9260_TIMER)		+= asm9260_timer.o
 obj-$(CONFIG_H8300_TMR8)		+= h8300_timer8.o
 obj-$(CONFIG_H8300_TMR16)		+= h8300_timer16.o
 obj-$(CONFIG_H8300_TPU)			+= h8300_tpu.o
+obj-$(CONFIG_INGENIC_OST)		+= ingenic-ost.o
 obj-$(CONFIG_INGENIC_TIMER)		+= ingenic-timer.o
 obj-$(CONFIG_CLKSRC_ST_LPC)		+= clksrc_st_lpc.o
 obj-$(CONFIG_X86_NUMACHIP)		+= numachip.o
diff --git a/drivers/clocksource/ingenic-ost.c b/drivers/clocksource/ingenic-ost.c
new file mode 100644
index 000000000000..cc0fee3efd29
--- /dev/null
+++ b/drivers/clocksource/ingenic-ost.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * JZ47xx SoCs TCU Operating System Timer driver
+ *
+ * Copyright (C) 2016 Maarten ter Huurne <maarten@treewalker.org>
+ * Copyright (C) 2018 Paul Cercueil <paul@crapouillou.net>
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/mfd/ingenic-tcu.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/sched_clock.h>
+
+#include "ingenic-timer.h"
+
+#define TCU_OST_TCSR_MASK	0xc0
+#define TCU_OST_TCSR_CNT_MD	BIT(15)
+
+#define TCU_OST_CHANNEL		15
+
+struct ingenic_ost_soc_info {
+	bool is64bit;
+};
+
+struct ingenic_ost {
+	struct regmap *map;
+	struct clk *clk;
+
+	struct clocksource cs;
+};
+
+static u64 notrace ingenic_ost_read_cntl(void)
+{
+	/* Bypass the regmap here as we must return as soon as possible */
+	return readl(ingenic_tcu_base + TCU_REG_OST_CNTL);
+}
+
+static u64 notrace ingenic_ost_read_cnth(void)
+{
+	/* Bypass the regmap here as we must return as soon as possible */
+	return readl(ingenic_tcu_base + TCU_REG_OST_CNTH);
+}
+
+static u64 notrace ingenic_ost_clocksource_read(struct clocksource *cs)
+{
+	u32 val1, val2;
+	u64 count, recount;
+	s64 diff;
+
+	/*
+	 * The buffering of the upper 32 bits of the timer prevents wrong
+	 * results from the bottom 32 bits overflowing due to the timer ticking
+	 * along. However, it does not prevent wrong results from simultaneous
+	 * reads of the timer, which could reset the buffer mid-read.
+	 * Since this kind of wrong read can happen only when the bottom bits
+	 * overflow, there will be minutes between wrong reads, so if we read
+	 * twice in succession, at least one of the reads will be correct.
+	 */
+
+	/* Bypass the regmap here as we must return as soon as possible */
+	val1 = readl(ingenic_tcu_base + TCU_REG_OST_CNTL);
+	val2 = readl(ingenic_tcu_base + TCU_REG_OST_CNTHBUF);
+	count = (u64)val1 | (u64)val2 << 32;
+
+	val1 = readl(ingenic_tcu_base + TCU_REG_OST_CNTL);
+	val2 = readl(ingenic_tcu_base + TCU_REG_OST_CNTHBUF);
+	recount = (u64)val1 | (u64)val2 << 32;
+
+	/*
+	 * A wrong read will produce a result that is 1<<32 too high: the bottom
+	 * part from before overflow and the upper part from after overflow.
+	 * Therefore, the lower value of the two reads is the correct value.
+	 */
+
+	diff = (s64)(recount - count);
+	if (unlikely(diff < 0))
+		count = recount;
+
+	return count;
+}
+
+static int __init ingenic_ost_probe(struct platform_device *pdev)
+{
+	const struct ingenic_ost_soc_info *soc_info;
+	struct device *dev = &pdev->dev;
+	struct ingenic_ost *ost;
+	struct clocksource *cs;
+	unsigned long rate, flags;
+	int err;
+
+	soc_info = device_get_match_data(dev);
+	if (!soc_info)
+		return -EINVAL;
+
+	ost = devm_kzalloc(dev, sizeof(*ost), GFP_KERNEL);
+	if (!ost)
+		return -ENOMEM;
+
+	ost->map = dev_get_regmap(dev->parent, NULL);
+	if (!ost->map) {
+		dev_err(dev, "regmap not found\n");
+		return -EINVAL;
+	}
+
+	ost->clk = devm_clk_get(dev, "ost");
+	if (IS_ERR(ost->clk))
+		return PTR_ERR(ost->clk);
+
+	err = clk_prepare_enable(ost->clk);
+	if (err)
+		return err;
+
+	/* Clear counter high/low registers */
+	if (soc_info->is64bit)
+		regmap_write(ost->map, TCU_REG_OST_CNTL, 0);
+	regmap_write(ost->map, TCU_REG_OST_CNTH, 0);
+
+	/* Don't reset counter at compare value. */
+	regmap_update_bits(ost->map, TCU_REG_OST_TCSR,
+			   TCU_OST_TCSR_MASK, TCU_OST_TCSR_CNT_MD);
+
+	rate = clk_get_rate(ost->clk);
+
+	/* Enable OST TCU channel */
+	regmap_write(ost->map, TCU_REG_TESR, BIT(TCU_OST_CHANNEL));
+
+	cs = &ost->cs;
+	cs->name	= "ingenic-ost";
+	cs->rating	= 320;
+	cs->flags	= CLOCK_SOURCE_IS_CONTINUOUS;
+
+	if (soc_info->is64bit) {
+		cs->mask = CLOCKSOURCE_MASK(64);
+		cs->read = ingenic_ost_clocksource_read;
+	} else {
+		cs->mask = CLOCKSOURCE_MASK(32);
+		cs->read = (u64 (*)(struct clocksource *))ingenic_ost_read_cnth;
+	}
+
+	err = clocksource_register_hz(cs, rate);
+	if (err) {
+		dev_err(dev, "clocksource registration failed: %d\n", err);
+		clk_disable_unprepare(ost->clk);
+		return err;
+	}
+
+	/* Cannot register a sched_clock with interrupts on */
+	local_irq_save(flags);
+	if (soc_info->is64bit)
+		sched_clock_register(ingenic_ost_read_cntl, 32, rate);
+	else
+		sched_clock_register(ingenic_ost_read_cnth, 32, rate);
+	local_irq_restore(flags);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ingenic_ost_suspend(struct device *dev)
+{
+	struct ingenic_ost *ost = dev_get_drvdata(dev);
+
+	clk_disable(ost->clk);
+	return 0;
+}
+
+static int ingenic_ost_resume(struct device *dev)
+{
+	struct ingenic_ost *ost = dev_get_drvdata(dev);
+
+	return clk_enable(ost->clk);
+}
+
+static SIMPLE_DEV_PM_OPS(ingenic_ost_pm_ops, ingenic_ost_suspend,
+			 ingenic_ost_resume);
+#define INGENIC_OST_PM_OPS (&ingenic_ost_pm_ops)
+#else
+#define INGENIC_OST_PM_OPS NULL
+#endif /* CONFIG_PM_SUSPEND */
+
+static const struct ingenic_ost_soc_info jz4725b_ost_soc_info = {
+	.is64bit = false,
+};
+
+static const struct ingenic_ost_soc_info jz4770_ost_soc_info = {
+	.is64bit = true,
+};
+
+static const struct of_device_id ingenic_ost_of_match[] = {
+	{ .compatible = "ingenic,jz4725b-ost", .data = &jz4725b_ost_soc_info, },
+	{ .compatible = "ingenic,jz4770-ost",  .data = &jz4770_ost_soc_info,  },
+	{ }
+};
+
+static struct platform_driver ingenic_ost_driver = {
+	.driver = {
+		.name	= "ingenic-ost",
+		.pm	= INGENIC_OST_PM_OPS,
+		.of_match_table = ingenic_ost_of_match,
+	},
+};
+
+/* FIXME: Using device_initcall (or buildin_platform_driver_probe) results
+ * in the driver not being probed at all. It worked in 4.18...
+ */
+static int __init ingenic_ost_drv_register(void)
+{
+	return platform_driver_probe(&ingenic_ost_driver,
+				     ingenic_ost_probe);
+}
+late_initcall(ingenic_ost_drv_register);
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 06/27] MAINTAINERS: Add myself as maintainer for Ingenic TCU drivers
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (4 preceding siblings ...)
  2018-12-27 18:12 ` [PATCH v9 05/27] clocksource: Add driver for the Ingenic JZ47xx OST Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2018-12-27 18:12 ` [PATCH v9 07/27] watchdog: jz4740: Use WDT clock provided by TCU driver Paul Cercueil
                   ` (20 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Add myself as maintainer for the ingenic-timer and ingenic-ost drivers.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v2: No change
    
     v3: No change
    
     v4: No change
    
     v5: Update with new files
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 MAINTAINERS | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index f3a5c97e3419..da08c2b9f261 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7401,6 +7401,15 @@ L:	linux-mtd@lists.infradead.org
 S:	Maintained
 F:	drivers/mtd/nand/raw/jz4780_*
 
+INGENIC TCU driver
+M:	Paul Cercueil <paul@crapouillou.net>
+S:	Maintained
+F:	drivers/clocksource/ingenic-ost.c
+F:	drivers/clocksource/ingenic-timer.c
+F:	drivers/clocksource/ingenic-timer.h
+F:	include/linux/mfd/ingenic-tcu.h
+F:	include/dt-bindings/clock/ingenic,tcu.h
+
 INOTIFY
 M:	Jan Kara <jack@suse.cz>
 R:	Amir Goldstein <amir73il@gmail.com>
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 07/27] watchdog: jz4740: Use WDT clock provided by TCU driver
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (5 preceding siblings ...)
  2018-12-27 18:12 ` [PATCH v9 06/27] MAINTAINERS: Add myself as maintainer for Ingenic TCU drivers Paul Cercueil
@ 2018-12-27 18:12 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 08/27] watchdog: jz4740: Use regmap " Paul Cercueil
                   ` (19 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:12 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Instead of requesting the "ext" clock and handling the watchdog clock
divider and gating in the watchdog driver, we now request and use the
"wdt" clock that is supplied by the ingenic-timer "TCU" driver.

The major benefit is that the watchdog's clock rate and parent can now
be specified from within devicetree, instead of hardcoded in the driver.

Also, this driver won't poke anymore into the TCU registers to
enable/disable the clock, as this is now handled by the TCU driver.

On the bad side, we break the ABI with devicetree - as we now request a
different clock. In this very specific case it is still okay, as every
Ingenic JZ47xx-based board out there compile the devicetree within the
kernel; so it's still time to push breaking changes, in order to get a
clean devicetree that won't break once it musn't.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---

Notes:
     v5: New patch
    
     v6: - Split regmap change to new patch 09/24
         - The code now sets the WDT clock to the smallest rate possible and
           calculates the maximum timeout from that
    
     v7: No change

     v8: No change

     v9: No change

 drivers/watchdog/Kconfig      |  2 +
 drivers/watchdog/jz4740_wdt.c | 86 +++++++++++++++++--------------------------
 2 files changed, 36 insertions(+), 52 deletions(-)

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 2d64333f4782..cfd7368fc3c0 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1497,7 +1497,9 @@ config INDYDOG
 config JZ4740_WDT
 	tristate "Ingenic jz4740 SoC hardware watchdog"
 	depends on MACH_JZ4740 || MACH_JZ4780
+	depends on COMMON_CLK
 	select WATCHDOG_CORE
+	select INGENIC_TIMER
 	help
 	  Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs.
 
diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c
index ec4d99a830ba..1d504ecf45e1 100644
--- a/drivers/watchdog/jz4740_wdt.c
+++ b/drivers/watchdog/jz4740_wdt.c
@@ -26,25 +26,9 @@
 #include <linux/err.h>
 #include <linux/of.h>
 
-#include <asm/mach-jz4740/timer.h>
-
 #define JZ_REG_WDT_TIMER_DATA     0x0
 #define JZ_REG_WDT_COUNTER_ENABLE 0x4
 #define JZ_REG_WDT_TIMER_COUNTER  0x8
-#define JZ_REG_WDT_TIMER_CONTROL  0xC
-
-#define JZ_WDT_CLOCK_PCLK 0x1
-#define JZ_WDT_CLOCK_RTC  0x2
-#define JZ_WDT_CLOCK_EXT  0x4
-
-#define JZ_WDT_CLOCK_DIV_SHIFT   3
-
-#define JZ_WDT_CLOCK_DIV_1    (0 << JZ_WDT_CLOCK_DIV_SHIFT)
-#define JZ_WDT_CLOCK_DIV_4    (1 << JZ_WDT_CLOCK_DIV_SHIFT)
-#define JZ_WDT_CLOCK_DIV_16   (2 << JZ_WDT_CLOCK_DIV_SHIFT)
-#define JZ_WDT_CLOCK_DIV_64   (3 << JZ_WDT_CLOCK_DIV_SHIFT)
-#define JZ_WDT_CLOCK_DIV_256  (4 << JZ_WDT_CLOCK_DIV_SHIFT)
-#define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT)
 
 #define DEFAULT_HEARTBEAT 5
 #define MAX_HEARTBEAT     2048
@@ -65,7 +49,8 @@ MODULE_PARM_DESC(heartbeat,
 struct jz4740_wdt_drvdata {
 	struct watchdog_device wdt;
 	void __iomem *base;
-	struct clk *rtc_clk;
+	struct clk *clk;
+	unsigned long clk_rate;
 };
 
 static int jz4740_wdt_ping(struct watchdog_device *wdt_dev)
@@ -80,31 +65,12 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
 				    unsigned int new_timeout)
 {
 	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
-	unsigned int rtc_clk_rate;
-	unsigned int timeout_value;
-	unsigned short clock_div = JZ_WDT_CLOCK_DIV_1;
-
-	rtc_clk_rate = clk_get_rate(drvdata->rtc_clk);
-
-	timeout_value = rtc_clk_rate * new_timeout;
-	while (timeout_value > 0xffff) {
-		if (clock_div == JZ_WDT_CLOCK_DIV_1024) {
-			/* Requested timeout too high;
-			* use highest possible value. */
-			timeout_value = 0xffff;
-			break;
-		}
-		timeout_value >>= 2;
-		clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT);
-	}
+	u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout);
 
 	writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE);
-	writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL);
 
 	writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA);
 	writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER);
-	writew(clock_div | JZ_WDT_CLOCK_RTC,
-		drvdata->base + JZ_REG_WDT_TIMER_CONTROL);
 
 	writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE);
 
@@ -114,7 +80,13 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
 
 static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
 {
-	jz4740_timer_enable_watchdog();
+	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+	int ret;
+
+	ret = clk_prepare_enable(drvdata->clk);
+	if (ret)
+		return ret;
+
 	jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
 
 	return 0;
@@ -125,7 +97,7 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev)
 	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
 
 	writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE);
-	jz4740_timer_disable_watchdog();
+	clk_disable_unprepare(drvdata->clk);
 
 	return 0;
 }
@@ -163,26 +135,42 @@ MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches);
 
 static int jz4740_wdt_probe(struct platform_device *pdev)
 {
+	struct device *dev = &pdev->dev;
 	struct jz4740_wdt_drvdata *drvdata;
 	struct watchdog_device *jz4740_wdt;
 	struct resource	*res;
+	long rate;
 	int ret;
 
-	drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata),
-			       GFP_KERNEL);
+	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
 	if (!drvdata)
 		return -ENOMEM;
 
-	if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
-		heartbeat = DEFAULT_HEARTBEAT;
+	drvdata->clk = devm_clk_get(&pdev->dev, "wdt");
+	if (IS_ERR(drvdata->clk)) {
+		dev_err(&pdev->dev, "cannot find WDT clock\n");
+		return PTR_ERR(drvdata->clk);
+	}
+
+	/* Set smallest clock possible */
+	rate = clk_round_rate(drvdata->clk, 1);
+	if (rate < 0)
+		return rate;
 
+	ret = clk_set_rate(drvdata->clk, rate);
+	if (ret)
+		return ret;
+
+	drvdata->clk_rate = rate;
 	jz4740_wdt = &drvdata->wdt;
 	jz4740_wdt->info = &jz4740_wdt_info;
 	jz4740_wdt->ops = &jz4740_wdt_ops;
-	jz4740_wdt->timeout = heartbeat;
 	jz4740_wdt->min_timeout = 1;
-	jz4740_wdt->max_timeout = MAX_HEARTBEAT;
-	jz4740_wdt->parent = &pdev->dev;
+	jz4740_wdt->max_timeout = 0xffff / rate;
+	jz4740_wdt->timeout = clamp(heartbeat,
+				    jz4740_wdt->min_timeout,
+				    jz4740_wdt->max_timeout);
+	jz4740_wdt->parent = dev;
 	watchdog_set_nowayout(jz4740_wdt, nowayout);
 	watchdog_set_drvdata(jz4740_wdt, drvdata);
 
@@ -191,12 +179,6 @@ static int jz4740_wdt_probe(struct platform_device *pdev)
 	if (IS_ERR(drvdata->base))
 		return PTR_ERR(drvdata->base);
 
-	drvdata->rtc_clk = devm_clk_get(&pdev->dev, "rtc");
-	if (IS_ERR(drvdata->rtc_clk)) {
-		dev_err(&pdev->dev, "cannot find RTC clock\n");
-		return PTR_ERR(drvdata->rtc_clk);
-	}
-
 	ret = devm_watchdog_register_device(&pdev->dev, &drvdata->wdt);
 	if (ret < 0)
 		return ret;
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 08/27] watchdog: jz4740: Use regmap provided by TCU driver
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (6 preceding siblings ...)
  2018-12-27 18:12 ` [PATCH v9 07/27] watchdog: jz4740: Use WDT clock provided by TCU driver Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 09/27] watchdog: jz4740: Avoid starting watchdog in set_timeout Paul Cercueil
                   ` (18 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Since we broke the ABI by changing the clock, the driver was also
updated to use the regmap provided by the TCU driver.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---

Notes:
     v6: New patch
    
     v7: No change

     v8: No change

     v9: No change

 drivers/watchdog/jz4740_wdt.c | 30 ++++++++++++++----------------
 1 file changed, 14 insertions(+), 16 deletions(-)

diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c
index 1d504ecf45e1..0f54306aee25 100644
--- a/drivers/watchdog/jz4740_wdt.c
+++ b/drivers/watchdog/jz4740_wdt.c
@@ -13,6 +13,7 @@
  *
  */
 
+#include <linux/mfd/ingenic-tcu.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
 #include <linux/types.h>
@@ -25,10 +26,7 @@
 #include <linux/slab.h>
 #include <linux/err.h>
 #include <linux/of.h>
-
-#define JZ_REG_WDT_TIMER_DATA     0x0
-#define JZ_REG_WDT_COUNTER_ENABLE 0x4
-#define JZ_REG_WDT_TIMER_COUNTER  0x8
+#include <linux/regmap.h>
 
 #define DEFAULT_HEARTBEAT 5
 #define MAX_HEARTBEAT     2048
@@ -48,7 +46,7 @@ MODULE_PARM_DESC(heartbeat,
 
 struct jz4740_wdt_drvdata {
 	struct watchdog_device wdt;
-	void __iomem *base;
+	struct regmap *map;
 	struct clk *clk;
 	unsigned long clk_rate;
 };
@@ -57,7 +55,7 @@ static int jz4740_wdt_ping(struct watchdog_device *wdt_dev)
 {
 	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
 
-	writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
 	return 0;
 }
 
@@ -67,12 +65,12 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
 	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
 	u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout);
 
-	writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
 
-	writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA);
-	writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER);
+	regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
 
-	writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
 
 	wdt_dev->timeout = new_timeout;
 	return 0;
@@ -96,7 +94,7 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev)
 {
 	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
 
-	writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
 	clk_disable_unprepare(drvdata->clk);
 
 	return 0;
@@ -138,7 +136,6 @@ static int jz4740_wdt_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	struct jz4740_wdt_drvdata *drvdata;
 	struct watchdog_device *jz4740_wdt;
-	struct resource	*res;
 	long rate;
 	int ret;
 
@@ -174,10 +171,11 @@ static int jz4740_wdt_probe(struct platform_device *pdev)
 	watchdog_set_nowayout(jz4740_wdt, nowayout);
 	watchdog_set_drvdata(jz4740_wdt, drvdata);
 
-	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	drvdata->base = devm_ioremap_resource(&pdev->dev, res);
-	if (IS_ERR(drvdata->base))
-		return PTR_ERR(drvdata->base);
+	drvdata->map = dev_get_regmap(dev->parent, NULL);
+	if (!drvdata->map) {
+		dev_err(dev, "regmap not found\n");
+		return -EINVAL;
+	}
 
 	ret = devm_watchdog_register_device(&pdev->dev, &drvdata->wdt);
 	if (ret < 0)
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 09/27] watchdog: jz4740: Avoid starting watchdog in set_timeout
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (7 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 08/27] watchdog: jz4740: Use regmap " Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 10/27] watchdog: jz4740: Drop dependency on MACH_JZ47xx, use COMPILE_TEST Paul Cercueil
                   ` (17 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Previously the jz4740_wdt_set_timeout() function was starting the timer
unconditionally, even if it was stopped when that function was entered.

Now, the timer will be restarted only if it was already running before
this function is called.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---

Notes:
     v6: New patch
    
     v7: No change

     v8: No change

     v9: No change

 drivers/watchdog/jz4740_wdt.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c
index 0f54306aee25..45d9495170e5 100644
--- a/drivers/watchdog/jz4740_wdt.c
+++ b/drivers/watchdog/jz4740_wdt.c
@@ -64,13 +64,15 @@ static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
 {
 	struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
 	u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout);
+	u32 tcer;
 
+	regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
 	regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
 
 	regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value);
 	regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
 
-	regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCER, tcer & TCU_WDT_TCER_TCEN);
 
 	wdt_dev->timeout = new_timeout;
 	return 0;
@@ -86,6 +88,7 @@ static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
 		return ret;
 
 	jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+	regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
 
 	return 0;
 }
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 10/27] watchdog: jz4740: Drop dependency on MACH_JZ47xx, use COMPILE_TEST
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (8 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 09/27] watchdog: jz4740: Avoid starting watchdog in set_timeout Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 11/27] pwm: jz4740: Apply configuration atomically Paul Cercueil
                   ` (16 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Depending on MACH_JZ47xx prevent us from creating a generic kernel that
works on more than one MIPS board. Instead, we just depend on MIPS being
set.

On other architectures, this driver can still be built, thanks to
COMPILE_TEST. This is used by automated tools to find bugs, for
instance.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 drivers/watchdog/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index cfd7368fc3c0..eb5dbb1db64d 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1496,7 +1496,7 @@ config INDYDOG
 
 config JZ4740_WDT
 	tristate "Ingenic jz4740 SoC hardware watchdog"
-	depends on MACH_JZ4740 || MACH_JZ4780
+	depends on MIPS || COMPILE_TEST
 	depends on COMMON_CLK
 	select WATCHDOG_CORE
 	select INGENIC_TIMER
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 11/27] pwm: jz4740: Apply configuration atomically
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (9 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 10/27] watchdog: jz4740: Drop dependency on MACH_JZ47xx, use COMPILE_TEST Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver Paul Cercueil
                   ` (15 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

This is cleaner, more future-proof, and incidentally it also fixes the
PWM resetting its config when stopped/started several times.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v9: New patch

 drivers/pwm/pwm-jz4740.c | 37 ++++++++++++-------------------------
 1 file changed, 12 insertions(+), 25 deletions(-)

diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index a7b134af5e04..b2f910413f81 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -83,17 +83,16 @@ static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 	jz4740_timer_disable(pwm->hwpwm);
 }
 
-static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
-			     int duty_ns, int period_ns)
+static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			    struct pwm_state *state)
 {
 	struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
 	unsigned long long tmp;
 	unsigned long period, duty;
 	unsigned int prescaler = 0;
 	uint16_t ctrl;
-	bool is_enabled;
 
-	tmp = (unsigned long long)clk_get_rate(jz4740->clk) * period_ns;
+	tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period;
 	do_div(tmp, 1000000000);
 	period = tmp;
 
@@ -105,16 +104,14 @@ static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 	if (prescaler == 6)
 		return -EINVAL;
 
-	tmp = (unsigned long long)period * duty_ns;
-	do_div(tmp, period_ns);
+	tmp = (unsigned long long)period * state->duty_cycle;
+	do_div(tmp, state->period);
 	duty = period - tmp;
 
 	if (duty >= period)
 		duty = period - 1;
 
-	is_enabled = jz4740_timer_is_enabled(pwm->hwpwm);
-	if (is_enabled)
-		jz4740_pwm_disable(chip, pwm);
+	jz4740_pwm_disable(chip, pwm);
 
 	jz4740_timer_set_count(pwm->hwpwm, 0);
 	jz4740_timer_set_duty(pwm->hwpwm, duty);
@@ -125,18 +122,7 @@ static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 
 	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
 
-	if (is_enabled)
-		jz4740_pwm_enable(chip, pwm);
-
-	return 0;
-}
-
-static int jz4740_pwm_set_polarity(struct pwm_chip *chip,
-		struct pwm_device *pwm, enum pwm_polarity polarity)
-{
-	uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm);
-
-	switch (polarity) {
+	switch (state->polarity) {
 	case PWM_POLARITY_NORMAL:
 		ctrl &= ~JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
 		break;
@@ -146,16 +132,17 @@ static int jz4740_pwm_set_polarity(struct pwm_chip *chip,
 	}
 
 	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
+
+	if (state->enabled)
+		jz4740_pwm_enable(chip, pwm);
+
 	return 0;
 }
 
 static const struct pwm_ops jz4740_pwm_ops = {
 	.request = jz4740_pwm_request,
 	.free = jz4740_pwm_free,
-	.config = jz4740_pwm_config,
-	.set_polarity = jz4740_pwm_set_polarity,
-	.enable = jz4740_pwm_enable,
-	.disable = jz4740_pwm_disable,
+	.apply = jz4740_pwm_apply,
 	.owner = THIS_MODULE,
 };
 
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (10 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 11/27] pwm: jz4740: Apply configuration atomically Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2019-01-05 19:42   ` Uwe Kleine-König
  2018-12-27 18:13 ` [PATCH v9 13/27] pwm: jz4740: Use clocks " Paul Cercueil
                   ` (14 subsequent siblings)
  26 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The ingenic-timer "TCU" driver provides us with a regmap, that we can
use to safely access the TCU registers.

While this driver is devicetree-compatible, it is never (as of now)
probed from devicetree, so this change does not introduce a ABI problem
with current devicetree files.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v9: New patch

 drivers/pwm/Kconfig      |  1 +
 drivers/pwm/pwm-jz4740.c | 74 +++++++++++++++++++++++++++++++-----------------
 2 files changed, 49 insertions(+), 26 deletions(-)

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 27e5dd47a01f..4ed003bc3d8d 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -202,6 +202,7 @@ config PWM_IMX
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
 	depends on MACH_INGENIC
+	select REGMAP
 	help
 	  Generic PWM framework driver for Ingenic JZ47xx based
 	  machines.
diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index b2f910413f81..8dfac5ffd71c 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -17,18 +17,19 @@
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/kernel.h>
+#include <linux/mfd/ingenic-tcu.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
-
-#include <asm/mach-jz4740/timer.h>
+#include <linux/regmap.h>
 
 #define NUM_PWM 8
 
 struct jz4740_pwm_chip {
 	struct pwm_chip chip;
 	struct clk *clk;
+	struct regmap *map;
 };
 
 static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip)
@@ -38,6 +39,8 @@ static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip)
 
 static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 {
+	struct jz4740_pwm_chip *jz = to_jz4740(chip);
+
 	/*
 	 * Timers 0 and 1 are used for system tasks, so they are unavailable
 	 * for use as PWMs.
@@ -45,42 +48,44 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 	if (pwm->hwpwm < 2)
 		return -EBUSY;
 
-	jz4740_timer_start(pwm->hwpwm);
+	regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm));
 
 	return 0;
 }
 
 static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 {
-	jz4740_timer_set_ctrl(pwm->hwpwm, 0);
+	struct jz4740_pwm_chip *jz = to_jz4740(chip);
 
-	jz4740_timer_stop(pwm->hwpwm);
+	regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm));
 }
 
 static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
-	uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm);
+	struct jz4740_pwm_chip *jz = to_jz4740(chip);
 
-	ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
-	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
-	jz4740_timer_enable(pwm->hwpwm);
+	/* Enable PWM output */
+	regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
+			   TCU_TCSR_PWM_EN, TCU_TCSR_PWM_EN);
 
+	/* Start counter */
+	regmap_write(jz->map, TCU_REG_TESR, BIT(pwm->hwpwm));
 	return 0;
 }
 
 static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
-	uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm);
+	struct jz4740_pwm_chip *jz = to_jz4740(chip);
 
 	/* Disable PWM output.
 	 * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the
 	 * counter is stopped, while in TCU1 mode the order does not matter.
 	 */
-	ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
-	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
+	regmap_update_bits(jz->map, TCU_REG_TCSRc(pwm->hwpwm),
+			   TCU_TCSR_PWM_EN, 0);
 
 	/* Stop counter */
-	jz4740_timer_disable(pwm->hwpwm);
+	regmap_write(jz->map, TCU_REG_TECR, BIT(pwm->hwpwm));
 }
 
 static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -90,7 +95,6 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	unsigned long long tmp;
 	unsigned long period, duty;
 	unsigned int prescaler = 0;
-	uint16_t ctrl;
 
 	tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period;
 	do_div(tmp, 1000000000);
@@ -113,26 +117,37 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 
 	jz4740_pwm_disable(chip, pwm);
 
-	jz4740_timer_set_count(pwm->hwpwm, 0);
-	jz4740_timer_set_duty(pwm->hwpwm, duty);
-	jz4740_timer_set_period(pwm->hwpwm, period);
+	/* Set abrupt shutdown */
+	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
+			   TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD);
+
+	/* Set clock prescale */
+	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
+			   TCU_TCSR_PRESCALE_MASK,
+			   prescaler << TCU_TCSR_PRESCALE_LSB);
+
+	/* Reset counter to 0 */
+	regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0);
 
-	ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
-		JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
+	/* Set duty */
+	regmap_write(jz4740->map, TCU_REG_TDHRc(pwm->hwpwm), duty);
 
-	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
+	/* Set period */
+	regmap_write(jz4740->map, TCU_REG_TDFRc(pwm->hwpwm), period);
 
+	/* Set polarity */
 	switch (state->polarity) {
 	case PWM_POLARITY_NORMAL:
-		ctrl &= ~JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
+		regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
+				   TCU_TCSR_PWM_INITL_HIGH, 0);
 		break;
 	case PWM_POLARITY_INVERSED:
-		ctrl |= JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
+		regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
+				   TCU_TCSR_PWM_INITL_HIGH,
+				   TCU_TCSR_PWM_INITL_HIGH);
 		break;
 	}
 
-	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
-
 	if (state->enabled)
 		jz4740_pwm_enable(chip, pwm);
 
@@ -149,8 +164,9 @@ static const struct pwm_ops jz4740_pwm_ops = {
 static int jz4740_pwm_probe(struct platform_device *pdev)
 {
 	struct jz4740_pwm_chip *jz4740;
+	struct device *dev = &pdev->dev;
 
-	jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL);
+	jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL);
 	if (!jz4740)
 		return -ENOMEM;
 
@@ -158,7 +174,13 @@ static int jz4740_pwm_probe(struct platform_device *pdev)
 	if (IS_ERR(jz4740->clk))
 		return PTR_ERR(jz4740->clk);
 
-	jz4740->chip.dev = &pdev->dev;
+	jz4740->map = dev_get_regmap(dev->parent, NULL);
+	if (!jz4740->map) {
+		dev_err(dev, "regmap not found\n");
+		return -EINVAL;
+	}
+
+	jz4740->chip.dev = dev;
 	jz4740->chip.ops = &jz4740_pwm_ops;
 	jz4740->chip.npwm = NUM_PWM;
 	jz4740->chip.base = -1;
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 13/27] pwm: jz4740: Use clocks from TCU driver
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (11 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2019-01-05 19:45   ` Uwe Kleine-König
  2018-12-27 18:13 ` [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation Paul Cercueil
                   ` (13 subsequent siblings)
  26 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The ingenic-timer "TCU" driver provides us with clocks, that can be
(un)gated, reparented or reclocked from devicetree, instead of having
these settings hardcoded in this driver.

While this driver is devicetree-compatible, it is never (as of now)
probed from devicetree, so this change does not introduce a ABI problem
with current devicetree files.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v9: New patch

 drivers/pwm/Kconfig      |  3 ++-
 drivers/pwm/pwm-jz4740.c | 39 ++++++++++++++++++++++++++-------------
 2 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 4ed003bc3d8d..0343f0c1238e 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -202,7 +202,8 @@ config PWM_IMX
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
 	depends on MACH_INGENIC
-	select REGMAP
+	depends on COMMON_CLK
+	select INGENIC_TIMER
 	help
 	  Generic PWM framework driver for Ingenic JZ47xx based
 	  machines.
diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index 8dfac5ffd71c..c6136bd4434b 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -28,7 +28,7 @@
 
 struct jz4740_pwm_chip {
 	struct pwm_chip chip;
-	struct clk *clk;
+	struct clk *clks[NUM_PWM];
 	struct regmap *map;
 };
 
@@ -40,6 +40,9 @@ static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip)
 static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct jz4740_pwm_chip *jz = to_jz4740(chip);
+	struct clk *clk;
+	char clk_name[16];
+	int ret;
 
 	/*
 	 * Timers 0 and 1 are used for system tasks, so they are unavailable
@@ -48,16 +51,29 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 	if (pwm->hwpwm < 2)
 		return -EBUSY;
 
-	regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm));
+	snprintf(clk_name, sizeof(clk_name), "timer%u", pwm->hwpwm);
 
+	clk = clk_get(chip->dev, clk_name);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	ret = clk_prepare_enable(clk);
+	if (ret) {
+		clk_put(clk);
+		return ret;
+	}
+
+	jz->clks[pwm->hwpwm] = clk;
 	return 0;
 }
 
 static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct jz4740_pwm_chip *jz = to_jz4740(chip);
+	struct clk *clk = jz->clks[pwm->hwpwm];
 
-	regmap_write(jz->map, TCU_REG_TSCR, BIT(pwm->hwpwm));
+	clk_disable_unprepare(clk);
+	clk_put(clk);
 }
 
 static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
@@ -92,16 +108,20 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 			    struct pwm_state *state)
 {
 	struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
+	struct clk *clk = jz4740->clks[pwm->hwpwm],
+		   *parent_clk = clk_get_parent(clk);
+	unsigned long rate, period, duty;
 	unsigned long long tmp;
-	unsigned long period, duty;
 	unsigned int prescaler = 0;
 
-	tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period;
+	rate = clk_get_rate(parent_clk);
+	tmp = (unsigned long long)rate * state->period;
 	do_div(tmp, 1000000000);
 	period = tmp;
 
 	while (period > 0xffff && prescaler < 6) {
 		period >>= 2;
+		rate >>= 2;
 		++prescaler;
 	}
 
@@ -121,10 +141,7 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
 			   TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD);
 
-	/* Set clock prescale */
-	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
-			   TCU_TCSR_PRESCALE_MASK,
-			   prescaler << TCU_TCSR_PRESCALE_LSB);
+	clk_set_rate(clk, rate);
 
 	/* Reset counter to 0 */
 	regmap_write(jz4740->map, TCU_REG_TCNTc(pwm->hwpwm), 0);
@@ -170,10 +187,6 @@ static int jz4740_pwm_probe(struct platform_device *pdev)
 	if (!jz4740)
 		return -ENOMEM;
 
-	jz4740->clk = devm_clk_get(&pdev->dev, "ext");
-	if (IS_ERR(jz4740->clk))
-		return PTR_ERR(jz4740->clk);
-
 	jz4740->map = dev_get_regmap(dev->parent, NULL);
 	if (!jz4740->map) {
 		dev_err(dev, "regmap not found\n");
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (12 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 13/27] pwm: jz4740: Use clocks " Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2019-01-05 19:57   ` Uwe Kleine-König
  2018-12-27 18:13 ` [PATCH v9 15/27] pwm: jz4740: Allow selection of PWM channels 0 and 1 Paul Cercueil
                   ` (12 subsequent siblings)
  26 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The previous algorithm hardcoded details about how the TCU clocks work.
The new algorithm will use clk_round_rate to find the perfect clock rate
for the PWM channel.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v9: New patch

 drivers/pwm/pwm-jz4740.c | 26 +++++++++++++++-----------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index c6136bd4434b..dd80a2cf6528 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -110,23 +110,27 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
 	struct clk *clk = jz4740->clks[pwm->hwpwm],
 		   *parent_clk = clk_get_parent(clk);
-	unsigned long rate, period, duty;
+	unsigned long rate, new_rate, period, duty;
 	unsigned long long tmp;
-	unsigned int prescaler = 0;
 
 	rate = clk_get_rate(parent_clk);
-	tmp = (unsigned long long)rate * state->period;
-	do_div(tmp, 1000000000);
-	period = tmp;
 
-	while (period > 0xffff && prescaler < 6) {
-		period >>= 2;
-		rate >>= 2;
-		++prescaler;
+	for (;;) {
+		tmp = (unsigned long long)rate * state->period;
+		do_div(tmp, 1000000000);
+
+		if (tmp <= 0xffff)
+			break;
+
+		new_rate = clk_round_rate(clk, rate - 1);
+
+		if (new_rate < rate)
+			rate = new_rate;
+		else
+			return -EINVAL;
 	}
 
-	if (prescaler == 6)
-		return -EINVAL;
+	period = tmp;
 
 	tmp = (unsigned long long)period * state->duty_cycle;
 	do_div(tmp, state->period);
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 15/27] pwm: jz4740: Allow selection of PWM channels 0 and 1
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (13 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 16/27] pwm: jz4740: Drop dependency on MACH_INGENIC, use COMPILE_TEST Paul Cercueil
                   ` (11 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The TCU channels 0 and 1 were previously reserved for system tasks, and
thus unavailable for PWM.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v6: New patch
    
     v7: No change

     v8: ingenic_tcu_[request,release]_channel are dropped. We now use
         the memory resources provided to the driver to detect whether
	 or not we are allowed to use the TCU channel.

     v9: Drop previous system. Call a API function provided by the
         clocksource driver to know if we can use the channel as PWM.

 drivers/pwm/pwm-jz4740.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index dd80a2cf6528..b6e105e774bd 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -44,11 +44,7 @@ static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 	char clk_name[16];
 	int ret;
 
-	/*
-	 * Timers 0 and 1 are used for system tasks, so they are unavailable
-	 * for use as PWMs.
-	 */
-	if (pwm->hwpwm < 2)
+	if (!ingenic_tcu_pwm_can_use_chn(pwm->hwpwm))
 		return -EBUSY;
 
 	snprintf(clk_name, sizeof(clk_name), "timer%u", pwm->hwpwm);
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 16/27] pwm: jz4740: Drop dependency on MACH_INGENIC, use COMPILE_TEST
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (14 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 15/27] pwm: jz4740: Allow selection of PWM channels 0 and 1 Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 17/27] pwm: jz4740: Remove unused devicetree compatible strings Paul Cercueil
                   ` (10 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Depending on MACH_INGENIC prevent us from creating a generic kernel that
works on more than one MIPS board. Instead, we just depend on MIPS being
set.

On other architectures, this driver can still be built, thanks to
COMPILE_TEST. This is used by automated tools to find bugs, for
instance.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Acked-by: Thierry Reding <thierry.reding@gmail.com>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 drivers/pwm/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 0343f0c1238e..d6f62d9cdc18 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -201,7 +201,7 @@ config PWM_IMX
 
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
-	depends on MACH_INGENIC
+	depends on MIPS || COMPILE_TEST
 	depends on COMMON_CLK
 	select INGENIC_TIMER
 	help
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 17/27] pwm: jz4740: Remove unused devicetree compatible strings
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (15 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 16/27] pwm: jz4740: Drop dependency on MACH_INGENIC, use COMPILE_TEST Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 18/27] clk: jz4740: Add TCU clock Paul Cercueil
                   ` (9 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Right now none of the Ingenic-based boards probe this driver from
devicetree. This driver defined three compatible strings for the exact
same behaviour. Before these strings are used, we can remove two of
them.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Acked-by: Thierry Reding <thierry.reding@gmail.com>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 drivers/pwm/pwm-jz4740.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index b6e105e774bd..3171ada8c212 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -215,8 +215,6 @@ static int jz4740_pwm_remove(struct platform_device *pdev)
 #ifdef CONFIG_OF
 static const struct of_device_id jz4740_pwm_dt_ids[] = {
 	{ .compatible = "ingenic,jz4740-pwm", },
-	{ .compatible = "ingenic,jz4770-pwm", },
-	{ .compatible = "ingenic,jz4780-pwm", },
 	{},
 };
 MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids);
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 18/27] clk: jz4740: Add TCU clock
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (16 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 17/27] pwm: jz4740: Remove unused devicetree compatible strings Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 19/27] MIPS: Kconfig: Select TCU timer driver when MACH_INGENIC is set Paul Cercueil
                   ` (8 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Add the missing TCU clock to the list of clocks supplied by the CGU for
the JZ4740 SoC.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Acked-by: Stephen Boyd <sboyd@kernel.org>
Acked-by: Rob Herring <robh@kernel.org>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 drivers/clk/ingenic/jz4740-cgu.c       | 6 ++++++
 include/dt-bindings/clock/jz4740-cgu.h | 1 +
 2 files changed, 7 insertions(+)

diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c
index 4479c102e899..d8ac7f2e183a 100644
--- a/drivers/clk/ingenic/jz4740-cgu.c
+++ b/drivers/clk/ingenic/jz4740-cgu.c
@@ -211,6 +211,12 @@ static const struct ingenic_cgu_clk_info jz4740_cgu_clocks[] = {
 		.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
 		.gate = { CGU_REG_CLKGR, 5 },
 	},
+
+	[JZ4740_CLK_TCU] = {
+		"tcu", CGU_CLK_GATE,
+		.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR, 1 },
+	},
 };
 
 static void __init jz4740_cgu_init(struct device_node *np)
diff --git a/include/dt-bindings/clock/jz4740-cgu.h b/include/dt-bindings/clock/jz4740-cgu.h
index 6ed83f926ae7..e82d77028581 100644
--- a/include/dt-bindings/clock/jz4740-cgu.h
+++ b/include/dt-bindings/clock/jz4740-cgu.h
@@ -34,5 +34,6 @@
 #define JZ4740_CLK_ADC		19
 #define JZ4740_CLK_I2C		20
 #define JZ4740_CLK_AIC		21
+#define JZ4740_CLK_TCU		22
 
 #endif /* __DT_BINDINGS_CLOCK_JZ4740_CGU_H__ */
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 19/27] MIPS: Kconfig: Select TCU timer driver when MACH_INGENIC is set
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (17 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 18/27] clk: jz4740: Add TCU clock Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 20/27] MIPS: jz4740: Add DTS nodes for the TCU drivers Paul Cercueil
                   ` (7 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

We cannot boot to userspace (not even initramfs) if the timer driver is
not present; so it makes sense to enable it unconditionally when
MACH_INGENIC is set.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 arch/mips/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 8272ea4c7264..8bd53a4e871b 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -390,6 +390,7 @@ config MACH_INGENIC
 	select BUILTIN_DTB
 	select USE_OF
 	select LIBFDT
+	select INGENIC_TIMER
 
 config LANTIQ
 	bool "Lantiq based platforms"
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 20/27] MIPS: jz4740: Add DTS nodes for the TCU drivers
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (18 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 19/27] MIPS: Kconfig: Select TCU timer driver when MACH_INGENIC is set Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 21/27] MIPS: qi_lb60: Move PWM devices to devicetree Paul Cercueil
                   ` (6 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Add DTS nodes for the JZ4780, JZ4770 and JZ4740 devicetree files.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: Fix register lengths in watchdog/pwm nodes
    
     v7: No change

     v8: - Fix wrong start address for PWM node
         - Add system timer and clocksource sub-nodes

     v9: Drop timer and clocksource sub-nodes

 arch/mips/boot/dts/ingenic/jz4740.dtsi | 51 +++++++++++++++++++++++---
 arch/mips/boot/dts/ingenic/jz4770.dtsi | 59 ++++++++++++++++++++++++++++++
 arch/mips/boot/dts/ingenic/jz4780.dtsi | 67 ++++++++++++++++++++++++++++++----
 3 files changed, 164 insertions(+), 13 deletions(-)

diff --git a/arch/mips/boot/dts/ingenic/jz4740.dtsi b/arch/mips/boot/dts/ingenic/jz4740.dtsi
index 6fb16fd24035..b4ccca7204e8 100644
--- a/arch/mips/boot/dts/ingenic/jz4740.dtsi
+++ b/arch/mips/boot/dts/ingenic/jz4740.dtsi
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <dt-bindings/clock/jz4740-cgu.h>
+#include <dt-bindings/clock/ingenic,tcu.h>
 
 / {
 	#address-cells = <1>;
@@ -45,12 +46,52 @@
 		#clock-cells = <1>;
 	};
 
-	watchdog: watchdog@10002000 {
-		compatible = "ingenic,jz4740-watchdog";
-		reg = <0x10002000 0x10>;
+	tcu: timer@10002000 {
+		compatible = "ingenic,jz4740-tcu";
+		reg = <0x10002000 0x1000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges = <0x0 0x10002000 0x1000>;
 
-		clocks = <&cgu JZ4740_CLK_RTC>;
-		clock-names = "rtc";
+		#clock-cells = <1>;
+
+		clocks = <&cgu JZ4740_CLK_RTC
+			  &cgu JZ4740_CLK_EXT
+			  &cgu JZ4740_CLK_PCLK
+			  &cgu JZ4740_CLK_TCU>;
+		clock-names = "rtc", "ext", "pclk", "tcu";
+
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		interrupt-parent = <&intc>;
+		interrupts = <23 22 21>;
+
+		watchdog: watchdog@0 {
+			compatible = "ingenic,jz4740-watchdog";
+			reg = <0x0 0xc>;
+
+			clocks = <&tcu TCU_CLK_WDT>;
+			clock-names = "wdt";
+		};
+
+		pwm: pwm@40 {
+			compatible = "ingenic,jz4740-pwm";
+			reg = <0x40 0x80>;
+
+			#pwm-cells = <3>;
+
+			clocks = <&tcu TCU_CLK_TIMER0
+				  &tcu TCU_CLK_TIMER1
+				  &tcu TCU_CLK_TIMER2
+				  &tcu TCU_CLK_TIMER3
+				  &tcu TCU_CLK_TIMER4
+				  &tcu TCU_CLK_TIMER5
+				  &tcu TCU_CLK_TIMER6
+				  &tcu TCU_CLK_TIMER7>;
+			clock-names = "timer0", "timer1", "timer2", "timer3",
+				      "timer4", "timer5", "timer6", "timer7";
+		};
 	};
 
 	rtc_dev: rtc@10003000 {
diff --git a/arch/mips/boot/dts/ingenic/jz4770.dtsi b/arch/mips/boot/dts/ingenic/jz4770.dtsi
index 49ede6c14ff3..89c7a4b9773c 100644
--- a/arch/mips/boot/dts/ingenic/jz4770.dtsi
+++ b/arch/mips/boot/dts/ingenic/jz4770.dtsi
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <dt-bindings/clock/jz4770-cgu.h>
+#include <dt-bindings/clock/ingenic,tcu.h>
 
 / {
 	#address-cells = <1>;
@@ -46,6 +47,64 @@
 		#clock-cells = <1>;
 	};
 
+	tcu: timer@10002000 {
+		compatible = "ingenic,jz4770-tcu";
+		reg = <0x10002000 0x1000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges = <0x0 0x10002000 0x1000>;
+
+		#clock-cells = <1>;
+
+		clocks = <&cgu JZ4770_CLK_RTC
+			  &cgu JZ4770_CLK_EXT
+			  &cgu JZ4770_CLK_PCLK
+			  &cgu JZ4770_CLK_EXT>;
+		clock-names = "rtc", "ext", "pclk", "tcu";
+
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		interrupt-parent = <&intc>;
+		interrupts = <27 26 25>;
+
+		watchdog: watchdog@0 {
+			compatible = "ingenic,jz4740-watchdog";
+			reg = <0x0 0xc>;
+
+			clocks = <&tcu TCU_CLK_WDT>;
+			clock-names = "wdt";
+		};
+
+		pwm: pwm@40 {
+			compatible = "ingenic,jz4740-pwm";
+			reg = <0x40 0x80>;
+
+			#pwm-cells = <3>;
+
+			clocks = <&tcu TCU_CLK_TIMER0
+				  &tcu TCU_CLK_TIMER1
+				  &tcu TCU_CLK_TIMER2
+				  &tcu TCU_CLK_TIMER3
+				  &tcu TCU_CLK_TIMER4
+				  &tcu TCU_CLK_TIMER5
+				  &tcu TCU_CLK_TIMER6
+				  &tcu TCU_CLK_TIMER7>;
+			clock-names = "timer0", "timer1", "timer2", "timer3",
+				      "timer4", "timer5", "timer6", "timer7";
+		};
+
+		ost: timer@e0 {
+			compatible = "ingenic,jz4770-ost";
+			reg = <0xe0 0x20>;
+
+			clocks = <&tcu TCU_CLK_OST>;
+			clock-names = "ost";
+
+			interrupts = <15>;
+		};
+	};
+
 	pinctrl: pin-controller@10010000 {
 		compatible = "ingenic,jz4770-pinctrl";
 		reg = <0x10010000 0x600>;
diff --git a/arch/mips/boot/dts/ingenic/jz4780.dtsi b/arch/mips/boot/dts/ingenic/jz4780.dtsi
index b03cdec56de9..9f45c075b4f8 100644
--- a/arch/mips/boot/dts/ingenic/jz4780.dtsi
+++ b/arch/mips/boot/dts/ingenic/jz4780.dtsi
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <dt-bindings/clock/jz4780-cgu.h>
+#include <dt-bindings/clock/ingenic,tcu.h>
 #include <dt-bindings/dma/jz4780-dma.h>
 
 / {
@@ -46,6 +47,64 @@
 		#clock-cells = <1>;
 	};
 
+	tcu: timer@10002000 {
+		compatible = "ingenic,jz4770-tcu";
+		reg = <0x10002000 0x1000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges = <0x0 0x10002000 0x1000>;
+
+		#clock-cells = <1>;
+
+		clocks = <&cgu JZ4780_CLK_RTCLK
+			  &cgu JZ4780_CLK_EXCLK
+			  &cgu JZ4780_CLK_PCLK
+			  &cgu JZ4780_CLK_EXCLK>;
+		clock-names = "rtc", "ext", "pclk", "tcu";
+
+		interrupt-controller;
+		#interrupt-cells = <1>;
+
+		interrupt-parent = <&intc>;
+		interrupts = <27 26 25>;
+
+		watchdog: watchdog@0 {
+			compatible = "ingenic,jz4780-watchdog";
+			reg = <0x0 0xc>;
+
+			clocks = <&tcu TCU_CLK_WDT>;
+			clock-names = "wdt";
+		};
+
+		pwm: pwm@40 {
+			compatible = "ingenic,jz4740-pwm";
+			reg = <0x40 0x80>;
+
+			#pwm-cells = <3>;
+
+			clocks = <&tcu TCU_CLK_TIMER0
+				  &tcu TCU_CLK_TIMER1
+				  &tcu TCU_CLK_TIMER2
+				  &tcu TCU_CLK_TIMER3
+				  &tcu TCU_CLK_TIMER4
+				  &tcu TCU_CLK_TIMER5
+				  &tcu TCU_CLK_TIMER6
+				  &tcu TCU_CLK_TIMER7>;
+			clock-names = "timer0", "timer1", "timer2", "timer3",
+				      "timer4", "timer5", "timer6", "timer7";
+		};
+
+		ost: timer@e0 {
+			compatible = "ingenic,jz4770-ost";
+			reg = <0xe0 0x20>;
+
+			clocks = <&tcu TCU_CLK_OST>;
+			clock-names = "ost";
+
+			interrupts = <15>;
+		};
+	};
+
 	rtc_dev: rtc@10003000 {
 		compatible = "ingenic,jz4780-rtc";
 		reg = <0x10003000 0x4c>;
@@ -239,14 +298,6 @@
 		status = "disabled";
 	};
 
-	watchdog: watchdog@10002000 {
-		compatible = "ingenic,jz4780-watchdog";
-		reg = <0x10002000 0x10>;
-
-		clocks = <&cgu JZ4780_CLK_RTCLK>;
-		clock-names = "rtc";
-	};
-
 	nemc: nemc@13410000 {
 		compatible = "ingenic,jz4780-nemc";
 		reg = <0x13410000 0x10000>;
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 21/27] MIPS: qi_lb60: Move PWM devices to devicetree
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (19 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 20/27] MIPS: jz4740: Add DTS nodes for the TCU drivers Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 22/27] MIPS: qi_lb60: Reduce system timer and clocksource to 750 kHz Paul Cercueil
                   ` (5 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

Probe the few drivers using PWMs from devicetree, now that we have a
devicetree node for the PWM driver.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 arch/mips/boot/dts/ingenic/qi_lb60.dts | 14 ++++++++++++++
 arch/mips/jz4740/board-qi_lb60.c       | 19 -------------------
 2 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/arch/mips/boot/dts/ingenic/qi_lb60.dts b/arch/mips/boot/dts/ingenic/qi_lb60.dts
index 76aaf8982554..85529a142409 100644
--- a/arch/mips/boot/dts/ingenic/qi_lb60.dts
+++ b/arch/mips/boot/dts/ingenic/qi_lb60.dts
@@ -9,6 +9,14 @@
 	chosen {
 		stdout-path = &uart0;
 	};
+
+	beeper {
+		compatible = "pwm-beeper";
+		pwms = <&pwm 4 0 0>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&pins_pwm4>;
+	};
 };
 
 &ext {
@@ -30,4 +38,10 @@
 		groups = "uart0-data";
 		bias-disable;
 	};
+
+	pins_pwm4: pwm4 {
+		function = "pwm4";
+		groups = "pwm4";
+		bias-disable;
+	};
 };
diff --git a/arch/mips/jz4740/board-qi_lb60.c b/arch/mips/jz4740/board-qi_lb60.c
index af0c8ace0141..cc556be656d6 100644
--- a/arch/mips/jz4740/board-qi_lb60.c
+++ b/arch/mips/jz4740/board-qi_lb60.c
@@ -27,7 +27,6 @@
 #include <linux/power_supply.h>
 #include <linux/power/jz4740-battery.h>
 #include <linux/power/gpio-charger.h>
-#include <linux/pwm.h>
 
 #include <linux/platform_data/jz4740/jz4740_nand.h>
 
@@ -392,17 +391,6 @@ static struct jz4740_mmc_platform_data qi_lb60_mmc_pdata = {
 	.power_active_low	= 1,
 };
 
-/* beeper */
-static struct pwm_lookup qi_lb60_pwm_lookup[] = {
-	PWM_LOOKUP("jz4740-pwm", 4, "pwm-beeper", NULL, 0,
-		   PWM_POLARITY_NORMAL),
-};
-
-static struct platform_device qi_lb60_pwm_beeper = {
-	.name = "pwm-beeper",
-	.id = -1,
-};
-
 /* charger */
 static char *qi_lb60_batteries[] = {
 	"battery",
@@ -451,10 +439,8 @@ static struct platform_device *jz_platform_devices[] __initdata = {
 	&jz4740_i2s_device,
 	&jz4740_codec_device,
 	&jz4740_adc_device,
-	&jz4740_pwm_device,
 	&jz4740_dma_device,
 	&qi_lb60_gpio_keys,
-	&qi_lb60_pwm_beeper,
 	&qi_lb60_charger_device,
 	&qi_lb60_audio_device,
 };
@@ -483,10 +469,6 @@ static struct pinctrl_map pin_map[] __initdata = {
 			"10010000.jz4740-pinctrl", "PD0", pin_cfg_bias_disable),
 	PIN_MAP_CONFIGS_PIN_DEFAULT("jz4740-mmc.0",
 			"10010000.jz4740-pinctrl", "PD2", pin_cfg_bias_disable),
-
-	/* PWM pin configuration */
-	PIN_MAP_MUX_GROUP_DEFAULT("jz4740-pwm",
-			"10010000.jz4740-pinctrl", "pwm4", "pwm4"),
 };
 
 
@@ -504,7 +486,6 @@ static int __init qi_lb60_init_platform_devices(void)
 	spi_register_board_info(qi_lb60_spi_board_info,
 				ARRAY_SIZE(qi_lb60_spi_board_info));
 
-	pwm_add_table(qi_lb60_pwm_lookup, ARRAY_SIZE(qi_lb60_pwm_lookup));
 	pinctrl_register_mappings(pin_map, ARRAY_SIZE(pin_map));
 
 	return platform_add_devices(jz_platform_devices,
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 22/27] MIPS: qi_lb60: Reduce system timer and clocksource to 750 kHz
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (20 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 21/27] MIPS: qi_lb60: Move PWM devices to devicetree Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 23/27] MIPS: CI20: Reduce system timer to 3 MHz Paul Cercueil
                   ` (4 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The default clock (12 MHz) is too fast for the system timer, which fails
to report time accurately.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: Remove ingenic,clocksource-channel property
    
     v7: No change

     v8: No change

     v9: No change

 arch/mips/boot/dts/ingenic/qi_lb60.dts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/mips/boot/dts/ingenic/qi_lb60.dts b/arch/mips/boot/dts/ingenic/qi_lb60.dts
index 85529a142409..de9d45e2c24a 100644
--- a/arch/mips/boot/dts/ingenic/qi_lb60.dts
+++ b/arch/mips/boot/dts/ingenic/qi_lb60.dts
@@ -45,3 +45,9 @@
 		bias-disable;
 	};
 };
+
+&tcu {
+	/* 750 kHz for the system timer and clocksource */
+	assigned-clocks = <&tcu TCU_CLK_TIMER0>, <&tcu TCU_CLK_TIMER1>;
+	assigned-clock-rates = <750000>, <750000>;
+};
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 23/27] MIPS: CI20: Reduce system timer to 3 MHz
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (21 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 22/27] MIPS: qi_lb60: Reduce system timer and clocksource to 750 kHz Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 24/27] MIPS: CI20: defconfig: enable OST driver Paul Cercueil
                   ` (3 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The default clock (48 MHz) is too fast for the system timer.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: Set also the rate for the clocksource channel's clock
    
     v7: No change

     v8: No change

     v9: Don't configure clock timer1, as the OS Timer is used as
         clocksource on this SoC

 arch/mips/boot/dts/ingenic/ci20.dts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/mips/boot/dts/ingenic/ci20.dts b/arch/mips/boot/dts/ingenic/ci20.dts
index 50cff3cbcc6d..700cf28a52ec 100644
--- a/arch/mips/boot/dts/ingenic/ci20.dts
+++ b/arch/mips/boot/dts/ingenic/ci20.dts
@@ -238,3 +238,9 @@
 		bias-disable;
 	};
 };
+
+&tcu {
+	/* 3 MHz for the system timer */
+	assigned-clocks = <&tcu TCU_CLK_TIMER0>;
+	assigned-clock-rates = <3000000>;
+};
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 24/27] MIPS: CI20: defconfig: enable OST driver
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (22 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 23/27] MIPS: CI20: Reduce system timer to 3 MHz Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 25/27] MIPS: GCW0: Reduce system timer to 750 kHz Paul Cercueil
                   ` (2 subsequent siblings)
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The OST driver provides a clocksource and sched_clock that are much more
accurate than the default ones.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 arch/mips/configs/ci20_defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/mips/configs/ci20_defconfig b/arch/mips/configs/ci20_defconfig
index 030ff9c205fb..9b09b9a7f943 100644
--- a/arch/mips/configs/ci20_defconfig
+++ b/arch/mips/configs/ci20_defconfig
@@ -111,6 +111,7 @@ CONFIG_RTC_CLASS=y
 CONFIG_RTC_DRV_JZ4740=y
 CONFIG_DMADEVICES=y
 CONFIG_DMA_JZ4780=y
+CONFIG_INGENIC_OST=y
 # CONFIG_IOMMU_SUPPORT is not set
 CONFIG_MEMORY=y
 CONFIG_EXT4_FS=y
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 25/27] MIPS: GCW0: Reduce system timer to 750 kHz
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (23 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 24/27] MIPS: CI20: defconfig: enable OST driver Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 26/27] MIPS: GCW0: defconfig: Enable OST, watchdog, PWM drivers Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 27/27] MIPS: jz4740: Drop obsolete code Paul Cercueil
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The default clock (12 MHz) is too fast for the system timer.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v8: New patch

     v9: Don't configure clock timer1, as the OS Timer is used as
         clocksource on this SoC

 arch/mips/boot/dts/ingenic/gcw0.dts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/mips/boot/dts/ingenic/gcw0.dts b/arch/mips/boot/dts/ingenic/gcw0.dts
index 35f0291e8d38..c26572f8b8ae 100644
--- a/arch/mips/boot/dts/ingenic/gcw0.dts
+++ b/arch/mips/boot/dts/ingenic/gcw0.dts
@@ -60,3 +60,9 @@
 	/* The WiFi module is connected to the UHC. */
 	status = "okay";
 };
+
+&tcu {
+	/* 750 kHz for the system timer */
+	assigned-clocks = <&tcu TCU_CLK_TIMER0>;
+	assigned-clock-rates = <750000>;
+};
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 26/27] MIPS: GCW0: defconfig: Enable OST, watchdog, PWM drivers
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (24 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 25/27] MIPS: GCW0: Reduce system timer to 750 kHz Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  2018-12-27 18:13 ` [PATCH v9 27/27] MIPS: jz4740: Drop obsolete code Paul Cercueil
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The OST driver provides a clocksource and sched_clock that are much more
accurate than the default ones.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v8: New patch

     v9: No change

 arch/mips/configs/gcw0_defconfig | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/arch/mips/configs/gcw0_defconfig b/arch/mips/configs/gcw0_defconfig
index 99ac1fa3b35f..7116400e8cbf 100644
--- a/arch/mips/configs/gcw0_defconfig
+++ b/arch/mips/configs/gcw0_defconfig
@@ -1,14 +1,14 @@
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_PREEMPT_VOLUNTARY=y
+CONFIG_EMBEDDED=y
 CONFIG_MACH_INGENIC=y
 CONFIG_JZ4770_GCW0=y
 CONFIG_HIGHMEM=y
-# CONFIG_BOUNCE is not set
-CONFIG_PREEMPT_VOLUNTARY=y
 # CONFIG_SECCOMP is not set
-CONFIG_NO_HZ_IDLE=y
-CONFIG_HIGH_RES_TIMERS=y
-CONFIG_EMBEDDED=y
-# CONFIG_BLK_DEV_BSG is not set
 # CONFIG_SUSPEND is not set
+# CONFIG_BLK_DEV_BSG is not set
+# CONFIG_BOUNCE is not set
 CONFIG_NET=y
 CONFIG_PACKET=y
 CONFIG_UNIX=y
@@ -20,8 +20,13 @@ CONFIG_SERIAL_8250=y
 # CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
 CONFIG_SERIAL_8250_CONSOLE=y
 CONFIG_SERIAL_8250_INGENIC=y
+CONFIG_WATCHDOG=y
+CONFIG_JZ4740_WDT=y
 CONFIG_USB=y
 CONFIG_USB_OHCI_HCD=y
 CONFIG_USB_OHCI_HCD_PLATFORM=y
 CONFIG_NOP_USB_XCEIV=y
+CONFIG_INGENIC_OST=y
+CONFIG_PWM=y
+CONFIG_PWM_JZ4740=y
 CONFIG_TMPFS=y
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v9 27/27] MIPS: jz4740: Drop obsolete code
  2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
                   ` (25 preceding siblings ...)
  2018-12-27 18:13 ` [PATCH v9 26/27] MIPS: GCW0: defconfig: Enable OST, watchdog, PWM drivers Paul Cercueil
@ 2018-12-27 18:13 ` Paul Cercueil
  26 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2018-12-27 18:13 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

The old clocksource/timer platform code is now obsoleted by the newly
introduced ingenic-timer and ingenic-ost drivers.

Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---

Notes:
     v5: New patch
    
     v6: No change
    
     v7: No change

     v8: No change

     v9: No change

 arch/mips/include/asm/mach-jz4740/platform.h |   1 -
 arch/mips/include/asm/mach-jz4740/timer.h    | 135 --------------------
 arch/mips/jz4740/Makefile                    |   3 +-
 arch/mips/jz4740/platform.c                  |   6 -
 arch/mips/jz4740/setup.c                     |   8 ++
 arch/mips/jz4740/time.c                      | 176 ---------------------------
 arch/mips/jz4740/timer.c                     |  51 --------
 7 files changed, 9 insertions(+), 371 deletions(-)
 delete mode 100644 arch/mips/include/asm/mach-jz4740/timer.h
 delete mode 100644 arch/mips/jz4740/time.c
 delete mode 100644 arch/mips/jz4740/timer.c

diff --git a/arch/mips/include/asm/mach-jz4740/platform.h b/arch/mips/include/asm/mach-jz4740/platform.h
index c0c932ac72a7..cd464d956882 100644
--- a/arch/mips/include/asm/mach-jz4740/platform.h
+++ b/arch/mips/include/asm/mach-jz4740/platform.h
@@ -29,7 +29,6 @@ extern struct platform_device jz4740_i2s_device;
 extern struct platform_device jz4740_pcm_device;
 extern struct platform_device jz4740_codec_device;
 extern struct platform_device jz4740_adc_device;
-extern struct platform_device jz4740_pwm_device;
 extern struct platform_device jz4740_dma_device;
 
 #endif
diff --git a/arch/mips/include/asm/mach-jz4740/timer.h b/arch/mips/include/asm/mach-jz4740/timer.h
deleted file mode 100644
index 8750a1d04e22..000000000000
--- a/arch/mips/include/asm/mach-jz4740/timer.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
- *  JZ4740 platform timer support
- *
- *  This program is free software; you can redistribute it and/or modify it
- *  under  the terms of the GNU General	 Public License as published by the
- *  Free Software Foundation;  either version 2 of the License, or (at your
- *  option) any later version.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-#ifndef __ASM_MACH_JZ4740_TIMER
-#define __ASM_MACH_JZ4740_TIMER
-
-#define JZ_REG_TIMER_STOP		0x0C
-#define JZ_REG_TIMER_STOP_SET		0x1C
-#define JZ_REG_TIMER_STOP_CLEAR		0x2C
-#define JZ_REG_TIMER_ENABLE		0x00
-#define JZ_REG_TIMER_ENABLE_SET		0x04
-#define JZ_REG_TIMER_ENABLE_CLEAR	0x08
-#define JZ_REG_TIMER_FLAG		0x10
-#define JZ_REG_TIMER_FLAG_SET		0x14
-#define JZ_REG_TIMER_FLAG_CLEAR		0x18
-#define JZ_REG_TIMER_MASK		0x20
-#define JZ_REG_TIMER_MASK_SET		0x24
-#define JZ_REG_TIMER_MASK_CLEAR		0x28
-
-#define JZ_REG_TIMER_DFR(x) (((x) * 0x10) + 0x30)
-#define JZ_REG_TIMER_DHR(x) (((x) * 0x10) + 0x34)
-#define JZ_REG_TIMER_CNT(x) (((x) * 0x10) + 0x38)
-#define JZ_REG_TIMER_CTRL(x) (((x) * 0x10) + 0x3C)
-
-#define JZ_TIMER_IRQ_HALF(x) BIT((x) + 0x10)
-#define JZ_TIMER_IRQ_FULL(x) BIT(x)
-
-#define JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN	BIT(9)
-#define JZ_TIMER_CTRL_PWM_ACTIVE_LOW		BIT(8)
-#define JZ_TIMER_CTRL_PWM_ENABLE		BIT(7)
-#define JZ_TIMER_CTRL_PRESCALE_MASK		0x1c
-#define JZ_TIMER_CTRL_PRESCALE_OFFSET		0x3
-#define JZ_TIMER_CTRL_PRESCALE_1		(0 << 3)
-#define JZ_TIMER_CTRL_PRESCALE_4		(1 << 3)
-#define JZ_TIMER_CTRL_PRESCALE_16		(2 << 3)
-#define JZ_TIMER_CTRL_PRESCALE_64		(3 << 3)
-#define JZ_TIMER_CTRL_PRESCALE_256		(4 << 3)
-#define JZ_TIMER_CTRL_PRESCALE_1024		(5 << 3)
-
-#define JZ_TIMER_CTRL_PRESCALER(x) ((x) << JZ_TIMER_CTRL_PRESCALE_OFFSET)
-
-#define JZ_TIMER_CTRL_SRC_EXT		BIT(2)
-#define JZ_TIMER_CTRL_SRC_RTC		BIT(1)
-#define JZ_TIMER_CTRL_SRC_PCLK		BIT(0)
-
-extern void __iomem *jz4740_timer_base;
-void __init jz4740_timer_init(void);
-
-void jz4740_timer_enable_watchdog(void);
-void jz4740_timer_disable_watchdog(void);
-
-static inline void jz4740_timer_stop(unsigned int timer)
-{
-	writel(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_STOP_SET);
-}
-
-static inline void jz4740_timer_start(unsigned int timer)
-{
-	writel(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_STOP_CLEAR);
-}
-
-static inline bool jz4740_timer_is_enabled(unsigned int timer)
-{
-	return readb(jz4740_timer_base + JZ_REG_TIMER_ENABLE) & BIT(timer);
-}
-
-static inline void jz4740_timer_enable(unsigned int timer)
-{
-	writeb(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_ENABLE_SET);
-}
-
-static inline void jz4740_timer_disable(unsigned int timer)
-{
-	writeb(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_ENABLE_CLEAR);
-}
-
-static inline void jz4740_timer_set_period(unsigned int timer, uint16_t period)
-{
-	writew(period, jz4740_timer_base + JZ_REG_TIMER_DFR(timer));
-}
-
-static inline void jz4740_timer_set_duty(unsigned int timer, uint16_t duty)
-{
-	writew(duty, jz4740_timer_base + JZ_REG_TIMER_DHR(timer));
-}
-
-static inline void jz4740_timer_set_count(unsigned int timer, uint16_t count)
-{
-	writew(count, jz4740_timer_base + JZ_REG_TIMER_CNT(timer));
-}
-
-static inline uint16_t jz4740_timer_get_count(unsigned int timer)
-{
-	return readw(jz4740_timer_base + JZ_REG_TIMER_CNT(timer));
-}
-
-static inline void jz4740_timer_ack_full(unsigned int timer)
-{
-	writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_FLAG_CLEAR);
-}
-
-static inline void jz4740_timer_irq_full_enable(unsigned int timer)
-{
-	writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_FLAG_CLEAR);
-	writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_MASK_CLEAR);
-}
-
-static inline void jz4740_timer_irq_full_disable(unsigned int timer)
-{
-	writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_MASK_SET);
-}
-
-static inline void jz4740_timer_set_ctrl(unsigned int timer, uint16_t ctrl)
-{
-	writew(ctrl, jz4740_timer_base + JZ_REG_TIMER_CTRL(timer));
-}
-
-static inline uint16_t jz4740_timer_get_ctrl(unsigned int timer)
-{
-	return readw(jz4740_timer_base + JZ_REG_TIMER_CTRL(timer));
-}
-
-#endif
diff --git a/arch/mips/jz4740/Makefile b/arch/mips/jz4740/Makefile
index 88d6aa7d000b..72eb805028a4 100644
--- a/arch/mips/jz4740/Makefile
+++ b/arch/mips/jz4740/Makefile
@@ -5,8 +5,7 @@
 
 # Object file lists.
 
-obj-y += prom.o time.o reset.o setup.o \
-	platform.o timer.o
+obj-y += prom.o reset.o setup.o platform.o
 
 CFLAGS_setup.o = -I$(src)/../../../scripts/dtc/libfdt
 
diff --git a/arch/mips/jz4740/platform.c b/arch/mips/jz4740/platform.c
index cbc5f8e87230..af0ecaeb4931 100644
--- a/arch/mips/jz4740/platform.c
+++ b/arch/mips/jz4740/platform.c
@@ -233,12 +233,6 @@ struct platform_device jz4740_adc_device = {
 	.resource	= jz4740_adc_resources,
 };
 
-/* PWM */
-struct platform_device jz4740_pwm_device = {
-	.name = "jz4740-pwm",
-	.id   = -1,
-};
-
 /* DMA */
 static struct resource jz4740_dma_resources[] = {
 	{
diff --git a/arch/mips/jz4740/setup.c b/arch/mips/jz4740/setup.c
index afb40f8bce96..099e4164afff 100644
--- a/arch/mips/jz4740/setup.c
+++ b/arch/mips/jz4740/setup.c
@@ -14,6 +14,8 @@
  *
  */
 
+#include <linux/clk-provider.h>
+#include <linux/clocksource.h>
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/irqchip.h>
@@ -101,3 +103,9 @@ void __init arch_init_irq(void)
 {
 	irqchip_init();
 }
+
+void __init plat_time_init(void)
+{
+	of_clk_init(NULL);
+	timer_probe();
+}
diff --git a/arch/mips/jz4740/time.c b/arch/mips/jz4740/time.c
deleted file mode 100644
index 2ca9160f642a..000000000000
--- a/arch/mips/jz4740/time.c
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
- *  JZ4740 platform time support
- *
- *  This program is free software; you can redistribute it and/or modify it
- *  under  the terms of the GNU General	 Public License as published by the
- *  Free Software Foundation;  either version 2 of the License, or (at your
- *  option) any later version.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-#include <linux/clk.h>
-#include <linux/clk-provider.h>
-#include <linux/interrupt.h>
-#include <linux/kernel.h>
-#include <linux/time.h>
-
-#include <linux/clockchips.h>
-#include <linux/sched_clock.h>
-
-#include <asm/mach-jz4740/clock.h>
-#include <asm/mach-jz4740/irq.h>
-#include <asm/mach-jz4740/timer.h>
-#include <asm/time.h>
-
-#include "clock.h"
-
-#define TIMER_CLOCKEVENT 0
-#define TIMER_CLOCKSOURCE 1
-
-static uint16_t jz4740_jiffies_per_tick;
-
-static u64 jz4740_clocksource_read(struct clocksource *cs)
-{
-	return jz4740_timer_get_count(TIMER_CLOCKSOURCE);
-}
-
-static struct clocksource jz4740_clocksource = {
-	.name = "jz4740-timer",
-	.rating = 200,
-	.read = jz4740_clocksource_read,
-	.mask = CLOCKSOURCE_MASK(16),
-	.flags = CLOCK_SOURCE_IS_CONTINUOUS,
-};
-
-static u64 notrace jz4740_read_sched_clock(void)
-{
-	return jz4740_timer_get_count(TIMER_CLOCKSOURCE);
-}
-
-static irqreturn_t jz4740_clockevent_irq(int irq, void *devid)
-{
-	struct clock_event_device *cd = devid;
-
-	jz4740_timer_ack_full(TIMER_CLOCKEVENT);
-
-	if (!clockevent_state_periodic(cd))
-		jz4740_timer_disable(TIMER_CLOCKEVENT);
-
-	cd->event_handler(cd);
-
-	return IRQ_HANDLED;
-}
-
-static int jz4740_clockevent_set_periodic(struct clock_event_device *evt)
-{
-	jz4740_timer_set_count(TIMER_CLOCKEVENT, 0);
-	jz4740_timer_set_period(TIMER_CLOCKEVENT, jz4740_jiffies_per_tick);
-	jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
-	jz4740_timer_enable(TIMER_CLOCKEVENT);
-
-	return 0;
-}
-
-static int jz4740_clockevent_resume(struct clock_event_device *evt)
-{
-	jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
-	jz4740_timer_enable(TIMER_CLOCKEVENT);
-
-	return 0;
-}
-
-static int jz4740_clockevent_shutdown(struct clock_event_device *evt)
-{
-	jz4740_timer_disable(TIMER_CLOCKEVENT);
-
-	return 0;
-}
-
-static int jz4740_clockevent_set_next(unsigned long evt,
-	struct clock_event_device *cd)
-{
-	jz4740_timer_set_count(TIMER_CLOCKEVENT, 0);
-	jz4740_timer_set_period(TIMER_CLOCKEVENT, evt);
-	jz4740_timer_enable(TIMER_CLOCKEVENT);
-
-	return 0;
-}
-
-static struct clock_event_device jz4740_clockevent = {
-	.name = "jz4740-timer",
-	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
-	.set_next_event = jz4740_clockevent_set_next,
-	.set_state_shutdown = jz4740_clockevent_shutdown,
-	.set_state_periodic = jz4740_clockevent_set_periodic,
-	.set_state_oneshot = jz4740_clockevent_shutdown,
-	.tick_resume = jz4740_clockevent_resume,
-	.rating = 200,
-#ifdef CONFIG_MACH_JZ4740
-	.irq = JZ4740_IRQ_TCU0,
-#endif
-#if defined(CONFIG_MACH_JZ4770) || defined(CONFIG_MACH_JZ4780)
-	.irq = JZ4780_IRQ_TCU2,
-#endif
-};
-
-static struct irqaction timer_irqaction = {
-	.handler	= jz4740_clockevent_irq,
-	.flags		= IRQF_PERCPU | IRQF_TIMER,
-	.name		= "jz4740-timerirq",
-	.dev_id		= &jz4740_clockevent,
-};
-
-void __init plat_time_init(void)
-{
-	int ret;
-	uint32_t clk_rate;
-	uint16_t ctrl;
-	struct clk *ext_clk;
-
-	of_clk_init(NULL);
-	jz4740_timer_init();
-
-	ext_clk = clk_get(NULL, "ext");
-	if (IS_ERR(ext_clk))
-		panic("unable to get ext clock");
-	clk_rate = clk_get_rate(ext_clk) >> 4;
-	clk_put(ext_clk);
-
-	jz4740_jiffies_per_tick = DIV_ROUND_CLOSEST(clk_rate, HZ);
-
-	clockevent_set_clock(&jz4740_clockevent, clk_rate);
-	jz4740_clockevent.min_delta_ns = clockevent_delta2ns(100, &jz4740_clockevent);
-	jz4740_clockevent.min_delta_ticks = 100;
-	jz4740_clockevent.max_delta_ns = clockevent_delta2ns(0xffff, &jz4740_clockevent);
-	jz4740_clockevent.max_delta_ticks = 0xffff;
-	jz4740_clockevent.cpumask = cpumask_of(0);
-
-	clockevents_register_device(&jz4740_clockevent);
-
-	ret = clocksource_register_hz(&jz4740_clocksource, clk_rate);
-
-	if (ret)
-		printk(KERN_ERR "Failed to register clocksource: %d\n", ret);
-
-	sched_clock_register(jz4740_read_sched_clock, 16, clk_rate);
-
-	setup_irq(jz4740_clockevent.irq, &timer_irqaction);
-
-	ctrl = JZ_TIMER_CTRL_PRESCALE_16 | JZ_TIMER_CTRL_SRC_EXT;
-
-	jz4740_timer_set_ctrl(TIMER_CLOCKEVENT, ctrl);
-	jz4740_timer_set_ctrl(TIMER_CLOCKSOURCE, ctrl);
-
-	jz4740_timer_set_period(TIMER_CLOCKEVENT, jz4740_jiffies_per_tick);
-	jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
-
-	jz4740_timer_set_period(TIMER_CLOCKSOURCE, 0xffff);
-
-	jz4740_timer_enable(TIMER_CLOCKEVENT);
-	jz4740_timer_enable(TIMER_CLOCKSOURCE);
-}
diff --git a/arch/mips/jz4740/timer.c b/arch/mips/jz4740/timer.c
deleted file mode 100644
index 777877feef71..000000000000
--- a/arch/mips/jz4740/timer.c
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
- *  JZ4740 platform timer support
- *
- *  This program is free software; you can redistribute it and/or modify it
- *  under  the terms of the GNU General	 Public License as published by the
- *  Free Software Foundation;  either version 2 of the License, or (at your
- *  option) any later version.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-#include <linux/export.h>
-#include <linux/io.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-
-#include <asm/mach-jz4740/base.h>
-#include <asm/mach-jz4740/timer.h>
-
-void __iomem *jz4740_timer_base;
-EXPORT_SYMBOL_GPL(jz4740_timer_base);
-
-void jz4740_timer_enable_watchdog(void)
-{
-	writel(BIT(16), jz4740_timer_base + JZ_REG_TIMER_STOP_CLEAR);
-}
-EXPORT_SYMBOL_GPL(jz4740_timer_enable_watchdog);
-
-void jz4740_timer_disable_watchdog(void)
-{
-	writel(BIT(16), jz4740_timer_base + JZ_REG_TIMER_STOP_SET);
-}
-EXPORT_SYMBOL_GPL(jz4740_timer_disable_watchdog);
-
-void __init jz4740_timer_init(void)
-{
-	jz4740_timer_base = ioremap(JZ4740_TCU_BASE_ADDR, 0x100);
-
-	if (!jz4740_timer_base)
-		panic("Failed to ioremap timer registers");
-
-	/* Disable all timer clocks except for those used as system timers */
-	writel(0x000100fc, jz4740_timer_base + JZ_REG_TIMER_STOP_SET);
-
-	/* Timer irqs are unmasked by default, mask them */
-	writel(0x00ff00ff, jz4740_timer_base + JZ_REG_TIMER_MASK_SET);
-}
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 03/27] dt-bindings: Add doc for the Ingenic TCU drivers
  2018-12-27 18:12 ` [PATCH v9 03/27] dt-bindings: Add doc for the Ingenic TCU drivers Paul Cercueil
@ 2018-12-28 21:48   ` Rob Herring
  0 siblings, 0 replies; 43+ messages in thread
From: Rob Herring @ 2018-12-28 21:48 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Jonathan Corbet, Uwe Kleine-König,
	linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk, Paul Cercueil

On Thu, 27 Dec 2018 19:12:55 +0100, Paul Cercueil wrote:
> Add documentation about how to properly use the Ingenic TCU
> (Timer/Counter Unit) drivers from devicetree.
> 
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
> ---
> 
> Notes:
>      v4: New patch in this series. Corresponds to V2 patches 3-4-5 with
>          added content.
>     
>      v5: - Edited PWM/watchdog DT bindings documentation to point to the new
>            document.
>          - Moved main document to
>            Documentation/devicetree/bindings/timer/ingenic,tcu.txt
>          - Updated documentation to reflect the new devicetree bindings.
>     
>      v6: - Removed PWM/watchdog documentation files as asked by upstream
>          - Removed doc about properties that should be implicit
>          - Removed doc about ingenic,timer-channel /
>            ingenic,clocksource-channel as they are gone
>          - Fix WDT clock name in the binding doc
>          - Fix lengths of register areas in watchdog/pwm nodes
>     
>      v7: No change
> 
>      v8: - Fix address of the PWM node
>          - Added doc about system timer and clocksource children nodes
> 
>      v9: - Remove doc about system timer and clocksource children
>            nodes...
> 	 - Add doc about ingenic,pwm-channels-mask property
> 
>  .../devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt |  25 ----
>  .../devicetree/bindings/timer/ingenic,tcu.txt      | 139 +++++++++++++++++++++
>  .../bindings/watchdog/ingenic,jz4740-wdt.txt       |  17 ---
>  3 files changed, 139 insertions(+), 42 deletions(-)
>  delete mode 100644 Documentation/devicetree/bindings/pwm/ingenic,jz47xx-pwm.txt
>  create mode 100644 Documentation/devicetree/bindings/timer/ingenic,tcu.txt
>  delete mode 100644 Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt
> 

Reviewed-by: Rob Herring <robh@kernel.org>

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver
  2018-12-27 18:13 ` [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver Paul Cercueil
@ 2019-01-05 19:42   ` Uwe Kleine-König
  2019-01-05 20:46     ` Paul Cercueil
  0 siblings, 1 reply; 43+ messages in thread
From: Uwe Kleine-König @ 2019-01-05 19:42 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

On Thu, Dec 27, 2018 at 07:13:04PM +0100, Paul Cercueil wrote:
> The ingenic-timer "TCU" driver provides us with a regmap, that we can
> use to safely access the TCU registers.
> 
> While this driver is devicetree-compatible, it is never (as of now)
> probed from devicetree, so this change does not introduce a ABI problem
> with current devicetree files.

Does it change behaviour? If so, how?

> @@ -113,26 +117,37 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>  
>  	jz4740_pwm_disable(chip, pwm);
>  
> -	jz4740_timer_set_count(pwm->hwpwm, 0);
> -	jz4740_timer_set_duty(pwm->hwpwm, duty);
> -	jz4740_timer_set_period(pwm->hwpwm, period);
> +	/* Set abrupt shutdown */
> +	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
> +			   TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD);

I think I already pointed that out before: abrupt mode is wrong. If
.apply is called with a new set of parameters the currently running
period with the old values is expected to complete before the new values
take effect.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 13/27] pwm: jz4740: Use clocks from TCU driver
  2018-12-27 18:13 ` [PATCH v9 13/27] pwm: jz4740: Use clocks " Paul Cercueil
@ 2019-01-05 19:45   ` Uwe Kleine-König
  2019-01-05 20:52     ` Paul Cercueil
  0 siblings, 1 reply; 43+ messages in thread
From: Uwe Kleine-König @ 2019-01-05 19:45 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hello,

On Thu, Dec 27, 2018 at 07:13:05PM +0100, Paul Cercueil wrote:
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 4ed003bc3d8d..0343f0c1238e 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -202,7 +202,8 @@ config PWM_IMX
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> -	select REGMAP
> +	depends on COMMON_CLK
> +	select INGENIC_TIMER

Did you drop REGMAP on purpose?

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2018-12-27 18:13 ` [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation Paul Cercueil
@ 2019-01-05 19:57   ` Uwe Kleine-König
  2019-01-05 21:05     ` Paul Cercueil
  0 siblings, 1 reply; 43+ messages in thread
From: Uwe Kleine-König @ 2019-01-05 19:57 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

On Thu, Dec 27, 2018 at 07:13:06PM +0100, Paul Cercueil wrote:
> The previous algorithm hardcoded details about how the TCU clocks work.
> The new algorithm will use clk_round_rate to find the perfect clock rate
> for the PWM channel.
> 
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
> ---
> 
> Notes:
>      v9: New patch
> 
>  drivers/pwm/pwm-jz4740.c | 26 +++++++++++++++-----------
>  1 file changed, 15 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
> index c6136bd4434b..dd80a2cf6528 100644
> --- a/drivers/pwm/pwm-jz4740.c
> +++ b/drivers/pwm/pwm-jz4740.c
> @@ -110,23 +110,27 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>  	struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
>  	struct clk *clk = jz4740->clks[pwm->hwpwm],
>  		   *parent_clk = clk_get_parent(clk);
> -	unsigned long rate, period, duty;
> +	unsigned long rate, new_rate, period, duty;
>  	unsigned long long tmp;
> -	unsigned int prescaler = 0;
>  
>  	rate = clk_get_rate(parent_clk);
> -	tmp = (unsigned long long)rate * state->period;
> -	do_div(tmp, 1000000000);
> -	period = tmp;
>  
> -	while (period > 0xffff && prescaler < 6) {
> -		period >>= 2;
> -		rate >>= 2;
> -		++prescaler;
> +	for (;;) {
> +		tmp = (unsigned long long)rate * state->period;
> +		do_div(tmp, 1000000000);

NSEC_PER_SEC?

> +
> +		if (tmp <= 0xffff)
> +			break;
> +
> +		new_rate = clk_round_rate(clk, rate - 1);
> +
> +		if (new_rate < rate)
> +			rate = new_rate;
> +		else
> +			return -EINVAL;

You are assuming stuff here about the parent clk which isn't guaranteed
(AFAICT) by the clk framework: If you call clk_round_rate(clk, rate - 1)
this might well return rate even if the clock could run slower than
rate.

Wouldn't it make sense to start iterating with rate = 0xffff * 1e9 /
period? Otherwise you get bad configurations if rate is considerable
slower than necessary.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver
  2019-01-05 19:42   ` Uwe Kleine-König
@ 2019-01-05 20:46     ` Paul Cercueil
  2019-01-05 21:18       ` Uwe Kleine-König
  0 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2019-01-05 20:46 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hi Uwe,

On Sat, Jan 5, 2019 at 4:42 PM, Uwe Kleine-König 
<u.kleine-koenig@pengutronix.de> wrote:
> On Thu, Dec 27, 2018 at 07:13:04PM +0100, Paul Cercueil wrote:
>>  The ingenic-timer "TCU" driver provides us with a regmap, that we 
>> can
>>  use to safely access the TCU registers.
>> 
>>  While this driver is devicetree-compatible, it is never (as of now)
>>  probed from devicetree, so this change does not introduce a ABI 
>> problem
>>  with current devicetree files.
> 
> Does it change behaviour? If so, how?

No, it does not change the behaviour.

>>  @@ -113,26 +117,37 @@ static int jz4740_pwm_apply(struct pwm_chip 
>> *chip, struct pwm_device *pwm,
>> 
>>   	jz4740_pwm_disable(chip, pwm);
>> 
>>  -	jz4740_timer_set_count(pwm->hwpwm, 0);
>>  -	jz4740_timer_set_duty(pwm->hwpwm, duty);
>>  -	jz4740_timer_set_period(pwm->hwpwm, period);
>>  +	/* Set abrupt shutdown */
>>  +	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
>>  +			   TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD);
> 
> I think I already pointed that out before: abrupt mode is wrong. If
> .apply is called with a new set of parameters the currently running
> period with the old values is expected to complete before the new 
> values
> take effect.

You pointed it, indeed; but I won't change it until I can verify that 
the
behaviour is correct (which does not seem to be the case even if I leave
this bit cleared). Besides, this is the TCU patchset, fixes and patches
unrelated to the TCU don't belong here.

> Best regards
> Uwe

Kind regards,
-Paul


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 13/27] pwm: jz4740: Use clocks from TCU driver
  2019-01-05 19:45   ` Uwe Kleine-König
@ 2019-01-05 20:52     ` Paul Cercueil
  2019-01-05 21:19       ` Uwe Kleine-König
  0 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2019-01-05 20:52 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hi,

On Sat, Jan 5, 2019 at 4:45 PM, Uwe Kleine-König 
<u.kleine-koenig@pengutronix.de> wrote:
> Hello,
> 
> On Thu, Dec 27, 2018 at 07:13:05PM +0100, Paul Cercueil wrote:
>>  diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>  index 4ed003bc3d8d..0343f0c1238e 100644
>>  --- a/drivers/pwm/Kconfig
>>  +++ b/drivers/pwm/Kconfig
>>  @@ -202,7 +202,8 @@ config PWM_IMX
>>   config PWM_JZ4740
>>   	tristate "Ingenic JZ47xx PWM support"
>>   	depends on MACH_INGENIC
>>  -	select REGMAP
>>  +	depends on COMMON_CLK
>>  +	select INGENIC_TIMER
> 
> Did you drop REGMAP on purpose?

INGENIC_TIMER selects it, so it's still a dependency but indirectly.
Should I restore it?

> Best regards
> Uwe
> 
> --
> Pengutronix e.K.                           | Uwe Kleine-König        
>     |
> Industrial Linux Solutions                 | 
> http://www.pengutronix.de/  |


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2019-01-05 19:57   ` Uwe Kleine-König
@ 2019-01-05 21:05     ` Paul Cercueil
  2019-01-05 21:27       ` Uwe Kleine-König
  0 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2019-01-05 21:05 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hi,

On Sat, Jan 5, 2019 at 4:57 PM, Uwe Kleine-König 
<u.kleine-koenig@pengutronix.de> wrote:
> On Thu, Dec 27, 2018 at 07:13:06PM +0100, Paul Cercueil wrote:
>>  The previous algorithm hardcoded details about how the TCU clocks 
>> work.
>>  The new algorithm will use clk_round_rate to find the perfect clock 
>> rate
>>  for the PWM channel.
>> 
>>  Signed-off-by: Paul Cercueil <paul@crapouillou.net>
>>  ---
>> 
>>  Notes:
>>       v9: New patch
>> 
>>   drivers/pwm/pwm-jz4740.c | 26 +++++++++++++++-----------
>>   1 file changed, 15 insertions(+), 11 deletions(-)
>> 
>>  diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
>>  index c6136bd4434b..dd80a2cf6528 100644
>>  --- a/drivers/pwm/pwm-jz4740.c
>>  +++ b/drivers/pwm/pwm-jz4740.c
>>  @@ -110,23 +110,27 @@ static int jz4740_pwm_apply(struct pwm_chip 
>> *chip, struct pwm_device *pwm,
>>   	struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
>>   	struct clk *clk = jz4740->clks[pwm->hwpwm],
>>   		   *parent_clk = clk_get_parent(clk);
>>  -	unsigned long rate, period, duty;
>>  +	unsigned long rate, new_rate, period, duty;
>>   	unsigned long long tmp;
>>  -	unsigned int prescaler = 0;
>> 
>>   	rate = clk_get_rate(parent_clk);
>>  -	tmp = (unsigned long long)rate * state->period;
>>  -	do_div(tmp, 1000000000);
>>  -	period = tmp;
>> 
>>  -	while (period > 0xffff && prescaler < 6) {
>>  -		period >>= 2;
>>  -		rate >>= 2;
>>  -		++prescaler;
>>  +	for (;;) {
>>  +		tmp = (unsigned long long)rate * state->period;
>>  +		do_div(tmp, 1000000000);
> 
> NSEC_PER_SEC?

Ok, didn't know about it.

>>  +
>>  +		if (tmp <= 0xffff)
>>  +			break;
>>  +
>>  +		new_rate = clk_round_rate(clk, rate - 1);
>>  +
>>  +		if (new_rate < rate)
>>  +			rate = new_rate;
>>  +		else
>>  +			return -EINVAL;
> 
> You are assuming stuff here about the parent clk which isn't 
> guaranteed
> (AFAICT) by the clk framework: If you call clk_round_rate(clk, rate - 
> 1)
> this might well return rate even if the clock could run slower than
> rate.

It may not be guaranteed by the clock framework itself, but it is 
guaranteed
to behave like that on this family of SoCs.

> Wouldn't it make sense to start iterating with rate = 0xffff * 1e9 /
> period? Otherwise you get bad configurations if rate is considerable
> slower than necessary.

The algorithm will start with 'rate' being the parent clock's rate, 
which
will always be the highest rate that the child clock will support.

> Best regards
> Uwe
> 
> --
> Pengutronix e.K.                           | Uwe Kleine-König        
>     |
> Industrial Linux Solutions                 | 
> http://www.pengutronix.de/  |


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver
  2019-01-05 20:46     ` Paul Cercueil
@ 2019-01-05 21:18       ` Uwe Kleine-König
  0 siblings, 0 replies; 43+ messages in thread
From: Uwe Kleine-König @ 2019-01-05 21:18 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hello Paul,

On Sat, Jan 05, 2019 at 05:46:18PM -0300, Paul Cercueil wrote:
> On Sat, Jan 5, 2019 at 4:42 PM, Uwe Kleine-König
> <u.kleine-koenig@pengutronix.de> wrote:
> > On Thu, Dec 27, 2018 at 07:13:04PM +0100, Paul Cercueil wrote:
> > >  The ingenic-timer "TCU" driver provides us with a regmap, that we
> > > can
> > >  use to safely access the TCU registers.
> > > 
> > >  While this driver is devicetree-compatible, it is never (as of now)
> > >  probed from devicetree, so this change does not introduce a ABI
> > > problem
> > >  with current devicetree files.
> > 
> > Does it change behaviour? If so, how?
> 
> No, it does not change the behaviour.

Then this paragraph in the commit log can better be dropped.

> > >  @@ -113,26 +117,37 @@ static int jz4740_pwm_apply(struct pwm_chip
> > > *chip, struct pwm_device *pwm,
> > > 
> > >   	jz4740_pwm_disable(chip, pwm);
> > > 
> > >  -	jz4740_timer_set_count(pwm->hwpwm, 0);
> > >  -	jz4740_timer_set_duty(pwm->hwpwm, duty);
> > >  -	jz4740_timer_set_period(pwm->hwpwm, period);
> > >  +	/* Set abrupt shutdown */
> > >  +	regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
> > >  +			   TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD);
> > 
> > I think I already pointed that out before: abrupt mode is wrong. If
> > .apply is called with a new set of parameters the currently running
> > period with the old values is expected to complete before the new values
> > take effect.
> 
> You pointed it, indeed; but I won't change it until I can verify that the
> behaviour is correct (which does not seem to be the case even if I leave
> this bit cleared). Besides, this is the TCU patchset, fixes and patches
> unrelated to the TCU don't belong here.

So abrupt mode was already used before? Then maybe note in a comment
that this is wrong but kept for now as this change is only refactoring
without changing behaviour.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 13/27] pwm: jz4740: Use clocks from TCU driver
  2019-01-05 20:52     ` Paul Cercueil
@ 2019-01-05 21:19       ` Uwe Kleine-König
  0 siblings, 0 replies; 43+ messages in thread
From: Uwe Kleine-König @ 2019-01-05 21:19 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hello Paul,

On Sat, Jan 05, 2019 at 05:52:46PM -0300, Paul Cercueil wrote:
> On Sat, Jan 5, 2019 at 4:45 PM, Uwe Kleine-König
> <u.kleine-koenig@pengutronix.de> wrote:
> > On Thu, Dec 27, 2018 at 07:13:05PM +0100, Paul Cercueil wrote:
> > >  diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> > >  index 4ed003bc3d8d..0343f0c1238e 100644
> > >  --- a/drivers/pwm/Kconfig
> > >  +++ b/drivers/pwm/Kconfig
> > >  @@ -202,7 +202,8 @@ config PWM_IMX
> > >   config PWM_JZ4740
> > >   	tristate "Ingenic JZ47xx PWM support"
> > >   	depends on MACH_INGENIC
> > >  -	select REGMAP
> > >  +	depends on COMMON_CLK
> > >  +	select INGENIC_TIMER
> > 
> > Did you drop REGMAP on purpose?
> 
> INGENIC_TIMER selects it, so it's still a dependency but indirectly.
> Should I restore it?

I think noting this in the commit log to not make a reader think (as I
did) that it was dropped by mistake is good enough.
 
Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2019-01-05 21:05     ` Paul Cercueil
@ 2019-01-05 21:27       ` Uwe Kleine-König
  2019-01-10 14:04         ` Paul Cercueil
  0 siblings, 1 reply; 43+ messages in thread
From: Uwe Kleine-König @ 2019-01-05 21:27 UTC (permalink / raw)
  To: Paul Cercueil
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk, Russell King

Hello Paul,

On Sat, Jan 05, 2019 at 06:05:38PM -0300, Paul Cercueil wrote:
> On Sat, Jan 5, 2019 at 4:57 PM, Uwe Kleine-König
> <u.kleine-koenig@pengutronix.de> wrote:
> > You are assuming stuff here about the parent clk which isn't guaranteed
> > (AFAICT) by the clk framework: If you call clk_round_rate(clk, rate - 1)
> > this might well return rate even if the clock could run slower than
> > rate.
> 
> It may not be guaranteed by the clock framework itself, but it is guaranteed
> to behave like that on this family of SoCs.

You shouldn't rely on that. Experience shows that people will start
copying code to machines where this is not guaranteed. Even if they
don't copy and only learn from reading this is bad. Also how do you
guarantee that this won't change in the future making the pwm code break
without noticing?

If you use an API better don't assume more things given than are
guaranteed by the API.

Having said that I would consider it sensible to introduce something
like clk_roundup_rate() and clk_rounddown_rate() which would allow
calculations like that.

> > Wouldn't it make sense to start iterating with rate = 0xffff * 1e9 /
> > period? Otherwise you get bad configurations if rate is considerable
> > slower than necessary.
> 
> The algorithm will start with 'rate' being the parent clock's rate, which
> will always be the highest rate that the child clock will support.

Ah right, I missed that bit.
 
Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2019-01-05 21:27       ` Uwe Kleine-König
@ 2019-01-10 14:04         ` Paul Cercueil
  2019-02-23  1:17           ` Paul Cercueil
  0 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2019-01-10 14:04 UTC (permalink / raw)
  To: Uwe Kleine-König, Stephen Boyd
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk, Russell King

Adding Stephen to the discussion.
Adding Stephen to the discussion.

On Sat, Jan 5, 2019 at 6:27 PM, Uwe Kleine-König 
<u.kleine-koenig@pengutronix.de> wrote:
> Hello Paul,
> 
> On Sat, Jan 05, 2019 at 06:05:38PM -0300, Paul Cercueil wrote:
>>  On Sat, Jan 5, 2019 at 4:57 PM, Uwe Kleine-König
>>  <u.kleine-koenig@pengutronix.de> wrote:
>>  > You are assuming stuff here about the parent clk which isn't 
>> guaranteed
>>  > (AFAICT) by the clk framework: If you call clk_round_rate(clk, 
>> rate - 1)
>>  > this might well return rate even if the clock could run slower 
>> than
>>  > rate.
>> 
>>  It may not be guaranteed by the clock framework itself, but it is 
>> guaranteed
>>  to behave like that on this family of SoCs.
> 
> You shouldn't rely on that. Experience shows that people will start
> copying code to machines where this is not guaranteed. Even if they
> don't copy and only learn from reading this is bad. Also how do you
> guarantee that this won't change in the future making the pwm code 
> break
> without noticing?
> 
> If you use an API better don't assume more things given than are
> guaranteed by the API.
> 
> Having said that I would consider it sensible to introduce something
> like clk_roundup_rate() and clk_rounddown_rate() which would allow
> calculations like that.

@Stephen:
Some context: my algorithm makes use of clk_round_rate(clk, rate - 1) 
to get the
next (smaller) clock rate that a clock support.

Is it something safe to assume? If not is there a better way?

>>  > Wouldn't it make sense to start iterating with rate = 0xffff * 
>> 1e9 /
>>  > period? Otherwise you get bad configurations if rate is 
>> considerable
>>  > slower than necessary.
>> 
>>  The algorithm will start with 'rate' being the parent clock's rate, 
>> which
>>  will always be the highest rate that the child clock will support.
> 
> Ah right, I missed that bit.

Thanks,
-Paul


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2019-01-10 14:04         ` Paul Cercueil
@ 2019-02-23  1:17           ` Paul Cercueil
  2019-02-28 18:42             ` Stephen Boyd
  0 siblings, 1 reply; 43+ messages in thread
From: Paul Cercueil @ 2019-02-23  1:17 UTC (permalink / raw)
  To: Uwe Kleine-König, Stephen Boyd
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk, Russell King

Hi,

Le jeu. 10 janv. 2019 à 11:04, Paul Cercueil <paul@crapouillou.net> a 
écrit :
> Adding Stephen to the discussion.
> Adding Stephen to the discussion.
> 
> On Sat, Jan 5, 2019 at 6:27 PM, Uwe Kleine-König 
> <u.kleine-koenig@pengutronix.de> wrote:
>> Hello Paul,
>> 
>> On Sat, Jan 05, 2019 at 06:05:38PM -0300, Paul Cercueil wrote:
>>>  On Sat, Jan 5, 2019 at 4:57 PM, Uwe Kleine-König
>>>  <u.kleine-koenig@pengutronix.de> wrote:
>>>  > You are assuming stuff here about the parent clk which isn't 
>>> \x7f\x7fguaranteed
>>>  > (AFAICT) by the clk framework: If you call clk_round_rate(clk, 
>>> \x7f\x7frate - 1)
>>>  > this might well return rate even if the clock could run slower 
>>> \x7f\x7fthan
>>>  > rate.
>>> 
>>>  It may not be guaranteed by the clock framework itself, but it is 
>>> \x7f\x7fguaranteed
>>>  to behave like that on this family of SoCs.
>> 
>> You shouldn't rely on that. Experience shows that people will start
>> copying code to machines where this is not guaranteed. Even if they
>> don't copy and only learn from reading this is bad. Also how do you
>> guarantee that this won't change in the future making the pwm code 
>> \x7fbreak
>> without noticing?
>> 
>> If you use an API better don't assume more things given than are
>> guaranteed by the API.
>> 
>> Having said that I would consider it sensible to introduce something
>> like clk_roundup_rate() and clk_rounddown_rate() which would allow
>> calculations like that.
> 
> @Stephen:
> Some context: my algorithm makes use of clk_round_rate(clk, rate - 1) 
> to get the
> next (smaller) clock rate that a clock support.
> 
> Is it something safe to assume? If not is there a better way?

Bump.

What should I do here?

>>>  > Wouldn't it make sense to start iterating with rate = 0xffff * 
>>> \x7f\x7f1e9 /
>>>  > period? Otherwise you get bad configurations if rate is 
>>> \x7f\x7fconsiderable
>>>  > slower than necessary.
>>> 
>>>  The algorithm will start with 'rate' being the parent clock's 
>>> rate, \x7f\x7fwhich
>>>  will always be the highest rate that the child clock will support.
>> 
>> Ah right, I missed that bit.

Thanks,
-Paul


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver
  2018-12-27 18:12 ` [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver Paul Cercueil
@ 2019-02-25 15:55   ` Daniel Lezcano
  2019-02-25 22:59     ` Paul Cercueil
  0 siblings, 1 reply; 43+ messages in thread
From: Daniel Lezcano @ 2019-02-25 15:55 UTC (permalink / raw)
  To: Paul Cercueil, Thierry Reding, Rob Herring, Mark Rutland,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, Uwe Kleine-König
  Cc: linux-pwm, devicetree, linux-kernel, linux-watchdog, linux-mips,
	linux-doc, linux-clk

Hi Paul,

sorry for the delay but this driver requires a bit more of attention.

Overall I disagree the driver because of its complexity. We should not
see that for this driver.

Did you check if using node alias to specify the clocksource and the
clockevent can fit your need.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clocksource/timer-integrator-ap.c#n199

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm/boot/dts/integratorap.dts#n49


On 27/12/2018 19:12, Paul Cercueil wrote:
> This driver handles the TCU (Timer Counter Unit) present on the Ingenic
> JZ47xx SoCs, and provides the kernel with a system timer, and optionally
> with a clocksource and a sched_clock.
> 
> It also provides clocks and interrupt handling to client drivers.
> 
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
> ---
> 
> Notes:
>      v2: Use SPDX identifier for the license
>     
>      v3: - Move documentation to its own patch
>          - Search the devicetree for PWM clients, and use all the TCU
>     	   channels that won't be used for PWM
>     
>      v4: - Add documentation about why we search for PWM clients
>          - Verify that the PWM clients are for the TCU PWM driver
>     
>      v5: Major overhaul. Too many changes to list. Consider it's a new
>          patch.
>     
>      v6: - Add two API functions ingenic_tcu_request_channel and
>            ingenic_tcu_release_channel. To be used by the PWM driver to
>            request the use of a TCU channel. The driver will now dynamically
>            move away the system timer or clocksource to a new TCU channel.
>          - The system timer now defaults to channel 0, the clocksource now
>            defaults to channel 1 and is no more optional. The
>            ingenic,timer-channel and ingenic,clocksource-channel devicetree
>            properties are now gone.
>          - Fix round_rate / set_rate not calculating the prescale divider
>            the same way. This caused problems when (parent_rate / div) would
>            give a non-integer result. The behaviour is correct now.
>          - The clocksource clock is turned off on suspend now.
>     
>      v7: Fix section mismatch by using builtin_platform_driver_probe()
> 
>      v8: - Removed ingenic_tcu_[request,release]_channel, and the mechanism
>            to dynamically change the TCU channel of the system timer or
> 	   the clocksource.
> 	 - The driver's devicetree node can now have two more children
> 	   nodes, that correspond to the system timer and clocksource.
> 	   For these two, the driver will use the TCU timer that
> 	   correspond to the memory resource supplied in their
> 	   respective node.
> 
>      v9: - Removed support for clocksource / timer children devicetree
>            nodes. Now, we use a property "ingenic,pwm-channels-mask" to
> 	   know which PWM channels are reserved for PWM use and should
> 	   not be used as OS timers.
> 
>  drivers/clocksource/Kconfig         |  10 +
>  drivers/clocksource/Makefile        |   1 +
>  drivers/clocksource/ingenic-timer.c | 901 ++++++++++++++++++++++++++++++++++++
>  drivers/clocksource/ingenic-timer.h |  15 +
>  include/linux/mfd/ingenic-tcu.h     |   2 +
>  5 files changed, 929 insertions(+)
>  create mode 100644 drivers/clocksource/ingenic-timer.c
>  create mode 100644 drivers/clocksource/ingenic-timer.h
> 
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index 55c77e44bb2d..4e69af15c3e7 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -638,4 +638,14 @@ config GX6605S_TIMER
>  	help
>  	  This option enables support for gx6605s SOC's timer.
>  
> +config INGENIC_TIMER
> +	bool "Clocksource/timer using the TCU in Ingenic JZ SoCs"
> +	depends on MIPS || COMPILE_TEST
> +	depends on COMMON_CLK
> +	select TIMER_OF
> +	select IRQ_DOMAIN
> +	select REGMAP
> +	help
> +	  Support for the timer/counter unit of the Ingenic JZ SoCs.
> +
>  endmenu
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index dd9138104568..7c8f790dcf67 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -75,6 +75,7 @@ obj-$(CONFIG_ASM9260_TIMER)		+= asm9260_timer.o
>  obj-$(CONFIG_H8300_TMR8)		+= h8300_timer8.o
>  obj-$(CONFIG_H8300_TMR16)		+= h8300_timer16.o
>  obj-$(CONFIG_H8300_TPU)			+= h8300_tpu.o
> +obj-$(CONFIG_INGENIC_TIMER)		+= ingenic-timer.o
>  obj-$(CONFIG_CLKSRC_ST_LPC)		+= clksrc_st_lpc.o
>  obj-$(CONFIG_X86_NUMACHIP)		+= numachip.o
>  obj-$(CONFIG_ATCPIT100_TIMER)		+= timer-atcpit100.o
> diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c
> new file mode 100644
> index 000000000000..81faa120cfee
> --- /dev/null
> +++ b/drivers/clocksource/ingenic-timer.c
> @@ -0,0 +1,901 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * JZ47xx SoCs TCU IRQ driver
> + * Copyright (C) 2018 Paul Cercueil <paul@crapouillou.net>
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/mfd/ingenic-tcu.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/sched_clock.h>
> +
> +#include <dt-bindings/clock/ingenic,tcu.h>
> +
> +#include "ingenic-timer.h"
> +
> +/* 8 channels max + watchdog + OST */
> +#define TCU_CLK_COUNT	10
> +
> +enum tcu_clk_parent {
> +	TCU_PARENT_PCLK,
> +	TCU_PARENT_RTC,
> +	TCU_PARENT_EXT,
> +};
> +
> +struct ingenic_soc_info {
> +	unsigned char num_channels;
> +	bool has_ost;
> +};
> +
> +struct ingenic_tcu_clk_info {
> +	struct clk_init_data init_data;
> +	u8 gate_bit;
> +	u8 tcsr_reg;
> +};
> +
> +struct ingenic_tcu_clk {
> +	struct clk_hw hw;
> +
> +	struct regmap *map;
> +	const struct ingenic_tcu_clk_info *info;
> +
> +	unsigned int idx;
> +};
> +
> +#define to_tcu_clk(_hw) container_of(_hw, struct ingenic_tcu_clk, hw)
> +
> +struct ingenic_tcu {
> +	const struct ingenic_soc_info *soc_info;
> +	struct regmap *map;
> +	struct clk *clk, *timer_clk, *cs_clk;
> +
> +	struct irq_domain *domain;
> +	unsigned int nb_parent_irqs;
> +	u32 parent_irqs[3];
> +
> +	struct clk_hw_onecell_data *clocks;
> +
> +	unsigned int timer_channel, cs_channel;
> +	struct clock_event_device cevt;
> +	struct clocksource cs;
> +	char name[4];
> +
> +	unsigned long pwm_channels_mask;
> +};
> +
> +static struct ingenic_tcu *ingenic_tcu;
> +
> +void __iomem *ingenic_tcu_base;
> +EXPORT_SYMBOL_GPL(ingenic_tcu_base);
> +
> +static int ingenic_tcu_enable(struct clk_hw *hw)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +
> +	regmap_write(tcu_clk->map, TCU_REG_TSCR, BIT(info->gate_bit));
> +	return 0;
> +}
> +
> +static void ingenic_tcu_disable(struct clk_hw *hw)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +
> +	regmap_write(tcu_clk->map, TCU_REG_TSSR, BIT(info->gate_bit));
> +}
> +
> +static int ingenic_tcu_is_enabled(struct clk_hw *hw)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +	unsigned int value;
> +
> +	regmap_read(tcu_clk->map, TCU_REG_TSR, &value);
> +
> +	return !(value & BIT(info->gate_bit));
> +}
> +
> +static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +	unsigned int val = 0;
> +	int ret;
> +
> +	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &val);
> +	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
> +
> +	return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
> +}
> +
> +static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +	struct regmap *map = tcu_clk->map;
> +	int ret;
> +
> +	/*
> +	 * Our clock provider has the CLK_SET_PARENT_GATE flag set, so we know
> +	 * that the clk is in unprepared state. To be able to access TCSR
> +	 * we must ungate the clock supply and we gate it again when done.
> +	 */
> +
> +	regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
> +
> +	ret = regmap_update_bits(map, info->tcsr_reg,
> +				TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
> +	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
> +
> +	regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
> +
> +	return 0;
> +}
> +
> +static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
> +		unsigned long parent_rate)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +	unsigned int prescale;
> +	int ret;
> +
> +	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &prescale);
> +	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
> +
> +	prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> TCU_TCSR_PRESCALE_LSB;
> +
> +	return parent_rate >> (prescale * 2);
> +}
> +
> +static u8 ingenic_tcu_get_prescale(unsigned long rate, unsigned long req_rate)
> +{
> +	u8 prescale;
> +
> +	for (prescale = 0; prescale < 5; prescale++)
> +		if ((rate >> (prescale * 2)) <= req_rate)
> +			return prescale;
> +
> +	return 5; /* /1024 divider */
> +}
> +
> +static long ingenic_tcu_round_rate(struct clk_hw *hw, unsigned long req_rate,
> +		unsigned long *parent_rate)
> +{
> +	unsigned long rate = *parent_rate;
> +	u8 prescale;
> +
> +	if (req_rate > rate)
> +		return -EINVAL;
> +
> +	prescale = ingenic_tcu_get_prescale(rate, req_rate);
> +	return rate >> (prescale * 2);
> +}
> +
> +static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long req_rate,
> +		unsigned long parent_rate)
> +{
> +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
> +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
> +	struct regmap *map = tcu_clk->map;
> +	u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
> +	int ret;
> +
> +	/*
> +	 * Our clock provider has the CLK_SET_RATE_GATE flag set, so we know
> +	 * that the clk is in unprepared state. To be able to access TCSR
> +	 * we must ungate the clock supply and we gate it again when done.
> +	 */
> +
> +	regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
> +
> +	ret = regmap_update_bits(map, info->tcsr_reg,
> +				TCU_TCSR_PRESCALE_MASK,
> +				prescale << TCU_TCSR_PRESCALE_LSB);
> +	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
> +
> +	regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops ingenic_tcu_clk_ops = {
> +	.get_parent	= ingenic_tcu_get_parent,
> +	.set_parent	= ingenic_tcu_set_parent,
> +
> +	.recalc_rate	= ingenic_tcu_recalc_rate,
> +	.round_rate	= ingenic_tcu_round_rate,
> +	.set_rate	= ingenic_tcu_set_rate,
> +
> +	.enable		= ingenic_tcu_enable,
> +	.disable	= ingenic_tcu_disable,
> +	.is_enabled	= ingenic_tcu_is_enabled,
> +};
> +
> +static const char * const ingenic_tcu_timer_parents[] = {
> +	[TCU_PARENT_PCLK] = "pclk",
> +	[TCU_PARENT_RTC]  = "rtc",
> +	[TCU_PARENT_EXT]  = "ext",
> +};
> +
> +#define DEF_TIMER(_name, _gate_bit, _tcsr)				\
> +	{								\
> +		.init_data = {						\
> +			.name = _name,					\
> +			.parent_names = ingenic_tcu_timer_parents,	\
> +			.num_parents = ARRAY_SIZE(ingenic_tcu_timer_parents),\
> +			.ops = &ingenic_tcu_clk_ops,			\
> +			.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE,\
> +		},							\
> +		.gate_bit = _gate_bit,					\
> +		.tcsr_reg = _tcsr,					\
> +	}
> +static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
> +	[TCU_CLK_TIMER0] = DEF_TIMER("timer0", 0, TCU_REG_TCSRc(0)),
> +	[TCU_CLK_TIMER1] = DEF_TIMER("timer1", 1, TCU_REG_TCSRc(1)),
> +	[TCU_CLK_TIMER2] = DEF_TIMER("timer2", 2, TCU_REG_TCSRc(2)),
> +	[TCU_CLK_TIMER3] = DEF_TIMER("timer3", 3, TCU_REG_TCSRc(3)),
> +	[TCU_CLK_TIMER4] = DEF_TIMER("timer4", 4, TCU_REG_TCSRc(4)),
> +	[TCU_CLK_TIMER5] = DEF_TIMER("timer5", 5, TCU_REG_TCSRc(5)),
> +	[TCU_CLK_TIMER6] = DEF_TIMER("timer6", 6, TCU_REG_TCSRc(6)),
> +	[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
> +};
> +
> +static const struct ingenic_tcu_clk_info ingenic_tcu_watchdog_clk_info =
> +				DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
> +static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
> +				DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
> +#undef DEF_TIMER
> +
> +static void ingenic_tcu_intc_cascade(struct irq_desc *desc)
> +{
> +	struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
> +	struct irq_domain *domain = irq_desc_get_handler_data(desc);
> +	struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
> +	struct regmap *map = gc->private;
> +	uint32_t irq_reg, irq_mask;
> +	unsigned int i;
> +
> +	regmap_read(map, TCU_REG_TFR, &irq_reg);
> +	regmap_read(map, TCU_REG_TMR, &irq_mask);
> +
> +	chained_irq_enter(irq_chip, desc);
> +
> +	irq_reg &= ~irq_mask;
> +
> +	for_each_set_bit(i, (unsigned long *)&irq_reg, 32)
> +		generic_handle_irq(irq_linear_revmap(domain, i));
> +
> +	chained_irq_exit(irq_chip, desc);
> +}
> +
> +static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d)
> +{
> +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
> +	struct irq_chip_type *ct = irq_data_get_chip_type(d);
> +	struct regmap *map = gc->private;
> +	u32 mask = d->mask;
> +
> +	irq_gc_lock(gc);
> +	regmap_write(map, ct->regs.ack, mask);
> +	regmap_write(map, ct->regs.enable, mask);
> +	*ct->mask_cache |= mask;
> +	irq_gc_unlock(gc);
> +}
> +
> +static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d)
> +{
> +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
> +	struct irq_chip_type *ct = irq_data_get_chip_type(d);
> +	struct regmap *map = gc->private;
> +	u32 mask = d->mask;
> +
> +	irq_gc_lock(gc);
> +	regmap_write(map, ct->regs.disable, mask);
> +	*ct->mask_cache &= ~mask;
> +	irq_gc_unlock(gc);
> +}
> +
> +static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct irq_data *d)
> +{
> +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
> +	struct irq_chip_type *ct = irq_data_get_chip_type(d);
> +	struct regmap *map = gc->private;
> +	u32 mask = d->mask;
> +
> +	irq_gc_lock(gc);
> +	regmap_write(map, ct->regs.ack, mask);
> +	regmap_write(map, ct->regs.disable, mask);
> +	irq_gc_unlock(gc);
> +}
> +
> +static u64 notrace ingenic_tcu_timer_read(void)
> +{
> +	unsigned int channel = ingenic_tcu->cs_channel;
> +	u16 count;
> +
> +	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
> +
> +	return count;
> +}
> +
> +static inline struct ingenic_tcu *to_ingenic_tcu(struct clock_event_device *evt)
> +{
> +	return container_of(evt, struct ingenic_tcu, cevt);
> +}
> +
> +static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt)
> +{
> +	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
> +
> +	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
> +	return 0;
> +}
> +
> +static int ingenic_tcu_cevt_set_next(unsigned long next,
> +				     struct clock_event_device *evt)
> +{
> +	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
> +
> +	if (next > 0xffff)
> +		return -EINVAL;
> +
> +	regmap_write(tcu->map, TCU_REG_TDFRc(tcu->timer_channel), next);
> +	regmap_write(tcu->map, TCU_REG_TCNTc(tcu->timer_channel), 0);
> +	regmap_write(tcu->map, TCU_REG_TESR, BIT(tcu->timer_channel));
> +
> +	return 0;
> +}
> +
> +static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt = dev_id;
> +	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
> +
> +	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
> +
> +	if (evt->event_handler)
> +		evt->event_handler(evt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
> +			unsigned int idx, enum tcu_clk_parent parent,
> +			const struct ingenic_tcu_clk_info *info,
> +			struct clk_hw_onecell_data *clocks)
> +{
> +	struct ingenic_tcu_clk *tcu_clk;
> +	int err;
> +
> +	tcu_clk = kzalloc(sizeof(*tcu_clk), GFP_KERNEL);
> +	if (!tcu_clk)
> +		return -ENOMEM;
> +
> +	tcu_clk->hw.init = &info->init_data;
> +	tcu_clk->idx = idx;
> +	tcu_clk->info = info;
> +	tcu_clk->map = tcu->map;
> +
> +	/* Reset channel and clock divider, set default parent */
> +	ingenic_tcu_enable(&tcu_clk->hw);
> +	regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
> +	ingenic_tcu_disable(&tcu_clk->hw);
> +
> +	err = clk_hw_register(NULL, &tcu_clk->hw);
> +	if (err)
> +		goto err_free_tcu_clk;
> +
> +	err = clk_hw_register_clkdev(&tcu_clk->hw, info->init_data.name, NULL);
> +	if (err)
> +		goto err_clk_unregister;
> +
> +	clocks->hws[idx] = &tcu_clk->hw;
> +	return 0;
> +
> +err_clk_unregister:
> +	clk_hw_unregister(&tcu_clk->hw);
> +err_free_tcu_clk:
> +	kfree(tcu_clk);
> +	return err;
> +}
> +
> +static int __init ingenic_tcu_clk_init(struct ingenic_tcu *tcu,
> +				       struct device_node *np)
> +{
> +	size_t i;
> +	int ret;
> +
> +	tcu->clocks = kzalloc(sizeof(*tcu->clocks) +
> +			 sizeof(*tcu->clocks->hws) * TCU_CLK_COUNT,
> +			 GFP_KERNEL);
> +	if (!tcu->clocks)
> +		return -ENOMEM;
> +
> +	tcu->clocks->num = TCU_CLK_COUNT;
> +
> +	for (i = 0; i < tcu->soc_info->num_channels; i++) {
> +		ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
> +				&ingenic_tcu_clk_info[i], tcu->clocks);
> +		if (ret) {
> +			pr_err("ingenic-timer: cannot register clock %i\n", i);
> +			goto err_unregister_timer_clocks;
> +		}
> +	}
> +
> +	/*
> +	 * We set EXT as the default parent clock for all the TCU clocks
> +	 * except for the watchdog one, where we set the RTC clock as the
> +	 * parent. Since the EXT and PCLK are much faster than the RTC clock,
> +	 * the watchdog would kick after a maximum time of 5s, and we might
> +	 * want a slower kicking time.
> +	 */
> +	ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
> +				&ingenic_tcu_watchdog_clk_info, tcu->clocks);
> +	if (ret) {
> +		pr_err("ingenic-timer: cannot register watchdog clock\n");
> +		goto err_unregister_timer_clocks;
> +	}
> +
> +	if (tcu->soc_info->has_ost) {
> +		ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
> +					TCU_PARENT_EXT,
> +					&ingenic_tcu_ost_clk_info,
> +					tcu->clocks);
> +		if (ret) {
> +			pr_err("ingenic-timer: cannot register ost clock\n");
> +			goto err_unregister_watchdog_clock;
> +		}
> +	}
> +
> +	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, tcu->clocks);
> +	if (ret) {
> +		pr_err("ingenic-timer: cannot add OF clock provider\n");
> +		goto err_unregister_ost_clock;
> +	}
> +
> +	return 0;
> +
> +err_unregister_ost_clock:
> +	if (tcu->soc_info->has_ost)
> +		clk_hw_unregister(tcu->clocks->hws[i + 1]);
> +err_unregister_watchdog_clock:
> +	clk_hw_unregister(tcu->clocks->hws[i]);
> +err_unregister_timer_clocks:
> +	for (i = 0; i < tcu->clocks->num; i++)
> +		if (tcu->clocks->hws[i])
> +			clk_hw_unregister(tcu->clocks->hws[i]);
> +	kfree(tcu->clocks);
> +	return ret;
> +}
> +
> +static void __init ingenic_tcu_clk_cleanup(struct ingenic_tcu *tcu,
> +					   struct device_node *np)
> +{
> +	unsigned int i;
> +
> +	of_clk_del_provider(np);
> +
> +	for (i = 0; i < tcu->clocks->num; i++)
> +		clk_hw_unregister(tcu->clocks->hws[i]);
> +	kfree(tcu->clocks);
> +}
> +
> +static int __init ingenic_tcu_intc_init(struct ingenic_tcu *tcu,
> +					struct device_node *np)
> +{
> +	struct irq_chip_generic *gc;
> +	struct irq_chip_type *ct;
> +	int err, i, irqs;
> +
> +	irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32));
> +	if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs))
> +		return -EINVAL;
> +
> +	tcu->nb_parent_irqs = irqs;
> +
> +	tcu->domain = irq_domain_add_linear(np, 32,
> +			&irq_generic_chip_ops, NULL);
> +	if (!tcu->domain)
> +		return -ENOMEM;
> +
> +	err = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU",
> +			handle_level_irq, 0, IRQ_NOPROBE | IRQ_LEVEL, 0);
> +	if (err)
> +		goto out_domain_remove;
> +
> +	gc = irq_get_domain_generic_chip(tcu->domain, 0);
> +	ct = gc->chip_types;
> +
> +	gc->wake_enabled = IRQ_MSK(32);
> +	gc->private = tcu->map;
> +
> +	ct->regs.disable = TCU_REG_TMSR;
> +	ct->regs.enable = TCU_REG_TMCR;
> +	ct->regs.ack = TCU_REG_TFCR;
> +	ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg;
> +	ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg;
> +	ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack;
> +	ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
> +
> +	/* Mask all IRQs by default */
> +	regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32));
> +
> +	/* On JZ4740, timer 0 and timer 1 have their own interrupt line;
> +	 * timers 2-7 share one interrupt.
> +	 * On SoCs >= JZ4770, timer 5 has its own interrupt line;
> +	 * timers 0-4 and 6-7 share one single interrupt.
> +	 *
> +	 * To keep things simple, we just register the same handler to
> +	 * all parent interrupts. The handler will properly detect which
> +	 * channel fired the interrupt.
> +	 */
> +	for (i = 0; i < irqs; i++) {
> +		tcu->parent_irqs[i] = irq_of_parse_and_map(np, i);
> +		if (!tcu->parent_irqs[i]) {
> +			err = -EINVAL;
> +			goto out_unmap_irqs;
> +		}
> +
> +		irq_set_chained_handler_and_data(tcu->parent_irqs[i],
> +				ingenic_tcu_intc_cascade, tcu->domain);
> +	}
> +
> +	return 0;
> +
> +out_unmap_irqs:
> +	for (; i > 0; i--)
> +		irq_dispose_mapping(tcu->parent_irqs[i - 1]);
> +out_domain_remove:
> +	irq_domain_remove(tcu->domain);
> +	return err;
> +}
> +
> +static void __init ingenic_tcu_intc_cleanup(struct ingenic_tcu *tcu)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < tcu->nb_parent_irqs; i++)
> +		irq_dispose_mapping(tcu->parent_irqs[i]);
> +
> +	irq_domain_remove(tcu->domain);
> +}
> +
> +static int __init ingenic_tcu_timer_init(struct ingenic_tcu *tcu)
> +{
> +	unsigned int timer_virq;
> +	unsigned long rate;
> +	int err;
> +
> +	tcu->timer_clk = tcu->clocks->hws[tcu->timer_channel]->clk;
> +
> +	err = clk_prepare_enable(tcu->timer_clk);
> +	if (err)
> +		return err;
> +
> +	rate = clk_get_rate(tcu->timer_clk);
> +	if (!rate) {
> +		err = -EINVAL;
> +		goto err_clk_disable;
> +	}
> +
> +	timer_virq = irq_create_mapping(tcu->domain, tcu->timer_channel);
> +	if (!timer_virq) {
> +		err = -EINVAL;
> +		goto err_clk_disable;
> +	}
> +
> +	snprintf(tcu->name, sizeof(tcu->name), "TCU");
> +
> +	err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
> +			  tcu->name, &tcu->cevt);
> +	if (err)
> +		goto err_irq_dispose_mapping;
> +
> +	tcu->cevt.cpumask = cpumask_of(smp_processor_id());
> +	tcu->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
> +	tcu->cevt.name = tcu->name;
> +	tcu->cevt.rating = 200;
> +	tcu->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown;
> +	tcu->cevt.set_next_event = ingenic_tcu_cevt_set_next;
> +
> +	clockevents_config_and_register(&tcu->cevt, rate, 10, 0xffff);
> +
> +	return 0;
> +
> +err_irq_dispose_mapping:
> +	irq_dispose_mapping(timer_virq);
> +err_clk_disable:
> +	clk_disable_unprepare(tcu->timer_clk);
> +	return err;
> +}
> +
> +static int __init ingenic_tcu_clocksource_init(struct ingenic_tcu *tcu)
> +{
> +	unsigned int channel = tcu->cs_channel;
> +	struct clocksource *cs = &tcu->cs;
> +	unsigned long rate;
> +	int err;
> +
> +	tcu->cs_clk = tcu->clocks->hws[channel]->clk;
> +
> +	err = clk_prepare_enable(tcu->cs_clk);
> +	if (err)
> +		return err;
> +
> +	rate = clk_get_rate(tcu->cs_clk);
> +	if (!rate) {
> +		err = -EINVAL;
> +		goto err_clk_disable;
> +	}
> +
> +	/* Reset channel */
> +	regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel),
> +			   0xffff & ~TCU_TCSR_RESERVED_BITS, 0);
> +
> +	/* Reset counter */
> +	regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff);
> +	regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0);
> +
> +	/* Enable channel */
> +	regmap_write(tcu->map, TCU_REG_TESR, BIT(channel));
> +
> +	cs->name = "ingenic-timer";
> +	cs->rating = 200;
> +	cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
> +	cs->mask = CLOCKSOURCE_MASK(16);
> +	cs->read = (u64 (*)(struct clocksource *))ingenic_tcu_timer_read;
> +
> +	err = clocksource_register_hz(cs, rate);
> +	if (err)
> +		goto err_clk_disable;
> +
> +	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
> +
> +	return 0;
> +
> +err_clk_disable:
> +	clk_disable_unprepare(tcu->cs_clk);
> +	return err;
> +}
> +
> +static void __init ingenic_tcu_clocksource_cleanup(struct ingenic_tcu *tcu)
> +{
> +	clocksource_unregister(&tcu->cs);
> +	clk_disable_unprepare(tcu->cs_clk);
> +}
> +
> +static const struct regmap_config ingenic_tcu_regmap_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +};
> +
> +static const struct ingenic_soc_info jz4740_soc_info = {
> +	.num_channels = 8,
> +	.has_ost = false,
> +};
> +
> +static const struct ingenic_soc_info jz4725b_soc_info = {
> +	.num_channels = 6,
> +	.has_ost = true,
> +};
> +
> +static const struct ingenic_soc_info jz4770_soc_info = {
> +	.num_channels = 8,
> +	.has_ost = true,
> +};
> +
> +static const struct of_device_id ingenic_tcu_of_match[] = {
> +	{ .compatible = "ingenic,jz4740-tcu",  .data = &jz4740_soc_info, },
> +	{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
> +	{ .compatible = "ingenic,jz4770-tcu",  .data = &jz4770_soc_info, },
> +	{ }
> +};
> +
> +static int __init ingenic_tcu_init(struct device_node *np)
> +{
> +	const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
> +	const struct ingenic_soc_info *soc_info = id->data;
> +	unsigned int max_pwm_num;
> +	struct ingenic_tcu *tcu;
> +	struct resource res;
> +	void __iomem *base;
> +	int ret;
> +
> +	of_node_clear_flag(np, OF_POPULATED);
> +
> +	tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
> +	if (!tcu)
> +		return -ENOMEM;
> +
> +	/*
> +	 * Enable all TCU channels for PWM use by default except channel 0
> +	 * and, if the OS Timer is present, channel 1.
> +	 */
> +	tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1,
> +					 2 - !!soc_info->has_ost);
> +	of_property_read_u32(np, "ingenic,pwm-channels-mask",
> +			     (u32 *)&tcu->pwm_channels_mask);
> +
> +	max_pwm_num = soc_info->num_channels - !soc_info->has_ost - 1;
> +
> +	if (hweight8(tcu->pwm_channels_mask) > max_pwm_num) {
> +		pr_crit("ingenic-tcu: Invalid PWM channel mask: 0x%02lx\n",
> +					tcu->pwm_channels_mask);
> +		return -EINVAL;
> +	}
> +
> +	tcu->soc_info = soc_info;
> +	ingenic_tcu = tcu;
> +
> +	tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
> +						 soc_info->num_channels);
> +	if (soc_info->has_ost) {
> +		tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
> +						     soc_info->num_channels,
> +						     tcu->timer_channel + 1);
> +	}
> +
> +	base = of_io_request_and_map(np, 0, NULL);
> +	if (IS_ERR(base)) {
> +		ret = PTR_ERR(base);
> +		goto err_free_ingenic_tcu;
> +	}
> +
> +	of_address_to_resource(np, 0, &res);
> +
> +	ingenic_tcu_base = base;
> +
> +	tcu->map = regmap_init_mmio(NULL, base, &ingenic_tcu_regmap_config);
> +	if (IS_ERR(tcu->map)) {
> +		ret = PTR_ERR(tcu->map);
> +		goto err_iounmap;
> +	}
> +
> +	tcu->clk = of_clk_get_by_name(np, "tcu");
> +	if (IS_ERR(tcu->clk)) {
> +		ret = PTR_ERR(tcu->clk);
> +		pr_crit("ingenic-tcu: Unable to find TCU clock: %i\n", ret);
> +		goto err_free_regmap;
> +	}
> +
> +	ret = clk_prepare_enable(tcu->clk);
> +	if (ret) {
> +		pr_crit("ingenic-tcu: Unable to enable TCU clock: %i\n", ret);
> +		goto err_clk_put;
> +	}
> +
> +	ret = ingenic_tcu_intc_init(tcu, np);
> +	if (ret)
> +		goto err_clk_disable;
> +
> +	ret = ingenic_tcu_clk_init(tcu, np);
> +	if (ret)
> +		goto err_tcu_intc_cleanup;
> +
> +	if (!soc_info->has_ost) {
> +		ret = ingenic_tcu_clocksource_init(tcu);
> +		if (ret)
> +			goto err_tcu_clk_cleanup;
> +	}
> +
> +	ret = ingenic_tcu_timer_init(tcu);
> +	if (ret)
> +		goto err_tcu_clocksource_cleanup;
> +
> +
> +	return 0;
> +
> +err_tcu_clocksource_cleanup:
> +	if (!soc_info->has_ost)
> +		ingenic_tcu_clocksource_cleanup(tcu);
> +err_tcu_clk_cleanup:
> +	ingenic_tcu_clk_cleanup(tcu, np);
> +err_tcu_intc_cleanup:
> +	ingenic_tcu_intc_cleanup(tcu);
> +err_clk_disable:
> +	clk_disable_unprepare(tcu->clk);
> +err_clk_put:
> +	clk_put(tcu->clk);
> +err_free_regmap:
> +	regmap_exit(tcu->map);
> +err_iounmap:
> +	iounmap(base);
> +	release_mem_region(res.start, resource_size(&res));
> +err_free_ingenic_tcu:
> +	kfree(tcu);
> +	return ret;
> +}
> +
> +TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",  ingenic_tcu_init);
> +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init);
> +TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",  ingenic_tcu_init);
> +
> +
> +static int __init ingenic_tcu_probe(struct platform_device *pdev)
> +{
> +	platform_set_drvdata(pdev, ingenic_tcu);
> +
> +	regmap_attach_dev(&pdev->dev, ingenic_tcu->map,
> +			  &ingenic_tcu_regmap_config);
> +
> +	return devm_of_platform_populate(&pdev->dev);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int ingenic_tcu_suspend(struct device *dev)
> +{
> +	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
> +
> +	clk_disable(tcu->cs_clk);
> +	clk_disable(tcu->timer_clk);
> +	clk_disable(tcu->clk);
> +	return 0;
> +}
> +
> +static int ingenic_tcu_resume(struct device *dev)
> +{
> +	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = clk_enable(tcu->clk);
> +	if (ret)
> +		return ret;
> +
> +	ret = clk_enable(tcu->timer_clk);
> +	if (ret)
> +		goto err_tcu_clk_disable;
> +
> +	ret = clk_enable(tcu->cs_clk);
> +	if (ret)
> +		goto err_tcu_timer_clk_disable;
> +
> +	return 0;
> +
> +err_tcu_timer_clk_disable:
> +	clk_disable(tcu->timer_clk);
> +err_tcu_clk_disable:
> +	clk_disable(tcu->clk);
> +	return ret;
> +}
> +
> +static const struct dev_pm_ops ingenic_tcu_pm_ops = {
> +	/* _noirq: We want the TCU clock to be gated last / ungated first */
> +	.suspend_noirq = ingenic_tcu_suspend,
> +	.resume_noirq  = ingenic_tcu_resume,
> +};
> +#define INGENIC_TCU_PM_OPS (&ingenic_tcu_pm_ops)
> +
> +#else
> +#define INGENIC_TCU_PM_OPS NULL
> +#endif /* CONFIG_PM_SUSPEND */
> +
> +static struct platform_driver ingenic_tcu_driver = {
> +	.driver = {
> +		.name	= "ingenic-tcu",
> +		.pm	= INGENIC_TCU_PM_OPS,
> +		.of_match_table = ingenic_tcu_of_match,
> +	},
> +};
> +builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe);
> +
> +bool ingenic_tcu_pwm_can_use_chn(unsigned int channel)
> +{
> +	return !!(ingenic_tcu->pwm_channels_mask & BIT(channel));
> +}
> +EXPORT_SYMBOL_GPL(ingenic_tcu_pwm_can_use_chn);
> diff --git a/drivers/clocksource/ingenic-timer.h b/drivers/clocksource/ingenic-timer.h
> new file mode 100644
> index 000000000000..fa43da836ab6
> --- /dev/null
> +++ b/drivers/clocksource/ingenic-timer.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
> +#define __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
> +
> +#include <linux/compiler_types.h>
> +
> +/*
> + * README: For use *ONLY* by the ingenic-ost driver.
> + * Regular drivers which want to access the TCU registers
> + * must have ingenic-timer as parent and retrieve the regmap
> + * doing dev_get_regmap(pdev->dev.parent);
> + */
> +extern void __iomem *ingenic_tcu_base;
> +
> +#endif /* __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__ */
> diff --git a/include/linux/mfd/ingenic-tcu.h b/include/linux/mfd/ingenic-tcu.h
> index ab16ad283def..dac3fac35c1e 100644
> --- a/include/linux/mfd/ingenic-tcu.h
> +++ b/include/linux/mfd/ingenic-tcu.h
> @@ -53,4 +53,6 @@
>  #define TCU_REG_TCNTc(c)	(TCU_REG_TCNT0 + ((c) * TCU_CHANNEL_STRIDE))
>  #define TCU_REG_TCSRc(c)	(TCU_REG_TCSR0 + ((c) * TCU_CHANNEL_STRIDE))
>  
> +bool ingenic_tcu_pwm_can_use_chn(unsigned int channel);
> +
>  #endif /* __LINUX_MFD_INGENIC_TCU_H_ */
> 


-- 
 <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver
  2019-02-25 15:55   ` Daniel Lezcano
@ 2019-02-25 22:59     ` Paul Cercueil
  0 siblings, 0 replies; 43+ messages in thread
From: Paul Cercueil @ 2019-02-25 22:59 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Thomas Gleixner,
	Ralf Baechle, Paul Burton, James Hogan, Jonathan Corbet,
	Uwe Kleine-König, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk

Hi,

Le lun. 25 févr. 2019 à 12:55, Daniel Lezcano 
<daniel.lezcano@linaro.org> a écrit :
> Hi Paul,
> 
> sorry for the delay but this driver requires a bit more of attention.
> 
> Overall I disagree the driver because of its complexity. We should not
> see that for this driver.

What's complex in this driver?

> Did you check if using node alias to specify the clocksource and the
> clockevent can fit your need.
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/clocksource/timer-integrator-ap.c#n199
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm/boot/dts/integratorap.dts#n49

I'd have nothing to point the aliases to. The driver does not expose 
individual
timers.

I thought we were good with the ingenic,pwm-channels-mask property, I
got Rob to ack the bindings and the driver is much simpler than what it
was some revisions ago. Why change it again?

> On 27/12/2018 19:12, Paul Cercueil wrote:
>>  This driver handles the TCU (Timer Counter Unit) present on the 
>> Ingenic
>>  JZ47xx SoCs, and provides the kernel with a system timer, and 
>> optionally
>>  with a clocksource and a sched_clock.
>> 
>>  It also provides clocks and interrupt handling to client drivers.
>> 
>>  Signed-off-by: Paul Cercueil <paul@crapouillou.net>
>>  ---
>> 
>>  Notes:
>>       v2: Use SPDX identifier for the license
>> 
>>       v3: - Move documentation to its own patch
>>           - Search the devicetree for PWM clients, and use all the 
>> TCU
>>      	   channels that won't be used for PWM
>> 
>>       v4: - Add documentation about why we search for PWM clients
>>           - Verify that the PWM clients are for the TCU PWM driver
>> 
>>       v5: Major overhaul. Too many changes to list. Consider it's a 
>> new
>>           patch.
>> 
>>       v6: - Add two API functions ingenic_tcu_request_channel and
>>             ingenic_tcu_release_channel. To be used by the PWM 
>> driver to
>>             request the use of a TCU channel. The driver will now 
>> dynamically
>>             move away the system timer or clocksource to a new TCU 
>> channel.
>>           - The system timer now defaults to channel 0, the 
>> clocksource now
>>             defaults to channel 1 and is no more optional. The
>>             ingenic,timer-channel and ingenic,clocksource-channel 
>> devicetree
>>             properties are now gone.
>>           - Fix round_rate / set_rate not calculating the prescale 
>> divider
>>             the same way. This caused problems when (parent_rate / 
>> div) would
>>             give a non-integer result. The behaviour is correct now.
>>           - The clocksource clock is turned off on suspend now.
>> 
>>       v7: Fix section mismatch by using 
>> builtin_platform_driver_probe()
>> 
>>       v8: - Removed ingenic_tcu_[request,release]_channel, and the 
>> mechanism
>>             to dynamically change the TCU channel of the system 
>> timer or
>>  	   the clocksource.
>>  	 - The driver's devicetree node can now have two more children
>>  	   nodes, that correspond to the system timer and clocksource.
>>  	   For these two, the driver will use the TCU timer that
>>  	   correspond to the memory resource supplied in their
>>  	   respective node.
>> 
>>       v9: - Removed support for clocksource / timer children 
>> devicetree
>>             nodes. Now, we use a property 
>> "ingenic,pwm-channels-mask" to
>>  	   know which PWM channels are reserved for PWM use and should
>>  	   not be used as OS timers.
>> 
>>   drivers/clocksource/Kconfig         |  10 +
>>   drivers/clocksource/Makefile        |   1 +
>>   drivers/clocksource/ingenic-timer.c | 901 
>> ++++++++++++++++++++++++++++++++++++
>>   drivers/clocksource/ingenic-timer.h |  15 +
>>   include/linux/mfd/ingenic-tcu.h     |   2 +
>>   5 files changed, 929 insertions(+)
>>   create mode 100644 drivers/clocksource/ingenic-timer.c
>>   create mode 100644 drivers/clocksource/ingenic-timer.h
>> 
>>  diff --git a/drivers/clocksource/Kconfig 
>> b/drivers/clocksource/Kconfig
>>  index 55c77e44bb2d..4e69af15c3e7 100644
>>  --- a/drivers/clocksource/Kconfig
>>  +++ b/drivers/clocksource/Kconfig
>>  @@ -638,4 +638,14 @@ config GX6605S_TIMER
>>   	help
>>   	  This option enables support for gx6605s SOC's timer.
>> 
>>  +config INGENIC_TIMER
>>  +	bool "Clocksource/timer using the TCU in Ingenic JZ SoCs"
>>  +	depends on MIPS || COMPILE_TEST
>>  +	depends on COMMON_CLK
>>  +	select TIMER_OF
>>  +	select IRQ_DOMAIN
>>  +	select REGMAP
>>  +	help
>>  +	  Support for the timer/counter unit of the Ingenic JZ SoCs.
>>  +
>>   endmenu
>>  diff --git a/drivers/clocksource/Makefile 
>> b/drivers/clocksource/Makefile
>>  index dd9138104568..7c8f790dcf67 100644
>>  --- a/drivers/clocksource/Makefile
>>  +++ b/drivers/clocksource/Makefile
>>  @@ -75,6 +75,7 @@ obj-$(CONFIG_ASM9260_TIMER)		+= asm9260_timer.o
>>   obj-$(CONFIG_H8300_TMR8)		+= h8300_timer8.o
>>   obj-$(CONFIG_H8300_TMR16)		+= h8300_timer16.o
>>   obj-$(CONFIG_H8300_TPU)			+= h8300_tpu.o
>>  +obj-$(CONFIG_INGENIC_TIMER)		+= ingenic-timer.o
>>   obj-$(CONFIG_CLKSRC_ST_LPC)		+= clksrc_st_lpc.o
>>   obj-$(CONFIG_X86_NUMACHIP)		+= numachip.o
>>   obj-$(CONFIG_ATCPIT100_TIMER)		+= timer-atcpit100.o
>>  diff --git a/drivers/clocksource/ingenic-timer.c 
>> b/drivers/clocksource/ingenic-timer.c
>>  new file mode 100644
>>  index 000000000000..81faa120cfee
>>  --- /dev/null
>>  +++ b/drivers/clocksource/ingenic-timer.c
>>  @@ -0,0 +1,901 @@
>>  +// SPDX-License-Identifier: GPL-2.0
>>  +/*
>>  + * JZ47xx SoCs TCU IRQ driver
>>  + * Copyright (C) 2018 Paul Cercueil <paul@crapouillou.net>
>>  + */
>>  +
>>  +#include <linux/bitops.h>
>>  +#include <linux/clk.h>
>>  +#include <linux/clk-provider.h>
>>  +#include <linux/clkdev.h>
>>  +#include <linux/clockchips.h>
>>  +#include <linux/clocksource.h>
>>  +#include <linux/interrupt.h>
>>  +#include <linux/irqchip.h>
>>  +#include <linux/irqchip/chained_irq.h>
>>  +#include <linux/mfd/ingenic-tcu.h>
>>  +#include <linux/of.h>
>>  +#include <linux/of_address.h>
>>  +#include <linux/of_irq.h>
>>  +#include <linux/of_platform.h>
>>  +#include <linux/platform_device.h>
>>  +#include <linux/regmap.h>
>>  +#include <linux/sched_clock.h>
>>  +
>>  +#include <dt-bindings/clock/ingenic,tcu.h>
>>  +
>>  +#include "ingenic-timer.h"
>>  +
>>  +/* 8 channels max + watchdog + OST */
>>  +#define TCU_CLK_COUNT	10
>>  +
>>  +enum tcu_clk_parent {
>>  +	TCU_PARENT_PCLK,
>>  +	TCU_PARENT_RTC,
>>  +	TCU_PARENT_EXT,
>>  +};
>>  +
>>  +struct ingenic_soc_info {
>>  +	unsigned char num_channels;
>>  +	bool has_ost;
>>  +};
>>  +
>>  +struct ingenic_tcu_clk_info {
>>  +	struct clk_init_data init_data;
>>  +	u8 gate_bit;
>>  +	u8 tcsr_reg;
>>  +};
>>  +
>>  +struct ingenic_tcu_clk {
>>  +	struct clk_hw hw;
>>  +
>>  +	struct regmap *map;
>>  +	const struct ingenic_tcu_clk_info *info;
>>  +
>>  +	unsigned int idx;
>>  +};
>>  +
>>  +#define to_tcu_clk(_hw) container_of(_hw, struct ingenic_tcu_clk, 
>> hw)
>>  +
>>  +struct ingenic_tcu {
>>  +	const struct ingenic_soc_info *soc_info;
>>  +	struct regmap *map;
>>  +	struct clk *clk, *timer_clk, *cs_clk;
>>  +
>>  +	struct irq_domain *domain;
>>  +	unsigned int nb_parent_irqs;
>>  +	u32 parent_irqs[3];
>>  +
>>  +	struct clk_hw_onecell_data *clocks;
>>  +
>>  +	unsigned int timer_channel, cs_channel;
>>  +	struct clock_event_device cevt;
>>  +	struct clocksource cs;
>>  +	char name[4];
>>  +
>>  +	unsigned long pwm_channels_mask;
>>  +};
>>  +
>>  +static struct ingenic_tcu *ingenic_tcu;
>>  +
>>  +void __iomem *ingenic_tcu_base;
>>  +EXPORT_SYMBOL_GPL(ingenic_tcu_base);
>>  +
>>  +static int ingenic_tcu_enable(struct clk_hw *hw)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +
>>  +	regmap_write(tcu_clk->map, TCU_REG_TSCR, BIT(info->gate_bit));
>>  +	return 0;
>>  +}
>>  +
>>  +static void ingenic_tcu_disable(struct clk_hw *hw)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +
>>  +	regmap_write(tcu_clk->map, TCU_REG_TSSR, BIT(info->gate_bit));
>>  +}
>>  +
>>  +static int ingenic_tcu_is_enabled(struct clk_hw *hw)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +	unsigned int value;
>>  +
>>  +	regmap_read(tcu_clk->map, TCU_REG_TSR, &value);
>>  +
>>  +	return !(value & BIT(info->gate_bit));
>>  +}
>>  +
>>  +static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +	unsigned int val = 0;
>>  +	int ret;
>>  +
>>  +	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &val);
>>  +	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
>>  +
>>  +	return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
>>  +}
>>  +
>>  +static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +	struct regmap *map = tcu_clk->map;
>>  +	int ret;
>>  +
>>  +	/*
>>  +	 * Our clock provider has the CLK_SET_PARENT_GATE flag set, so we 
>> know
>>  +	 * that the clk is in unprepared state. To be able to access TCSR
>>  +	 * we must ungate the clock supply and we gate it again when done.
>>  +	 */
>>  +
>>  +	regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
>>  +
>>  +	ret = regmap_update_bits(map, info->tcsr_reg,
>>  +				TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
>>  +	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
>>  +
>>  +	regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
>>  +
>>  +	return 0;
>>  +}
>>  +
>>  +static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
>>  +		unsigned long parent_rate)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +	unsigned int prescale;
>>  +	int ret;
>>  +
>>  +	ret = regmap_read(tcu_clk->map, info->tcsr_reg, &prescale);
>>  +	WARN_ONCE(ret < 0, "Unable to read TCSR %i", tcu_clk->idx);
>>  +
>>  +	prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> 
>> TCU_TCSR_PRESCALE_LSB;
>>  +
>>  +	return parent_rate >> (prescale * 2);
>>  +}
>>  +
>>  +static u8 ingenic_tcu_get_prescale(unsigned long rate, unsigned 
>> long req_rate)
>>  +{
>>  +	u8 prescale;
>>  +
>>  +	for (prescale = 0; prescale < 5; prescale++)
>>  +		if ((rate >> (prescale * 2)) <= req_rate)
>>  +			return prescale;
>>  +
>>  +	return 5; /* /1024 divider */
>>  +}
>>  +
>>  +static long ingenic_tcu_round_rate(struct clk_hw *hw, unsigned 
>> long req_rate,
>>  +		unsigned long *parent_rate)
>>  +{
>>  +	unsigned long rate = *parent_rate;
>>  +	u8 prescale;
>>  +
>>  +	if (req_rate > rate)
>>  +		return -EINVAL;
>>  +
>>  +	prescale = ingenic_tcu_get_prescale(rate, req_rate);
>>  +	return rate >> (prescale * 2);
>>  +}
>>  +
>>  +static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long 
>> req_rate,
>>  +		unsigned long parent_rate)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
>>  +	const struct ingenic_tcu_clk_info *info = tcu_clk->info;
>>  +	struct regmap *map = tcu_clk->map;
>>  +	u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
>>  +	int ret;
>>  +
>>  +	/*
>>  +	 * Our clock provider has the CLK_SET_RATE_GATE flag set, so we 
>> know
>>  +	 * that the clk is in unprepared state. To be able to access TCSR
>>  +	 * we must ungate the clock supply and we gate it again when done.
>>  +	 */
>>  +
>>  +	regmap_write(map, TCU_REG_TSCR, BIT(info->gate_bit));
>>  +
>>  +	ret = regmap_update_bits(map, info->tcsr_reg,
>>  +				TCU_TCSR_PRESCALE_MASK,
>>  +				prescale << TCU_TCSR_PRESCALE_LSB);
>>  +	WARN_ONCE(ret < 0, "Unable to update TCSR %i", tcu_clk->idx);
>>  +
>>  +	regmap_write(map, TCU_REG_TSSR, BIT(info->gate_bit));
>>  +
>>  +	return 0;
>>  +}
>>  +
>>  +static const struct clk_ops ingenic_tcu_clk_ops = {
>>  +	.get_parent	= ingenic_tcu_get_parent,
>>  +	.set_parent	= ingenic_tcu_set_parent,
>>  +
>>  +	.recalc_rate	= ingenic_tcu_recalc_rate,
>>  +	.round_rate	= ingenic_tcu_round_rate,
>>  +	.set_rate	= ingenic_tcu_set_rate,
>>  +
>>  +	.enable		= ingenic_tcu_enable,
>>  +	.disable	= ingenic_tcu_disable,
>>  +	.is_enabled	= ingenic_tcu_is_enabled,
>>  +};
>>  +
>>  +static const char * const ingenic_tcu_timer_parents[] = {
>>  +	[TCU_PARENT_PCLK] = "pclk",
>>  +	[TCU_PARENT_RTC]  = "rtc",
>>  +	[TCU_PARENT_EXT]  = "ext",
>>  +};
>>  +
>>  +#define DEF_TIMER(_name, _gate_bit, _tcsr)				\
>>  +	{								\
>>  +		.init_data = {						\
>>  +			.name = _name,					\
>>  +			.parent_names = ingenic_tcu_timer_parents,	\
>>  +			.num_parents = ARRAY_SIZE(ingenic_tcu_timer_parents),\
>>  +			.ops = &ingenic_tcu_clk_ops,			\
>>  +			.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE,\
>>  +		},							\
>>  +		.gate_bit = _gate_bit,					\
>>  +		.tcsr_reg = _tcsr,					\
>>  +	}
>>  +static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
>>  +	[TCU_CLK_TIMER0] = DEF_TIMER("timer0", 0, TCU_REG_TCSRc(0)),
>>  +	[TCU_CLK_TIMER1] = DEF_TIMER("timer1", 1, TCU_REG_TCSRc(1)),
>>  +	[TCU_CLK_TIMER2] = DEF_TIMER("timer2", 2, TCU_REG_TCSRc(2)),
>>  +	[TCU_CLK_TIMER3] = DEF_TIMER("timer3", 3, TCU_REG_TCSRc(3)),
>>  +	[TCU_CLK_TIMER4] = DEF_TIMER("timer4", 4, TCU_REG_TCSRc(4)),
>>  +	[TCU_CLK_TIMER5] = DEF_TIMER("timer5", 5, TCU_REG_TCSRc(5)),
>>  +	[TCU_CLK_TIMER6] = DEF_TIMER("timer6", 6, TCU_REG_TCSRc(6)),
>>  +	[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
>>  +};
>>  +
>>  +static const struct ingenic_tcu_clk_info 
>> ingenic_tcu_watchdog_clk_info =
>>  +				DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
>>  +static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
>>  +				DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
>>  +#undef DEF_TIMER
>>  +
>>  +static void ingenic_tcu_intc_cascade(struct irq_desc *desc)
>>  +{
>>  +	struct irq_chip *irq_chip = 
>> irq_data_get_irq_chip(&desc->irq_data);
>>  +	struct irq_domain *domain = irq_desc_get_handler_data(desc);
>>  +	struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 
>> 0);
>>  +	struct regmap *map = gc->private;
>>  +	uint32_t irq_reg, irq_mask;
>>  +	unsigned int i;
>>  +
>>  +	regmap_read(map, TCU_REG_TFR, &irq_reg);
>>  +	regmap_read(map, TCU_REG_TMR, &irq_mask);
>>  +
>>  +	chained_irq_enter(irq_chip, desc);
>>  +
>>  +	irq_reg &= ~irq_mask;
>>  +
>>  +	for_each_set_bit(i, (unsigned long *)&irq_reg, 32)
>>  +		generic_handle_irq(irq_linear_revmap(domain, i));
>>  +
>>  +	chained_irq_exit(irq_chip, desc);
>>  +}
>>  +
>>  +static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d)
>>  +{
>>  +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
>>  +	struct irq_chip_type *ct = irq_data_get_chip_type(d);
>>  +	struct regmap *map = gc->private;
>>  +	u32 mask = d->mask;
>>  +
>>  +	irq_gc_lock(gc);
>>  +	regmap_write(map, ct->regs.ack, mask);
>>  +	regmap_write(map, ct->regs.enable, mask);
>>  +	*ct->mask_cache |= mask;
>>  +	irq_gc_unlock(gc);
>>  +}
>>  +
>>  +static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d)
>>  +{
>>  +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
>>  +	struct irq_chip_type *ct = irq_data_get_chip_type(d);
>>  +	struct regmap *map = gc->private;
>>  +	u32 mask = d->mask;
>>  +
>>  +	irq_gc_lock(gc);
>>  +	regmap_write(map, ct->regs.disable, mask);
>>  +	*ct->mask_cache &= ~mask;
>>  +	irq_gc_unlock(gc);
>>  +}
>>  +
>>  +static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct 
>> irq_data *d)
>>  +{
>>  +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
>>  +	struct irq_chip_type *ct = irq_data_get_chip_type(d);
>>  +	struct regmap *map = gc->private;
>>  +	u32 mask = d->mask;
>>  +
>>  +	irq_gc_lock(gc);
>>  +	regmap_write(map, ct->regs.ack, mask);
>>  +	regmap_write(map, ct->regs.disable, mask);
>>  +	irq_gc_unlock(gc);
>>  +}
>>  +
>>  +static u64 notrace ingenic_tcu_timer_read(void)
>>  +{
>>  +	unsigned int channel = ingenic_tcu->cs_channel;
>>  +	u16 count;
>>  +
>>  +	count = readw(ingenic_tcu_base + TCU_REG_TCNTc(channel));
>>  +
>>  +	return count;
>>  +}
>>  +
>>  +static inline struct ingenic_tcu *to_ingenic_tcu(struct 
>> clock_event_device *evt)
>>  +{
>>  +	return container_of(evt, struct ingenic_tcu, cevt);
>>  +}
>>  +
>>  +static int ingenic_tcu_cevt_set_state_shutdown(struct 
>> clock_event_device *evt)
>>  +{
>>  +	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
>>  +
>>  +	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
>>  +	return 0;
>>  +}
>>  +
>>  +static int ingenic_tcu_cevt_set_next(unsigned long next,
>>  +				     struct clock_event_device *evt)
>>  +{
>>  +	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
>>  +
>>  +	if (next > 0xffff)
>>  +		return -EINVAL;
>>  +
>>  +	regmap_write(tcu->map, TCU_REG_TDFRc(tcu->timer_channel), next);
>>  +	regmap_write(tcu->map, TCU_REG_TCNTc(tcu->timer_channel), 0);
>>  +	regmap_write(tcu->map, TCU_REG_TESR, BIT(tcu->timer_channel));
>>  +
>>  +	return 0;
>>  +}
>>  +
>>  +static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id)
>>  +{
>>  +	struct clock_event_device *evt = dev_id;
>>  +	struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
>>  +
>>  +	regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
>>  +
>>  +	if (evt->event_handler)
>>  +		evt->event_handler(evt);
>>  +
>>  +	return IRQ_HANDLED;
>>  +}
>>  +
>>  +static int __init ingenic_tcu_register_clock(struct ingenic_tcu 
>> *tcu,
>>  +			unsigned int idx, enum tcu_clk_parent parent,
>>  +			const struct ingenic_tcu_clk_info *info,
>>  +			struct clk_hw_onecell_data *clocks)
>>  +{
>>  +	struct ingenic_tcu_clk *tcu_clk;
>>  +	int err;
>>  +
>>  +	tcu_clk = kzalloc(sizeof(*tcu_clk), GFP_KERNEL);
>>  +	if (!tcu_clk)
>>  +		return -ENOMEM;
>>  +
>>  +	tcu_clk->hw.init = &info->init_data;
>>  +	tcu_clk->idx = idx;
>>  +	tcu_clk->info = info;
>>  +	tcu_clk->map = tcu->map;
>>  +
>>  +	/* Reset channel and clock divider, set default parent */
>>  +	ingenic_tcu_enable(&tcu_clk->hw);
>>  +	regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
>>  +	ingenic_tcu_disable(&tcu_clk->hw);
>>  +
>>  +	err = clk_hw_register(NULL, &tcu_clk->hw);
>>  +	if (err)
>>  +		goto err_free_tcu_clk;
>>  +
>>  +	err = clk_hw_register_clkdev(&tcu_clk->hw, info->init_data.name, 
>> NULL);
>>  +	if (err)
>>  +		goto err_clk_unregister;
>>  +
>>  +	clocks->hws[idx] = &tcu_clk->hw;
>>  +	return 0;
>>  +
>>  +err_clk_unregister:
>>  +	clk_hw_unregister(&tcu_clk->hw);
>>  +err_free_tcu_clk:
>>  +	kfree(tcu_clk);
>>  +	return err;
>>  +}
>>  +
>>  +static int __init ingenic_tcu_clk_init(struct ingenic_tcu *tcu,
>>  +				       struct device_node *np)
>>  +{
>>  +	size_t i;
>>  +	int ret;
>>  +
>>  +	tcu->clocks = kzalloc(sizeof(*tcu->clocks) +
>>  +			 sizeof(*tcu->clocks->hws) * TCU_CLK_COUNT,
>>  +			 GFP_KERNEL);
>>  +	if (!tcu->clocks)
>>  +		return -ENOMEM;
>>  +
>>  +	tcu->clocks->num = TCU_CLK_COUNT;
>>  +
>>  +	for (i = 0; i < tcu->soc_info->num_channels; i++) {
>>  +		ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
>>  +				&ingenic_tcu_clk_info[i], tcu->clocks);
>>  +		if (ret) {
>>  +			pr_err("ingenic-timer: cannot register clock %i\n", i);
>>  +			goto err_unregister_timer_clocks;
>>  +		}
>>  +	}
>>  +
>>  +	/*
>>  +	 * We set EXT as the default parent clock for all the TCU clocks
>>  +	 * except for the watchdog one, where we set the RTC clock as the
>>  +	 * parent. Since the EXT and PCLK are much faster than the RTC 
>> clock,
>>  +	 * the watchdog would kick after a maximum time of 5s, and we 
>> might
>>  +	 * want a slower kicking time.
>>  +	 */
>>  +	ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
>>  +				&ingenic_tcu_watchdog_clk_info, tcu->clocks);
>>  +	if (ret) {
>>  +		pr_err("ingenic-timer: cannot register watchdog clock\n");
>>  +		goto err_unregister_timer_clocks;
>>  +	}
>>  +
>>  +	if (tcu->soc_info->has_ost) {
>>  +		ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
>>  +					TCU_PARENT_EXT,
>>  +					&ingenic_tcu_ost_clk_info,
>>  +					tcu->clocks);
>>  +		if (ret) {
>>  +			pr_err("ingenic-timer: cannot register ost clock\n");
>>  +			goto err_unregister_watchdog_clock;
>>  +		}
>>  +	}
>>  +
>>  +	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, 
>> tcu->clocks);
>>  +	if (ret) {
>>  +		pr_err("ingenic-timer: cannot add OF clock provider\n");
>>  +		goto err_unregister_ost_clock;
>>  +	}
>>  +
>>  +	return 0;
>>  +
>>  +err_unregister_ost_clock:
>>  +	if (tcu->soc_info->has_ost)
>>  +		clk_hw_unregister(tcu->clocks->hws[i + 1]);
>>  +err_unregister_watchdog_clock:
>>  +	clk_hw_unregister(tcu->clocks->hws[i]);
>>  +err_unregister_timer_clocks:
>>  +	for (i = 0; i < tcu->clocks->num; i++)
>>  +		if (tcu->clocks->hws[i])
>>  +			clk_hw_unregister(tcu->clocks->hws[i]);
>>  +	kfree(tcu->clocks);
>>  +	return ret;
>>  +}
>>  +
>>  +static void __init ingenic_tcu_clk_cleanup(struct ingenic_tcu *tcu,
>>  +					   struct device_node *np)
>>  +{
>>  +	unsigned int i;
>>  +
>>  +	of_clk_del_provider(np);
>>  +
>>  +	for (i = 0; i < tcu->clocks->num; i++)
>>  +		clk_hw_unregister(tcu->clocks->hws[i]);
>>  +	kfree(tcu->clocks);
>>  +}
>>  +
>>  +static int __init ingenic_tcu_intc_init(struct ingenic_tcu *tcu,
>>  +					struct device_node *np)
>>  +{
>>  +	struct irq_chip_generic *gc;
>>  +	struct irq_chip_type *ct;
>>  +	int err, i, irqs;
>>  +
>>  +	irqs = of_property_count_elems_of_size(np, "interrupts", 
>> sizeof(u32));
>>  +	if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs))
>>  +		return -EINVAL;
>>  +
>>  +	tcu->nb_parent_irqs = irqs;
>>  +
>>  +	tcu->domain = irq_domain_add_linear(np, 32,
>>  +			&irq_generic_chip_ops, NULL);
>>  +	if (!tcu->domain)
>>  +		return -ENOMEM;
>>  +
>>  +	err = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU",
>>  +			handle_level_irq, 0, IRQ_NOPROBE | IRQ_LEVEL, 0);
>>  +	if (err)
>>  +		goto out_domain_remove;
>>  +
>>  +	gc = irq_get_domain_generic_chip(tcu->domain, 0);
>>  +	ct = gc->chip_types;
>>  +
>>  +	gc->wake_enabled = IRQ_MSK(32);
>>  +	gc->private = tcu->map;
>>  +
>>  +	ct->regs.disable = TCU_REG_TMSR;
>>  +	ct->regs.enable = TCU_REG_TMCR;
>>  +	ct->regs.ack = TCU_REG_TFCR;
>>  +	ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg;
>>  +	ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg;
>>  +	ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack;
>>  +	ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
>>  +
>>  +	/* Mask all IRQs by default */
>>  +	regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32));
>>  +
>>  +	/* On JZ4740, timer 0 and timer 1 have their own interrupt line;
>>  +	 * timers 2-7 share one interrupt.
>>  +	 * On SoCs >= JZ4770, timer 5 has its own interrupt line;
>>  +	 * timers 0-4 and 6-7 share one single interrupt.
>>  +	 *
>>  +	 * To keep things simple, we just register the same handler to
>>  +	 * all parent interrupts. The handler will properly detect which
>>  +	 * channel fired the interrupt.
>>  +	 */
>>  +	for (i = 0; i < irqs; i++) {
>>  +		tcu->parent_irqs[i] = irq_of_parse_and_map(np, i);
>>  +		if (!tcu->parent_irqs[i]) {
>>  +			err = -EINVAL;
>>  +			goto out_unmap_irqs;
>>  +		}
>>  +
>>  +		irq_set_chained_handler_and_data(tcu->parent_irqs[i],
>>  +				ingenic_tcu_intc_cascade, tcu->domain);
>>  +	}
>>  +
>>  +	return 0;
>>  +
>>  +out_unmap_irqs:
>>  +	for (; i > 0; i--)
>>  +		irq_dispose_mapping(tcu->parent_irqs[i - 1]);
>>  +out_domain_remove:
>>  +	irq_domain_remove(tcu->domain);
>>  +	return err;
>>  +}
>>  +
>>  +static void __init ingenic_tcu_intc_cleanup(struct ingenic_tcu 
>> *tcu)
>>  +{
>>  +	unsigned int i;
>>  +
>>  +	for (i = 0; i < tcu->nb_parent_irqs; i++)
>>  +		irq_dispose_mapping(tcu->parent_irqs[i]);
>>  +
>>  +	irq_domain_remove(tcu->domain);
>>  +}
>>  +
>>  +static int __init ingenic_tcu_timer_init(struct ingenic_tcu *tcu)
>>  +{
>>  +	unsigned int timer_virq;
>>  +	unsigned long rate;
>>  +	int err;
>>  +
>>  +	tcu->timer_clk = tcu->clocks->hws[tcu->timer_channel]->clk;
>>  +
>>  +	err = clk_prepare_enable(tcu->timer_clk);
>>  +	if (err)
>>  +		return err;
>>  +
>>  +	rate = clk_get_rate(tcu->timer_clk);
>>  +	if (!rate) {
>>  +		err = -EINVAL;
>>  +		goto err_clk_disable;
>>  +	}
>>  +
>>  +	timer_virq = irq_create_mapping(tcu->domain, tcu->timer_channel);
>>  +	if (!timer_virq) {
>>  +		err = -EINVAL;
>>  +		goto err_clk_disable;
>>  +	}
>>  +
>>  +	snprintf(tcu->name, sizeof(tcu->name), "TCU");
>>  +
>>  +	err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
>>  +			  tcu->name, &tcu->cevt);
>>  +	if (err)
>>  +		goto err_irq_dispose_mapping;
>>  +
>>  +	tcu->cevt.cpumask = cpumask_of(smp_processor_id());
>>  +	tcu->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
>>  +	tcu->cevt.name = tcu->name;
>>  +	tcu->cevt.rating = 200;
>>  +	tcu->cevt.set_state_shutdown = 
>> ingenic_tcu_cevt_set_state_shutdown;
>>  +	tcu->cevt.set_next_event = ingenic_tcu_cevt_set_next;
>>  +
>>  +	clockevents_config_and_register(&tcu->cevt, rate, 10, 0xffff);
>>  +
>>  +	return 0;
>>  +
>>  +err_irq_dispose_mapping:
>>  +	irq_dispose_mapping(timer_virq);
>>  +err_clk_disable:
>>  +	clk_disable_unprepare(tcu->timer_clk);
>>  +	return err;
>>  +}
>>  +
>>  +static int __init ingenic_tcu_clocksource_init(struct ingenic_tcu 
>> *tcu)
>>  +{
>>  +	unsigned int channel = tcu->cs_channel;
>>  +	struct clocksource *cs = &tcu->cs;
>>  +	unsigned long rate;
>>  +	int err;
>>  +
>>  +	tcu->cs_clk = tcu->clocks->hws[channel]->clk;
>>  +
>>  +	err = clk_prepare_enable(tcu->cs_clk);
>>  +	if (err)
>>  +		return err;
>>  +
>>  +	rate = clk_get_rate(tcu->cs_clk);
>>  +	if (!rate) {
>>  +		err = -EINVAL;
>>  +		goto err_clk_disable;
>>  +	}
>>  +
>>  +	/* Reset channel */
>>  +	regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel),
>>  +			   0xffff & ~TCU_TCSR_RESERVED_BITS, 0);
>>  +
>>  +	/* Reset counter */
>>  +	regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff);
>>  +	regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0);
>>  +
>>  +	/* Enable channel */
>>  +	regmap_write(tcu->map, TCU_REG_TESR, BIT(channel));
>>  +
>>  +	cs->name = "ingenic-timer";
>>  +	cs->rating = 200;
>>  +	cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
>>  +	cs->mask = CLOCKSOURCE_MASK(16);
>>  +	cs->read = (u64 (*)(struct clocksource *))ingenic_tcu_timer_read;
>>  +
>>  +	err = clocksource_register_hz(cs, rate);
>>  +	if (err)
>>  +		goto err_clk_disable;
>>  +
>>  +	sched_clock_register(ingenic_tcu_timer_read, 16, rate);
>>  +
>>  +	return 0;
>>  +
>>  +err_clk_disable:
>>  +	clk_disable_unprepare(tcu->cs_clk);
>>  +	return err;
>>  +}
>>  +
>>  +static void __init ingenic_tcu_clocksource_cleanup(struct 
>> ingenic_tcu *tcu)
>>  +{
>>  +	clocksource_unregister(&tcu->cs);
>>  +	clk_disable_unprepare(tcu->cs_clk);
>>  +}
>>  +
>>  +static const struct regmap_config ingenic_tcu_regmap_config = {
>>  +	.reg_bits = 32,
>>  +	.val_bits = 32,
>>  +	.reg_stride = 4,
>>  +};
>>  +
>>  +static const struct ingenic_soc_info jz4740_soc_info = {
>>  +	.num_channels = 8,
>>  +	.has_ost = false,
>>  +};
>>  +
>>  +static const struct ingenic_soc_info jz4725b_soc_info = {
>>  +	.num_channels = 6,
>>  +	.has_ost = true,
>>  +};
>>  +
>>  +static const struct ingenic_soc_info jz4770_soc_info = {
>>  +	.num_channels = 8,
>>  +	.has_ost = true,
>>  +};
>>  +
>>  +static const struct of_device_id ingenic_tcu_of_match[] = {
>>  +	{ .compatible = "ingenic,jz4740-tcu",  .data = &jz4740_soc_info, 
>> },
>>  +	{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, 
>> },
>>  +	{ .compatible = "ingenic,jz4770-tcu",  .data = &jz4770_soc_info, 
>> },
>>  +	{ }
>>  +};
>>  +
>>  +static int __init ingenic_tcu_init(struct device_node *np)
>>  +{
>>  +	const struct of_device_id *id = 
>> of_match_node(ingenic_tcu_of_match, np);
>>  +	const struct ingenic_soc_info *soc_info = id->data;
>>  +	unsigned int max_pwm_num;
>>  +	struct ingenic_tcu *tcu;
>>  +	struct resource res;
>>  +	void __iomem *base;
>>  +	int ret;
>>  +
>>  +	of_node_clear_flag(np, OF_POPULATED);
>>  +
>>  +	tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
>>  +	if (!tcu)
>>  +		return -ENOMEM;
>>  +
>>  +	/*
>>  +	 * Enable all TCU channels for PWM use by default except channel 0
>>  +	 * and, if the OS Timer is present, channel 1.
>>  +	 */
>>  +	tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1,
>>  +					 2 - !!soc_info->has_ost);
>>  +	of_property_read_u32(np, "ingenic,pwm-channels-mask",
>>  +			     (u32 *)&tcu->pwm_channels_mask);
>>  +
>>  +	max_pwm_num = soc_info->num_channels - !soc_info->has_ost - 1;
>>  +
>>  +	if (hweight8(tcu->pwm_channels_mask) > max_pwm_num) {
>>  +		pr_crit("ingenic-tcu: Invalid PWM channel mask: 0x%02lx\n",
>>  +					tcu->pwm_channels_mask);
>>  +		return -EINVAL;
>>  +	}
>>  +
>>  +	tcu->soc_info = soc_info;
>>  +	ingenic_tcu = tcu;
>>  +
>>  +	tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
>>  +						 soc_info->num_channels);
>>  +	if (soc_info->has_ost) {
>>  +		tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
>>  +						     soc_info->num_channels,
>>  +						     tcu->timer_channel + 1);
>>  +	}
>>  +
>>  +	base = of_io_request_and_map(np, 0, NULL);
>>  +	if (IS_ERR(base)) {
>>  +		ret = PTR_ERR(base);
>>  +		goto err_free_ingenic_tcu;
>>  +	}
>>  +
>>  +	of_address_to_resource(np, 0, &res);
>>  +
>>  +	ingenic_tcu_base = base;
>>  +
>>  +	tcu->map = regmap_init_mmio(NULL, base, 
>> &ingenic_tcu_regmap_config);
>>  +	if (IS_ERR(tcu->map)) {
>>  +		ret = PTR_ERR(tcu->map);
>>  +		goto err_iounmap;
>>  +	}
>>  +
>>  +	tcu->clk = of_clk_get_by_name(np, "tcu");
>>  +	if (IS_ERR(tcu->clk)) {
>>  +		ret = PTR_ERR(tcu->clk);
>>  +		pr_crit("ingenic-tcu: Unable to find TCU clock: %i\n", ret);
>>  +		goto err_free_regmap;
>>  +	}
>>  +
>>  +	ret = clk_prepare_enable(tcu->clk);
>>  +	if (ret) {
>>  +		pr_crit("ingenic-tcu: Unable to enable TCU clock: %i\n", ret);
>>  +		goto err_clk_put;
>>  +	}
>>  +
>>  +	ret = ingenic_tcu_intc_init(tcu, np);
>>  +	if (ret)
>>  +		goto err_clk_disable;
>>  +
>>  +	ret = ingenic_tcu_clk_init(tcu, np);
>>  +	if (ret)
>>  +		goto err_tcu_intc_cleanup;
>>  +
>>  +	if (!soc_info->has_ost) {
>>  +		ret = ingenic_tcu_clocksource_init(tcu);
>>  +		if (ret)
>>  +			goto err_tcu_clk_cleanup;
>>  +	}
>>  +
>>  +	ret = ingenic_tcu_timer_init(tcu);
>>  +	if (ret)
>>  +		goto err_tcu_clocksource_cleanup;
>>  +
>>  +
>>  +	return 0;
>>  +
>>  +err_tcu_clocksource_cleanup:
>>  +	if (!soc_info->has_ost)
>>  +		ingenic_tcu_clocksource_cleanup(tcu);
>>  +err_tcu_clk_cleanup:
>>  +	ingenic_tcu_clk_cleanup(tcu, np);
>>  +err_tcu_intc_cleanup:
>>  +	ingenic_tcu_intc_cleanup(tcu);
>>  +err_clk_disable:
>>  +	clk_disable_unprepare(tcu->clk);
>>  +err_clk_put:
>>  +	clk_put(tcu->clk);
>>  +err_free_regmap:
>>  +	regmap_exit(tcu->map);
>>  +err_iounmap:
>>  +	iounmap(base);
>>  +	release_mem_region(res.start, resource_size(&res));
>>  +err_free_ingenic_tcu:
>>  +	kfree(tcu);
>>  +	return ret;
>>  +}
>>  +
>>  +TIMER_OF_DECLARE(jz4740_tcu_intc,  "ingenic,jz4740-tcu",  
>> ingenic_tcu_init);
>>  +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", 
>> ingenic_tcu_init);
>>  +TIMER_OF_DECLARE(jz4770_tcu_intc,  "ingenic,jz4770-tcu",  
>> ingenic_tcu_init);
>>  +
>>  +
>>  +static int __init ingenic_tcu_probe(struct platform_device *pdev)
>>  +{
>>  +	platform_set_drvdata(pdev, ingenic_tcu);
>>  +
>>  +	regmap_attach_dev(&pdev->dev, ingenic_tcu->map,
>>  +			  &ingenic_tcu_regmap_config);
>>  +
>>  +	return devm_of_platform_populate(&pdev->dev);
>>  +}
>>  +
>>  +#ifdef CONFIG_PM_SLEEP
>>  +static int ingenic_tcu_suspend(struct device *dev)
>>  +{
>>  +	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
>>  +
>>  +	clk_disable(tcu->cs_clk);
>>  +	clk_disable(tcu->timer_clk);
>>  +	clk_disable(tcu->clk);
>>  +	return 0;
>>  +}
>>  +
>>  +static int ingenic_tcu_resume(struct device *dev)
>>  +{
>>  +	struct ingenic_tcu *tcu = dev_get_drvdata(dev);
>>  +	int ret;
>>  +
>>  +	ret = clk_enable(tcu->clk);
>>  +	if (ret)
>>  +		return ret;
>>  +
>>  +	ret = clk_enable(tcu->timer_clk);
>>  +	if (ret)
>>  +		goto err_tcu_clk_disable;
>>  +
>>  +	ret = clk_enable(tcu->cs_clk);
>>  +	if (ret)
>>  +		goto err_tcu_timer_clk_disable;
>>  +
>>  +	return 0;
>>  +
>>  +err_tcu_timer_clk_disable:
>>  +	clk_disable(tcu->timer_clk);
>>  +err_tcu_clk_disable:
>>  +	clk_disable(tcu->clk);
>>  +	return ret;
>>  +}
>>  +
>>  +static const struct dev_pm_ops ingenic_tcu_pm_ops = {
>>  +	/* _noirq: We want the TCU clock to be gated last / ungated first 
>> */
>>  +	.suspend_noirq = ingenic_tcu_suspend,
>>  +	.resume_noirq  = ingenic_tcu_resume,
>>  +};
>>  +#define INGENIC_TCU_PM_OPS (&ingenic_tcu_pm_ops)
>>  +
>>  +#else
>>  +#define INGENIC_TCU_PM_OPS NULL
>>  +#endif /* CONFIG_PM_SUSPEND */
>>  +
>>  +static struct platform_driver ingenic_tcu_driver = {
>>  +	.driver = {
>>  +		.name	= "ingenic-tcu",
>>  +		.pm	= INGENIC_TCU_PM_OPS,
>>  +		.of_match_table = ingenic_tcu_of_match,
>>  +	},
>>  +};
>>  +builtin_platform_driver_probe(ingenic_tcu_driver, 
>> ingenic_tcu_probe);
>>  +
>>  +bool ingenic_tcu_pwm_can_use_chn(unsigned int channel)
>>  +{
>>  +	return !!(ingenic_tcu->pwm_channels_mask & BIT(channel));
>>  +}
>>  +EXPORT_SYMBOL_GPL(ingenic_tcu_pwm_can_use_chn);
>>  diff --git a/drivers/clocksource/ingenic-timer.h 
>> b/drivers/clocksource/ingenic-timer.h
>>  new file mode 100644
>>  index 000000000000..fa43da836ab6
>>  --- /dev/null
>>  +++ b/drivers/clocksource/ingenic-timer.h
>>  @@ -0,0 +1,15 @@
>>  +/* SPDX-License-Identifier: GPL-2.0 */
>>  +#ifndef __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
>>  +#define __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__
>>  +
>>  +#include <linux/compiler_types.h>
>>  +
>>  +/*
>>  + * README: For use *ONLY* by the ingenic-ost driver.
>>  + * Regular drivers which want to access the TCU registers
>>  + * must have ingenic-timer as parent and retrieve the regmap
>>  + * doing dev_get_regmap(pdev->dev.parent);
>>  + */
>>  +extern void __iomem *ingenic_tcu_base;
>>  +
>>  +#endif /* __DRIVERS_CLOCKSOURCE_INGENIC_TIMER_H__ */
>>  diff --git a/include/linux/mfd/ingenic-tcu.h 
>> b/include/linux/mfd/ingenic-tcu.h
>>  index ab16ad283def..dac3fac35c1e 100644
>>  --- a/include/linux/mfd/ingenic-tcu.h
>>  +++ b/include/linux/mfd/ingenic-tcu.h
>>  @@ -53,4 +53,6 @@
>>   #define TCU_REG_TCNTc(c)	(TCU_REG_TCNT0 + ((c) * 
>> TCU_CHANNEL_STRIDE))
>>   #define TCU_REG_TCSRc(c)	(TCU_REG_TCSR0 + ((c) * 
>> TCU_CHANNEL_STRIDE))
>> 
>>  +bool ingenic_tcu_pwm_can_use_chn(unsigned int channel);
>>  +
>>   #endif /* __LINUX_MFD_INGENIC_TCU_H_ */
>> 
> 
> 
> --
>  <http://www.linaro.org/> Linaro.org │ Open source software for ARM 
> SoCs
> 
> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
> <http://twitter.com/#!/linaroorg> Twitter |
> <http://www.linaro.org/linaro-blog/> Blog
> 


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation
  2019-02-23  1:17           ` Paul Cercueil
@ 2019-02-28 18:42             ` Stephen Boyd
  0 siblings, 0 replies; 43+ messages in thread
From: Stephen Boyd @ 2019-02-28 18:42 UTC (permalink / raw)
  To: Paul Cercueil, u.kleine-koenig
  Cc: Thierry Reding, Rob Herring, Mark Rutland, Daniel Lezcano,
	Thomas Gleixner, Ralf Baechle, Paul Burton, James Hogan,
	Jonathan Corbet, linux-pwm, devicetree, linux-kernel,
	linux-watchdog, linux-mips, linux-doc, linux-clk, Russell King

Quoting Paul Cercueil (2019-02-22 17:17:58)
> Bump.
> 
> What should I do here?
> 

I thought I replied to the list but maybe it got rejected because my MUA
is currently failing hard at sending 8-bit mails without
using quoted printable. Let me remove all non-ascii characters from this
mail!

If someone has the mail please rebounce it to the list. Otherwise, I
think I basically said this is OK because clk_round_rate() semantics are
specifically vague here to allow the implementation to decide how rates
are rounded (up, down, closest, etc). As long as the whole rate space is
searched with a +1 or a -1 style of search it should be work. We're not
going to add round_up() or round_down() APIs as far as I'm concerned,
and you can look on the list to see previous proposals on that topic to
get some background on why they aren't liked.


^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~2019-02-28 18:42 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-12-27 18:12 [PATCH v9 00/27] Ingenic TCU patchset v9 Paul Cercueil
2018-12-27 18:12 ` [PATCH v9 01/27] dt-bindings: ingenic: Add DT bindings for TCU clocks Paul Cercueil
2018-12-27 18:12 ` [PATCH v9 02/27] doc: Add doc for the Ingenic TCU hardware Paul Cercueil
2018-12-27 18:12 ` [PATCH v9 03/27] dt-bindings: Add doc for the Ingenic TCU drivers Paul Cercueil
2018-12-28 21:48   ` Rob Herring
2018-12-27 18:12 ` [PATCH v9 04/27] clocksource: Add a new timer-ingenic driver Paul Cercueil
2019-02-25 15:55   ` Daniel Lezcano
2019-02-25 22:59     ` Paul Cercueil
2018-12-27 18:12 ` [PATCH v9 05/27] clocksource: Add driver for the Ingenic JZ47xx OST Paul Cercueil
2018-12-27 18:12 ` [PATCH v9 06/27] MAINTAINERS: Add myself as maintainer for Ingenic TCU drivers Paul Cercueil
2018-12-27 18:12 ` [PATCH v9 07/27] watchdog: jz4740: Use WDT clock provided by TCU driver Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 08/27] watchdog: jz4740: Use regmap " Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 09/27] watchdog: jz4740: Avoid starting watchdog in set_timeout Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 10/27] watchdog: jz4740: Drop dependency on MACH_JZ47xx, use COMPILE_TEST Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 11/27] pwm: jz4740: Apply configuration atomically Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 12/27] pwm: jz4740: Use regmap from TCU driver Paul Cercueil
2019-01-05 19:42   ` Uwe Kleine-König
2019-01-05 20:46     ` Paul Cercueil
2019-01-05 21:18       ` Uwe Kleine-König
2018-12-27 18:13 ` [PATCH v9 13/27] pwm: jz4740: Use clocks " Paul Cercueil
2019-01-05 19:45   ` Uwe Kleine-König
2019-01-05 20:52     ` Paul Cercueil
2019-01-05 21:19       ` Uwe Kleine-König
2018-12-27 18:13 ` [PATCH v9 14/27] pwm: jz4740: Improve algorithm of clock calculation Paul Cercueil
2019-01-05 19:57   ` Uwe Kleine-König
2019-01-05 21:05     ` Paul Cercueil
2019-01-05 21:27       ` Uwe Kleine-König
2019-01-10 14:04         ` Paul Cercueil
2019-02-23  1:17           ` Paul Cercueil
2019-02-28 18:42             ` Stephen Boyd
2018-12-27 18:13 ` [PATCH v9 15/27] pwm: jz4740: Allow selection of PWM channels 0 and 1 Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 16/27] pwm: jz4740: Drop dependency on MACH_INGENIC, use COMPILE_TEST Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 17/27] pwm: jz4740: Remove unused devicetree compatible strings Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 18/27] clk: jz4740: Add TCU clock Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 19/27] MIPS: Kconfig: Select TCU timer driver when MACH_INGENIC is set Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 20/27] MIPS: jz4740: Add DTS nodes for the TCU drivers Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 21/27] MIPS: qi_lb60: Move PWM devices to devicetree Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 22/27] MIPS: qi_lb60: Reduce system timer and clocksource to 750 kHz Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 23/27] MIPS: CI20: Reduce system timer to 3 MHz Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 24/27] MIPS: CI20: defconfig: enable OST driver Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 25/27] MIPS: GCW0: Reduce system timer to 750 kHz Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 26/27] MIPS: GCW0: defconfig: Enable OST, watchdog, PWM drivers Paul Cercueil
2018-12-27 18:13 ` [PATCH v9 27/27] MIPS: jz4740: Drop obsolete code Paul Cercueil

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).