All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings
       [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
@ 2020-03-06 13:00 ` Sergey.Semin
  2020-03-10  2:02   ` Stephen Boyd
       [not found]   ` <20200310021052.2E40F80307C5@mail.baikalelectronics.ru>
  2020-03-06 13:00 ` [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings Sergey.Semin
                   ` (4 subsequent siblings)
  5 siblings, 2 replies; 19+ messages in thread
From: Sergey.Semin @ 2020-03-06 13:00 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Mark Rutland
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-clk, devicetree, linux-kernel

From: Serge Semin <Sergey.Semin@baikalelectronics.ru>

Baikal-T1 Clocks Control Unit is responsible for transformation of a
signal coming from an external oscillator into clocks of various
frequencies to propagate them then to the corresponding clocks
consumers (either individual IP-blocks or clock domains). In order
to create a set of high-frequency clocks the external signal is
firstly handled by the embedded into CCU PLLs. So the corresponding
dts-node is just a normal clock-provider node with standard set of
properties.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Paul Burton <paulburton@kernel.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
---
 .../bindings/clock/be,bt1-ccu-pll.yaml        | 139 ++++++++++++++++++
 include/dt-bindings/clock/bt1-ccu.h           |  17 +++
 2 files changed, 156 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
 create mode 100644 include/dt-bindings/clock/bt1-ccu.h

diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
new file mode 100644
index 000000000000..f2e397cc147b
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
@@ -0,0 +1,139 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
+#
+# Baikal-T1 Clocks Control Unit PLL Device Tree Bindings.
+#
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/be,bt1-ccu-pll.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Baikal-T1 Clock Control Unit PLLs
+
+maintainers:
+  - Serge Semin <fancer.lancer@gmail.com>
+
+description: |
+  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
+  subsystems clocking and resetting. The CCU is connected with an external
+  fixed rate oscillator, which signal is transformed into clocks of various
+  frequencies and then propagated to either individual IP-blocks or to groups
+  of blocks (clock domains). The transformation is done by means of PLLs and
+  gateable/non-gateable dividers embedded into the CCU. It's logically divided
+  into the next components:
+  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
+     in general can provide any frequency supported by the CCU PLLs).
+  2) PLLs clocks generators (PLLs) - described in this bindings file.
+  3) AXI-bus clock dividers (AXI).
+  4) System devices reference clock dividers (SYS).
+  which are connected with each other as shown on the next figure:
+          +---------------+
+          | Baikal-T1 CCU |
+          |   +----+------|- MIPS P5600 cores
+          | +-|PLLs|------|- DDR controller
+          | | +----+      |
+  +----+  | |  |  |       |
+  |XTAL|--|-+  |  | +---+-|
+  +----+  | |  |  +-|AXI|-|- AXI-bus
+          | |  |    +---+-|
+          | |  |          |
+          | |  +----+---+-|- APB-bus
+          | +-------|SYS|-|- Low-speed Devices
+          |         +---+-|- High-speed Devices
+          +---------------+
+  Each CCU sub-block is represented as a separate dts-node and has an
+  individual driver to be bound with.
+
+  In order to create signals of wide range frequencies the external oscillator
+  output is primarily connected to a set of CCU PLLs. There are five PLLs
+  to create a clock for the MIPS P5600 cores, the embedded DDR controller,
+  SATA, Ethernet and PCIe domains. The last three domains though named by the
+  biggest system interfaces in fact include nearly all of the rest SoC
+  peripherals. Each of the PLLs is based on True Circuits TSMC CLN28HPM core
+  with an interface wrapper (so called safe PLL' clocks switcher) to simplify
+  the PLL configuration procedure. The PLLs work as depicted on the next
+  diagram:
+      +--------------------------+
+      |                          |
+      +-->+---+    +---+   +---+ |  +---+   0|\
+  CLKF--->|/NF|--->|PFD|...|VCO|-+->|/OD|--->| |
+          +---+ +->+---+   +---+ /->+---+    | |--->CLKOUT
+  CLKOD---------C----------------+          1| |
+       +--------C--------------------------->|/
+       |        |                             ^
+  Rclk-+->+---+ |                             |
+  CLKR--->|/NR|-+                             |
+          +---+                               |
+  BYPASS--------------------------------------+
+  BWADJ--->
+  where Rclk is the reference clock coming  from XTAL, NR - reference clock
+  divider, NF - PLL clock multiplier, OD - VCO output clock divider, CLKOUT -
+  output clock, BWADJ is the PLL bandwidth adjustment parameter. At this moment
+  the binding supports the PLL dividers configuration in accordance with a
+  requested rate, while bypassing and bandwidth adjustment settings can be
+  added in future if it gets to be necessary.
+
+  The PLLs CLKOUT is then either directly connected with the corresponding
+  clocks consumer (like P5600 cores or DDR controller) or passed over a CCU
+  divider to create a signal required for the clock domain.
+
+  The CCU PLL dts-node uses the common clock bindings [1] with no custom
+  parameters. The list of exported clocks can be found in
+  'dt-bindings/clock/bt1-ccu.h'.
+
+  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+allOf:
+  - $ref: /schemas/clock/clock.yaml#
+
+properties:
+  compatible:
+    const: be,bt1-ccu-pll
+
+  reg:
+    description: CCU PLLs sub-block base address.
+    maxItems: 1
+
+  "#clock-cells":
+    description: |
+      Clocks are referenced by the node phandle and an unique identifier
+      from 'dt-bindings/clock/bt1-ccu.h'.
+    const: 1
+
+  clocks:
+    description: Phandle of CCU External reference clock.
+    maxItems: 1
+
+  clock-names:
+    const: ref_clk
+
+  clock-output-names: true
+
+  assigned-clocks: true
+
+  assigned-clock-rates: true
+
+additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - "#clock-cells"
+  - clocks
+  - clock-names
+
+examples:
+  - |
+    ccu_pll: ccu_pll@1F04D000 {
+      compatible = "be,bt1-ccu-pll";
+      reg = <0x1F04D000 0x028>;
+      #clock-cells = <1>;
+
+      clocks = <&osc25>;
+      clock-names = "ref_clk";
+
+      clock-output-names = "cpu_pll", "sata_pll", "ddr_pll",
+                           "pcie_pll", "eth_pll";
+    };
+...
diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
new file mode 100644
index 000000000000..86e63162ade0
--- /dev/null
+++ b/include/dt-bindings/clock/bt1-ccu.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal-T1 CCU clock indeces.
+ */
+#ifndef __DT_BINDINGS_CLOCK_BT1_CCU_H
+#define __DT_BINDINGS_CLOCK_BT1_CCU_H
+
+/* Baikal-T1 CCU PLL indeces. */
+#define CCU_CPU_PLL			0
+#define CCU_SATA_PLL			1
+#define CCU_DDR_PLL			2
+#define CCU_PCIE_PLL			3
+#define CCU_ETH_PLL			4
+
+#endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
-- 
2.25.1


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

* [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings
       [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
  2020-03-06 13:00 ` [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings Sergey.Semin
@ 2020-03-06 13:00 ` Sergey.Semin
  2020-03-12 20:50   ` Rob Herring
  2020-03-06 13:00 ` [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices " Sergey.Semin
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 19+ messages in thread
From: Sergey.Semin @ 2020-03-06 13:00 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Mark Rutland,
	Philipp Zabel
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-clk, devicetree, linux-kernel

From: Serge Semin <Sergey.Semin@baikalelectronics.ru>

After being gained by the CCU PLLs the signals must be transformed to
be suitable for the clock-consumers. This is done by a set of dividers
embedded into the CCU. A first block of dividers is used to create
reference clocks for AXI-bus of high-speed peripheral IP-cores of the
chip. So the AXI-bus CCU dts-node is an ordinary clock-provider with
standard set of properties supported. But in addition to that each
AXI-bus clock divider provide a way to reset the corresponding clock
domain. This makes the AXI-bus CCU dts-node to be also a reset-provider.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Paul Burton <paulburton@kernel.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
---
 .../bindings/clock/be,bt1-ccu-axi.yaml        | 151 ++++++++++++++++++
 include/dt-bindings/clock/bt1-ccu.h           |  13 ++
 include/dt-bindings/reset/bt1-ccu.h           |  23 +++
 3 files changed, 187 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
 create mode 100644 include/dt-bindings/reset/bt1-ccu.h

diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
new file mode 100644
index 000000000000..6b1eefdead27
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
@@ -0,0 +1,151 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
+#
+# Baikal-T1 AXI-bus Clocks Control Unit Device Tree Bindings.
+#
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/be,bt1-ccu-axi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Baikal-T1 AXI-bus Clock Control Unit
+
+maintainers:
+  - Serge Semin <fancer.lancer@gmail.com>
+
+description: |
+  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
+  subsystems clocking and resetting. The CCU is connected with an external
+  fixed rate oscillator, which signal is transformed into clocks of various
+  frequencies and then propagated to either individual IP-blocks or to groups
+  of blocks (clock domains). The transformation is done by means of an embedded
+  into CCU PLLs and gateable/non-gateable dividers. Each clock domain can be
+  also individually reset by using the domain clocks divider configuration
+  registers. Baikal-T1 CCU is logically divided into the next components:
+  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
+     in general can provide any frequency supported by the CCU PLLs).
+  2) PLLs clocks generators (PLLs).
+  3) AXI-bus clock dividers (AXI) - described in this bindings file.
+  4) System devices reference clock dividers (SYS).
+  which are connected with each other as shown on the next figure:
+          +---------------+
+          | Baikal-T1 CCU |
+          |   +----+------|- MIPS P5600 cores
+          | +-|PLLs|------|- DDR controller
+          | | +----+      |
+  +----+  | |  |  |       |
+  |XTAL|--|-+  |  | +---+-|
+  +----+  | |  |  +-|AXI|-|- AXI-bus
+          | |  |    +---+-|
+          | |  |          |
+          | |  +----+---+-|- APB-bus
+          | +-------|SYS|-|- Low-speed Devices
+          |         +---+-|- High-speed Devices
+          +---------------+
+  Each sub-block is represented as a separate dts-node and has an individual
+  driver to be bound with.
+
+  In order to create signals of wide range frequencies the external oscillator
+  output is primarily connected to a set of CCU PLLs. Some of PLLs CLKOUT are
+  then passed over CCU dividers to create signals required for the target clock
+  domain (like AXI-bus consumers). The dividers have the following structure:
+          +--------------+
+  CLKIN --|->+----+ 1|\  |
+  SETCLK--|--|/DIV|->| | |
+  CLKDIV--|--|    |  | |-|->CLKLOUT
+  LOCK----|--+----+  | | |
+          |          |/  |
+          |           |  |
+  EN------|-----------+  |
+  RST-----|--------------|->RSTOUT
+          +--------------+
+  where CLKIN is the reference clock coming either from a CCU PLL, SETCLK - a
+  command to update the output clock in accordance with a set divider,
+  CLKDIV - clocks divider, LOCK - a signal of the output clock stabilization,
+  EN - enable/disable the divider block, RST/RSTOUT - reset clocks domain
+  signal. Depending on the consumer IP-core peculiarities the dividers may lack
+  of some functionality depicted on the figure above (like EN,
+  CLKDIV/LOCK/SETCLK). In this case the corresponding clock provider just
+  doesn't expose either switching functions, or the rate configuration, or
+  both of them.
+
+  The CCU AXI dts-node uses the common clock bindings [1] with no custom
+  properties. The list of exported clocks and reset signals can be found in
+  the files: 'dt-bindings/clock/bt1-ccu.h' and 'dt-bindings/reset/bt1-ccu.h'.
+
+  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+allOf:
+  - $ref: /schemas/clock/clock.yaml#
+
+properties:
+  compatible:
+    const: be,bt1-ccu-axi
+
+  reg:
+    description: AXI-bus CCU dividers sub-block base address.
+    maxItems: 1
+
+  "#clock-cells":
+    description: |
+      Clocks are referenced by the node phandle and an unique identifier
+      from 'dt-bindings/clock/bt1-ccu.h'.
+    const: 1
+
+  "#reset-cells":
+    description: |
+      AXI-bus CCU sub-block provides a reset signal for each clock domain,
+      which unique identifiers are in 'dt-bindings/reset/bt1-ccu.h'.
+    const: 1
+
+  clocks:
+    items:
+      - description: CCU SATA PLL output clock.
+      - description: CCU PCIe PLL output clock.
+      - description: CCU Ethernet PLL output clock.
+
+  clock-names:
+    items:
+      - const: sata_clk
+      - const: pcie_clk
+      - const: eth_clk
+
+  clock-output-names: true
+
+  assigned-clocks: true
+
+  assigned-clock-rates: true
+
+additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - "#clock-cells"
+  - clocks
+  - clock-names
+
+examples:
+  - |
+    #include <dt-bindings/clock/bt1-ccu.h>
+
+    ccu_axi: ccu_axi@1F04D030 {
+      compatible = "be,bt1-ccu-axi";
+      reg = <0x1F04D030 0x030>;
+      #clock-cells = <1>;
+      #reset-cells = <1>;
+
+      clocks = <&ccu_pll CCU_SATA_PLL>,
+               <&ccu_pll CCU_PCIE_PLL>,
+               <&ccu_pll CCU_ETH_PLL>;
+      clock-names = "sata_clk", "pcie_clk", "eth_clk";
+
+      clock-output-names = "axi_main_clk", "axi_ddr_clk",
+                           "axi_sata_clk", "axi_gmac0_clk",
+                           "axi_gmac1_clk", "axi_xgmac_clk",
+                           "axi_pcie_m_clk", "axi_pcie_s_clk",
+                           "axi_usb_clk", "axi_hwa_clk",
+                           "axi_sram_clk";
+    };
+...
diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
index 86e63162ade0..ebe723c6e0a8 100644
--- a/include/dt-bindings/clock/bt1-ccu.h
+++ b/include/dt-bindings/clock/bt1-ccu.h
@@ -14,4 +14,17 @@
 #define CCU_PCIE_PLL			3
 #define CCU_ETH_PLL			4
 
+/* Baikal-T1 AXI-bus CCU Clocks indeces. */
+#define CCU_AXI_MAIN_CLK		0
+#define CCU_AXI_DDR_CLK			1
+#define CCU_AXI_SATA_CLK		2
+#define CCU_AXI_GMAC0_CLK		3
+#define CCU_AXI_GMAC1_CLK		4
+#define CCU_AXI_XGMAC_CLK		5
+#define CCU_AXI_PCIE_M_CLK		6
+#define CCU_AXI_PCIE_S_CLK		7
+#define CCU_AXI_USB_CLK			8
+#define CCU_AXI_HWA_CLK			9
+#define CCU_AXI_SRAM_CLK		10
+
 #endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h
new file mode 100644
index 000000000000..4de5b6bcd433
--- /dev/null
+++ b/include/dt-bindings/reset/bt1-ccu.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal-T1 CCU reset indeces.
+ */
+#ifndef __DT_BINDINGS_RESET_BT1_CCU_H
+#define __DT_BINDINGS_RESET_BT1_CCU_H
+
+/* Baikal-T1 AXI-bus CCU Reset indeces. */
+#define CCU_AXI_MAIN_RST		0
+#define CCU_AXI_DDR_RST			1
+#define CCU_AXI_SATA_RST		2
+#define CCU_AXI_GMAC0_RST		3
+#define CCU_AXI_GMAC1_RST		4
+#define CCU_AXI_XGMAC_RST		5
+#define CCU_AXI_PCIE_M_RST		6
+#define CCU_AXI_PCIE_S_RST		7
+#define CCU_AXI_USB_RST			8
+#define CCU_AXI_HWA_RST			9
+#define CCU_AXI_SRAM_RST		10
+
+#endif /* __DT_BINDINGS_RESET_BT1_CCU_H */
-- 
2.25.1


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

* [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices CCU bindings
       [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
  2020-03-06 13:00 ` [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings Sergey.Semin
  2020-03-06 13:00 ` [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings Sergey.Semin
@ 2020-03-06 13:00 ` Sergey.Semin
  2020-03-10  2:19   ` Stephen Boyd
       [not found]   ` <20200310021915.8A0E7803087C@mail.baikalelectronics.ru>
  2020-03-06 13:00 ` [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver Sergey.Semin
                   ` (2 subsequent siblings)
  5 siblings, 2 replies; 19+ messages in thread
From: Sergey.Semin @ 2020-03-06 13:00 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Mark Rutland,
	Philipp Zabel
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-clk, devicetree, linux-kernel

From: Serge Semin <Sergey.Semin@baikalelectronics.ru>

Aside from providing an individual reference clocks for AXI-bus the
Baikal-T1 CCU dividers are used to alter the PLLs output signals for
the SoC peripheral devices. These dividers are represented by means
of the SYS CCU dts-node as an ordinary clock-provider with standard
set of properties supported. In the same way as AXI-bus CCU dividers
do they can be used to reset the APB and SATA clock domains, which
also makes the SYS CCU dts-node to be a reset-controller.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Paul Burton <paulburton@kernel.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
---
 .../bindings/clock/be,bt1-ccu-sys.yaml        | 169 ++++++++++++++++++
 include/dt-bindings/clock/bt1-ccu.h           |  24 +++
 include/dt-bindings/reset/bt1-ccu.h           |   4 +
 3 files changed, 197 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml

diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
new file mode 100644
index 000000000000..aea09fbafc89
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
@@ -0,0 +1,169 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
+#
+# Baikal-T1 System Devices Clocks Control Unit Device Tree Bindings.
+#
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/be,bt1-ccu-sys.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Baikal-T1 System Devices Clock Control Unit
+
+maintainers:
+  - Serge Semin <fancer.lancer@gmail.com>
+
+description: |
+  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
+  subsystems clocking and resetting. The CCU is connected with an external
+  fixed rate oscillator, which signal is transformed into clocks of various
+  frequencies and then propagated to either individual IP-blocks or to groups
+  of blocks (clock domains). The transformation is done by means of an embedded
+  into CCU PLLs and gateable/non-gateable dividers. APB-bus divider register
+  provides a specific flag to initiate the full APB clock domain reset, so
+  causing each sub-device reset. Baikal-T1 CCU is logically divided into the
+  next components:
+  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
+     in general can provide any frequency supported by the CCU PLLs).
+  2) PLLs clocks generators (PLLs).
+  3) AXI-bus clock dividers (AXI).
+  4) System devices reference clock dividers (SYS) - described in this bindings
+     file.
+  which are connected with each other as shown on the next figure:
+          +---------------+
+          | Baikal-T1 CCU |
+          |   +----+------|- MIPS P5600 cores
+          | +-|PLLs|------|- DDR controller
+          | | +----+      |
+  +----+  | |  |  |       |
+  |XTAL|--|-+  |  | +---+-|
+  +----+  | |  |  +-|AXI|-|- AXI-bus
+          | |  |    +---+-|
+          | |  |          |
+          | |  +----+---+-|- APB-bus
+          | +-------|SYS|-|- Low-speed Devices
+          |         +---+-|- High-speed Devices
+          +---------------+
+  Each sub-block is represented as a separate dts-node and has an individual
+  driver to be bound with.
+
+  In order to create signals of wide range frequencies the external oscillator
+  output is primarily connected to a set of CCU PLLs. The PLLs CLKOUT is then
+  either directly connected with the corresponding clocks consumer (like P5600
+  cores or DDR controller) or passed over a CCU divider to create a signal
+  required for the clock domain. The dividers have the following structure
+          +--------------+
+  CLKIN --|->+----+ 1|\  |
+  SETCLK--|--|/DIV|->| | |
+  CLKDIV--|--|    |  | |-|->CLKLOUT
+  LOCK----|--+----+  | | |
+          |          |/  |
+          |           |  |
+  EN------|-----------+  |
+  RST-----|--------------|->RSTOUT
+          +--------------+
+  where CLKIN is the reference clock coming either from XTAL or from a CCU PLL,
+  SETCLK - a command to update the output clock in accordance with a set
+  divider, CLKDIV - clocks divider, LOCK - a signal of the output clock
+  stabilization, EN - enable/disable the divider block, RST/RSTOUT - reset
+  clocks domain signal. Depending on the consumer IP-core peculiarities the
+  dividers may lack of some functionality depicted on the figure above (like
+  EN, CLKDIV/LOCK/SETCLK or RST). In this case the corresponding clock provider
+  just doesn't expose either switching functions, or the rate configuration, or
+  both of them.
+
+  The clock dividers, which output clock is then consumed by the SoC individual
+  devices, are united into a single clocks provider called System Devices CCU.
+  The System Devices CCU dts-node uses the common clock bindings [1] with no
+  custom properties. The list of exported clocks and reset signals can be found
+  in the files: 'dt-bindings/clock/bt1-ccu.h' and
+  'dt-bindings/reset/bt1-ccu.h'.
+
+  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+allOf:
+  - $ref: /schemas/clock/clock.yaml#
+
+properties:
+  compatible:
+    const: be,bt1-ccu-sys
+
+  reg:
+    items:
+      - description: System Devices CCU sub-block base address.
+      - description: Watchdog clock divider register address in CCU.
+
+  "#clock-cells":
+    description: |
+      Clocks are referenced by the node phandle and an unique identifier
+      from 'dt-bindings/clock/bt1-ccu.h'.
+    const: 1
+
+  "#reset-cells":
+    description: |
+      CCU system devices sub-block provides a reset signal for APB and SATA
+      clock domains, which unique identifiers reside in
+      'dt-bindings/reset/bt1-ccu.h'.
+    const: 1
+
+  clocks:
+    items:
+      - description: CCU external reference clock.
+      - description: CCU SATA PLL output clock.
+      - description: CCU PCIe PLL output clock.
+      - description: CCU Ethernet PLL output clock.
+
+  clock-names:
+    items:
+      - const: ref_clk
+      - const: sata_clk
+      - const: pcie_clk
+      - const: eth_clk
+
+  clock-output-names: true
+
+  assigned-clocks: true
+
+  assigned-clock-rates: true
+
+additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - "#clock-cells"
+  - clocks
+  - clock-names
+
+examples:
+  - |
+    #include <dt-bindings/clock/bt1-ccu.h>
+
+    ccu_sys: ccu_sys@1F04D060 {
+      compatible = "be,bt1-ccu-sys";
+      reg = <0x1F04D060 0x0A0>,
+            <0x1F04D150 0x004>;
+      #clock-cells = <1>;
+      #reset-cells = <1>;
+
+      clocks = <&osc25>,
+               <&ccu_pll CCU_SATA_PLL>,
+               <&ccu_pll CCU_PCIE_PLL>,
+               <&ccu_pll CCU_ETH_PLL>;
+      clock-names = "ref_clk", "sata_clk", "pcie_clk",
+                    "eth_clk";
+
+      clock-output-names = "sys_sata_ref_clk", "sys_apb_clk",
+                           "sys_gmac0_csr_clk", "sys_gmac0_tx_clk",
+                           "sys_gmac0_ptp_clk", "sys_gmac1_csr_clk",
+                           "sys_gmac1_tx_clk", "sys_gmac1_ptp_clk",
+                           "sys_xgmac_ref_clk", "sys_xgmac_ptp_clk",
+                           "sys_usb_clk", "sys_pvt_clk",
+                           "sys_hwa_clk", "sys_uart_clk",
+                           "sys_spi_clk", "sys_i2c1_clk",
+                           "sys_i2c2_clk", "sys_gpio_clk",
+                           "sys_timer0_clk", "sys_timer1_clk",
+                           "sys_timer2_clk", "sys_wdt_clk";
+      };
+...
diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
index ebe723c6e0a8..4dcad1eb721f 100644
--- a/include/dt-bindings/clock/bt1-ccu.h
+++ b/include/dt-bindings/clock/bt1-ccu.h
@@ -27,4 +27,28 @@
 #define CCU_AXI_HWA_CLK			9
 #define CCU_AXI_SRAM_CLK		10
 
+/* Baikal-T1 System Devices CCU Clocks indeces. */
+#define CCU_SYS_SATA_REF_CLK		0
+#define CCU_SYS_APB_CLK			1
+#define CCU_SYS_GMAC0_CSR_CLK		2
+#define CCU_SYS_GMAC0_TX_CLK		3
+#define CCU_SYS_GMAC0_PTP_CLK		4
+#define CCU_SYS_GMAC1_CSR_CLK		5
+#define CCU_SYS_GMAC1_TX_CLK		6
+#define CCU_SYS_GMAC1_PTP_CLK		7
+#define CCU_SYS_XGMAC_REF_CLK		8
+#define CCU_SYS_XGMAC_PTP_CLK		9
+#define CCU_SYS_USB_CLK			10
+#define CCU_SYS_PVT_CLK			11
+#define CCU_SYS_HWA_CLK			12
+#define CCU_SYS_UART_CLK		13
+#define CCU_SYS_SPI_CLK			14
+#define CCU_SYS_I2C1_CLK		15
+#define CCU_SYS_I2C2_CLK		16
+#define CCU_SYS_GPIO_CLK		17
+#define CCU_SYS_TIMER0_CLK		18
+#define CCU_SYS_TIMER1_CLK		19
+#define CCU_SYS_TIMER2_CLK		20
+#define CCU_SYS_WDT_CLK			21
+
 #endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h
index 4de5b6bcd433..0bd8fd0edb41 100644
--- a/include/dt-bindings/reset/bt1-ccu.h
+++ b/include/dt-bindings/reset/bt1-ccu.h
@@ -20,4 +20,8 @@
 #define CCU_AXI_HWA_RST			9
 #define CCU_AXI_SRAM_RST		10
 
+/* Baikal-T1 System Devices CCU Reset indeces. */
+#define CCU_SYS_SATA_REF_RST		0
+#define CCU_SYS_APB_RST			1
+
 #endif /* __DT_BINDINGS_RESET_BT1_CCU_H */
-- 
2.25.1


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

* [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver
       [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
                   ` (2 preceding siblings ...)
  2020-03-06 13:00 ` [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices " Sergey.Semin
@ 2020-03-06 13:00 ` Sergey.Semin
  2020-03-10 15:30   ` Stephen Boyd
  2020-03-06 13:00 ` [PATCH 5/5] clk: Add Baikal-T1 CCU dividers driver Sergey.Semin
  2020-03-10  0:21 ` [PATCH 0/5] clk: Add Baikal-T1 SoC Clock Control Unit support Sergey Semin
  5 siblings, 1 reply; 19+ messages in thread
From: Sergey.Semin @ 2020-03-06 13:00 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-kernel, linux-clk

From: Serge Semin <Sergey.Semin@baikalelectronics.ru>

Baikal-T1 is supposed to be supplied with a high-frequency external
oscillator. But in order to create signals suitable for each IP-block
embedded into the SoC the oscillator output is primarily connected to
a set of CCU PLLs. There are five of them to create clocks for the MIPS
P5600 cores, the embedded DDR controller, SATA, Ethernet and PCIe
domains. The last three domains though named by the biggest system
interfaces in fact include nearly all of the rest SoC peripherals.
Each of the PLLs is based on True Circuits TSMC CLN28HPM IP-core with
an interface wrapper (so called safe PLL' clocks switcher) to simplify
the PLL configuration procedure.

This driver creates the of-based hardware clocks to use them then in
the corresponding subsystems. In order to simplify the driver code we
split the functionality up into the PLLs clocks operations and hardware
clocks declaration/registration procedures. So if CLK_BT1_CCU is
defined, then the first part is available in the kernel, while
CLK_BT1_CCU_PLL config makes the actual clocks being registered at the
time of_clk_init() is called.

Even though the PLLs are based on the same IP-core, they actually may
have some differences. In particular, some CCU PLLs supports the output
clock change without gating them (like CPU or PCIe PLLs), while the
others don't, some CCU PLLs are critical and aren't supposed to be
gated. In order to cover all of these cases the hardware clocks driver
is designed with a info-descriptor pattern. So there are special static
descriptors declared for each PLL, which is then used to create a
hardware clock with proper operations. Additionally debugfs-files are
provided for each PLL' field to make sure the implemented
rate-PLLs-dividers calculation algorithm is correct.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Paul Burton <paulburton@kernel.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
---
 drivers/clk/Kconfig                 |   1 +
 drivers/clk/Makefile                |   1 +
 drivers/clk/baikal-t1/Kconfig       |  35 ++
 drivers/clk/baikal-t1/Makefile      |   2 +
 drivers/clk/baikal-t1/ccu-pll.c     | 474 ++++++++++++++++++++++++++++
 drivers/clk/baikal-t1/ccu-pll.h     |  73 +++++
 drivers/clk/baikal-t1/clk-ccu-pll.c | 217 +++++++++++++
 drivers/clk/baikal-t1/common.h      |  44 +++
 8 files changed, 847 insertions(+)
 create mode 100644 drivers/clk/baikal-t1/Kconfig
 create mode 100644 drivers/clk/baikal-t1/Makefile
 create mode 100644 drivers/clk/baikal-t1/ccu-pll.c
 create mode 100644 drivers/clk/baikal-t1/ccu-pll.h
 create mode 100644 drivers/clk/baikal-t1/clk-ccu-pll.c
 create mode 100644 drivers/clk/baikal-t1/common.h

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index bcb257baed06..b32da34ebcf9 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -341,6 +341,7 @@ config COMMON_CLK_FIXED_MMIO
 
 source "drivers/clk/actions/Kconfig"
 source "drivers/clk/analogbits/Kconfig"
+source "drivers/clk/baikal-t1/Kconfig"
 source "drivers/clk/bcm/Kconfig"
 source "drivers/clk/hisilicon/Kconfig"
 source "drivers/clk/imgtec/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f4169cc2fd31..1496045d4e01 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -75,6 +75,7 @@ obj-y					+= analogbits/
 obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
 obj-$(CONFIG_ARCH_ARTPEC)		+= axis/
 obj-$(CONFIG_ARC_PLAT_AXS10X)		+= axs10x/
+obj-$(CONFIG_CLK_BAIKAL_T1)		+= baikal-t1/
 obj-y					+= bcm/
 obj-$(CONFIG_ARCH_BERLIN)		+= berlin/
 obj-$(CONFIG_ARCH_DAVINCI)		+= davinci/
diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig
new file mode 100644
index 000000000000..0e2fc86f3ab8
--- /dev/null
+++ b/drivers/clk/baikal-t1/Kconfig
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0
+config CLK_BAIKAL_T1
+	bool "Baikal-T1 Clocks Control Unit interface"
+	depends on (MIPS_BAIKAL_T1 || COMPILE_TEST) && OF
+	default y
+	help
+	  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the
+	  chip subsystems clocking and resetting. It consists of multiple
+	  global clock domains, which can be reset by means of the CCU control
+	  registers. These domains and devices placed in them are fed with
+	  clocks generated by a hierarchy of PLLs, configurable and fixed
+	  dividers. In addition CCU exposes several unrelated functional blocks
+	  like irqless Designware i2c controller with indirectly accessed
+	  registers, AXI bus errors detector, DW PCIe controller PM/clocks/reset
+	  manager, etc.
+
+	  This driver provides a set of functions to create the kernel clock
+	  devices of Baikal-T1 PLLs and dividers, and to manipulate the reset
+	  signals of the SoC.
+
+if CLK_BAIKAL_T1
+
+config CLK_BT1_CCU_PLL
+	bool "Baikal-T1 CCU PLLs support"
+	default y
+	help
+	  Enable this to support the PLLs embedded into the Baikal-T1 SoCs.
+	  These are five PLLs placed at the root of the clocks hierarchy,
+	  right after the external reference osciallator (normally of 25MHz).
+	  They are used to generate a high frequency signals, which are
+	  either directly wired to the consumers (like CPUs, DDR) or passed
+	  over the clock dividers to be only then used as an individual
+	  reference clocks of a target device.
+
+endif
diff --git a/drivers/clk/baikal-t1/Makefile b/drivers/clk/baikal-t1/Makefile
new file mode 100644
index 000000000000..559f034af19e
--- /dev/null
+++ b/drivers/clk/baikal-t1/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_CLK_BT1_CCU_PLL) += ccu-pll.o clk-ccu-pll.o
diff --git a/drivers/clk/baikal-t1/ccu-pll.c b/drivers/clk/baikal-t1/ccu-pll.c
new file mode 100644
index 000000000000..f2087a80b64d
--- /dev/null
+++ b/drivers/clk/baikal-t1/ccu-pll.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ *
+ * Baikal-T1 CCU PLL interface driver.
+ */
+
+#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/limits.h>
+#include <linux/bits.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/time64.h>
+#include <linux/rational.h>
+#include <linux/debugfs.h>
+
+#include "ccu-pll.h"
+#include "common.h"
+
+#define CCU_PLL_CTL			0x00
+#define CCU_PLL_CTL_EN			BIT(0)
+#define CCU_PLL_CTL_RST			BIT(1)
+#define CCU_PLL_CTL_CLKR_FLD		2
+#define CCU_PLL_CTL_CLKR_MASK		GENMASK(7, CCU_PLL_CTL_CLKR_FLD)
+#define CCU_PLL_CTL_CLKF_FLD		8
+#define CCU_PLL_CTL_CLKF_MASK		GENMASK(20, CCU_PLL_CTL_CLKF_FLD)
+#define CCU_PLL_CTL_CLKOD_FLD		21
+#define CCU_PLL_CTL_CLKOD_MASK		GENMASK(24, CCU_PLL_CTL_CLKOD_FLD)
+#define CCU_PLL_CTL_BYPASS		BIT(30)
+#define CCU_PLL_CTL_LOCK		BIT(31)
+#define CCU_PLL_CTL1			0x04
+#define CCU_PLL_CTL1_BWADJ_FLD		3
+#define CCU_PLL_CTL1_BWADJ_MASK		GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD)
+
+#define CCU_PLL_RST_DELAY_US		5
+#define CCU_PLL_LOCK_DELAY_US(_ref_rate, _nr) ({	\
+	uint64_t _n = 500ULL * (_nr) * USEC_PER_SEC;	\
+	do_div(_n, _ref_rate);				\
+	_n;						\
+})
+#define CCU_PLL_LOCK_CHECK_RETRIES	50
+
+#define CCU_PLL_NR_MAX \
+	((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1)
+#define CCU_PLL_NF_MAX \
+	((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1)
+#define CCU_PLL_OD_MAX \
+	((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1)
+#define CCU_PLL_NB_MAX \
+	((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1)
+#define CCU_PLL_FDIV_MIN		427000UL
+#define CCU_PLL_FDIV_MAX		3500000000UL
+#define CCU_PLL_FOUT_MIN		200000000UL
+#define CCU_PLL_FOUT_MAX		2500000000UL
+#define CCU_PLL_FVCO_MIN		700000000UL
+#define CCU_PLL_FVCO_MAX		3500000000UL
+#define CCU_PLL_CLKOD_FACTOR		2
+
+#define CCU_PLL_CALC_FREQ(_ref_rate, _nr, _nf, _od) \
+	((_ref_rate) / (_nr) * (_nf) / (_od))
+
+static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk,
+			 unsigned long nr)
+{
+	unsigned long ud;
+	int count;
+	u32 val;
+
+	ud = CCU_PLL_LOCK_DELAY_US(ref_clk, nr);
+
+	ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_RST, CCU_PLL_CTL_RST);
+
+	count = CCU_PLL_LOCK_CHECK_RETRIES;
+	do {
+		udelay(ud);
+		val = ccu_read(pll->regs + CCU_PLL_CTL);
+	} while (!(val & CCU_PLL_CTL_LOCK) && --count);
+
+	return (val & CCU_PLL_CTL_LOCK) ? 0 : -ETIMEDOUT;
+}
+
+static int ccu_pll_enable(struct clk_hw *hw)
+{
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+	struct ccu_pll *pll = to_ccu_pll(hw);
+	unsigned long flags;
+	int ret;
+	u32 val;
+
+	if (!parent_hw) {
+		pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw));
+		return -EINVAL;
+	}
+
+	val = ccu_read(pll->regs + CCU_PLL_CTL);
+	if (val & CCU_PLL_CTL_EN)
+		return 0;
+
+	spin_lock_irqsave(&pll->regs_lock, flags);
+	ccu_write(pll->regs + CCU_PLL_CTL, val | CCU_PLL_CTL_EN);
+	ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw),
+			    CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1);
+	spin_unlock_irqrestore(&pll->regs_lock, flags);
+	if (ret)
+		pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
+
+	return ret;
+}
+
+static void ccu_pll_disable(struct clk_hw *hw)
+{
+	struct ccu_pll *pll = to_ccu_pll(hw);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pll->regs_lock, flags);
+	ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_EN, 0);
+	spin_unlock_irqrestore(&pll->regs_lock, flags);
+}
+
+static int ccu_pll_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_pll *pll = to_ccu_pll(hw);
+
+	return !!(ccu_read(pll->regs + CCU_PLL_CTL) & CCU_PLL_CTL_EN);
+}
+
+static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct ccu_pll *pll = to_ccu_pll(hw);
+	unsigned long nr, nf, od;
+	u32 val;
+
+	val = ccu_read(pll->regs + CCU_PLL_CTL);
+	nr = CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1;
+	nf = CCU_GET_FLD(CCU_PLL_CTL_CLKF, val) + 1;
+	od = CCU_GET_FLD(CCU_PLL_CTL_CLKOD, val) + 1;
+
+	return CCU_PLL_CALC_FREQ(parent_rate, nr, nf, od);
+}
+
+static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate,
+				 unsigned long *nr, unsigned long *nf,
+				 unsigned long *od)
+{
+	unsigned long err, freq, min_err = ULONG_MAX;
+	unsigned long num, denom, n1, d1, nri;
+	unsigned long nr_max, nf_max, od_max;
+
+	/*
+	 * Make sure PLL is working with valid input signal (Fdiv). If
+	 * you want to speed the function up just reduce CCU_PLL_NR_MAX.
+	 * This will cause a worse approximation though.
+	 */
+	nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1;
+	nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX);
+
+	/*
+	 * Find a closest [nr;nf;od] vector taking into account the
+	 * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is
+	 * either 1 or even number within the acceptable range (alas 1s
+	 * is also excluded by the next loop).
+	 */
+	for (; nri <= nr_max; ++nri) {
+		/* Use Od factor to fulfill the limitation 2). */
+		num = CCU_PLL_CLKOD_FACTOR * rate;
+		denom = parent_rate / nri;
+
+		/*
+		 * Make sure Fvco is within the acceptable range to fulfill
+		 * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value
+		 * the actual upper limit is also divided by that factor.
+		 * It's not big problem for us since practically there is no
+		 * need in clocks with that high frequency.
+		 */
+		nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX);
+		od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR;
+
+		/*
+		 * Bypass the out-of-bound values, which can't be properly
+		 * handled by the rational fraction approximation algorithm.
+		 */
+		if (num / denom >= nf_max) {
+			n1 = nf_max;
+			d1 = 1;
+		} else if (denom / num >= od_max) {
+			n1 = 1;
+			d1 = od_max;
+		} else {
+			rational_best_approximation(num, denom, nf_max, od_max,
+						    &n1, &d1);
+		}
+
+		/* Select the best approximation of the target rate. */
+		freq = (((parent_rate / nri) * n1) / d1);
+		err = abs((int64_t)freq - num);
+		if (err < min_err) {
+			min_err = err;
+			*nr = nri;
+			*nf = n1;
+			*od = CCU_PLL_CLKOD_FACTOR * d1;
+		}
+	}
+}
+
+static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long *parent_rate)
+{
+	unsigned long nr = 1, nf = 1, od = 1;
+
+	ccu_pll_calc_factors(rate, *parent_rate, &nr, &nf, &od);
+
+	return CCU_PLL_CALC_FREQ(*parent_rate, nr, nf, od);
+}
+
+/*
+ * This method is used for PLLs, which support the on-the-fly dividers
+ * adjustment. So there is no need in gating such clocks.
+ */
+static int ccu_pll_set_rate_reset(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct ccu_pll *pll = to_ccu_pll(hw);
+	unsigned long nr, nf, od;
+	unsigned long flags;
+	u32 mask, val;
+	int ret;
+
+	ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od);
+
+	mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK |
+	       CCU_PLL_CTL_CLKOD_MASK;
+	val = CCU_SET_FLD(CCU_PLL_CTL_CLKR, 0, nr - 1) |
+	      CCU_SET_FLD(CCU_PLL_CTL_CLKF, 0, nf - 1) |
+	      CCU_SET_FLD(CCU_PLL_CTL_CLKOD, 0, od - 1);
+
+	spin_lock_irqsave(&pll->regs_lock, flags);
+	ccu_update(pll->regs + CCU_PLL_CTL, mask, val);
+	ret = ccu_pll_reset(pll, parent_rate, nr);
+	spin_unlock_irqrestore(&pll->regs_lock, flags);
+	if (ret)
+		pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
+
+	return ret;
+}
+
+/*
+ * This method is used for PLLs, which don't support the on-the-fly dividers
+ * adjustment. So the corresponding clocks are supposed to be gated first.
+ */
+static int ccu_pll_set_rate_norst(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	struct ccu_pll *pll = to_ccu_pll(hw);
+	unsigned long nr, nf, od;
+	unsigned long flags;
+	u32 mask, val;
+
+	ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od);
+
+	/*
+	 * Disable PLL if it was enabled by default or left enabled by the
+	 * system bootloader.
+	 */
+	mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK |
+	       CCU_PLL_CTL_CLKOD_MASK | CCU_PLL_CTL_EN;
+	val = CCU_SET_FLD(CCU_PLL_CTL_CLKR, 0, nr - 1) |
+	      CCU_SET_FLD(CCU_PLL_CTL_CLKF, 0, nf - 1) |
+	      CCU_SET_FLD(CCU_PLL_CTL_CLKOD, 0, od - 1);
+
+	spin_lock_irqsave(&pll->regs_lock, flags);
+	ccu_update(pll->regs + CCU_PLL_CTL, mask, val);
+	spin_unlock_irqrestore(&pll->regs_lock, flags);
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask)			\
+static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)		\
+{									\
+	struct ccu_pll *pll = priv;					\
+									\
+	*val = !!(ccu_read(pll->regs + (_reg)) & (_mask));		\
+									\
+	return 0;							\
+}									\
+static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)		\
+{									\
+	struct ccu_pll *pll = priv;					\
+	unsigned long flags;						\
+									\
+	spin_lock_irqsave(&pll->regs_lock, flags);			\
+	ccu_update(pll->regs + (_reg), (_mask), val ? (_mask) : 0);	\
+	spin_unlock_irqrestore(&pll->regs_lock, flags);			\
+									\
+	return 0;							\
+}									\
+DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,			\
+	ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
+
+#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _fld, _min, _max)		\
+static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)		\
+{									\
+	struct ccu_pll *pll = priv;					\
+	u32 data;							\
+									\
+	data = ccu_read(pll->regs + (_reg));				\
+	*val = CCU_GET_FLD(_fld, data) + 1;				\
+									\
+	return 0;							\
+}									\
+static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)		\
+{									\
+	struct ccu_pll *pll = priv;					\
+	unsigned long flags;						\
+	u32 data;							\
+									\
+	val = clamp_t(u64, val, _min, _max);				\
+	data = CCU_SET_FLD(_fld, 0, val - 1);				\
+									\
+	spin_lock_irqsave(&pll->regs_lock, flags);			\
+	ccu_update(pll->regs + (_reg), _fld ## _MASK, data);		\
+	spin_unlock_irqrestore(&pll->regs_lock, flags);			\
+									\
+	return 0;							\
+}									\
+DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,			\
+	ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
+
+CCU_PLL_DBGFS_BIT_ATTR(en, CCU_PLL_CTL, CCU_PLL_CTL_EN);
+CCU_PLL_DBGFS_BIT_ATTR(rst, CCU_PLL_CTL, CCU_PLL_CTL_RST);
+CCU_PLL_DBGFS_FLD_ATTR(nr, CCU_PLL_CTL, CCU_PLL_CTL_CLKR, 1, CCU_PLL_NR_MAX);
+CCU_PLL_DBGFS_FLD_ATTR(nf, CCU_PLL_CTL, CCU_PLL_CTL_CLKF, 1, CCU_PLL_NF_MAX);
+CCU_PLL_DBGFS_FLD_ATTR(od, CCU_PLL_CTL, CCU_PLL_CTL_CLKOD, 1, CCU_PLL_OD_MAX);
+CCU_PLL_DBGFS_BIT_ATTR(bypass, CCU_PLL_CTL, CCU_PLL_CTL_BYPASS);
+CCU_PLL_DBGFS_BIT_ATTR(lock, CCU_PLL_CTL, CCU_PLL_CTL_LOCK);
+CCU_PLL_DBGFS_FLD_ATTR(nb, CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ, 1, CCU_PLL_NB_MAX);
+
+static const struct debugfs_reg32 ccu_pll_dbgfs_regs[] = {
+	CCU_DBGFS_REG("ctl", CCU_PLL_CTL),
+	CCU_DBGFS_REG("ctl1", CCU_PLL_CTL1)
+};
+
+static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct ccu_pll *pll = to_ccu_pll(hw);
+	struct debugfs_regset32 *regset;
+
+	regset = kzalloc(sizeof(*regset), GFP_KERNEL);
+	if (!regset)
+		return;
+
+	regset->regs = ccu_pll_dbgfs_regs;
+	regset->nregs = ARRAY_SIZE(ccu_pll_dbgfs_regs);
+	regset->base = pll->regs;
+	debugfs_create_regset32("registers", 0400, dentry, regset);
+
+	debugfs_create_file_unsafe("en", 0600, dentry, pll,
+				   &ccu_pll_dbgfs_en_fops);
+	debugfs_create_file_unsafe("rst", 0200, dentry, pll,
+				   &ccu_pll_dbgfs_rst_fops);
+	debugfs_create_file_unsafe("nr", 0600, dentry, pll,
+				   &ccu_pll_dbgfs_nr_fops);
+	debugfs_create_file_unsafe("nf", 0600, dentry, pll,
+				   &ccu_pll_dbgfs_nf_fops);
+	debugfs_create_file_unsafe("od", 0600, dentry, pll,
+				   &ccu_pll_dbgfs_od_fops);
+	debugfs_create_file_unsafe("bypass", 0600, dentry, pll,
+				   &ccu_pll_dbgfs_bypass_fops);
+	debugfs_create_file_unsafe("lock", 0400, dentry, pll,
+				   &ccu_pll_dbgfs_lock_fops);
+	debugfs_create_file_unsafe("nb", 0600, dentry, pll,
+				   &ccu_pll_dbgfs_nb_fops);
+}
+
+#else /* !CONFIG_DEBUG_FS */
+
+#define ccu_pll_debug_init NULL
+
+#endif /* !CONFIG_DEBUG_FS */
+
+static const struct clk_ops ccu_pll_gate_to_set_ops = {
+	.enable = ccu_pll_enable,
+	.disable = ccu_pll_disable,
+	.is_enabled = ccu_pll_is_enabled,
+	.recalc_rate = ccu_pll_recalc_rate,
+	.round_rate = ccu_pll_round_rate,
+	.set_rate = ccu_pll_set_rate_norst,
+	.debug_init = ccu_pll_debug_init
+};
+
+static const struct clk_ops ccu_pll_straight_set_ops = {
+	.enable = ccu_pll_enable,
+	.disable = ccu_pll_disable,
+	.is_enabled = ccu_pll_is_enabled,
+	.recalc_rate = ccu_pll_recalc_rate,
+	.round_rate = ccu_pll_round_rate,
+	.set_rate = ccu_pll_set_rate_reset,
+	.debug_init = ccu_pll_debug_init
+};
+
+struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *pll_init)
+{
+	struct clk_parent_data parent_data = {0};
+	struct clk_init_data hw_init = {0};
+	struct ccu_pll *pll;
+	int ret;
+
+	if (!pll_init)
+		return ERR_PTR(-EINVAL);
+
+	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return ERR_PTR(-ENOMEM);
+
+	pll->hw.init = &hw_init;
+	pll->id = pll_init->id;
+	pll->regs = pll_init->regs;
+	spin_lock_init(&pll->regs_lock);
+
+	hw_init.name = pll_init->name;
+	hw_init.flags = CLK_IGNORE_UNUSED;
+
+	if (pll_init->flags & CCU_PLL_GATE_TO_SET) {
+		hw_init.flags |= CLK_SET_RATE_GATE;
+		hw_init.ops = &ccu_pll_gate_to_set_ops;
+	} else {
+		hw_init.ops = &ccu_pll_straight_set_ops;
+	}
+
+	if (pll_init->flags & CCU_PLL_CRITICAL)
+		hw_init.flags |= CLK_IS_CRITICAL;
+
+	if (!pll_init->parent_name) {
+		ret = -EINVAL;
+		goto err_free_pll;
+	}
+	parent_data.fw_name = pll_init->parent_name;
+	hw_init.parent_data = &parent_data;
+	hw_init.num_parents = 1;
+
+	ret = of_clk_hw_register(pll_init->np, &pll->hw);
+	if (ret)
+		goto err_free_pll;
+
+	return pll;
+
+err_free_pll:
+	kfree(pll);
+
+	return ERR_PTR(ret);
+}
+
+void ccu_pll_hw_unregister(struct ccu_pll *pll)
+{
+	if (!pll)
+		return;
+
+	clk_hw_unregister(&pll->hw);
+
+	kfree(pll);
+}
diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h
new file mode 100644
index 000000000000..6921516311fb
--- /dev/null
+++ b/drivers/clk/baikal-t1/ccu-pll.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal-T1 CCU PLL interface driver.
+ */
+#ifndef __CLK_BT1_CCU_PLL_H__
+#define __CLK_BT1_CCU_PLL_H__
+
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+#include <linux/bits.h>
+#include <linux/of.h>
+
+/*
+ * CCU PLL private flags.
+ * @CCU_PLL_GATE_TO_SET: Some PLLs output clock can't be changed on-the-fly,
+ *			 so according to documentation they need to be gated
+ *			 first.
+ * @CCU_PLL_CRITICAL: Even though there is a way to switch any PLL off, there
+ *		      might be some, which shouldn't be in any case.
+ */
+#define CCU_PLL_GATE_TO_SET		BIT(0)
+#define CCU_PLL_CRITICAL		BIT(1)
+
+/*
+ * struct ccu_pll_init_data - CCU PLL initialization data.
+ * @id: Clock private identifier.
+ * @regs: PLL registers base address.
+ * @name: Clocks name.
+ * @parent_name: Clocks parent name in a fw node.
+ * @np: Pointer to the node describing the CCU PLLs.
+ * @flags: PLL private flags.
+ */
+struct ccu_pll_init_data {
+	unsigned int id;
+	void __iomem *regs;
+	const char *name;
+	const char *parent_name;
+	struct device_node *np;
+	unsigned long flags;
+};
+
+/*
+ * struct ccu_pll - CCU PLL descriptor.
+ * @hw: clk_hw of the PLL.
+ * @id: Clock private identifier.
+ * @regs: PLL registers base address.
+ * @regs_lock: The registers exclusive access spin-lock.
+ */
+struct ccu_pll {
+	struct clk_hw hw;
+	unsigned int id;
+	void __iomem *regs;
+	spinlock_t regs_lock;
+};
+#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw)
+
+static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll)
+{
+	return pll ? &pll->hw : NULL;
+}
+
+static inline unsigned int ccu_pll_get_clk_id(struct ccu_pll *pll)
+{
+	return pll ? pll->id : -1;
+}
+
+extern struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *init);
+
+extern void ccu_pll_hw_unregister(struct ccu_pll *pll);
+
+#endif /* __CLK_BT1_CCU_PLL_H__ */
diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c
new file mode 100644
index 000000000000..990f0a4a12f9
--- /dev/null
+++ b/drivers/clk/baikal-t1/clk-ccu-pll.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ *
+ * Baikal-T1 CCU PLL clocks driver.
+ */
+
+#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/ioport.h>
+
+#include <dt-bindings/clock/bt1-ccu.h>
+
+#include "ccu-pll.h"
+
+#define CCU_CPU_PLL_BASE		0x000
+#define CCU_SATA_PLL_BASE		0x008
+#define CCU_DDR_PLL_BASE		0x010
+#define CCU_PCIE_PLL_BASE		0x018
+#define CCU_ETH_PLL_BASE		0x020
+
+#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags)	\
+	{						\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _pname,			\
+		.base = _base,				\
+		.flags = _flags				\
+	}
+
+#define CCU_PLL_NUM			ARRAY_SIZE(pll_info)
+
+struct ccu_pll_info {
+	unsigned int id;
+	const char *name;
+	const char *parent_name;
+	unsigned int base;
+	unsigned long flags;
+};
+
+static const struct ccu_pll_info pll_info[] = {
+	CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
+		     CCU_PLL_CRITICAL),
+	CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
+		     CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
+	CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
+		     CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
+	CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
+		     CCU_PLL_CRITICAL),
+	CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
+		     CCU_PLL_GATE_TO_SET)
+};
+
+struct ccu_pll_data {
+	struct device_node *np;
+	void __iomem *regs;
+	struct ccu_pll *plls[CCU_PLL_NUM];
+};
+
+static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
+					 unsigned int clk_id)
+{
+	struct ccu_pll *pll;
+	int idx;
+
+	for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
+		pll = data->plls[idx];
+		if (clk_id == ccu_pll_get_clk_id(pll))
+			return pll;
+	}
+
+	return ERR_PTR(-EINVAL);
+}
+
+static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
+{
+	struct ccu_pll_data *data;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return ERR_PTR(-ENOMEM);
+
+	data->np = np;
+
+	return data;
+}
+
+static void ccu_pll_free_data(struct ccu_pll_data *data)
+{
+	kfree(data);
+}
+
+static int ccu_pll_request_regs(struct ccu_pll_data *data)
+{
+	data->regs = of_io_request_and_map(data->np, 0,
+					   of_node_full_name(data->np));
+	if (IS_ERR(data->regs)) {
+		pr_err("Failed to request PLLs '%s' regs\n",
+			of_node_full_name(data->np));
+		return PTR_ERR(data->regs);
+	}
+
+	return 0;
+}
+
+static void ccu_pll_release_regs(struct ccu_pll_data *data)
+{
+	struct resource res;
+
+	iounmap(data->regs);
+
+	/* Try to release the resource as well. */
+	if (of_address_to_resource(data->np, 0, &res))
+		return;
+
+	(void)release_mem_region(res.start, resource_size(&res));
+}
+
+static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
+					    void *priv)
+{
+	struct ccu_pll_data *data = priv;
+	struct ccu_pll *pll;
+	unsigned int clk_id;
+
+	clk_id = clkspec->args[0];
+	pll = ccu_pll_find_desc(data, clk_id);
+	if (IS_ERR(pll)) {
+		pr_info("Invalid PLL clock ID %d specified\n", clk_id);
+		return ERR_CAST(pll);
+	}
+
+	return ccu_pll_get_clk_hw(pll);
+}
+
+static int ccu_pll_clk_register(struct ccu_pll_data *data)
+{
+	int idx, ret;
+
+	for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
+		const struct ccu_pll_info *info = &pll_info[idx];
+		struct ccu_pll_init_data init = {0};
+
+		init.id = info->id;
+		init.regs = data->regs + info->base;
+		init.parent_name = info->parent_name;
+		init.np = data->np;
+		init.flags = info->flags;
+
+		ret = of_property_read_string_index(data->np,
+			"clock-output-names", init.id, &init.name);
+		if (ret)
+			init.name = info->name;
+
+		data->plls[idx] = ccu_pll_hw_register(&init);
+		if (IS_ERR(data->plls[idx])) {
+			ret = PTR_ERR(data->plls[idx]);
+			pr_err("Couldn't register PLL hw '%s'\n",
+				init.name);
+			goto err_hw_unregister;
+		}
+	}
+
+	ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
+	if (ret) {
+		pr_err("Couldn't register PLL provider of '%s'\n",
+			of_node_full_name(data->np));
+		goto err_hw_unregister;
+	}
+
+	return 0;
+
+err_hw_unregister:
+	for (--idx; idx >= 0; --idx)
+		ccu_pll_hw_unregister(data->plls[idx]);
+
+	return ret;
+}
+
+static __init void ccu_pll_init(struct device_node *np)
+{
+	struct ccu_pll_data *data;
+	int ret;
+
+	data = ccu_pll_create_data(np);
+	if (IS_ERR(data))
+		return;
+
+	ret = ccu_pll_request_regs(data);
+	if (ret)
+		goto err_free_data;
+
+	ret = ccu_pll_clk_register(data);
+	if (ret)
+		goto err_release_regs;
+
+	pr_info("CCU CPU/SATA/DDR/PCIe/Ethernet PLLs are initialized\n");
+
+	return;
+
+err_release_regs:
+	ccu_pll_release_regs(data);
+
+err_free_data:
+	ccu_pll_free_data(data);
+}
+CLK_OF_DECLARE(ccu_pll, "be,bt1-ccu-pll", ccu_pll_init);
diff --git a/drivers/clk/baikal-t1/common.h b/drivers/clk/baikal-t1/common.h
new file mode 100644
index 000000000000..07c8d67f5275
--- /dev/null
+++ b/drivers/clk/baikal-t1/common.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal-T1 CCU common methods.
+ */
+#ifndef __CLK_BT1_COMMON_H__
+#define __CLK_BT1_COMMON_H__
+
+#include <linux/debugfs.h>
+#include <linux/io.h>
+
+#define CCU_GET_FLD(_name, _data) \
+	(((u32)(_data) & _name ## _MASK) >> _name ## _FLD)
+
+#define CCU_SET_FLD(_name, _data, _val) \
+	(((u32)(_data) & ~_name ## _MASK) | \
+	(((u32)(_val) << _name ## _FLD) & _name ## _MASK))
+
+#define CCU_DBGFS_REG(_name, _off)	\
+{					\
+	.name = _name,			\
+	.offset = _off			\
+}
+
+static inline u32 ccu_read(void __iomem *reg)
+{
+	return readl(reg);
+}
+
+static inline void ccu_write(void __iomem *reg, u32 data)
+{
+	writel(data, reg);
+}
+
+static inline void ccu_update(void __iomem *reg, u32 mask, u32 data)
+{
+	u32 old;
+
+	old = readl_relaxed(reg);
+	writel((old & ~mask) | (data & mask), reg);
+}
+
+#endif /* __CLK_BT1_COMMON_H__ */
-- 
2.25.1


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

* [PATCH 5/5] clk: Add Baikal-T1 CCU dividers driver
       [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
                   ` (3 preceding siblings ...)
  2020-03-06 13:00 ` [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver Sergey.Semin
@ 2020-03-06 13:00 ` Sergey.Semin
  2020-03-10  0:21 ` [PATCH 0/5] clk: Add Baikal-T1 SoC Clock Control Unit support Sergey Semin
  5 siblings, 0 replies; 19+ messages in thread
From: Sergey.Semin @ 2020-03-06 13:00 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-kernel, linux-clk

From: Serge Semin <Sergey.Semin@baikalelectronics.ru>

Nearly each Baikal-T1 IP-core is supposed to have a clock source
of particular frequency. But since there are greater than five
IP-blocks embedded into the SoC, the CCU PLLs can't fulfill all the
devices needs. Baikal-T1 CCU provides a set of fixed and configurable
clocks dividers in order to generate a necessary signal for each chip
sub-block.

This driver creates the of-based hardware clocks for each divider
available in Baikal-T1 CCU. The same way as for PLLs we split the
functionality up into the clocks operations (gate, ungate, set rate,
etc) and hardware clocks declaration/registration procedures. So if
CLK_BT1_CCU is defined, then the first part is available in the
kernel, while CLK_BT1_CCU_DIV config makes the actual clocks being
registered at the time of_clk_init() is called.

All CCU dividers are distributed into two CCU sub-block: AXI-bus and
system devices reference clocks. So the first sub-block is used to
supply the clocks for AXI-bus interfaces (AXI clock domains) and the
second one provides the SoC IP-cores reference clocks. Each sub-block
is represented by a dedicated dts node, so they have different
compatible strings to distinguish one from another.

For some reason CCU provides the dividers of different types. Some
dividers can be gateable some can't, some are fixed while the others
are variable, some have special divider' limitations, some's got a
non-standard register layout and so on. In order to cover all of these
cases the hardware clocks driver is designed with an info-descriptor
pattern. So there are special static descriptors declared for the
dividers of each type with additional flags describing the block
peculiarity. These descriptors are then used to create hardware clocks
with proper operations.

Some CCU dividers also provides a way to reset a domain they generate
a clock for. So the CCU AXI dividers and CCU system devices clock
drivers also perform the reset controller registration.

Finally in addition to the clocks and reset functionality CCU exposes
several unrelated blocks like irqless Designware i2c controller with
indirectly accessed registers, AXI bus errors detector, DW PCIe
controller PM/clocks/reset manager, CPU/L2 settings block. They are
considered to be a part of CCU system devices clock sub-block and
some of them available as syscons when CLK_BT1_CCU_MFD config is
enabled.

Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Cc: Paul Burton <paulburton@kernel.org>
Cc: Ralf Baechle <ralf@linux-mips.org>
---
 drivers/clk/baikal-t1/Kconfig       |  11 +
 drivers/clk/baikal-t1/Makefile      |   1 +
 drivers/clk/baikal-t1/ccu-div.c     | 531 ++++++++++++++++++++++++++++
 drivers/clk/baikal-t1/ccu-div.h     | 114 ++++++
 drivers/clk/baikal-t1/clk-ccu-div.c | 522 +++++++++++++++++++++++++++
 drivers/clk/baikal-t1/common.h      |   7 +
 6 files changed, 1186 insertions(+)
 create mode 100644 drivers/clk/baikal-t1/ccu-div.c
 create mode 100644 drivers/clk/baikal-t1/ccu-div.h
 create mode 100644 drivers/clk/baikal-t1/clk-ccu-div.c

diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig
index 0e2fc86f3ab8..e77aef17f734 100644
--- a/drivers/clk/baikal-t1/Kconfig
+++ b/drivers/clk/baikal-t1/Kconfig
@@ -32,4 +32,15 @@ config CLK_BT1_CCU_PLL
 	  over the clock dividers to be only then used as an individual
 	  reference clocks of a target device.
 
+config CLK_BT1_CCU_DIV
+	bool "Baikal-T1 CCU Dividers support"
+	select RESET_CONTROLLER
+	default y
+	help
+	  Enable this to support the CCU dividers used to distribute clocks
+	  between AXI-bus and system devices coming from CCU PLLs of Baikal-T1
+	  SoCs. CCU dividers can be either configurable or with fixed divider,
+	  either gateable or ungateable. Some of the CCU dividers can be as well
+	  used to reset the domains they're supplying clocks too.
+
 endif
diff --git a/drivers/clk/baikal-t1/Makefile b/drivers/clk/baikal-t1/Makefile
index 559f034af19e..0daf1bfcb72f 100644
--- a/drivers/clk/baikal-t1/Makefile
+++ b/drivers/clk/baikal-t1/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_CLK_BT1_CCU_PLL) += ccu-pll.o clk-ccu-pll.o
+obj-$(CONFIG_CLK_BT1_CCU_DIV) += ccu-div.o clk-ccu-div.o
diff --git a/drivers/clk/baikal-t1/ccu-div.c b/drivers/clk/baikal-t1/ccu-div.c
new file mode 100644
index 000000000000..5c49309d1fe5
--- /dev/null
+++ b/drivers/clk/baikal-t1/ccu-div.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ *
+ * Baikal-T1 CCU Divider interface driver.
+ */
+
+#define pr_fmt(fmt) "bt1-ccu-div: " fmt
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/bits.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/time64.h>
+#include <linux/debugfs.h>
+
+#include "ccu-div.h"
+#include "common.h"
+
+#define CCU_DIV_CTL			0x00
+#define CCU_DIV_CTL_EN			BIT(0)
+#define CCU_DIV_CTL_RST			BIT(1)
+#define CCU_DIV_CTL_SET_CLKDIV		BIT(2)
+#define CCU_DIV_CTL_CLKDIV_FLD		4
+#define CCU_DIV_CTL_CLKDIV_MASK(_width) \
+	GENMASK((_width) + CCU_DIV_CTL_CLKDIV_FLD - 1, CCU_DIV_CTL_CLKDIV_FLD)
+#define CCU_DIV_CTL_LOCK_SHIFTED	BIT(27)
+#define CCU_DIV_CTL_LOCK_NORMAL		BIT(31)
+
+#define CCU_DIV_RST_DELAY_US		1
+#define CCU_DIV_LOCK_DELAY_NS(_parent_rate, _div) ({		\
+	uint64_t _n = 4ULL * ((_div) ?: 1) * NSEC_PER_SEC;	\
+	do_div(_n, _parent_rate);				\
+	_n;							\
+})
+#define CCU_DIV_LOCK_CHECK_RETRIES	50
+
+#define CCU_DIV_CLKDIV_MIN		0
+#define CCU_DIV_CLKDIV_MAX(_mask) \
+	((_mask) >> CCU_DIV_CTL_CLKDIV_FLD)
+
+#define CCU_DIV_CALC_FREQ(_parent_rate, _div) \
+	((_parent_rate) / ((_div) ?: 1))
+
+static int ccu_div_var_update_clkdiv(struct ccu_div *div,
+				     unsigned long parent_rate,
+				     unsigned long divider)
+{
+	unsigned long nd;
+	u32 val, lock;
+	int count;
+
+	nd = CCU_DIV_LOCK_DELAY_NS(parent_rate, divider);
+
+	if (div->flags & CCU_DIV_LOCK_SHIFTED)
+		lock = CCU_DIV_CTL_LOCK_SHIFTED;
+	else
+		lock = CCU_DIV_CTL_LOCK_NORMAL;
+
+	ccu_update(div->reg, CCU_DIV_CTL_SET_CLKDIV, CCU_DIV_CTL_SET_CLKDIV);
+
+	count = CCU_DIV_LOCK_CHECK_RETRIES;
+	do {
+		ndelay(nd);
+		val = ccu_read(div->reg);
+	} while (!(val & lock) && --count);
+
+	return (val & lock) ? 0 : -ETIMEDOUT;
+}
+
+static int ccu_div_var_enable(struct clk_hw *hw)
+{
+	struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long flags;
+	int ret;
+	u32 val;
+
+	if (!parent_hw) {
+		pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw));
+		return -EINVAL;
+	}
+
+	val = ccu_read(div->reg);
+	if (val & CCU_DIV_CTL_EN)
+		return 0;
+
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ret = ccu_div_var_update_clkdiv(div, clk_hw_get_rate(parent_hw),
+		CCU_GET_VAR_FLD(CCU_DIV_CTL_CLKDIV, div->mask, val));
+	if (!ret)
+		ccu_write(div->reg, val | CCU_DIV_CTL_EN);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+	if (ret)
+		pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw));
+
+	return ret;
+}
+
+static int ccu_div_gate_enable(struct clk_hw *hw)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long flags;
+
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ccu_update(div->reg, CCU_DIV_CTL_EN, CCU_DIV_CTL_EN);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+
+	return 0;
+}
+
+static void ccu_div_gate_disable(struct clk_hw *hw)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long flags;
+
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ccu_update(div->reg, CCU_DIV_CTL_EN, 0);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+}
+
+static int ccu_div_gate_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+
+	return !!(ccu_read(div->reg) & CCU_DIV_CTL_EN);
+}
+
+static unsigned long ccu_div_var_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long divider;
+	u32 val;
+
+	val = ccu_read(div->reg);
+	divider = CCU_GET_VAR_FLD(CCU_DIV_CTL_CLKDIV, div->mask, val);
+
+	return CCU_DIV_CALC_FREQ(parent_rate, divider);
+}
+
+static inline unsigned long ccu_div_var_calc_divider(unsigned long rate,
+						     unsigned long parent_rate,
+						     unsigned int mask)
+{
+	unsigned long divider;
+
+	divider = parent_rate / rate;
+	return clamp_t(unsigned long, divider, CCU_DIV_CLKDIV_MIN,
+		       CCU_DIV_CLKDIV_MAX(mask));
+}
+
+static long ccu_div_var_round_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long *parent_rate)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long divider;
+
+	divider = ccu_div_var_calc_divider(rate, *parent_rate, div->mask);
+
+	return CCU_DIV_CALC_FREQ(*parent_rate, divider);
+}
+
+/*
+ * This method is used for the clock divider blocks, which support the
+ * on-the-fly rate change. So due to lacking the EN bit functionality
+ * they can't be gated before the rate adjustment.
+ */
+static int ccu_div_var_set_rate_slow(struct clk_hw *hw, unsigned long rate,
+				     unsigned long parent_rate)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long flags, divider;
+	u32 val;
+	int ret;
+
+	divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask);
+	if (divider == 1 && div->flags & CCU_DIV_SKIP_ONE) {
+		divider = 0;
+	} else if (div->flags & CCU_DIV_SKIP_ONE_TO_THREE) {
+		if (divider == 1 || divider == 2)
+			divider = 0;
+		else if (divider == 3)
+			divider = 4;
+	}
+
+	val = CCU_SET_VAR_FLD(CCU_DIV_CTL_CLKDIV, div->mask, 0, divider);
+
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ccu_update(div->reg, div->mask, val);
+	ret = ccu_div_var_update_clkdiv(div, parent_rate, divider);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+	if (ret)
+		pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw));
+
+	return ret;
+}
+
+/*
+ * This method is used for the clock divider blocks, which don't support
+ * the on-the-fly rate change.
+ */
+static int ccu_div_var_set_rate_fast(struct clk_hw *hw, unsigned long rate,
+				     unsigned long parent_rate)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	unsigned long flags, divider = 1;
+	u32 val;
+
+	divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask);
+	val = CCU_SET_VAR_FLD(CCU_DIV_CTL_CLKDIV, div->mask, 0, divider);
+
+	/*
+	 * Also disable the clock divider block if it was enabled by default
+	 * or by the bootloader.
+	 */
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ccu_update(div->reg, div->mask | CCU_DIV_CTL_EN, val);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+
+	return 0;
+}
+
+static unsigned long ccu_div_fixed_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+
+	return CCU_DIV_CALC_FREQ(parent_rate, div->divider);
+}
+
+static long ccu_div_fixed_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+
+	return CCU_DIV_CALC_FREQ(*parent_rate, div->divider);
+}
+
+static int ccu_div_fixed_set_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long parent_rate)
+{
+	return 0;
+}
+
+int ccu_div_reset_domain(struct ccu_div *div)
+{
+	unsigned long flags;
+
+	if (!div || !(div->flags & CCU_DIV_RESET_DOMAIN))
+		return -EINVAL;
+
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ccu_update(div->reg, CCU_DIV_CTL_RST, CCU_DIV_CTL_RST);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+
+	/* The next delay must be enough to cover all the resets. */
+	udelay(CCU_DIV_RST_DELAY_US);
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define CCU_DIV_DBGFS_BIT_ATTR(_name, _mask)			\
+static int ccu_div_dbgfs_##_name##_get(void *priv, u64 *val)	\
+{								\
+	struct ccu_div *div = priv;				\
+								\
+	*val = !!(ccu_read(div->reg) & _mask);			\
+								\
+	return 0;						\
+}								\
+static int ccu_div_dbgfs_##_name##_set(void *priv, u64 val)	\
+{								\
+	struct ccu_div *div = priv;				\
+	unsigned long flags;					\
+								\
+	spin_lock_irqsave(&div->reg_lock, flags);		\
+	ccu_update(div->reg, (_mask), val ? (_mask) : 0);	\
+	spin_unlock_irqrestore(&div->reg_lock, flags);		\
+								\
+	return 0;						\
+}								\
+DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_##_name##_fops,		\
+	ccu_div_dbgfs_##_name##_get, ccu_div_dbgfs_##_name##_set, "%llu\n")
+
+CCU_DIV_DBGFS_BIT_ATTR(en, CCU_DIV_CTL_EN);
+CCU_DIV_DBGFS_BIT_ATTR(rst, CCU_DIV_CTL_RST);
+CCU_DIV_DBGFS_BIT_ATTR(set_clkdiv, CCU_DIV_CTL_SET_CLKDIV);
+CCU_DIV_DBGFS_BIT_ATTR(lock_shifted, CCU_DIV_CTL_LOCK_SHIFTED);
+CCU_DIV_DBGFS_BIT_ATTR(lock_normal, CCU_DIV_CTL_LOCK_NORMAL);
+
+static int ccu_div_dbgfs_var_clkdiv_get(void *priv, u64 *val)
+{
+	struct ccu_div *div = priv;
+	u32 data;
+
+	data = ccu_read(div->reg);
+	*val = CCU_GET_VAR_FLD(CCU_DIV_CTL_CLKDIV, div->mask, data);
+
+	return 0;
+}
+static int ccu_div_dbgfs_var_clkdiv_set(void *priv, u64 val)
+{
+	struct ccu_div *div = priv;
+	unsigned long flags;
+	u32 data;
+
+	val = clamp_t(u64, val, CCU_DIV_CLKDIV_MIN,
+		      CCU_DIV_CLKDIV_MAX(div->mask));
+	data = CCU_SET_VAR_FLD(CCU_DIV_CTL_CLKDIV, div->mask, 0, val);
+
+	spin_lock_irqsave(&div->reg_lock, flags);
+	ccu_update(div->reg, div->mask, data);
+	spin_unlock_irqrestore(&div->reg_lock, flags);
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_var_clkdiv_fops,
+	ccu_div_dbgfs_var_clkdiv_get, ccu_div_dbgfs_var_clkdiv_set, "%llu\n");
+
+static int ccu_div_dbgfs_fixed_clkdiv_get(void *priv, u64 *val)
+{
+	struct ccu_div *div = priv;
+
+	*val = div->divider;
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_fixed_clkdiv_fops,
+	ccu_div_dbgfs_fixed_clkdiv_get, NULL, "%llu\n");
+
+static const struct debugfs_reg32 ccu_div_dbgfs_regs[] = {
+	CCU_DBGFS_REG("ctl", CCU_DIV_CTL)
+};
+
+static void ccu_div_var_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	struct debugfs_regset32 *regset;
+
+	regset = kzalloc(sizeof(*regset), GFP_KERNEL);
+	if (!regset)
+		return;
+
+	regset->regs = ccu_div_dbgfs_regs;
+	regset->nregs = ARRAY_SIZE(ccu_div_dbgfs_regs);
+	regset->base = div->reg;
+	debugfs_create_regset32("register", 0400, dentry, regset);
+
+	if (!(div->flags & CCU_DIV_NO_GATE)) {
+		debugfs_create_file_unsafe("en", 0600, dentry, div,
+					   &ccu_div_dbgfs_en_fops);
+	}
+
+	if (div->flags & CCU_DIV_RESET_DOMAIN) {
+		debugfs_create_file_unsafe("rst", 0200, dentry, div,
+					   &ccu_div_dbgfs_rst_fops);
+	}
+
+	debugfs_create_file_unsafe("set_clkdiv", 0200, dentry, div,
+				   &ccu_div_dbgfs_set_clkdiv_fops);
+	debugfs_create_file_unsafe("clkdiv", 0600, dentry, div,
+				   &ccu_div_dbgfs_var_clkdiv_fops);
+
+	if (div->flags & CCU_DIV_LOCK_SHIFTED) {
+		debugfs_create_file_unsafe("lock", 0400, dentry, div,
+					   &ccu_div_dbgfs_lock_shifted_fops);
+	} else {
+		debugfs_create_file_unsafe("lock", 0400, dentry, div,
+					   &ccu_div_dbgfs_lock_normal_fops);
+	}
+}
+
+static void ccu_div_gate_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+	struct debugfs_regset32 *regset;
+
+	regset = kzalloc(sizeof(*regset), GFP_KERNEL);
+	if (!regset)
+		return;
+
+	regset->regs = ccu_div_dbgfs_regs;
+	regset->nregs = ARRAY_SIZE(ccu_div_dbgfs_regs);
+	regset->base = div->reg;
+	debugfs_create_regset32("register", 0400, dentry, regset);
+
+	debugfs_create_file_unsafe("en", 0600, dentry, div,
+				   &ccu_div_dbgfs_en_fops);
+
+	debugfs_create_file_unsafe("clkdiv", 0400, dentry, div,
+				   &ccu_div_dbgfs_fixed_clkdiv_fops);
+}
+
+static void ccu_div_fixed_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct ccu_div *div = to_ccu_div(hw);
+
+	debugfs_create_file_unsafe("clkdiv", 0400, dentry, div,
+				   &ccu_div_dbgfs_fixed_clkdiv_fops);
+}
+
+#else /* !CONFIG_DEBUG_FS */
+
+#define ccu_div_var_debug_init NULL
+#define ccu_div_gate_debug_init NULL
+#define ccu_div_fixed_debug_init NULL
+
+#endif /* !CONFIG_DEBUG_FS */
+
+static const struct clk_ops ccu_div_var_gate_to_set_ops = {
+	.enable = ccu_div_var_enable,
+	.disable = ccu_div_gate_disable,
+	.is_enabled = ccu_div_gate_is_enabled,
+	.recalc_rate = ccu_div_var_recalc_rate,
+	.round_rate = ccu_div_var_round_rate,
+	.set_rate = ccu_div_var_set_rate_fast,
+	.debug_init = ccu_div_var_debug_init
+};
+
+static const struct clk_ops ccu_div_var_nogate_ops = {
+	.recalc_rate = ccu_div_var_recalc_rate,
+	.round_rate = ccu_div_var_round_rate,
+	.set_rate = ccu_div_var_set_rate_slow,
+	.debug_init = ccu_div_var_debug_init
+};
+
+static const struct clk_ops ccu_div_gate_ops = {
+	.enable = ccu_div_gate_enable,
+	.disable = ccu_div_gate_disable,
+	.is_enabled = ccu_div_gate_is_enabled,
+	.recalc_rate = ccu_div_fixed_recalc_rate,
+	.round_rate = ccu_div_fixed_round_rate,
+	.set_rate = ccu_div_fixed_set_rate,
+	.debug_init = ccu_div_gate_debug_init
+};
+
+static const struct clk_ops ccu_div_fixed_ops = {
+	.recalc_rate = ccu_div_fixed_recalc_rate,
+	.round_rate = ccu_div_fixed_round_rate,
+	.set_rate = ccu_div_fixed_set_rate,
+	.debug_init = ccu_div_fixed_debug_init
+};
+
+struct ccu_div *ccu_div_hw_register(struct ccu_div_init_data *div_init)
+{
+	struct clk_parent_data parent_data = {0};
+	struct clk_init_data hw_init = {0};
+	struct ccu_div *div;
+	int ret;
+
+	if (!div_init)
+		return ERR_PTR(-EINVAL);
+
+	div = kzalloc(sizeof(*div), GFP_KERNEL);
+	if (!div)
+		return ERR_PTR(-ENOMEM);
+
+	div->hw.init = &hw_init;
+	div->id = div_init->id;
+	div->reg = div_init->reg;
+	div->flags = div_init->flags;
+	spin_lock_init(&div->reg_lock);
+
+	hw_init.name = div_init->name;
+	hw_init.flags = CLK_IGNORE_UNUSED;
+
+	if (div_init->type == CCU_DIV_VAR) {
+		if (div_init->flags & CCU_DIV_NO_GATE) {
+			hw_init.ops = &ccu_div_var_nogate_ops;
+		} else {
+			hw_init.flags |= CLK_SET_RATE_GATE;
+			hw_init.ops = &ccu_div_var_gate_to_set_ops;
+		}
+		div->mask = CCU_DIV_CTL_CLKDIV_MASK(div_init->width);
+	} else if (div_init->type == CCU_DIV_GATE) {
+		hw_init.ops = &ccu_div_gate_ops;
+		div->divider = div_init->divider;
+	} else if (div_init->type == CCU_DIV_FIXED) {
+		hw_init.ops = &ccu_div_fixed_ops;
+		div->divider = div_init->divider;
+	} else {
+		ret = -EINVAL;
+		goto err_free_div;
+	}
+
+	if (div_init->flags & CCU_DIV_CRITICAL)
+		hw_init.flags |= CLK_IS_CRITICAL;
+
+	if (div_init->parent_name) {
+		parent_data.fw_name = div_init->parent_name;
+	} else if (div_init->parent_hw) {
+		parent_data.hw = div_init->parent_hw;
+	} else {
+		ret = -EINVAL;
+		goto err_free_div;
+	}
+	hw_init.parent_data = &parent_data;
+	hw_init.num_parents = 1;
+
+	ret = of_clk_hw_register(div_init->np, &div->hw);
+	if (ret)
+		goto err_free_div;
+
+	return div;
+
+err_free_div:
+	kfree(div);
+
+	return ERR_PTR(ret);
+}
+
+void ccu_div_hw_unregister(struct ccu_div *div)
+{
+	if (!div)
+		return;
+
+	clk_hw_unregister(&div->hw);
+
+	kfree(div);
+}
diff --git a/drivers/clk/baikal-t1/ccu-div.h b/drivers/clk/baikal-t1/ccu-div.h
new file mode 100644
index 000000000000..b166a1061078
--- /dev/null
+++ b/drivers/clk/baikal-t1/ccu-div.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal-T1 CCU Divider interface driver.
+ */
+#ifndef __CLK_BT1_CCU_DIV_H__
+#define __CLK_BT1_CCU_DIV_H__
+
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+#include <linux/bits.h>
+#include <linux/of.h>
+
+/*
+ * CCU Divider private flags.
+ * @CCU_DIV_NO_GATE: Some of CCU variable dividers can't be disabled due to
+ *		     lack of corresponding functionality.
+ * @CCU_DIV_CRITICAL: Even though there is a way to switch most of the dividers
+ *		      gate PLL off, there might be some, which shouldn't be.
+ * @CCU_DIV_SKIP_ONE: Due to some reason divider can't be set to 1.
+ *		      It can be 0 though, which is functionally the same.
+ * @CCU_DIV_SKIP_ONE_TO_THREE: For some reason divider can't be within [1,3].
+ *			       It can be either 0 or greater than 3.
+ * @CCU_DIV_LOCK_SHIFTED: Find lock-bit at non-standard position.
+ * @CCU_DIV_RESET_DOMAIN: Provide reset clock domain method.
+ */
+#define CCU_DIV_NO_GATE			BIT(0)
+#define CCU_DIV_CRITICAL		BIT(1)
+#define CCU_DIV_SKIP_ONE		BIT(2)
+#define CCU_DIV_SKIP_ONE_TO_THREE	BIT(3)
+#define CCU_DIV_LOCK_SHIFTED		BIT(4)
+#define CCU_DIV_RESET_DOMAIN		BIT(5)
+
+/*
+ * enum ccu_div_type - CCU Divider types.
+ * @CCU_DIV_VAR: Clocks gate with variable divider.
+ * @CCU_DIV_GATE: Clocks gate with fixed divider.
+ * @CCU_DIV_FIXED: Ungateable clock with fixed divider.
+ */
+enum ccu_div_type {
+	CCU_DIV_VAR,
+	CCU_DIV_GATE,
+	CCU_DIV_FIXED
+};
+
+/*
+ * struct ccu_div_init_data - CCU Divider initialization data.
+ * @id: Clocks private identifier.
+ * @reg: Divider registers base address.
+ * @name: Clocks name.
+ * @parent_name: Parent clocks name in a fw node.
+ * @parent_hw: Pointer to the parent clk_hw descriptor.
+ * @np: Pointer to the node describing the CCU Dividers.
+ * @type: CCU divider type (variable, fixed with and without gate).
+ * @width: Divider width if it's variable.
+ * @divider: Divider fixed value.
+ * @flags: CCU Divider private flags.
+ */
+struct ccu_div_init_data {
+	unsigned int id;
+	void __iomem *reg;
+	const char *name;
+	const char *parent_name;
+	const struct clk_hw *parent_hw;
+	struct device_node *np;
+	enum ccu_div_type type;
+	union {
+		unsigned int width;
+		unsigned int divider;
+	};
+	unsigned long flags;
+};
+
+/*
+ * struct ccu_div - CCU Divider descriptor.
+ * @hw: clk_hw of the divider.
+ * @id: Clock private identifier.
+ * @reg: Divider control register base address.
+ * @reg_lock: The control register access spin-lock.
+ * @mask: Divider field mask.
+ * @divider: Divider fixed value.
+ * @flags: Divider private flags.
+ */
+struct ccu_div {
+	struct clk_hw hw;
+	unsigned int id;
+	void __iomem *reg;
+	spinlock_t reg_lock;
+	union {
+		u32 mask;
+		unsigned int divider;
+	};
+	unsigned long flags;
+};
+#define to_ccu_div(_hw) container_of(_hw, struct ccu_div, hw)
+
+static inline struct clk_hw *ccu_div_get_clk_hw(struct ccu_div *div)
+{
+	return div ? &div->hw : NULL;
+}
+
+static inline unsigned int ccu_div_get_clk_id(struct ccu_div *div)
+{
+	return div ? div->id : -1;
+}
+
+extern struct ccu_div *ccu_div_hw_register(struct ccu_div_init_data *init);
+
+extern void ccu_div_hw_unregister(struct ccu_div *div);
+
+extern int ccu_div_reset_domain(struct ccu_div *div);
+
+#endif /* __CLK_BT1_CCU_DIV_H__ */
diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c
new file mode 100644
index 000000000000..71165ffb7140
--- /dev/null
+++ b/drivers/clk/baikal-t1/clk-ccu-div.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
+ *
+ * Baikal-T1 CCU Divider clocks driver.
+ */
+
+#define pr_fmt(fmt) "bt1-ccu-div: " fmt
+
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/clk-provider.h>
+#include <linux/reset-controller.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/ioport.h>
+
+#include <dt-bindings/clock/bt1-ccu.h>
+#include <dt-bindings/reset/bt1-ccu.h>
+
+#include "ccu-div.h"
+
+#define CCU_AXI_MAIN_BASE		0x000
+#define CCU_AXI_DDR_BASE		0x004
+#define CCU_AXI_SATA_BASE		0x008
+#define CCU_AXI_GMAC0_BASE		0x00C
+#define CCU_AXI_GMAC1_BASE		0x010
+#define CCU_AXI_XGMAC_BASE		0x014
+#define CCU_AXI_PCIE_M_BASE		0x018
+#define CCU_AXI_PCIE_S_BASE		0x01C
+#define CCU_AXI_USB_BASE		0x020
+#define CCU_AXI_HWA_BASE		0x024
+#define CCU_AXI_SRAM_BASE		0x028
+
+#define CCU_SYS_SATA_REF_BASE		0x000
+#define CCU_SYS_APB_BASE		0x004
+#define CCU_SYS_GMAC0_BASE		0x008
+#define CCU_SYS_GMAC1_BASE		0x00C
+#define CCU_SYS_XGMAC_BASE		0x010
+#define CCU_SYS_USB_BASE		0x014
+#define CCU_SYS_PVT_BASE		0x018
+#define CCU_SYS_HWA_BASE		0x01C
+#define CCU_SYS_UART_BASE		0x024
+#define CCU_SYS_TIMER0_BASE		0x028
+#define CCU_SYS_TIMER1_BASE		0x02C
+#define CCU_SYS_TIMER2_BASE		0x030
+#define CCU_SYS_WDT_BASE		0x0F0
+
+#define CCU_DIV_VAR_INFO(_id, _name, _pname, _base, _width, _flags)	\
+	{								\
+		.id = _id,						\
+		.name = _name,						\
+		.parent_name = _pname,					\
+		.parent_id = -1,					\
+		.base = _base,						\
+		.type = CCU_DIV_VAR,					\
+		.width = _width,					\
+		.flags = _flags						\
+	}
+
+#define CCU_DIV_GATE_INFO(_id, _name, _pname, _base, _divider)	\
+	{							\
+		.id = _id,					\
+		.name = _name,					\
+		.parent_name = _pname,				\
+		.parent_id = -1,				\
+		.base = _base,					\
+		.type = CCU_DIV_GATE,				\
+		.divider = _divider				\
+	}
+
+#define CCU_DIV_FIXED_INFO(_id, _name, _pname, _pid, _divider)	\
+	{							\
+		.id = _id,					\
+		.name = _name,					\
+		.parent_name = _pname,				\
+		.parent_id = _pid,				\
+		.type = CCU_DIV_FIXED,				\
+		.divider = _divider				\
+	}
+
+#define CCU_DIV_FIXED_EXT_INFO(_id, _name, _pname, _divider) \
+	CCU_DIV_FIXED_INFO(_id, _name, _pname, -1, _divider)
+
+#define CCU_DIV_FIXED_LOC_INFO(_id, _name, _pid, _divider) \
+	CCU_DIV_FIXED_INFO(_id, _name, NULL, _pid, _divider)
+
+#define CCU_DIV_RST_MAP(_rst_id, _clk_id)	\
+	{					\
+		.rst_id = _rst_id,		\
+		.clk_id = _clk_id		\
+	}
+
+#define CCU_AXI_DIV_NUM			ARRAY_SIZE(axi_info)
+#define CCU_AXI_RST_NUM			ARRAY_SIZE(axi_rst_map)
+
+#define CCU_SYS_DIV_NUM			ARRAY_SIZE(sys_info)
+#define CCU_SYS_RST_NUM			ARRAY_SIZE(sys_rst_map)
+
+struct ccu_div_info {
+	unsigned int id;
+	const char *name;
+	const char *parent_name;
+	unsigned int parent_id;
+	unsigned int base;
+	enum ccu_div_type type;
+	union {
+		unsigned int width;
+		unsigned int divider;
+	};
+	unsigned long flags;
+};
+
+struct ccu_div_rst_map {
+	unsigned int rst_id;
+	unsigned int clk_id;
+};
+
+struct ccu_div_data {
+	struct device_node *np;
+	void __iomem *regs;
+
+	unsigned int divs_num;
+	const struct ccu_div_info *divs_info;
+	struct ccu_div **divs;
+
+	unsigned int rst_num;
+	const struct ccu_div_rst_map *rst_map;
+	struct reset_controller_dev rcdev;
+};
+#define to_ccu_div_data(_rcdev) container_of(_rcdev, struct ccu_div_data, rcdev)
+
+static const struct ccu_div_info axi_info[] = {
+	CCU_DIV_VAR_INFO(CCU_AXI_MAIN_CLK, "axi_main_clk", "pcie_clk",
+			 CCU_AXI_MAIN_BASE, 4,
+			 CCU_DIV_CRITICAL | CCU_DIV_NO_GATE |
+			 CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_DDR_CLK, "axi_ddr_clk", "sata_clk",
+			 CCU_AXI_DDR_BASE, 4,
+			 CCU_DIV_CRITICAL | CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_SATA_CLK, "axi_sata_clk", "sata_clk",
+			 CCU_AXI_SATA_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_GMAC0_CLK, "axi_gmac0_clk", "eth_clk",
+			 CCU_AXI_GMAC0_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_GMAC1_CLK, "axi_gmac1_clk", "eth_clk",
+			 CCU_AXI_GMAC1_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_XGMAC_CLK, "axi_xgmac_clk", "eth_clk",
+			 CCU_AXI_XGMAC_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_PCIE_M_CLK, "axi_pcie_m_clk", "pcie_clk",
+			 CCU_AXI_PCIE_M_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_PCIE_S_CLK, "axi_pcie_s_clk", "pcie_clk",
+			 CCU_AXI_PCIE_S_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_USB_CLK, "axi_usb_clk", "sata_clk",
+			 CCU_AXI_USB_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_HWA_CLK, "axi_hwa_clk", "sata_clk",
+			 CCU_AXI_HWA_BASE, 4, CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_AXI_SRAM_CLK, "axi_sram_clk", "eth_clk",
+			 CCU_AXI_SRAM_BASE, 4, CCU_DIV_RESET_DOMAIN)
+};
+
+static const struct ccu_div_rst_map axi_rst_map[] = {
+	CCU_DIV_RST_MAP(CCU_AXI_MAIN_RST, CCU_AXI_MAIN_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_DDR_RST, CCU_AXI_DDR_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_SATA_RST, CCU_AXI_SATA_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_GMAC0_RST, CCU_AXI_GMAC0_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_GMAC1_RST, CCU_AXI_GMAC1_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_XGMAC_RST, CCU_AXI_XGMAC_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_PCIE_M_RST, CCU_AXI_PCIE_M_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_PCIE_S_RST, CCU_AXI_PCIE_S_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_USB_RST, CCU_AXI_USB_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_HWA_RST, CCU_AXI_HWA_CLK),
+	CCU_DIV_RST_MAP(CCU_AXI_SRAM_RST, CCU_AXI_SRAM_CLK)
+};
+
+static const struct ccu_div_info sys_info[] = {
+	CCU_DIV_VAR_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk",
+			 "sata_clk", CCU_SYS_SATA_REF_BASE, 4,
+			 CCU_DIV_SKIP_ONE | CCU_DIV_LOCK_SHIFTED |
+			 CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk",
+			 "pcie_clk", CCU_SYS_APB_BASE, 5,
+			 CCU_DIV_CRITICAL | CCU_DIV_NO_GATE |
+			 CCU_DIV_RESET_DOMAIN),
+	CCU_DIV_FIXED_LOC_INFO(CCU_SYS_GMAC0_CSR_CLK, "sys_gmac0_csr_clk",
+			       CCU_SYS_APB_CLK, 1),
+	CCU_DIV_GATE_INFO(CCU_SYS_GMAC0_TX_CLK, "sys_gmac0_tx_clk",
+			  "eth_clk", CCU_SYS_GMAC0_BASE, 5),
+	CCU_DIV_FIXED_EXT_INFO(CCU_SYS_GMAC0_PTP_CLK, "sys_gmac0_ptp_clk",
+			       "eth_clk", 10),
+	CCU_DIV_FIXED_LOC_INFO(CCU_SYS_GMAC1_CSR_CLK, "sys_gmac1_csr_clk",
+			       CCU_SYS_APB_CLK, 1),
+	CCU_DIV_GATE_INFO(CCU_SYS_GMAC1_TX_CLK, "sys_gmac1_tx_clk",
+			  "eth_clk", CCU_SYS_GMAC1_BASE, 5),
+	CCU_DIV_FIXED_EXT_INFO(CCU_SYS_GMAC1_PTP_CLK, "sys_gmac1_ptp_clk",
+			       "eth_clk", 10),
+	CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk",
+			  "eth_clk", CCU_SYS_XGMAC_BASE, 8),
+	CCU_DIV_FIXED_EXT_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk",
+			       "eth_clk", 10),
+	CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk",
+			  "eth_clk", CCU_SYS_USB_BASE, 10),
+	CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk",
+			 "ref_clk", CCU_SYS_PVT_BASE, 5, 0),
+	CCU_DIV_VAR_INFO(CCU_SYS_HWA_CLK, "sys_hwa_clk",
+			 "sata_clk", CCU_SYS_HWA_BASE, 4, 0),
+	CCU_DIV_VAR_INFO(CCU_SYS_UART_CLK, "sys_uart_clk",
+			 "eth_clk", CCU_SYS_UART_BASE, 17, 0),
+	CCU_DIV_FIXED_LOC_INFO(CCU_SYS_SPI_CLK, "sys_spi_clk",
+			   CCU_SYS_APB_CLK, 1),
+	CCU_DIV_FIXED_EXT_INFO(CCU_SYS_I2C1_CLK, "sys_i2c1_clk",
+			       "eth_clk", 10),
+	CCU_DIV_FIXED_EXT_INFO(CCU_SYS_I2C2_CLK, "sys_i2c2_clk",
+			       "eth_clk", 10),
+	CCU_DIV_FIXED_EXT_INFO(CCU_SYS_GPIO_CLK, "sys_gpio_clk",
+			       "ref_clk", 25),
+	CCU_DIV_VAR_INFO(CCU_SYS_TIMER0_CLK, "sys_timer0_clk",
+			 "ref_clk", CCU_SYS_TIMER0_BASE, 17, 0),
+	CCU_DIV_VAR_INFO(CCU_SYS_TIMER1_CLK, "sys_timer1_clk",
+			 "ref_clk", CCU_SYS_TIMER1_BASE, 17, 0),
+	CCU_DIV_VAR_INFO(CCU_SYS_TIMER2_CLK, "sys_timer2_clk",
+			 "ref_clk", CCU_SYS_TIMER2_BASE, 17, 0),
+	CCU_DIV_VAR_INFO(CCU_SYS_WDT_CLK, "sys_wdt_clk",
+			 "eth_clk", CCU_SYS_WDT_BASE, 17,
+			 CCU_DIV_SKIP_ONE_TO_THREE)
+};
+
+static const struct ccu_div_rst_map sys_rst_map[] = {
+	CCU_DIV_RST_MAP(CCU_SYS_SATA_REF_RST, CCU_SYS_SATA_REF_CLK),
+	CCU_DIV_RST_MAP(CCU_SYS_APB_RST, CCU_SYS_APB_CLK),
+};
+
+static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data,
+					 unsigned int clk_id)
+{
+	struct ccu_div *div;
+	int idx;
+
+	for (idx = 0; idx < data->divs_num; ++idx) {
+		div = data->divs[idx];
+		if (clk_id == ccu_div_get_clk_id(div))
+			return div;
+	}
+
+	return ERR_PTR(-EINVAL);
+}
+
+static int ccu_div_reset(struct reset_controller_dev *rcdev,
+			 unsigned long rst_id)
+{
+	struct ccu_div_data *data = to_ccu_div_data(rcdev);
+	const struct ccu_div_rst_map *map;
+	struct ccu_div *div;
+	int idx, ret;
+
+	for (idx = 0, map = data->rst_map; idx < data->rst_num; ++idx, ++map) {
+		if (map->rst_id == rst_id)
+			break;
+	}
+	if (idx == data->rst_num) {
+		pr_err("Invalid reset ID %lu specified\n", rst_id);
+		return -EINVAL;
+	}
+
+	div = ccu_div_find_desc(data, map->clk_id);
+	if (IS_ERR(div)) {
+		pr_err("Invalid clock ID %d in mapping\n", map->clk_id);
+		return PTR_ERR(div);
+	}
+
+	ret = ccu_div_reset_domain(div);
+	if (ret) {
+		pr_err("Reset isn't supported by divider %s\n",
+			clk_hw_get_name(ccu_div_get_clk_hw(div)));
+	}
+
+	return ret;
+}
+
+static const struct reset_control_ops ccu_div_rst_ops = {
+	.reset = ccu_div_reset,
+};
+
+static struct ccu_div_data *ccu_div_create_data(struct device_node *np,
+					unsigned int divs_num,
+					const struct ccu_div_info *divs_info,
+					unsigned int rst_num,
+					const struct ccu_div_rst_map *rst_map)
+{
+	struct ccu_div_data *data;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return ERR_PTR(-ENOMEM);
+
+	data->divs = kcalloc(divs_num, sizeof(*data->divs), GFP_KERNEL);
+	if (!data->divs) {
+		kfree(data);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	data->np = np;
+	data->divs_num = divs_num;
+	data->divs_info = divs_info;
+	data->rst_num = rst_num;
+	data->rst_map = rst_map;
+
+	return data;
+}
+
+static void ccu_div_free_data(struct ccu_div_data *data)
+{
+	kfree(data->divs);
+
+	kfree(data);
+}
+
+static int ccu_div_request_regs(struct ccu_div_data *data)
+{
+	data->regs = of_io_request_and_map(data->np, 0,
+					   of_node_full_name(data->np));
+	if (IS_ERR(data->regs)) {
+		pr_err("Failed to request dividers '%s' registers\n",
+			of_node_full_name(data->np));
+		return PTR_ERR(data->regs);
+	}
+
+	return 0;
+}
+
+static void ccu_div_release_regs(struct ccu_div_data *data)
+{
+	struct resource res;
+
+	iounmap(data->regs);
+
+	/* Try to release the resource as well. */
+	if (of_address_to_resource(data->np, 0, &res))
+		return;
+
+	(void)release_mem_region(res.start, resource_size(&res));
+}
+
+static struct clk_hw *ccu_div_of_clk_hw_get(struct of_phandle_args *clkspec,
+					    void *priv)
+{
+	struct ccu_div_data *data = priv;
+	struct ccu_div *div;
+	unsigned int clk_id;
+
+	clk_id = clkspec->args[0];
+	div = ccu_div_find_desc(data, clk_id);
+	if (IS_ERR(div)) {
+		pr_info("Invalid clock ID %d specified\n", clk_id);
+		return ERR_CAST(div);
+	}
+
+	return ccu_div_get_clk_hw(div);
+}
+
+static int ccu_div_clk_register(struct ccu_div_data *data)
+{
+	int idx, ret;
+
+	for (idx = 0; idx < data->divs_num; ++idx) {
+		const struct ccu_div_info *info = &data->divs_info[idx];
+		struct ccu_div_init_data init = {0};
+
+		init.id = info->id;
+		init.np = data->np;
+		init.type = info->type;
+		init.flags = info->flags;
+
+		if (init.type == CCU_DIV_VAR) {
+			init.reg = data->regs + info->base;
+			init.width = info->width;
+		} else if (init.type == CCU_DIV_GATE) {
+			init.reg = data->regs + info->base;
+			init.divider = info->divider;
+		} else {
+			init.divider = info->divider;
+		}
+
+		if (info->parent_name) {
+			init.parent_name = info->parent_name;
+		} else {
+			struct ccu_div *div;
+
+			div = ccu_div_find_desc(data, info->parent_id);
+			if (IS_ERR_OR_NULL(div)) {
+				ret = -EINVAL;
+				pr_err("Invalid parent ID '%u'\n",
+					info->parent_id);
+				goto err_hw_unregister;
+			}
+
+			init.parent_hw = ccu_div_get_clk_hw(div);
+		}
+
+		ret = of_property_read_string_index(data->np,
+			"clock-output-names", init.id, &init.name);
+		if (ret)
+			init.name = info->name;
+
+		data->divs[idx] = ccu_div_hw_register(&init);
+		if (IS_ERR(data->divs[idx])) {
+			ret = PTR_ERR(data->divs[idx]);
+			pr_err("Couldn't register divider '%s' hw\n",
+				init.name);
+			goto err_hw_unregister;
+		}
+	}
+
+	ret = of_clk_add_hw_provider(data->np, ccu_div_of_clk_hw_get, data);
+	if (ret) {
+		pr_err("Couldn't register dividers '%s' clock provider\n",
+			of_node_full_name(data->np));
+		goto err_hw_unregister;
+	}
+
+	return 0;
+
+err_hw_unregister:
+	for (--idx; idx >= 0; --idx)
+		ccu_div_hw_unregister(data->divs[idx]);
+
+	return ret;
+}
+
+static void ccu_div_clk_unregister(struct ccu_div_data *data)
+{
+	int idx;
+
+	of_clk_del_provider(data->np);
+
+	for (idx = 0; idx < data->divs_num; ++idx)
+		ccu_div_hw_unregister(data->divs[idx]);
+}
+
+static int ccu_div_rst_register(struct ccu_div_data *data)
+{
+	int ret;
+
+	data->rcdev.ops = &ccu_div_rst_ops;
+	data->rcdev.of_node = data->np;
+	data->rcdev.nr_resets = data->rst_num;
+
+	ret = reset_controller_register(&data->rcdev);
+	if (ret) {
+		pr_err("Couldn't register divider '%s' reset controller\n",
+			of_node_full_name(data->np));
+	}
+
+	return ret;
+}
+
+static int ccu_div_init(struct device_node *np,
+			unsigned int divs_num,
+			const struct ccu_div_info *divs_info,
+			unsigned int rst_num,
+			const struct ccu_div_rst_map *rst_map)
+{
+	struct ccu_div_data *data;
+	int ret;
+
+	data = ccu_div_create_data(np, divs_num, divs_info, rst_num, rst_map);
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	ret = ccu_div_request_regs(data);
+	if (ret)
+		goto err_free_data;
+
+	ret = ccu_div_clk_register(data);
+	if (ret)
+		goto err_release_regs;
+
+	ret = ccu_div_rst_register(data);
+	if (ret)
+		goto err_clk_unregister;
+
+	return 0;
+
+err_clk_unregister:
+	ccu_div_clk_unregister(data);
+
+err_release_regs:
+	ccu_div_release_regs(data);
+
+err_free_data:
+	ccu_div_free_data(data);
+
+	return ret;
+}
+
+
+static __init void ccu_axi_init(struct device_node *np)
+{
+	int ret;
+
+	ret = ccu_div_init(np, CCU_AXI_DIV_NUM, axi_info,
+			   CCU_AXI_RST_NUM, axi_rst_map);
+	if (!ret)
+		pr_info("CCU AXI-bus clocks/resets are initialized\n");
+}
+CLK_OF_DECLARE(ccu_axi, "be,bt1-ccu-axi", ccu_axi_init);
+
+static __init void ccu_sys_init(struct device_node *np)
+{
+	int ret;
+
+	ret = ccu_div_init(np, CCU_SYS_DIV_NUM, sys_info,
+			   CCU_SYS_RST_NUM, sys_rst_map);
+	if (!ret)
+		pr_info("CCU system devices clocks/resets are initialized\n");
+}
+CLK_OF_DECLARE_DRIVER(ccu_sys, "be,bt1-ccu-sys", ccu_sys_init);
diff --git a/drivers/clk/baikal-t1/common.h b/drivers/clk/baikal-t1/common.h
index 07c8d67f5275..34a3015c4633 100644
--- a/drivers/clk/baikal-t1/common.h
+++ b/drivers/clk/baikal-t1/common.h
@@ -13,10 +13,17 @@
 #define CCU_GET_FLD(_name, _data) \
 	(((u32)(_data) & _name ## _MASK) >> _name ## _FLD)
 
+#define CCU_GET_VAR_FLD(_name, _mask, _data) \
+	(((u32)(_data) & (u32)(_mask)) >> _name ## _FLD)
+
 #define CCU_SET_FLD(_name, _data, _val) \
 	(((u32)(_data) & ~_name ## _MASK) | \
 	(((u32)(_val) << _name ## _FLD) & _name ## _MASK))
 
+#define CCU_SET_VAR_FLD(_name, _mask, _data, _val) \
+	(((u32)(_data) & ~(u32)(_mask)) | \
+	(((u32)(_val) << _name ## _FLD) & (u32)(_mask)))
+
 #define CCU_DBGFS_REG(_name, _off)	\
 {					\
 	.name = _name,			\
-- 
2.25.1



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

* Re: [PATCH 0/5] clk: Add Baikal-T1 SoC Clock Control Unit support
       [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
                   ` (4 preceding siblings ...)
  2020-03-06 13:00 ` [PATCH 5/5] clk: Add Baikal-T1 CCU dividers driver Sergey.Semin
@ 2020-03-10  0:21 ` Sergey Semin
  2020-03-10  2:03   ` Stephen Boyd
  5 siblings, 1 reply; 19+ messages in thread
From: Sergey Semin @ 2020-03-10  0:21 UTC (permalink / raw)
  To: Alexey Malahov, Maxim Kaurkin, Pavel Parkhomenko, Ramil Zaripov,
	Ekaterina Skachko, Vadim Vlasov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, Michael Turquette, Stephen Boyd,
	Rob Herring, Mark Rutland, linux-clk, devicetree, linux-kernel

On Fri, Mar 06, 2020 at 04:00:43PM +0300, Sergey.Semin@baikalelectronics.ru wrote:
> From: Serge Semin <fancer.lancer@gmail.com>
> 
> Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> subsystems clocking and resetting. The CCU is connected with an external
> fixed rate oscillator, which signal is transformed into clocks of various
> frequencies and then propagated to either individual IP-blocks or to groups
> of blocks (clock domains). The transformation is done by means of PLLs and
> gateable/non-gateable, fixed/variable dividers embedded into the CCU. There
> are five PLLs to create a clock for the MIPS P5600 cores, the embedded DDR
> controller, SATA, Ethernet and PCIe domains. The last three PLLs CLKOUT are
> then passed over CCU dividers to create signals required for the target clock
> domains: individual AXI and APB bus clocks, SoC devices reference clocks.
> The CCU divider registers may also provide a way to reset the target devices
> state.
> 
> So this patchset introduces the Baikal-T1 clock and reset drivers of CCU
> PLLs, AXI-bus clock dividers and system devices clock dividers.
> 
> This patchset is rebased and tested on the mainline Linux kernel 5.6-rc4:
> commit 98d54f81e36b ("Linux 5.6-rc4").
> 
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Maxim Kaurkin <Maxim.Kaurkin@baikalelectronics.ru>
> Cc: Pavel Parkhomenko <Pavel.Parkhomenko@baikalelectronics.ru>
> Cc: Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru>
> Cc: Ekaterina Skachko <Ekaterina.Skachko@baikalelectronics.ru>
> Cc: Vadim Vlasov <V.Vlasov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Paul Burton <paulburton@kernel.org>
> Cc: Ralf Baechle <ralf@linux-mips.org>
> Cc: Michael Turquette <mturquette@baylibre.com>
> Cc: Stephen Boyd <sboyd@kernel.org>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: linux-clk@vger.kernel.org
> Cc: devicetree@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> 
> Serge Semin (5):
>   dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings
>   dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings
>   dt-bindings: clk: Add Baikal-T1 System Devices CCU bindings
>   clk: Add Baikal-T1 CCU PLLs driver
>   clk: Add Baikal-T1 CCU dividers driver
> 
>  .../bindings/clock/be,bt1-ccu-axi.yaml        | 151 +++++
>  .../bindings/clock/be,bt1-ccu-pll.yaml        | 139 +++++
>  .../bindings/clock/be,bt1-ccu-sys.yaml        | 169 ++++++
>  drivers/clk/Kconfig                           |   1 +
>  drivers/clk/Makefile                          |   1 +
>  drivers/clk/baikal-t1/Kconfig                 |  46 ++
>  drivers/clk/baikal-t1/Makefile                |   3 +
>  drivers/clk/baikal-t1/ccu-div.c               | 531 ++++++++++++++++++
>  drivers/clk/baikal-t1/ccu-div.h               | 114 ++++
>  drivers/clk/baikal-t1/ccu-pll.c               | 474 ++++++++++++++++
>  drivers/clk/baikal-t1/ccu-pll.h               |  73 +++
>  drivers/clk/baikal-t1/clk-ccu-div.c           | 522 +++++++++++++++++
>  drivers/clk/baikal-t1/clk-ccu-pll.c           | 217 +++++++
>  drivers/clk/baikal-t1/common.h                |  51 ++
>  include/dt-bindings/clock/bt1-ccu.h           |  54 ++
>  include/dt-bindings/reset/bt1-ccu.h           |  27 +
>  16 files changed, 2573 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
>  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
>  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
>  create mode 100644 drivers/clk/baikal-t1/Kconfig
>  create mode 100644 drivers/clk/baikal-t1/Makefile
>  create mode 100644 drivers/clk/baikal-t1/ccu-div.c
>  create mode 100644 drivers/clk/baikal-t1/ccu-div.h
>  create mode 100644 drivers/clk/baikal-t1/ccu-pll.c
>  create mode 100644 drivers/clk/baikal-t1/ccu-pll.h
>  create mode 100644 drivers/clk/baikal-t1/clk-ccu-div.c
>  create mode 100644 drivers/clk/baikal-t1/clk-ccu-pll.c
>  create mode 100644 drivers/clk/baikal-t1/common.h
>  create mode 100644 include/dt-bindings/clock/bt1-ccu.h
>  create mode 100644 include/dt-bindings/reset/bt1-ccu.h
> 
> -- 
> 2.25.1
> 

Folks,

It appears our corporate email server changes the Message-Id field of 
messages passing through it. Due to that the emails threading gets to be
broken. I'll resubmit the properly structured patchset as soon as our system
administrator fixes the problem. Sorry for the inconvenience caused by it.

Regards,
-Sergey

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

* Re: [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings
  2020-03-06 13:00 ` [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings Sergey.Semin
@ 2020-03-10  2:02   ` Stephen Boyd
       [not found]   ` <20200310021052.2E40F80307C5@mail.baikalelectronics.ru>
  1 sibling, 0 replies; 19+ messages in thread
From: Stephen Boyd @ 2020-03-10  2:02 UTC (permalink / raw)
  To: Mark Rutland, Michael Turquette, Rob Herring, Sergey.Semin
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-clk, devicetree, linux-kernel

Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:44)
> From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> 
> Baikal-T1 Clocks Control Unit is responsible for transformation of a
> signal coming from an external oscillator into clocks of various
> frequencies to propagate them then to the corresponding clocks
> consumers (either individual IP-blocks or clock domains). In order
> to create a set of high-frequency clocks the external signal is
> firstly handled by the embedded into CCU PLLs. So the corresponding
> dts-node is just a normal clock-provider node with standard set of
> properties.
> 
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>

SoB chain is backwards. Is Alexey the author? Or Co-developed-by?

> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Paul Burton <paulburton@kernel.org>
> Cc: Ralf Baechle <ralf@linux-mips.org>
> ---
>  .../bindings/clock/be,bt1-ccu-pll.yaml        | 139 ++++++++++++++++++
>  include/dt-bindings/clock/bt1-ccu.h           |  17 +++
>  2 files changed, 156 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
>  create mode 100644 include/dt-bindings/clock/bt1-ccu.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> new file mode 100644
> index 000000000000..f2e397cc147b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> @@ -0,0 +1,139 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
> +#
> +# Baikal-T1 Clocks Control Unit PLL Device Tree Bindings.
> +#

I don't think we need any of these comments besides the license
identifier line. Can you dual license this?

> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/be,bt1-ccu-pll.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Baikal-T1 Clock Control Unit PLLs
> +
> +maintainers:
> +  - Serge Semin <fancer.lancer@gmail.com>
> +
> +description: |
> +  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> +  subsystems clocking and resetting. The CCU is connected with an external
> +  fixed rate oscillator, which signal is transformed into clocks of various
> +  frequencies and then propagated to either individual IP-blocks or to groups
> +  of blocks (clock domains). The transformation is done by means of PLLs and
> +  gateable/non-gateable dividers embedded into the CCU. It's logically divided
> +  into the next components:
> +  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
> +     in general can provide any frequency supported by the CCU PLLs).
> +  2) PLLs clocks generators (PLLs) - described in this bindings file.
> +  3) AXI-bus clock dividers (AXI).
> +  4) System devices reference clock dividers (SYS).
> +  which are connected with each other as shown on the next figure:

Please add a newline here

> +          +---------------+
> +          | Baikal-T1 CCU |
> +          |   +----+------|- MIPS P5600 cores
> +          | +-|PLLs|------|- DDR controller
> +          | | +----+      |
> +  +----+  | |  |  |       |
> +  |XTAL|--|-+  |  | +---+-|
> +  +----+  | |  |  +-|AXI|-|- AXI-bus
> +          | |  |    +---+-|
> +          | |  |          |
> +          | |  +----+---+-|- APB-bus
> +          | +-------|SYS|-|- Low-speed Devices
> +          |         +---+-|- High-speed Devices
> +          +---------------+

And here.

> +  Each CCU sub-block is represented as a separate dts-node and has an
> +  individual driver to be bound with.
> +
> +  In order to create signals of wide range frequencies the external oscillator
> +  output is primarily connected to a set of CCU PLLs. There are five PLLs
> +  to create a clock for the MIPS P5600 cores, the embedded DDR controller,
> +  SATA, Ethernet and PCIe domains. The last three domains though named by the
> +  biggest system interfaces in fact include nearly all of the rest SoC
> +  peripherals. Each of the PLLs is based on True Circuits TSMC CLN28HPM core
> +  with an interface wrapper (so called safe PLL' clocks switcher) to simplify
> +  the PLL configuration procedure. The PLLs work as depicted on the next
> +  diagram:

Same, space out the diagrams.

> +      +--------------------------+
> +      |                          |
> +      +-->+---+    +---+   +---+ |  +---+   0|\
> +  CLKF--->|/NF|--->|PFD|...|VCO|-+->|/OD|--->| |
> +          +---+ +->+---+   +---+ /->+---+    | |--->CLKOUT
> +  CLKOD---------C----------------+          1| |
> +       +--------C--------------------------->|/
> +       |        |                             ^
> +  Rclk-+->+---+ |                             |
> +  CLKR--->|/NR|-+                             |
> +          +---+                               |
> +  BYPASS--------------------------------------+
> +  BWADJ--->
> +  where Rclk is the reference clock coming  from XTAL, NR - reference clock
> +  divider, NF - PLL clock multiplier, OD - VCO output clock divider, CLKOUT -
> +  output clock, BWADJ is the PLL bandwidth adjustment parameter. At this moment
> +  the binding supports the PLL dividers configuration in accordance with a
> +  requested rate, while bypassing and bandwidth adjustment settings can be
> +  added in future if it gets to be necessary.
> +
> +  The PLLs CLKOUT is then either directly connected with the corresponding
> +  clocks consumer (like P5600 cores or DDR controller) or passed over a CCU
> +  divider to create a signal required for the clock domain.
> +
> +  The CCU PLL dts-node uses the common clock bindings [1] with no custom
> +  parameters. The list of exported clocks can be found in
> +  'dt-bindings/clock/bt1-ccu.h'.
> +
> +  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt

Don't think we need to mention this binding anymore. But it's good that
we know what exported clock ids are.

> +
> +allOf:
> +  - $ref: /schemas/clock/clock.yaml#
> +
> +properties:
> +  compatible:
> +    const: be,bt1-ccu-pll
> +
> +  reg:
> +    description: CCU PLLs sub-block base address.
> +    maxItems: 1
> +
> +  "#clock-cells":
> +    description: |
> +      Clocks are referenced by the node phandle and an unique identifier
> +      from 'dt-bindings/clock/bt1-ccu.h'.

Don't think we need this description.

> +    const: 1
> +
> +  clocks:
> +    description: Phandle of CCU External reference clock.
> +    maxItems: 1
> +
> +  clock-names:
> +    const: ref_clk

Can we drop _clk? It's redundant.

> +
> +  clock-output-names: true
> +
> +  assigned-clocks: true
> +
> +  assigned-clock-rates: true
> +
> +additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - "#clock-cells"
> +  - clocks
> +  - clock-names
> +
> +examples:
> +  - |
> +    ccu_pll: ccu_pll@1F04D000 {

Drop the phandle unless it's actually used.

> +      compatible = "be,bt1-ccu-pll";
> +      reg = <0x1F04D000 0x028>;

Lowercase hex please. That size is oddly small.

> +      #clock-cells = <1>;
> +
> +      clocks = <&osc25>;
> +      clock-names = "ref_clk";
> +
> +      clock-output-names = "cpu_pll", "sata_pll", "ddr_pll",
> +                           "pcie_pll", "eth_pll";
> +    };
> +...
> diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
> new file mode 100644
> index 000000000000..86e63162ade0
> --- /dev/null
> +++ b/include/dt-bindings/clock/bt1-ccu.h
> @@ -0,0 +1,17 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> + *
> + * Baikal-T1 CCU clock indeces.
> + */
> +#ifndef __DT_BINDINGS_CLOCK_BT1_CCU_H
> +#define __DT_BINDINGS_CLOCK_BT1_CCU_H
> +
> +/* Baikal-T1 CCU PLL indeces. */

Please drop this comment. It's not useful.

> +#define CCU_CPU_PLL                    0
> +#define CCU_SATA_PLL                   1
> +#define CCU_DDR_PLL                    2
> +#define CCU_PCIE_PLL                   3
> +#define CCU_ETH_PLL                    4
> +
> +#endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
> -- 
> 2.25.1
>

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

* Re: [PATCH 0/5] clk: Add Baikal-T1 SoC Clock Control Unit support
  2020-03-10  0:21 ` [PATCH 0/5] clk: Add Baikal-T1 SoC Clock Control Unit support Sergey Semin
@ 2020-03-10  2:03   ` Stephen Boyd
  0 siblings, 0 replies; 19+ messages in thread
From: Stephen Boyd @ 2020-03-10  2:03 UTC (permalink / raw)
  To: Alexey Malahov, Ekaterina Skachko, Mark Rutland, Maxim Kaurkin,
	Michael Turquette, Paul Burton, Pavel Parkhomenko, Ralf Baechle,
	Ramil Zaripov, Rob Herring, Sergey Semin, Thomas Bogendoerfer,
	Vadim Vlasov, devicetree, linux-clk, linux-kernel

Quoting Sergey Semin (2020-03-09 17:21:26)
> 
> It appears our corporate email server changes the Message-Id field of 
> messages passing through it. Due to that the emails threading gets to be
> broken. I'll resubmit the properly structured patchset as soon as our system
> administrator fixes the problem. Sorry for the inconvenience caused by it.
> 

Please trim replies. I had to scroll and that made my life super hard! :P 

Anyway, I see a thread so maybe my MUA figured it out. I can wait for it
to be sorted on the corporate end though.

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

* Re: [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices CCU bindings
  2020-03-06 13:00 ` [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices " Sergey.Semin
@ 2020-03-10  2:19   ` Stephen Boyd
       [not found]   ` <20200310021915.8A0E7803087C@mail.baikalelectronics.ru>
  1 sibling, 0 replies; 19+ messages in thread
From: Stephen Boyd @ 2020-03-10  2:19 UTC (permalink / raw)
  To: Mark Rutland, Michael Turquette, Philipp Zabel, Rob Herring,
	Sergey.Semin
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-clk, devicetree, linux-kernel

Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:46)
> diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
> new file mode 100644
> index 000000000000..aea09fbafc89
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
> @@ -0,0 +1,169 @@
[..]
> +  assigned-clock-rates: true
> +
> +additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - "#clock-cells"
> +  - clocks
> +  - clock-names
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/bt1-ccu.h>
> +
> +    ccu_sys: ccu_sys@1F04D060 {

Node name should be clock-controller@1f04d060.

Also, binding looks wrong because that address isn't aligned. Most
likely it's one hardware block that has many different functionalities
so splitting it up into different regions isn't doing anything besides
logically splitting up the register space for software benefits.

> +      compatible = "be,bt1-ccu-sys";
> +      reg = <0x1F04D060 0x0A0>,
> +            <0x1F04D150 0x004>;
> +      #clock-cells = <1>;
> +      #reset-cells = <1>;
> +
> +      clocks = <&osc25>,
> +               <&ccu_pll CCU_SATA_PLL>,
> +               <&ccu_pll CCU_PCIE_PLL>,
> +               <&ccu_pll CCU_ETH_PLL>;
> +      clock-names = "ref_clk", "sata_clk", "pcie_clk",
> +                    "eth_clk";
> +
> +      clock-output-names = "sys_sata_ref_clk", "sys_apb_clk",
> +                           "sys_gmac0_csr_clk", "sys_gmac0_tx_clk",
> +                           "sys_gmac0_ptp_clk", "sys_gmac1_csr_clk",
> +                           "sys_gmac1_tx_clk", "sys_gmac1_ptp_clk",
> +                           "sys_xgmac_ref_clk", "sys_xgmac_ptp_clk",
> +                           "sys_usb_clk", "sys_pvt_clk",
> +                           "sys_hwa_clk", "sys_uart_clk",
> +                           "sys_spi_clk", "sys_i2c1_clk",
> +                           "sys_i2c2_clk", "sys_gpio_clk",
> +                           "sys_timer0_clk", "sys_timer1_clk",
> +                           "sys_timer2_clk", "sys_wdt_clk";
> +      };
> +...
> diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h
> index 4de5b6bcd433..0bd8fd0edb41 100644
> --- a/include/dt-bindings/reset/bt1-ccu.h
> +++ b/include/dt-bindings/reset/bt1-ccu.h
> @@ -20,4 +20,8 @@
>  #define CCU_AXI_HWA_RST                        9
>  #define CCU_AXI_SRAM_RST               10
>  
> +/* Baikal-T1 System Devices CCU Reset indeces. */

indeces is not a word.

> +#define CCU_SYS_SATA_REF_RST           0
> +#define CCU_SYS_APB_RST                        1
> +

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

* Re: [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver
  2020-03-06 13:00 ` [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver Sergey.Semin
@ 2020-03-10 15:30   ` Stephen Boyd
  2020-04-07 12:08     ` Sergey Semin
  0 siblings, 1 reply; 19+ messages in thread
From: Stephen Boyd @ 2020-03-10 15:30 UTC (permalink / raw)
  To: Michael Turquette, Sergey.Semin
  Cc: Serge Semin, Serge Semin, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-kernel, linux-clk

Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:47)
> From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> 
> Baikal-T1 is supposed to be supplied with a high-frequency external
> oscillator. But in order to create signals suitable for each IP-block
> embedded into the SoC the oscillator output is primarily connected to
> a set of CCU PLLs. There are five of them to create clocks for the MIPS
> P5600 cores, the embedded DDR controller, SATA, Ethernet and PCIe
> domains. The last three domains though named by the biggest system
> interfaces in fact include nearly all of the rest SoC peripherals.
> Each of the PLLs is based on True Circuits TSMC CLN28HPM IP-core with
> an interface wrapper (so called safe PLL' clocks switcher) to simplify
> the PLL configuration procedure.
> 
> This driver creates the of-based hardware clocks to use them then in
> the corresponding subsystems. In order to simplify the driver code we
> split the functionality up into the PLLs clocks operations and hardware
> clocks declaration/registration procedures. So if CLK_BT1_CCU is
> defined, then the first part is available in the kernel, while
> CLK_BT1_CCU_PLL config makes the actual clocks being registered at the
> time of_clk_init() is called.
> 
> Even though the PLLs are based on the same IP-core, they actually may
> have some differences. In particular, some CCU PLLs supports the output
> clock change without gating them (like CPU or PCIe PLLs), while the
> others don't, some CCU PLLs are critical and aren't supposed to be
> gated. In order to cover all of these cases the hardware clocks driver
> is designed with a info-descriptor pattern. So there are special static
> descriptors declared for each PLL, which is then used to create a
> hardware clock with proper operations. Additionally debugfs-files are
> provided for each PLL' field to make sure the implemented
> rate-PLLs-dividers calculation algorithm is correct.
> 
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Paul Burton <paulburton@kernel.org>
> Cc: Ralf Baechle <ralf@linux-mips.org>

> diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig
> new file mode 100644
> index 000000000000..0e2fc86f3ab8
> --- /dev/null
> +++ b/drivers/clk/baikal-t1/Kconfig
> @@ -0,0 +1,35 @@
> +# SPDX-License-Identifier: GPL-2.0
> +config CLK_BAIKAL_T1
> +       bool "Baikal-T1 Clocks Control Unit interface"
> +       depends on (MIPS_BAIKAL_T1 || COMPILE_TEST) && OF

Does it actually depend on OF for any compilation?

> +       default y

Please no default y. Maybe default MIPS_BAIKAL_T1?

> +       help
> +         Clocks Control Unit is the core of Baikal-T1 SoC responsible for the
> +         chip subsystems clocking and resetting. It consists of multiple
> +         global clock domains, which can be reset by means of the CCU control
> +         registers. These domains and devices placed in them are fed with
> +         clocks generated by a hierarchy of PLLs, configurable and fixed
> +         dividers. In addition CCU exposes several unrelated functional blocks
> +         like irqless Designware i2c controller with indirectly accessed
> +         registers, AXI bus errors detector, DW PCIe controller PM/clocks/reset
> +         manager, etc.
> +
> +         This driver provides a set of functions to create the kernel clock
> +         devices of Baikal-T1 PLLs and dividers, and to manipulate the reset
> +         signals of the SoC.
> +
> +if CLK_BAIKAL_T1
> +
> +config CLK_BT1_CCU_PLL
> +       bool "Baikal-T1 CCU PLLs support"
> +       default y

default MIPS_BAIKAL_T1?

> +       help
> +         Enable this to support the PLLs embedded into the Baikal-T1 SoCs.
> +         These are five PLLs placed at the root of the clocks hierarchy,
> +         right after the external reference osciallator (normally of 25MHz).
> +         They are used to generate a high frequency signals, which are

s/generate a high/generate high/

> +         either directly wired to the consumers (like CPUs, DDR) or passed
> +         over the clock dividers to be only then used as an individual
> +         reference clocks of a target device.

s/clocks/clock/

> +
> +endif
> diff --git a/drivers/clk/baikal-t1/ccu-pll.c b/drivers/clk/baikal-t1/ccu-pll.c
> new file mode 100644
> index 000000000000..f2087a80b64d
> --- /dev/null
> +++ b/drivers/clk/baikal-t1/ccu-pll.c
> @@ -0,0 +1,474 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> + *
> + * Authors:
> + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> + *
> + * Baikal-T1 CCU PLL interface driver.
> + */
> +
> +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/printk.h>
> +#include <linux/limits.h>
> +#include <linux/bits.h>
> +#include <linux/slab.h>
> +#include <linux/clk-provider.h>
> +#include <linux/of.h>
> +#include <linux/spinlock.h>
> +#include <linux/delay.h>
> +#include <linux/time64.h>
> +#include <linux/rational.h>
> +#include <linux/debugfs.h>
> +
> +#include "ccu-pll.h"
> +#include "common.h"
> +
> +#define CCU_PLL_CTL                    0x00
> +#define CCU_PLL_CTL_EN                 BIT(0)
> +#define CCU_PLL_CTL_RST                        BIT(1)
> +#define CCU_PLL_CTL_CLKR_FLD           2
> +#define CCU_PLL_CTL_CLKR_MASK          GENMASK(7, CCU_PLL_CTL_CLKR_FLD)
> +#define CCU_PLL_CTL_CLKF_FLD           8
> +#define CCU_PLL_CTL_CLKF_MASK          GENMASK(20, CCU_PLL_CTL_CLKF_FLD)
> +#define CCU_PLL_CTL_CLKOD_FLD          21
> +#define CCU_PLL_CTL_CLKOD_MASK         GENMASK(24, CCU_PLL_CTL_CLKOD_FLD)
> +#define CCU_PLL_CTL_BYPASS             BIT(30)
> +#define CCU_PLL_CTL_LOCK               BIT(31)
> +#define CCU_PLL_CTL1                   0x04
> +#define CCU_PLL_CTL1_BWADJ_FLD         3
> +#define CCU_PLL_CTL1_BWADJ_MASK                GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD)
> +
> +#define CCU_PLL_RST_DELAY_US           5
> +#define CCU_PLL_LOCK_DELAY_US(_ref_rate, _nr) ({       \
> +       uint64_t _n = 500ULL * (_nr) * USEC_PER_SEC;    \
> +       do_div(_n, _ref_rate);                          \
> +       _n;                                             \
> +})

Can this be a static inline function? We get to learn what the types are
then.

> +#define CCU_PLL_LOCK_CHECK_RETRIES     50
> +
> +#define CCU_PLL_NR_MAX \
> +       ((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1)
> +#define CCU_PLL_NF_MAX \
> +       ((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1)
> +#define CCU_PLL_OD_MAX \
> +       ((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1)
> +#define CCU_PLL_NB_MAX \
> +       ((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1)
> +#define CCU_PLL_FDIV_MIN               427000UL
> +#define CCU_PLL_FDIV_MAX               3500000000UL
> +#define CCU_PLL_FOUT_MIN               200000000UL
> +#define CCU_PLL_FOUT_MAX               2500000000UL
> +#define CCU_PLL_FVCO_MIN               700000000UL
> +#define CCU_PLL_FVCO_MAX               3500000000UL
> +#define CCU_PLL_CLKOD_FACTOR           2
> +
> +#define CCU_PLL_CALC_FREQ(_ref_rate, _nr, _nf, _od) \
> +       ((_ref_rate) / (_nr) * (_nf) / (_od))

Static inline function please. Does this need to be 64-bit math?

> +
> +static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk,
> +                        unsigned long nr)
> +{
> +       unsigned long ud;
> +       int count;
> +       u32 val;
> +
> +       ud = CCU_PLL_LOCK_DELAY_US(ref_clk, nr);
> +
> +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_RST, CCU_PLL_CTL_RST);
> +
> +       count = CCU_PLL_LOCK_CHECK_RETRIES;
> +       do {
> +               udelay(ud);
> +               val = ccu_read(pll->regs + CCU_PLL_CTL);
> +       } while (!(val & CCU_PLL_CTL_LOCK) && --count);
> +
> +       return (val & CCU_PLL_CTL_LOCK) ? 0 : -ETIMEDOUT;

Looks like readl_poll_timeout().

> +}
> +
> +static int ccu_pll_enable(struct clk_hw *hw)
> +{
> +       struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +       unsigned long flags;
> +       int ret;
> +       u32 val;
> +
> +       if (!parent_hw) {
> +               pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw));
> +               return -EINVAL;
> +       }
> +
> +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> +       if (val & CCU_PLL_CTL_EN)
> +               return 0;
> +
> +       spin_lock_irqsave(&pll->regs_lock, flags);
> +       ccu_write(pll->regs + CCU_PLL_CTL, val | CCU_PLL_CTL_EN);
> +       ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw),
> +                           CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1);
> +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> +       if (ret)
> +               pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
> +
> +       return ret;
> +}
> +
> +static void ccu_pll_disable(struct clk_hw *hw)
> +{
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&pll->regs_lock, flags);
> +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_EN, 0);
> +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> +}
> +
> +static int ccu_pll_is_enabled(struct clk_hw *hw)
> +{
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +
> +       return !!(ccu_read(pll->regs + CCU_PLL_CTL) & CCU_PLL_CTL_EN);
> +}
> +
> +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
> +                                        unsigned long parent_rate)
> +{
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +       unsigned long nr, nf, od;
> +       u32 val;
> +
> +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> +       nr = CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1;

We have FIELD_GET macros for this. Please use them instead of creating
your own.

> +       nf = CCU_GET_FLD(CCU_PLL_CTL_CLKF, val) + 1;
> +       od = CCU_GET_FLD(CCU_PLL_CTL_CLKOD, val) + 1;
> +
> +       return CCU_PLL_CALC_FREQ(parent_rate, nr, nf, od);
> +}
> +
> +static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate,
> +                                unsigned long *nr, unsigned long *nf,
> +                                unsigned long *od)
> +{
> +       unsigned long err, freq, min_err = ULONG_MAX;
> +       unsigned long num, denom, n1, d1, nri;
> +       unsigned long nr_max, nf_max, od_max;
> +
> +       /*
> +        * Make sure PLL is working with valid input signal (Fdiv). If
> +        * you want to speed the function up just reduce CCU_PLL_NR_MAX.
> +        * This will cause a worse approximation though.
> +        */
> +       nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1;
> +       nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX);
> +
> +       /*
> +        * Find a closest [nr;nf;od] vector taking into account the
> +        * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is
> +        * either 1 or even number within the acceptable range (alas 1s
> +        * is also excluded by the next loop).
> +        */
> +       for (; nri <= nr_max; ++nri) {
> +               /* Use Od factor to fulfill the limitation 2). */
> +               num = CCU_PLL_CLKOD_FACTOR * rate;
> +               denom = parent_rate / nri;
> +
> +               /*
> +                * Make sure Fvco is within the acceptable range to fulfill
> +                * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value
> +                * the actual upper limit is also divided by that factor.
> +                * It's not big problem for us since practically there is no
> +                * need in clocks with that high frequency.
> +                */
> +               nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX);
> +               od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR;
> +
> +               /*
> +                * Bypass the out-of-bound values, which can't be properly
> +                * handled by the rational fraction approximation algorithm.
> +                */
> +               if (num / denom >= nf_max) {
> +                       n1 = nf_max;
> +                       d1 = 1;
> +               } else if (denom / num >= od_max) {
> +                       n1 = 1;
> +                       d1 = od_max;
> +               } else {
> +                       rational_best_approximation(num, denom, nf_max, od_max,
> +                                                   &n1, &d1);
> +               }
> +
> +               /* Select the best approximation of the target rate. */
> +               freq = (((parent_rate / nri) * n1) / d1);

Please drop extra parenthesis.

> +               err = abs((int64_t)freq - num);
> +               if (err < min_err) {
> +                       min_err = err;
> +                       *nr = nri;
> +                       *nf = n1;
> +                       *od = CCU_PLL_CLKOD_FACTOR * d1;
> +               }
> +       }
> +}
> +
> +static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long *parent_rate)
> +{
> +       unsigned long nr = 1, nf = 1, od = 1;
> +
> +       ccu_pll_calc_factors(rate, *parent_rate, &nr, &nf, &od);
> +
> +       return CCU_PLL_CALC_FREQ(*parent_rate, nr, nf, od);
> +}
> +
> +/*
> + * This method is used for PLLs, which support the on-the-fly dividers
> + * adjustment. So there is no need in gating such clocks.
> + */
> +static int ccu_pll_set_rate_reset(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +       unsigned long nr, nf, od;
> +       unsigned long flags;
> +       u32 mask, val;
> +       int ret;
> +
> +       ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od);
> +
> +       mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK |
> +              CCU_PLL_CTL_CLKOD_MASK;
> +       val = CCU_SET_FLD(CCU_PLL_CTL_CLKR, 0, nr - 1) |
> +             CCU_SET_FLD(CCU_PLL_CTL_CLKF, 0, nf - 1) |
> +             CCU_SET_FLD(CCU_PLL_CTL_CLKOD, 0, od - 1);
> +
> +       spin_lock_irqsave(&pll->regs_lock, flags);
> +       ccu_update(pll->regs + CCU_PLL_CTL, mask, val);
> +       ret = ccu_pll_reset(pll, parent_rate, nr);
> +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> +       if (ret)
> +               pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
> +
> +       return ret;
> +}
> +
> +/*
> + * This method is used for PLLs, which don't support the on-the-fly dividers
> + * adjustment. So the corresponding clocks are supposed to be gated first.
> + */
> +static int ccu_pll_set_rate_norst(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long parent_rate)
> +{
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +       unsigned long nr, nf, od;
> +       unsigned long flags;
> +       u32 mask, val;
> +
> +       ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od);
> +
> +       /*
> +        * Disable PLL if it was enabled by default or left enabled by the
> +        * system bootloader.
> +        */
> +       mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK |
> +              CCU_PLL_CTL_CLKOD_MASK | CCU_PLL_CTL_EN;
> +       val = CCU_SET_FLD(CCU_PLL_CTL_CLKR, 0, nr - 1) |
> +             CCU_SET_FLD(CCU_PLL_CTL_CLKF, 0, nf - 1) |
> +             CCU_SET_FLD(CCU_PLL_CTL_CLKOD, 0, od - 1);
> +
> +       spin_lock_irqsave(&pll->regs_lock, flags);
> +       ccu_update(pll->regs + CCU_PLL_CTL, mask, val);
> +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_DEBUG_FS
> +
> +#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask)                     \
> +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> +{                                                                      \
> +       struct ccu_pll *pll = priv;                                     \
> +                                                                       \
> +       *val = !!(ccu_read(pll->regs + (_reg)) & (_mask));              \
> +                                                                       \
> +       return 0;                                                       \
> +}                                                                      \
> +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> +{                                                                      \
> +       struct ccu_pll *pll = priv;                                     \
> +       unsigned long flags;                                            \
> +                                                                       \
> +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> +       ccu_update(pll->regs + (_reg), (_mask), val ? (_mask) : 0);     \
> +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> +                                                                       \
> +       return 0;                                                       \
> +}                                                                      \
> +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> +
> +#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _fld, _min, _max)          \
> +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> +{                                                                      \
> +       struct ccu_pll *pll = priv;                                     \
> +       u32 data;                                                       \
> +                                                                       \
> +       data = ccu_read(pll->regs + (_reg));                            \
> +       *val = CCU_GET_FLD(_fld, data) + 1;                             \
> +                                                                       \
> +       return 0;                                                       \
> +}                                                                      \
> +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> +{                                                                      \
> +       struct ccu_pll *pll = priv;                                     \
> +       unsigned long flags;                                            \
> +       u32 data;                                                       \
> +                                                                       \
> +       val = clamp_t(u64, val, _min, _max);                            \
> +       data = CCU_SET_FLD(_fld, 0, val - 1);                           \
> +                                                                       \
> +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> +       ccu_update(pll->regs + (_reg), _fld ## _MASK, data);            \
> +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> +                                                                       \
> +       return 0;                                                       \
> +}                                                                      \
> +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> +
> +CCU_PLL_DBGFS_BIT_ATTR(en, CCU_PLL_CTL, CCU_PLL_CTL_EN);
> +CCU_PLL_DBGFS_BIT_ATTR(rst, CCU_PLL_CTL, CCU_PLL_CTL_RST);
> +CCU_PLL_DBGFS_FLD_ATTR(nr, CCU_PLL_CTL, CCU_PLL_CTL_CLKR, 1, CCU_PLL_NR_MAX);
> +CCU_PLL_DBGFS_FLD_ATTR(nf, CCU_PLL_CTL, CCU_PLL_CTL_CLKF, 1, CCU_PLL_NF_MAX);
> +CCU_PLL_DBGFS_FLD_ATTR(od, CCU_PLL_CTL, CCU_PLL_CTL_CLKOD, 1, CCU_PLL_OD_MAX);
> +CCU_PLL_DBGFS_BIT_ATTR(bypass, CCU_PLL_CTL, CCU_PLL_CTL_BYPASS);
> +CCU_PLL_DBGFS_BIT_ATTR(lock, CCU_PLL_CTL, CCU_PLL_CTL_LOCK);
> +CCU_PLL_DBGFS_FLD_ATTR(nb, CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ, 1, CCU_PLL_NB_MAX);
> +
> +static const struct debugfs_reg32 ccu_pll_dbgfs_regs[] = {
> +       CCU_DBGFS_REG("ctl", CCU_PLL_CTL),
> +       CCU_DBGFS_REG("ctl1", CCU_PLL_CTL1)
> +};
> +
> +static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
> +{
> +       struct ccu_pll *pll = to_ccu_pll(hw);
> +       struct debugfs_regset32 *regset;
> +
> +       regset = kzalloc(sizeof(*regset), GFP_KERNEL);
> +       if (!regset)
> +               return;
> +
> +       regset->regs = ccu_pll_dbgfs_regs;
> +       regset->nregs = ARRAY_SIZE(ccu_pll_dbgfs_regs);
> +       regset->base = pll->regs;
> +       debugfs_create_regset32("registers", 0400, dentry, regset);
> +
> +       debugfs_create_file_unsafe("en", 0600, dentry, pll,

Why unsafe?

> +                                  &ccu_pll_dbgfs_en_fops);
> +       debugfs_create_file_unsafe("rst", 0200, dentry, pll,
> +                                  &ccu_pll_dbgfs_rst_fops);
> +       debugfs_create_file_unsafe("nr", 0600, dentry, pll,
> +                                  &ccu_pll_dbgfs_nr_fops);
> +       debugfs_create_file_unsafe("nf", 0600, dentry, pll,
> +                                  &ccu_pll_dbgfs_nf_fops);
> +       debugfs_create_file_unsafe("od", 0600, dentry, pll,
> +                                  &ccu_pll_dbgfs_od_fops);
> +       debugfs_create_file_unsafe("bypass", 0600, dentry, pll,
> +                                  &ccu_pll_dbgfs_bypass_fops);
> +       debugfs_create_file_unsafe("lock", 0400, dentry, pll,
> +                                  &ccu_pll_dbgfs_lock_fops);
> +       debugfs_create_file_unsafe("nb", 0600, dentry, pll,
> +                                  &ccu_pll_dbgfs_nb_fops);
> +}

Is there any usage of these outside of development of this driver? I'd
rather not see us expose blanket register write access with code in
debugfs. If you're interested in poking register why not use /dev/mem?

> +
> +#else /* !CONFIG_DEBUG_FS */
> +
> +#define ccu_pll_debug_init NULL
> +
> +#endif /* !CONFIG_DEBUG_FS */
> +
> +static const struct clk_ops ccu_pll_gate_to_set_ops = {
> +       .enable = ccu_pll_enable,
> +       .disable = ccu_pll_disable,
> +       .is_enabled = ccu_pll_is_enabled,
> +       .recalc_rate = ccu_pll_recalc_rate,
> +       .round_rate = ccu_pll_round_rate,
> +       .set_rate = ccu_pll_set_rate_norst,
> +       .debug_init = ccu_pll_debug_init
> +};
> +
> +static const struct clk_ops ccu_pll_straight_set_ops = {
> +       .enable = ccu_pll_enable,
> +       .disable = ccu_pll_disable,
> +       .is_enabled = ccu_pll_is_enabled,
> +       .recalc_rate = ccu_pll_recalc_rate,
> +       .round_rate = ccu_pll_round_rate,
> +       .set_rate = ccu_pll_set_rate_reset,
> +       .debug_init = ccu_pll_debug_init
> +};
> +
> +struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *pll_init)

Can pll_init be const?

> +{
> +       struct clk_parent_data parent_data = {0};
> +       struct clk_init_data hw_init = {0};
> +       struct ccu_pll *pll;
> +       int ret;
> +
> +       if (!pll_init)
> +               return ERR_PTR(-EINVAL);
> +
> +       pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> +       if (!pll)
> +               return ERR_PTR(-ENOMEM);
> +
> +       pll->hw.init = &hw_init;
> +       pll->id = pll_init->id;
> +       pll->regs = pll_init->regs;
> +       spin_lock_init(&pll->regs_lock);
> +
> +       hw_init.name = pll_init->name;
> +       hw_init.flags = CLK_IGNORE_UNUSED;

Why ignore unused? Please have a comment.

> +
> +       if (pll_init->flags & CCU_PLL_GATE_TO_SET) {
> +               hw_init.flags |= CLK_SET_RATE_GATE;
> +               hw_init.ops = &ccu_pll_gate_to_set_ops;
> +       } else {
> +               hw_init.ops = &ccu_pll_straight_set_ops;
> +       }
> +
> +       if (pll_init->flags & CCU_PLL_CRITICAL)
> +               hw_init.flags |= CLK_IS_CRITICAL;
> +
> +       if (!pll_init->parent_name) {
> +               ret = -EINVAL;
> +               goto err_free_pll;
> +       }
> +       parent_data.fw_name = pll_init->parent_name;
> +       hw_init.parent_data = &parent_data;
> +       hw_init.num_parents = 1;
> +
> +       ret = of_clk_hw_register(pll_init->np, &pll->hw);
> +       if (ret)
> +               goto err_free_pll;
> +
> +       return pll;
> +
> +err_free_pll:
> +       kfree(pll);
> +
> +       return ERR_PTR(ret);
> +}
> +
> +void ccu_pll_hw_unregister(struct ccu_pll *pll)
> +{
> +       if (!pll)
> +               return;

That would be quite bad. Just let that blow up if so.

> +
> +       clk_hw_unregister(&pll->hw);
> +
> +       kfree(pll);
> +}
> diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h
> new file mode 100644
> index 000000000000..6921516311fb
> --- /dev/null
> +++ b/drivers/clk/baikal-t1/ccu-pll.h
> @@ -0,0 +1,73 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> + *
> + * Baikal-T1 CCU PLL interface driver.
> + */
> +#ifndef __CLK_BT1_CCU_PLL_H__
> +#define __CLK_BT1_CCU_PLL_H__
> +
> +#include <linux/clk-provider.h>
> +#include <linux/spinlock.h>
> +#include <linux/bits.h>
> +#include <linux/of.h>
> +
> +/*
> + * CCU PLL private flags.
> + * @CCU_PLL_GATE_TO_SET: Some PLLs output clock can't be changed on-the-fly,
> + *                      so according to documentation they need to be gated
> + *                      first.
> + * @CCU_PLL_CRITICAL: Even though there is a way to switch any PLL off, there
> + *                   might be some, which shouldn't be in any case.
> + */
> +#define CCU_PLL_GATE_TO_SET            BIT(0)
> +#define CCU_PLL_CRITICAL               BIT(1)
> +
> +/*
> + * struct ccu_pll_init_data - CCU PLL initialization data.
> + * @id: Clock private identifier.
> + * @regs: PLL registers base address.
> + * @name: Clocks name.
> + * @parent_name: Clocks parent name in a fw node.
> + * @np: Pointer to the node describing the CCU PLLs.
> + * @flags: PLL private flags.
> + */
> +struct ccu_pll_init_data {
> +       unsigned int id;
> +       void __iomem *regs;
> +       const char *name;
> +       const char *parent_name;
> +       struct device_node *np;
> +       unsigned long flags;
> +};
> +
> +/*
> + * struct ccu_pll - CCU PLL descriptor.

Please drop the full stop on title

> + * @hw: clk_hw of the PLL.
> + * @id: Clock private identifier.
> + * @regs: PLL registers base address.
> + * @regs_lock: The registers exclusive access spin-lock.
> + */
> +struct ccu_pll {
> +       struct clk_hw hw;
> +       unsigned int id;
> +       void __iomem *regs;
> +       spinlock_t regs_lock;
> +};
> +#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw)
> +
> +static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll)
> +{
> +       return pll ? &pll->hw : NULL;
> +}
> +
> +static inline unsigned int ccu_pll_get_clk_id(struct ccu_pll *pll)
> +{
> +       return pll ? pll->id : -1;

It's unsigned return value, but return -1? Can this be inlined in the
one call site and if !pll just continue instead of having to turn it
into a negative value returned through an unsigned int?

> +}
> +
> +extern struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *init);
> +
> +extern void ccu_pll_hw_unregister(struct ccu_pll *pll);
> +
> +#endif /* __CLK_BT1_CCU_PLL_H__ */
> diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c
> new file mode 100644
> index 000000000000..990f0a4a12f9
> --- /dev/null
> +++ b/drivers/clk/baikal-t1/clk-ccu-pll.c
> @@ -0,0 +1,217 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> + *
> + * Authors:
> + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> + *
> + * Baikal-T1 CCU PLL clocks driver.
> + */
> +
> +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/printk.h>
> +#include <linux/slab.h>
> +#include <linux/clk-provider.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/ioport.h>
> +
> +#include <dt-bindings/clock/bt1-ccu.h>
> +
> +#include "ccu-pll.h"
> +
> +#define CCU_CPU_PLL_BASE               0x000
> +#define CCU_SATA_PLL_BASE              0x008
> +#define CCU_DDR_PLL_BASE               0x010
> +#define CCU_PCIE_PLL_BASE              0x018
> +#define CCU_ETH_PLL_BASE               0x020
> +
> +#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags)        \
> +       {                                               \
> +               .id = _id,                              \
> +               .name = _name,                          \
> +               .parent_name = _pname,                  \
> +               .base = _base,                          \
> +               .flags = _flags                         \
> +       }
> +
> +#define CCU_PLL_NUM                    ARRAY_SIZE(pll_info)
> +
> +struct ccu_pll_info {
> +       unsigned int id;
> +       const char *name;
> +       const char *parent_name;
> +       unsigned int base;
> +       unsigned long flags;
> +};
> +
> +static const struct ccu_pll_info pll_info[] = {
> +       CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
> +                    CCU_PLL_CRITICAL),
> +       CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
> +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),

Please comment all CLK_IS_CRITICAL usage and don't make your own version
of the same flags in the common clk framework. Just use the ones in the
framework.

> +       CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
> +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> +       CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
> +                    CCU_PLL_CRITICAL),
> +       CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
> +                    CCU_PLL_GATE_TO_SET)
> +};
> +
> +struct ccu_pll_data {
> +       struct device_node *np;
> +       void __iomem *regs;
> +       struct ccu_pll *plls[CCU_PLL_NUM];
> +};
> +
> +static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
> +                                        unsigned int clk_id)
> +{
> +       struct ccu_pll *pll;
> +       int idx;
> +
> +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> +               pll = data->plls[idx];
> +               if (clk_id == ccu_pll_get_clk_id(pll))
> +                       return pll;
> +       }
> +
> +       return ERR_PTR(-EINVAL);
> +}
> +
> +static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
> +{
> +       struct ccu_pll_data *data;
> +
> +       data = kzalloc(sizeof(*data), GFP_KERNEL);
> +       if (!data)
> +               return ERR_PTR(-ENOMEM);
> +
> +       data->np = np;
> +
> +       return data;
> +}
> +
> +static void ccu_pll_free_data(struct ccu_pll_data *data)
> +{
> +       kfree(data);
> +}
> +
> +static int ccu_pll_request_regs(struct ccu_pll_data *data)
> +{
> +       data->regs = of_io_request_and_map(data->np, 0,
> +                                          of_node_full_name(data->np));
> +       if (IS_ERR(data->regs)) {
> +               pr_err("Failed to request PLLs '%s' regs\n",
> +                       of_node_full_name(data->np));
> +               return PTR_ERR(data->regs);
> +       }
> +
> +       return 0;
> +}
> +
> +static void ccu_pll_release_regs(struct ccu_pll_data *data)
> +{
> +       struct resource res;
> +
> +       iounmap(data->regs);
> +
> +       /* Try to release the resource as well. */
> +       if (of_address_to_resource(data->np, 0, &res))
> +               return;
> +
> +       (void)release_mem_region(res.start, resource_size(&res));
> +}

Instead of having these three functions please inline them at the one call
site.

> +
> +static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
> +                                           void *priv)
> +{
> +       struct ccu_pll_data *data = priv;
> +       struct ccu_pll *pll;
> +       unsigned int clk_id;
> +
> +       clk_id = clkspec->args[0];
> +       pll = ccu_pll_find_desc(data, clk_id);
> +       if (IS_ERR(pll)) {
> +               pr_info("Invalid PLL clock ID %d specified\n", clk_id);
> +               return ERR_CAST(pll);
> +       }
> +
> +       return ccu_pll_get_clk_hw(pll);
> +}
> +
> +static int ccu_pll_clk_register(struct ccu_pll_data *data)
> +{
> +       int idx, ret;
> +
> +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> +               const struct ccu_pll_info *info = &pll_info[idx];
> +               struct ccu_pll_init_data init = {0};
> +
> +               init.id = info->id;
> +               init.regs = data->regs + info->base;
> +               init.parent_name = info->parent_name;
> +               init.np = data->np;
> +               init.flags = info->flags;
> +
> +               ret = of_property_read_string_index(data->np,
> +                       "clock-output-names", init.id, &init.name);

Do you need this property ever? It would be nice to not have it if
possible.

> +               if (ret)
> +                       init.name = info->name;
> +
> +               data->plls[idx] = ccu_pll_hw_register(&init);
> +               if (IS_ERR(data->plls[idx])) {
> +                       ret = PTR_ERR(data->plls[idx]);
> +                       pr_err("Couldn't register PLL hw '%s'\n",
> +                               init.name);
> +                       goto err_hw_unregister;
> +               }
> +       }
> +
> +       ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
> +       if (ret) {
> +               pr_err("Couldn't register PLL provider of '%s'\n",
> +                       of_node_full_name(data->np));
> +               goto err_hw_unregister;
> +       }
> +
> +       return 0;
> +
> +err_hw_unregister:
> +       for (--idx; idx >= 0; --idx)
> +               ccu_pll_hw_unregister(data->plls[idx]);
> +
> +       return ret;
> +}
> +
> +static __init void ccu_pll_init(struct device_node *np)
> +{
> +       struct ccu_pll_data *data;
> +       int ret;
> +
> +       data = ccu_pll_create_data(np);
> +       if (IS_ERR(data))
> +               return;
> +
> +       ret = ccu_pll_request_regs(data);
> +       if (ret)
> +               goto err_free_data;
> +
> +       ret = ccu_pll_clk_register(data);
> +       if (ret)
> +               goto err_release_regs;
> +
> +       pr_info("CCU CPU/SATA/DDR/PCIe/Ethernet PLLs are initialized\n");

Please don't have I'm alive probe messages at info level. Just slows
down boot for developer comfort.

> +
> +       return;
> +
> +err_release_regs:
> +       ccu_pll_release_regs(data);
> +
> +err_free_data:
> +       ccu_pll_free_data(data);
> +}
> +CLK_OF_DECLARE(ccu_pll, "be,bt1-ccu-pll", ccu_pll_init);

Any reason this can't be a platform device driver?

> diff --git a/drivers/clk/baikal-t1/common.h b/drivers/clk/baikal-t1/common.h
> new file mode 100644
> index 000000000000..07c8d67f5275
> --- /dev/null
> +++ b/drivers/clk/baikal-t1/common.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> + *
> + * Baikal-T1 CCU common methods.
> + */
> +#ifndef __CLK_BT1_COMMON_H__
> +#define __CLK_BT1_COMMON_H__
> +
> +#include <linux/debugfs.h>
> +#include <linux/io.h>
> +
> +#define CCU_GET_FLD(_name, _data) \
> +       (((u32)(_data) & _name ## _MASK) >> _name ## _FLD)
> +
> +#define CCU_SET_FLD(_name, _data, _val) \
> +       (((u32)(_data) & ~_name ## _MASK) | \
> +       (((u32)(_val) << _name ## _FLD) & _name ## _MASK))
> +
> +#define CCU_DBGFS_REG(_name, _off)     \
> +{                                      \
> +       .name = _name,                  \
> +       .offset = _off                  \
> +}
> +
> +static inline u32 ccu_read(void __iomem *reg)
> +{
> +       return readl(reg);
> +}
> +
> +static inline void ccu_write(void __iomem *reg, u32 data)
> +{
> +       writel(data, reg);
> +}
> +
> +static inline void ccu_update(void __iomem *reg, u32 mask, u32 data)
> +{
> +       u32 old;
> +
> +       old = readl_relaxed(reg);
> +       writel((old & ~mask) | (data & mask), reg);
> +}

Please just write the code in line as readl/writel/etc. This whole file
doesn't look necessary.

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

* Re: [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings
  2020-03-06 13:00 ` [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings Sergey.Semin
@ 2020-03-12 20:50   ` Rob Herring
  2020-04-05 10:28     ` Sergey Semin
  0 siblings, 1 reply; 19+ messages in thread
From: Rob Herring @ 2020-03-12 20:50 UTC (permalink / raw)
  To: Sergey.Semin
  Cc: Michael Turquette, Stephen Boyd, Mark Rutland, Philipp Zabel,
	Serge Semin, Alexey Malahov, Thomas Bogendoerfer, Paul Burton,
	Ralf Baechle, linux-clk, devicetree, linux-kernel

On Fri, Mar 06, 2020 at 04:00:45PM +0300, Sergey.Semin@baikalelectronics.ru wrote:
> From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> 
> After being gained by the CCU PLLs the signals must be transformed to
> be suitable for the clock-consumers. This is done by a set of dividers
> embedded into the CCU. A first block of dividers is used to create
> reference clocks for AXI-bus of high-speed peripheral IP-cores of the
> chip. So the AXI-bus CCU dts-node is an ordinary clock-provider with
> standard set of properties supported. But in addition to that each
> AXI-bus clock divider provide a way to reset the corresponding clock
> domain. This makes the AXI-bus CCU dts-node to be also a reset-provider.
> 
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Paul Burton <paulburton@kernel.org>
> Cc: Ralf Baechle <ralf@linux-mips.org>
> ---
>  .../bindings/clock/be,bt1-ccu-axi.yaml        | 151 ++++++++++++++++++
>  include/dt-bindings/clock/bt1-ccu.h           |  13 ++
>  include/dt-bindings/reset/bt1-ccu.h           |  23 +++
>  3 files changed, 187 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
>  create mode 100644 include/dt-bindings/reset/bt1-ccu.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
> new file mode 100644
> index 000000000000..6b1eefdead27
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
> @@ -0,0 +1,151 @@
> +# SPDX-License-Identifier: GPL-2.0

Dual license new bindings:

(GPL-2.0-only OR BSD-2-Clause)

> +#
> +# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
> +#
> +# Baikal-T1 AXI-bus Clocks Control Unit Device Tree Bindings.

As Stephen said, drop this. You can keep the copyright, just make it 
line 2.

> +#
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/be,bt1-ccu-axi.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Baikal-T1 AXI-bus Clock Control Unit
> +
> +maintainers:
> +  - Serge Semin <fancer.lancer@gmail.com>
> +
> +description: |
> +  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> +  subsystems clocking and resetting. The CCU is connected with an external
> +  fixed rate oscillator, which signal is transformed into clocks of various
> +  frequencies and then propagated to either individual IP-blocks or to groups
> +  of blocks (clock domains). The transformation is done by means of an embedded
> +  into CCU PLLs and gateable/non-gateable dividers. Each clock domain can be
> +  also individually reset by using the domain clocks divider configuration
> +  registers. Baikal-T1 CCU is logically divided into the next components:
> +  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
> +     in general can provide any frequency supported by the CCU PLLs).
> +  2) PLLs clocks generators (PLLs).
> +  3) AXI-bus clock dividers (AXI) - described in this bindings file.
> +  4) System devices reference clock dividers (SYS).
> +  which are connected with each other as shown on the next figure:
> +          +---------------+
> +          | Baikal-T1 CCU |
> +          |   +----+------|- MIPS P5600 cores
> +          | +-|PLLs|------|- DDR controller
> +          | | +----+      |
> +  +----+  | |  |  |       |
> +  |XTAL|--|-+  |  | +---+-|
> +  +----+  | |  |  +-|AXI|-|- AXI-bus
> +          | |  |    +---+-|
> +          | |  |          |
> +          | |  +----+---+-|- APB-bus
> +          | +-------|SYS|-|- Low-speed Devices
> +          |         +---+-|- High-speed Devices
> +          +---------------+
> +  Each sub-block is represented as a separate dts-node and has an individual
> +  driver to be bound with.
> +
> +  In order to create signals of wide range frequencies the external oscillator
> +  output is primarily connected to a set of CCU PLLs. Some of PLLs CLKOUT are
> +  then passed over CCU dividers to create signals required for the target clock
> +  domain (like AXI-bus consumers). The dividers have the following structure:
> +          +--------------+
> +  CLKIN --|->+----+ 1|\  |
> +  SETCLK--|--|/DIV|->| | |
> +  CLKDIV--|--|    |  | |-|->CLKLOUT
> +  LOCK----|--+----+  | | |
> +          |          |/  |
> +          |           |  |
> +  EN------|-----------+  |
> +  RST-----|--------------|->RSTOUT
> +          +--------------+
> +  where CLKIN is the reference clock coming either from a CCU PLL, SETCLK - a
> +  command to update the output clock in accordance with a set divider,
> +  CLKDIV - clocks divider, LOCK - a signal of the output clock stabilization,
> +  EN - enable/disable the divider block, RST/RSTOUT - reset clocks domain
> +  signal. Depending on the consumer IP-core peculiarities the dividers may lack
> +  of some functionality depicted on the figure above (like EN,
> +  CLKDIV/LOCK/SETCLK). In this case the corresponding clock provider just
> +  doesn't expose either switching functions, or the rate configuration, or
> +  both of them.
> +
> +  The CCU AXI dts-node uses the common clock bindings [1] with no custom
> +  properties. The list of exported clocks and reset signals can be found in
> +  the files: 'dt-bindings/clock/bt1-ccu.h' and 'dt-bindings/reset/bt1-ccu.h'.
> +
> +  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
> +
> +allOf:
> +  - $ref: /schemas/clock/clock.yaml#

Drop this, not needed (clock.yaml is applied to every node).

> +
> +properties:
> +  compatible:
> +    const: be,bt1-ccu-axi
> +
> +  reg:
> +    description: AXI-bus CCU dividers sub-block base address.

Don't really need this.

> +    maxItems: 1
> +
> +  "#clock-cells":
> +    description: |
> +      Clocks are referenced by the node phandle and an unique identifier
> +      from 'dt-bindings/clock/bt1-ccu.h'.
> +    const: 1
> +
> +  "#reset-cells":
> +    description: |
> +      AXI-bus CCU sub-block provides a reset signal for each clock domain,
> +      which unique identifiers are in 'dt-bindings/reset/bt1-ccu.h'.
> +    const: 1
> +
> +  clocks:
> +    items:
> +      - description: CCU SATA PLL output clock.
> +      - description: CCU PCIe PLL output clock.
> +      - description: CCU Ethernet PLL output clock.
> +
> +  clock-names:
> +    items:
> +      - const: sata_clk
> +      - const: pcie_clk
> +      - const: eth_clk
> +
> +  clock-output-names: true
> +
> +  assigned-clocks: true
> +
> +  assigned-clock-rates: true
> +
> +additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - "#clock-cells"
> +  - clocks
> +  - clock-names
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/bt1-ccu.h>
> +
> +    ccu_axi: ccu_axi@1F04D030 {

clock-controller@1f04d030

> +      compatible = "be,bt1-ccu-axi";
> +      reg = <0x1F04D030 0x030>;
> +      #clock-cells = <1>;
> +      #reset-cells = <1>;
> +
> +      clocks = <&ccu_pll CCU_SATA_PLL>,
> +               <&ccu_pll CCU_PCIE_PLL>,
> +               <&ccu_pll CCU_ETH_PLL>;
> +      clock-names = "sata_clk", "pcie_clk", "eth_clk";
> +
> +      clock-output-names = "axi_main_clk", "axi_ddr_clk",
> +                           "axi_sata_clk", "axi_gmac0_clk",
> +                           "axi_gmac1_clk", "axi_xgmac_clk",
> +                           "axi_pcie_m_clk", "axi_pcie_s_clk",
> +                           "axi_usb_clk", "axi_hwa_clk",
> +                           "axi_sram_clk";
> +    };
> +...
> diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
> index 86e63162ade0..ebe723c6e0a8 100644
> --- a/include/dt-bindings/clock/bt1-ccu.h
> +++ b/include/dt-bindings/clock/bt1-ccu.h
> @@ -14,4 +14,17 @@
>  #define CCU_PCIE_PLL			3
>  #define CCU_ETH_PLL			4
>  
> +/* Baikal-T1 AXI-bus CCU Clocks indeces. */
> +#define CCU_AXI_MAIN_CLK		0
> +#define CCU_AXI_DDR_CLK			1
> +#define CCU_AXI_SATA_CLK		2
> +#define CCU_AXI_GMAC0_CLK		3
> +#define CCU_AXI_GMAC1_CLK		4
> +#define CCU_AXI_XGMAC_CLK		5
> +#define CCU_AXI_PCIE_M_CLK		6
> +#define CCU_AXI_PCIE_S_CLK		7
> +#define CCU_AXI_USB_CLK			8
> +#define CCU_AXI_HWA_CLK			9
> +#define CCU_AXI_SRAM_CLK		10
> +
>  #endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
> diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h
> new file mode 100644
> index 000000000000..4de5b6bcd433
> --- /dev/null
> +++ b/include/dt-bindings/reset/bt1-ccu.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> + *
> + * Baikal-T1 CCU reset indeces.
> + */
> +#ifndef __DT_BINDINGS_RESET_BT1_CCU_H
> +#define __DT_BINDINGS_RESET_BT1_CCU_H
> +
> +/* Baikal-T1 AXI-bus CCU Reset indeces. */
> +#define CCU_AXI_MAIN_RST		0
> +#define CCU_AXI_DDR_RST			1
> +#define CCU_AXI_SATA_RST		2
> +#define CCU_AXI_GMAC0_RST		3
> +#define CCU_AXI_GMAC1_RST		4
> +#define CCU_AXI_XGMAC_RST		5
> +#define CCU_AXI_PCIE_M_RST		6
> +#define CCU_AXI_PCIE_S_RST		7
> +#define CCU_AXI_USB_RST			8
> +#define CCU_AXI_HWA_RST			9
> +#define CCU_AXI_SRAM_RST		10
> +
> +#endif /* __DT_BINDINGS_RESET_BT1_CCU_H */
> -- 
> 2.25.1
> 

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

* Re: [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings
       [not found]   ` <20200310021052.2E40F80307C5@mail.baikalelectronics.ru>
@ 2020-04-05  9:59     ` Sergey Semin
  2020-04-16 19:27       ` Sergey Semin
  2020-04-26  6:18       ` Sergey Semin
  0 siblings, 2 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-05  9:59 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Mark Rutland, Michael Turquette, Rob Herring, Alexey Malahov,
	Thomas Bogendoerfer, Paul Burton, Ralf Baechle, linux-clk,
	devicetree, linux-kernel

Hello Stephen,

Sorry for a delayed response. My answers to your comments are below.

On Mon, Mar 09, 2020 at 07:02:27PM -0700, Stephen Boyd wrote:
> Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:44)
> > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > 
> > Baikal-T1 Clocks Control Unit is responsible for transformation of a
> > signal coming from an external oscillator into clocks of various
> > frequencies to propagate them then to the corresponding clocks
> > consumers (either individual IP-blocks or clock domains). In order
> > to create a set of high-frequency clocks the external signal is
> > firstly handled by the embedded into CCU PLLs. So the corresponding
> > dts-node is just a normal clock-provider node with standard set of
> > properties.
> > 
> > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> 
> SoB chain is backwards. Is Alexey the author? Or Co-developed-by?

Thanks for noticing this. I'll rearrange the SoB's in v2.

> 
> > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > Cc: Paul Burton <paulburton@kernel.org>
> > Cc: Ralf Baechle <ralf@linux-mips.org>
> > ---
> >  .../bindings/clock/be,bt1-ccu-pll.yaml        | 139 ++++++++++++++++++
> >  include/dt-bindings/clock/bt1-ccu.h           |  17 +++
> >  2 files changed, 156 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> >  create mode 100644 include/dt-bindings/clock/bt1-ccu.h
> > 
> > diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > new file mode 100644
> > index 000000000000..f2e397cc147b
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > @@ -0,0 +1,139 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
> > +#
> > +# Baikal-T1 Clocks Control Unit PLL Device Tree Bindings.
> > +#
> 
> I don't think we need any of these comments besides the license
> identifier line. Can you dual license this?
> 

It's normal to have a copyright here, but in a single-lined form.
I'll do this in v2 and also dual license the binding file.

> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/clock/be,bt1-ccu-pll.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Baikal-T1 Clock Control Unit PLLs
> > +
> > +maintainers:
> > +  - Serge Semin <fancer.lancer@gmail.com>
> > +
> > +description: |
> > +  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> > +  subsystems clocking and resetting. The CCU is connected with an external
> > +  fixed rate oscillator, which signal is transformed into clocks of various
> > +  frequencies and then propagated to either individual IP-blocks or to groups
> > +  of blocks (clock domains). The transformation is done by means of PLLs and
> > +  gateable/non-gateable dividers embedded into the CCU. It's logically divided
> > +  into the next components:
> > +  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
> > +     in general can provide any frequency supported by the CCU PLLs).
> > +  2) PLLs clocks generators (PLLs) - described in this bindings file.
> > +  3) AXI-bus clock dividers (AXI).
> > +  4) System devices reference clock dividers (SYS).
> > +  which are connected with each other as shown on the next figure:
> 
> Please add a newline here

Ok.

> 
> > +          +---------------+
> > +          | Baikal-T1 CCU |
> > +          |   +----+------|- MIPS P5600 cores
> > +          | +-|PLLs|------|- DDR controller
> > +          | | +----+      |
> > +  +----+  | |  |  |       |
> > +  |XTAL|--|-+  |  | +---+-|
> > +  +----+  | |  |  +-|AXI|-|- AXI-bus
> > +          | |  |    +---+-|
> > +          | |  |          |
> > +          | |  +----+---+-|- APB-bus
> > +          | +-------|SYS|-|- Low-speed Devices
> > +          |         +---+-|- High-speed Devices
> > +          +---------------+
> 
> And here.
> 

Ok

> > +  Each CCU sub-block is represented as a separate dts-node and has an
> > +  individual driver to be bound with.
> > +
> > +  In order to create signals of wide range frequencies the external oscillator
> > +  output is primarily connected to a set of CCU PLLs. There are five PLLs
> > +  to create a clock for the MIPS P5600 cores, the embedded DDR controller,
> > +  SATA, Ethernet and PCIe domains. The last three domains though named by the
> > +  biggest system interfaces in fact include nearly all of the rest SoC
> > +  peripherals. Each of the PLLs is based on True Circuits TSMC CLN28HPM core
> > +  with an interface wrapper (so called safe PLL' clocks switcher) to simplify
> > +  the PLL configuration procedure. The PLLs work as depicted on the next
> > +  diagram:
> 
> Same, space out the diagrams.
> 

Ok

> > +      +--------------------------+
> > +      |                          |
> > +      +-->+---+    +---+   +---+ |  +---+   0|\
> > +  CLKF--->|/NF|--->|PFD|...|VCO|-+->|/OD|--->| |
> > +          +---+ +->+---+   +---+ /->+---+    | |--->CLKOUT
> > +  CLKOD---------C----------------+          1| |
> > +       +--------C--------------------------->|/
> > +       |        |                             ^
> > +  Rclk-+->+---+ |                             |
> > +  CLKR--->|/NR|-+                             |
> > +          +---+                               |
> > +  BYPASS--------------------------------------+
> > +  BWADJ--->
> > +  where Rclk is the reference clock coming  from XTAL, NR - reference clock
> > +  divider, NF - PLL clock multiplier, OD - VCO output clock divider, CLKOUT -
> > +  output clock, BWADJ is the PLL bandwidth adjustment parameter. At this moment
> > +  the binding supports the PLL dividers configuration in accordance with a
> > +  requested rate, while bypassing and bandwidth adjustment settings can be
> > +  added in future if it gets to be necessary.
> > +
> > +  The PLLs CLKOUT is then either directly connected with the corresponding
> > +  clocks consumer (like P5600 cores or DDR controller) or passed over a CCU
> > +  divider to create a signal required for the clock domain.
> > +
> > +  The CCU PLL dts-node uses the common clock bindings [1] with no custom
> > +  parameters. The list of exported clocks can be found in
> > +  'dt-bindings/clock/bt1-ccu.h'.
> > +
> > +  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
> 
> Don't think we need to mention this binding anymore. But it's good that
> we know what exported clock ids are.
> 

Ok. I'll remove the legacy text binding file mention here and retain the
reference to the header file with the clock IDs defined. The similar
thing will be done for the others bindings in the patchset.

> > +
> > +allOf:
> > +  - $ref: /schemas/clock/clock.yaml#
> > +
> > +properties:
> > +  compatible:
> > +    const: be,bt1-ccu-pll
> > +

> > +  reg:
> > +    description: CCU PLLs sub-block base address.
> > +    maxItems: 1
> > +

Sometime ago I sent a RFC to Rob and you being in Cc there:
https://lkml.org/lkml/2020/3/22/393
Simply speaking there are several issues raised in comments to different
patchsets, which are indirectly connected with the Baikal-T1 System Controller
DT node design I've initially chosen. In accordance with that I've spread its
functional blocks into different DT nodes with no reference to being related
to the System Controller. Clock Control Unit nodes are amongst these blocks.
Seeing such design caused these issues I suggested an alternative solution
of having a single System Controller node and multiple functional sub-nodes.
These sub-nodes will include the Clock Control Unit PLLs, AXI-bus and System
Device blocks. I thoroughly described the solution in the RFC. So if no
arguments against it pop up soon in the RFC comments, I'll implement it in
v2 of this patchset as well. This solution cause the reg-property removal
from this binding. Instead the drivers shall refer to the parental syscon
node to get a regmap with CCU registers from it.

> > +  "#clock-cells":
> > +    description: |
> > +      Clocks are referenced by the node phandle and an unique identifier
> > +      from 'dt-bindings/clock/bt1-ccu.h'.
> 
> Don't think we need this description.

Agreed.

> 
> > +    const: 1
> > +
> > +  clocks:
> > +    description: Phandle of CCU External reference clock.
> > +    maxItems: 1
> > +
> > +  clock-names:
> > +    const: ref_clk
> 
> Can we drop _clk? It's redundant.

I would leave this and "pcie_clk", "sata_clk", "eth_clk" declared in the
next two bindings as is, since this way they would exactly match the names
used in the documentation. The same thing is with the clock-output-names
property values.

I've seen such names in many other drivers/bindings including the
bindings in the clock subsystem even submitted rather recently, not to
mention the names like "aclk", "pclk", etc used all over the dt nodes.
Are there any requirements in naming the clocks? Should I avoid using the
'_clk' clock names suffix in accordance with them? If so, please point
me out to that requirement in docs for future reference.

Normally If I don't find something in the requirements documented in the kernel,
I use either a commonly utilized practice seen in other similar drivers, or
select a solution which seems better to me like providing a better readability
and code understanding. 

> 
> > +
> > +  clock-output-names: true
> > +
> > +  assigned-clocks: true
> > +
> > +  assigned-clock-rates: true
> > +
> > +additionalProperties: false
> > +

I'll also replace these four properties with a single
"unevaluatedProperties: false". In the framework of other patchset
review Rob said this property is more suitable in such situations and
will get to be supported by the dt_binding_check script eventually.

> > +required:
> > +  - compatible
> > +  - reg
> > +  - "#clock-cells"
> > +  - clocks
> > +  - clock-names
> > +
> > +examples:
> > +  - |
> > +    ccu_pll: ccu_pll@1F04D000 {
> 
> Drop the phandle unless it's actually used.

Do you mean the label definition? If so, Ok. I'll remove it.

Unit-address will be also lowercased if I don't remove the reg property
from here. As I said in RFC in accordance with the alternative solution
this node will be a sub-node of the system controller, which regmap will
be used instead of the individual reg-property definition. So if the
reg-property is removed from the node, the unit-address will be also
discarded from here.

> 
> > +      compatible = "be,bt1-ccu-pll";
> > +      reg = <0x1F04D000 0x028>;
> 
> Lowercase hex please. That size is oddly small.

It's small due to be range being part of the system controller registers
set. I've briefly described this above and thoroughly - in the RFC.
Please see the RFC text and send your comments regarding an alternative
solution there shall you have any.

Anyway if no comments are received there soon, I'll remove the reg
property from here. The PLL driver will refer to the parental system
controller to get the registers regmap handler.

> 
> > +      #clock-cells = <1>;
> > +
> > +      clocks = <&osc25>;
> > +      clock-names = "ref_clk";
> > +
> > +      clock-output-names = "cpu_pll", "sata_pll", "ddr_pll",
> > +                           "pcie_pll", "eth_pll";
> > +    };
> > +...
> > diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
> > new file mode 100644
> > index 000000000000..86e63162ade0
> > --- /dev/null
> > +++ b/include/dt-bindings/clock/bt1-ccu.h
> > @@ -0,0 +1,17 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > + *
> > + * Baikal-T1 CCU clock indeces.
> > + */
> > +#ifndef __DT_BINDINGS_CLOCK_BT1_CCU_H
> > +#define __DT_BINDINGS_CLOCK_BT1_CCU_H
> > +
> > +/* Baikal-T1 CCU PLL indeces. */
> 
> Please drop this comment. It's not useful.

Ok.

Regards,
-Sergey

> 
> > +#define CCU_CPU_PLL                    0
> > +#define CCU_SATA_PLL                   1
> > +#define CCU_DDR_PLL                    2
> > +#define CCU_PCIE_PLL                   3
> > +#define CCU_ETH_PLL                    4
> > +
> > +#endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
> > -- 
> > 2.25.1
> >

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

* Re: [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings
  2020-03-12 20:50   ` Rob Herring
@ 2020-04-05 10:28     ` Sergey Semin
  0 siblings, 0 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-05 10:28 UTC (permalink / raw)
  To: Rob Herring
  Cc: Michael Turquette, Stephen Boyd, Mark Rutland, Philipp Zabel,
	Alexey Malahov, Thomas Bogendoerfer, Paul Burton, Ralf Baechle,
	linux-clk, devicetree, linux-kernel

On Thu, Mar 12, 2020 at 03:50:11PM -0500, Rob Herring wrote:
> On Fri, Mar 06, 2020 at 04:00:45PM +0300, Sergey.Semin@baikalelectronics.ru wrote:
> > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > 
> > After being gained by the CCU PLLs the signals must be transformed to
> > be suitable for the clock-consumers. This is done by a set of dividers
> > embedded into the CCU. A first block of dividers is used to create
> > reference clocks for AXI-bus of high-speed peripheral IP-cores of the
> > chip. So the AXI-bus CCU dts-node is an ordinary clock-provider with
> > standard set of properties supported. But in addition to that each
> > AXI-bus clock divider provide a way to reset the corresponding clock
> > domain. This makes the AXI-bus CCU dts-node to be also a reset-provider.
> > 
> > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > Cc: Paul Burton <paulburton@kernel.org>
> > Cc: Ralf Baechle <ralf@linux-mips.org>
> > ---
> >  .../bindings/clock/be,bt1-ccu-axi.yaml        | 151 ++++++++++++++++++
> >  include/dt-bindings/clock/bt1-ccu.h           |  13 ++
> >  include/dt-bindings/reset/bt1-ccu.h           |  23 +++
> >  3 files changed, 187 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
> >  create mode 100644 include/dt-bindings/reset/bt1-ccu.h
> > 
> > diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
> > new file mode 100644
> > index 000000000000..6b1eefdead27
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-axi.yaml
> > @@ -0,0 +1,151 @@
> > +# SPDX-License-Identifier: GPL-2.0
> 
> Dual license new bindings:
> 
> (GPL-2.0-only OR BSD-2-Clause)

Ok.

> 
> > +#
> > +# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
> > +#
> > +# Baikal-T1 AXI-bus Clocks Control Unit Device Tree Bindings.
> 
> As Stephen said, drop this. You can keep the copyright, just make it 
> line 2.

Ok.

> 
> > +#
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/clock/be,bt1-ccu-axi.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Baikal-T1 AXI-bus Clock Control Unit
> > +
> > +maintainers:
> > +  - Serge Semin <fancer.lancer@gmail.com>
> > +
> > +description: |
> > +  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> > +  subsystems clocking and resetting. The CCU is connected with an external
> > +  fixed rate oscillator, which signal is transformed into clocks of various
> > +  frequencies and then propagated to either individual IP-blocks or to groups
> > +  of blocks (clock domains). The transformation is done by means of an embedded
> > +  into CCU PLLs and gateable/non-gateable dividers. Each clock domain can be
> > +  also individually reset by using the domain clocks divider configuration
> > +  registers. Baikal-T1 CCU is logically divided into the next components:
> > +  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
> > +     in general can provide any frequency supported by the CCU PLLs).
> > +  2) PLLs clocks generators (PLLs).
> > +  3) AXI-bus clock dividers (AXI) - described in this bindings file.
> > +  4) System devices reference clock dividers (SYS).
> > +  which are connected with each other as shown on the next figure:
> > +          +---------------+
> > +          | Baikal-T1 CCU |
> > +          |   +----+------|- MIPS P5600 cores
> > +          | +-|PLLs|------|- DDR controller
> > +          | | +----+      |
> > +  +----+  | |  |  |       |
> > +  |XTAL|--|-+  |  | +---+-|
> > +  +----+  | |  |  +-|AXI|-|- AXI-bus
> > +          | |  |    +---+-|
> > +          | |  |          |
> > +          | |  +----+---+-|- APB-bus
> > +          | +-------|SYS|-|- Low-speed Devices
> > +          |         +---+-|- High-speed Devices
> > +          +---------------+
> > +  Each sub-block is represented as a separate dts-node and has an individual
> > +  driver to be bound with.
> > +
> > +  In order to create signals of wide range frequencies the external oscillator
> > +  output is primarily connected to a set of CCU PLLs. Some of PLLs CLKOUT are
> > +  then passed over CCU dividers to create signals required for the target clock
> > +  domain (like AXI-bus consumers). The dividers have the following structure:
> > +          +--------------+
> > +  CLKIN --|->+----+ 1|\  |
> > +  SETCLK--|--|/DIV|->| | |
> > +  CLKDIV--|--|    |  | |-|->CLKLOUT
> > +  LOCK----|--+----+  | | |
> > +          |          |/  |
> > +          |           |  |
> > +  EN------|-----------+  |
> > +  RST-----|--------------|->RSTOUT
> > +          +--------------+
> > +  where CLKIN is the reference clock coming either from a CCU PLL, SETCLK - a
> > +  command to update the output clock in accordance with a set divider,
> > +  CLKDIV - clocks divider, LOCK - a signal of the output clock stabilization,
> > +  EN - enable/disable the divider block, RST/RSTOUT - reset clocks domain
> > +  signal. Depending on the consumer IP-core peculiarities the dividers may lack
> > +  of some functionality depicted on the figure above (like EN,
> > +  CLKDIV/LOCK/SETCLK). In this case the corresponding clock provider just
> > +  doesn't expose either switching functions, or the rate configuration, or
> > +  both of them.
> > +
> > +  The CCU AXI dts-node uses the common clock bindings [1] with no custom
> > +  properties. The list of exported clocks and reset signals can be found in
> > +  the files: 'dt-bindings/clock/bt1-ccu.h' and 'dt-bindings/reset/bt1-ccu.h'.
> > +
> > +  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
> > +
> > +allOf:
> > +  - $ref: /schemas/clock/clock.yaml#
> 
> Drop this, not needed (clock.yaml is applied to every node).

Ok.

> 
> > +
> > +properties:
> > +  compatible:
> > +    const: be,bt1-ccu-axi
> > +
> > +  reg:
> > +    description: AXI-bus CCU dividers sub-block base address.
> 
> Don't really need this.

Ok, but as I said multiple times in others patchsets reply messages,
most likely this property will be removed from the bindings in accordance
with an alternative design of the Baikal-T1 System Controller DT node.
See the next RFC for details: https://lkml.org/lkml/2020/3/22/393

> 
> > +    maxItems: 1
> > +
> > +  "#clock-cells":
> > +    description: |
> > +      Clocks are referenced by the node phandle and an unique identifier
> > +      from 'dt-bindings/clock/bt1-ccu.h'.
> > +    const: 1
> > +
> > +  "#reset-cells":
> > +    description: |
> > +      AXI-bus CCU sub-block provides a reset signal for each clock domain,
> > +      which unique identifiers are in 'dt-bindings/reset/bt1-ccu.h'.
> > +    const: 1
> > +
> > +  clocks:
> > +    items:
> > +      - description: CCU SATA PLL output clock.
> > +      - description: CCU PCIe PLL output clock.
> > +      - description: CCU Ethernet PLL output clock.
> > +
> > +  clock-names:
> > +    items:
> > +      - const: sata_clk
> > +      - const: pcie_clk
> > +      - const: eth_clk
> > +
> > +  clock-output-names: true
> > +
> > +  assigned-clocks: true
> > +
> > +  assigned-clock-rates: true
> > +
> > +additionalProperties: false
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - "#clock-cells"
> > +  - clocks
> > +  - clock-names
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/clock/bt1-ccu.h>
> > +
> > +    ccu_axi: ccu_axi@1F04D030 {
> 
> clock-controller@1f04d030

Ok.

> 
> > +      compatible = "be,bt1-ccu-axi";
> > +      reg = <0x1F04D030 0x030>;
> > +      #clock-cells = <1>;
> > +      #reset-cells = <1>;
> > +
> > +      clocks = <&ccu_pll CCU_SATA_PLL>,
> > +               <&ccu_pll CCU_PCIE_PLL>,
> > +               <&ccu_pll CCU_ETH_PLL>;
> > +      clock-names = "sata_clk", "pcie_clk", "eth_clk";
> > +
> > +      clock-output-names = "axi_main_clk", "axi_ddr_clk",
> > +                           "axi_sata_clk", "axi_gmac0_clk",
> > +                           "axi_gmac1_clk", "axi_xgmac_clk",
> > +                           "axi_pcie_m_clk", "axi_pcie_s_clk",
> > +                           "axi_usb_clk", "axi_hwa_clk",
> > +                           "axi_sram_clk";
> > +    };
> > +...
> > diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
> > index 86e63162ade0..ebe723c6e0a8 100644
> > --- a/include/dt-bindings/clock/bt1-ccu.h
> > +++ b/include/dt-bindings/clock/bt1-ccu.h
> > @@ -14,4 +14,17 @@
> >  #define CCU_PCIE_PLL			3
> >  #define CCU_ETH_PLL			4
> >  
> > +/* Baikal-T1 AXI-bus CCU Clocks indeces. */
> > +#define CCU_AXI_MAIN_CLK		0
> > +#define CCU_AXI_DDR_CLK			1
> > +#define CCU_AXI_SATA_CLK		2
> > +#define CCU_AXI_GMAC0_CLK		3
> > +#define CCU_AXI_GMAC1_CLK		4
> > +#define CCU_AXI_XGMAC_CLK		5
> > +#define CCU_AXI_PCIE_M_CLK		6
> > +#define CCU_AXI_PCIE_S_CLK		7
> > +#define CCU_AXI_USB_CLK			8
> > +#define CCU_AXI_HWA_CLK			9
> > +#define CCU_AXI_SRAM_CLK		10
> > +
> >  #endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
> > diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h
> > new file mode 100644
> > index 000000000000..4de5b6bcd433
> > --- /dev/null
> > +++ b/include/dt-bindings/reset/bt1-ccu.h
> > @@ -0,0 +1,23 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > + *
> > + * Baikal-T1 CCU reset indeces.
> > + */
> > +#ifndef __DT_BINDINGS_RESET_BT1_CCU_H
> > +#define __DT_BINDINGS_RESET_BT1_CCU_H
> > +
> > +/* Baikal-T1 AXI-bus CCU Reset indeces. */
> > +#define CCU_AXI_MAIN_RST		0
> > +#define CCU_AXI_DDR_RST			1
> > +#define CCU_AXI_SATA_RST		2
> > +#define CCU_AXI_GMAC0_RST		3
> > +#define CCU_AXI_GMAC1_RST		4
> > +#define CCU_AXI_XGMAC_RST		5
> > +#define CCU_AXI_PCIE_M_RST		6
> > +#define CCU_AXI_PCIE_S_RST		7
> > +#define CCU_AXI_USB_RST			8
> > +#define CCU_AXI_HWA_RST			9
> > +#define CCU_AXI_SRAM_RST		10
> > +
> > +#endif /* __DT_BINDINGS_RESET_BT1_CCU_H */
> > -- 
> > 2.25.1
> > 

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

* Re: [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices CCU bindings
       [not found]   ` <20200310021915.8A0E7803087C@mail.baikalelectronics.ru>
@ 2020-04-05 15:35     ` Sergey Semin
  0 siblings, 0 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-05 15:35 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Mark Rutland, Michael Turquette, Philipp Zabel, Rob Herring,
	Alexey Malahov, Thomas Bogendoerfer, Paul Burton, Ralf Baechle,
	linux-clk, devicetree, linux-kernel

On Mon, Mar 09, 2020 at 07:19:12PM -0700, Stephen Boyd wrote:
> Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:46)
> > diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
> > new file mode 100644
> > index 000000000000..aea09fbafc89
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-sys.yaml
> > @@ -0,0 +1,169 @@
> [..]
> > +  assigned-clock-rates: true
> > +
> > +additionalProperties: false
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - "#clock-cells"
> > +  - clocks
> > +  - clock-names
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/clock/bt1-ccu.h>
> > +
> > +    ccu_sys: ccu_sys@1F04D060 {
> 
> Node name should be clock-controller@1f04d060.

Ok.

> 
> Also, binding looks wrong because that address isn't aligned. Most
> likely it's one hardware block that has many different functionalities
> so splitting it up into different regions isn't doing anything besides
> logically splitting up the register space for software benefits.

As I said in RFC: https://lkml.org/lkml/2020/3/22/393 , CCU (Clock
Control Unit) is part of the Baikal-T1 System Control Module (simply
speaking the system controller). In details why I split the system
controller registers space up, I described in the text. Alternatively I
also suggested there to make the CCU nodes being sub-nodes of the System
Control Module DT node. This would better reflect the hardware blocks,
but instead of having an independent registers MMIO in driver I would
have to use the syscon regmap handler.

As I also said in the RFC, in accordance with the Baikal-T1 CCU documentation
CCU is split up into three blocks: PLLs, AXI-bus clocks and System devices
clocks. That's why the CCU driver expects to find three DT nodes:
CCU PLL (be,bt1-ccu-pll), CCU AXI (be,bt1-ccu-axi) and CCU Sys
(be,bt1-ccu-sys).

> 
> > +      compatible = "be,bt1-ccu-sys";
> > +      reg = <0x1F04D060 0x0A0>,
> > +            <0x1F04D150 0x004>;
> > +      #clock-cells = <1>;
> > +      #reset-cells = <1>;
> > +
> > +      clocks = <&osc25>,
> > +               <&ccu_pll CCU_SATA_PLL>,
> > +               <&ccu_pll CCU_PCIE_PLL>,
> > +               <&ccu_pll CCU_ETH_PLL>;
> > +      clock-names = "ref_clk", "sata_clk", "pcie_clk",
> > +                    "eth_clk";
> > +
> > +      clock-output-names = "sys_sata_ref_clk", "sys_apb_clk",
> > +                           "sys_gmac0_csr_clk", "sys_gmac0_tx_clk",
> > +                           "sys_gmac0_ptp_clk", "sys_gmac1_csr_clk",
> > +                           "sys_gmac1_tx_clk", "sys_gmac1_ptp_clk",
> > +                           "sys_xgmac_ref_clk", "sys_xgmac_ptp_clk",
> > +                           "sys_usb_clk", "sys_pvt_clk",
> > +                           "sys_hwa_clk", "sys_uart_clk",
> > +                           "sys_spi_clk", "sys_i2c1_clk",
> > +                           "sys_i2c2_clk", "sys_gpio_clk",
> > +                           "sys_timer0_clk", "sys_timer1_clk",
> > +                           "sys_timer2_clk", "sys_wdt_clk";
> > +      };
> > +...
> > diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h
> > index 4de5b6bcd433..0bd8fd0edb41 100644
> > --- a/include/dt-bindings/reset/bt1-ccu.h
> > +++ b/include/dt-bindings/reset/bt1-ccu.h
> > @@ -20,4 +20,8 @@
> >  #define CCU_AXI_HWA_RST                        9
> >  #define CCU_AXI_SRAM_RST               10
> >  
> > +/* Baikal-T1 System Devices CCU Reset indeces. */
> 
> indeces is not a word.

Yeah, it was supposed to be "indices". I'll remove this comment anyway.

Regards,
-Sergey

> 
> > +#define CCU_SYS_SATA_REF_RST           0
> > +#define CCU_SYS_APB_RST                        1
> > +

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

* Re: [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver
  2020-03-10 15:30   ` Stephen Boyd
@ 2020-04-07 12:08     ` Sergey Semin
  2020-04-16 19:29       ` Sergey Semin
  2020-04-26  6:16       ` Sergey Semin
  0 siblings, 2 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-07 12:08 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Michael Turquette, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-kernel, linux-clk

On Tue, Mar 10, 2020 at 08:30:15AM -0700, Stephen Boyd wrote:
> Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:47)
> > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > 
> > Baikal-T1 is supposed to be supplied with a high-frequency external
> > oscillator. But in order to create signals suitable for each IP-block
> > embedded into the SoC the oscillator output is primarily connected to
> > a set of CCU PLLs. There are five of them to create clocks for the MIPS
> > P5600 cores, the embedded DDR controller, SATA, Ethernet and PCIe
> > domains. The last three domains though named by the biggest system
> > interfaces in fact include nearly all of the rest SoC peripherals.
> > Each of the PLLs is based on True Circuits TSMC CLN28HPM IP-core with
> > an interface wrapper (so called safe PLL' clocks switcher) to simplify
> > the PLL configuration procedure.
> > 
> > This driver creates the of-based hardware clocks to use them then in
> > the corresponding subsystems. In order to simplify the driver code we
> > split the functionality up into the PLLs clocks operations and hardware
> > clocks declaration/registration procedures. So if CLK_BT1_CCU is
> > defined, then the first part is available in the kernel, while
> > CLK_BT1_CCU_PLL config makes the actual clocks being registered at the
> > time of_clk_init() is called.
> > 
> > Even though the PLLs are based on the same IP-core, they actually may
> > have some differences. In particular, some CCU PLLs supports the output
> > clock change without gating them (like CPU or PCIe PLLs), while the
> > others don't, some CCU PLLs are critical and aren't supposed to be
> > gated. In order to cover all of these cases the hardware clocks driver
> > is designed with a info-descriptor pattern. So there are special static
> > descriptors declared for each PLL, which is then used to create a
> > hardware clock with proper operations. Additionally debugfs-files are
> > provided for each PLL' field to make sure the implemented
> > rate-PLLs-dividers calculation algorithm is correct.
> > 
> > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > Cc: Paul Burton <paulburton@kernel.org>
> > Cc: Ralf Baechle <ralf@linux-mips.org>
> 
> > diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig
> > new file mode 100644
> > index 000000000000..0e2fc86f3ab8
> > --- /dev/null
> > +++ b/drivers/clk/baikal-t1/Kconfig
> > @@ -0,0 +1,35 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +config CLK_BAIKAL_T1
> > +       bool "Baikal-T1 Clocks Control Unit interface"
> > +       depends on (MIPS_BAIKAL_T1 || COMPILE_TEST) && OF
> 
> Does it actually depend on OF for any compilation?

Hm, good question. AFAICS it doesn't. The compilation should work for
both of these configs. While AFAIU it is meaningful to have
(MIPS_BAIKAL_T1 && OF) only instead of making the compile test depending
on the OF as well.

On the other hand MIPS_BAIKAL_T1 indirectly selects OF. I knew about
this reverse dependency, but still preferred to signify that the driver
relies on the OF functionality to properly work. What do you think would
be the best solution in this case:
1) just drop OF, which would mean to have (MIPS_BAIKAL_T1 ||
COMPILE_TEST),
2) redefine the statement to (MIPS_BAIKAL_T1 && OF) || COMPILE_TEST ?

BTW AFAICS OF API is dummy defined if no CONFIG_OF is enabled. So there
is no need in having OF-dependency for any compile-test usecase. Right?
Do you know any case when it's required? For instance, I've found the
similar OF-based conditional statement for CLK_QORIQ,
COMMON_CLK_FIXED_MMIO and COMMON_CLK_KEYSTONE. Do you think these
configs are wrong and have to be fixed by removing the "&& OF" statement
as well?

> 
> > +       default y
> 
> Please no default y. Maybe default MIPS_BAIKAL_T1?

Do you mean that in case if COMPILE_TEST is enabled only, the
drivers below shouldn't be available by default? If so then ok.
I'll use MIPS_BAIKAL_T1 here. If no, then "default y" would be more
appropriate.

> 
> > +       help
> > +         Clocks Control Unit is the core of Baikal-T1 SoC responsible for the
> > +         chip subsystems clocking and resetting. It consists of multiple
> > +         global clock domains, which can be reset by means of the CCU control
> > +         registers. These domains and devices placed in them are fed with
> > +         clocks generated by a hierarchy of PLLs, configurable and fixed
> > +         dividers. In addition CCU exposes several unrelated functional blocks
> > +         like irqless Designware i2c controller with indirectly accessed
> > +         registers, AXI bus errors detector, DW PCIe controller PM/clocks/reset
> > +         manager, etc.
> > +
> > +         This driver provides a set of functions to create the kernel clock
> > +         devices of Baikal-T1 PLLs and dividers, and to manipulate the reset
> > +         signals of the SoC.
> > +
> > +if CLK_BAIKAL_T1
> > +
> > +config CLK_BT1_CCU_PLL
> > +       bool "Baikal-T1 CCU PLLs support"
> > +       default y
> 
> default MIPS_BAIKAL_T1?

see the comment above.

> 
> > +       help
> > +         Enable this to support the PLLs embedded into the Baikal-T1 SoCs.
> > +         These are five PLLs placed at the root of the clocks hierarchy,
> > +         right after the external reference osciallator (normally of 25MHz).
> > +         They are used to generate a high frequency signals, which are
> 
> s/generate a high/generate high/

Ok.

> 
> > +         either directly wired to the consumers (like CPUs, DDR) or passed
> > +         over the clock dividers to be only then used as an individual
> > +         reference clocks of a target device.
> 
> s/clocks/clock/

Ok.

> 
> > +
> > +endif
> > diff --git a/drivers/clk/baikal-t1/ccu-pll.c b/drivers/clk/baikal-t1/ccu-pll.c
> > new file mode 100644
> > index 000000000000..f2087a80b64d
> > --- /dev/null
> > +++ b/drivers/clk/baikal-t1/ccu-pll.c
> > @@ -0,0 +1,474 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > + *
> > + * Authors:
> > + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> > + *
> > + * Baikal-T1 CCU PLL interface driver.
> > + */
> > +
> > +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/printk.h>
> > +#include <linux/limits.h>
> > +#include <linux/bits.h>
> > +#include <linux/slab.h>
> > +#include <linux/clk-provider.h>
> > +#include <linux/of.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/delay.h>
> > +#include <linux/time64.h>
> > +#include <linux/rational.h>
> > +#include <linux/debugfs.h>
> > +
> > +#include "ccu-pll.h"
> > +#include "common.h"
> > +
> > +#define CCU_PLL_CTL                    0x00
> > +#define CCU_PLL_CTL_EN                 BIT(0)
> > +#define CCU_PLL_CTL_RST                        BIT(1)
> > +#define CCU_PLL_CTL_CLKR_FLD           2
> > +#define CCU_PLL_CTL_CLKR_MASK          GENMASK(7, CCU_PLL_CTL_CLKR_FLD)
> > +#define CCU_PLL_CTL_CLKF_FLD           8
> > +#define CCU_PLL_CTL_CLKF_MASK          GENMASK(20, CCU_PLL_CTL_CLKF_FLD)
> > +#define CCU_PLL_CTL_CLKOD_FLD          21
> > +#define CCU_PLL_CTL_CLKOD_MASK         GENMASK(24, CCU_PLL_CTL_CLKOD_FLD)
> > +#define CCU_PLL_CTL_BYPASS             BIT(30)
> > +#define CCU_PLL_CTL_LOCK               BIT(31)
> > +#define CCU_PLL_CTL1                   0x04
> > +#define CCU_PLL_CTL1_BWADJ_FLD         3
> > +#define CCU_PLL_CTL1_BWADJ_MASK                GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD)
> > +
> > +#define CCU_PLL_RST_DELAY_US           5
> > +#define CCU_PLL_LOCK_DELAY_US(_ref_rate, _nr) ({       \
> > +       uint64_t _n = 500ULL * (_nr) * USEC_PER_SEC;    \
> > +       do_div(_n, _ref_rate);                          \
> > +       _n;                                             \
> > +})
> 
> Can this be a static inline function? We get to learn what the types are
> then.

Ok I'll convert it to the explicit static inline version cause' you asked it.
Personally I don't see the necessity of this here. The arithmetics is
converted to u64, which is more than enough to cover any possible
nr-value.

> 
> > +#define CCU_PLL_LOCK_CHECK_RETRIES     50
> > +
> > +#define CCU_PLL_NR_MAX \
> > +       ((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1)
> > +#define CCU_PLL_NF_MAX \
> > +       ((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1)
> > +#define CCU_PLL_OD_MAX \
> > +       ((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1)
> > +#define CCU_PLL_NB_MAX \
> > +       ((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1)
> > +#define CCU_PLL_FDIV_MIN               427000UL
> > +#define CCU_PLL_FDIV_MAX               3500000000UL
> > +#define CCU_PLL_FOUT_MIN               200000000UL
> > +#define CCU_PLL_FOUT_MAX               2500000000UL
> > +#define CCU_PLL_FVCO_MIN               700000000UL
> > +#define CCU_PLL_FVCO_MAX               3500000000UL
> > +#define CCU_PLL_CLKOD_FACTOR           2
> > +
> > +#define CCU_PLL_CALC_FREQ(_ref_rate, _nr, _nf, _od) \
> > +       ((_ref_rate) / (_nr) * (_nf) / (_od))
> 
> Static inline function please. Does this need to be 64-bit math?

Ok for the same reason. In our platform 32-bit data is enough here.
Though you are right. Some NF may overflow the unsigned long ref_rate
data. I'll add the 64-bit arithmetics in this function too.

BTW I thought about 64-bits here in first place, but then I decided it
may cause the performance drop in ccu_pll_calc_factors() and in
ccu_pll_recalc_rate() so I left the unsigned long calc here.

> 
> > +
> > +static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk,
> > +                        unsigned long nr)
> > +{
> > +       unsigned long ud;
> > +       int count;
> > +       u32 val;
> > +
> > +       ud = CCU_PLL_LOCK_DELAY_US(ref_clk, nr);
> > +
> > +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_RST, CCU_PLL_CTL_RST);
> > +
> > +       count = CCU_PLL_LOCK_CHECK_RETRIES;
> > +       do {
> > +               udelay(ud);
> > +               val = ccu_read(pll->regs + CCU_PLL_CTL);
> > +       } while (!(val & CCU_PLL_CTL_LOCK) && --count);
> > +
> > +       return (val & CCU_PLL_CTL_LOCK) ? 0 : -ETIMEDOUT;
> 
> Looks like readl_poll_timeout().

Nice catch. readl_poll_timeout() perfectly fits here. Though the atomic
version is more appropriate, since the delays are normally small and the
method is executed in the atomic context.

> 
> > +}
> > +
> > +static int ccu_pll_enable(struct clk_hw *hw)
> > +{
> > +       struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > +       unsigned long flags;
> > +       int ret;
> > +       u32 val;
> > +
> > +       if (!parent_hw) {
> > +               pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw));
> > +               return -EINVAL;
> > +       }
> > +
> > +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> > +       if (val & CCU_PLL_CTL_EN)
> > +               return 0;
> > +
> > +       spin_lock_irqsave(&pll->regs_lock, flags);
> > +       ccu_write(pll->regs + CCU_PLL_CTL, val | CCU_PLL_CTL_EN);
> > +       ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw),
> > +                           CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1);
> > +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> > +       if (ret)
> > +               pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
> > +
> > +       return ret;
> > +}
> > +
> > +static void ccu_pll_disable(struct clk_hw *hw)
> > +{
> > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > +       unsigned long flags;
> > +
> > +       spin_lock_irqsave(&pll->regs_lock, flags);
> > +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_EN, 0);
> > +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> > +}
> > +
> > +static int ccu_pll_is_enabled(struct clk_hw *hw)
> > +{
> > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > +
> > +       return !!(ccu_read(pll->regs + CCU_PLL_CTL) & CCU_PLL_CTL_EN);
> > +}
> > +
> > +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
> > +                                        unsigned long parent_rate)
> > +{
> > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > +       unsigned long nr, nf, od;
> > +       u32 val;
> > +
> > +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> > +       nr = CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1;
> 
> We have FIELD_GET macros for this. Please use them instead of creating
> your own.

Right. I'll use it instead.

> 
> > +       nf = CCU_GET_FLD(CCU_PLL_CTL_CLKF, val) + 1;
> > +       od = CCU_GET_FLD(CCU_PLL_CTL_CLKOD, val) + 1;
> > +
> > +       return CCU_PLL_CALC_FREQ(parent_rate, nr, nf, od);
> > +}
> > +
> > +static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate,
> > +                                unsigned long *nr, unsigned long *nf,
> > +                                unsigned long *od)
> > +{
> > +       unsigned long err, freq, min_err = ULONG_MAX;
> > +       unsigned long num, denom, n1, d1, nri;
> > +       unsigned long nr_max, nf_max, od_max;
> > +
> > +       /*
> > +        * Make sure PLL is working with valid input signal (Fdiv). If
> > +        * you want to speed the function up just reduce CCU_PLL_NR_MAX.
> > +        * This will cause a worse approximation though.
> > +        */
> > +       nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1;
> > +       nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX);
> > +
> > +       /*
> > +        * Find a closest [nr;nf;od] vector taking into account the
> > +        * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is
> > +        * either 1 or even number within the acceptable range (alas 1s
> > +        * is also excluded by the next loop).
> > +        */
> > +       for (; nri <= nr_max; ++nri) {
> > +               /* Use Od factor to fulfill the limitation 2). */
> > +               num = CCU_PLL_CLKOD_FACTOR * rate;
> > +               denom = parent_rate / nri;
> > +
> > +               /*
> > +                * Make sure Fvco is within the acceptable range to fulfill
> > +                * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value
> > +                * the actual upper limit is also divided by that factor.
> > +                * It's not big problem for us since practically there is no
> > +                * need in clocks with that high frequency.
> > +                */
> > +               nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX);
> > +               od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR;
> > +
> > +               /*
> > +                * Bypass the out-of-bound values, which can't be properly
> > +                * handled by the rational fraction approximation algorithm.
> > +                */
> > +               if (num / denom >= nf_max) {
> > +                       n1 = nf_max;
> > +                       d1 = 1;
> > +               } else if (denom / num >= od_max) {
> > +                       n1 = 1;
> > +                       d1 = od_max;
> > +               } else {
> > +                       rational_best_approximation(num, denom, nf_max, od_max,
> > +                                                   &n1, &d1);
> > +               }
> > +
> > +               /* Select the best approximation of the target rate. */
> > +               freq = (((parent_rate / nri) * n1) / d1);
> 
> Please drop extra parenthesis.

Ok. Thanks for noticing this. I'll replace it with ccu_pll_calc_freq() function.

> 
> > +               err = abs((int64_t)freq - num);
> > +               if (err < min_err) {
> > +                       min_err = err;
> > +                       *nr = nri;
> > +                       *nf = n1;
> > +                       *od = CCU_PLL_CLKOD_FACTOR * d1;
> > +               }
> > +       }
> > +}
> > +
...
> > +
> > +#ifdef CONFIG_DEBUG_FS
> > +
> > +#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask)                     \
> > +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> > +{                                                                      \
> > +       struct ccu_pll *pll = priv;                                     \
> > +                                                                       \
> > +       *val = !!(ccu_read(pll->regs + (_reg)) & (_mask));              \
> > +                                                                       \
> > +       return 0;                                                       \
> > +}                                                                      \
> > +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> > +{                                                                      \
> > +       struct ccu_pll *pll = priv;                                     \
> > +       unsigned long flags;                                            \
> > +                                                                       \
> > +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> > +       ccu_update(pll->regs + (_reg), (_mask), val ? (_mask) : 0);     \
> > +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> > +                                                                       \
> > +       return 0;                                                       \
> > +}                                                                      \
> > +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> > +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> > +
> > +#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _fld, _min, _max)          \
> > +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> > +{                                                                      \
> > +       struct ccu_pll *pll = priv;                                     \
> > +       u32 data;                                                       \
> > +                                                                       \
> > +       data = ccu_read(pll->regs + (_reg));                            \
> > +       *val = CCU_GET_FLD(_fld, data) + 1;                             \
> > +                                                                       \
> > +       return 0;                                                       \
> > +}                                                                      \
> > +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> > +{                                                                      \
> > +       struct ccu_pll *pll = priv;                                     \
> > +       unsigned long flags;                                            \
> > +       u32 data;                                                       \
> > +                                                                       \
> > +       val = clamp_t(u64, val, _min, _max);                            \
> > +       data = CCU_SET_FLD(_fld, 0, val - 1);                           \
> > +                                                                       \
> > +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> > +       ccu_update(pll->regs + (_reg), _fld ## _MASK, data);            \
> > +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> > +                                                                       \
> > +       return 0;                                                       \
> > +}                                                                      \
> > +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> > +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> > +
> > +CCU_PLL_DBGFS_BIT_ATTR(en, CCU_PLL_CTL, CCU_PLL_CTL_EN);
> > +CCU_PLL_DBGFS_BIT_ATTR(rst, CCU_PLL_CTL, CCU_PLL_CTL_RST);
> > +CCU_PLL_DBGFS_FLD_ATTR(nr, CCU_PLL_CTL, CCU_PLL_CTL_CLKR, 1, CCU_PLL_NR_MAX);
> > +CCU_PLL_DBGFS_FLD_ATTR(nf, CCU_PLL_CTL, CCU_PLL_CTL_CLKF, 1, CCU_PLL_NF_MAX);
> > +CCU_PLL_DBGFS_FLD_ATTR(od, CCU_PLL_CTL, CCU_PLL_CTL_CLKOD, 1, CCU_PLL_OD_MAX);
> > +CCU_PLL_DBGFS_BIT_ATTR(bypass, CCU_PLL_CTL, CCU_PLL_CTL_BYPASS);
> > +CCU_PLL_DBGFS_BIT_ATTR(lock, CCU_PLL_CTL, CCU_PLL_CTL_LOCK);
> > +CCU_PLL_DBGFS_FLD_ATTR(nb, CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ, 1, CCU_PLL_NB_MAX);
> > +
> > +static const struct debugfs_reg32 ccu_pll_dbgfs_regs[] = {
> > +       CCU_DBGFS_REG("ctl", CCU_PLL_CTL),
> > +       CCU_DBGFS_REG("ctl1", CCU_PLL_CTL1)
> > +};
> > +
> > +static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
> > +{
> > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > +       struct debugfs_regset32 *regset;
> > +
> > +       regset = kzalloc(sizeof(*regset), GFP_KERNEL);
> > +       if (!regset)
> > +               return;
> > +
> > +       regset->regs = ccu_pll_dbgfs_regs;
> > +       regset->nregs = ARRAY_SIZE(ccu_pll_dbgfs_regs);
> > +       regset->base = pll->regs;
> > +       debugfs_create_regset32("registers", 0400, dentry, regset);
> > +
> > +       debugfs_create_file_unsafe("en", 0600, dentry, pll,
> 
> Why unsafe?

I don't need full proxy file-operations in this case. Moreover since all
fops are defined by means of DEFINE_DEBUGFS_ATTRIBUTE() the set/get
callbacks used will be automatically protected against the corresponding
files removal. See the comment to the debugfs_create_file_unsafe() for
details and commit e7e6198c6056 ("clk: tegra: dfll: Fix debugfs_simple_attr.cocci
warnings") as an example of why unsafe is appropriate here. BTW The commit
has got your SoB.

> 
> > +                                  &ccu_pll_dbgfs_en_fops);
> > +       debugfs_create_file_unsafe("rst", 0200, dentry, pll,
> > +                                  &ccu_pll_dbgfs_rst_fops);
> > +       debugfs_create_file_unsafe("nr", 0600, dentry, pll,
> > +                                  &ccu_pll_dbgfs_nr_fops);
> > +       debugfs_create_file_unsafe("nf", 0600, dentry, pll,
> > +                                  &ccu_pll_dbgfs_nf_fops);
> > +       debugfs_create_file_unsafe("od", 0600, dentry, pll,
> > +                                  &ccu_pll_dbgfs_od_fops);
> > +       debugfs_create_file_unsafe("bypass", 0600, dentry, pll,
> > +                                  &ccu_pll_dbgfs_bypass_fops);
> > +       debugfs_create_file_unsafe("lock", 0400, dentry, pll,
> > +                                  &ccu_pll_dbgfs_lock_fops);
> > +       debugfs_create_file_unsafe("nb", 0600, dentry, pll,
> > +                                  &ccu_pll_dbgfs_nb_fops);
> > +}
> 
> Is there any usage of these outside of development of this driver? I'd
> rather not see us expose blanket register write access with code in
> debugfs. If you're interested in poking register why not use /dev/mem?

These parameters are indeed mostly useful in the driver development. But
they can be useful to diagnose the peripheral device problems like data
corruption or weird clock consumers behaviour. Moreover some of the
parameters aren't utilized by the driver like bypass and nb, but might
be theoretically useful in fixing the problems.

Anyway I do understand your concern regarding write access to these
fields. What about if I implemented the same approach as the one used in
drivers/clk/clk.c? By default the fields are exported in RO-mode.
If a developer needs to debug a clock-based problem one will need to
recompile the driver with #define CLOCK_ALLOW_WRITE_DEBUGFS macro
specified. What do you think?

> 
> > +
> > +#else /* !CONFIG_DEBUG_FS */
> > +
> > +#define ccu_pll_debug_init NULL
> > +
> > +#endif /* !CONFIG_DEBUG_FS */
> > +
> > +static const struct clk_ops ccu_pll_gate_to_set_ops = {
> > +       .enable = ccu_pll_enable,
> > +       .disable = ccu_pll_disable,
> > +       .is_enabled = ccu_pll_is_enabled,
> > +       .recalc_rate = ccu_pll_recalc_rate,
> > +       .round_rate = ccu_pll_round_rate,
> > +       .set_rate = ccu_pll_set_rate_norst,
> > +       .debug_init = ccu_pll_debug_init
> > +};
> > +
> > +static const struct clk_ops ccu_pll_straight_set_ops = {
> > +       .enable = ccu_pll_enable,
> > +       .disable = ccu_pll_disable,
> > +       .is_enabled = ccu_pll_is_enabled,
> > +       .recalc_rate = ccu_pll_recalc_rate,
> > +       .round_rate = ccu_pll_round_rate,
> > +       .set_rate = ccu_pll_set_rate_reset,
> > +       .debug_init = ccu_pll_debug_init
> > +};
> > +
> > +struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *pll_init)
> 
> Can pll_init be const?

Can't believe I missed this. Yes, it can. Thanks.

> 
> > +{
> > +       struct clk_parent_data parent_data = {0};
> > +       struct clk_init_data hw_init = {0};
> > +       struct ccu_pll *pll;
> > +       int ret;
> > +
> > +       if (!pll_init)
> > +               return ERR_PTR(-EINVAL);
> > +
> > +       pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> > +       if (!pll)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       pll->hw.init = &hw_init;
> > +       pll->id = pll_init->id;
> > +       pll->regs = pll_init->regs;
> > +       spin_lock_init(&pll->regs_lock);
> > +
> > +       hw_init.name = pll_init->name;
> > +       hw_init.flags = CLK_IGNORE_UNUSED;
> 
> Why ignore unused? Please have a comment.

Ok. I thought it was obvious, since the flag naming said by itself.
These PLLs are supposed to be left enabled even if they are unused by
any of the drivers. System bootloader makes sure that required clocks
are enabled by default like CPU and DDR PLLs. So we don't want to let
the clock subsystem to disable the PLLs if they are left unused.

> 
> > +
> > +       if (pll_init->flags & CCU_PLL_GATE_TO_SET) {
> > +               hw_init.flags |= CLK_SET_RATE_GATE;
> > +               hw_init.ops = &ccu_pll_gate_to_set_ops;
> > +       } else {
> > +               hw_init.ops = &ccu_pll_straight_set_ops;
> > +       }
> > +
> > +       if (pll_init->flags & CCU_PLL_CRITICAL)
> > +               hw_init.flags |= CLK_IS_CRITICAL;
> > +
> > +       if (!pll_init->parent_name) {
> > +               ret = -EINVAL;
> > +               goto err_free_pll;
> > +       }
> > +       parent_data.fw_name = pll_init->parent_name;
> > +       hw_init.parent_data = &parent_data;
> > +       hw_init.num_parents = 1;
> > +
> > +       ret = of_clk_hw_register(pll_init->np, &pll->hw);
> > +       if (ret)
> > +               goto err_free_pll;
> > +
> > +       return pll;
> > +
> > +err_free_pll:
> > +       kfree(pll);
> > +
> > +       return ERR_PTR(ret);
> > +}
> > +
> > +void ccu_pll_hw_unregister(struct ccu_pll *pll)
> > +{
> > +       if (!pll)
> > +               return;
> 
> That would be quite bad. Just let that blow up if so.

Ok.

> 
> > +
> > +       clk_hw_unregister(&pll->hw);
> > +
> > +       kfree(pll);
> > +}
> > diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h
> > new file mode 100644
> > index 000000000000..6921516311fb
> > --- /dev/null
> > +++ b/drivers/clk/baikal-t1/ccu-pll.h
> > @@ -0,0 +1,73 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > + *
> > + * Baikal-T1 CCU PLL interface driver.
> > + */
> > +#ifndef __CLK_BT1_CCU_PLL_H__
> > +#define __CLK_BT1_CCU_PLL_H__
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/bits.h>
> > +#include <linux/of.h>
> > +
> > +/*
> > + * CCU PLL private flags.
> > + * @CCU_PLL_GATE_TO_SET: Some PLLs output clock can't be changed on-the-fly,
> > + *                      so according to documentation they need to be gated
> > + *                      first.
> > + * @CCU_PLL_CRITICAL: Even though there is a way to switch any PLL off, there
> > + *                   might be some, which shouldn't be in any case.
> > + */
> > +#define CCU_PLL_GATE_TO_SET            BIT(0)
> > +#define CCU_PLL_CRITICAL               BIT(1)
> > +
> > +/*
> > + * struct ccu_pll_init_data - CCU PLL initialization data.
> > + * @id: Clock private identifier.
> > + * @regs: PLL registers base address.
> > + * @name: Clocks name.
> > + * @parent_name: Clocks parent name in a fw node.
> > + * @np: Pointer to the node describing the CCU PLLs.
> > + * @flags: PLL private flags.
> > + */
> > +struct ccu_pll_init_data {
> > +       unsigned int id;
> > +       void __iomem *regs;
> > +       const char *name;
> > +       const char *parent_name;
> > +       struct device_node *np;
> > +       unsigned long flags;
> > +};
> > +
> > +/*
> > + * struct ccu_pll - CCU PLL descriptor.
> 
> Please drop the full stop on title

Ok.

> 
> > + * @hw: clk_hw of the PLL.
> > + * @id: Clock private identifier.
> > + * @regs: PLL registers base address.
> > + * @regs_lock: The registers exclusive access spin-lock.
> > + */
> > +struct ccu_pll {
> > +       struct clk_hw hw;
> > +       unsigned int id;
> > +       void __iomem *regs;
> > +       spinlock_t regs_lock;
> > +};
> > +#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw)
> > +
> > +static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll)
> > +{
> > +       return pll ? &pll->hw : NULL;
> > +}
> > +
> > +static inline unsigned int ccu_pll_get_clk_id(struct ccu_pll *pll)
> > +{
> > +       return pll ? pll->id : -1;
> 
> It's unsigned return value, but return -1? Can this be inlined in the
> one call site and if !pll just continue instead of having to turn it
> into a negative value returned through an unsigned int?

Right, unsigned int, but return -1, which was supposed to be an invalid
value. (unsigned int)-1 => 0xFFFFFFFF is good portable way of setting
FFs to any int.

Anyway your solution seems better. I'll use it instead.

> 
> > +}
> > +
> > +extern struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *init);
> > +
> > +extern void ccu_pll_hw_unregister(struct ccu_pll *pll);
> > +
> > +#endif /* __CLK_BT1_CCU_PLL_H__ */
> > diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c
> > new file mode 100644
> > index 000000000000..990f0a4a12f9
> > --- /dev/null
> > +++ b/drivers/clk/baikal-t1/clk-ccu-pll.c
> > @@ -0,0 +1,217 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > + *
> > + * Authors:
> > + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> > + *
> > + * Baikal-T1 CCU PLL clocks driver.
> > + */
> > +
> > +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/printk.h>
> > +#include <linux/slab.h>
> > +#include <linux/clk-provider.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/ioport.h>
> > +
> > +#include <dt-bindings/clock/bt1-ccu.h>
> > +
> > +#include "ccu-pll.h"
> > +
> > +#define CCU_CPU_PLL_BASE               0x000
> > +#define CCU_SATA_PLL_BASE              0x008
> > +#define CCU_DDR_PLL_BASE               0x010
> > +#define CCU_PCIE_PLL_BASE              0x018
> > +#define CCU_ETH_PLL_BASE               0x020
> > +
> > +#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags)        \
> > +       {                                               \
> > +               .id = _id,                              \
> > +               .name = _name,                          \
> > +               .parent_name = _pname,                  \
> > +               .base = _base,                          \
> > +               .flags = _flags                         \
> > +       }
> > +
> > +#define CCU_PLL_NUM                    ARRAY_SIZE(pll_info)
> > +
> > +struct ccu_pll_info {
> > +       unsigned int id;
> > +       const char *name;
> > +       const char *parent_name;
> > +       unsigned int base;
> > +       unsigned long flags;
> > +};
> > +
> > +static const struct ccu_pll_info pll_info[] = {
> > +       CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
> > +                    CCU_PLL_CRITICAL),
> > +       CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
> > +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> 
> Please comment all CLK_IS_CRITICAL usage

Ok. CPU and DDR PLLs are sources of CPU cores and DDR controller reference clocks,
which obviously shouldn't be ever gated. SATA and PCIe PLLs are the parents of
APB-bus and DDR controller AXI-bus clocks, which if gated would cause
the system being unusable too.

> and don't make your own version
> of the same flags in the common clk framework. Just use the ones in the
> framework.

This was my first design of the clock flags, but then I had to create a
set of custom flags for the CCU dividers driver. That time I turned to
be at a fork either to have two sets of flags: common clock and custom
ones, or a single set of custom flags, some of which then I would
translate to the common clock flags. So I've chosen the latter solution.
I see you suggest the former. Since I was in doubt which one would be
better, I'll implement yours then.

> 
> > +       CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
> > +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> > +       CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
> > +                    CCU_PLL_CRITICAL),
> > +       CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
> > +                    CCU_PLL_GATE_TO_SET)
> > +};
> > +
> > +struct ccu_pll_data {
> > +       struct device_node *np;
> > +       void __iomem *regs;
> > +       struct ccu_pll *plls[CCU_PLL_NUM];
> > +};
> > +
> > +static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
> > +                                        unsigned int clk_id)
> > +{
> > +       struct ccu_pll *pll;
> > +       int idx;
> > +
> > +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> > +               pll = data->plls[idx];
> > +               if (clk_id == ccu_pll_get_clk_id(pll))
> > +                       return pll;
> > +       }
> > +
> > +       return ERR_PTR(-EINVAL);
> > +}
> > +
> > +static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
> > +{
> > +       struct ccu_pll_data *data;
> > +
> > +       data = kzalloc(sizeof(*data), GFP_KERNEL);
> > +       if (!data)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       data->np = np;
> > +
> > +       return data;
> > +}
> > +
> > +static void ccu_pll_free_data(struct ccu_pll_data *data)
> > +{
> > +       kfree(data);
> > +}
> > +
> > +static int ccu_pll_request_regs(struct ccu_pll_data *data)
> > +{
> > +       data->regs = of_io_request_and_map(data->np, 0,
> > +                                          of_node_full_name(data->np));
> > +       if (IS_ERR(data->regs)) {
> > +               pr_err("Failed to request PLLs '%s' regs\n",
> > +                       of_node_full_name(data->np));
> > +               return PTR_ERR(data->regs);
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static void ccu_pll_release_regs(struct ccu_pll_data *data)
> > +{
> > +       struct resource res;
> > +
> > +       iounmap(data->regs);
> > +
> > +       /* Try to release the resource as well. */
> > +       if (of_address_to_resource(data->np, 0, &res))
> > +               return;
> > +
> > +       (void)release_mem_region(res.start, resource_size(&res));
> > +}
> 
> Instead of having these three functions please inline them at the one call
> site.

This time I disagree. Inlining them won't gain much of the performance
benefit, while combination of weakly coherent code into a single function
will cause worse readability. In my drivers I prefer separating unrelated
or weakly related functionality from each other and creating a set of functions
with coherent code (in this case it's memory allocation for the driver private
data and registers mapping, which are only related in the order of execution).
By this approach I improve the driver code readability making it better
structured. So instead of having a pile of weakly coherent code in the probe
I have a set of work methods and their antagonists (if necessary),
which are called in the required order. Similar design I normally
prefer in callbacks, event handlers, etc, if coherent code can't be
embedded into them.

Moreover I don't see any requirement by which I would have to rearrange the
code in this case, while following my way will perfectly fit to the
kernel coding style described in Documentation/process/coding-style.rst as
"Functions should be short and sweet, and do just one thing." Sorry, I
would prefer to leave this part of the driver being structured as is.

Anyway, this part of the driver will be changed in v2 due to implementing
an alternative approach of Baikal-T1 System Controller DT node design I've
described in RFC: https://lkml.org/lkml/2020/3/22/393 . According to it I
will use a syscon_node_to_regmap(parent) method to get the registers map
from the parental node instead of requesting a dedicated registers range.

> 
> > +
> > +static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
> > +                                           void *priv)
> > +{
> > +       struct ccu_pll_data *data = priv;
> > +       struct ccu_pll *pll;
> > +       unsigned int clk_id;
> > +
> > +       clk_id = clkspec->args[0];
> > +       pll = ccu_pll_find_desc(data, clk_id);
> > +       if (IS_ERR(pll)) {
> > +               pr_info("Invalid PLL clock ID %d specified\n", clk_id);
> > +               return ERR_CAST(pll);
> > +       }
> > +
> > +       return ccu_pll_get_clk_hw(pll);
> > +}
> > +
> > +static int ccu_pll_clk_register(struct ccu_pll_data *data)
> > +{
> > +       int idx, ret;
> > +
> > +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> > +               const struct ccu_pll_info *info = &pll_info[idx];
> > +               struct ccu_pll_init_data init = {0};
> > +
> > +               init.id = info->id;
> > +               init.regs = data->regs + info->base;
> > +               init.parent_name = info->parent_name;
> > +               init.np = data->np;
> > +               init.flags = info->flags;
> > +
> > +               ret = of_property_read_string_index(data->np,
> > +                       "clock-output-names", init.id, &init.name);
> 
> Do you need this property ever? It would be nice to not have it if
> possible.

It isn't needed other than to provide a custom output clock names for the
predefined set of clocks. I've seen it has been used for this purpose in
many drivers of the common clk subsystem. Is this enough to have it
supported in the driver or should I remove it anyway?

> 
> > +               if (ret)
> > +                       init.name = info->name;
> > +
> > +               data->plls[idx] = ccu_pll_hw_register(&init);
> > +               if (IS_ERR(data->plls[idx])) {
> > +                       ret = PTR_ERR(data->plls[idx]);
> > +                       pr_err("Couldn't register PLL hw '%s'\n",
> > +                               init.name);
> > +                       goto err_hw_unregister;
> > +               }
> > +       }
> > +
> > +       ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
> > +       if (ret) {
> > +               pr_err("Couldn't register PLL provider of '%s'\n",
> > +                       of_node_full_name(data->np));
> > +               goto err_hw_unregister;
> > +       }
> > +
> > +       return 0;
> > +
> > +err_hw_unregister:
> > +       for (--idx; idx >= 0; --idx)
> > +               ccu_pll_hw_unregister(data->plls[idx]);
> > +
> > +       return ret;
> > +}
> > +
> > +static __init void ccu_pll_init(struct device_node *np)
> > +{
> > +       struct ccu_pll_data *data;
> > +       int ret;
> > +
> > +       data = ccu_pll_create_data(np);
> > +       if (IS_ERR(data))
> > +               return;
> > +
> > +       ret = ccu_pll_request_regs(data);
> > +       if (ret)
> > +               goto err_free_data;
> > +
> > +       ret = ccu_pll_clk_register(data);
> > +       if (ret)
> > +               goto err_release_regs;
> > +
> > +       pr_info("CCU CPU/SATA/DDR/PCIe/Ethernet PLLs are initialized\n");
> 
> Please don't have I'm alive probe messages at info level. Just slows
> down boot for developer comfort.

Ok.

> 
> > +
> > +       return;
> > +
> > +err_release_regs:
> > +       ccu_pll_release_regs(data);
> > +
> > +err_free_data:
> > +       ccu_pll_free_data(data);
> > +}
> > +CLK_OF_DECLARE(ccu_pll, "be,bt1-ccu-pll", ccu_pll_init);
> 
> Any reason this can't be a platform device driver?

I thought it was normal to have the clock devices available as early
as possible. Especially when it comes to the system-wide PLLs and dividers.
So the question is kind of surprising for me.

Anyway in my case the platform-specific code is using the CPU PLLs rate
to get the  MIPS count/compare timer reference clock. At least due to this
the PLL clock providers must be available at the system initialization stage.
Platform device probe stage wouldn't work for us here.

Moreover we can't rely on the order of the devices probe procedure, because
the CCU PLLs and CCU dividers will be sub-nodes of the Baikal-T1 System Controller,
which will be a sub-node of the APB-bus node consuming the corresponding CCU
divider clock. The CCU PLL and divider clocks are utilized literally by each
device in the system, so there might be too many PROBE_DEFER errors before we get
to the clock devices binding with drivers.

To sum up I'd leave both PLL, AXI-bus and System device dividers
clocks being populated in the init stage by means of the CLK_OF_DECLARE
macro.

> 
> > diff --git a/drivers/clk/baikal-t1/common.h b/drivers/clk/baikal-t1/common.h
> > new file mode 100644
> > index 000000000000..07c8d67f5275
> > --- /dev/null
> > +++ b/drivers/clk/baikal-t1/common.h
> > @@ -0,0 +1,44 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > + *
> > + * Baikal-T1 CCU common methods.
> > + */
> > +#ifndef __CLK_BT1_COMMON_H__
> > +#define __CLK_BT1_COMMON_H__
> > +
> > +#include <linux/debugfs.h>
> > +#include <linux/io.h>
> > +
> > +#define CCU_GET_FLD(_name, _data) \
> > +       (((u32)(_data) & _name ## _MASK) >> _name ## _FLD)
> > +
> > +#define CCU_SET_FLD(_name, _data, _val) \
> > +       (((u32)(_data) & ~_name ## _MASK) | \
> > +       (((u32)(_val) << _name ## _FLD) & _name ## _MASK))
> > +
> > +#define CCU_DBGFS_REG(_name, _off)     \
> > +{                                      \
> > +       .name = _name,                  \
> > +       .offset = _off                  \
> > +}
> > +
> > +static inline u32 ccu_read(void __iomem *reg)
> > +{
> > +       return readl(reg);
> > +}
> > +
> > +static inline void ccu_write(void __iomem *reg, u32 data)
> > +{
> > +       writel(data, reg);
> > +}
> > +
> > +static inline void ccu_update(void __iomem *reg, u32 mask, u32 data)
> > +{
> > +       u32 old;
> > +
> > +       old = readl_relaxed(reg);
> > +       writel((old & ~mask) | (data & mask), reg);
> > +}
> 
> Please just write the code in line as readl/writel/etc. This whole file
> doesn't look necessary.

Agreed.

---

Stephen, thank you very much for the very thorough review. v2 will be
sent shortly after I get answers to a few question I've asked in this
message, after I fix the issues you raised here and get to refactor
the System Controller DT node design described in the RFC
(https://lkml.org/lkml/2020/3/22/393). Should you have any comments
regarding it please reply right to the RFC. You must have received a
copy in your email.

I'll also do my best to take into account the fixes suggested here by you
in [Patch 5/5] "clk: Add Baikal-T1 CCU dividers driver" of this
patchset.

Regards,
-Sergey


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

* Re: [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings
  2020-04-05  9:59     ` Sergey Semin
@ 2020-04-16 19:27       ` Sergey Semin
  2020-04-26  6:18       ` Sergey Semin
  1 sibling, 0 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-16 19:27 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Mark Rutland, Michael Turquette, Rob Herring, Alexey Malahov,
	Thomas Bogendoerfer, Paul Burton, Ralf Baechle, linux-clk,
	devicetree, linux-kernel

Stephen,

Any back responses on the questions below?

Regards,
-Sergey

On Sun, Apr 05, 2020 at 12:59:25PM +0300, Sergey Semin wrote:
> Hello Stephen,
> 
> Sorry for a delayed response. My answers to your comments are below.
> 
> On Mon, Mar 09, 2020 at 07:02:27PM -0700, Stephen Boyd wrote:
> > Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:44)
> > > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > 
> > > Baikal-T1 Clocks Control Unit is responsible for transformation of a
> > > signal coming from an external oscillator into clocks of various
> > > frequencies to propagate them then to the corresponding clocks
> > > consumers (either individual IP-blocks or clock domains). In order
> > > to create a set of high-frequency clocks the external signal is
> > > firstly handled by the embedded into CCU PLLs. So the corresponding
> > > dts-node is just a normal clock-provider node with standard set of
> > > properties.
> > > 
> > > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> > 
> > SoB chain is backwards. Is Alexey the author? Or Co-developed-by?
> 
> Thanks for noticing this. I'll rearrange the SoB's in v2.
> 
> > 
> > > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > > Cc: Paul Burton <paulburton@kernel.org>
> > > Cc: Ralf Baechle <ralf@linux-mips.org>
> > > ---
> > >  .../bindings/clock/be,bt1-ccu-pll.yaml        | 139 ++++++++++++++++++
> > >  include/dt-bindings/clock/bt1-ccu.h           |  17 +++
> > >  2 files changed, 156 insertions(+)
> > >  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > >  create mode 100644 include/dt-bindings/clock/bt1-ccu.h
> > > 
> > > diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > > new file mode 100644
> > > index 000000000000..f2e397cc147b
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > > @@ -0,0 +1,139 @@
> > > +# SPDX-License-Identifier: GPL-2.0
> > > +#
> > > +# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
> > > +#
> > > +# Baikal-T1 Clocks Control Unit PLL Device Tree Bindings.
> > > +#
> > 
> > I don't think we need any of these comments besides the license
> > identifier line. Can you dual license this?
> > 
> 
> It's normal to have a copyright here, but in a single-lined form.
> I'll do this in v2 and also dual license the binding file.
> 
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/clock/be,bt1-ccu-pll.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: Baikal-T1 Clock Control Unit PLLs
> > > +
> > > +maintainers:
> > > +  - Serge Semin <fancer.lancer@gmail.com>
> > > +
> > > +description: |
> > > +  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> > > +  subsystems clocking and resetting. The CCU is connected with an external
> > > +  fixed rate oscillator, which signal is transformed into clocks of various
> > > +  frequencies and then propagated to either individual IP-blocks or to groups
> > > +  of blocks (clock domains). The transformation is done by means of PLLs and
> > > +  gateable/non-gateable dividers embedded into the CCU. It's logically divided
> > > +  into the next components:
> > > +  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
> > > +     in general can provide any frequency supported by the CCU PLLs).
> > > +  2) PLLs clocks generators (PLLs) - described in this bindings file.
> > > +  3) AXI-bus clock dividers (AXI).
> > > +  4) System devices reference clock dividers (SYS).
> > > +  which are connected with each other as shown on the next figure:
> > 
> > Please add a newline here
> 
> Ok.
> 
> > 
> > > +          +---------------+
> > > +          | Baikal-T1 CCU |
> > > +          |   +----+------|- MIPS P5600 cores
> > > +          | +-|PLLs|------|- DDR controller
> > > +          | | +----+      |
> > > +  +----+  | |  |  |       |
> > > +  |XTAL|--|-+  |  | +---+-|
> > > +  +----+  | |  |  +-|AXI|-|- AXI-bus
> > > +          | |  |    +---+-|
> > > +          | |  |          |
> > > +          | |  +----+---+-|- APB-bus
> > > +          | +-------|SYS|-|- Low-speed Devices
> > > +          |         +---+-|- High-speed Devices
> > > +          +---------------+
> > 
> > And here.
> > 
> 
> Ok
> 
> > > +  Each CCU sub-block is represented as a separate dts-node and has an
> > > +  individual driver to be bound with.
> > > +
> > > +  In order to create signals of wide range frequencies the external oscillator
> > > +  output is primarily connected to a set of CCU PLLs. There are five PLLs
> > > +  to create a clock for the MIPS P5600 cores, the embedded DDR controller,
> > > +  SATA, Ethernet and PCIe domains. The last three domains though named by the
> > > +  biggest system interfaces in fact include nearly all of the rest SoC
> > > +  peripherals. Each of the PLLs is based on True Circuits TSMC CLN28HPM core
> > > +  with an interface wrapper (so called safe PLL' clocks switcher) to simplify
> > > +  the PLL configuration procedure. The PLLs work as depicted on the next
> > > +  diagram:
> > 
> > Same, space out the diagrams.
> > 
> 
> Ok
> 
> > > +      +--------------------------+
> > > +      |                          |
> > > +      +-->+---+    +---+   +---+ |  +---+   0|\
> > > +  CLKF--->|/NF|--->|PFD|...|VCO|-+->|/OD|--->| |
> > > +          +---+ +->+---+   +---+ /->+---+    | |--->CLKOUT
> > > +  CLKOD---------C----------------+          1| |
> > > +       +--------C--------------------------->|/
> > > +       |        |                             ^
> > > +  Rclk-+->+---+ |                             |
> > > +  CLKR--->|/NR|-+                             |
> > > +          +---+                               |
> > > +  BYPASS--------------------------------------+
> > > +  BWADJ--->
> > > +  where Rclk is the reference clock coming  from XTAL, NR - reference clock
> > > +  divider, NF - PLL clock multiplier, OD - VCO output clock divider, CLKOUT -
> > > +  output clock, BWADJ is the PLL bandwidth adjustment parameter. At this moment
> > > +  the binding supports the PLL dividers configuration in accordance with a
> > > +  requested rate, while bypassing and bandwidth adjustment settings can be
> > > +  added in future if it gets to be necessary.
> > > +
> > > +  The PLLs CLKOUT is then either directly connected with the corresponding
> > > +  clocks consumer (like P5600 cores or DDR controller) or passed over a CCU
> > > +  divider to create a signal required for the clock domain.
> > > +
> > > +  The CCU PLL dts-node uses the common clock bindings [1] with no custom
> > > +  parameters. The list of exported clocks can be found in
> > > +  'dt-bindings/clock/bt1-ccu.h'.
> > > +
> > > +  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
> > 
> > Don't think we need to mention this binding anymore. But it's good that
> > we know what exported clock ids are.
> > 
> 
> Ok. I'll remove the legacy text binding file mention here and retain the
> reference to the header file with the clock IDs defined. The similar
> thing will be done for the others bindings in the patchset.
> 
> > > +
> > > +allOf:
> > > +  - $ref: /schemas/clock/clock.yaml#
> > > +
> > > +properties:
> > > +  compatible:
> > > +    const: be,bt1-ccu-pll
> > > +
> 
> > > +  reg:
> > > +    description: CCU PLLs sub-block base address.
> > > +    maxItems: 1
> > > +
> 
> Sometime ago I sent a RFC to Rob and you being in Cc there:
> https://lkml.org/lkml/2020/3/22/393
> Simply speaking there are several issues raised in comments to different
> patchsets, which are indirectly connected with the Baikal-T1 System Controller
> DT node design I've initially chosen. In accordance with that I've spread its
> functional blocks into different DT nodes with no reference to being related
> to the System Controller. Clock Control Unit nodes are amongst these blocks.
> Seeing such design caused these issues I suggested an alternative solution
> of having a single System Controller node and multiple functional sub-nodes.
> These sub-nodes will include the Clock Control Unit PLLs, AXI-bus and System
> Device blocks. I thoroughly described the solution in the RFC. So if no
> arguments against it pop up soon in the RFC comments, I'll implement it in
> v2 of this patchset as well. This solution cause the reg-property removal
> from this binding. Instead the drivers shall refer to the parental syscon
> node to get a regmap with CCU registers from it.
> 
> > > +  "#clock-cells":
> > > +    description: |
> > > +      Clocks are referenced by the node phandle and an unique identifier
> > > +      from 'dt-bindings/clock/bt1-ccu.h'.
> > 
> > Don't think we need this description.
> 
> Agreed.
> 
> > 
> > > +    const: 1
> > > +
> > > +  clocks:
> > > +    description: Phandle of CCU External reference clock.
> > > +    maxItems: 1
> > > +
> > > +  clock-names:
> > > +    const: ref_clk
> > 
> > Can we drop _clk? It's redundant.
> 
> I would leave this and "pcie_clk", "sata_clk", "eth_clk" declared in the
> next two bindings as is, since this way they would exactly match the names
> used in the documentation. The same thing is with the clock-output-names
> property values.
> 
> I've seen such names in many other drivers/bindings including the
> bindings in the clock subsystem even submitted rather recently, not to
> mention the names like "aclk", "pclk", etc used all over the dt nodes.
> Are there any requirements in naming the clocks? Should I avoid using the
> '_clk' clock names suffix in accordance with them? If so, please point
> me out to that requirement in docs for future reference.
> 
> Normally If I don't find something in the requirements documented in the kernel,
> I use either a commonly utilized practice seen in other similar drivers, or
> select a solution which seems better to me like providing a better readability
> and code understanding. 
> 
> > 
> > > +
> > > +  clock-output-names: true
> > > +
> > > +  assigned-clocks: true
> > > +
> > > +  assigned-clock-rates: true
> > > +
> > > +additionalProperties: false
> > > +
> 
> I'll also replace these four properties with a single
> "unevaluatedProperties: false". In the framework of other patchset
> review Rob said this property is more suitable in such situations and
> will get to be supported by the dt_binding_check script eventually.
> 
> > > +required:
> > > +  - compatible
> > > +  - reg
> > > +  - "#clock-cells"
> > > +  - clocks
> > > +  - clock-names
> > > +
> > > +examples:
> > > +  - |
> > > +    ccu_pll: ccu_pll@1F04D000 {
> > 
> > Drop the phandle unless it's actually used.
> 
> Do you mean the label definition? If so, Ok. I'll remove it.
> 
> Unit-address will be also lowercased if I don't remove the reg property
> from here. As I said in RFC in accordance with the alternative solution
> this node will be a sub-node of the system controller, which regmap will
> be used instead of the individual reg-property definition. So if the
> reg-property is removed from the node, the unit-address will be also
> discarded from here.
> 
> > 
> > > +      compatible = "be,bt1-ccu-pll";
> > > +      reg = <0x1F04D000 0x028>;
> > 
> > Lowercase hex please. That size is oddly small.
> 
> It's small due to be range being part of the system controller registers
> set. I've briefly described this above and thoroughly - in the RFC.
> Please see the RFC text and send your comments regarding an alternative
> solution there shall you have any.
> 
> Anyway if no comments are received there soon, I'll remove the reg
> property from here. The PLL driver will refer to the parental system
> controller to get the registers regmap handler.
> 
> > 
> > > +      #clock-cells = <1>;
> > > +
> > > +      clocks = <&osc25>;
> > > +      clock-names = "ref_clk";
> > > +
> > > +      clock-output-names = "cpu_pll", "sata_pll", "ddr_pll",
> > > +                           "pcie_pll", "eth_pll";
> > > +    };
> > > +...
> > > diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
> > > new file mode 100644
> > > index 000000000000..86e63162ade0
> > > --- /dev/null
> > > +++ b/include/dt-bindings/clock/bt1-ccu.h
> > > @@ -0,0 +1,17 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Baikal-T1 CCU clock indeces.
> > > + */
> > > +#ifndef __DT_BINDINGS_CLOCK_BT1_CCU_H
> > > +#define __DT_BINDINGS_CLOCK_BT1_CCU_H
> > > +
> > > +/* Baikal-T1 CCU PLL indeces. */
> > 
> > Please drop this comment. It's not useful.
> 
> Ok.
> 
> Regards,
> -Sergey
> 
> > 
> > > +#define CCU_CPU_PLL                    0
> > > +#define CCU_SATA_PLL                   1
> > > +#define CCU_DDR_PLL                    2
> > > +#define CCU_PCIE_PLL                   3
> > > +#define CCU_ETH_PLL                    4
> > > +
> > > +#endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
> > > -- 
> > > 2.25.1
> > >

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

* Re: [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver
  2020-04-07 12:08     ` Sergey Semin
@ 2020-04-16 19:29       ` Sergey Semin
  2020-04-26  6:16       ` Sergey Semin
  1 sibling, 0 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-16 19:29 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Michael Turquette, Alexey Malahov, Thomas Bogendoerfer,
	Paul Burton, Ralf Baechle, linux-kernel, linux-clk

Stephen,

Any back responses on the questions below?

Regards,
-Sergey

On Tue, Apr 07, 2020 at 03:08:35PM +0300, Sergey Semin wrote:
> On Tue, Mar 10, 2020 at 08:30:15AM -0700, Stephen Boyd wrote:
> > Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:47)
> > > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > 
> > > Baikal-T1 is supposed to be supplied with a high-frequency external
> > > oscillator. But in order to create signals suitable for each IP-block
> > > embedded into the SoC the oscillator output is primarily connected to
> > > a set of CCU PLLs. There are five of them to create clocks for the MIPS
> > > P5600 cores, the embedded DDR controller, SATA, Ethernet and PCIe
> > > domains. The last three domains though named by the biggest system
> > > interfaces in fact include nearly all of the rest SoC peripherals.
> > > Each of the PLLs is based on True Circuits TSMC CLN28HPM IP-core with
> > > an interface wrapper (so called safe PLL' clocks switcher) to simplify
> > > the PLL configuration procedure.
> > > 
> > > This driver creates the of-based hardware clocks to use them then in
> > > the corresponding subsystems. In order to simplify the driver code we
> > > split the functionality up into the PLLs clocks operations and hardware
> > > clocks declaration/registration procedures. So if CLK_BT1_CCU is
> > > defined, then the first part is available in the kernel, while
> > > CLK_BT1_CCU_PLL config makes the actual clocks being registered at the
> > > time of_clk_init() is called.
> > > 
> > > Even though the PLLs are based on the same IP-core, they actually may
> > > have some differences. In particular, some CCU PLLs supports the output
> > > clock change without gating them (like CPU or PCIe PLLs), while the
> > > others don't, some CCU PLLs are critical and aren't supposed to be
> > > gated. In order to cover all of these cases the hardware clocks driver
> > > is designed with a info-descriptor pattern. So there are special static
> > > descriptors declared for each PLL, which is then used to create a
> > > hardware clock with proper operations. Additionally debugfs-files are
> > > provided for each PLL' field to make sure the implemented
> > > rate-PLLs-dividers calculation algorithm is correct.
> > > 
> > > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> > > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > > Cc: Paul Burton <paulburton@kernel.org>
> > > Cc: Ralf Baechle <ralf@linux-mips.org>
> > 
> > > diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig
> > > new file mode 100644
> > > index 000000000000..0e2fc86f3ab8
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/Kconfig
> > > @@ -0,0 +1,35 @@
> > > +# SPDX-License-Identifier: GPL-2.0
> > > +config CLK_BAIKAL_T1
> > > +       bool "Baikal-T1 Clocks Control Unit interface"
> > > +       depends on (MIPS_BAIKAL_T1 || COMPILE_TEST) && OF
> > 
> > Does it actually depend on OF for any compilation?
> 
> Hm, good question. AFAICS it doesn't. The compilation should work for
> both of these configs. While AFAIU it is meaningful to have
> (MIPS_BAIKAL_T1 && OF) only instead of making the compile test depending
> on the OF as well.
> 
> On the other hand MIPS_BAIKAL_T1 indirectly selects OF. I knew about
> this reverse dependency, but still preferred to signify that the driver
> relies on the OF functionality to properly work. What do you think would
> be the best solution in this case:
> 1) just drop OF, which would mean to have (MIPS_BAIKAL_T1 ||
> COMPILE_TEST),
> 2) redefine the statement to (MIPS_BAIKAL_T1 && OF) || COMPILE_TEST ?
> 
> BTW AFAICS OF API is dummy defined if no CONFIG_OF is enabled. So there
> is no need in having OF-dependency for any compile-test usecase. Right?
> Do you know any case when it's required? For instance, I've found the
> similar OF-based conditional statement for CLK_QORIQ,
> COMMON_CLK_FIXED_MMIO and COMMON_CLK_KEYSTONE. Do you think these
> configs are wrong and have to be fixed by removing the "&& OF" statement
> as well?
> 
> > 
> > > +       default y
> > 
> > Please no default y. Maybe default MIPS_BAIKAL_T1?
> 
> Do you mean that in case if COMPILE_TEST is enabled only, the
> drivers below shouldn't be available by default? If so then ok.
> I'll use MIPS_BAIKAL_T1 here. If no, then "default y" would be more
> appropriate.
> 
> > 
> > > +       help
> > > +         Clocks Control Unit is the core of Baikal-T1 SoC responsible for the
> > > +         chip subsystems clocking and resetting. It consists of multiple
> > > +         global clock domains, which can be reset by means of the CCU control
> > > +         registers. These domains and devices placed in them are fed with
> > > +         clocks generated by a hierarchy of PLLs, configurable and fixed
> > > +         dividers. In addition CCU exposes several unrelated functional blocks
> > > +         like irqless Designware i2c controller with indirectly accessed
> > > +         registers, AXI bus errors detector, DW PCIe controller PM/clocks/reset
> > > +         manager, etc.
> > > +
> > > +         This driver provides a set of functions to create the kernel clock
> > > +         devices of Baikal-T1 PLLs and dividers, and to manipulate the reset
> > > +         signals of the SoC.
> > > +
> > > +if CLK_BAIKAL_T1
> > > +
> > > +config CLK_BT1_CCU_PLL
> > > +       bool "Baikal-T1 CCU PLLs support"
> > > +       default y
> > 
> > default MIPS_BAIKAL_T1?
> 
> see the comment above.
> 
> > 
> > > +       help
> > > +         Enable this to support the PLLs embedded into the Baikal-T1 SoCs.
> > > +         These are five PLLs placed at the root of the clocks hierarchy,
> > > +         right after the external reference osciallator (normally of 25MHz).
> > > +         They are used to generate a high frequency signals, which are
> > 
> > s/generate a high/generate high/
> 
> Ok.
> 
> > 
> > > +         either directly wired to the consumers (like CPUs, DDR) or passed
> > > +         over the clock dividers to be only then used as an individual
> > > +         reference clocks of a target device.
> > 
> > s/clocks/clock/
> 
> Ok.
> 
> > 
> > > +
> > > +endif
> > > diff --git a/drivers/clk/baikal-t1/ccu-pll.c b/drivers/clk/baikal-t1/ccu-pll.c
> > > new file mode 100644
> > > index 000000000000..f2087a80b64d
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/ccu-pll.c
> > > @@ -0,0 +1,474 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Authors:
> > > + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> > > + *
> > > + * Baikal-T1 CCU PLL interface driver.
> > > + */
> > > +
> > > +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/printk.h>
> > > +#include <linux/limits.h>
> > > +#include <linux/bits.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/of.h>
> > > +#include <linux/spinlock.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/time64.h>
> > > +#include <linux/rational.h>
> > > +#include <linux/debugfs.h>
> > > +
> > > +#include "ccu-pll.h"
> > > +#include "common.h"
> > > +
> > > +#define CCU_PLL_CTL                    0x00
> > > +#define CCU_PLL_CTL_EN                 BIT(0)
> > > +#define CCU_PLL_CTL_RST                        BIT(1)
> > > +#define CCU_PLL_CTL_CLKR_FLD           2
> > > +#define CCU_PLL_CTL_CLKR_MASK          GENMASK(7, CCU_PLL_CTL_CLKR_FLD)
> > > +#define CCU_PLL_CTL_CLKF_FLD           8
> > > +#define CCU_PLL_CTL_CLKF_MASK          GENMASK(20, CCU_PLL_CTL_CLKF_FLD)
> > > +#define CCU_PLL_CTL_CLKOD_FLD          21
> > > +#define CCU_PLL_CTL_CLKOD_MASK         GENMASK(24, CCU_PLL_CTL_CLKOD_FLD)
> > > +#define CCU_PLL_CTL_BYPASS             BIT(30)
> > > +#define CCU_PLL_CTL_LOCK               BIT(31)
> > > +#define CCU_PLL_CTL1                   0x04
> > > +#define CCU_PLL_CTL1_BWADJ_FLD         3
> > > +#define CCU_PLL_CTL1_BWADJ_MASK                GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD)
> > > +
> > > +#define CCU_PLL_RST_DELAY_US           5
> > > +#define CCU_PLL_LOCK_DELAY_US(_ref_rate, _nr) ({       \
> > > +       uint64_t _n = 500ULL * (_nr) * USEC_PER_SEC;    \
> > > +       do_div(_n, _ref_rate);                          \
> > > +       _n;                                             \
> > > +})
> > 
> > Can this be a static inline function? We get to learn what the types are
> > then.
> 
> Ok I'll convert it to the explicit static inline version cause' you asked it.
> Personally I don't see the necessity of this here. The arithmetics is
> converted to u64, which is more than enough to cover any possible
> nr-value.
> 
> > 
> > > +#define CCU_PLL_LOCK_CHECK_RETRIES     50
> > > +
> > > +#define CCU_PLL_NR_MAX \
> > > +       ((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1)
> > > +#define CCU_PLL_NF_MAX \
> > > +       ((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1)
> > > +#define CCU_PLL_OD_MAX \
> > > +       ((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1)
> > > +#define CCU_PLL_NB_MAX \
> > > +       ((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1)
> > > +#define CCU_PLL_FDIV_MIN               427000UL
> > > +#define CCU_PLL_FDIV_MAX               3500000000UL
> > > +#define CCU_PLL_FOUT_MIN               200000000UL
> > > +#define CCU_PLL_FOUT_MAX               2500000000UL
> > > +#define CCU_PLL_FVCO_MIN               700000000UL
> > > +#define CCU_PLL_FVCO_MAX               3500000000UL
> > > +#define CCU_PLL_CLKOD_FACTOR           2
> > > +
> > > +#define CCU_PLL_CALC_FREQ(_ref_rate, _nr, _nf, _od) \
> > > +       ((_ref_rate) / (_nr) * (_nf) / (_od))
> > 
> > Static inline function please. Does this need to be 64-bit math?
> 
> Ok for the same reason. In our platform 32-bit data is enough here.
> Though you are right. Some NF may overflow the unsigned long ref_rate
> data. I'll add the 64-bit arithmetics in this function too.
> 
> BTW I thought about 64-bits here in first place, but then I decided it
> may cause the performance drop in ccu_pll_calc_factors() and in
> ccu_pll_recalc_rate() so I left the unsigned long calc here.
> 
> > 
> > > +
> > > +static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk,
> > > +                        unsigned long nr)
> > > +{
> > > +       unsigned long ud;
> > > +       int count;
> > > +       u32 val;
> > > +
> > > +       ud = CCU_PLL_LOCK_DELAY_US(ref_clk, nr);
> > > +
> > > +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_RST, CCU_PLL_CTL_RST);
> > > +
> > > +       count = CCU_PLL_LOCK_CHECK_RETRIES;
> > > +       do {
> > > +               udelay(ud);
> > > +               val = ccu_read(pll->regs + CCU_PLL_CTL);
> > > +       } while (!(val & CCU_PLL_CTL_LOCK) && --count);
> > > +
> > > +       return (val & CCU_PLL_CTL_LOCK) ? 0 : -ETIMEDOUT;
> > 
> > Looks like readl_poll_timeout().
> 
> Nice catch. readl_poll_timeout() perfectly fits here. Though the atomic
> version is more appropriate, since the delays are normally small and the
> method is executed in the atomic context.
> 
> > 
> > > +}
> > > +
> > > +static int ccu_pll_enable(struct clk_hw *hw)
> > > +{
> > > +       struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       unsigned long flags;
> > > +       int ret;
> > > +       u32 val;
> > > +
> > > +       if (!parent_hw) {
> > > +               pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw));
> > > +               return -EINVAL;
> > > +       }
> > > +
> > > +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> > > +       if (val & CCU_PLL_CTL_EN)
> > > +               return 0;
> > > +
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);
> > > +       ccu_write(pll->regs + CCU_PLL_CTL, val | CCU_PLL_CTL_EN);
> > > +       ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw),
> > > +                           CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1);
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> > > +       if (ret)
> > > +               pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
> > > +
> > > +       return ret;
> > > +}
> > > +
> > > +static void ccu_pll_disable(struct clk_hw *hw)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       unsigned long flags;
> > > +
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);
> > > +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_EN, 0);
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> > > +}
> > > +
> > > +static int ccu_pll_is_enabled(struct clk_hw *hw)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +
> > > +       return !!(ccu_read(pll->regs + CCU_PLL_CTL) & CCU_PLL_CTL_EN);
> > > +}
> > > +
> > > +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
> > > +                                        unsigned long parent_rate)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       unsigned long nr, nf, od;
> > > +       u32 val;
> > > +
> > > +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> > > +       nr = CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1;
> > 
> > We have FIELD_GET macros for this. Please use them instead of creating
> > your own.
> 
> Right. I'll use it instead.
> 
> > 
> > > +       nf = CCU_GET_FLD(CCU_PLL_CTL_CLKF, val) + 1;
> > > +       od = CCU_GET_FLD(CCU_PLL_CTL_CLKOD, val) + 1;
> > > +
> > > +       return CCU_PLL_CALC_FREQ(parent_rate, nr, nf, od);
> > > +}
> > > +
> > > +static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate,
> > > +                                unsigned long *nr, unsigned long *nf,
> > > +                                unsigned long *od)
> > > +{
> > > +       unsigned long err, freq, min_err = ULONG_MAX;
> > > +       unsigned long num, denom, n1, d1, nri;
> > > +       unsigned long nr_max, nf_max, od_max;
> > > +
> > > +       /*
> > > +        * Make sure PLL is working with valid input signal (Fdiv). If
> > > +        * you want to speed the function up just reduce CCU_PLL_NR_MAX.
> > > +        * This will cause a worse approximation though.
> > > +        */
> > > +       nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1;
> > > +       nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX);
> > > +
> > > +       /*
> > > +        * Find a closest [nr;nf;od] vector taking into account the
> > > +        * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is
> > > +        * either 1 or even number within the acceptable range (alas 1s
> > > +        * is also excluded by the next loop).
> > > +        */
> > > +       for (; nri <= nr_max; ++nri) {
> > > +               /* Use Od factor to fulfill the limitation 2). */
> > > +               num = CCU_PLL_CLKOD_FACTOR * rate;
> > > +               denom = parent_rate / nri;
> > > +
> > > +               /*
> > > +                * Make sure Fvco is within the acceptable range to fulfill
> > > +                * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value
> > > +                * the actual upper limit is also divided by that factor.
> > > +                * It's not big problem for us since practically there is no
> > > +                * need in clocks with that high frequency.
> > > +                */
> > > +               nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX);
> > > +               od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR;
> > > +
> > > +               /*
> > > +                * Bypass the out-of-bound values, which can't be properly
> > > +                * handled by the rational fraction approximation algorithm.
> > > +                */
> > > +               if (num / denom >= nf_max) {
> > > +                       n1 = nf_max;
> > > +                       d1 = 1;
> > > +               } else if (denom / num >= od_max) {
> > > +                       n1 = 1;
> > > +                       d1 = od_max;
> > > +               } else {
> > > +                       rational_best_approximation(num, denom, nf_max, od_max,
> > > +                                                   &n1, &d1);
> > > +               }
> > > +
> > > +               /* Select the best approximation of the target rate. */
> > > +               freq = (((parent_rate / nri) * n1) / d1);
> > 
> > Please drop extra parenthesis.
> 
> Ok. Thanks for noticing this. I'll replace it with ccu_pll_calc_freq() function.
> 
> > 
> > > +               err = abs((int64_t)freq - num);
> > > +               if (err < min_err) {
> > > +                       min_err = err;
> > > +                       *nr = nri;
> > > +                       *nf = n1;
> > > +                       *od = CCU_PLL_CLKOD_FACTOR * d1;
> > > +               }
> > > +       }
> > > +}
> > > +
> ...
> > > +
> > > +#ifdef CONFIG_DEBUG_FS
> > > +
> > > +#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask)                     \
> > > +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +                                                                       \
> > > +       *val = !!(ccu_read(pll->regs + (_reg)) & (_mask));              \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +       unsigned long flags;                                            \
> > > +                                                                       \
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> > > +       ccu_update(pll->regs + (_reg), (_mask), val ? (_mask) : 0);     \
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> > > +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> > > +
> > > +#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _fld, _min, _max)          \
> > > +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +       u32 data;                                                       \
> > > +                                                                       \
> > > +       data = ccu_read(pll->regs + (_reg));                            \
> > > +       *val = CCU_GET_FLD(_fld, data) + 1;                             \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +       unsigned long flags;                                            \
> > > +       u32 data;                                                       \
> > > +                                                                       \
> > > +       val = clamp_t(u64, val, _min, _max);                            \
> > > +       data = CCU_SET_FLD(_fld, 0, val - 1);                           \
> > > +                                                                       \
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> > > +       ccu_update(pll->regs + (_reg), _fld ## _MASK, data);            \
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> > > +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> > > +
> > > +CCU_PLL_DBGFS_BIT_ATTR(en, CCU_PLL_CTL, CCU_PLL_CTL_EN);
> > > +CCU_PLL_DBGFS_BIT_ATTR(rst, CCU_PLL_CTL, CCU_PLL_CTL_RST);
> > > +CCU_PLL_DBGFS_FLD_ATTR(nr, CCU_PLL_CTL, CCU_PLL_CTL_CLKR, 1, CCU_PLL_NR_MAX);
> > > +CCU_PLL_DBGFS_FLD_ATTR(nf, CCU_PLL_CTL, CCU_PLL_CTL_CLKF, 1, CCU_PLL_NF_MAX);
> > > +CCU_PLL_DBGFS_FLD_ATTR(od, CCU_PLL_CTL, CCU_PLL_CTL_CLKOD, 1, CCU_PLL_OD_MAX);
> > > +CCU_PLL_DBGFS_BIT_ATTR(bypass, CCU_PLL_CTL, CCU_PLL_CTL_BYPASS);
> > > +CCU_PLL_DBGFS_BIT_ATTR(lock, CCU_PLL_CTL, CCU_PLL_CTL_LOCK);
> > > +CCU_PLL_DBGFS_FLD_ATTR(nb, CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ, 1, CCU_PLL_NB_MAX);
> > > +
> > > +static const struct debugfs_reg32 ccu_pll_dbgfs_regs[] = {
> > > +       CCU_DBGFS_REG("ctl", CCU_PLL_CTL),
> > > +       CCU_DBGFS_REG("ctl1", CCU_PLL_CTL1)
> > > +};
> > > +
> > > +static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       struct debugfs_regset32 *regset;
> > > +
> > > +       regset = kzalloc(sizeof(*regset), GFP_KERNEL);
> > > +       if (!regset)
> > > +               return;
> > > +
> > > +       regset->regs = ccu_pll_dbgfs_regs;
> > > +       regset->nregs = ARRAY_SIZE(ccu_pll_dbgfs_regs);
> > > +       regset->base = pll->regs;
> > > +       debugfs_create_regset32("registers", 0400, dentry, regset);
> > > +
> > > +       debugfs_create_file_unsafe("en", 0600, dentry, pll,
> > 
> > Why unsafe?
> 
> I don't need full proxy file-operations in this case. Moreover since all
> fops are defined by means of DEFINE_DEBUGFS_ATTRIBUTE() the set/get
> callbacks used will be automatically protected against the corresponding
> files removal. See the comment to the debugfs_create_file_unsafe() for
> details and commit e7e6198c6056 ("clk: tegra: dfll: Fix debugfs_simple_attr.cocci
> warnings") as an example of why unsafe is appropriate here. BTW The commit
> has got your SoB.
> 
> > 
> > > +                                  &ccu_pll_dbgfs_en_fops);
> > > +       debugfs_create_file_unsafe("rst", 0200, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_rst_fops);
> > > +       debugfs_create_file_unsafe("nr", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_nr_fops);
> > > +       debugfs_create_file_unsafe("nf", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_nf_fops);
> > > +       debugfs_create_file_unsafe("od", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_od_fops);
> > > +       debugfs_create_file_unsafe("bypass", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_bypass_fops);
> > > +       debugfs_create_file_unsafe("lock", 0400, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_lock_fops);
> > > +       debugfs_create_file_unsafe("nb", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_nb_fops);
> > > +}
> > 
> > Is there any usage of these outside of development of this driver? I'd
> > rather not see us expose blanket register write access with code in
> > debugfs. If you're interested in poking register why not use /dev/mem?
> 
> These parameters are indeed mostly useful in the driver development. But
> they can be useful to diagnose the peripheral device problems like data
> corruption or weird clock consumers behaviour. Moreover some of the
> parameters aren't utilized by the driver like bypass and nb, but might
> be theoretically useful in fixing the problems.
> 
> Anyway I do understand your concern regarding write access to these
> fields. What about if I implemented the same approach as the one used in
> drivers/clk/clk.c? By default the fields are exported in RO-mode.
> If a developer needs to debug a clock-based problem one will need to
> recompile the driver with #define CLOCK_ALLOW_WRITE_DEBUGFS macro
> specified. What do you think?
> 
> > 
> > > +
> > > +#else /* !CONFIG_DEBUG_FS */
> > > +
> > > +#define ccu_pll_debug_init NULL
> > > +
> > > +#endif /* !CONFIG_DEBUG_FS */
> > > +
> > > +static const struct clk_ops ccu_pll_gate_to_set_ops = {
> > > +       .enable = ccu_pll_enable,
> > > +       .disable = ccu_pll_disable,
> > > +       .is_enabled = ccu_pll_is_enabled,
> > > +       .recalc_rate = ccu_pll_recalc_rate,
> > > +       .round_rate = ccu_pll_round_rate,
> > > +       .set_rate = ccu_pll_set_rate_norst,
> > > +       .debug_init = ccu_pll_debug_init
> > > +};
> > > +
> > > +static const struct clk_ops ccu_pll_straight_set_ops = {
> > > +       .enable = ccu_pll_enable,
> > > +       .disable = ccu_pll_disable,
> > > +       .is_enabled = ccu_pll_is_enabled,
> > > +       .recalc_rate = ccu_pll_recalc_rate,
> > > +       .round_rate = ccu_pll_round_rate,
> > > +       .set_rate = ccu_pll_set_rate_reset,
> > > +       .debug_init = ccu_pll_debug_init
> > > +};
> > > +
> > > +struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *pll_init)
> > 
> > Can pll_init be const?
> 
> Can't believe I missed this. Yes, it can. Thanks.
> 
> > 
> > > +{
> > > +       struct clk_parent_data parent_data = {0};
> > > +       struct clk_init_data hw_init = {0};
> > > +       struct ccu_pll *pll;
> > > +       int ret;
> > > +
> > > +       if (!pll_init)
> > > +               return ERR_PTR(-EINVAL);
> > > +
> > > +       pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> > > +       if (!pll)
> > > +               return ERR_PTR(-ENOMEM);
> > > +
> > > +       pll->hw.init = &hw_init;
> > > +       pll->id = pll_init->id;
> > > +       pll->regs = pll_init->regs;
> > > +       spin_lock_init(&pll->regs_lock);
> > > +
> > > +       hw_init.name = pll_init->name;
> > > +       hw_init.flags = CLK_IGNORE_UNUSED;
> > 
> > Why ignore unused? Please have a comment.
> 
> Ok. I thought it was obvious, since the flag naming said by itself.
> These PLLs are supposed to be left enabled even if they are unused by
> any of the drivers. System bootloader makes sure that required clocks
> are enabled by default like CPU and DDR PLLs. So we don't want to let
> the clock subsystem to disable the PLLs if they are left unused.
> 
> > 
> > > +
> > > +       if (pll_init->flags & CCU_PLL_GATE_TO_SET) {
> > > +               hw_init.flags |= CLK_SET_RATE_GATE;
> > > +               hw_init.ops = &ccu_pll_gate_to_set_ops;
> > > +       } else {
> > > +               hw_init.ops = &ccu_pll_straight_set_ops;
> > > +       }
> > > +
> > > +       if (pll_init->flags & CCU_PLL_CRITICAL)
> > > +               hw_init.flags |= CLK_IS_CRITICAL;
> > > +
> > > +       if (!pll_init->parent_name) {
> > > +               ret = -EINVAL;
> > > +               goto err_free_pll;
> > > +       }
> > > +       parent_data.fw_name = pll_init->parent_name;
> > > +       hw_init.parent_data = &parent_data;
> > > +       hw_init.num_parents = 1;
> > > +
> > > +       ret = of_clk_hw_register(pll_init->np, &pll->hw);
> > > +       if (ret)
> > > +               goto err_free_pll;
> > > +
> > > +       return pll;
> > > +
> > > +err_free_pll:
> > > +       kfree(pll);
> > > +
> > > +       return ERR_PTR(ret);
> > > +}
> > > +
> > > +void ccu_pll_hw_unregister(struct ccu_pll *pll)
> > > +{
> > > +       if (!pll)
> > > +               return;
> > 
> > That would be quite bad. Just let that blow up if so.
> 
> Ok.
> 
> > 
> > > +
> > > +       clk_hw_unregister(&pll->hw);
> > > +
> > > +       kfree(pll);
> > > +}
> > > diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h
> > > new file mode 100644
> > > index 000000000000..6921516311fb
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/ccu-pll.h
> > > @@ -0,0 +1,73 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Baikal-T1 CCU PLL interface driver.
> > > + */
> > > +#ifndef __CLK_BT1_CCU_PLL_H__
> > > +#define __CLK_BT1_CCU_PLL_H__
> > > +
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/spinlock.h>
> > > +#include <linux/bits.h>
> > > +#include <linux/of.h>
> > > +
> > > +/*
> > > + * CCU PLL private flags.
> > > + * @CCU_PLL_GATE_TO_SET: Some PLLs output clock can't be changed on-the-fly,
> > > + *                      so according to documentation they need to be gated
> > > + *                      first.
> > > + * @CCU_PLL_CRITICAL: Even though there is a way to switch any PLL off, there
> > > + *                   might be some, which shouldn't be in any case.
> > > + */
> > > +#define CCU_PLL_GATE_TO_SET            BIT(0)
> > > +#define CCU_PLL_CRITICAL               BIT(1)
> > > +
> > > +/*
> > > + * struct ccu_pll_init_data - CCU PLL initialization data.
> > > + * @id: Clock private identifier.
> > > + * @regs: PLL registers base address.
> > > + * @name: Clocks name.
> > > + * @parent_name: Clocks parent name in a fw node.
> > > + * @np: Pointer to the node describing the CCU PLLs.
> > > + * @flags: PLL private flags.
> > > + */
> > > +struct ccu_pll_init_data {
> > > +       unsigned int id;
> > > +       void __iomem *regs;
> > > +       const char *name;
> > > +       const char *parent_name;
> > > +       struct device_node *np;
> > > +       unsigned long flags;
> > > +};
> > > +
> > > +/*
> > > + * struct ccu_pll - CCU PLL descriptor.
> > 
> > Please drop the full stop on title
> 
> Ok.
> 
> > 
> > > + * @hw: clk_hw of the PLL.
> > > + * @id: Clock private identifier.
> > > + * @regs: PLL registers base address.
> > > + * @regs_lock: The registers exclusive access spin-lock.
> > > + */
> > > +struct ccu_pll {
> > > +       struct clk_hw hw;
> > > +       unsigned int id;
> > > +       void __iomem *regs;
> > > +       spinlock_t regs_lock;
> > > +};
> > > +#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw)
> > > +
> > > +static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll)
> > > +{
> > > +       return pll ? &pll->hw : NULL;
> > > +}
> > > +
> > > +static inline unsigned int ccu_pll_get_clk_id(struct ccu_pll *pll)
> > > +{
> > > +       return pll ? pll->id : -1;
> > 
> > It's unsigned return value, but return -1? Can this be inlined in the
> > one call site and if !pll just continue instead of having to turn it
> > into a negative value returned through an unsigned int?
> 
> Right, unsigned int, but return -1, which was supposed to be an invalid
> value. (unsigned int)-1 => 0xFFFFFFFF is good portable way of setting
> FFs to any int.
> 
> Anyway your solution seems better. I'll use it instead.
> 
> > 
> > > +}
> > > +
> > > +extern struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *init);
> > > +
> > > +extern void ccu_pll_hw_unregister(struct ccu_pll *pll);
> > > +
> > > +#endif /* __CLK_BT1_CCU_PLL_H__ */
> > > diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c
> > > new file mode 100644
> > > index 000000000000..990f0a4a12f9
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/clk-ccu-pll.c
> > > @@ -0,0 +1,217 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Authors:
> > > + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> > > + *
> > > + * Baikal-T1 CCU PLL clocks driver.
> > > + */
> > > +
> > > +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/printk.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/of.h>
> > > +#include <linux/of_address.h>
> > > +#include <linux/ioport.h>
> > > +
> > > +#include <dt-bindings/clock/bt1-ccu.h>
> > > +
> > > +#include "ccu-pll.h"
> > > +
> > > +#define CCU_CPU_PLL_BASE               0x000
> > > +#define CCU_SATA_PLL_BASE              0x008
> > > +#define CCU_DDR_PLL_BASE               0x010
> > > +#define CCU_PCIE_PLL_BASE              0x018
> > > +#define CCU_ETH_PLL_BASE               0x020
> > > +
> > > +#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags)        \
> > > +       {                                               \
> > > +               .id = _id,                              \
> > > +               .name = _name,                          \
> > > +               .parent_name = _pname,                  \
> > > +               .base = _base,                          \
> > > +               .flags = _flags                         \
> > > +       }
> > > +
> > > +#define CCU_PLL_NUM                    ARRAY_SIZE(pll_info)
> > > +
> > > +struct ccu_pll_info {
> > > +       unsigned int id;
> > > +       const char *name;
> > > +       const char *parent_name;
> > > +       unsigned int base;
> > > +       unsigned long flags;
> > > +};
> > > +
> > > +static const struct ccu_pll_info pll_info[] = {
> > > +       CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL),
> > > +       CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> > 
> > Please comment all CLK_IS_CRITICAL usage
> 
> Ok. CPU and DDR PLLs are sources of CPU cores and DDR controller reference clocks,
> which obviously shouldn't be ever gated. SATA and PCIe PLLs are the parents of
> APB-bus and DDR controller AXI-bus clocks, which if gated would cause
> the system being unusable too.
> 
> > and don't make your own version
> > of the same flags in the common clk framework. Just use the ones in the
> > framework.
> 
> This was my first design of the clock flags, but then I had to create a
> set of custom flags for the CCU dividers driver. That time I turned to
> be at a fork either to have two sets of flags: common clock and custom
> ones, or a single set of custom flags, some of which then I would
> translate to the common clock flags. So I've chosen the latter solution.
> I see you suggest the former. Since I was in doubt which one would be
> better, I'll implement yours then.
> 
> > 
> > > +       CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> > > +       CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL),
> > > +       CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
> > > +                    CCU_PLL_GATE_TO_SET)
> > > +};
> > > +
> > > +struct ccu_pll_data {
> > > +       struct device_node *np;
> > > +       void __iomem *regs;
> > > +       struct ccu_pll *plls[CCU_PLL_NUM];
> > > +};
> > > +
> > > +static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
> > > +                                        unsigned int clk_id)
> > > +{
> > > +       struct ccu_pll *pll;
> > > +       int idx;
> > > +
> > > +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> > > +               pll = data->plls[idx];
> > > +               if (clk_id == ccu_pll_get_clk_id(pll))
> > > +                       return pll;
> > > +       }
> > > +
> > > +       return ERR_PTR(-EINVAL);
> > > +}
> > > +
> > > +static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
> > > +{
> > > +       struct ccu_pll_data *data;
> > > +
> > > +       data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > +       if (!data)
> > > +               return ERR_PTR(-ENOMEM);
> > > +
> > > +       data->np = np;
> > > +
> > > +       return data;
> > > +}
> > > +
> > > +static void ccu_pll_free_data(struct ccu_pll_data *data)
> > > +{
> > > +       kfree(data);
> > > +}
> > > +
> > > +static int ccu_pll_request_regs(struct ccu_pll_data *data)
> > > +{
> > > +       data->regs = of_io_request_and_map(data->np, 0,
> > > +                                          of_node_full_name(data->np));
> > > +       if (IS_ERR(data->regs)) {
> > > +               pr_err("Failed to request PLLs '%s' regs\n",
> > > +                       of_node_full_name(data->np));
> > > +               return PTR_ERR(data->regs);
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static void ccu_pll_release_regs(struct ccu_pll_data *data)
> > > +{
> > > +       struct resource res;
> > > +
> > > +       iounmap(data->regs);
> > > +
> > > +       /* Try to release the resource as well. */
> > > +       if (of_address_to_resource(data->np, 0, &res))
> > > +               return;
> > > +
> > > +       (void)release_mem_region(res.start, resource_size(&res));
> > > +}
> > 
> > Instead of having these three functions please inline them at the one call
> > site.
> 
> This time I disagree. Inlining them won't gain much of the performance
> benefit, while combination of weakly coherent code into a single function
> will cause worse readability. In my drivers I prefer separating unrelated
> or weakly related functionality from each other and creating a set of functions
> with coherent code (in this case it's memory allocation for the driver private
> data and registers mapping, which are only related in the order of execution).
> By this approach I improve the driver code readability making it better
> structured. So instead of having a pile of weakly coherent code in the probe
> I have a set of work methods and their antagonists (if necessary),
> which are called in the required order. Similar design I normally
> prefer in callbacks, event handlers, etc, if coherent code can't be
> embedded into them.
> 
> Moreover I don't see any requirement by which I would have to rearrange the
> code in this case, while following my way will perfectly fit to the
> kernel coding style described in Documentation/process/coding-style.rst as
> "Functions should be short and sweet, and do just one thing." Sorry, I
> would prefer to leave this part of the driver being structured as is.
> 
> Anyway, this part of the driver will be changed in v2 due to implementing
> an alternative approach of Baikal-T1 System Controller DT node design I've
> described in RFC: https://lkml.org/lkml/2020/3/22/393 . According to it I
> will use a syscon_node_to_regmap(parent) method to get the registers map
> from the parental node instead of requesting a dedicated registers range.
> 
> > 
> > > +
> > > +static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
> > > +                                           void *priv)
> > > +{
> > > +       struct ccu_pll_data *data = priv;
> > > +       struct ccu_pll *pll;
> > > +       unsigned int clk_id;
> > > +
> > > +       clk_id = clkspec->args[0];
> > > +       pll = ccu_pll_find_desc(data, clk_id);
> > > +       if (IS_ERR(pll)) {
> > > +               pr_info("Invalid PLL clock ID %d specified\n", clk_id);
> > > +               return ERR_CAST(pll);
> > > +       }
> > > +
> > > +       return ccu_pll_get_clk_hw(pll);
> > > +}
> > > +
> > > +static int ccu_pll_clk_register(struct ccu_pll_data *data)
> > > +{
> > > +       int idx, ret;
> > > +
> > > +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> > > +               const struct ccu_pll_info *info = &pll_info[idx];
> > > +               struct ccu_pll_init_data init = {0};
> > > +
> > > +               init.id = info->id;
> > > +               init.regs = data->regs + info->base;
> > > +               init.parent_name = info->parent_name;
> > > +               init.np = data->np;
> > > +               init.flags = info->flags;
> > > +
> > > +               ret = of_property_read_string_index(data->np,
> > > +                       "clock-output-names", init.id, &init.name);
> > 
> > Do you need this property ever? It would be nice to not have it if
> > possible.
> 
> It isn't needed other than to provide a custom output clock names for the
> predefined set of clocks. I've seen it has been used for this purpose in
> many drivers of the common clk subsystem. Is this enough to have it
> supported in the driver or should I remove it anyway?
> 
> > 
> > > +               if (ret)
> > > +                       init.name = info->name;
> > > +
> > > +               data->plls[idx] = ccu_pll_hw_register(&init);
> > > +               if (IS_ERR(data->plls[idx])) {
> > > +                       ret = PTR_ERR(data->plls[idx]);
> > > +                       pr_err("Couldn't register PLL hw '%s'\n",
> > > +                               init.name);
> > > +                       goto err_hw_unregister;
> > > +               }
> > > +       }
> > > +
> > > +       ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
> > > +       if (ret) {
> > > +               pr_err("Couldn't register PLL provider of '%s'\n",
> > > +                       of_node_full_name(data->np));
> > > +               goto err_hw_unregister;
> > > +       }
> > > +
> > > +       return 0;
> > > +
> > > +err_hw_unregister:
> > > +       for (--idx; idx >= 0; --idx)
> > > +               ccu_pll_hw_unregister(data->plls[idx]);
> > > +
> > > +       return ret;
> > > +}
> > > +
> > > +static __init void ccu_pll_init(struct device_node *np)
> > > +{
> > > +       struct ccu_pll_data *data;
> > > +       int ret;
> > > +
> > > +       data = ccu_pll_create_data(np);
> > > +       if (IS_ERR(data))
> > > +               return;
> > > +
> > > +       ret = ccu_pll_request_regs(data);
> > > +       if (ret)
> > > +               goto err_free_data;
> > > +
> > > +       ret = ccu_pll_clk_register(data);
> > > +       if (ret)
> > > +               goto err_release_regs;
> > > +
> > > +       pr_info("CCU CPU/SATA/DDR/PCIe/Ethernet PLLs are initialized\n");
> > 
> > Please don't have I'm alive probe messages at info level. Just slows
> > down boot for developer comfort.
> 
> Ok.
> 
> > 
> > > +
> > > +       return;
> > > +
> > > +err_release_regs:
> > > +       ccu_pll_release_regs(data);
> > > +
> > > +err_free_data:
> > > +       ccu_pll_free_data(data);
> > > +}
> > > +CLK_OF_DECLARE(ccu_pll, "be,bt1-ccu-pll", ccu_pll_init);
> > 
> > Any reason this can't be a platform device driver?
> 
> I thought it was normal to have the clock devices available as early
> as possible. Especially when it comes to the system-wide PLLs and dividers.
> So the question is kind of surprising for me.
> 
> Anyway in my case the platform-specific code is using the CPU PLLs rate
> to get the  MIPS count/compare timer reference clock. At least due to this
> the PLL clock providers must be available at the system initialization stage.
> Platform device probe stage wouldn't work for us here.
> 
> Moreover we can't rely on the order of the devices probe procedure, because
> the CCU PLLs and CCU dividers will be sub-nodes of the Baikal-T1 System Controller,
> which will be a sub-node of the APB-bus node consuming the corresponding CCU
> divider clock. The CCU PLL and divider clocks are utilized literally by each
> device in the system, so there might be too many PROBE_DEFER errors before we get
> to the clock devices binding with drivers.
> 
> To sum up I'd leave both PLL, AXI-bus and System device dividers
> clocks being populated in the init stage by means of the CLK_OF_DECLARE
> macro.
> 
> > 
> > > diff --git a/drivers/clk/baikal-t1/common.h b/drivers/clk/baikal-t1/common.h
> > > new file mode 100644
> > > index 000000000000..07c8d67f5275
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/common.h
> > > @@ -0,0 +1,44 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Baikal-T1 CCU common methods.
> > > + */
> > > +#ifndef __CLK_BT1_COMMON_H__
> > > +#define __CLK_BT1_COMMON_H__
> > > +
> > > +#include <linux/debugfs.h>
> > > +#include <linux/io.h>
> > > +
> > > +#define CCU_GET_FLD(_name, _data) \
> > > +       (((u32)(_data) & _name ## _MASK) >> _name ## _FLD)
> > > +
> > > +#define CCU_SET_FLD(_name, _data, _val) \
> > > +       (((u32)(_data) & ~_name ## _MASK) | \
> > > +       (((u32)(_val) << _name ## _FLD) & _name ## _MASK))
> > > +
> > > +#define CCU_DBGFS_REG(_name, _off)     \
> > > +{                                      \
> > > +       .name = _name,                  \
> > > +       .offset = _off                  \
> > > +}
> > > +
> > > +static inline u32 ccu_read(void __iomem *reg)
> > > +{
> > > +       return readl(reg);
> > > +}
> > > +
> > > +static inline void ccu_write(void __iomem *reg, u32 data)
> > > +{
> > > +       writel(data, reg);
> > > +}
> > > +
> > > +static inline void ccu_update(void __iomem *reg, u32 mask, u32 data)
> > > +{
> > > +       u32 old;
> > > +
> > > +       old = readl_relaxed(reg);
> > > +       writel((old & ~mask) | (data & mask), reg);
> > > +}
> > 
> > Please just write the code in line as readl/writel/etc. This whole file
> > doesn't look necessary.
> 
> Agreed.
> 
> ---
> 
> Stephen, thank you very much for the very thorough review. v2 will be
> sent shortly after I get answers to a few question I've asked in this
> message, after I fix the issues you raised here and get to refactor
> the System Controller DT node design described in the RFC
> (https://lkml.org/lkml/2020/3/22/393). Should you have any comments
> regarding it please reply right to the RFC. You must have received a
> copy in your email.
> 
> I'll also do my best to take into account the fixes suggested here by you
> in [Patch 5/5] "clk: Add Baikal-T1 CCU dividers driver" of this
> patchset.
> 
> Regards,
> -Sergey


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

* Re: [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver
  2020-04-07 12:08     ` Sergey Semin
  2020-04-16 19:29       ` Sergey Semin
@ 2020-04-26  6:16       ` Sergey Semin
  1 sibling, 0 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-26  6:16 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: fancer.lancer, Michael Turquette, Alexey Malahov,
	Thomas Bogendoerfer, Paul Burton, Ralf Baechle, linux-kernel,
	linux-clk

Hello Stephen,
It has been a while since I responded with a few backward questions. Do you
have anything to answer? I am going to send v2 soon, so I need to have those
questions cleared before that.

Regards,
-Sergey

On Tue, Apr 07, 2020 at 03:08:35PM +0300, Sergey Semin wrote:
> On Tue, Mar 10, 2020 at 08:30:15AM -0700, Stephen Boyd wrote:
> > Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:47)
> > > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > 
> > > Baikal-T1 is supposed to be supplied with a high-frequency external
> > > oscillator. But in order to create signals suitable for each IP-block
> > > embedded into the SoC the oscillator output is primarily connected to
> > > a set of CCU PLLs. There are five of them to create clocks for the MIPS
> > > P5600 cores, the embedded DDR controller, SATA, Ethernet and PCIe
> > > domains. The last three domains though named by the biggest system
> > > interfaces in fact include nearly all of the rest SoC peripherals.
> > > Each of the PLLs is based on True Circuits TSMC CLN28HPM IP-core with
> > > an interface wrapper (so called safe PLL' clocks switcher) to simplify
> > > the PLL configuration procedure.
> > > 
> > > This driver creates the of-based hardware clocks to use them then in
> > > the corresponding subsystems. In order to simplify the driver code we
> > > split the functionality up into the PLLs clocks operations and hardware
> > > clocks declaration/registration procedures. So if CLK_BT1_CCU is
> > > defined, then the first part is available in the kernel, while
> > > CLK_BT1_CCU_PLL config makes the actual clocks being registered at the
> > > time of_clk_init() is called.
> > > 
> > > Even though the PLLs are based on the same IP-core, they actually may
> > > have some differences. In particular, some CCU PLLs supports the output
> > > clock change without gating them (like CPU or PCIe PLLs), while the
> > > others don't, some CCU PLLs are critical and aren't supposed to be
> > > gated. In order to cover all of these cases the hardware clocks driver
> > > is designed with a info-descriptor pattern. So there are special static
> > > descriptors declared for each PLL, which is then used to create a
> > > hardware clock with proper operations. Additionally debugfs-files are
> > > provided for each PLL' field to make sure the implemented
> > > rate-PLLs-dividers calculation algorithm is correct.
> > > 
> > > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> > > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > > Cc: Paul Burton <paulburton@kernel.org>
> > > Cc: Ralf Baechle <ralf@linux-mips.org>
> > 
> > > diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig
> > > new file mode 100644
> > > index 000000000000..0e2fc86f3ab8
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/Kconfig
> > > @@ -0,0 +1,35 @@
> > > +# SPDX-License-Identifier: GPL-2.0
> > > +config CLK_BAIKAL_T1
> > > +       bool "Baikal-T1 Clocks Control Unit interface"
> > > +       depends on (MIPS_BAIKAL_T1 || COMPILE_TEST) && OF
> > 
> > Does it actually depend on OF for any compilation?
> 
> Hm, good question. AFAICS it doesn't. The compilation should work for
> both of these configs. While AFAIU it is meaningful to have
> (MIPS_BAIKAL_T1 && OF) only instead of making the compile test depending
> on the OF as well.
> 
> On the other hand MIPS_BAIKAL_T1 indirectly selects OF. I knew about
> this reverse dependency, but still preferred to signify that the driver
> relies on the OF functionality to properly work. What do you think would
> be the best solution in this case:
> 1) just drop OF, which would mean to have (MIPS_BAIKAL_T1 ||
> COMPILE_TEST),
> 2) redefine the statement to (MIPS_BAIKAL_T1 && OF) || COMPILE_TEST ?
> 
> BTW AFAICS OF API is dummy defined if no CONFIG_OF is enabled. So there
> is no need in having OF-dependency for any compile-test usecase. Right?
> Do you know any case when it's required? For instance, I've found the
> similar OF-based conditional statement for CLK_QORIQ,
> COMMON_CLK_FIXED_MMIO and COMMON_CLK_KEYSTONE. Do you think these
> configs are wrong and have to be fixed by removing the "&& OF" statement
> as well?
> 
> > 
> > > +       default y
> > 
> > Please no default y. Maybe default MIPS_BAIKAL_T1?
> 
> Do you mean that in case if COMPILE_TEST is enabled only, the
> drivers below shouldn't be available by default? If so then ok.
> I'll use MIPS_BAIKAL_T1 here. If no, then "default y" would be more
> appropriate.
> 
> > 
> > > +       help
> > > +         Clocks Control Unit is the core of Baikal-T1 SoC responsible for the
> > > +         chip subsystems clocking and resetting. It consists of multiple
> > > +         global clock domains, which can be reset by means of the CCU control
> > > +         registers. These domains and devices placed in them are fed with
> > > +         clocks generated by a hierarchy of PLLs, configurable and fixed
> > > +         dividers. In addition CCU exposes several unrelated functional blocks
> > > +         like irqless Designware i2c controller with indirectly accessed
> > > +         registers, AXI bus errors detector, DW PCIe controller PM/clocks/reset
> > > +         manager, etc.
> > > +
> > > +         This driver provides a set of functions to create the kernel clock
> > > +         devices of Baikal-T1 PLLs and dividers, and to manipulate the reset
> > > +         signals of the SoC.
> > > +
> > > +if CLK_BAIKAL_T1
> > > +
> > > +config CLK_BT1_CCU_PLL
> > > +       bool "Baikal-T1 CCU PLLs support"
> > > +       default y
> > 
> > default MIPS_BAIKAL_T1?
> 
> see the comment above.
> 
> > 
> > > +       help
> > > +         Enable this to support the PLLs embedded into the Baikal-T1 SoCs.
> > > +         These are five PLLs placed at the root of the clocks hierarchy,
> > > +         right after the external reference osciallator (normally of 25MHz).
> > > +         They are used to generate a high frequency signals, which are
> > 
> > s/generate a high/generate high/
> 
> Ok.
> 
> > 
> > > +         either directly wired to the consumers (like CPUs, DDR) or passed
> > > +         over the clock dividers to be only then used as an individual
> > > +         reference clocks of a target device.
> > 
> > s/clocks/clock/
> 
> Ok.
> 
> > 
> > > +
> > > +endif
> > > diff --git a/drivers/clk/baikal-t1/ccu-pll.c b/drivers/clk/baikal-t1/ccu-pll.c
> > > new file mode 100644
> > > index 000000000000..f2087a80b64d
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/ccu-pll.c
> > > @@ -0,0 +1,474 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Authors:
> > > + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> > > + *
> > > + * Baikal-T1 CCU PLL interface driver.
> > > + */
> > > +
> > > +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/printk.h>
> > > +#include <linux/limits.h>
> > > +#include <linux/bits.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/of.h>
> > > +#include <linux/spinlock.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/time64.h>
> > > +#include <linux/rational.h>
> > > +#include <linux/debugfs.h>
> > > +
> > > +#include "ccu-pll.h"
> > > +#include "common.h"
> > > +
> > > +#define CCU_PLL_CTL                    0x00
> > > +#define CCU_PLL_CTL_EN                 BIT(0)
> > > +#define CCU_PLL_CTL_RST                        BIT(1)
> > > +#define CCU_PLL_CTL_CLKR_FLD           2
> > > +#define CCU_PLL_CTL_CLKR_MASK          GENMASK(7, CCU_PLL_CTL_CLKR_FLD)
> > > +#define CCU_PLL_CTL_CLKF_FLD           8
> > > +#define CCU_PLL_CTL_CLKF_MASK          GENMASK(20, CCU_PLL_CTL_CLKF_FLD)
> > > +#define CCU_PLL_CTL_CLKOD_FLD          21
> > > +#define CCU_PLL_CTL_CLKOD_MASK         GENMASK(24, CCU_PLL_CTL_CLKOD_FLD)
> > > +#define CCU_PLL_CTL_BYPASS             BIT(30)
> > > +#define CCU_PLL_CTL_LOCK               BIT(31)
> > > +#define CCU_PLL_CTL1                   0x04
> > > +#define CCU_PLL_CTL1_BWADJ_FLD         3
> > > +#define CCU_PLL_CTL1_BWADJ_MASK                GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD)
> > > +
> > > +#define CCU_PLL_RST_DELAY_US           5
> > > +#define CCU_PLL_LOCK_DELAY_US(_ref_rate, _nr) ({       \
> > > +       uint64_t _n = 500ULL * (_nr) * USEC_PER_SEC;    \
> > > +       do_div(_n, _ref_rate);                          \
> > > +       _n;                                             \
> > > +})
> > 
> > Can this be a static inline function? We get to learn what the types are
> > then.
> 
> Ok I'll convert it to the explicit static inline version cause' you asked it.
> Personally I don't see the necessity of this here. The arithmetics is
> converted to u64, which is more than enough to cover any possible
> nr-value.
> 
> > 
> > > +#define CCU_PLL_LOCK_CHECK_RETRIES     50
> > > +
> > > +#define CCU_PLL_NR_MAX \
> > > +       ((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1)
> > > +#define CCU_PLL_NF_MAX \
> > > +       ((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1)
> > > +#define CCU_PLL_OD_MAX \
> > > +       ((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1)
> > > +#define CCU_PLL_NB_MAX \
> > > +       ((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1)
> > > +#define CCU_PLL_FDIV_MIN               427000UL
> > > +#define CCU_PLL_FDIV_MAX               3500000000UL
> > > +#define CCU_PLL_FOUT_MIN               200000000UL
> > > +#define CCU_PLL_FOUT_MAX               2500000000UL
> > > +#define CCU_PLL_FVCO_MIN               700000000UL
> > > +#define CCU_PLL_FVCO_MAX               3500000000UL
> > > +#define CCU_PLL_CLKOD_FACTOR           2
> > > +
> > > +#define CCU_PLL_CALC_FREQ(_ref_rate, _nr, _nf, _od) \
> > > +       ((_ref_rate) / (_nr) * (_nf) / (_od))
> > 
> > Static inline function please. Does this need to be 64-bit math?
> 
> Ok for the same reason. In our platform 32-bit data is enough here.
> Though you are right. Some NF may overflow the unsigned long ref_rate
> data. I'll add the 64-bit arithmetics in this function too.
> 
> BTW I thought about 64-bits here in first place, but then I decided it
> may cause the performance drop in ccu_pll_calc_factors() and in
> ccu_pll_recalc_rate() so I left the unsigned long calc here.
> 
> > 
> > > +
> > > +static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk,
> > > +                        unsigned long nr)
> > > +{
> > > +       unsigned long ud;
> > > +       int count;
> > > +       u32 val;
> > > +
> > > +       ud = CCU_PLL_LOCK_DELAY_US(ref_clk, nr);
> > > +
> > > +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_RST, CCU_PLL_CTL_RST);
> > > +
> > > +       count = CCU_PLL_LOCK_CHECK_RETRIES;
> > > +       do {
> > > +               udelay(ud);
> > > +               val = ccu_read(pll->regs + CCU_PLL_CTL);
> > > +       } while (!(val & CCU_PLL_CTL_LOCK) && --count);
> > > +
> > > +       return (val & CCU_PLL_CTL_LOCK) ? 0 : -ETIMEDOUT;
> > 
> > Looks like readl_poll_timeout().
> 
> Nice catch. readl_poll_timeout() perfectly fits here. Though the atomic
> version is more appropriate, since the delays are normally small and the
> method is executed in the atomic context.
> 
> > 
> > > +}
> > > +
> > > +static int ccu_pll_enable(struct clk_hw *hw)
> > > +{
> > > +       struct clk_hw *parent_hw = clk_hw_get_parent(hw);
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       unsigned long flags;
> > > +       int ret;
> > > +       u32 val;
> > > +
> > > +       if (!parent_hw) {
> > > +               pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw));
> > > +               return -EINVAL;
> > > +       }
> > > +
> > > +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> > > +       if (val & CCU_PLL_CTL_EN)
> > > +               return 0;
> > > +
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);
> > > +       ccu_write(pll->regs + CCU_PLL_CTL, val | CCU_PLL_CTL_EN);
> > > +       ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw),
> > > +                           CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1);
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> > > +       if (ret)
> > > +               pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw));
> > > +
> > > +       return ret;
> > > +}
> > > +
> > > +static void ccu_pll_disable(struct clk_hw *hw)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       unsigned long flags;
> > > +
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);
> > > +       ccu_update(pll->regs + CCU_PLL_CTL, CCU_PLL_CTL_EN, 0);
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);
> > > +}
> > > +
> > > +static int ccu_pll_is_enabled(struct clk_hw *hw)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +
> > > +       return !!(ccu_read(pll->regs + CCU_PLL_CTL) & CCU_PLL_CTL_EN);
> > > +}
> > > +
> > > +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
> > > +                                        unsigned long parent_rate)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       unsigned long nr, nf, od;
> > > +       u32 val;
> > > +
> > > +       val = ccu_read(pll->regs + CCU_PLL_CTL);
> > > +       nr = CCU_GET_FLD(CCU_PLL_CTL_CLKR, val) + 1;
> > 
> > We have FIELD_GET macros for this. Please use them instead of creating
> > your own.
> 
> Right. I'll use it instead.
> 
> > 
> > > +       nf = CCU_GET_FLD(CCU_PLL_CTL_CLKF, val) + 1;
> > > +       od = CCU_GET_FLD(CCU_PLL_CTL_CLKOD, val) + 1;
> > > +
> > > +       return CCU_PLL_CALC_FREQ(parent_rate, nr, nf, od);
> > > +}
> > > +
> > > +static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate,
> > > +                                unsigned long *nr, unsigned long *nf,
> > > +                                unsigned long *od)
> > > +{
> > > +       unsigned long err, freq, min_err = ULONG_MAX;
> > > +       unsigned long num, denom, n1, d1, nri;
> > > +       unsigned long nr_max, nf_max, od_max;
> > > +
> > > +       /*
> > > +        * Make sure PLL is working with valid input signal (Fdiv). If
> > > +        * you want to speed the function up just reduce CCU_PLL_NR_MAX.
> > > +        * This will cause a worse approximation though.
> > > +        */
> > > +       nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1;
> > > +       nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX);
> > > +
> > > +       /*
> > > +        * Find a closest [nr;nf;od] vector taking into account the
> > > +        * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is
> > > +        * either 1 or even number within the acceptable range (alas 1s
> > > +        * is also excluded by the next loop).
> > > +        */
> > > +       for (; nri <= nr_max; ++nri) {
> > > +               /* Use Od factor to fulfill the limitation 2). */
> > > +               num = CCU_PLL_CLKOD_FACTOR * rate;
> > > +               denom = parent_rate / nri;
> > > +
> > > +               /*
> > > +                * Make sure Fvco is within the acceptable range to fulfill
> > > +                * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value
> > > +                * the actual upper limit is also divided by that factor.
> > > +                * It's not big problem for us since practically there is no
> > > +                * need in clocks with that high frequency.
> > > +                */
> > > +               nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX);
> > > +               od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR;
> > > +
> > > +               /*
> > > +                * Bypass the out-of-bound values, which can't be properly
> > > +                * handled by the rational fraction approximation algorithm.
> > > +                */
> > > +               if (num / denom >= nf_max) {
> > > +                       n1 = nf_max;
> > > +                       d1 = 1;
> > > +               } else if (denom / num >= od_max) {
> > > +                       n1 = 1;
> > > +                       d1 = od_max;
> > > +               } else {
> > > +                       rational_best_approximation(num, denom, nf_max, od_max,
> > > +                                                   &n1, &d1);
> > > +               }
> > > +
> > > +               /* Select the best approximation of the target rate. */
> > > +               freq = (((parent_rate / nri) * n1) / d1);
> > 
> > Please drop extra parenthesis.
> 
> Ok. Thanks for noticing this. I'll replace it with ccu_pll_calc_freq() function.
> 
> > 
> > > +               err = abs((int64_t)freq - num);
> > > +               if (err < min_err) {
> > > +                       min_err = err;
> > > +                       *nr = nri;
> > > +                       *nf = n1;
> > > +                       *od = CCU_PLL_CLKOD_FACTOR * d1;
> > > +               }
> > > +       }
> > > +}
> > > +
> ...
> > > +
> > > +#ifdef CONFIG_DEBUG_FS
> > > +
> > > +#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask)                     \
> > > +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +                                                                       \
> > > +       *val = !!(ccu_read(pll->regs + (_reg)) & (_mask));              \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +       unsigned long flags;                                            \
> > > +                                                                       \
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> > > +       ccu_update(pll->regs + (_reg), (_mask), val ? (_mask) : 0);     \
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> > > +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> > > +
> > > +#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _fld, _min, _max)          \
> > > +static int ccu_pll_dbgfs_##_name##_get(void *priv, u64 *val)           \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +       u32 data;                                                       \
> > > +                                                                       \
> > > +       data = ccu_read(pll->regs + (_reg));                            \
> > > +       *val = CCU_GET_FLD(_fld, data) + 1;                             \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +static int ccu_pll_dbgfs_##_name##_set(void *priv, u64 val)            \
> > > +{                                                                      \
> > > +       struct ccu_pll *pll = priv;                                     \
> > > +       unsigned long flags;                                            \
> > > +       u32 data;                                                       \
> > > +                                                                       \
> > > +       val = clamp_t(u64, val, _min, _max);                            \
> > > +       data = CCU_SET_FLD(_fld, 0, val - 1);                           \
> > > +                                                                       \
> > > +       spin_lock_irqsave(&pll->regs_lock, flags);                      \
> > > +       ccu_update(pll->regs + (_reg), _fld ## _MASK, data);            \
> > > +       spin_unlock_irqrestore(&pll->regs_lock, flags);                 \
> > > +                                                                       \
> > > +       return 0;                                                       \
> > > +}                                                                      \
> > > +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_##_name##_fops,                 \
> > > +       ccu_pll_dbgfs_##_name##_get, ccu_pll_dbgfs_##_name##_set, "%llu\n")
> > > +
> > > +CCU_PLL_DBGFS_BIT_ATTR(en, CCU_PLL_CTL, CCU_PLL_CTL_EN);
> > > +CCU_PLL_DBGFS_BIT_ATTR(rst, CCU_PLL_CTL, CCU_PLL_CTL_RST);
> > > +CCU_PLL_DBGFS_FLD_ATTR(nr, CCU_PLL_CTL, CCU_PLL_CTL_CLKR, 1, CCU_PLL_NR_MAX);
> > > +CCU_PLL_DBGFS_FLD_ATTR(nf, CCU_PLL_CTL, CCU_PLL_CTL_CLKF, 1, CCU_PLL_NF_MAX);
> > > +CCU_PLL_DBGFS_FLD_ATTR(od, CCU_PLL_CTL, CCU_PLL_CTL_CLKOD, 1, CCU_PLL_OD_MAX);
> > > +CCU_PLL_DBGFS_BIT_ATTR(bypass, CCU_PLL_CTL, CCU_PLL_CTL_BYPASS);
> > > +CCU_PLL_DBGFS_BIT_ATTR(lock, CCU_PLL_CTL, CCU_PLL_CTL_LOCK);
> > > +CCU_PLL_DBGFS_FLD_ATTR(nb, CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ, 1, CCU_PLL_NB_MAX);
> > > +
> > > +static const struct debugfs_reg32 ccu_pll_dbgfs_regs[] = {
> > > +       CCU_DBGFS_REG("ctl", CCU_PLL_CTL),
> > > +       CCU_DBGFS_REG("ctl1", CCU_PLL_CTL1)
> > > +};
> > > +
> > > +static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
> > > +{
> > > +       struct ccu_pll *pll = to_ccu_pll(hw);
> > > +       struct debugfs_regset32 *regset;
> > > +
> > > +       regset = kzalloc(sizeof(*regset), GFP_KERNEL);
> > > +       if (!regset)
> > > +               return;
> > > +
> > > +       regset->regs = ccu_pll_dbgfs_regs;
> > > +       regset->nregs = ARRAY_SIZE(ccu_pll_dbgfs_regs);
> > > +       regset->base = pll->regs;
> > > +       debugfs_create_regset32("registers", 0400, dentry, regset);
> > > +
> > > +       debugfs_create_file_unsafe("en", 0600, dentry, pll,
> > 
> > Why unsafe?
> 
> I don't need full proxy file-operations in this case. Moreover since all
> fops are defined by means of DEFINE_DEBUGFS_ATTRIBUTE() the set/get
> callbacks used will be automatically protected against the corresponding
> files removal. See the comment to the debugfs_create_file_unsafe() for
> details and commit e7e6198c6056 ("clk: tegra: dfll: Fix debugfs_simple_attr.cocci
> warnings") as an example of why unsafe is appropriate here. BTW The commit
> has got your SoB.
> 
> > 
> > > +                                  &ccu_pll_dbgfs_en_fops);
> > > +       debugfs_create_file_unsafe("rst", 0200, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_rst_fops);
> > > +       debugfs_create_file_unsafe("nr", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_nr_fops);
> > > +       debugfs_create_file_unsafe("nf", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_nf_fops);
> > > +       debugfs_create_file_unsafe("od", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_od_fops);
> > > +       debugfs_create_file_unsafe("bypass", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_bypass_fops);
> > > +       debugfs_create_file_unsafe("lock", 0400, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_lock_fops);
> > > +       debugfs_create_file_unsafe("nb", 0600, dentry, pll,
> > > +                                  &ccu_pll_dbgfs_nb_fops);
> > > +}
> > 
> > Is there any usage of these outside of development of this driver? I'd
> > rather not see us expose blanket register write access with code in
> > debugfs. If you're interested in poking register why not use /dev/mem?
> 
> These parameters are indeed mostly useful in the driver development. But
> they can be useful to diagnose the peripheral device problems like data
> corruption or weird clock consumers behaviour. Moreover some of the
> parameters aren't utilized by the driver like bypass and nb, but might
> be theoretically useful in fixing the problems.
> 
> Anyway I do understand your concern regarding write access to these
> fields. What about if I implemented the same approach as the one used in
> drivers/clk/clk.c? By default the fields are exported in RO-mode.
> If a developer needs to debug a clock-based problem one will need to
> recompile the driver with #define CLOCK_ALLOW_WRITE_DEBUGFS macro
> specified. What do you think?
> 
> > 
> > > +
> > > +#else /* !CONFIG_DEBUG_FS */
> > > +
> > > +#define ccu_pll_debug_init NULL
> > > +
> > > +#endif /* !CONFIG_DEBUG_FS */
> > > +
> > > +static const struct clk_ops ccu_pll_gate_to_set_ops = {
> > > +       .enable = ccu_pll_enable,
> > > +       .disable = ccu_pll_disable,
> > > +       .is_enabled = ccu_pll_is_enabled,
> > > +       .recalc_rate = ccu_pll_recalc_rate,
> > > +       .round_rate = ccu_pll_round_rate,
> > > +       .set_rate = ccu_pll_set_rate_norst,
> > > +       .debug_init = ccu_pll_debug_init
> > > +};
> > > +
> > > +static const struct clk_ops ccu_pll_straight_set_ops = {
> > > +       .enable = ccu_pll_enable,
> > > +       .disable = ccu_pll_disable,
> > > +       .is_enabled = ccu_pll_is_enabled,
> > > +       .recalc_rate = ccu_pll_recalc_rate,
> > > +       .round_rate = ccu_pll_round_rate,
> > > +       .set_rate = ccu_pll_set_rate_reset,
> > > +       .debug_init = ccu_pll_debug_init
> > > +};
> > > +
> > > +struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *pll_init)
> > 
> > Can pll_init be const?
> 
> Can't believe I missed this. Yes, it can. Thanks.
> 
> > 
> > > +{
> > > +       struct clk_parent_data parent_data = {0};
> > > +       struct clk_init_data hw_init = {0};
> > > +       struct ccu_pll *pll;
> > > +       int ret;
> > > +
> > > +       if (!pll_init)
> > > +               return ERR_PTR(-EINVAL);
> > > +
> > > +       pll = kzalloc(sizeof(*pll), GFP_KERNEL);
> > > +       if (!pll)
> > > +               return ERR_PTR(-ENOMEM);
> > > +
> > > +       pll->hw.init = &hw_init;
> > > +       pll->id = pll_init->id;
> > > +       pll->regs = pll_init->regs;
> > > +       spin_lock_init(&pll->regs_lock);
> > > +
> > > +       hw_init.name = pll_init->name;
> > > +       hw_init.flags = CLK_IGNORE_UNUSED;
> > 
> > Why ignore unused? Please have a comment.
> 
> Ok. I thought it was obvious, since the flag naming said by itself.
> These PLLs are supposed to be left enabled even if they are unused by
> any of the drivers. System bootloader makes sure that required clocks
> are enabled by default like CPU and DDR PLLs. So we don't want to let
> the clock subsystem to disable the PLLs if they are left unused.
> 
> > 
> > > +
> > > +       if (pll_init->flags & CCU_PLL_GATE_TO_SET) {
> > > +               hw_init.flags |= CLK_SET_RATE_GATE;
> > > +               hw_init.ops = &ccu_pll_gate_to_set_ops;
> > > +       } else {
> > > +               hw_init.ops = &ccu_pll_straight_set_ops;
> > > +       }
> > > +
> > > +       if (pll_init->flags & CCU_PLL_CRITICAL)
> > > +               hw_init.flags |= CLK_IS_CRITICAL;
> > > +
> > > +       if (!pll_init->parent_name) {
> > > +               ret = -EINVAL;
> > > +               goto err_free_pll;
> > > +       }
> > > +       parent_data.fw_name = pll_init->parent_name;
> > > +       hw_init.parent_data = &parent_data;
> > > +       hw_init.num_parents = 1;
> > > +
> > > +       ret = of_clk_hw_register(pll_init->np, &pll->hw);
> > > +       if (ret)
> > > +               goto err_free_pll;
> > > +
> > > +       return pll;
> > > +
> > > +err_free_pll:
> > > +       kfree(pll);
> > > +
> > > +       return ERR_PTR(ret);
> > > +}
> > > +
> > > +void ccu_pll_hw_unregister(struct ccu_pll *pll)
> > > +{
> > > +       if (!pll)
> > > +               return;
> > 
> > That would be quite bad. Just let that blow up if so.
> 
> Ok.
> 
> > 
> > > +
> > > +       clk_hw_unregister(&pll->hw);
> > > +
> > > +       kfree(pll);
> > > +}
> > > diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h
> > > new file mode 100644
> > > index 000000000000..6921516311fb
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/ccu-pll.h
> > > @@ -0,0 +1,73 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Baikal-T1 CCU PLL interface driver.
> > > + */
> > > +#ifndef __CLK_BT1_CCU_PLL_H__
> > > +#define __CLK_BT1_CCU_PLL_H__
> > > +
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/spinlock.h>
> > > +#include <linux/bits.h>
> > > +#include <linux/of.h>
> > > +
> > > +/*
> > > + * CCU PLL private flags.
> > > + * @CCU_PLL_GATE_TO_SET: Some PLLs output clock can't be changed on-the-fly,
> > > + *                      so according to documentation they need to be gated
> > > + *                      first.
> > > + * @CCU_PLL_CRITICAL: Even though there is a way to switch any PLL off, there
> > > + *                   might be some, which shouldn't be in any case.
> > > + */
> > > +#define CCU_PLL_GATE_TO_SET            BIT(0)
> > > +#define CCU_PLL_CRITICAL               BIT(1)
> > > +
> > > +/*
> > > + * struct ccu_pll_init_data - CCU PLL initialization data.
> > > + * @id: Clock private identifier.
> > > + * @regs: PLL registers base address.
> > > + * @name: Clocks name.
> > > + * @parent_name: Clocks parent name in a fw node.
> > > + * @np: Pointer to the node describing the CCU PLLs.
> > > + * @flags: PLL private flags.
> > > + */
> > > +struct ccu_pll_init_data {
> > > +       unsigned int id;
> > > +       void __iomem *regs;
> > > +       const char *name;
> > > +       const char *parent_name;
> > > +       struct device_node *np;
> > > +       unsigned long flags;
> > > +};
> > > +
> > > +/*
> > > + * struct ccu_pll - CCU PLL descriptor.
> > 
> > Please drop the full stop on title
> 
> Ok.
> 
> > 
> > > + * @hw: clk_hw of the PLL.
> > > + * @id: Clock private identifier.
> > > + * @regs: PLL registers base address.
> > > + * @regs_lock: The registers exclusive access spin-lock.
> > > + */
> > > +struct ccu_pll {
> > > +       struct clk_hw hw;
> > > +       unsigned int id;
> > > +       void __iomem *regs;
> > > +       spinlock_t regs_lock;
> > > +};
> > > +#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw)
> > > +
> > > +static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll)
> > > +{
> > > +       return pll ? &pll->hw : NULL;
> > > +}
> > > +
> > > +static inline unsigned int ccu_pll_get_clk_id(struct ccu_pll *pll)
> > > +{
> > > +       return pll ? pll->id : -1;
> > 
> > It's unsigned return value, but return -1? Can this be inlined in the
> > one call site and if !pll just continue instead of having to turn it
> > into a negative value returned through an unsigned int?
> 
> Right, unsigned int, but return -1, which was supposed to be an invalid
> value. (unsigned int)-1 => 0xFFFFFFFF is good portable way of setting
> FFs to any int.
> 
> Anyway your solution seems better. I'll use it instead.
> 
> > 
> > > +}
> > > +
> > > +extern struct ccu_pll *ccu_pll_hw_register(struct ccu_pll_init_data *init);
> > > +
> > > +extern void ccu_pll_hw_unregister(struct ccu_pll *pll);
> > > +
> > > +#endif /* __CLK_BT1_CCU_PLL_H__ */
> > > diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c
> > > new file mode 100644
> > > index 000000000000..990f0a4a12f9
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/clk-ccu-pll.c
> > > @@ -0,0 +1,217 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Authors:
> > > + *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > + *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
> > > + *
> > > + * Baikal-T1 CCU PLL clocks driver.
> > > + */
> > > +
> > > +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/printk.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/of.h>
> > > +#include <linux/of_address.h>
> > > +#include <linux/ioport.h>
> > > +
> > > +#include <dt-bindings/clock/bt1-ccu.h>
> > > +
> > > +#include "ccu-pll.h"
> > > +
> > > +#define CCU_CPU_PLL_BASE               0x000
> > > +#define CCU_SATA_PLL_BASE              0x008
> > > +#define CCU_DDR_PLL_BASE               0x010
> > > +#define CCU_PCIE_PLL_BASE              0x018
> > > +#define CCU_ETH_PLL_BASE               0x020
> > > +
> > > +#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags)        \
> > > +       {                                               \
> > > +               .id = _id,                              \
> > > +               .name = _name,                          \
> > > +               .parent_name = _pname,                  \
> > > +               .base = _base,                          \
> > > +               .flags = _flags                         \
> > > +       }
> > > +
> > > +#define CCU_PLL_NUM                    ARRAY_SIZE(pll_info)
> > > +
> > > +struct ccu_pll_info {
> > > +       unsigned int id;
> > > +       const char *name;
> > > +       const char *parent_name;
> > > +       unsigned int base;
> > > +       unsigned long flags;
> > > +};
> > > +
> > > +static const struct ccu_pll_info pll_info[] = {
> > > +       CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL),
> > > +       CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> > 
> > Please comment all CLK_IS_CRITICAL usage
> 
> Ok. CPU and DDR PLLs are sources of CPU cores and DDR controller reference clocks,
> which obviously shouldn't be ever gated. SATA and PCIe PLLs are the parents of
> APB-bus and DDR controller AXI-bus clocks, which if gated would cause
> the system being unusable too.
> 
> > and don't make your own version
> > of the same flags in the common clk framework. Just use the ones in the
> > framework.
> 
> This was my first design of the clock flags, but then I had to create a
> set of custom flags for the CCU dividers driver. That time I turned to
> be at a fork either to have two sets of flags: common clock and custom
> ones, or a single set of custom flags, some of which then I would
> translate to the common clock flags. So I've chosen the latter solution.
> I see you suggest the former. Since I was in doubt which one would be
> better, I'll implement yours then.
> 
> > 
> > > +       CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL | CCU_PLL_GATE_TO_SET),
> > > +       CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
> > > +                    CCU_PLL_CRITICAL),
> > > +       CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
> > > +                    CCU_PLL_GATE_TO_SET)
> > > +};
> > > +
> > > +struct ccu_pll_data {
> > > +       struct device_node *np;
> > > +       void __iomem *regs;
> > > +       struct ccu_pll *plls[CCU_PLL_NUM];
> > > +};
> > > +
> > > +static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
> > > +                                        unsigned int clk_id)
> > > +{
> > > +       struct ccu_pll *pll;
> > > +       int idx;
> > > +
> > > +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> > > +               pll = data->plls[idx];
> > > +               if (clk_id == ccu_pll_get_clk_id(pll))
> > > +                       return pll;
> > > +       }
> > > +
> > > +       return ERR_PTR(-EINVAL);
> > > +}
> > > +
> > > +static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
> > > +{
> > > +       struct ccu_pll_data *data;
> > > +
> > > +       data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > +       if (!data)
> > > +               return ERR_PTR(-ENOMEM);
> > > +
> > > +       data->np = np;
> > > +
> > > +       return data;
> > > +}
> > > +
> > > +static void ccu_pll_free_data(struct ccu_pll_data *data)
> > > +{
> > > +       kfree(data);
> > > +}
> > > +
> > > +static int ccu_pll_request_regs(struct ccu_pll_data *data)
> > > +{
> > > +       data->regs = of_io_request_and_map(data->np, 0,
> > > +                                          of_node_full_name(data->np));
> > > +       if (IS_ERR(data->regs)) {
> > > +               pr_err("Failed to request PLLs '%s' regs\n",
> > > +                       of_node_full_name(data->np));
> > > +               return PTR_ERR(data->regs);
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static void ccu_pll_release_regs(struct ccu_pll_data *data)
> > > +{
> > > +       struct resource res;
> > > +
> > > +       iounmap(data->regs);
> > > +
> > > +       /* Try to release the resource as well. */
> > > +       if (of_address_to_resource(data->np, 0, &res))
> > > +               return;
> > > +
> > > +       (void)release_mem_region(res.start, resource_size(&res));
> > > +}
> > 
> > Instead of having these three functions please inline them at the one call
> > site.
> 
> This time I disagree. Inlining them won't gain much of the performance
> benefit, while combination of weakly coherent code into a single function
> will cause worse readability. In my drivers I prefer separating unrelated
> or weakly related functionality from each other and creating a set of functions
> with coherent code (in this case it's memory allocation for the driver private
> data and registers mapping, which are only related in the order of execution).
> By this approach I improve the driver code readability making it better
> structured. So instead of having a pile of weakly coherent code in the probe
> I have a set of work methods and their antagonists (if necessary),
> which are called in the required order. Similar design I normally
> prefer in callbacks, event handlers, etc, if coherent code can't be
> embedded into them.
> 
> Moreover I don't see any requirement by which I would have to rearrange the
> code in this case, while following my way will perfectly fit to the
> kernel coding style described in Documentation/process/coding-style.rst as
> "Functions should be short and sweet, and do just one thing." Sorry, I
> would prefer to leave this part of the driver being structured as is.
> 
> Anyway, this part of the driver will be changed in v2 due to implementing
> an alternative approach of Baikal-T1 System Controller DT node design I've
> described in RFC: https://lkml.org/lkml/2020/3/22/393 . According to it I
> will use a syscon_node_to_regmap(parent) method to get the registers map
> from the parental node instead of requesting a dedicated registers range.
> 
> > 
> > > +
> > > +static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
> > > +                                           void *priv)
> > > +{
> > > +       struct ccu_pll_data *data = priv;
> > > +       struct ccu_pll *pll;
> > > +       unsigned int clk_id;
> > > +
> > > +       clk_id = clkspec->args[0];
> > > +       pll = ccu_pll_find_desc(data, clk_id);
> > > +       if (IS_ERR(pll)) {
> > > +               pr_info("Invalid PLL clock ID %d specified\n", clk_id);
> > > +               return ERR_CAST(pll);
> > > +       }
> > > +
> > > +       return ccu_pll_get_clk_hw(pll);
> > > +}
> > > +
> > > +static int ccu_pll_clk_register(struct ccu_pll_data *data)
> > > +{
> > > +       int idx, ret;
> > > +
> > > +       for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
> > > +               const struct ccu_pll_info *info = &pll_info[idx];
> > > +               struct ccu_pll_init_data init = {0};
> > > +
> > > +               init.id = info->id;
> > > +               init.regs = data->regs + info->base;
> > > +               init.parent_name = info->parent_name;
> > > +               init.np = data->np;
> > > +               init.flags = info->flags;
> > > +
> > > +               ret = of_property_read_string_index(data->np,
> > > +                       "clock-output-names", init.id, &init.name);
> > 
> > Do you need this property ever? It would be nice to not have it if
> > possible.
> 
> It isn't needed other than to provide a custom output clock names for the
> predefined set of clocks. I've seen it has been used for this purpose in
> many drivers of the common clk subsystem. Is this enough to have it
> supported in the driver or should I remove it anyway?
> 
> > 
> > > +               if (ret)
> > > +                       init.name = info->name;
> > > +
> > > +               data->plls[idx] = ccu_pll_hw_register(&init);
> > > +               if (IS_ERR(data->plls[idx])) {
> > > +                       ret = PTR_ERR(data->plls[idx]);
> > > +                       pr_err("Couldn't register PLL hw '%s'\n",
> > > +                               init.name);
> > > +                       goto err_hw_unregister;
> > > +               }
> > > +       }
> > > +
> > > +       ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
> > > +       if (ret) {
> > > +               pr_err("Couldn't register PLL provider of '%s'\n",
> > > +                       of_node_full_name(data->np));
> > > +               goto err_hw_unregister;
> > > +       }
> > > +
> > > +       return 0;
> > > +
> > > +err_hw_unregister:
> > > +       for (--idx; idx >= 0; --idx)
> > > +               ccu_pll_hw_unregister(data->plls[idx]);
> > > +
> > > +       return ret;
> > > +}
> > > +
> > > +static __init void ccu_pll_init(struct device_node *np)
> > > +{
> > > +       struct ccu_pll_data *data;
> > > +       int ret;
> > > +
> > > +       data = ccu_pll_create_data(np);
> > > +       if (IS_ERR(data))
> > > +               return;
> > > +
> > > +       ret = ccu_pll_request_regs(data);
> > > +       if (ret)
> > > +               goto err_free_data;
> > > +
> > > +       ret = ccu_pll_clk_register(data);
> > > +       if (ret)
> > > +               goto err_release_regs;
> > > +
> > > +       pr_info("CCU CPU/SATA/DDR/PCIe/Ethernet PLLs are initialized\n");
> > 
> > Please don't have I'm alive probe messages at info level. Just slows
> > down boot for developer comfort.
> 
> Ok.
> 
> > 
> > > +
> > > +       return;
> > > +
> > > +err_release_regs:
> > > +       ccu_pll_release_regs(data);
> > > +
> > > +err_free_data:
> > > +       ccu_pll_free_data(data);
> > > +}
> > > +CLK_OF_DECLARE(ccu_pll, "be,bt1-ccu-pll", ccu_pll_init);
> > 
> > Any reason this can't be a platform device driver?
> 
> I thought it was normal to have the clock devices available as early
> as possible. Especially when it comes to the system-wide PLLs and dividers.
> So the question is kind of surprising for me.
> 
> Anyway in my case the platform-specific code is using the CPU PLLs rate
> to get the  MIPS count/compare timer reference clock. At least due to this
> the PLL clock providers must be available at the system initialization stage.
> Platform device probe stage wouldn't work for us here.
> 
> Moreover we can't rely on the order of the devices probe procedure, because
> the CCU PLLs and CCU dividers will be sub-nodes of the Baikal-T1 System Controller,
> which will be a sub-node of the APB-bus node consuming the corresponding CCU
> divider clock. The CCU PLL and divider clocks are utilized literally by each
> device in the system, so there might be too many PROBE_DEFER errors before we get
> to the clock devices binding with drivers.
> 
> To sum up I'd leave both PLL, AXI-bus and System device dividers
> clocks being populated in the init stage by means of the CLK_OF_DECLARE
> macro.
> 
> > 
> > > diff --git a/drivers/clk/baikal-t1/common.h b/drivers/clk/baikal-t1/common.h
> > > new file mode 100644
> > > index 000000000000..07c8d67f5275
> > > --- /dev/null
> > > +++ b/drivers/clk/baikal-t1/common.h
> > > @@ -0,0 +1,44 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Baikal-T1 CCU common methods.
> > > + */
> > > +#ifndef __CLK_BT1_COMMON_H__
> > > +#define __CLK_BT1_COMMON_H__
> > > +
> > > +#include <linux/debugfs.h>
> > > +#include <linux/io.h>
> > > +
> > > +#define CCU_GET_FLD(_name, _data) \
> > > +       (((u32)(_data) & _name ## _MASK) >> _name ## _FLD)
> > > +
> > > +#define CCU_SET_FLD(_name, _data, _val) \
> > > +       (((u32)(_data) & ~_name ## _MASK) | \
> > > +       (((u32)(_val) << _name ## _FLD) & _name ## _MASK))
> > > +
> > > +#define CCU_DBGFS_REG(_name, _off)     \
> > > +{                                      \
> > > +       .name = _name,                  \
> > > +       .offset = _off                  \
> > > +}
> > > +
> > > +static inline u32 ccu_read(void __iomem *reg)
> > > +{
> > > +       return readl(reg);
> > > +}
> > > +
> > > +static inline void ccu_write(void __iomem *reg, u32 data)
> > > +{
> > > +       writel(data, reg);
> > > +}
> > > +
> > > +static inline void ccu_update(void __iomem *reg, u32 mask, u32 data)
> > > +{
> > > +       u32 old;
> > > +
> > > +       old = readl_relaxed(reg);
> > > +       writel((old & ~mask) | (data & mask), reg);
> > > +}
> > 
> > Please just write the code in line as readl/writel/etc. This whole file
> > doesn't look necessary.
> 
> Agreed.
> 
> ---
> 
> Stephen, thank you very much for the very thorough review. v2 will be
> sent shortly after I get answers to a few question I've asked in this
> message, after I fix the issues you raised here and get to refactor
> the System Controller DT node design described in the RFC
> (https://lkml.org/lkml/2020/3/22/393). Should you have any comments
> regarding it please reply right to the RFC. You must have received a
> copy in your email.
> 
> I'll also do my best to take into account the fixes suggested here by you
> in [Patch 5/5] "clk: Add Baikal-T1 CCU dividers driver" of this
> patchset.
> 
> Regards,
> -Sergey


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

* Re: [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings
  2020-04-05  9:59     ` Sergey Semin
  2020-04-16 19:27       ` Sergey Semin
@ 2020-04-26  6:18       ` Sergey Semin
  1 sibling, 0 replies; 19+ messages in thread
From: Sergey Semin @ 2020-04-26  6:18 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: fancer.lancer, Mark Rutland, Michael Turquette, Rob Herring,
	Alexey Malahov, Thomas Bogendoerfer, Paul Burton, Ralf Baechle,
	linux-clk, devicetree, linux-kernel

Hello Stephen,
It has been a while since I responded with a few backward questions. Do you
have anything to answer? I am going to send v2 soon, so I need to have those
questions cleared before that.

Regards,
-Sergey

On Sun, Apr 05, 2020 at 12:59:25PM +0300, Sergey Semin wrote:
> Hello Stephen,
> 
> Sorry for a delayed response. My answers to your comments are below.
> 
> On Mon, Mar 09, 2020 at 07:02:27PM -0700, Stephen Boyd wrote:
> > Quoting Sergey.Semin@baikalelectronics.ru (2020-03-06 05:00:44)
> > > From: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > 
> > > Baikal-T1 Clocks Control Unit is responsible for transformation of a
> > > signal coming from an external oscillator into clocks of various
> > > frequencies to propagate them then to the corresponding clocks
> > > consumers (either individual IP-blocks or clock domains). In order
> > > to create a set of high-frequency clocks the external signal is
> > > firstly handled by the embedded into CCU PLLs. So the corresponding
> > > dts-node is just a normal clock-provider node with standard set of
> > > properties.
> > > 
> > > Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> > > Signed-off-by: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> > 
> > SoB chain is backwards. Is Alexey the author? Or Co-developed-by?
> 
> Thanks for noticing this. I'll rearrange the SoB's in v2.
> 
> > 
> > > Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> > > Cc: Paul Burton <paulburton@kernel.org>
> > > Cc: Ralf Baechle <ralf@linux-mips.org>
> > > ---
> > >  .../bindings/clock/be,bt1-ccu-pll.yaml        | 139 ++++++++++++++++++
> > >  include/dt-bindings/clock/bt1-ccu.h           |  17 +++
> > >  2 files changed, 156 insertions(+)
> > >  create mode 100644 Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > >  create mode 100644 include/dt-bindings/clock/bt1-ccu.h
> > > 
> > > diff --git a/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > > new file mode 100644
> > > index 000000000000..f2e397cc147b
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/clock/be,bt1-ccu-pll.yaml
> > > @@ -0,0 +1,139 @@
> > > +# SPDX-License-Identifier: GPL-2.0
> > > +#
> > > +# Copyright (C) 2019 - 2020 BAIKAL ELECTRONICS, JSC
> > > +#
> > > +# Baikal-T1 Clocks Control Unit PLL Device Tree Bindings.
> > > +#
> > 
> > I don't think we need any of these comments besides the license
> > identifier line. Can you dual license this?
> > 
> 
> It's normal to have a copyright here, but in a single-lined form.
> I'll do this in v2 and also dual license the binding file.
> 
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/clock/be,bt1-ccu-pll.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: Baikal-T1 Clock Control Unit PLLs
> > > +
> > > +maintainers:
> > > +  - Serge Semin <fancer.lancer@gmail.com>
> > > +
> > > +description: |
> > > +  Clocks Control Unit is the core of Baikal-T1 SoC responsible for the chip
> > > +  subsystems clocking and resetting. The CCU is connected with an external
> > > +  fixed rate oscillator, which signal is transformed into clocks of various
> > > +  frequencies and then propagated to either individual IP-blocks or to groups
> > > +  of blocks (clock domains). The transformation is done by means of PLLs and
> > > +  gateable/non-gateable dividers embedded into the CCU. It's logically divided
> > > +  into the next components:
> > > +  1) External oscillator (normally XTAL's 25 MHz crystal oscillator, but
> > > +     in general can provide any frequency supported by the CCU PLLs).
> > > +  2) PLLs clocks generators (PLLs) - described in this bindings file.
> > > +  3) AXI-bus clock dividers (AXI).
> > > +  4) System devices reference clock dividers (SYS).
> > > +  which are connected with each other as shown on the next figure:
> > 
> > Please add a newline here
> 
> Ok.
> 
> > 
> > > +          +---------------+
> > > +          | Baikal-T1 CCU |
> > > +          |   +----+------|- MIPS P5600 cores
> > > +          | +-|PLLs|------|- DDR controller
> > > +          | | +----+      |
> > > +  +----+  | |  |  |       |
> > > +  |XTAL|--|-+  |  | +---+-|
> > > +  +----+  | |  |  +-|AXI|-|- AXI-bus
> > > +          | |  |    +---+-|
> > > +          | |  |          |
> > > +          | |  +----+---+-|- APB-bus
> > > +          | +-------|SYS|-|- Low-speed Devices
> > > +          |         +---+-|- High-speed Devices
> > > +          +---------------+
> > 
> > And here.
> > 
> 
> Ok
> 
> > > +  Each CCU sub-block is represented as a separate dts-node and has an
> > > +  individual driver to be bound with.
> > > +
> > > +  In order to create signals of wide range frequencies the external oscillator
> > > +  output is primarily connected to a set of CCU PLLs. There are five PLLs
> > > +  to create a clock for the MIPS P5600 cores, the embedded DDR controller,
> > > +  SATA, Ethernet and PCIe domains. The last three domains though named by the
> > > +  biggest system interfaces in fact include nearly all of the rest SoC
> > > +  peripherals. Each of the PLLs is based on True Circuits TSMC CLN28HPM core
> > > +  with an interface wrapper (so called safe PLL' clocks switcher) to simplify
> > > +  the PLL configuration procedure. The PLLs work as depicted on the next
> > > +  diagram:
> > 
> > Same, space out the diagrams.
> > 
> 
> Ok
> 
> > > +      +--------------------------+
> > > +      |                          |
> > > +      +-->+---+    +---+   +---+ |  +---+   0|\
> > > +  CLKF--->|/NF|--->|PFD|...|VCO|-+->|/OD|--->| |
> > > +          +---+ +->+---+   +---+ /->+---+    | |--->CLKOUT
> > > +  CLKOD---------C----------------+          1| |
> > > +       +--------C--------------------------->|/
> > > +       |        |                             ^
> > > +  Rclk-+->+---+ |                             |
> > > +  CLKR--->|/NR|-+                             |
> > > +          +---+                               |
> > > +  BYPASS--------------------------------------+
> > > +  BWADJ--->
> > > +  where Rclk is the reference clock coming  from XTAL, NR - reference clock
> > > +  divider, NF - PLL clock multiplier, OD - VCO output clock divider, CLKOUT -
> > > +  output clock, BWADJ is the PLL bandwidth adjustment parameter. At this moment
> > > +  the binding supports the PLL dividers configuration in accordance with a
> > > +  requested rate, while bypassing and bandwidth adjustment settings can be
> > > +  added in future if it gets to be necessary.
> > > +
> > > +  The PLLs CLKOUT is then either directly connected with the corresponding
> > > +  clocks consumer (like P5600 cores or DDR controller) or passed over a CCU
> > > +  divider to create a signal required for the clock domain.
> > > +
> > > +  The CCU PLL dts-node uses the common clock bindings [1] with no custom
> > > +  parameters. The list of exported clocks can be found in
> > > +  'dt-bindings/clock/bt1-ccu.h'.
> > > +
> > > +  [1] Documentation/devicetree/bindings/clock/clock-bindings.txt
> > 
> > Don't think we need to mention this binding anymore. But it's good that
> > we know what exported clock ids are.
> > 
> 
> Ok. I'll remove the legacy text binding file mention here and retain the
> reference to the header file with the clock IDs defined. The similar
> thing will be done for the others bindings in the patchset.
> 
> > > +
> > > +allOf:
> > > +  - $ref: /schemas/clock/clock.yaml#
> > > +
> > > +properties:
> > > +  compatible:
> > > +    const: be,bt1-ccu-pll
> > > +
> 
> > > +  reg:
> > > +    description: CCU PLLs sub-block base address.
> > > +    maxItems: 1
> > > +
> 
> Sometime ago I sent a RFC to Rob and you being in Cc there:
> https://lkml.org/lkml/2020/3/22/393
> Simply speaking there are several issues raised in comments to different
> patchsets, which are indirectly connected with the Baikal-T1 System Controller
> DT node design I've initially chosen. In accordance with that I've spread its
> functional blocks into different DT nodes with no reference to being related
> to the System Controller. Clock Control Unit nodes are amongst these blocks.
> Seeing such design caused these issues I suggested an alternative solution
> of having a single System Controller node and multiple functional sub-nodes.
> These sub-nodes will include the Clock Control Unit PLLs, AXI-bus and System
> Device blocks. I thoroughly described the solution in the RFC. So if no
> arguments against it pop up soon in the RFC comments, I'll implement it in
> v2 of this patchset as well. This solution cause the reg-property removal
> from this binding. Instead the drivers shall refer to the parental syscon
> node to get a regmap with CCU registers from it.
> 
> > > +  "#clock-cells":
> > > +    description: |
> > > +      Clocks are referenced by the node phandle and an unique identifier
> > > +      from 'dt-bindings/clock/bt1-ccu.h'.
> > 
> > Don't think we need this description.
> 
> Agreed.
> 
> > 
> > > +    const: 1
> > > +
> > > +  clocks:
> > > +    description: Phandle of CCU External reference clock.
> > > +    maxItems: 1
> > > +
> > > +  clock-names:
> > > +    const: ref_clk
> > 
> > Can we drop _clk? It's redundant.
> 
> I would leave this and "pcie_clk", "sata_clk", "eth_clk" declared in the
> next two bindings as is, since this way they would exactly match the names
> used in the documentation. The same thing is with the clock-output-names
> property values.
> 
> I've seen such names in many other drivers/bindings including the
> bindings in the clock subsystem even submitted rather recently, not to
> mention the names like "aclk", "pclk", etc used all over the dt nodes.
> Are there any requirements in naming the clocks? Should I avoid using the
> '_clk' clock names suffix in accordance with them? If so, please point
> me out to that requirement in docs for future reference.
> 
> Normally If I don't find something in the requirements documented in the kernel,
> I use either a commonly utilized practice seen in other similar drivers, or
> select a solution which seems better to me like providing a better readability
> and code understanding. 
> 
> > 
> > > +
> > > +  clock-output-names: true
> > > +
> > > +  assigned-clocks: true
> > > +
> > > +  assigned-clock-rates: true
> > > +
> > > +additionalProperties: false
> > > +
> 
> I'll also replace these four properties with a single
> "unevaluatedProperties: false". In the framework of other patchset
> review Rob said this property is more suitable in such situations and
> will get to be supported by the dt_binding_check script eventually.
> 
> > > +required:
> > > +  - compatible
> > > +  - reg
> > > +  - "#clock-cells"
> > > +  - clocks
> > > +  - clock-names
> > > +
> > > +examples:
> > > +  - |
> > > +    ccu_pll: ccu_pll@1F04D000 {
> > 
> > Drop the phandle unless it's actually used.
> 
> Do you mean the label definition? If so, Ok. I'll remove it.
> 
> Unit-address will be also lowercased if I don't remove the reg property
> from here. As I said in RFC in accordance with the alternative solution
> this node will be a sub-node of the system controller, which regmap will
> be used instead of the individual reg-property definition. So if the
> reg-property is removed from the node, the unit-address will be also
> discarded from here.
> 
> > 
> > > +      compatible = "be,bt1-ccu-pll";
> > > +      reg = <0x1F04D000 0x028>;
> > 
> > Lowercase hex please. That size is oddly small.
> 
> It's small due to be range being part of the system controller registers
> set. I've briefly described this above and thoroughly - in the RFC.
> Please see the RFC text and send your comments regarding an alternative
> solution there shall you have any.
> 
> Anyway if no comments are received there soon, I'll remove the reg
> property from here. The PLL driver will refer to the parental system
> controller to get the registers regmap handler.
> 
> > 
> > > +      #clock-cells = <1>;
> > > +
> > > +      clocks = <&osc25>;
> > > +      clock-names = "ref_clk";
> > > +
> > > +      clock-output-names = "cpu_pll", "sata_pll", "ddr_pll",
> > > +                           "pcie_pll", "eth_pll";
> > > +    };
> > > +...
> > > diff --git a/include/dt-bindings/clock/bt1-ccu.h b/include/dt-bindings/clock/bt1-ccu.h
> > > new file mode 100644
> > > index 000000000000..86e63162ade0
> > > --- /dev/null
> > > +++ b/include/dt-bindings/clock/bt1-ccu.h
> > > @@ -0,0 +1,17 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * Copyright (C) 2019 BAIKAL ELECTRONICS, JSC
> > > + *
> > > + * Baikal-T1 CCU clock indeces.
> > > + */
> > > +#ifndef __DT_BINDINGS_CLOCK_BT1_CCU_H
> > > +#define __DT_BINDINGS_CLOCK_BT1_CCU_H
> > > +
> > > +/* Baikal-T1 CCU PLL indeces. */
> > 
> > Please drop this comment. It's not useful.
> 
> Ok.
> 
> Regards,
> -Sergey
> 
> > 
> > > +#define CCU_CPU_PLL                    0
> > > +#define CCU_SATA_PLL                   1
> > > +#define CCU_DDR_PLL                    2
> > > +#define CCU_PCIE_PLL                   3
> > > +#define CCU_ETH_PLL                    4
> > > +
> > > +#endif /* __DT_BINDINGS_CLOCK_BT1_CCU_H */
> > > -- 
> > > 2.25.1
> > >

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

end of thread, other threads:[~2020-04-26  6:18 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20200306130048.8868-1-Sergey.Semin@baikalelectronics.ru>
2020-03-06 13:00 ` [PATCH 1/5] dt-bindings: clk: Add Baikal-T1 CCU PLLs bindings Sergey.Semin
2020-03-10  2:02   ` Stephen Boyd
     [not found]   ` <20200310021052.2E40F80307C5@mail.baikalelectronics.ru>
2020-04-05  9:59     ` Sergey Semin
2020-04-16 19:27       ` Sergey Semin
2020-04-26  6:18       ` Sergey Semin
2020-03-06 13:00 ` [PATCH 2/5] dt-bindings: clk: Add Baikal-T1 AXI-bus CCU bindings Sergey.Semin
2020-03-12 20:50   ` Rob Herring
2020-04-05 10:28     ` Sergey Semin
2020-03-06 13:00 ` [PATCH 3/5] dt-bindings: clk: Add Baikal-T1 System Devices " Sergey.Semin
2020-03-10  2:19   ` Stephen Boyd
     [not found]   ` <20200310021915.8A0E7803087C@mail.baikalelectronics.ru>
2020-04-05 15:35     ` Sergey Semin
2020-03-06 13:00 ` [PATCH 4/5] clk: Add Baikal-T1 CCU PLLs driver Sergey.Semin
2020-03-10 15:30   ` Stephen Boyd
2020-04-07 12:08     ` Sergey Semin
2020-04-16 19:29       ` Sergey Semin
2020-04-26  6:16       ` Sergey Semin
2020-03-06 13:00 ` [PATCH 5/5] clk: Add Baikal-T1 CCU dividers driver Sergey.Semin
2020-03-10  0:21 ` [PATCH 0/5] clk: Add Baikal-T1 SoC Clock Control Unit support Sergey Semin
2020-03-10  2:03   ` Stephen Boyd

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.