public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support
@ 2026-02-09  5:48 Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Chuan Liu via B4 Relay
                   ` (13 more replies)
  0 siblings, 14 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

This patch adds driver support for Phase-Locked Loop (PLL) controllers
and Clock Control Units (CCUs) in A9 SoC family.

In the A9 SoC architecture, PLLs and clock control units are implemented
as standardized hardware instances to reduce unnecessary differentiation
across individual units.

All A9 PLLs and CCUs are exposed as device tree nodes, providing an
accurate representation of the SoC's internal clock hardware structure.

These drivers are designed for reuse in subsequent SoC generations,
guaranteeing code inheritance and maximizing reusability.

Makefile rules compile A9 PLL and CCU drivers into clk-amlogic.o (see
drivers/clk/amlogic/Makefile), simplifying deployment and enhancing load
efficiency (single insmod for kernel module).

Since the foundational A9 DTS hasn't been upstreamed yet, I'm temporarily
pushing the PLL/CCU DTS files to github for driver comprehension [1].
These patches will be included in a later release after the base A9 DTS
is merged.

[1] https://github.com/torvalds/linux/commit/d6a82e4cce675fa5146c5f638c2a926c1c8cb1d9

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
Chuan Liu (13):
      dt-bindings: clock: Add Amlogic A9 standardized model clock control units
      dt-bindings: clock: Add Amlogic A9 PLL controllers
      dt-bindings: clock: Add Amlogic A9 misc clock control units
      clk: amlogic: Add basic clock driver
      clk: amlogic: Add composite clock driver
      clk: amlogic: Add noglitch clock driver
      clk: amlogic: Add duandiv clock driver
      clk: amlogic: Add PLL driver
      clk: amlogic: Add DT-based clock registration functions
      clk: amlogic: Add A9 standardized model clock control units driver
      clk: amlogic: Add A9 PLL controllers driver
      clk: amlogic: Add A9 misc clock control units driver
      clk: amlogic: Add support for building as combined kernel module

 .../bindings/clock/amlogic,a9-misc-ccu.yaml        | 523 +++++++++++
 .../bindings/clock/amlogic,a9-model-ccu.yaml       | 435 +++++++++
 .../devicetree/bindings/clock/amlogic,a9-pll.yaml  | 134 +++
 drivers/clk/Kconfig                                |   1 +
 drivers/clk/Makefile                               |   1 +
 drivers/clk/amlogic/Kconfig                        |  52 ++
 drivers/clk/amlogic/Makefile                       |  23 +
 drivers/clk/amlogic/a9-misc-ccu.c                  | 970 +++++++++++++++++++++
 drivers/clk/amlogic/a9-model-ccu.c                 | 475 ++++++++++
 drivers/clk/amlogic/a9-pll.c                       | 156 ++++
 drivers/clk/amlogic/clk-basic.c                    | 219 +++++
 drivers/clk/amlogic/clk-basic.h                    |  39 +
 drivers/clk/amlogic/clk-composite.c                | 280 ++++++
 drivers/clk/amlogic/clk-composite.h                |  20 +
 drivers/clk/amlogic/clk-dualdiv.c                  | 365 ++++++++
 drivers/clk/amlogic/clk-dualdiv.h                  |  27 +
 drivers/clk/amlogic/clk-module.c                   |  42 +
 drivers/clk/amlogic/clk-module.h                   |  53 ++
 drivers/clk/amlogic/clk-noglitch.c                 | 584 +++++++++++++
 drivers/clk/amlogic/clk-noglitch.h                 |  29 +
 drivers/clk/amlogic/clk-pll.c                      | 701 +++++++++++++++
 drivers/clk/amlogic/clk-pll.h                      |  43 +
 drivers/clk/amlogic/clk.c                          | 464 ++++++++++
 drivers/clk/amlogic/clk.h                          |  56 ++
 include/dt-bindings/clock/amlogic,a9-misc-ccu.h    |  53 ++
 25 files changed, 5745 insertions(+)
---
base-commit: 4d310797262f0ddf129e76c2aad2b950adaf1fda
change-id: 20260130-a9_clock_driver-ddd90357848c

Best regards,
-- 
Chuan Liu <chuan.liu@amlogic.com>



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

* [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09 13:14   ` Krzysztof Kozlowski
  2026-02-09 13:18   ` Krzysztof Kozlowski
  2026-02-09  5:48 ` [PATCH 02/13] dt-bindings: clock: Add Amlogic A9 PLL controllers Chuan Liu via B4 Relay
                   ` (12 subsequent siblings)
  13 siblings, 2 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Add dt-binding documentation for standardized model clock control units
in A9 SoC family.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 .../bindings/clock/amlogic,a9-model-ccu.yaml       | 435 +++++++++++++++++++++
 1 file changed, 435 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.yaml
new file mode 100644
index 000000000000..56c5cbe1b246
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.yaml
@@ -0,0 +1,435 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2026 Amlogic, Inc. All rights reserved
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/amlogic,a9-model-ccu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A9 Family Standardized Model Clock Control Unit
+
+maintainers:
+  - Chuan Liu <chuan.liu@amlogic.com>
+
+description:
+  The clock tree within the A9 is composed of numerous instances of these
+  standardized model CCU (Clock Control Units).
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - const: amlogic,a9-composite-ccu
+        description: Supports clock source selection, frequency division, and
+                     clock gating.
+      - items:
+          - const: amlogic,a9-composite-ccu-mult
+        description: Some modules have multiple input clocks and contain
+                     multiple composite-ccus internally.
+      - items:
+          - const: amlogic,a9-noglitch-ccu
+        description: Provides the same functionality as composite-ccu but
+                     includes glitch suppression during frequency transitions.
+      - items:
+          - const: amlogic,a9-noglitch-ccu-mult
+        description: Some modules have multiple input clocks and contain
+                     multiple noglitch-ccus internally.
+      - items:
+          - const: amlogic,a9-sysbus-ccu
+        description: Consists of multiple gating arrays, commonly used for
+                     Amlogic's sys_clk and axi_clk.
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    minItems: 1
+    maxItems: 16
+
+  clock-names:
+    minItems: 1
+    maxItems: 16
+
+  clock-output-names:
+    minItems: 1
+    # The sysbus-ccu of A9 supports up to 128 gates
+    maxItems: 128
+
+  '#clock-cells':
+    description:
+      The clock controller of a module may contain one or more CCU(s). When a
+      clock controller has multiple CCUs, an index is required to specify a
+      particular CCU within the clock controller.
+    oneOf:
+      - const: 0
+        description: Single clock output, no specifier needed
+      - const: 1
+        description: Multiple clocks, index selects specific output
+
+  amlogic,clock-max-frequency:
+    description: |
+      Each clock's maximum output frequency is constrained during hardware
+      design to ensure proper timing requirements for the clock network. If the
+      clock frequency configured exceeds this design limit, it can lead to
+      abnormal behavior in modules relying on that clock and may even cause
+      cross-talk that affects other modules.
+
+      In the driver, this property is parsed, and interface functions from the
+      CCF are called to enforce the clock's maximum frequency, preventing
+      potential issues caused by excessive clock frequency configurations.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+
+  amlogic,reg-layout:
+    description:
+      These standardized model CCUs require register configuration for their
+      clock functions. This property node describes the register layout
+      parameters for each model's CCU.
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          anyOf:
+            - contains:
+                const: amlogic,a9-composite-ccu
+            - contains:
+                const: amlogic,a9-noglitch-ccu
+    then:
+      properties:
+        '#clock-cells':
+          const: 0
+        clock-output-names:
+          minItems: 1
+          maxItems: 1
+    else:
+      properties:
+        '#clock-cells':
+          const: 1
+        clock-output-names:
+          minItems: 2
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-composite-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+            - description: input clock source 4 (optional)
+            - description: input clock source 5 (optional)
+            - description: input clock source 6 (optional)
+            - description: input clock source 7 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+        amlogic,reg-layout:
+          description: |
+            composite-ccu contains three register layout parameters:
+              * register offset
+              * bit offset
+              * divider effective bit width
+      required:
+        - amlogic,reg-layout
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-composite-ccu-mult
+    then:
+      properties:
+        clocks:
+          description:
+            Some clock controllers contain two composite-ccus (labeled
+            composite_a and composite_b). In certain controllers, composite_a
+            and composite_b share a common clock source, while in others they
+            have independent clock sources.
+          minItems: 1
+          items:
+            - description: composite_a/b's input clock source 0
+            - description: composite_a/b's input clock source 1 (optional)
+            - description: composite_a/b's input clock source 2 (optional)
+            - description: composite_a/b's input clock source 3 (optional)
+            - description: composite_a/b's input clock source 4 (optional)
+            - description: composite_a/b's input clock source 5 (optional)
+            - description: composite_a/b's input clock source 6 (optional)
+            - description: composite_a/b's input clock source 7 (optional)
+            - description: composite_b's input clock source 0 (optional)
+            - description: composite_b's input clock source 1 (optional)
+            - description: composite_b's input clock source 2 (optional)
+            - description: composite_b's input clock source 3 (optional)
+            - description: composite_b's input clock source 4 (optional)
+            - description: composite_b's input clock source 5 (optional)
+            - description: composite_b's input clock source 6 (optional)
+            - description: composite_b's input clock source 7 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+            - const: bclkin0
+            - const: bclkin1
+            - const: bclkin2
+            - const: bclkin3
+            - const: bclkin4
+            - const: bclkin5
+            - const: bclkin6
+            - const: bclkin7
+        amlogic,reg-layout:
+          description: |
+            composite-ccu contains three register layout parameters:
+              * register offset
+              * bit offset
+              * divider effective bit width
+      required:
+        - amlogic,reg-layout
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-noglitch-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+            - description: input clock source 4 (optional)
+            - description: input clock source 5 (optional)
+            - description: input clock source 6 (optional)
+            - description: input clock source 7 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+      required:
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-noglitch-ccu-mult
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+            - description: input clock source 4 (optional)
+            - description: input clock source 5 (optional)
+            - description: input clock source 6 (optional)
+            - description: input clock source 7 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+        amlogic,reg-layout:
+          description: |
+            composite-ccu contains one register layout parameters:
+              * register offset
+      required:
+        - amlogic,reg-layout
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-sysbus-ccu
+    then:
+      properties:
+        clocks:
+          maxItems: 1
+          description: input clock of sysbus-ccu
+        amlogic,reg-layout:
+          description: |
+            composite-ccu contains two register layout parameters:
+              * register offset
+              * bit offset
+      required:
+        - amlogic,reg-layout
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-output-names
+  - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    clk_dummy: clock-dummy {
+        compatible = "fixed-clock";
+        #clock-cells = <0>;
+        clock-frequency = <0>;
+        clock-output-names = "dummy";
+        status = "disabled";
+    };
+
+    apb {
+        #address-cells = <2>;
+        #size-cells = <2>;
+        clock-controller@350 {
+            compatible = "amlogic,a9-composite-ccu";
+            reg = <0x0 0x350 0x0 0x4>;
+            #clock-cells = <0>;
+            amlogic,clock-max-frequency = <50000000>;
+            amlogic,reg-layout = <0x0 0  7>;
+            clock-output-names = "sar_adc";
+            clocks = <&xtal_24m>,
+                     <&scmi_clk 17>;
+            clock-names = "clkin0", "clkin1";
+        };
+
+        clock-controller@290 {
+            compatible = "amlogic,a9-composite-ccu-mult";
+            reg = <0x0 0x290 0x0 0x8>;
+            #clock-cells = <1>;
+            amlogic,clock-max-frequency = <250000000>,
+                                          <250000000>,
+                                          <1200000000>;
+            amlogic,reg-layout = <0x0 0  7>,
+                                 <0x0 16 7>,
+                                 <0x4 0  7>;
+            clock-output-names = "sd_emmc_a",
+                                 "sd_emmc_b",
+                                 "sd_emmc_c";
+            clocks = <&xtal_24m>,
+                     <&scmi_clk 6>,
+                     <&scmi_clk 10>;
+            clock-names = "clkin0",
+                          "clkin1",
+                          "clkin2";
+        };
+
+        clock-controller@378 {
+            compatible = "amlogic,a9-composite-ccu-mult";
+            reg = <0x0 0x378 0x0 0x4>;
+            #clock-cells = <1>;
+            amlogic,clock-max-frequency = <500000000>,
+                                          <667000000>;
+            amlogic,reg-layout = <0x0 0  7>,
+                                 <0x0 16 7>;
+            clock-output-names = "dptx_apb2",
+                                 "dptx_aud";
+            clocks = <&xtal_24m>,
+                     <&scmi_clk 17>,
+                     <&scmi_clk 12>,
+                     <&scmi_clk 14>,
+                     <&clk_dummy>,
+                     <&clk_dummy>,
+                     <&clk_dummy>,
+                     <&clk_dummy>,
+                     <&xtal_24m>, /* composite-ccu_b's clock source 0 */
+                     <&scmi_clk 17>, /* composite-ccu_b's clock source 1 */
+                     <&scmi_clk 10>, /* composite-ccu_b's clock source 2 */
+                     <&scmi_clk 12>; /* composite-ccu_b's clock source 3 */
+            clock-names = "clkin0",
+                          "clkin1",
+                          "clkin2",
+                          "clkin3",
+                          "clkin4",
+                          "clkin5",
+                          "clkin6",
+                          "clkin7",
+                          "bclkin0",
+                          "bclkin1",
+                          "bclkin2",
+                          "bclkin3";
+        };
+
+        clock-controller@420 {
+            compatible = "amlogic,a9-noglitch-ccu";
+            reg = <0x0 0x420 0x0 0x4>;
+            #clock-cells = <0>;
+            amlogic,clock-max-frequency = <800000000>;
+            clock-output-names = "dspa";
+            clocks = <&xtal_24m>,
+                     <&scmi_clk 8>,
+                     <&scmi_clk 10>;
+            clock-names = "clkin0",
+                          "clkin1",
+                          "clkin2";
+        };
+
+        clock-controller@400 {
+            compatible = "amlogic,a9-noglitch-ccu-mult";
+            reg = <0x0 0x400 0x0 0x8>;
+            #clock-cells = <1>;
+            /*
+             * If only one maximum frequency is specified, it is shared by all
+             * clocks under the current device node.
+             */
+            amlogic,clock-max-frequency = <1000000000>;
+            amlogic,reg-layout = <0x0>,
+                                 <0x4>;
+            clock-output-names = "mali",
+                                 "mali_stack";
+            clocks = <&xtal_24m>,
+                     <&scmi_clk 23>,
+                     <&scmi_clk 6>;
+            clock-names = "clkin0",
+                          "clkin1",
+                          "clkin2";
+        };
+
+        clock-sysbus@230 {
+            compatible = "amlogic,a9-sysbus-ccu";
+            reg = <0x0 0x230 0x0 0x10>;
+            #clock-cells = <1>;
+            amlogic,reg-layout = <0 0>,
+                                 <0 1>,
+                                 <0 3>;
+            clock-output-names = "sys_am_axi",
+                                 "sys_dos",
+                                 "sys_mipi_dsi";
+            clocks = <&scmi_clk 17>;
+        };
+    };

-- 
2.42.0



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

* [PATCH 02/13] dt-bindings: clock: Add Amlogic A9 PLL controllers
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units Chuan Liu via B4 Relay
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Add dt-binding documentation for PLL controllers used in A9 SoC family.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 .../devicetree/bindings/clock/amlogic,a9-pll.yaml  | 134 +++++++++++++++++++++
 1 file changed, 134 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-pll.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-pll.yaml
new file mode 100644
index 000000000000..26655716f040
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,a9-pll.yaml
@@ -0,0 +1,134 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2026 Amlogic, Inc. All rights reserved
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/amlogic,a9-pll.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A9 Family PLL Controllers
+
+maintainers:
+  - Chuan Liu <chuan.liu@amlogic.com>
+
+description: |
+  PLLs generate high-frequency clocks by frequency multiplication, feeding them
+  into the clock tree where they can be configured as operational clocks for
+  various system modules.
+
+  A diagram of the A9 PLL is as follows:
+         +------------------------------------------------------+
+         |      +-------+      +-----+                          |
+    osc-------->| div N |----->|     |   +-----+                |
+         |      +-------+      |     |   |     |                |
+         |                     |     |   | VCO |   +--------+   |
+         |                     |     |-->|  /  |-->| div OD |------>pll_out
+         |                     |     |   | DCO |   +--------+   |
+         |      +----------+   |     |   |     |                |
+         |  +-->| M & frac |-->|     |   +-----+                |
+         |  |   +----------+   +-----+      |                   |
+         |  |                               |                   |
+         |  +-------------------------------+                   |
+         +------------------------------------------------------+
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - const: amlogic,a9-int-pll
+        description: Integer multiplier PLL
+      - items:
+          - const: amlogic,a9-frac-pll
+        description: Fractional multiplier PLL
+      - items:
+          - const: amlogic,a9-frac-step-pll
+        description: Fractional PLL with integer step granularity
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+    description: input clock of pll
+
+  clock-output-names:
+    maxItems: 1
+
+  '#clock-cells':
+    const: 0
+
+  amlogic,clock-max-frequency:
+    description: |
+      Each clock's maximum output frequency is constrained during hardware
+      design to ensure proper timing requirements for the clock network. If the
+      clock frequency configured exceeds this design limit, it can lead to
+      abnormal behavior in modules relying on that clock and may even cause
+      cross-talk that affects other modules.
+
+      In the driver, this property is parsed, and interface functions from the
+      CCF are called to enforce the clock's maximum frequency, preventing
+      potential issues caused by excessive clock frequency configurations.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+
+  amlogic,clock-init-regs:
+    description:
+      Certain CCUs and PLLs require initialization through dedicated registers
+      before becoming operational. This initialization configures internal clock
+      drive characteristics, divider parameters, and PLL internal circuitry.
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+    items:
+      items:
+        - description: The register offset address
+        - description: The value to be written to the register
+        - description: The delay after the register write (unit is us)
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-output-names
+  - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    apb {
+        #address-cells = <2>;
+        #size-cells = <2>;
+        clock-mclk_pll@8330 {
+            compatible = "amlogic,a9-int-pll";
+            reg = <0x0 0x8330 0x0 0xc>;
+            #clock-cells = <0>;
+            amlogic,clock-init-regs = <0x4  0x00402000 0>,
+                                      <0x8  0x60000100 0>;
+            amlogic,clock-max-frequency = <2800000000>;
+            clock-output-names = "mclk_pll";
+            clocks = <&xtal_24m>;
+        };
+
+        clock-gp0_pll@8200 {
+            compatible = "amlogic,a9-frac-pll";
+            reg = <0x0 0x8200 0x0 0x10>;
+            #clock-cells = <0>;
+            amlogic,clock-init-regs = <0x0  0x08010000 0>,
+                                      <0x4  0x11480000 0>,
+                                      <0x8  0x1219b010 0>,
+                                      <0xc  0x00008010 0>;
+            amlogic,clock-max-frequency = <2800000000>;
+            clock-output-names = "gp0_pll";
+            clocks = <&xtal_24m>;
+        };
+
+        clock-hifi_pll@8280 {
+            compatible = "amlogic,a9-frac-step-pll";
+            reg = <0x0 0x8280 0x0 0x10>;
+            #clock-cells = <0>;
+            amlogic,clock-init-regs = <0x0  0x08010000 0>,
+                                      <0x4  0x11480000 0>,
+                                      <0x8  0x1219b010 0>,
+                                      <0xc  0x00008010 0>;
+            amlogic,clock-max-frequency = <2800000000>;
+            clock-output-names = "hifi_pll";
+            clocks = <&xtal_24m>;
+        };
+    };

-- 
2.42.0



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

* [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 02/13] dt-bindings: clock: Add Amlogic A9 PLL controllers Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09 13:15   ` Krzysztof Kozlowski
  2026-02-09  5:48 ` [PATCH 04/13] clk: amlogic: Add basic clock driver Chuan Liu via B4 Relay
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Add dt-binding documentation for various miscellaneous peripheral clock
control units in A9 SoC family.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 .../bindings/clock/amlogic,a9-misc-ccu.yaml        | 523 +++++++++++++++++++++
 include/dt-bindings/clock/amlogic,a9-misc-ccu.h    |  53 +++
 2 files changed, 576 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-misc-ccu.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-misc-ccu.yaml
new file mode 100644
index 000000000000..cce209f75a6e
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,a9-misc-ccu.yaml
@@ -0,0 +1,523 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2026 Amlogic, Inc. All rights reserved
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/amlogic,a9-misc-ccu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic A9 Family Misc Clock Control Unit
+
+maintainers:
+  - Chuan Liu <chuan.liu@amlogic.com>
+
+description: |
+  The misc-ccu contains clock control units which are built from dedicated,
+  non-universal model clock controllers, such as the sc-ccu for smart card and
+  the ts-ccu for temperature sensor.
+
+  The clock IDs of the subclocks of these CCUs are defined in
+  "include/dt-bindings/clock/amlogic,a9-misc-ccu.h".
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - const: amlogic,a9-sc-ccu
+        description: Clock control unit of smart card
+      - items:
+          - const: amlogic,a9-ts-ccu
+        description: Clock control unit of temperature sensor(s)
+      - items:
+          - const: amlogic,a9-genout-ccu
+        description: Clock control unit that generates clock output to pin(s)
+      - items:
+          - const: amlogic,a9-clk12_24m-ccu
+        description: Clock control unit that generates clock output 12MHz or
+                     24MHz to pin(s)
+      - items:
+          - const: amlogic,a9-vapb_ge2d-ccu
+        description: Clock control unit for vapb and ge2d
+      - items:
+          - const: amlogic,a9-di-ccu
+        description: Clock control unit of de-interlace
+      - items:
+          - const: amlogic,a9-eth-ccu
+        description: Clock control unit of ethernet
+      - items:
+          - const: amlogic,a9-mclk-ccu
+        description: Clock control unit that provides the clock to the external
+                     camera sensor
+      - items:
+          - const: amlogic,a9-dualdivmux-ccu
+        description: Clock control unit using dual divider channel toggling for
+                     fractional division
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    minItems: 1
+    maxItems: 32
+
+  clock-names:
+    minItems: 1
+    maxItems: 32
+
+  clock-output-names:
+    minItems: 1
+    maxItems: 8
+
+  '#clock-cells':
+    description:
+      The clock controller of a module may contain one or more child clock(s).
+      When a clock controller has multiple child clocks, an index is required
+      to specify a particular clock within the clock controller.
+    oneOf:
+      - const: 0
+        description: Single clock output, no specifier needed
+      - const: 1
+        description: Multiple clocks, index selects specific output
+
+  amlogic,clock-max-frequency:
+    description: |
+      Each clock's maximum output frequency is constrained during hardware
+      design to ensure proper timing requirements for the clock network. If the
+      clock frequency configured exceeds this design limit, it can lead to
+      abnormal behavior in modules relying on that clock and may even cause
+      cross-talk that affects other modules.
+
+      In the driver, this property is parsed, and interface functions from the
+      CCF are called to enforce the clock's maximum frequency, preventing
+      potential issues caused by excessive clock frequency configurations.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+
+  amlogic,clock-init-regs:
+    description:
+      Certain CCUs and PLLs require initialization through dedicated registers
+      before becoming operational. This initialization configures internal clock
+      drive characteristics, divider parameters, and PLL internal circuitry.
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+    items:
+      items:
+        - description: The register offset address
+        - description: The value to be written to the register
+        - description: The delay after the register write (unit is us)
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-sc-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+        clock-output-names:
+
+          items:
+            - description: preprocessing clock
+            - description: divider clock
+      required:
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-ts-ccu
+    then:
+      properties:
+        clocks:
+          description: input clock of ts-ccu
+        clock-output-names:
+          items:
+            - description: divider clock
+            - description: gate clock
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-genout-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+            - description: input clock source 4 (optional)
+            - description: input clock source 5 (optional)
+            - description: input clock source 6 (optional)
+            - description: input clock source 7 (optional)
+            - description: input clock source 8 (optional)
+            - description: input clock source 9 (optional)
+            - description: input clock source 10 (optional)
+            - description: input clock source 11 (optional)
+            - description: input clock source 12 (optional)
+            - description: input clock source 13 (optional)
+            - description: input clock source 14 (optional)
+            - description: input clock source 15 (optional)
+            - description: input clock source 16 (optional)
+            - description: input clock source 17 (optional)
+            - description: input clock source 18 (optional)
+            - description: input clock source 19 (optional)
+            - description: input clock source 20 (optional)
+            - description: input clock source 21 (optional)
+            - description: input clock source 22 (optional)
+            - description: input clock source 23 (optional)
+            - description: input clock source 24 (optional)
+            - description: input clock source 25 (optional)
+            - description: input clock source 26 (optional)
+            - description: input clock source 27 (optional)
+            - description: input clock source 28 (optional)
+            - description: input clock source 29 (optional)
+            - description: input clock source 30 (optional)
+            - description: input clock source 31 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+            - const: clkin8
+            - const: clkin9
+            - const: clkin10
+            - const: clkin11
+            - const: clkin12
+            - const: clkin13
+            - const: clkin14
+            - const: clkin15
+            - const: clkin16
+            - const: clkin17
+            - const: clkin18
+            - const: clkin19
+            - const: clkin20
+            - const: clkin21
+            - const: clkin22
+            - const: clkin23
+            - const: clkin24
+            - const: clkin25
+            - const: clkin26
+            - const: clkin27
+            - const: clkin28
+            - const: clkin29
+            - const: clkin30
+            - const: clkin31
+        clock-output-names:
+          items:
+            - description: multiplexer clock
+            - description: divider clock
+            - description: gate clock
+      required:
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-clk12_24m-ccu
+    then:
+      properties:
+        clocks:
+          maxItems: 1
+          description: 24m-xtal clock
+        clock-output-names:
+          items:
+            - description: input gate clock
+            - description: divider clock
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-vapb_ge2d-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+            - description: input clock source 4 (optional)
+            - description: input clock source 5 (optional)
+            - description: input clock source 6 (optional)
+            - description: input clock source 7 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+        clock-output-names:
+          items:
+            - description: vapb clock
+            - description: ge2d clock
+      required:
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-di-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: input clock source 0
+            - description: input clock source 1 (optional)
+            - description: input clock source 2 (optional)
+            - description: input clock source 3 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+        clock-output-names:
+          items:
+            - description: preprocessing clock
+            - description: divider clock
+            - description: gate clock
+      required:
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-eth-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 2
+          items:
+            - description: eth_125m input clock
+            - description: rmii input clock source 0
+            - description: rmii input clock source 1 (optional)
+            - description: rmii input clock source 2 (optional)
+            - description: rmii input clock source 3 (optional)
+            - description: rmii input clock source 4 (optional)
+            - description: rmii input clock source 5 (optional)
+            - description: rmii input clock source 6 (optional)
+            - description: rmii input clock source 7 (optional)
+        clock-names:
+          minItems: 2
+          items:
+            - const: clk125m
+            - const: clkin0
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+            - const: clkin4
+            - const: clkin5
+            - const: clkin6
+            - const: clkin7
+        clock-output-names:
+          items:
+            - description: eth_125m clock
+            - description: eth_rmii clock
+      required:
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-mclk-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 1
+          items:
+            - description: mclk0/1 predivider clock source
+            - description: mclk0/1 clock source 1 (optional)
+            - description: mclk0/1 clock source 2 (optional)
+            - description: mclk0/1 clock source 3 (optional)
+        clock-names:
+          minItems: 1
+          items:
+            - const: divin
+            - const: clkin1
+            - const: clkin2
+            - const: clkin3
+        clock-output-names:
+          items:
+            - description: mclk0 pre-divider clock
+            - description: mclk0 multiplexer clock
+            - description: mclk0 divider clock
+            - description: mclk0 gate clock
+            - description: mclk1 pre-divider clock
+            - description: mclk1 multiplexer clock
+            - description: mclk1 divider clock
+            - description: mclk1 gate clock
+      required:
+        - amlogic,clock-init-regs
+        - clock-names
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: amlogic,a9-dualdivmux-ccu
+    then:
+      properties:
+        clocks:
+          minItems: 2
+          items:
+            - description: input clock of dualdiv
+            - description: mux clock source 0
+            - description: mux clock source 1 (optional)
+            - description: mux clock source 2 (optional)
+            - description: mux clock source 3 (optional)
+        clock-names:
+          minItems: 2
+          items:
+            - const: divin
+            - const: mux0
+            - const: mux1
+            - const: mux2
+            - const: mux3
+        clock-output-names:
+          items:
+            - description: dualdiv clock
+            - description: multiplexer clock
+      required:
+        - clock-names
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-output-names
+  - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    apb {
+        #address-cells = <2>;
+        #size-cells = <2>;
+        clock-sc@370 {
+            compatible = "amlogic,a9-sc-ccu";
+            reg = <0x0 0x370 0x0 0x4>;
+            #clock-cells = <1>;
+            clock-output-names = "sc_pre",
+                                 "sc";
+            clocks = <&scmi_clk 6>,
+                     <&scmi_clk 9>,
+                     <&scmi_clk 14>,
+                     <&xtal_24m>;
+            clock-names = "clkin0",
+                          "clkin1",
+                          "clkin2",
+                          "clkin3";
+        };
+
+        clock-ts@3a0 {
+            compatible = "amlogic,a9-ts-ccu";
+            reg = <0x0 0x3a0 0x0 0x4>;
+            #clock-cells = <1>;
+            clock-output-names = "ts_div",
+                                 "ts";
+            clocks = <&xtal_24m>;
+        };
+
+        clock-eth@3a4 {
+            compatible = "amlogic,a9-eth-ccu";
+            reg = <0x0 0x3a4 0x0 0x4>;
+            #clock-cells = <1>;
+            clock-output-names = "eth_125m",
+                                 "eth_rmii";
+            clocks = <&clk_eth_125m_div>,
+                     <&scmi_clk 6>;
+            clock-names = "clk125m",
+                          "clkin0";
+        };
+
+        clock-mclk@833c {
+            compatible = "amlogic,a9-mclk-ccu";
+            reg = <0x0 0x833c 0x0 0x8>;
+            #clock-cells = <1>;
+            amlogic,clock-init-regs = <0x0  0x04000400 0>,
+                                      <0x4  0x96169616 0>;
+            clock-output-names = "mclk0_pre_div",
+                                 "mclk0_sel",
+                                 "mclk0_div",
+                                 "mclk0",
+                                 "mclk1_pre_div",
+                                 "mclk1_sel",
+                                 "mclk1_div",
+                                 "mclk1";
+            clocks = <&mclk_pll>,
+                     <&xtal_24m>,
+                     <&scmi_clk 4>;
+            clock-names = "divin",
+                          "clkin1",
+                          "clkin2";
+        };
+    };
+
+    aobus {
+        #address-cells = <2>;
+        #size-cells = <2>;
+        clkc_ao_cecb: clock-ao_cecb@38 {
+            compatible = "amlogic,a9-dualdivmux-ccu";
+            reg = <0x0 0x38 0x0 0x8>;
+            #clock-cells = <1>;
+
+            clock-output-names = "ao_cecb_dualdiv",
+                                 "ao_cecb";
+            clocks = <&xtal>,
+                     <&clkc_ao_cecb 0>,
+                     <&clkc_rtc 1>;
+            clock-names = "divin",
+                          "mux0",
+                          "mux1";
+        };
+
+        clkc_rtc: clock-rtc@8014c {
+            compatible = "amlogic,a9-dualdivmux-ccu";
+            reg = <0x0 0x8014c 0x0 0x8>;
+            #clock-cells = <1>;
+            clock-output-names = "rtc_dualdiv",
+                                 "rtc";
+            clocks = <&xtal>,
+                     <&xtal>,
+                     <&clkc_rtc 0>;
+            clock-names = "divin",
+                          "mux0",
+                          "mux1";
+        };
+    };
diff --git a/include/dt-bindings/clock/amlogic,a9-misc-ccu.h b/include/dt-bindings/clock/amlogic,a9-misc-ccu.h
new file mode 100644
index 000000000000..102aff8d68e4
--- /dev/null
+++ b/include/dt-bindings/clock/amlogic,a9-misc-ccu.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AMLOGIC_A9_MISC_CCU_H
+#define __AMLOGIC_A9_MISC_CCU_H
+
+/* &clkc_sc (Smart Card) */
+#define A9_CLK_SC_PRE				0
+#define A9_CLK_SC				1
+
+/* &clkc_ts (Temperature Sensor) */
+#define A9_CLK_TS_DIV				0
+#define A9_CLK_TS				1
+
+/* &clkc_gen_out (Generate Output) */
+#define A9_CLK_GENOUT_SEL			0
+#define A9_CLK_GENOUT_DIV			1
+#define A9_CLK_GENOUT				2
+
+/* &clkc_12_24m (12M & 24M) */
+#define A9_CLK_24M_IN				0
+#define A9_CLK_12_24M				1
+
+/* &clkc_vapb_ge2d (VAPB & GE2D) */
+#define A9_CLK_VAPB				0
+#define A9_CLK_GE2D				1
+
+/* &clkc_di (Deinterlacer) */
+#define A9_CLK_VPU_CLKB_TEMP			0
+#define A9_CLK_VPU_CLKB_DIV			1
+#define A9_CLK_VPU_CLKB				2
+
+/* &clkc_eth (ETH) */
+#define A9_CLK_ETH_125M				0
+#define A9_CLK_ETH_RMII				1
+
+/* &clkc_mclk or &clkc_mclk1 (mclk-ccu) */
+#define A9_CLK_MCLK_0_PRE_DIV			0
+#define A9_CLK_MCLK_0_SEL			1
+#define A9_CLK_MCLK_0_DIV			2
+#define A9_CLK_MCLK_0				3
+#define A9_CLK_MCLK_1_PRE_DIV			4
+#define A9_CLK_MCLK_1_SEL			5
+#define A9_CLK_MCLK_1_DIV			6
+#define A9_CLK_MCLK_1				7
+
+/* &clkc_ao_cecb, &clkc_ao_rtc, &clkc_rtc (dualdivmux-ccu) */
+#define A9_CLK_DUALDIV				0
+#define A9_CLK_DUALDIV_SEL			1
+
+#endif /* __AMLOGIC_A9_MISC_CCU_H */

-- 
2.42.0



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

* [PATCH 04/13] clk: amlogic: Add basic clock driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (2 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09 13:17   ` Krzysztof Kozlowski
  2026-02-09  5:48 ` [PATCH 05/13] clk: amlogic: Add composite " Chuan Liu via B4 Relay
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Implement core clock driver for Amlogic SoC platforms, supporting
fundamental clock types: mux (multiplexer), div (divider), and gate. The
Amlogic clock architecture heavily utilizes these basic building blocks
throughout its clock tree.

Features included:
- clk_ops implementations for all basic clock types
- Debugfs interface with two diagnostic nodes:
    * clk_type: displays clock type identifier
    * clk_available_rates: shows configurable frequency ranges

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/Kconfig             |   1 +
 drivers/clk/Makefile            |   1 +
 drivers/clk/amlogic/Kconfig     |  12 +++
 drivers/clk/amlogic/Makefile    |   6 ++
 drivers/clk/amlogic/clk-basic.c | 219 ++++++++++++++++++++++++++++++++++++++++
 drivers/clk/amlogic/clk-basic.h |  39 +++++++
 drivers/clk/amlogic/clk.c       | 142 ++++++++++++++++++++++++++
 drivers/clk/amlogic/clk.h       |  38 +++++++
 8 files changed, 458 insertions(+)

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 3a1611008e48..57c13348e7a5 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -512,6 +512,7 @@ config COMMON_CLK_RPMI
 	  the RISC-V platform management interface (RPMI) specification.
 
 source "drivers/clk/actions/Kconfig"
+source "drivers/clk/amlogic/Kconfig"
 source "drivers/clk/analogbits/Kconfig"
 source "drivers/clk/baikal-t1/Kconfig"
 source "drivers/clk/bcm/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 61ec08404442..c667f22aa414 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE)		+= clk-xgene.o
 
 # please keep this section sorted lexicographically by directory path name
 obj-y					+= actions/
+obj-$(CONFIG_ARCH_MESON)		+= amlogic/
 obj-y					+= analogbits/
 obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
 obj-$(CONFIG_ARCH_ARTPEC)		+= axis/
diff --git a/drivers/clk/amlogic/Kconfig b/drivers/clk/amlogic/Kconfig
new file mode 100644
index 000000000000..216fe98a413b
--- /dev/null
+++ b/drivers/clk/amlogic/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+
+config COMMON_CLK_AMLOGIC
+	tristate "Amlogic Common Clock"
+	depends on ARCH_MESON || COMPILE_TEST
+	depends on OF
+	default ARCH_MESON
+	select REGMAP
+	help
+	  This driver provides the basic clock infrastructure for Amlogic SoCs,
+	  offering read and write interfaces for various clock control units.
+	  Select Y if your target SoC needs clock driver support.
diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
new file mode 100644
index 000000000000..bd9dd5b78b23
--- /dev/null
+++ b/drivers/clk/amlogic/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+
+obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-amlogic.o
+
+clk-amlogic-y += clk.o
+clk-amlogic-y += clk-basic.o
diff --git a/drivers/clk/amlogic/clk-basic.c b/drivers/clk/amlogic/clk-basic.c
new file mode 100644
index 000000000000..1d0d1bc7f24d
--- /dev/null
+++ b/drivers/clk/amlogic/clk-basic.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-basic.h"
+
+/*
+ * This file implements the ops functions for basic Amlogic clock models
+ * (mux/div/gate), based on clk-mux.c, clk-divider.c, and clk-gate.c in the CCF.
+ */
+
+static int aml_clk_gate_endisable(struct clk_hw *hw, int enable)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_gate_data *gate = clk->data;
+	int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
+
+	set ^= enable;
+
+	return regmap_update_bits(clk->map, gate->reg_offset,
+				  BIT(gate->bit_idx),
+				  set ? BIT(gate->bit_idx) : 0);
+}
+
+static int aml_clk_gate_enable(struct clk_hw *hw)
+{
+	return aml_clk_gate_endisable(hw, 1);
+}
+
+static void aml_clk_gate_disable(struct clk_hw *hw)
+{
+	aml_clk_gate_endisable(hw, 0);
+}
+
+static int aml_clk_gate_is_enabled(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_gate_data *gate = clk->data;
+	unsigned int val;
+
+	regmap_read(clk->map, gate->reg_offset, &val);
+	if (gate->flags & CLK_GATE_SET_TO_DISABLE)
+		val ^= BIT(gate->bit_idx);
+
+	val &= BIT(gate->bit_idx);
+
+	return val ? 1 : 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static void aml_clk_basic_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+
+	debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops);
+	if (clk->type == AML_CLKTYPE_DIV)
+		debugfs_create_file("clk_available_rates", 0444, dentry, hw,
+				    &aml_clk_div_available_rates_fops);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+const struct clk_ops aml_clk_gate_ops = {
+	.enable = aml_clk_gate_enable,
+	.disable = aml_clk_gate_disable,
+	.is_enabled = aml_clk_gate_is_enabled,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_basic_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_gate_ops, "CLK_AMLOGIC");
+
+static unsigned long aml_clk_div_recalc_rate(struct clk_hw *hw,
+					     unsigned long prate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_divider_data *div = clk->data;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(clk->map, div->reg_offset, &val);
+	if (ret)
+		/* Gives a hint that something is wrong */
+		return 0;
+
+	val >>= div->shift;
+	val &= clk_div_mask(div->width);
+
+	return divider_recalc_rate(hw, prate, val, div->table, div->flags,
+				   div->width);
+}
+
+static int aml_clk_div_determine_rate(struct clk_hw *hw,
+				      struct clk_rate_request *req)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_divider_data *div = clk->data;
+	unsigned int val;
+	int ret;
+
+	/* if read only, just return current value */
+	if (div->flags & CLK_DIVIDER_READ_ONLY) {
+		ret = regmap_read(clk->map, div->reg_offset, &val);
+		if (ret)
+			return ret;
+
+		val >>= div->shift;
+		val &= clk_div_mask(div->width);
+
+		return divider_ro_determine_rate(hw, req, div->table,
+						 div->width, div->flags, val);
+	}
+
+	return divider_determine_rate(hw, req, div->table, div->width,
+				      div->flags);
+}
+
+static int aml_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_divider_data *div = clk->data;
+	unsigned int val;
+	int ret;
+
+	ret = divider_get_val(rate, parent_rate, div->table, div->width,
+			      div->flags);
+	if (ret < 0)
+		return ret;
+
+	val = (unsigned int)ret << div->shift;
+
+	return regmap_update_bits(clk->map, div->reg_offset,
+				  clk_div_mask(div->width) << div->shift, val);
+};
+
+const struct clk_ops aml_clk_divider_ops = {
+	.recalc_rate = aml_clk_div_recalc_rate,
+	.determine_rate = aml_clk_div_determine_rate,
+	.set_rate = aml_clk_div_set_rate,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_basic_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_divider_ops, "CLK_AMLOGIC");
+
+const struct clk_ops aml_clk_divider_ro_ops = {
+	.recalc_rate = aml_clk_div_recalc_rate,
+	.determine_rate = aml_clk_div_determine_rate,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_basic_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_divider_ro_ops, "CLK_AMLOGIC");
+
+static u8 aml_clk_mux_get_parent(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_mux_data *mux = clk->data;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(clk->map, mux->reg_offset, &val);
+	if (ret)
+		return ret;
+
+	val >>= mux->shift;
+	val &= mux->mask;
+	return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
+}
+
+static int aml_clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_mux_data *mux = clk->data;
+	unsigned int val = clk_mux_index_to_val(mux->table, mux->flags, index);
+
+	return regmap_update_bits(clk->map, mux->reg_offset,
+				  mux->mask << mux->shift,
+				  val << mux->shift);
+}
+
+static int aml_clk_mux_determine_rate(struct clk_hw *hw,
+				      struct clk_rate_request *req)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_mux_data *mux = clk->data;
+
+	return clk_mux_determine_rate_flags(hw, req, mux->flags);
+}
+
+const struct clk_ops aml_clk_mux_ops = {
+	.get_parent = aml_clk_mux_get_parent,
+	.set_parent = aml_clk_mux_set_parent,
+	.determine_rate = aml_clk_mux_determine_rate,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_basic_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_mux_ops, "CLK_AMLOGIC");
+
+const struct clk_ops aml_clk_mux_ro_ops = {
+	.get_parent = aml_clk_mux_get_parent,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_basic_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_mux_ro_ops, "CLK_AMLOGIC");
+
+MODULE_DESCRIPTION("Amlogic Basic Clock Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk-basic.h b/drivers/clk/amlogic/clk-basic.h
new file mode 100644
index 000000000000..fb2133fa239b
--- /dev/null
+++ b/drivers/clk/amlogic/clk-basic.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_BASIC_H
+#define __AML_CLK_BASIC_H
+
+#include <linux/clk-provider.h>
+
+struct aml_clk_mux_data {
+	unsigned int	reg_offset;
+	u32		*table;
+	u32		mask;
+	u8		shift;
+	u8		flags;
+};
+
+struct aml_clk_divider_data {
+	unsigned int	reg_offset;
+	u8		shift;
+	u8		width;
+	u16		flags;
+	struct clk_div_table	*table;
+};
+
+struct aml_clk_gate_data {
+	unsigned int	reg_offset;
+	u8		bit_idx;
+	u8		flags;
+};
+
+extern const struct clk_ops aml_clk_gate_ops;
+extern const struct clk_ops aml_clk_divider_ops;
+extern const struct clk_ops aml_clk_divider_ro_ops;
+extern const struct clk_ops aml_clk_mux_ops;
+extern const struct clk_ops aml_clk_mux_ro_ops;
+
+#endif /* __AML_CLK_BASIC_H */
diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c
new file mode 100644
index 000000000000..03ccfa78c511
--- /dev/null
+++ b/drivers/clk/amlogic/clk.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/module.h>
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/err.h>
+
+#include "clk.h"
+#include "clk-basic.h"
+
+static const struct {
+	unsigned int type;
+	const char *name;
+} clk_types[] = {
+#define ENTRY(f) { f, #f }
+	ENTRY(AML_CLKTYPE_MUX),
+	ENTRY(AML_CLKTYPE_DIV),
+	ENTRY(AML_CLKTYPE_GATE),
+#undef ENTRY
+};
+
+static int aml_clk_type_show(struct seq_file *s, void *data)
+{
+	struct clk_hw *hw = s->private;
+	struct aml_clk *clk = to_aml_clk(hw);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(clk_types); i++) {
+		if (clk_types[i].type == clk->type) {
+			seq_printf(s, "%s\n", clk_types[i].name);
+			return 0;
+		}
+	}
+
+	seq_puts(s, "UNKNOWN\n");
+
+	return -EINVAL;
+}
+
+static int aml_clk_type_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, aml_clk_type_show, inode->i_private);
+}
+
+const struct file_operations aml_clk_type_fops = {
+	.owner		= THIS_MODULE,
+	.open		= aml_clk_type_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_type_fops, "CLK_AMLOGIC");
+
+/*
+ * SoC HW design constrains the maximum frequency for each clock network.
+ * Configuring frequencies beyond these limits may cause module malfunction
+ * or even crosstalk affecting other modules.
+ *
+ * This function synthesizes the HW-constrained frequency range and the
+ * divider's capability to output the permissible frequency range for the
+ * current clock.
+ */
+static int aml_clk_div_available_rates_show(struct seq_file *s, void *data)
+{
+	struct clk_hw *hw = s->private;
+	struct clk_hw *phw = clk_hw_get_parent(hw);
+	struct aml_clk *clk = to_aml_clk(hw);
+	unsigned long min, max, prate;
+	unsigned long range_min, range_max;
+	unsigned int div_val;
+	unsigned long div_width, div_flags = 0;
+	const struct clk_div_table *div_table = NULL;
+
+	if (!phw) {
+		pr_err("%s: Can't get parent\n", clk_hw_get_name(hw));
+
+		return -ENOENT;
+	}
+
+	prate = clk_hw_get_rate(phw);
+	clk_hw_get_rate_range(hw, &range_min, &range_max);
+	max = prate;
+	if (clk->type == AML_CLKTYPE_DIV) {
+		struct aml_clk_divider_data *div = clk->data;
+
+		if (div->flags & CLK_DIVIDER_READ_ONLY) {
+			min = prate;
+			goto out_printf;
+		} else {
+			div_val = (1 << div->width) - 1;
+			div_table = div->table;
+			div_flags = div->flags;
+			div_width = div->width;
+		}
+	} else {
+		pr_err("%s: Unsupported clock type\n", clk_hw_get_name(hw));
+		return -EINVAL;
+	}
+
+	min = divider_recalc_rate(hw, prate, div_val, div_table, div_flags,
+				  div_width);
+
+	clk_hw_get_rate_range(hw, &range_min, &range_max);
+	if (range_min > min)
+		min = range_min;
+
+	if (range_max < max)
+		max = range_max;
+
+	min = divider_round_rate(hw, min, &prate, NULL, div_width, 0);
+	max = divider_round_rate(hw, max, &prate, NULL, div_width, 0);
+
+out_printf:
+	seq_printf(s, "min_rate:%ld\n", min);
+	seq_printf(s, "max_rate:%ld\n", max);
+
+	return 0;
+}
+
+static int aml_clk_div_available_rates_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, aml_clk_div_available_rates_show,
+			   inode->i_private);
+}
+
+const struct file_operations aml_clk_div_available_rates_fops = {
+	.owner		= THIS_MODULE,
+	.open		= aml_clk_div_available_rates_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_div_available_rates_fops, "CLK_AMLOGIC");
+#endif /* CONFIG_DEBUG_FS */
+
+MODULE_DESCRIPTION("Amlogic Common Clock Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h
new file mode 100644
index 000000000000..ec0547c1354a
--- /dev/null
+++ b/drivers/clk/amlogic/clk.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_H
+#define __AML_CLK_H
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+enum aml_clk_type {
+	AML_CLKTYPE_MUX		= 1,
+	AML_CLKTYPE_DIV		= 2,
+	AML_CLKTYPE_GATE	= 3,
+};
+
+struct aml_clk {
+	struct clk_hw	hw;
+	enum aml_clk_type type;
+	struct regmap	*map;
+	void		*data;
+};
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+extern const struct file_operations aml_clk_type_fops;
+extern const struct file_operations aml_clk_div_available_rates_fops;
+#endif /* CONFIG_DEBUG_FS */
+
+static inline struct aml_clk *to_aml_clk(struct clk_hw *hw)
+{
+	return container_of(hw, struct aml_clk, hw);
+}
+
+#endif /* __AML_CLK_H */

-- 
2.42.0



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

* [PATCH 05/13] clk: amlogic: Add composite clock driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (3 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 04/13] clk: amlogic: Add basic clock driver Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09 13:18   ` Krzysztof Kozlowski
  2026-02-09  5:48 ` [PATCH 06/13] clk: amlogic: Add noglitch " Chuan Liu via B4 Relay
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Implement clk_ops support for Amlogic composite clocks. Composite clocks
are commonly used clock control units in Amlogic SoCs that integrate
multiplexer, divider, and gate functionality into a single block.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Makefile        |   1 +
 drivers/clk/amlogic/clk-composite.c | 280 ++++++++++++++++++++++++++++++++++++
 drivers/clk/amlogic/clk-composite.h |  20 +++
 drivers/clk/amlogic/clk.c           |   7 +
 drivers/clk/amlogic/clk.h           |   1 +
 5 files changed, 309 insertions(+)

diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index bd9dd5b78b23..58a5e7bc8993 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-amlogic.o
 
 clk-amlogic-y += clk.o
 clk-amlogic-y += clk-basic.o
+clk-amlogic-y += clk-composite.o
diff --git a/drivers/clk/amlogic/clk-composite.c b/drivers/clk/amlogic/clk-composite.c
new file mode 100644
index 000000000000..9d34ed4a90b7
--- /dev/null
+++ b/drivers/clk/amlogic/clk-composite.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-composite.h"
+
+/*
+ * Amlogic composite clock module:
+ *
+ *             mux       div         gate
+ *              |         |            |
+ *         +----|---------|------------|------+
+ *         |   \|/        |            |      |
+ *         |   |\         |            |      |
+ * clk0 ------>| |        |            |      |
+ * clk1 ------>| |        |            |      |
+ * clk2 ------>| |       \|/          \|/     |
+ * clk3 ------>| |     +-----+     +------+   |
+ *         |   | |---->| div |---->| gate |------> clk out
+ * clk4 ------>| |     +-----+     +------+   |
+ * clk5 ------>| |                            |
+ * clk6 ------>| |                            |
+ * clk7 ------>| |                            |
+ *         |   |/                             |
+ *         |                                  |
+ *         +----------------------------------+
+ *
+ * Amlogic composite clocks support up to 8 clock sources, and the divider width
+ * is configurable.
+ *
+ * The register bit-field allocation rules for mux, div, and gate are as
+ * follows:
+ * mux: bit[11:9] or bit[27:25]
+ * div: bit[7:0] or bit[23:16]
+ * gate: bit[8] or bit[24]
+ */
+
+#define CLK_COMPOSITE_MUX_SHIFT		9
+#define CLK_COMPOSITE_MUX_MASK		0x7
+
+#define CLK_COMPOSITE_DIV_SHIFT		0
+
+#define CLK_COMPOSITE_GATE_SHIFT		8
+
+static u8 aml_clk_composite_get_parent(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(clk->map, composite->reg_offset, &val);
+	if (ret)
+		return ret;
+
+	val >>=  CLK_COMPOSITE_MUX_SHIFT;
+	val &= CLK_COMPOSITE_MUX_MASK;
+
+	return clk_mux_val_to_index(hw, composite->table, 0, val);
+}
+
+static int aml_clk_composite_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	unsigned int val = clk_mux_index_to_val(composite->table, 0, index);
+	int mux_shift = composite->bit_offset + CLK_COMPOSITE_MUX_SHIFT;
+
+	return regmap_update_bits(clk->map, composite->reg_offset,
+				  CLK_COMPOSITE_MUX_MASK << mux_shift,
+				  val << mux_shift);
+}
+
+static unsigned long aml_clk_composite_recalc_rate(struct clk_hw *hw,
+						   unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(clk->map, composite->reg_offset, &val);
+	if (ret)
+		/* Gives a hint that something is wrong */
+		return 0;
+
+	val >>= composite->bit_offset + CLK_COMPOSITE_DIV_SHIFT;
+	val &= clk_div_mask(composite->div_width);
+
+	return divider_recalc_rate(hw, parent_rate, val, NULL, 0,
+				   composite->div_width);
+}
+
+static int
+aml_clk_composite_determine_rate_for_parent(struct clk_hw *rate_hw,
+					    struct clk_rate_request *req,
+					    struct clk_hw *parent_hw)
+{
+	struct aml_clk *clk = to_aml_clk(rate_hw);
+	struct aml_clk_composite_data *composite = clk->data;
+
+	req->best_parent_hw = parent_hw;
+	req->best_parent_rate = clk_hw_get_rate(parent_hw);
+
+	return divider_determine_rate(rate_hw, req, NULL,
+				      composite->div_width, 0);
+}
+
+static int aml_clk_composite_determine_rate(struct clk_hw *hw,
+					    struct clk_rate_request *req)
+{
+	struct clk_hw *parent;
+	struct clk_rate_request tmp_req;
+	unsigned long rate_diff;
+	unsigned long best_rate_diff = ULONG_MAX;
+	unsigned long best_rate = 0;
+	int i, ret;
+
+	req->best_parent_hw = NULL;
+
+	parent = clk_hw_get_parent(hw);
+	clk_hw_forward_rate_request(hw, req, parent, &tmp_req, req->rate);
+	ret = aml_clk_composite_determine_rate_for_parent(hw, &tmp_req,
+							  parent);
+	if (ret)
+		return ret;
+
+	/*
+	 * Check if rate can be satisfied by current parent clock. Avoid parent
+	 * switching when possible to reduce glitches.
+	 */
+	if (req->rate == tmp_req.rate ||
+	    (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT)) {
+		req->rate = tmp_req.rate;
+		req->best_parent_hw = tmp_req.best_parent_hw;
+		req->best_parent_rate = tmp_req.best_parent_rate;
+
+		return 0;
+	}
+
+	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (!parent)
+			continue;
+
+		clk_hw_forward_rate_request(hw, req, parent, &tmp_req,
+					    req->rate);
+		ret = aml_clk_composite_determine_rate_for_parent(hw, &tmp_req,
+								  parent);
+		if (ret)
+			continue;
+
+		if (req->rate >= tmp_req.rate)
+			rate_diff = req->rate - tmp_req.rate;
+		else
+			rate_diff = tmp_req.rate - req->rate;
+
+		if (!rate_diff || !req->best_parent_hw ||
+		    best_rate_diff > rate_diff) {
+			req->best_parent_hw = parent;
+			req->best_parent_rate = tmp_req.best_parent_rate;
+			best_rate_diff = rate_diff;
+			best_rate = tmp_req.rate;
+		}
+
+		if (!rate_diff)
+			return 0;
+	}
+
+	req->rate = best_rate;
+	return 0;
+}
+
+static int aml_clk_composite_set_rate(struct clk_hw *hw, unsigned long rate,
+				      unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	unsigned int val;
+	int ret;
+	int div_shift = composite->bit_offset + CLK_COMPOSITE_DIV_SHIFT;
+
+	ret = divider_get_val(rate, parent_rate, NULL, composite->div_width, 0);
+	if (ret < 0)
+		return ret;
+
+	val = (unsigned int)ret << div_shift;
+	return regmap_update_bits(clk->map, composite->reg_offset,
+				  clk_div_mask(composite->div_width) <<
+				  div_shift, val);
+}
+
+static int aml_clk_composite_set_rate_and_parent(struct clk_hw *hw,
+						 unsigned long rate,
+						 unsigned long parent_rate,
+						 u8 index)
+{
+	unsigned long temp_rate;
+
+	temp_rate = aml_clk_composite_recalc_rate(hw, parent_rate);
+	if (temp_rate > rate) {
+		aml_clk_composite_set_rate(hw, rate, parent_rate);
+		aml_clk_composite_set_parent(hw, index);
+	} else {
+		aml_clk_composite_set_parent(hw, index);
+		aml_clk_composite_set_rate(hw, rate, parent_rate);
+	}
+
+	return 0;
+}
+
+static int aml_clk_composite_is_enabled(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	unsigned int val;
+
+	regmap_read(clk->map, composite->reg_offset, &val);
+	val &= BIT(composite->bit_offset + CLK_COMPOSITE_GATE_SHIFT);
+
+	return val ? 1 : 0;
+}
+
+static int aml_clk_composite_enable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	u8 bit_idx = composite->bit_offset + CLK_COMPOSITE_GATE_SHIFT;
+
+	return regmap_update_bits(clk->map, composite->reg_offset,
+				  BIT(bit_idx), BIT(bit_idx));
+}
+
+static void aml_clk_composite_disable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_composite_data *composite = clk->data;
+	u8 bit_idx = composite->bit_offset + CLK_COMPOSITE_GATE_SHIFT;
+
+	regmap_update_bits(clk->map, composite->reg_offset,
+			   BIT(bit_idx), 0);
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static void aml_clk_composite_debug_init(struct clk_hw *hw,
+					 struct dentry *dentry)
+{
+	debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops);
+	debugfs_create_file("clk_available_rates", 0444, dentry, hw,
+			    &aml_clk_div_available_rates_fops);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+const struct clk_ops aml_clk_composite_ops = {
+	.get_parent = aml_clk_composite_get_parent,
+	.set_parent = aml_clk_composite_set_parent,
+	.determine_rate = aml_clk_composite_determine_rate,
+	.recalc_rate = aml_clk_composite_recalc_rate,
+	.set_rate = aml_clk_composite_set_rate,
+	.set_rate_and_parent = aml_clk_composite_set_rate_and_parent,
+	.enable = aml_clk_composite_enable,
+	.disable = aml_clk_composite_disable,
+	.is_enabled = aml_clk_composite_is_enabled,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_composite_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_composite_ops, "CLK_AMLOGIC");
+
+MODULE_DESCRIPTION("Amlogic Composite Clock Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk-composite.h b/drivers/clk/amlogic/clk-composite.h
new file mode 100644
index 000000000000..6a356d697b78
--- /dev/null
+++ b/drivers/clk/amlogic/clk-composite.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_COMPOSITE_H
+#define __AML_CLK_COMPOSITE_H
+
+#include <linux/clk-provider.h>
+
+struct aml_clk_composite_data {
+	unsigned int	reg_offset;
+	u8		bit_offset;
+	u8		div_width;
+	u32		*table;
+};
+
+extern const struct clk_ops aml_clk_composite_ops;
+
+#endif /* __AML_CLK_COMPOSITE_H */
diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c
index 03ccfa78c511..e71dcb6b46b7 100644
--- a/drivers/clk/amlogic/clk.c
+++ b/drivers/clk/amlogic/clk.c
@@ -10,6 +10,7 @@
 
 #include "clk.h"
 #include "clk-basic.h"
+#include "clk-composite.h"
 
 static const struct {
 	unsigned int type;
@@ -19,6 +20,7 @@ static const struct {
 	ENTRY(AML_CLKTYPE_MUX),
 	ENTRY(AML_CLKTYPE_DIV),
 	ENTRY(AML_CLKTYPE_GATE),
+	ENTRY(AML_CLKTYPE_COMPOSITE),
 #undef ENTRY
 };
 
@@ -95,6 +97,11 @@ static int aml_clk_div_available_rates_show(struct seq_file *s, void *data)
 			div_flags = div->flags;
 			div_width = div->width;
 		}
+	} else if (clk->type == AML_CLKTYPE_COMPOSITE) {
+		struct aml_clk_composite_data *composite = clk->data;
+
+		div_val = (1 << composite->div_width) - 1;
+		div_width = composite->div_width;
 	} else {
 		pr_err("%s: Unsupported clock type\n", clk_hw_get_name(hw));
 		return -EINVAL;
diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h
index ec0547c1354a..e5fe85c2969f 100644
--- a/drivers/clk/amlogic/clk.h
+++ b/drivers/clk/amlogic/clk.h
@@ -14,6 +14,7 @@ enum aml_clk_type {
 	AML_CLKTYPE_MUX		= 1,
 	AML_CLKTYPE_DIV		= 2,
 	AML_CLKTYPE_GATE	= 3,
+	AML_CLKTYPE_COMPOSITE	= 4,
 };
 
 struct aml_clk {

-- 
2.42.0



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

* [PATCH 06/13] clk: amlogic: Add noglitch clock driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (4 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 05/13] clk: amlogic: Add composite " Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09 21:51   ` Martin Blumenstingl
  2026-02-09  5:48 ` [PATCH 07/13] clk: amlogic: Add duandiv " Chuan Liu via B4 Relay
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Implement clk_ops support for Amlogic noglitch clocks. The noglitch
clock consists of two Amlogic composite clocks and a noglitch
multiplexer. It ensures output clock continuity through ping-pong
switching between the two composite clocks, with the noglitch mux
suppressing glitches during clock transitions.

Amlogic noglitch clocks are used for modules requiring high clock
quality, such as GPU, video codecs, and other sensitive peripherals.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Makefile       |   1 +
 drivers/clk/amlogic/clk-noglitch.c | 584 +++++++++++++++++++++++++++++++++++++
 drivers/clk/amlogic/clk-noglitch.h |  29 ++
 drivers/clk/amlogic/clk.c          |   5 +
 drivers/clk/amlogic/clk.h          |   1 +
 5 files changed, 620 insertions(+)

diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index 58a5e7bc8993..72cd2525e078 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-amlogic.o
 clk-amlogic-y += clk.o
 clk-amlogic-y += clk-basic.o
 clk-amlogic-y += clk-composite.o
+clk-amlogic-y += clk-noglitch.o
diff --git a/drivers/clk/amlogic/clk-noglitch.c b/drivers/clk/amlogic/clk-noglitch.c
new file mode 100644
index 000000000000..1290e266d33b
--- /dev/null
+++ b/drivers/clk/amlogic/clk-noglitch.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-noglitch.h"
+
+/*
+ * Amlogic no-glitch clock module:
+ *
+ *             mux0       div0        gate0     noglitch_mux
+ *              |  mux1    |    div1    |    gate1  |
+ *              |   |      |     |      |      |    |
+ *         +----|---|------|-----|------|------|----|----+
+ *         |   \|/  |      |     |      |      |    |    |
+ *         |   |\   |      |     |      |      |    |    |
+ * clk0 ------>| |  |      |     |      |      |    |    |
+ * clk1 ------>| |  |      |     |      |      |    |    |
+ * clk2 ------>| |  |     \|/    |     \|/     |   \|/   |
+ * clk3 ------>| |  |   +-----+  |   +------+  |   |\    |
+ *         |   | |--o-->| div |--o-->| gate |--o-->| |   |
+ * clk4 ------>| |  |   +-----+  |   +------+  |   | |   |
+ * clk5 ------>| |  |            |             |   | |   |
+ * clk6 ------>| |  |            |             |   | |   |
+ * clk7 ------>| |  |            |             |   | |   |
+ *         |   |/   |            |             |   | |   |
+ *         |        |            |             |   | |   |
+ *         |    +---+      +-----+      +------+   | |-------> clk out
+ *         |   \|/         |            |          | |   |
+ *         |   |\          |            |          | |   |
+ * clk0 ------>| |         |            |          | |   |
+ * clk1 ------>| |         |            |          | |   |
+ * clk2 ------>| |        \|/          \|/         | |   |
+ * clk3 ------>| |      +-----+     +------+       | |   |
+ *         |   | |--- ->| div |---->| gate |------>| |   |
+ * clk4 ------>| |      +-----+     +------+       |/    |
+ * clk5 ------>| |                                       |
+ * clk6 ------>| |                                       |
+ * clk7 ------>| |                                       |
+ *         |   |/                                        |
+ *         |                                             |
+ *         +---------------------------------------------+
+ *
+ * The purpose of the Amlogic no-glitch clock is to suppress glitches generated
+ * during mux switching.
+ *
+ * Its internal implementation consists of two Amlogic composite clocks and one
+ * noglitch_mux. The noglitch_mux filters out a certain number of clock cycles
+ * from both the original channel and the new channel, and then continuously
+ * outputs the clock from the new channel. This prevents glitches caused by an
+ * insufficient settling time when switching clocks. The timing diagram of
+ * noglitch_mux clock switching is shown below.
+ *
+ *        __    __    __    __    __    __    __    __
+ * ori:  |  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|
+ *                   ^
+ *                   1 * cycle original channel
+ *        _   _   _   _   _   _   _   _   _   _   _   _
+ * new:  | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_|
+ *                                       ^
+ *                                       5 * cycles new channel
+ *        __    __                        _   _   _   _
+ * out:  |  |__|  |______________________| |_| |_| |_| |_|
+ *              ^                        ^
+ *              start switching mux      switch to new channel
+ *
+ * To prevent glitches from propagating to clk_out and affecting the normal
+ * operation of glitch-sensitive modules, the no-glitch clock must be configured
+ * following the specified sequence:
+ *   - When the clock gate is disabled: configure it as a normal composite clock
+ *     (any glitches generated will be blocked by the gate and will not
+ *     propagate to clk_out).
+ *   - When the clock gate is enabled: configure it according to the following
+ *     sequence to suppress glitches:
+ *       - Configure and enable the idle composite clock path of the
+ *         noglitch_mux with the target frequency/parent clock.
+ *       - Switch the noglitch_mux to the channel prepared in the previous step.
+ *       - Disable the clock of the original noglitch_mux channel.
+ */
+
+union aml_clk_noglitch_reg {
+	struct {
+		u32 div0		:8;  /* bit0 - bit7 */
+		u32 gate0		:1;  /* bit8 */
+		u32 mux0		:3;  /* bit9 - bit11 */
+		u32 reserved		:4;  /* bit12 - bit15 */
+		u32 div1		:8;  /* bit16 - bit23 */
+		u32 gate1		:1;  /* bit24 */
+		u32 mux1		:3;  /* bit25 - bit27 */
+		u32 reserved1		:3;  /* bit28 - bit30 */
+		u32 noglitch_mux	:1;  /* bit31 */
+
+	} bits;
+	u32 val;
+};
+
+static u8 aml_clk_noglitch_get_parent(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	union aml_clk_noglitch_reg reg;
+	int ret;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux)  /* mux1 */
+		return clk_mux_val_to_index(hw, noglitch->table, 0,
+					    reg.bits.mux1);
+	else  /* mux0 */
+		return clk_mux_val_to_index(hw, noglitch->table, 0,
+					    reg.bits.mux0);
+}
+
+static int aml_clk_noglitch_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	unsigned int val = clk_mux_index_to_val(noglitch->table, 0, index);
+	union aml_clk_noglitch_reg reg;
+	int ret;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux) {  /* mux1 */
+		/*
+		 * When the no-glitch output is enabled, the clock must be
+		 * configured with the following timing sequence to suppress
+		 * glitches:
+		 *
+		 * step1: Configure and enable Channel 0 clock.
+		 * step2: Switch the no-glitch mux to Channel 0.
+		 * step3: Disable Channel 1 clock.
+		 */
+		if (reg.bits.gate1) {
+			/* step1: Configure and enable Channel 0 clock */
+			reg.bits.mux0 = val;
+			reg.bits.gate0 = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			/* step2: Switch the no-glitch mux to Channel 0 */
+			reg.bits.noglitch_mux = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			/* step3: Disable Channel 1 clock */
+			reg.bits.gate1 = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		} else {
+			/*
+			 * When no-glitch is disabled, no glitch will occur,
+			 * allowing direct clock source switching.
+			 */
+			reg.bits.mux1 = val;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		}
+	} else {  /* mux0 */
+		if (reg.bits.gate0) {
+			reg.bits.mux1 = val;
+			reg.bits.gate1 = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			reg.bits.noglitch_mux = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			reg.bits.gate0 = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		} else {
+			reg.bits.mux0 = val;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static unsigned long aml_clk_noglitch_recalc_rate(struct clk_hw *hw,
+						  unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	union aml_clk_noglitch_reg reg;
+	int ret;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux)  /* mux1 */
+		return divider_recalc_rate(hw, parent_rate, reg.bits.div1, NULL,
+					   0, CLK_NOGLITCH_DIV_WIDTH);
+	else  /* mux0 */
+		return divider_recalc_rate(hw, parent_rate, reg.bits.div0, NULL,
+					   0, CLK_NOGLITCH_DIV_WIDTH);
+}
+
+static int
+aml_clk_noglitch_determine_rate_for_parent(struct clk_hw *rate_hw,
+					   struct clk_rate_request *req,
+					   struct clk_hw *parent_hw)
+{
+	req->best_parent_hw = parent_hw;
+	req->best_parent_rate = clk_hw_get_rate(parent_hw);
+
+	return divider_determine_rate(rate_hw, req, NULL,
+				      CLK_NOGLITCH_DIV_WIDTH, 0);
+}
+
+static int aml_clk_noglitch_determine_rate(struct clk_hw *hw,
+					   struct clk_rate_request *req)
+{
+	struct clk_hw *parent;
+	struct clk_rate_request tmp_req;
+	unsigned long rate_diff;
+	unsigned long best_rate_diff = ULONG_MAX;
+	unsigned long best_rate = 0;
+	int i, ret;
+
+	req->best_parent_hw = NULL;
+
+	parent = clk_hw_get_parent(hw);
+	clk_hw_forward_rate_request(hw, req, parent, &tmp_req, req->rate);
+	ret = aml_clk_noglitch_determine_rate_for_parent(hw, &tmp_req,
+							 parent);
+	if (ret)
+		return ret;
+
+	/*
+	 * Check if rate can be satisfied by current parent clock. Avoid parent
+	 * switching when possible to reduce glitches.
+	 */
+	if (req->rate == tmp_req.rate ||
+	    (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT)) {
+		req->rate = tmp_req.rate;
+		req->best_parent_hw = tmp_req.best_parent_hw;
+		req->best_parent_rate = tmp_req.best_parent_rate;
+
+		return 0;
+	}
+
+	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (!parent)
+			continue;
+
+		clk_hw_forward_rate_request(hw, req, parent, &tmp_req,
+					    req->rate);
+		ret = aml_clk_noglitch_determine_rate_for_parent(hw, &tmp_req,
+								 parent);
+		if (ret)
+			continue;
+
+		if (req->rate >= tmp_req.rate)
+			rate_diff = req->rate - tmp_req.rate;
+		else
+			rate_diff = tmp_req.rate - req->rate;
+
+		if (!rate_diff || !req->best_parent_hw ||
+		    best_rate_diff > rate_diff) {
+			req->best_parent_hw = parent;
+			req->best_parent_rate = tmp_req.best_parent_rate;
+			best_rate_diff = rate_diff;
+			best_rate = tmp_req.rate;
+		}
+
+		if (!rate_diff)
+			return 0;
+	}
+
+	req->rate = best_rate;
+	return 0;
+}
+
+static int aml_clk_noglitch_set_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	union aml_clk_noglitch_reg reg;
+	int ret, div;
+
+	div = divider_get_val(rate, parent_rate, NULL,
+			      CLK_NOGLITCH_DIV_WIDTH, 0);
+	if (div < 0)
+		return div;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux) {  /* mux1 */
+		/*
+		 * When the no-glitch output is enabled, the clock must be
+		 * configured with the following timing sequence to suppress
+		 * glitches:
+		 *
+		 * step1: Configure and enable Channel 0 clock.
+		 * step2: Switch the no-glitch mux to Channel 0.
+		 * step3: Disable Channel 1 clock.
+		 */
+		if (reg.bits.gate1) {
+			/* step1: Configure and enable Channel 0 clock */
+			reg.bits.div0 = div;
+			reg.bits.gate0 = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			/* step2: Switch the no-glitch mux to Channel 0 */
+			reg.bits.noglitch_mux = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			/* step3: Disable Channel 1 clock */
+			reg.bits.gate1 = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		} else {
+			/*
+			 * When no-glitch is disabled, no glitch will occur,
+			 * allowing direct clock source switching.
+			 */
+			reg.bits.div1 = div;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		}
+	} else {  /* mux0 */
+		if (reg.bits.gate0) {
+			reg.bits.div1 = div;
+			reg.bits.gate1 = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			reg.bits.noglitch_mux = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			reg.bits.gate0 = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		} else {
+			reg.bits.div0 = div;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+
+	return 0;
+}
+
+static int aml_clk_noglitch_set_rate_and_parent(struct clk_hw *hw,
+					       unsigned long rate,
+					       unsigned long parent_rate,
+					       u8 index)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	unsigned int parent_index = clk_mux_index_to_val(noglitch->table, 0,
+							 index);
+	union aml_clk_noglitch_reg reg;
+	int ret, div;
+
+	div = divider_get_val(rate, parent_rate, NULL,
+			      CLK_NOGLITCH_DIV_WIDTH, 0);
+	if (div < 0)
+		return div;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux) {  /* mux1 */
+		/*
+		 * When the no-glitch output is enabled, the clock must be
+		 * configured with the following timing sequence to suppress
+		 * glitches:
+		 *
+		 * step1: Configure and enable Channel 0 clock.
+		 * step2: Switch the no-glitch mux to Channel 0.
+		 * step3: Disable Channel 1 clock.
+		 */
+		if (reg.bits.gate1) {
+			/* step1: Configure and enable Channel 0 clock */
+			reg.bits.mux0 = parent_index;
+			reg.bits.div0 = div;
+			reg.bits.gate0 = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			/* step2: Switch the no-glitch mux to Channel 0 */
+			reg.bits.noglitch_mux = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			/* step3: Disable Channel 1 clock */
+			reg.bits.gate1 = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		} else {
+			/*
+			 * When no-glitch is disabled, no glitch will occur,
+			 * allowing direct clock source switching.
+			 */
+			reg.bits.mux1 = parent_index;
+			reg.bits.div1 = div;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		}
+	} else {  /* mux0 */
+		if (reg.bits.gate0) {
+			reg.bits.mux1 = parent_index;
+			reg.bits.div1 = div;
+			reg.bits.gate1 = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			reg.bits.noglitch_mux = 1;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+
+			reg.bits.gate0 = 0;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		} else {
+			reg.bits.mux0 = parent_index;
+			reg.bits.div0 = div;
+			ret = regmap_write(clk->map, noglitch->reg_offset,
+					   reg.val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+
+	return 0;
+}
+
+static int aml_clk_noglitch_is_enabled(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	union aml_clk_noglitch_reg reg;
+	int ret;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux)  /* mux1 */
+		return reg.bits.gate1;
+	else
+		return reg.bits.gate0;
+}
+
+static int aml_clk_noglitch_enable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	union aml_clk_noglitch_reg reg;
+	int ret;
+
+	ret = regmap_read(clk->map, noglitch->reg_offset, &reg.val);
+	if (ret)
+		return ret;
+
+	if (reg.bits.noglitch_mux)  /* mux1 */
+		reg.bits.gate1 = 1;
+	else
+		reg.bits.gate0 = 1;
+
+	return regmap_write(clk->map, noglitch->reg_offset, reg.val);
+}
+
+static void aml_clk_noglitch_disable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_noglitch_data *noglitch = clk->data;
+	union aml_clk_noglitch_reg reg;
+
+	if (regmap_read(clk->map, noglitch->reg_offset, &reg.val))
+		return;
+
+	if (reg.bits.noglitch_mux)  /* mux1 */
+		reg.bits.gate1 = 0;
+	else
+		reg.bits.gate0 = 0;
+
+	regmap_write(clk->map, noglitch->reg_offset, reg.val);
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static void
+aml_clk_noglitch_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops);
+	debugfs_create_file("clk_available_rates", 0444, dentry, hw,
+			    &aml_clk_div_available_rates_fops);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+const struct clk_ops aml_clk_noglitch_ops = {
+	.get_parent = aml_clk_noglitch_get_parent,
+	.set_parent = aml_clk_noglitch_set_parent,
+	.determine_rate = aml_clk_noglitch_determine_rate,
+	.recalc_rate = aml_clk_noglitch_recalc_rate,
+	.set_rate = aml_clk_noglitch_set_rate,
+	.set_rate_and_parent = aml_clk_noglitch_set_rate_and_parent,
+	.enable = aml_clk_noglitch_enable,
+	.disable = aml_clk_noglitch_disable,
+	.is_enabled = aml_clk_noglitch_is_enabled,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init = aml_clk_noglitch_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_noglitch_ops, "CLK_AMLOGIC");
+
+MODULE_DESCRIPTION("Amlogic Noglitch Clock Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk-noglitch.h b/drivers/clk/amlogic/clk-noglitch.h
new file mode 100644
index 000000000000..3b8408afd77a
--- /dev/null
+++ b/drivers/clk/amlogic/clk-noglitch.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_NOGLITCH_H
+#define __AML_CLK_NOGLITCH_H
+
+#include <linux/clk-provider.h>
+
+/*
+ * NOTE: Currently, the divider of the AM no-glitch clock uses a fixed 7-bit
+ * register field width.
+ *
+ * To better consolidate the code, the implementation for calculating
+ * "clk_available_rates" for divider, composite, and no-glitch clocks has been
+ * unified into a single function (in clk.c). Therefore, this macro is defined
+ * here for use by the related functions.
+ */
+#define CLK_NOGLITCH_DIV_WIDTH		7
+
+struct aml_clk_noglitch_data {
+	unsigned int	reg_offset;
+	u32		*table;
+};
+
+extern const struct clk_ops aml_clk_noglitch_ops;
+
+#endif /* __AML_CLK_NOGLITCH_H */
diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c
index e71dcb6b46b7..12e9596668d2 100644
--- a/drivers/clk/amlogic/clk.c
+++ b/drivers/clk/amlogic/clk.c
@@ -11,6 +11,7 @@
 #include "clk.h"
 #include "clk-basic.h"
 #include "clk-composite.h"
+#include "clk-noglitch.h"
 
 static const struct {
 	unsigned int type;
@@ -21,6 +22,7 @@ static const struct {
 	ENTRY(AML_CLKTYPE_DIV),
 	ENTRY(AML_CLKTYPE_GATE),
 	ENTRY(AML_CLKTYPE_COMPOSITE),
+	ENTRY(AML_CLKTYPE_NOGLITCH),
 #undef ENTRY
 };
 
@@ -102,6 +104,9 @@ static int aml_clk_div_available_rates_show(struct seq_file *s, void *data)
 
 		div_val = (1 << composite->div_width) - 1;
 		div_width = composite->div_width;
+	} else if (clk->type == AML_CLKTYPE_NOGLITCH) {
+		div_val = (1 << CLK_NOGLITCH_DIV_WIDTH) - 1;
+		div_width = CLK_NOGLITCH_DIV_WIDTH;
 	} else {
 		pr_err("%s: Unsupported clock type\n", clk_hw_get_name(hw));
 		return -EINVAL;
diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h
index e5fe85c2969f..14045fa109c0 100644
--- a/drivers/clk/amlogic/clk.h
+++ b/drivers/clk/amlogic/clk.h
@@ -15,6 +15,7 @@ enum aml_clk_type {
 	AML_CLKTYPE_DIV		= 2,
 	AML_CLKTYPE_GATE	= 3,
 	AML_CLKTYPE_COMPOSITE	= 4,
+	AML_CLKTYPE_NOGLITCH	= 5,
 };
 
 struct aml_clk {

-- 
2.42.0



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

* [PATCH 07/13] clk: amlogic: Add duandiv clock driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (5 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 06/13] clk: amlogic: Add noglitch " Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 08/13] clk: amlogic: Add PLL driver Chuan Liu via B4 Relay
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Implement clk_ops support for Amlogic dualdiv clocks. The dualdiv clock
comprises two independent divider channels with counters. It achieves
fractional division functionality by alternating between the two divided
clocks.

Amlogic dualdiv clocks are used for modules requiring fractional
division without concern for clock phase or duty cycle, commonly
generating 32.768KHz from a 24MHz crystal input for RTC applications.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Makefile      |   1 +
 drivers/clk/amlogic/clk-dualdiv.c | 365 ++++++++++++++++++++++++++++++++++++++
 drivers/clk/amlogic/clk-dualdiv.h |  27 +++
 drivers/clk/amlogic/clk.c         |   1 +
 drivers/clk/amlogic/clk.h         |   1 +
 5 files changed, 395 insertions(+)

diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index 72cd2525e078..bc2b22b4d3c9 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -5,4 +5,5 @@ obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-amlogic.o
 clk-amlogic-y += clk.o
 clk-amlogic-y += clk-basic.o
 clk-amlogic-y += clk-composite.o
+clk-amlogic-y += clk-dualdiv.o
 clk-amlogic-y += clk-noglitch.o
diff --git a/drivers/clk/amlogic/clk-dualdiv.c b/drivers/clk/amlogic/clk-dualdiv.c
new file mode 100644
index 000000000000..1752b89ec0f2
--- /dev/null
+++ b/drivers/clk/amlogic/clk-dualdiv.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-basic.h"
+#include "clk-dualdiv.h"
+
+/*
+ * Amlogic dualdiv clock module:
+ *
+ *                          n0         m0     div_mode    clkout_gate
+ *                          |          |        |              |
+ *          clkin_gate      |    n1    |    m1  | force_clkout |
+ *             |            |    |     |    |   |     |        |
+ *      +------|------------|----|-----|----|---|-----|--------|----+
+ *      |      |           \|/   |    \|/   |  \|/    |        |    |
+ *      |      |         +-----+ |  +-----+ |  |\     |        |    |
+ *      |      |     +-->| div |-o->| cnt |-o->| |   \|/       |    |
+ *      |     \|/    |   +-----+ |  +-----+ |  | |   |\        |    |
+ * clk  |  +------+  |           |          |  | |   | |       |    |
+ * in ---->| gate |--+      +----+     +----+  | |-->| |      \|/   |
+ *      |  +------+  |     \|/        \|/      | |   | |   +------+ |
+ *      |            |   +-----+    +-----+    | |   | |-->| gate |----> clk
+ *      |            +-->| div |--->| cnt |--->| |   | |   +------+ |    out
+ *      |            |   +-----+    +-----+    |/    | |            |
+ *      |            +------------------------------>| |            |
+ *      |                                            |/             |
+ *      |                                                           |
+ *      +-----------------------------------------------------------+
+ *
+ * The Amlogic dualdiv clock is used to implement fractional division (e.g., a
+ * 24 MHz input clock with a 32.768 KHz output for the RTC).
+ *
+ * The functional description of these parameters is described below as follows:
+ *   - clkin_gate: clock input gate
+ *   - n0: Divider value corresponding to channel 0
+ *   - n1: Divider value corresponding to channel 1
+ *   - m0: Number of output cycles per burst on channel 0 (switches to channel 1
+ *         after the cycles are output)
+ *   - m1: Number of output cycles per burst on channel 0 (switches to channel 0
+ *         after the cycles are output)
+ *   - div_mode: Division mode configuration:
+ *     - div_mode = 0: Normal division mode:
+ *         dualdiv_out = clk_in / n0;
+ *     - div_mode = 1: Dualdiv (fractional) division mode:
+ *         dualdiv_out = clk_in / ((n0 * m0 + n1 * m1) / (m0 + m1));
+ *   - force_clkout: force clock_out = clock_in
+ *   - clkout_gate: clock output gate
+ *
+ * The fractional division principle of dualdiv is to alternately switch via a
+ * mux between two integer-divided clocks, thereby achieving an effective
+ * fractional division.
+ *
+ * For example, when configuring a dualdiv ratio of 1.75, the clock waveform
+ * is illustrated below (n0 = 1, m0 = 1, n1 = 2, m1 = 3):
+ *
+ *             _   _   _   _   _   _   _   _   _   _   _   _
+ * clk in:    | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_|
+ *             _   _   _   _   _   _   _   _   _   _   _   _
+ * channel0:  | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_|
+ *             __    __    __    __    __    __    __    __
+ * channel1:  |  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|
+ *             _   __    __    __    _   __    __    __    _
+ * clk out:   | |_|  |__|  |__|  |__| |_|  |__|  |__|  |__| |_|
+ *            |<->|<--------------->|<->|<--------------->|<->|
+ *              m0          m1        m0         m1         m0
+ */
+
+#define AML_DUALDIV_REG0_OFFSET			(0)
+#define AML_DUALDIV_REG1_OFFSET			(4)
+
+struct aml_dualdiv_reg_parms {
+	union {
+		struct {
+			u32 n0			:12;  /* bit0 - bit11 */
+			u32 n1			:12;  /* bit12 - bit23 */
+			u32 reserved0		:4;  /* bit24 - bit27 */
+			u32 div_mode		:2;  /* bit28 - bit29 */
+			u32 clkout_gate		:1;  /* bit30 */
+			u32 clkin_gate		:1;  /* bit31 */
+		} bits;
+		u32 val;
+	} reg0;
+	union {
+		struct {
+			u32 m0			:12;  /* bit0 - bit11 */
+			u32 m1			:12;  /* bit12 - bit23 */
+			u32 force_clkout	:1;  /* bit24 */
+			u32 reserved0		:5;  /* bit25 - bit29 */
+			u32 out_mux		:2;  /* bit30 - bit31 */
+
+		} bits;
+		u32 val;
+	} reg1;
+};
+
+static inline unsigned int _get_div_mult(unsigned int val)
+{
+	return val + 1;
+}
+
+static unsigned long
+aml_dualdiv_param_to_rate(unsigned long parent_rate,
+			  const struct aml_clk_dualdiv_param *p)
+{
+	unsigned int n0, n1, m0, m1;
+
+	n0 = _get_div_mult(p->n0);
+	if (!p->div_mode)
+		return DIV_ROUND_CLOSEST(parent_rate, n0);
+
+	n1 = _get_div_mult(p->n1);
+	m0 = _get_div_mult(p->m0);
+	m1 = _get_div_mult(p->m1);
+
+	return DIV_ROUND_CLOSEST(parent_rate * (m0 + m1), n0 * m0 + n1 * m1);
+}
+
+static unsigned long aml_clk_dualdiv_recalc_rate(struct clk_hw *hw,
+						 unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	struct aml_dualdiv_reg_parms reg;
+	struct aml_clk_dualdiv_param parm;
+
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET,
+		    &reg.reg1.val);
+	if (reg.reg1.bits.force_clkout == 1)
+		return parent_rate;
+
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		    &reg.reg0.val);
+	parm.div_mode = reg.reg0.bits.div_mode;
+	parm.n0 = reg.reg0.bits.n0;
+	parm.n1 = reg.reg0.bits.n1;
+	parm.m0 = reg.reg1.bits.m0;
+	parm.m1 = reg.reg1.bits.m1;
+
+	return aml_dualdiv_param_to_rate(parent_rate, &parm);
+}
+
+/*
+ * NOTE: Dynamically calculating the divider parameters based on the target
+ * frequency has relatively high algorithmic complexity, and the number of
+ * frequency points that require dualdiv division in current use cases is
+ * limited.
+ *
+ * Therefore, only a table-based lookup method is supported to obtain the
+ * best-matched divider parameters at present.
+ */
+static const struct aml_clk_dualdiv_param *
+aml_clk_dualdiv_get_setting(unsigned long rate, unsigned long parent_rate,
+			    struct aml_clk_dualdiv_data *dualdiv)
+{
+	const struct aml_clk_dualdiv_param *table = dualdiv->table;
+	unsigned long best = 0, now = 0;
+	unsigned int i, best_i = 0;
+
+	if (!table)
+		return NULL;
+
+	for (i = 0; i < dualdiv->table_count; i++) {
+		now = aml_dualdiv_param_to_rate(parent_rate, &table[i]);
+
+		/* If we get an exact match, don't bother any further */
+		if (now == rate) {
+			return &table[i];
+		} else if (abs(now - rate) < abs(best - rate)) {
+			best = now;
+			best_i = i;
+		}
+	}
+
+	return (struct aml_clk_dualdiv_param *)&table[best_i];
+}
+
+static int aml_clk_dualdiv_determine_rate(struct clk_hw *hw,
+					  struct clk_rate_request *req)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	const struct aml_clk_dualdiv_param *setting;
+
+	/* Just need to set "force_clkout" = 1 */
+	if (req->rate == req->best_parent_rate) {
+		req->rate = req->best_parent_rate;
+
+		return 0;
+	}
+
+	setting = aml_clk_dualdiv_get_setting(req->rate, req->best_parent_rate,
+					      dualdiv);
+	if (setting)
+		req->rate = aml_dualdiv_param_to_rate(req->best_parent_rate,
+						      setting);
+	else
+		req->rate = aml_clk_dualdiv_recalc_rate(hw,
+							req->best_parent_rate);
+
+	return 0;
+}
+
+static int aml_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	struct aml_dualdiv_reg_parms reg;
+	const struct aml_clk_dualdiv_param *setting;
+
+	/* Just need to set "force_clkout" = 1 */
+	if (rate == parent_rate) {
+		regmap_read(clk->map,
+			    dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET,
+			    &reg.reg1.val);
+		reg.reg1.bits.force_clkout = 1;
+		regmap_write(clk->map,
+			     dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET,
+			     reg.reg1.val);
+
+		return 0;
+	}
+
+	setting = aml_clk_dualdiv_get_setting(rate, parent_rate, dualdiv);
+	if (!setting)
+		return -EINVAL;
+
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		    &reg.reg0.val);
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET,
+		    &reg.reg1.val);
+	reg.reg1.bits.force_clkout = 0;
+
+	reg.reg0.bits.div_mode = setting->div_mode;
+	reg.reg0.bits.n0 = setting->n0;
+	reg.reg0.bits.n1 = setting->n1;
+	reg.reg1.bits.m0 = setting->m0;
+	reg.reg1.bits.m1 = setting->m1;
+
+	regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		     reg.reg0.val);
+	regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET,
+		     reg.reg1.val);
+
+	return 0;
+}
+
+static int aml_clk_dualdiv_enable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	struct aml_dualdiv_reg_parms reg;
+
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		    &reg.reg0.val);
+	reg.reg0.bits.clkin_gate = 1;
+	reg.reg0.bits.clkout_gate = 1;
+
+	regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		     reg.reg0.val);
+
+	return 0;
+}
+
+static void aml_clk_dualdiv_disable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	struct aml_dualdiv_reg_parms reg;
+
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		    &reg.reg0.val);
+	reg.reg0.bits.clkin_gate = 0;
+	reg.reg0.bits.clkout_gate = 0;
+
+	regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		     reg.reg0.val);
+}
+
+static int aml_clk_dualdiv_is_enabled(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	struct aml_dualdiv_reg_parms reg;
+
+	regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET,
+		    &reg.reg0.val);
+
+	if (reg.reg0.bits.clkin_gate && reg.reg0.bits.clkout_gate)
+		return 1;
+	else
+		return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static int aml_clk_dualdiv_available_rates_show(struct seq_file *s, void *data)
+{
+	struct clk_hw *hw = s->private;
+	struct clk_hw *phw = clk_hw_get_parent(hw);
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_clk_dualdiv_data *dualdiv = clk->data;
+	unsigned long rate, prate;
+	unsigned long core_min_rate, core_max_rate;
+	int i;
+
+	if (!phw) {
+		pr_err("%s: can't get parent\n", clk_hw_get_name(hw));
+
+		return -ENOENT;
+	}
+
+	prate = clk_hw_get_rate(phw);
+	clk_hw_get_rate_range(hw, &core_min_rate, &core_max_rate);
+	if (dualdiv->table) {
+		for (i = 0; i < dualdiv->table_count; i++) {
+			rate = aml_dualdiv_param_to_rate(prate,
+							 &dualdiv->table[i]);
+			if (rate < core_min_rate || rate > core_max_rate)
+				continue;
+
+			seq_printf(s, "%ld\n", (unsigned long)rate);
+		}
+	} else {
+		seq_printf(s, "%ld\n", prate);
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(aml_clk_dualdiv_available_rates);
+
+static void aml_clk_dualdiv_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+
+	debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops);
+	if (clk->type == AML_CLKTYPE_DUALDIV)
+		debugfs_create_file("clk_available_rates", 0444, dentry, hw,
+				    &aml_clk_dualdiv_available_rates_fops);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+const struct clk_ops aml_clk_dualdiv_ops = {
+	.enable		= aml_clk_dualdiv_enable,
+	.disable	= aml_clk_dualdiv_disable,
+	.is_enabled	= aml_clk_dualdiv_is_enabled,
+	.recalc_rate	= aml_clk_dualdiv_recalc_rate,
+	.determine_rate	= aml_clk_dualdiv_determine_rate,
+	.set_rate	= aml_clk_dualdiv_set_rate,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init	= aml_clk_dualdiv_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_clk_dualdiv_ops, "CLK_AMLOGIC");
+
+MODULE_DESCRIPTION("Amlogic Dualdiv Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk-dualdiv.h b/drivers/clk/amlogic/clk-dualdiv.h
new file mode 100644
index 000000000000..fbbae6686afd
--- /dev/null
+++ b/drivers/clk/amlogic/clk-dualdiv.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_DUALDIV_H
+#define __AML_CLK_DUALDIV_H
+
+#include <linux/clk-provider.h>
+
+struct aml_clk_dualdiv_param {
+	unsigned int div_mode;
+	unsigned int n0;
+	unsigned int m0;
+	unsigned int n1;
+	unsigned int m1;
+};
+
+struct aml_clk_dualdiv_data {
+	unsigned int			reg_offset;
+	struct	aml_clk_dualdiv_param	*table;
+	unsigned int			table_count;
+};
+
+extern const struct clk_ops aml_clk_dualdiv_ops;
+
+#endif /* __AML_CLK_DUALDIV_MUX_H */
diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c
index 12e9596668d2..5431aa320dfa 100644
--- a/drivers/clk/amlogic/clk.c
+++ b/drivers/clk/amlogic/clk.c
@@ -23,6 +23,7 @@ static const struct {
 	ENTRY(AML_CLKTYPE_GATE),
 	ENTRY(AML_CLKTYPE_COMPOSITE),
 	ENTRY(AML_CLKTYPE_NOGLITCH),
+	ENTRY(AML_CLKTYPE_DUALDIV),
 #undef ENTRY
 };
 
diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h
index 14045fa109c0..c1d58a08e407 100644
--- a/drivers/clk/amlogic/clk.h
+++ b/drivers/clk/amlogic/clk.h
@@ -16,6 +16,7 @@ enum aml_clk_type {
 	AML_CLKTYPE_GATE	= 3,
 	AML_CLKTYPE_COMPOSITE	= 4,
 	AML_CLKTYPE_NOGLITCH	= 5,
+	AML_CLKTYPE_DUALDIV	= 6,
 };
 
 struct aml_clk {

-- 
2.42.0



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

* [PATCH 08/13] clk: amlogic: Add PLL driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (6 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 07/13] clk: amlogic: Add duandiv " Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09 15:37   ` kernel test robot
  2026-02-09 17:35   ` kernel test robot
  2026-02-09  5:48 ` [PATCH 09/13] clk: amlogic: Add DT-based clock registration functions Chuan Liu via B4 Relay
                   ` (5 subsequent siblings)
  13 siblings, 2 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Implement clk_ops support for Amlogic PLL.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Makefile  |   1 +
 drivers/clk/amlogic/clk-pll.c | 701 ++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/amlogic/clk-pll.h |  43 +++
 drivers/clk/amlogic/clk.c     |   1 +
 drivers/clk/amlogic/clk.h     |   1 +
 5 files changed, 747 insertions(+)

diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index bc2b22b4d3c9..6956592c41c8 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -7,3 +7,4 @@ clk-amlogic-y += clk-basic.o
 clk-amlogic-y += clk-composite.o
 clk-amlogic-y += clk-dualdiv.o
 clk-amlogic-y += clk-noglitch.o
+clk-amlogic-y += clk-pll.o
diff --git a/drivers/clk/amlogic/clk-pll.c b/drivers/clk/amlogic/clk-pll.c
new file mode 100644
index 000000000000..fe97592a4619
--- /dev/null
+++ b/drivers/clk/amlogic/clk-pll.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-pll.h"
+
+/*
+ * Amlogic PLL module:
+ *
+ *           +------------------------------------------------------+
+ *           |      +-------+      +-----+                          |
+ * osc_in --------->| div N |----->|     |   +-----+                |
+ *           |      +-------+      |     |   |     |   +--------+   |
+ *           |                     |     |-->| VCO |-->| div OD |------>pll_out
+ *           |      +----------+   |     |   |     |   +--------+   |
+ *           |  +-->| M & frac |-->|     |   +-----+                |
+ *           |  |   +----------+   +-----+      |                   |
+ *           |  |                               |                   |
+ *           |  +-------------------------------+                   |
+ *           |                                                      |
+ *           +------------------------------------------------------+
+ *
+ * PLL output frequency calculation formula:
+ *
+ * pll_out = ((osc_in * (M + (frac / frac_max))) >> N) >> OD
+ *
+ * NOTE: Some PLLs support fractional multiplication. 'frac_max' is the counter
+ * used for fractional multiplication. Currently, there are two design values of
+ * 'frac_max' in Amlogic PLLs:
+ *   - frac_max = 2^17: Mainly used for Amlogic general-purpose PLLs, such as
+ *     gp_pll.
+ *   - frac_max = 100000: The PLL step is of integer type (which helps eliminate
+ *     accumulated errors in the driver), such as hifi_pll.
+ *
+ * Configuring 'N' for pre-division may affect the PLL bandwidth, phase margin,
+ * etc., resulting in increased PLL output jitter. Therefore, it is not
+ * recommended to arbitrarily configure 'N' for pre-division, and by default our
+ * driver does not enable 'N' pre-division.
+ *
+ * If a special PLL output frequency is required and 'N' pre-division must be
+ * used, and the resulting PLL output jitter is within an acceptable range, the
+ * PLL configuration parameters can be specified via 'pll_table' (refer to the
+ * definition of 'struct aml_pll_parms_table').
+ */
+
+#define AML_PLL_REG0_OFFSET			(0)
+#define AML_PLL_REG1_OFFSET			(4)
+
+struct aml_pll_reg_parms {
+	union {
+		struct {
+			u32 m		:9;  /* bit0 - bit8 */
+			u32 reserved	:3;  /* bit9 - bit11 */
+			u32 n		:3;  /* bit12 - bit14 */
+			u32 reserved1	:5;  /* bit15 - bit19 */
+			u32 od		:3;  /* bit20 - bit22 */
+			u32 reserved2	:3;  /* bit23 - bit25 */
+			u32 force_lock	:1;  /* bit26 */
+			u32 div0p5	:1;  /* bit27 */
+			u32 en		:1;  /* bit28 */
+			u32 rstn	:1;  /* bit29 */
+			u32 l_detect_en	:1;  /* bit30 */
+			u32 lock	:1;  /* bit31 */
+		} bits;
+		u32 val;
+	} reg0;
+	union {
+		struct {
+			u32 frac	:17;  /* bit0 - bit16 */
+			u32 reserved	:15;  /* bit17 - bit31 */
+		} bits;
+		u32 val;
+	} reg1;
+};
+
+static unsigned long __aml_pll_params_to_rate(unsigned long parent_rate,
+					      unsigned int m, unsigned int n,
+					      unsigned int frac,
+					      unsigned int od,
+					      struct aml_pll_data *pll)
+{
+	u64 rate = (u64)parent_rate * m;
+
+	if (pll->flags & AML_PLL_M_EN0P5)
+		rate = rate >> 1;
+
+	if (frac && pll->frac_max) {
+		u64 frac_rate = DIV_ROUND_UP_ULL((u64)parent_rate * frac,
+						 pll->frac_max);
+		if (pll->flags & AML_PLL_M_EN0P5)
+			frac_rate = frac_rate >> 1;
+
+		rate += frac_rate;
+	}
+
+	/* The 'n' divider has fixed power-of-two property */
+	rate = rate >> n;
+
+	rate = rate >> od;
+
+	/*
+	 * FIXME: CCF uses 'unsigned long' for rate values, which may overflow
+	 * on 32-bit systems.
+	 */
+	return (unsigned long)rate;
+}
+
+static unsigned long aml_pll_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	struct aml_pll_reg_parms regs;
+
+	regmap_read(clk->map, AML_PLL_REG0_OFFSET, &regs.reg0.val);
+	regmap_read(clk->map, AML_PLL_REG1_OFFSET, &regs.reg1.val);
+
+	return __aml_pll_params_to_rate(parent_rate, regs.reg0.bits.m,
+					regs.reg0.bits.n, regs.reg1.bits.frac,
+					regs.reg0.bits.od, pll);
+}
+
+static bool aml_pll_is_better(unsigned long rate, unsigned long best,
+			      unsigned long now, struct aml_pll_data *pll)
+{
+	if (pll->flags & AML_PLL_ROUND_CLOSEST) {
+		if (abs(now - rate) < abs(best - rate))
+			return true;
+	} else {
+		/* Round down */
+		if (now <= rate && best < now)
+			return true;
+	}
+
+	return false;
+}
+
+static int aml_pll_get_table(unsigned long rate, unsigned long parent_rate,
+			     struct aml_pll_parms_table *parm,
+			     struct aml_pll_data *pll, unsigned long *out_rate)
+{
+	unsigned int idx, best_idx;
+	unsigned long now, best = 0;
+
+	for (idx = 0; idx < pll->table_count; idx++) {
+		now = __aml_pll_params_to_rate(parent_rate, pll->table[idx].m,
+					       pll->table[idx].n,
+					       pll->table[idx].frac,
+					       pll->table[idx].od, pll);
+		if (aml_pll_is_better(rate, best, now, pll)) {
+			best = now;
+			best_idx = idx;
+
+			if (now == rate)
+				break;
+		}
+	}
+
+	if (idx >= pll->table_count)
+		return -EINVAL;
+
+	parm->m = pll->table[best_idx].m;
+	parm->n = pll->table[best_idx].n;
+	parm->frac = pll->table[best_idx].frac;
+	parm->od = pll->table[best_idx].od;
+
+	*out_rate = best;
+
+	return 0;
+}
+
+static int aml_pll_get_range(unsigned long rate, unsigned long parent_rate,
+			     struct aml_pll_parms_table *parm,
+			     struct aml_pll_data *pll, unsigned long *out_rate)
+{
+	unsigned int idx, t_m;
+	u64 vco_rate, req_vco_rate;
+	u64 val;
+	unsigned int frac = 0;
+	unsigned long frac_step;
+	unsigned long now_rate, best_rate = 0;
+	unsigned int best_m, best_frac, best_od;
+
+	if (pll->flags & AML_PLL_M_EN0P5)
+		parent_rate = parent_rate >> 1;
+
+	/*
+	 * NOTE: Configuring the 'n' divider may increase the PLL output
+	 * jitter. Here fix 'n = 0' to disable pre-division.
+	 *
+	 * If absolutely required (The resulting PLL output jitter is within an
+	 * acceptable range), ONLY implement via 'pll->table' configuration.
+	 */
+	parm->n = 0;
+
+	for (idx = 0; idx <= pll->od_max; idx++) {
+		req_vco_rate = (u64)rate << idx;
+		if (req_vco_rate < pll->range.min)
+			continue;
+
+		if (req_vco_rate > pll->range.max)
+			goto out;
+
+		/*
+		 * Ensure that the calculated vco_rate does not exceed
+		 * pll->range.max.
+		 */
+		if ((pll->flags & AML_PLL_ROUND_CLOSEST) &&
+		    !(pll->frac_max) &&
+		    (req_vco_rate + (parent_rate >> 1)) <= pll->range.max)
+			t_m = DIV_ROUND_CLOSEST_ULL(req_vco_rate, parent_rate);
+		else
+			t_m = div_u64(req_vco_rate,  parent_rate);
+
+		vco_rate = (u64)parent_rate * t_m;
+		if (pll->frac_max) {
+			val = div_u64(req_vco_rate * pll->frac_max,
+				      parent_rate);
+			val -= t_m * pll->frac_max;
+			frac = min((unsigned int)val, (pll->frac_max - 1));
+
+			frac_step = parent_rate / pll->frac_max;
+			vco_rate += frac_step * frac;
+
+			/*
+			 * With AML_PLL_ROUND_CLOSEST configured, the condition
+			 * req_vco_rate >= vco_rate is guaranteed to be true.
+			 */
+			val = req_vco_rate - vco_rate;
+			if (pll->flags & AML_PLL_ROUND_CLOSEST &&
+			    (abs(val - frac_step) < val) &&
+			    (vco_rate + frac_step <= pll->range.max)) {
+				frac += 1;
+				vco_rate += frac_step;
+			}
+		}
+
+		if (vco_rate < pll->range.min)
+			continue;
+
+		now_rate = vco_rate >> idx;
+		if (aml_pll_is_better(rate, best_rate, now_rate, pll)) {
+			best_rate = now_rate;
+
+			best_m = t_m;
+			best_frac = frac;
+			best_od = idx;
+
+			if (now_rate == rate)
+				break;
+		}
+	}
+
+out:
+	if (!best_rate)
+		return -EINVAL;
+
+	parm->m = best_m;
+	parm->frac = best_frac;
+	parm->od = best_od;
+
+	*out_rate = best_rate;
+
+	return 0;
+}
+
+static int aml_pll_get_best_parms(unsigned long rate, unsigned long parent_rate,
+				  struct aml_pll_parms_table *parm,
+				  struct aml_pll_data *pll,
+				  unsigned long *out_rate)
+{
+	unsigned long range_rate = 0, table_rate = 0;
+	struct aml_pll_parms_table range_parm, table_parm;
+
+	aml_pll_get_range(rate, parent_rate, &range_parm, pll, &range_rate);
+	aml_pll_get_table(rate, parent_rate, &table_parm, pll, &table_rate);
+	if (!range_rate && !table_rate)
+		return -EINVAL;
+
+	if (aml_pll_is_better(rate, range_rate, table_rate, pll)) {
+		if (parm) {
+			parm->m = table_parm.m;
+			parm->n = table_parm.n;
+			parm->frac = table_parm.frac;
+			parm->od = table_parm.od;
+		}
+
+		if (out_rate)
+			*out_rate = table_rate;
+	} else {
+		if (parm) {
+			parm->m = range_parm.m;
+			parm->n = range_parm.n;
+			parm->frac = range_parm.frac;
+			parm->od = range_parm.od;
+		}
+
+		if (out_rate)
+			*out_rate = range_rate;
+	}
+
+	return 0;
+}
+
+static int aml_pll_determine_rate(struct clk_hw *hw,
+				  struct clk_rate_request *req)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	int ret;
+
+	if (pll->flags & AML_PLL_READ_ONLY) {
+		req->rate = clk_hw_get_rate(hw);
+		return 0;
+	}
+
+	ret = aml_pll_get_best_parms(req->rate, req->best_parent_rate, NULL,
+				     pll, &req->rate);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int aml_pll_wait_lock(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	int delay = 1000;
+	struct aml_pll_reg_parms regs;
+
+	do {
+		regmap_read(clk->map, AML_PLL_REG0_OFFSET, &regs.reg0.val);
+		/* Wait for the PLL to lock */
+		if (regs.reg0.bits.lock)
+			return 0;
+
+		udelay(1);
+	} while (delay--);
+
+	return -ETIMEDOUT;
+}
+
+static int aml_pll_is_enabled(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_reg_parms regs;
+
+	regmap_read(clk->map, AML_PLL_REG0_OFFSET, &regs.reg0.val);
+	/* Enable and lock bit equal 1, it locks */
+	if (regs.reg0.bits.en && regs.reg0.bits.lock)
+		return 1;
+
+	return 0;
+}
+
+static void aml_pll_disable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_reg_parms regs;
+
+	regmap_read(clk->map, AML_PLL_REG0_OFFSET, &regs.reg0.val);
+
+	/* Put the pll is in reset */
+	regs.reg0.bits.rstn = 0;
+	regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+
+	/* Disable lock detect module */
+	regs.reg0.bits.l_detect_en = 0;
+	regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+
+	/* Disable the pll */
+	regs.reg0.bits.en = 0;
+	regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+}
+
+/*
+ * NOTE: Under extreme conditions (such as low temperatures), PLL lock may fail.
+ *
+ * Although we proactively address this by optimizing the PLL enable timing, a
+ * retry mechanism is added here to minimize the probability of PLL lock
+ * failure.
+ */
+#define PLL_LOCK_RETRY_MAX		10
+
+static int aml_pll_enable(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_reg_parms regs;
+	int retry = 0;
+
+	/* Do nothing if the PLL is already enabled */
+	if (clk_hw_is_enabled(hw))
+		return 0;
+
+	do {
+		/* Make sure the pll is disabled */
+		aml_pll_disable(hw);
+
+		regmap_read(clk->map, AML_PLL_REG0_OFFSET, &regs.reg0.val);
+
+		/* Powers up PLL supply */
+		regs.reg0.bits.en = 1;
+		regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+
+		/*
+		 * Wait for Bandgap and LDO to power up and stabilize.
+		 *
+		 * The spinlock is held during the execution of clk_enable(),
+		 * so usleep() cannot be used here.
+		 */
+		udelay(20);
+
+		/* Take the pll out reset */
+		regs.reg0.bits.rstn = 1;
+		regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+
+		/* Wait for PLL loop stabilization */
+		udelay(20);
+
+		/* Take the pll out lock reset */
+		regs.reg0.bits.l_detect_en = 1;
+		regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+
+		if (!aml_pll_wait_lock(hw))
+			return 0;
+	} while (retry > PLL_LOCK_RETRY_MAX);
+
+	/* disable PLL when PLL lock failed. */
+	aml_pll_disable(hw);
+	pr_warn("%s: PLL lock failed\n", clk_hw_get_name(hw));
+
+	return -EIO;
+}
+
+static int aml_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	struct aml_pll_reg_parms regs;
+	struct aml_pll_parms_table parm;
+	int enabled, ret;
+
+	if (parent_rate == 0 || rate == 0)
+		return -EINVAL;
+
+	ret = aml_pll_get_best_parms(rate, parent_rate, &parm, pll, NULL);
+	if (ret)
+		return ret;
+
+	enabled = aml_pll_is_enabled(hw);
+
+	regmap_read(clk->map, AML_PLL_REG0_OFFSET, &regs.reg0.val);
+	/* If neither m nor n is changed, there is no need to disable the PLL */
+	if ((regs.reg0.bits.m != parm.m || regs.reg0.bits.n != parm.n) &&
+	    enabled)
+		aml_pll_disable(hw);
+
+	regs.reg0.bits.m = parm.m;
+	regs.reg0.bits.n = parm.n;
+	regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+
+	if (pll->frac_max) {
+		regmap_read(clk->map, AML_PLL_REG1_OFFSET, &regs.reg1.val);
+		regs.reg1.bits.frac = parm.frac;
+		regmap_write(clk->map, AML_PLL_REG1_OFFSET, regs.reg1.val);
+	}
+
+	if (pll->od_max) {
+		regs.reg0.bits.od = parm.od;
+		regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val);
+	}
+
+	if (!enabled)
+		return 0;
+
+	return aml_pll_enable(hw);
+}
+
+static int aml_pll_save_context(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	unsigned long p_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
+
+	pll->context_is_enabled = aml_pll_is_enabled(hw);
+	pll->context_rate = aml_pll_recalc_rate(hw, p_rate);
+
+	return 0;
+}
+
+static void aml_pll_restore_context(struct clk_hw *hw)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	unsigned long p_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
+
+	aml_pll_set_rate(hw, pll->context_rate, p_rate);
+	if (pll->context_is_enabled)
+		aml_pll_enable(hw);
+	else
+		aml_pll_disable(hw);
+}
+
+/*
+ * If debugfs is enabled, two nodes "clk_available_rates" and "clk_type" will be
+ * created under the corresponding debugfs directory to assist with debugging or
+ * testing.
+ */
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+static unsigned long aml_pll_get_rate_step(struct aml_pll_data *pll,
+					   unsigned long parent_rate)
+{
+	if (pll->flags & AML_PLL_M_EN0P5)
+		parent_rate = parent_rate >> 1;
+
+	if (pll->frac_max)
+		return parent_rate / pll->frac_max;
+	else
+		return parent_rate;
+}
+
+enum round_type {
+	ROUND_DOWN	= 0,
+	ROUND_UP
+};
+
+static int aml_pll_get_best_rate(unsigned long rate, unsigned long step_rate,
+				 u64 min_vco_rate, u64 max_vco_rate,
+				 u8 od_max, enum round_type round,
+				 unsigned long *out_rate)
+{
+	int i;
+	u64 vco_rate;
+	unsigned long now_rate, best_rate = 0;
+
+	for (i = 0; i <= od_max; i++) {
+		vco_rate = rate << i;
+		if (vco_rate < min_vco_rate)
+			continue;
+
+		if (vco_rate > max_vco_rate)
+			break;
+
+		if (vco_rate % step_rate == 0) {
+			best_rate = rate;
+
+			break;
+		}
+
+		if (round == ROUND_DOWN) {
+			vco_rate = vco_rate - (vco_rate % step_rate);
+			now_rate = vco_rate >> i;
+			if ((rate - now_rate) < (rate - best_rate))
+				best_rate = now_rate;
+		} else {
+			vco_rate = vco_rate + step_rate;
+			vco_rate = vco_rate - (vco_rate % step_rate);
+			now_rate = vco_rate >> i;
+			if ((now_rate - rate) < (best_rate - rate))
+				best_rate = now_rate;
+		}
+	}
+
+	if (!best_rate)
+		return -EINVAL;
+
+	*out_rate = best_rate;
+
+	return 0;
+}
+
+static int aml_pll_get_rate_range(struct clk_hw *hw, unsigned long parent_rate,
+				  unsigned long *min, unsigned long *max)
+{
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	unsigned long step = aml_pll_get_rate_step(pll, parent_rate);
+	unsigned long min_rate, max_rate;
+	unsigned long core_min_rate, core_max_rate;
+	int ret;
+
+	min_rate = pll->range.min >> pll->od_max;
+	max_rate = pll->range.max;
+
+	clk_hw_get_rate_range(hw, &core_min_rate, &core_max_rate);
+	if (min_rate < core_min_rate)
+		min_rate = core_min_rate;
+
+	ret = aml_pll_get_best_rate(min_rate, step, pll->range.min,
+				    pll->range.max, pll->od_max, ROUND_UP, min);
+	if (ret)
+		return ret;
+
+	if (max_rate > core_max_rate)
+		max_rate = core_max_rate;
+
+	ret = aml_pll_get_best_rate(max_rate, step, pll->range.min,
+				    pll->range.max, pll->od_max,
+				    ROUND_DOWN, max);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int aml_pll_available_rates_show(struct seq_file *s, void *data)
+{
+	struct clk_hw *hw = s->private;
+	struct clk_hw *phw = clk_hw_get_parent(hw);
+	struct aml_clk *clk = to_aml_clk(hw);
+	struct aml_pll_data *pll = clk->data;
+	u64 rate, prate = 0;
+	unsigned long min, max;
+	int i, ret;
+
+	if (!phw) {
+		pr_err("%s: can't get parent\n", clk_hw_get_name(hw));
+
+		return -ENOENT;
+	}
+
+	prate = clk_hw_get_rate(phw);
+	if (pll->flags & AML_PLL_READ_ONLY) {
+		seq_printf(s, "%ld\n", clk_hw_get_rate(hw));
+
+		return 0;
+	}
+
+	if (pll->range.min || pll->range.max) {
+		ret = aml_pll_get_rate_range(hw, prate, &min, &max);
+		if (ret)
+			return ret;
+
+		seq_printf(s, "min_rate:%ld\n", min);
+		seq_printf(s, "max_rate:%ld\n", max);
+	} else if (pll->table) {
+		if (pll->flags & AML_PLL_M_EN0P5)
+			prate >>= 1;
+
+		clk_hw_get_rate_range(hw, &min, &max);
+
+		for (i = 0; pll->table[i].m != 0; i++) {
+			rate = (prate * pll->table[i].m) >> pll->table[i].n;
+
+			rate = rate >> pll->table[i].od;
+			if (rate < min || rate > max)
+				continue;
+
+			seq_printf(s, "%ld\n", (unsigned long)rate);
+		}
+	} else {
+		seq_printf(s, "%ld\n", clk_hw_get_rate(hw));
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(aml_pll_available_rates);
+
+static void aml_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops);
+	debugfs_create_file("clk_available_rates", 0444, dentry, hw,
+			    &aml_pll_available_rates_fops);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+const struct clk_ops aml_pll_ops = {
+	.recalc_rate	= aml_pll_recalc_rate,
+	.determine_rate	= aml_pll_determine_rate,
+	.set_rate	= aml_pll_set_rate,
+	.is_enabled	= aml_pll_is_enabled,
+	.save_context	= aml_pll_save_context,
+	.restore_context = aml_pll_restore_context,
+	.enable		= aml_pll_enable,
+	.disable	= aml_pll_disable,
+#ifdef CONFIG_DEBUG_FS
+	.debug_init	= aml_pll_debug_init,
+#endif /* CONFIG_DEBUG_FS */
+};
+EXPORT_SYMBOL_NS_GPL(aml_pll_ops, "CLK_AMLOGIC");
+
+const struct clk_ops aml_pll_ro_ops = {
+	.recalc_rate	= aml_pll_recalc_rate,
+	.is_enabled	= aml_pll_is_enabled,
+};
+EXPORT_SYMBOL_NS_GPL(aml_pll_ro_ops, "CLK_AMLOGIC");
+
+MODULE_DESCRIPTION("Amlogic PLL Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk-pll.h b/drivers/clk/amlogic/clk-pll.h
new file mode 100644
index 000000000000..99c2007d25d2
--- /dev/null
+++ b/drivers/clk/amlogic/clk-pll.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_PLL_H
+#define __AML_CLK_PLL_H
+
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+struct aml_pll_parms_table {
+	unsigned int	m;
+	unsigned int	n;
+	unsigned int	frac;
+	unsigned int	od;
+};
+
+struct aml_pll_dco_range {
+	unsigned long long	min;
+	unsigned long long	max;
+};
+
+#define AML_PLL_ROUND_CLOSEST	BIT(0)/* Supports fractional multiplication */
+#define AML_PLL_READ_ONLY	BIT(1)
+#define AML_PLL_M_EN0P5		BIT(2)/* Multiplication factor is m = m / 2 */
+
+struct aml_pll_data {
+	struct aml_pll_parms_table	*table;
+	unsigned int			table_count;
+	struct aml_pll_dco_range	range;
+	unsigned int			frac_max;
+	u8				od_max;
+	u16				flags;
+	/* Save the context information of the PLL */
+	int				context_is_enabled;
+	unsigned long			context_rate;
+};
+
+extern const struct clk_ops aml_pll_ops;
+extern const struct clk_ops aml_pll_ro_ops;
+
+#endif /* __AML_CLK_PLL_H */
diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c
index 5431aa320dfa..2558c3f48242 100644
--- a/drivers/clk/amlogic/clk.c
+++ b/drivers/clk/amlogic/clk.c
@@ -24,6 +24,7 @@ static const struct {
 	ENTRY(AML_CLKTYPE_COMPOSITE),
 	ENTRY(AML_CLKTYPE_NOGLITCH),
 	ENTRY(AML_CLKTYPE_DUALDIV),
+	ENTRY(AML_CLKTYPE_PLL),
 #undef ENTRY
 };
 
diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h
index c1d58a08e407..b62045aedfbf 100644
--- a/drivers/clk/amlogic/clk.h
+++ b/drivers/clk/amlogic/clk.h
@@ -17,6 +17,7 @@ enum aml_clk_type {
 	AML_CLKTYPE_COMPOSITE	= 4,
 	AML_CLKTYPE_NOGLITCH	= 5,
 	AML_CLKTYPE_DUALDIV	= 6,
+	AML_CLKTYPE_PLL		= 7,
 };
 
 struct aml_clk {

-- 
2.42.0



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

* [PATCH 09/13] clk: amlogic: Add DT-based clock registration functions
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (7 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 08/13] clk: amlogic: Add PLL driver Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 10/13] clk: amlogic: Add A9 standardized model clock control units driver Chuan Liu via B4 Relay
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Amlogic clock controllers require hardware information description in
device tree. This patch provides functions for parsing clock configuration
from DT and performing clock registration after obtaining clock details.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/clk.c | 310 +++++++++++++++++++++++++++++++++++++++++++++-
 drivers/clk/amlogic/clk.h |  14 +++
 2 files changed, 323 insertions(+), 1 deletion(-)

diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c
index 2558c3f48242..f3327c8414be 100644
--- a/drivers/clk/amlogic/clk.c
+++ b/drivers/clk/amlogic/clk.c
@@ -3,12 +3,15 @@
  * Copyright (c) 2026 Amlogic, Inc. All rights reserved
  */
 
+#include <linux/clk.h>
 #include <linux/module.h>
+#include <linux/of_clk.h>
+
+#include "clk.h"
 
 #ifdef CONFIG_DEBUG_FS
 #include <linux/err.h>
 
-#include "clk.h"
 #include "clk-basic.h"
 #include "clk-composite.h"
 #include "clk-noglitch.h"
@@ -150,6 +153,311 @@ const struct file_operations aml_clk_div_available_rates_fops = {
 EXPORT_SYMBOL_NS_GPL(aml_clk_div_available_rates_fops, "CLK_AMLOGIC");
 #endif /* CONFIG_DEBUG_FS */
 
+struct regmap *aml_clk_regmap_init(struct platform_device *pdev)
+{
+	void __iomem *base;
+	struct resource *res;
+	struct regmap_config clkc_regmap_config = {
+		.reg_bits	= 32,
+		.val_bits	= 32,
+		.reg_stride	= 4,
+	};
+
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(base))
+		return NULL;
+
+	clkc_regmap_config.max_register = resource_size(res) - 4;
+	if (!clkc_regmap_config.max_register)
+		clkc_regmap_config.max_register_is_0 = true;
+
+	return devm_regmap_init_mmio(&pdev->dev, base, &clkc_regmap_config);
+}
+EXPORT_SYMBOL_NS_GPL(aml_clk_regmap_init, "CLK_AMLOGIC");
+
+static inline int of_aml_clk_get_init_reg_count(struct device_node *np)
+{
+	return of_property_count_elems_of_size(np, "amlogic,clock-init-regs",
+					       sizeof(struct reg_sequence));
+}
+
+static inline int of_aml_clk_get_init_reg(struct device_node *np, int reg_count,
+					  struct reg_sequence *init_regs)
+{
+	return of_property_read_u32_array(np, "amlogic,clock-init-regs",
+					  (u32 *)init_regs,
+					  3 * reg_count);
+}
+
+int of_aml_clk_regs_init(struct device *dev)
+{
+	struct device_node *dev_np = dev_of_node(dev);
+	struct regmap *regmap = dev_get_regmap(dev, NULL);
+	int ret, reg_count;
+	struct reg_sequence *init_regs;
+
+	ret = of_aml_clk_get_init_reg_count(dev_np);
+	if (ret < 0)
+		return 0;
+
+	reg_count = ret;
+	init_regs = devm_kcalloc(dev, reg_count, sizeof(*init_regs),
+				 GFP_KERNEL);
+	if (!init_regs)
+		return -ENOMEM;
+
+	ret = of_aml_clk_get_init_reg(dev_np, reg_count, init_regs);
+	if (ret)
+		goto fail;
+
+	ret = regmap_multi_reg_write(regmap, init_regs, reg_count);
+
+fail:
+	devm_kfree(dev, init_regs);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_regs_init, "CLK_AMLOGIC");
+
+u32 of_aml_clk_get_count(struct device_node *np)
+{
+	/*
+	 * NOTE: Each clock under a clock device node must define the
+	 * "clock-output-names" property, so this property is used here to
+	 * determine how many clocks are contained in the current clock device
+	 * node.
+	 */
+	int ret = of_property_count_strings(np, "clock-output-names");
+
+	if (ret < 0)
+		return 0;
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_get_count, "CLK_AMLOGIC");
+
+const char *of_aml_clk_get_name_index(struct device_node *np, u32 index)
+{
+	const char *name;
+
+	if (of_property_read_string_index(np, "clock-output-names", index,
+					  &name)) {
+		pr_err("<%pOFn>: Invalid clock-output-names, index = %d\n",
+		       np, index);
+		return NULL;
+	}
+
+	return name;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_get_name_index, "CLK_AMLOGIC");
+
+static bool of_aml_clk_is_dummy_index(struct device_node *np, int index)
+{
+	struct of_phandle_args clk_args;
+	u32 rate;
+	int ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells",
+					     index, &clk_args);
+
+	if (ret < 0)
+		return true;
+
+	/*
+	 * If the device node description specified by clk_args indicates a
+	 * fixed clock with a frequency of 0, the device is considered a dummy
+	 * clock device.
+	 */
+	if (of_device_is_compatible(clk_args.np, "fixed-clock") &&
+	    !of_property_read_u32(clk_args.np, "clock-frequency", &rate) &&
+	    rate == 0)
+		return true;
+
+	return false;
+}
+
+int of_aml_clk_get_parent_num(struct device *dev, int start_index, int end_index)
+{
+	struct device_node *np = dev_of_node(dev);
+	unsigned int pcnt = of_clk_get_parent_count(np);
+	int i, real_pcnt = 0;
+
+	if (end_index < 0 || end_index >= pcnt)
+		/* Get the number of all "clocks" for the current device node */
+		end_index = pcnt - 1;
+
+	if (start_index > end_index ||
+	    start_index > pcnt)
+		return -EINVAL;
+
+	for (i = start_index; i <= end_index; i++) {
+		if (of_aml_clk_is_dummy_index(np, i))
+			continue;
+
+		real_pcnt++;
+	}
+
+	return real_pcnt;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_get_parent_num, "CLK_AMLOGIC");
+
+static struct clk_hw *of_aml_clk_get_hw(struct device_node *np,
+					struct clk_hw **dev_hws, int index)
+{
+	struct of_phandle_args out_args;
+	struct clk *clk;
+	struct clk_hw *clk_hw;
+	int ret;
+
+	ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
+					 &out_args);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (out_args.np == np) {
+		if (!dev_hws)
+			return ERR_PTR(-EFAULT);
+
+		/*
+		 * If a parent clock comes from the device node itself, the
+		 * corresponding clk_hw can be found using the
+		 * "out_args.args[0]" (clock index).
+		 */
+		clk_hw = dev_hws[out_args.args[0]];
+	} else {
+		clk = of_clk_get_from_provider(&out_args);
+		if (IS_ERR(clk)) {
+			if (PTR_ERR(clk) != -EPROBE_DEFER)
+				pr_warn("clk: couldn't get clock for %pOF\n",
+					out_args.np);
+
+			return ERR_CAST(clk);
+		}
+
+		clk_hw = __clk_get_hw(clk);
+		clk_put(clk);
+	}
+
+	return clk_hw;
+}
+
+int of_aml_clk_get_parent_data(struct device *dev, struct clk_hw **dev_hws,
+			       int start_index, int end_index,
+			       struct clk_parent_data *out_pdatas,
+			       u8 *out_num_parents)
+{
+	struct device_node *np = dev_of_node(dev);
+	unsigned int pcnt = of_clk_get_parent_count(np);
+	int i, real_pcnt;
+
+	if (end_index < 0 || end_index >= pcnt)
+		/* Get the number of all "clocks" for the current device node */
+		end_index = pcnt - 1;
+
+	if (start_index > end_index || start_index > pcnt)
+		return -EINVAL;
+
+	for (i = start_index, real_pcnt = 0; i <= end_index; i++) {
+		if (of_aml_clk_is_dummy_index(np, i))
+			continue;
+
+		out_pdatas[real_pcnt].hw = of_aml_clk_get_hw(np, dev_hws, i);
+		if (IS_ERR(out_pdatas[real_pcnt].hw))
+			return PTR_ERR(out_pdatas[real_pcnt].hw);
+
+		real_pcnt++;
+	}
+
+	if (out_num_parents)
+		*out_num_parents = real_pcnt;
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_get_parent_data, "CLK_AMLOGIC");
+
+u32 *of_aml_clk_get_parent_table(struct device *dev, int start_index,
+				 int end_index)
+{
+	struct device_node *np = dev_of_node(dev);
+	bool has_ptab = false;
+	u32 *ptab;
+	unsigned int pcnt = of_clk_get_parent_count(np);
+	int i, real_pcnt, ptab_i;
+
+	real_pcnt = of_aml_clk_get_parent_num(dev, start_index, end_index);
+	if (real_pcnt < 0)
+		return ERR_PTR(-EINVAL);
+	else if (!real_pcnt) /* no parent clock */
+		return NULL;
+
+	if (end_index < 0 || end_index >= pcnt)
+		end_index = pcnt - 1;
+
+	for (i = start_index, ptab_i = 0; i <= end_index; i++) {
+		/* dummy clock exist and ptab needs to be defined */
+		if (of_aml_clk_is_dummy_index(np, i)) {
+			has_ptab = true;
+			break;
+		}
+	}
+	if (!has_ptab)
+		return NULL;
+
+	ptab = devm_kcalloc(dev, real_pcnt, sizeof(*ptab), GFP_KERNEL);
+	if (!ptab)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = start_index, ptab_i = 0; i <= end_index; i++) {
+		if (!of_aml_clk_is_dummy_index(np, i))
+			ptab[ptab_i++] = i - start_index;
+	}
+
+	return ptab;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_get_parent_table, "CLK_AMLOGIC");
+
+static int of_aml_clk_get_max_rate(struct device_node *np, u32 index,
+			    unsigned long *out_max_rate)
+{
+	int count = of_property_count_u32_elems(np,
+						"amlogic,clock-max-frequency");
+
+	if (count < 0)
+		return count;
+	else if (count == 1)
+		/*
+		 * If the property "amlogic,clock-max-frequency" under the
+		 * current device node defines only a single value, that value
+		 * specifies the maximum frequency limit for all clocks under
+		 * this device node.
+		 */
+		index = 0;
+
+	return of_property_read_u32_index(np, "amlogic,clock-max-frequency",
+					  index, (u32 *)out_max_rate);
+}
+
+int of_aml_clk_register(struct device *dev, struct clk_hw *hw, int clkid)
+{
+	struct device_node *np = dev_of_node(dev);
+	unsigned long max_rate;
+	int ret;
+
+	ret = devm_clk_hw_register(dev, hw);
+	if (ret)
+		return ret;
+
+	ret = of_aml_clk_get_max_rate(np, clkid, &max_rate);
+	if (ret) {
+		if (ret != -EINVAL)
+			return ret;
+	} else {
+		if (max_rate)
+			clk_hw_set_rate_range(hw, 0, max_rate);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(of_aml_clk_register, "CLK_AMLOGIC");
+
 MODULE_DESCRIPTION("Amlogic Common Clock Driver");
 MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
 MODULE_LICENSE("GPL");
diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h
index b62045aedfbf..3cfe2e650ed4 100644
--- a/drivers/clk/amlogic/clk.h
+++ b/drivers/clk/amlogic/clk.h
@@ -39,4 +39,18 @@ static inline struct aml_clk *to_aml_clk(struct clk_hw *hw)
 	return container_of(hw, struct aml_clk, hw);
 }
 
+struct regmap *aml_clk_regmap_init(struct platform_device *pdev);
+int of_aml_clk_regs_init(struct device *dev);
+u32 of_aml_clk_get_count(struct device_node *np);
+const char *of_aml_clk_get_name_index(struct device_node *np, u32 index);
+int of_aml_clk_get_parent_num(struct device *dev, int start_index,
+			      int end_index);
+int of_aml_clk_get_parent_data(struct device *dev, struct clk_hw **dev_hws,
+			       int start_index, int end_index,
+			       struct clk_parent_data *out_pdatas,
+			       u8 *out_num_parents);
+u32 *of_aml_clk_get_parent_table(struct device *dev, int start_index,
+				 int end_index);
+int of_aml_clk_register(struct device *dev, struct clk_hw *hw, int clkid);
+
 #endif /* __AML_CLK_H */

-- 
2.42.0



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

* [PATCH 10/13] clk: amlogic: Add A9 standardized model clock control units driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (8 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 09/13] clk: amlogic: Add DT-based clock registration functions Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 11/13] clk: amlogic: Add A9 PLL controllers driver Chuan Liu via B4 Relay
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Add support for Amlogic standardized model clock control units on A9
SoC family. The standardized models include:
  - composite-ccu
  - noglitch-ccu
  - sysbus-ccu

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Kconfig        |  19 ++
 drivers/clk/amlogic/Makefile       |   3 +
 drivers/clk/amlogic/a9-model-ccu.c | 465 +++++++++++++++++++++++++++++++++++++
 3 files changed, 487 insertions(+)

diff --git a/drivers/clk/amlogic/Kconfig b/drivers/clk/amlogic/Kconfig
index 216fe98a413b..6e954c9388dc 100644
--- a/drivers/clk/amlogic/Kconfig
+++ b/drivers/clk/amlogic/Kconfig
@@ -10,3 +10,22 @@ config COMMON_CLK_AMLOGIC
 	  This driver provides the basic clock infrastructure for Amlogic SoCs,
 	  offering read and write interfaces for various clock control units.
 	  Select Y if your target SoC needs clock driver support.
+
+config COMMON_CLK_AMLOGIC_MODEL
+	tristate "Amlogic Standardized Model Clock Control Units"
+	depends on COMMON_CLK_AMLOGIC
+	help
+	  Supports standardized model clock control units commonly used in Amlogic
+	  SoC clock trees, such as composite-ccu, noglitch-ccu, and sysbus-ccu.
+	  Most peripheral clock controllers in Amlogic SoCs are composed of
+	  these models. Select Y if the current SoC contains these clock control
+	  unit models.
+
+config COMMON_CLK_AMLOGIC_A9
+	tristate "Amlogic A9 Family Clock Controller"
+	depends on COMMON_CLK_AMLOGIC
+	default COMMON_CLK_AMLOGIC
+	select COMMON_CLK_AMLOGIC_MODEL
+	help
+	  Support for the clock controller present on the Amlogic A9 family
+	  SoCs. Select Y if A9 family SoC needs to support clock controller.
diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index 6956592c41c8..ef3fb57cae9f 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -8,3 +8,6 @@ clk-amlogic-y += clk-composite.o
 clk-amlogic-y += clk-dualdiv.o
 clk-amlogic-y += clk-noglitch.o
 clk-amlogic-y += clk-pll.o
+ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MODEL),)
+clk-amlogic-y += a9-model-ccu.o
+endif
diff --git a/drivers/clk/amlogic/a9-model-ccu.c b/drivers/clk/amlogic/a9-model-ccu.c
new file mode 100644
index 000000000000..5d5bf1538f73
--- /dev/null
+++ b/drivers/clk/amlogic/a9-model-ccu.c
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-basic.h"
+#include "clk-composite.h"
+#include "clk-noglitch.h"
+
+/*
+ * The standardized model clock control units of Amlogic includes:
+ *   - composite-ccu
+ *   - noglitch-ccu
+ *   - sysbus-ccu
+ */
+#define MAX_AML_CLK_COMP_PARENTS		8
+
+enum aml_clk_model_type {
+	CLK_MODEL_COMPOSITE	= 1,
+	CLK_MODEL_NOGLITCH	= 2,
+	CLK_MODEL_SYSBUS	= 3,
+};
+
+struct aml_clk_model_data {
+	enum aml_clk_model_type	type;
+};
+
+static int of_aml_clk_model_get_type(struct device *dev,
+				     enum aml_clk_type *out_type)
+{
+	const struct aml_clk_model_data *data;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -EFAULT;
+
+	switch (data->type) {
+	case CLK_MODEL_COMPOSITE:
+		*out_type = AML_CLKTYPE_COMPOSITE;
+		return 0;
+
+	case CLK_MODEL_NOGLITCH:
+		*out_type = AML_CLKTYPE_NOGLITCH;
+		return 0;
+
+	case CLK_MODEL_SYSBUS:
+		*out_type = AML_CLKTYPE_GATE;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * A diagram of the A9 composite-ccu is as follows:
+ *         +----------------------------------+
+ *         |                                  |
+ *         |   |\                             |
+ * clk0 ------>| |                            |
+ * clk1 ------>| |                            |
+ * clk2 ------>| |                            |
+ * clk3 ------>| |     +-----+     +------+   |
+ *         |   | |---->| div |---->| gate |------> clk out
+ * clk4 ------>| |     +-----+     +------+   |
+ * clk5 ------>| |                            |
+ * clk6 ------>| |                            |
+ * clk7 ------>| |                            |
+ *         |   |/                             |
+ *         |                                  |
+ *         +----------------------------------+
+ */
+static int
+of_aml_clk_model_composite_init_register(struct device *dev,
+					 struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct aml_clk_composite_data *composite;
+	u32 reg_val[3];
+	unsigned int pcnt = of_clk_get_parent_count(np);
+	struct clk_parent_data pdata[MAX_AML_CLK_COMP_PARENTS];
+	struct clk_parent_data pdata_compb[MAX_AML_CLK_COMP_PARENTS];
+	u8 pnum, pnum_compb;
+	u32 *ptab, *ptab_compb;
+	struct clk_init_data init = { 0 };
+	int index;
+	int ret, clkid, i;
+
+	ret = of_aml_clk_get_parent_data(dev, hw_data->hws, 0, 7, pdata, &pnum);
+	if (ret)
+		return ret;
+
+	ptab = of_aml_clk_get_parent_table(dev, 0, 7);
+	if (IS_ERR(ptab))
+		return PTR_ERR(ptab);
+
+	/*
+	 * If the number of "clocks" defined in DT is less than or equal
+	 * to MAX_AML_CLK_COMP_PARENTS, composite-ccu_a and composite-ccu_b
+	 * share the same parent clocks.
+	 *
+	 * If the number of "clocks" defined in the DT is greater than
+	 * MAX_AML_CLK_COMP_PARENTS, composite-ccu_a and composite-ccu_b have
+	 * different parent clocks, the parent clocks specified by
+	 * "clocks" follow the rule below:
+	 *   - for composite-ccu_a: clocks indices 0-7 in "clocks"
+	 *   - for composite-ccu_b: clocks indices 8-15 in "clocks"
+	 */
+	if (pcnt > MAX_AML_CLK_COMP_PARENTS) { /* composite-ccu_b */
+		ret = of_aml_clk_get_parent_data(dev, hw_data->hws, 8, 15,
+						 pdata_compb, &pnum_compb);
+		if (ret)
+			return ret;
+
+		ptab_compb = of_aml_clk_get_parent_table(dev, 8, 15);
+		if (IS_ERR(ptab_compb))
+			return PTR_ERR(ptab_compb);
+	}
+
+	init.ops = &aml_clk_composite_ops;
+	for (clkid = 0; clkid < hw_data->num; clkid++) {
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name)
+			return -EINVAL;
+
+		/*
+		 * The set of "amlogic,reg-layout" attributes of composite_ccu
+		 * contains three u32 data:
+		 *   - reg_offset
+		 *   - bit_offset
+		 *   - div_width
+		 */
+		index = clkid * 3;
+		for (i = 0; i < 3; i++) {
+			ret = of_property_read_u32_index(np,
+							 "amlogic,reg-layout",
+							 index + i, &reg_val[i]);
+			if (ret)
+				return ret;
+		}
+
+		composite = devm_kzalloc(dev, sizeof(*composite), GFP_KERNEL);
+		if (!composite)
+			return -ENOMEM;
+
+		composite->reg_offset = reg_val[0];
+		composite->bit_offset = reg_val[1];
+		composite->div_width = reg_val[2];
+
+		/*
+		 * The register bit allocation for composite-ccu_a and
+		 * composite-ccu_b is as follows:
+		 *   - composite-ccu_a: bit[15: 0]
+		 *   - composite-ccu_b: bit[31: 16]
+		 * A value of "composite->bit_offset == 16" indicates that this
+		 * CCU corresponds to composite-ccu_b.
+		 */
+		if (pcnt > MAX_AML_CLK_COMP_PARENTS &&
+		    composite->bit_offset == 16) { /* composite-ccu_b */
+			init.num_parents = pnum_compb;
+			init.parent_data = pdata_compb;
+
+			if (ptab_compb)
+				composite->table = ptab_compb;
+		} else { /* composite-ccu_a */
+			init.num_parents = pnum;
+			init.parent_data = pdata;
+
+			if (ptab)
+				composite->table = ptab;
+		}
+
+		clk = to_aml_clk(hw_data->hws[clkid]);
+		clk->data = composite;
+
+		hw_data->hws[clkid]->init = &init;
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * A diagram of the A9 noglitch-ccu is as follows:
+ *         +---------------------------------------------+
+ *         |   |\                                        |
+ * clk0 ------>| |                                       |
+ * clk1 ------>| |                                       |
+ * clk2 ------>| |                                       |
+ * clk3 ------>| |      +-----+      +------+      |\    |
+ *         |   | |----->| div |----->| gate |----->| |   |
+ * clk4 ------>| |      +-----+      +------+      | |   |
+ * clk5 ------>| |                                 | |   |
+ * clk6 ------>| |                                 | |   |
+ * clk7 ------>| |                                 | |   |
+ *         |   |/                                  | |   |
+ *         |                                       | |-------> clk out
+ *         |   |\                                  | |   |
+ * clk0 ------>| |                                 | |   |
+ * clk1 ------>| |                                 | |   |
+ * clk2 ------>| |                                 | |   |
+ * clk3 ------>| |      +-----+     +------+       | |   |
+ *         |   | |----->| div |---->| gate |------>| |   |
+ * clk4 ------>| |      +-----+     +------+       |/    |
+ * clk5 ------>| |                                       |
+ * clk6 ------>| |                                       |
+ * clk7 ------>| |                                       |
+ *         |   |/                                        |
+ *         +---------------------------------------------+
+ */
+static int
+of_aml_clk_model_noglitch_init_register(struct device *dev,
+					struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct aml_clk_noglitch_data *noglitch;
+	struct clk_parent_data pdata[MAX_AML_CLK_COMP_PARENTS];
+	u8 pnum;
+	u32 *ptab;
+	struct clk_init_data init = { 0 };
+	int ret, clkid;
+
+	/* noglitch-ccu supports up to eight parent clocks. */
+	ret = of_aml_clk_get_parent_data(dev, hw_data->hws, 0, 7, pdata, &pnum);
+	if (ret)
+		return ret;
+
+	ptab = of_aml_clk_get_parent_table(dev, 0, 7);
+	if (IS_ERR(ptab))
+		return PTR_ERR(ptab);
+
+	init.ops = &aml_clk_noglitch_ops;
+	init.num_parents = pnum;
+	init.parent_data = pdata;
+	for (clkid = 0; clkid < hw_data->num; clkid++) {
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name)
+			return -EINVAL;
+
+		hw_data->hws[clkid]->init = &init;
+
+		noglitch = devm_kzalloc(dev, sizeof(*noglitch), GFP_KERNEL);
+		if (!noglitch)
+			return -ENOMEM;
+
+		ret = of_property_read_u32_index(np, "amlogic,reg-layout",
+						 clkid, &noglitch->reg_offset);
+		if (ret)
+			return ret;
+
+		if (ptab)
+			noglitch->table = ptab;
+
+		clk = to_aml_clk(hw_data->hws[clkid]);
+		clk->data = noglitch;
+
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * A diagram of the A9 sysbus-ccu is as follows:
+ *         +-------------------+
+ *         |                   |
+ *         |        +------+   |
+ *         |  +---->| gate |----->clkout0
+ *         |  |     +------+   |
+ *         |  |                |
+ *         |  |                |
+ *         |  |                |
+ *         |  |     +------+   |
+ * clk in-----+---->| gate |----->clkout1
+ *         |  |     +------+   |
+ *         |  |        ...     |
+ *         |  |                |
+ *         |  |                |
+ *         |  |     +------+   |
+ *         |  +---->| gate |----->clkoutn
+ *         |        +------+   |
+ *         |                   |
+ *         +-------------------+
+ */
+static int
+of_aml_clk_model_sysbus_init_register(struct device *dev,
+				      struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct aml_clk_gate_data *gate;
+	u32 reg_val[2];
+	struct clk_parent_data pdata;
+	u8 pnum;
+	struct clk_init_data init = { 0 };
+	int index;
+	int ret, clkid, i;
+
+	ret = of_aml_clk_get_parent_data(dev, hw_data->hws, 0, 0, &pdata, &pnum);
+	if (ret)
+		return ret;
+
+	init.ops = &aml_clk_gate_ops;
+	init.num_parents = pnum;
+	init.parent_data = &pdata;
+	for (clkid = 0; clkid < hw_data->num; clkid++) {
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name)
+			return -EINVAL;
+
+		/*
+		 * The set of "amlogic,reg-layout" attributes of sysbus_ccu
+		 * contains three u32 data:
+		 *   - reg_offset
+		 *   - bit_idx
+		 */
+		index = clkid * 2;
+		for (i = 0; i < 2; i++) {
+			ret = of_property_read_u32_index(np,
+							 "amlogic,reg-layout",
+							 index + i, &reg_val[i]);
+			if (ret)
+				return ret;
+		}
+
+		gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
+		if (!gate)
+			return -ENOMEM;
+
+		gate->reg_offset = reg_val[0];
+		gate->bit_idx = reg_val[1];
+
+		clk = to_aml_clk(hw_data->hws[clkid]);
+		clk->data = gate;
+
+		hw_data->hws[clkid]->init = &init;
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int of_aml_clk_model_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct aml_clk_model_data *data;
+	struct device_node *np = dev_of_node(dev);
+	struct regmap *regmap;
+	struct clk_hw_onecell_data *hw_data;
+	struct aml_clk *clk;
+	enum aml_clk_type clk_type;
+	int clk_num;
+	int ret, i;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -EFAULT;
+
+	clk_num = of_aml_clk_get_count(np);
+	if (clk_num == 0)
+		return -EINVAL;
+
+	regmap = aml_clk_regmap_init(pdev);
+	if (IS_ERR_OR_NULL(regmap))
+		return -EIO;
+
+	of_aml_clk_regs_init(dev);
+
+	hw_data = devm_kzalloc(dev, struct_size(hw_data, hws, clk_num),
+				GFP_KERNEL);
+	if (!hw_data)
+		return -ENOMEM;
+
+	hw_data->num = clk_num;
+	clk = devm_kcalloc(dev, clk_num, sizeof(*clk), GFP_KERNEL);
+	if (!clk)
+		return -ENOMEM;
+
+	ret = of_aml_clk_model_get_type(dev, &clk_type);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < clk_num; i++) {
+		clk[i].map = regmap;
+		clk[i].type = clk_type;
+		hw_data->hws[i] = &clk[i].hw;
+	}
+
+	if (data->type == CLK_MODEL_COMPOSITE)
+		ret = of_aml_clk_model_composite_init_register(dev, hw_data);
+	else if (data->type == CLK_MODEL_NOGLITCH)
+		ret = of_aml_clk_model_noglitch_init_register(dev, hw_data);
+	else if (data->type == CLK_MODEL_SYSBUS)
+		ret = of_aml_clk_model_sysbus_init_register(dev, hw_data);
+
+	if (clk_num == 1)
+		return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+						   &clk->hw);
+	else
+		return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
+						   hw_data);
+}
+
+static const struct aml_clk_model_data aml_composite_dev_data = {
+	.type = CLK_MODEL_COMPOSITE,
+};
+
+static const struct aml_clk_model_data aml_noglitch_dev_data = {
+	.type = CLK_MODEL_NOGLITCH,
+};
+
+static const struct aml_clk_model_data aml_sysbus_dev_data = {
+	.type = CLK_MODEL_SYSBUS,
+};
+
+static const struct of_device_id of_aml_clk_model_match_table[] = {
+	{
+		.compatible = "amlogic,a9-composite-ccu",
+		.data = &aml_composite_dev_data,
+	},
+	{
+		.compatible = "amlogic,a9-composite-ccu-mult",
+		.data = &aml_composite_dev_data,
+	},
+	{
+		.compatible = "amlogic,a9-noglitch-ccu",
+		.data = &aml_noglitch_dev_data,
+	},
+	{
+		.compatible = "amlogic,a9-noglitch-ccu-mult",
+		.data = &aml_noglitch_dev_data,
+	},
+	{
+		.compatible = "amlogic,a9-sysbus-ccu",
+		.data = &aml_sysbus_dev_data,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_aml_clk_model_match_table);
+
+static struct platform_driver of_aml_clk_model_driver = {
+	.probe		= of_aml_clk_model_probe,
+	.driver		= {
+		.name	= "aml-clk-model",
+		.of_match_table = of_aml_clk_model_match_table,
+	},
+};
+module_platform_driver(of_aml_clk_model_driver);
+
+MODULE_DESCRIPTION("Amlogic A9 Standardized Model Clock Control Units Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");

-- 
2.42.0



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

* [PATCH 11/13] clk: amlogic: Add A9 PLL controllers driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (9 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 10/13] clk: amlogic: Add A9 standardized model clock control units driver Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 12/13] clk: amlogic: Add A9 misc clock control units driver Chuan Liu via B4 Relay
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Add PLL controllers driver for the Amlogic A9 SoC family.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Kconfig  |  10 +++
 drivers/clk/amlogic/Makefile |   4 ++
 drivers/clk/amlogic/a9-pll.c | 146 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 160 insertions(+)

diff --git a/drivers/clk/amlogic/Kconfig b/drivers/clk/amlogic/Kconfig
index 6e954c9388dc..3177a02ecbd5 100644
--- a/drivers/clk/amlogic/Kconfig
+++ b/drivers/clk/amlogic/Kconfig
@@ -21,11 +21,21 @@ config COMMON_CLK_AMLOGIC_MODEL
 	  these models. Select Y if the current SoC contains these clock control
 	  unit models.
 
+config COMMON_CLK_AMLOGIC_PLL
+	tristate "Amlogic PLL Controller"
+	depends on COMMON_CLK_AMLOGIC
+	help
+	  Supports PLL controller used in Amlogic SoCs. The PLL supports dynamic
+	  configuration of output clock frequency, enabling flexible frequency
+	  settings to provide clocks for other modules. Select Y if the current
+	  SoC contains PLLs.
+
 config COMMON_CLK_AMLOGIC_A9
 	tristate "Amlogic A9 Family Clock Controller"
 	depends on COMMON_CLK_AMLOGIC
 	default COMMON_CLK_AMLOGIC
 	select COMMON_CLK_AMLOGIC_MODEL
+	select COMMON_CLK_AMLOGIC_PLL
 	help
 	  Support for the clock controller present on the Amlogic A9 family
 	  SoCs. Select Y if A9 family SoC needs to support clock controller.
diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index ef3fb57cae9f..74bf84dbd5a8 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -11,3 +11,7 @@ clk-amlogic-y += clk-pll.o
 ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MODEL),)
 clk-amlogic-y += a9-model-ccu.o
 endif
+
+ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_PLL),)
+clk-amlogic-y += a9-pll.o
+endif
diff --git a/drivers/clk/amlogic/a9-pll.c b/drivers/clk/amlogic/a9-pll.c
new file mode 100644
index 000000000000..c4c695caa8ed
--- /dev/null
+++ b/drivers/clk/amlogic/a9-pll.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-pll.h"
+
+static const struct aml_pll_data a9_mclk_pll_data = {
+	.range = {
+		.min = 1400000000,
+		.max = 2800000000,
+	},
+	.od_max = 4,
+};
+
+static const struct aml_pll_data a9_gp_pll_data = {
+	.range = {
+		.min = 1400000000,
+		.max = 2800000000,
+	},
+	.frac_max = 131072, /* 2^17 */
+	.od_max = 4,
+	.flags = AML_PLL_M_EN0P5,
+};
+
+static const struct aml_pll_data a9_hifi_pll_data = {
+	.range = {
+		.min = 1400000000,
+		.max = 2800000000,
+	},
+	/*
+	 * NOTE: The frac_max value of hifi_pll is set to 100000 so that the
+	 * output frequency step can be expressed as an integer value. For
+	 * example, with a 24 MHz input clock, the resulting frequency step is
+	 * 24 MHz / 100000 = 240 Hz.
+	 *
+	 * This design avoids the need for floating-point arithmetic in
+	 * frequency calculations, which helps prevent precision loss in
+	 * scenarios with strict frequency accuracy requirements, such as audio
+	 * and video applications.
+	 */
+	.frac_max = 100000,
+	.od_max = 4,
+	.flags = AML_PLL_M_EN0P5,
+};
+
+static int of_aml_clk_pll_init_register(struct device *dev, struct aml_clk *pll)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct clk_init_data init;
+	struct clk_parent_data pdata;
+	u8 pnum;
+	int ret;
+
+	init.name = of_aml_clk_get_name_index(np, 0);
+	if (!init.name)
+		return -EINVAL;
+
+	ret = of_aml_clk_get_parent_data(dev, NULL, 0, 0, &pdata, &pnum);
+	if (ret)
+		return ret;
+
+	init.ops = &aml_pll_ops;
+	init.num_parents = pnum;
+	init.parent_data = &pdata;
+
+	pll->hw.init = &init;
+	ret = of_aml_clk_register(dev, &pll->hw, 0);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int of_aml_clk_pll_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct regmap *regmap;
+	struct aml_clk *pll;
+	struct aml_pll_data *pll_data_tmp;
+	int ret;
+
+	pll_data_tmp = (void *)of_device_get_match_data(dev);
+	if (!pll_data_tmp)
+		return -EFAULT;
+
+	pll = devm_kmalloc(dev, sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return -ENOMEM;
+
+	pll->data = devm_kmemdup(dev, pll_data_tmp, sizeof(*pll_data_tmp),
+				 GFP_KERNEL);
+	if (!pll->data)
+		return -ENOMEM;
+
+	regmap = aml_clk_regmap_init(pdev);
+	if (IS_ERR_OR_NULL(regmap))
+		return -EIO;
+
+	pll->map = regmap;
+	pll->type = AML_CLKTYPE_PLL;
+	ret = of_aml_clk_regs_init(dev);
+	if (ret)
+		return ret;
+
+	ret = of_aml_clk_pll_init_register(dev, pll);
+	if (ret)
+		return ret;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &pll->hw);
+}
+
+static const struct of_device_id of_aml_clk_pll_match_table[] = {
+	{
+		.compatible = "amlogic,a9-int-pll",
+		.data = &a9_mclk_pll_data,
+	},
+	{
+		.compatible = "amlogic,a9-frac-pll",
+		.data = &a9_gp_pll_data,
+	},
+	{
+		.compatible = "amlogic,a9-frac-step-pll",
+		.data = &a9_hifi_pll_data,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_aml_clk_pll_match_table);
+
+static struct platform_driver of_aml_clk_pll_driver = {
+	.probe		= of_aml_clk_pll_probe,
+	.driver		= {
+		.name	= "aml-pll",
+		.of_match_table = of_aml_clk_pll_match_table,
+	},
+};
+module_platform_driver(of_aml_clk_pll_driver);
+
+MODULE_DESCRIPTION("Amlogic A9 PLL Controllers Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");

-- 
2.42.0



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

* [PATCH 12/13] clk: amlogic: Add A9 misc clock control units driver
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (10 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 11/13] clk: amlogic: Add A9 PLL controllers driver Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-09  5:48 ` [PATCH 13/13] clk: amlogic: Add support for building as combined kernel module Chuan Liu via B4 Relay
  2026-02-11  8:34 ` [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Jerome Brunet
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Add support for Amlogic miscellaneous clock control units on A9 SoC
family. Certain peripheral clocks do not utilize the standardized models
due to specific requirements. These specialized clock control units are
handled in this driver, including:
  - sc-ccu
  - ts-ccu
  - genout-ccu
  - clk12_24m-ccu
  - vapb_ge2d-ccu
  - di-ccu
  - eth-ccu
  - dualdivmux-ccu
  - mclk-ccu

These clock control units contain multiple sub-clocks. The clock IDs
for each sub-clock are defined in /bindings/clock/amlogic,a9-misc-ccu.h.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Kconfig       |  10 +
 drivers/clk/amlogic/Makefile      |   5 +
 drivers/clk/amlogic/a9-misc-ccu.c | 960 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 975 insertions(+)

diff --git a/drivers/clk/amlogic/Kconfig b/drivers/clk/amlogic/Kconfig
index 3177a02ecbd5..502aca5332bc 100644
--- a/drivers/clk/amlogic/Kconfig
+++ b/drivers/clk/amlogic/Kconfig
@@ -11,6 +11,15 @@ config COMMON_CLK_AMLOGIC
 	  offering read and write interfaces for various clock control units.
 	  Select Y if your target SoC needs clock driver support.
 
+config COMMON_CLK_AMLOGIC_MISC
+	tristate "Amlogic Misc Clock Control Units"
+	depends on COMMON_CLK_AMLOGIC
+	help
+	  Supports non-standard module clock control units in Amlogic SoC clock
+	  trees, such as sc-ccu (for smart card controller) and ts-ccu (for
+	  temperature sensor). Select Y if the current SoC contains these module
+	  clock control units.
+
 config COMMON_CLK_AMLOGIC_MODEL
 	tristate "Amlogic Standardized Model Clock Control Units"
 	depends on COMMON_CLK_AMLOGIC
@@ -34,6 +43,7 @@ config COMMON_CLK_AMLOGIC_A9
 	tristate "Amlogic A9 Family Clock Controller"
 	depends on COMMON_CLK_AMLOGIC
 	default COMMON_CLK_AMLOGIC
+	select COMMON_CLK_AMLOGIC_MISC
 	select COMMON_CLK_AMLOGIC_MODEL
 	select COMMON_CLK_AMLOGIC_PLL
 	help
diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index 74bf84dbd5a8..b174dce61ae9 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -8,6 +8,11 @@ clk-amlogic-y += clk-composite.o
 clk-amlogic-y += clk-dualdiv.o
 clk-amlogic-y += clk-noglitch.o
 clk-amlogic-y += clk-pll.o
+
+ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MISC),)
+clk-amlogic-y += a9-misc-ccu.o
+endif
+
 ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MODEL),)
 clk-amlogic-y += a9-model-ccu.o
 endif
diff --git a/drivers/clk/amlogic/a9-misc-ccu.c b/drivers/clk/amlogic/a9-misc-ccu.c
new file mode 100644
index 000000000000..db130d84ccdd
--- /dev/null
+++ b/drivers/clk/amlogic/a9-misc-ccu.c
@@ -0,0 +1,960 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include "clk.h"
+#include "clk-basic.h"
+#include "clk-composite.h"
+#include "clk-dualdiv.h"
+#include "clk-noglitch.h"
+#include "clk-pll.h"
+
+#include <dt-bindings/clock/amlogic,a9-misc-ccu.h>
+
+static struct aml_clk a9_sc_pre = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_composite_ops,
+	},
+	.type = AML_CLKTYPE_COMPOSITE,
+	.data = &(struct aml_clk_composite_data) {
+		.div_width = 8,
+	},
+};
+
+static struct aml_clk a9_sc = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_sc_pre.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.shift = 16,
+		.width = 4,
+	},
+};
+
+struct clk_hw_onecell_data a9_sc_clk_hw_data = {
+	.hws = {
+		[A9_CLK_SC_PRE]	= &a9_sc_pre.hw,
+		[A9_CLK_SC]	= &a9_sc.hw,
+	},
+	.num = 2,
+};
+
+static struct aml_clk a9_ts_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.shift = 0,
+		.width = 8,
+	},
+};
+
+static struct aml_clk a9_ts = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_ts_div.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.bit_idx = 8,
+	},
+};
+
+struct clk_hw_onecell_data a9_ts_clk_hw_data = {
+	.hws = {
+		[A9_CLK_TS_DIV]	= &a9_ts_div.hw,
+		[A9_CLK_TS]	= &a9_ts.hw,
+	},
+	.num = 2,
+};
+
+static struct aml_clk a9_genout_sel = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_mux_ops,
+	},
+	.type = AML_CLKTYPE_MUX,
+	.data = &(struct aml_clk_mux_data) {
+		.mask = 0x1f,
+		.shift = 12,
+	},
+};
+
+static struct aml_clk a9_genout_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_genout_sel.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.shift = 0,
+		.width = 11,
+	},
+};
+
+static struct aml_clk a9_genout = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_genout_div.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.bit_idx = 11,
+	},
+};
+
+struct clk_hw_onecell_data a9_genout_clk_hw_data = {
+	.hws = {
+		[A9_CLK_GENOUT_SEL]	= &a9_genout_sel.hw,
+		[A9_CLK_GENOUT_DIV]	= &a9_genout_div.hw,
+		[A9_CLK_GENOUT]		= &a9_genout.hw,
+	},
+	.num = 3,
+};
+
+static struct aml_clk a9_clk24m_in = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.bit_idx = 11,
+	},
+};
+
+static struct aml_clk a9_clk12_24m = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_clk24m_in.hw
+		},
+		.num_parents = 1,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.shift = 10,
+		.width = 1,
+	},
+};
+
+struct clk_hw_onecell_data a9_clk12_24m_clk_hw_data = {
+	.hws = {
+		[A9_CLK_24M_IN]	= &a9_clk24m_in.hw,
+		[A9_CLK_12_24M]	= &a9_clk12_24m.hw,
+	},
+	.num = 2,
+};
+
+static struct aml_clk a9_vapb = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_noglitch_ops,
+	},
+	.type = AML_CLKTYPE_NOGLITCH,
+	.data = &(struct aml_clk_composite_data) {
+		.div_width = 7,
+	},
+};
+
+static struct aml_clk a9_ge2d = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_vapb.hw
+		},
+		.num_parents = 1,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.bit_idx = 30,
+	},
+};
+
+struct clk_hw_onecell_data a9_vapbge2d_clk_hw_data = {
+	.hws = {
+		[A9_CLK_VAPB]	= &a9_vapb.hw,
+		[A9_CLK_GE2D]	= &a9_ge2d.hw,
+	},
+	.num = 2,
+};
+
+static struct aml_clk a9_vpu_clkb_temp = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_composite_ops,
+	},
+	.type = AML_CLKTYPE_COMPOSITE,
+	.data = &(struct aml_clk_composite_data) {
+		.bit_offset = 16,
+		.div_width = 4,
+	},
+};
+
+static struct aml_clk a9_vpu_clkb_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_vpu_clkb_temp.hw
+		},
+		.num_parents = 1,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.shift = 0,
+		.width = 8,
+	},
+};
+
+static struct aml_clk a9_vpu_clkb = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_vpu_clkb_div.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.bit_idx = 8,
+	},
+};
+
+struct clk_hw_onecell_data a9_di_clk_hw_data = {
+	.hws = {
+		[A9_CLK_VPU_CLKB_TEMP]	= &a9_vpu_clkb_temp.hw,
+		[A9_CLK_VPU_CLKB_DIV]	= &a9_vpu_clkb_div.hw,
+		[A9_CLK_VPU_CLKB]	= &a9_vpu_clkb.hw,
+	},
+	.num = 3,
+};
+
+static int of_aml_clk_misc_set_parent_table(struct device *dev,
+					    struct aml_clk *clk,
+					    int start_index, int end_index)
+{
+	u32 *ptab = of_aml_clk_get_parent_table(dev, start_index, end_index);
+	struct aml_clk_mux_data *mux_data;
+	struct aml_clk_composite_data *comp_data;
+	struct aml_clk_noglitch_data *noglitch_data;
+
+	if (IS_ERR(ptab))
+		return PTR_ERR(ptab);
+	else if (!ptab) /* parent clock indices are contiguous */
+		return 0;
+
+	switch (clk->type) {
+	case AML_CLKTYPE_MUX:
+		mux_data = clk->data;
+		mux_data->table = ptab;
+		return 0;
+
+	case AML_CLKTYPE_COMPOSITE:
+		comp_data = clk->data;
+		comp_data->table = ptab;
+		return 0;
+
+	case AML_CLKTYPE_NOGLITCH:
+		noglitch_data = clk->data;
+		noglitch_data->table = ptab;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * The default CCU block diagram is shown below:
+ *            +---------------------------------+
+ *            |  +-------+           +-------+  |
+ *            |  |       |           |       |  |
+ *   clkin0----->|   1   |           |   n   |  |
+ *           ... | level |--> ... -->| level |------>clkout
+ *           ... | clock |           | clock |  |
+ *   clkinn----->|       |           |       |  |
+ *            |  +-------+           +-------+  |
+ *            +---------------------------------+
+ *
+ * By default, the CCU has the following characteristics:
+ *   - The clock specified by "clocks" uses the first-level clock (clkid = 0)
+ *     as its parent.
+ *   - The parent-child relationship among sub-clocks follows this order:
+ *       - hw_data->hws[0] -> ... -> hw_data->hws[n].
+ */
+static int of_aml_clk_misc_init_register(struct device *dev,
+					 struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct clk_init_data init;
+	struct clk_parent_data *pdata;
+	u8 pnum;
+	int ret, clkid;
+
+	if (hw_data->num != of_aml_clk_get_count(np))
+		return -EINVAL;
+
+	ret = of_aml_clk_get_parent_num(dev, 0, -1);
+	if (ret < 0)
+		return ret;
+
+	pnum = (u8)ret;
+	pdata = devm_kcalloc(dev, pnum, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	for (clkid = 0; clkid < hw_data->num; clkid++) {
+		/*
+		 * By default, clocks specified in the DT clocks property act as
+		 * the parent clocks for the first-level CCU clock (clkid == 0).
+		 */
+		if (clkid == 0) {
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 0, -1, pdata, &pnum);
+			if (ret)
+				goto out;
+
+			clk = to_aml_clk(hw_data->hws[clkid]);
+			ret = of_aml_clk_misc_set_parent_table(dev, clk, 0, -1);
+			if (ret)
+				goto out;
+		} else {
+			pnum = 1;
+			/*
+			 *The parent-child relationship among sub-clocks follows
+			 * this order:
+			 *  - hw_data->hws[0] -> ... -> hw_data->hws[n].
+			 */
+			pdata[0].hw = hw_data->hws[clkid - 1];
+		}
+
+		memcpy(&init, hw_data->hws[clkid]->init, sizeof(init));
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		init.num_parents = pnum;
+		init.parent_data = pdata;
+		hw_data->hws[clkid]->init = &init;
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			goto out;
+	}
+
+out:
+	devm_kfree(dev, pdata);
+
+	return ret;
+}
+
+#define A9_ETH_CLK_NUM		2
+
+static struct aml_clk a9_eth_125m = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.bit_idx = 7,
+	},
+};
+
+static struct aml_clk a9_eth_rmii = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_composite_ops,
+	},
+	.type = AML_CLKTYPE_COMPOSITE,
+	.data = &(struct aml_clk_composite_data) {
+		.div_width = 7,
+	},
+};
+
+struct clk_hw_onecell_data a9_eth_clk_hw_data = {
+	.hws = {
+		[A9_CLK_ETH_125M]	= &a9_eth_125m.hw,
+		[A9_CLK_ETH_RMII]	= &a9_eth_rmii.hw,
+	},
+	.num = A9_ETH_CLK_NUM,
+};
+
+/*
+ * A diagram of the A9 eth-ccu is as follows:
+ *         +------------------------------------------+
+ *         |                             +------+     |
+ * clk125m------------------------------>| gate |-------> eth_125m
+ *         |                             +------+     |
+ *         |     +---------------------------------+  |
+ *         |     |  |\                             |  |
+ * clkin0 --------->| |                            |  |
+ *         |     |  | |      +-----+     +------+  |  |
+ *         | ... |  | |----->| div |---->| gate |-------> eth_rmii
+ *         |     |  | |      +-----+     +------+  |  |
+ * clkin7 --------->| |                            |  |
+ *         |     |  |/       composote-ccu         |  |
+ *         |     +---------------------------------+  |
+ *         +------------------------------------------+
+ */
+static int of_aml_clk_eth_init_register(struct device *dev,
+					struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct clk_parent_data pdata[9];
+	u8 pnum;
+	struct clk_init_data init;
+	int ret, clkid;
+
+	if (of_aml_clk_get_count(np) != A9_ETH_CLK_NUM)
+		return -EINVAL;
+
+	for (clkid = 0; clkid < A9_ETH_CLK_NUM; clkid++) {
+		if (clkid == A9_CLK_ETH_125M) { /* eth_125m */
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 0, 0, pdata, &pnum);
+			if (ret)
+				return ret;
+		} else { /* eth_rmii */
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 1, 8, pdata, &pnum);
+			if (ret)
+				return ret;
+
+			clk = to_aml_clk(hw_data->hws[clkid]);
+			ret = of_aml_clk_misc_set_parent_table(dev, clk, 1, 8);
+			if (ret)
+				return ret;
+		}
+
+		memcpy(&init, hw_data->hws[clkid]->init, sizeof(init));
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name)
+			return -EINVAL;
+
+		init.num_parents = pnum;
+		init.parent_data = pdata;
+		hw_data->hws[clkid]->init = &init;
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+#define A9_DUALDIVMUX_CLK_NUM		2
+
+static struct aml_clk_dualdiv_param a9_dualdiv_table[] = {
+	{ 1, 732, 7, 731, 10 }, /* 32.768k for rtc/cec */
+};
+
+static struct aml_clk a9_dualdiv = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_dualdiv_ops,
+	},
+	.type = AML_CLKTYPE_DUALDIV,
+	.data = &(struct aml_clk_dualdiv_data) {
+		.reg_offset = 0,
+		.table = a9_dualdiv_table,
+		.table_count = ARRAY_SIZE(a9_dualdiv_table),
+	},
+};
+
+static struct aml_clk a9_dualdiv_sel = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_mux_ops,
+		/*
+		 * NOTE: To ensure output clock continuity, the mux can switch
+		 * correctly only when both the current path and the target path
+		 * have valid clock inputs. Otherwise, the mux cannot complete
+		 * the switch.
+		 */
+		.flags = CLK_OPS_PARENT_ENABLE,
+	},
+	.type = AML_CLKTYPE_MUX,
+	.data = &(struct aml_clk_mux_data) {
+		.reg_offset = 4,
+		.mask = 0x3,
+		.shift = 30,
+	},
+};
+
+struct clk_hw_onecell_data a9_dualdiv_clk_hw_data = {
+	.hws = {
+		[A9_CLK_DUALDIV]	= &a9_dualdiv.hw,
+		[A9_CLK_DUALDIV_SEL]	= &a9_dualdiv_sel.hw,
+	},
+	.num = A9_DUALDIVMUX_CLK_NUM,
+};
+
+/*
+ * A diagram of the A9 dualdivmux-ccu is as follows:
+ *       +--------------------------------------+
+ *       |  +---------+                         |
+ *       |  |         |                         |
+ * divin--->| dualdiv |--+                      |
+ *       |  |         |  |                      |
+ *       |  +---------+  |   +---+         |\   |
+ *       |               +-->| ~ |--mux0-->| |  |
+ * clk0--------------------->| ~ |--mux1-->| |------> out
+ * clk1--------------------->| ~ |--mux2-->| |  |
+ * clk2--------------------->| ~ |--mux3-->|/   |
+ *       |                   +---+              |
+ *       +--------------------------------------+
+ *
+ * Its internal multiplexer selects from up to 4 parent clocks, one of which
+ * must be 'dualdiv' output clock.
+ */
+static int of_aml_clk_dualdiv_init_register(struct device *dev,
+					    struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct clk_parent_data pdata[5];
+	u8 pnum;
+	struct clk_init_data init;
+	int ret, clkid;
+
+	if (of_aml_clk_get_count(np) != A9_DUALDIVMUX_CLK_NUM)
+		return -EINVAL;
+
+	for (clkid = 0; clkid < A9_DUALDIVMUX_CLK_NUM; clkid++) {
+		if (clkid == A9_CLK_DUALDIV) { /* dualdiv */
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 0, 0, pdata, &pnum);
+			if (ret)
+				return ret;
+		} else { /* dualdiv_sel */
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 1, 4, pdata, &pnum);
+			if (ret)
+				return ret;
+
+			clk = to_aml_clk(hw_data->hws[clkid]);
+			ret = of_aml_clk_misc_set_parent_table(dev, clk, 1, 4);
+			if (ret)
+				return ret;
+		}
+
+		memcpy(&init, hw_data->hws[clkid]->init, sizeof(init));
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name)
+			return -EINVAL;
+
+		init.num_parents = pnum;
+		init.parent_data = pdata;
+		hw_data->hws[clkid]->init = &init;
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static struct aml_clk a9_mclk0_pre_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.num_parents = 1,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.reg_offset = 0xc,
+		.shift = 3,
+		.width = 5,
+		.flags = CLK_DIVIDER_MAX_AT_ZERO,
+	},
+};
+
+static struct aml_clk a9_mclk0_sel = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_mux_ops,
+	},
+	.type = AML_CLKTYPE_MUX,
+	.data = &(struct aml_clk_mux_data) {
+		.reg_offset = 0xc,
+		.mask = 0x3,
+		.shift = 12,
+	},
+};
+
+static struct aml_clk a9_mclk0_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_mclk0_sel.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.reg_offset = 0xc,
+		.shift = 9,
+		.width = 1,
+	},
+};
+
+static struct aml_clk a9_mclk0 = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_mclk0_div.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.reg_offset = 0xc,
+		.bit_idx = 8,
+	},
+};
+
+static struct aml_clk a9_mclk1_pre_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.num_parents = 1,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.reg_offset = 0xc,
+		.shift = 19,
+		.width = 5,
+		.flags = CLK_DIVIDER_MAX_AT_ZERO,
+	},
+};
+
+static struct aml_clk a9_mclk1_sel = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_mux_ops,
+	},
+	.type = AML_CLKTYPE_MUX,
+	.data = &(struct aml_clk_mux_data) {
+		.reg_offset = 0xc,
+		.mask = 0x3,
+		.shift = 28,
+	},
+};
+
+static struct aml_clk a9_mclk1_div = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_divider_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_mclk1_sel.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_DIV,
+	.data = &(struct aml_clk_divider_data) {
+		.reg_offset = 0xc,
+		.shift = 25,
+		.width = 1,
+	},
+};
+
+static struct aml_clk a9_mclk1 = {
+	.hw.init = &(const struct clk_init_data) {
+		.ops = &aml_clk_gate_ops,
+		.parent_hws = (const struct clk_hw *[]) {
+			&a9_mclk1_div.hw
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+	.type = AML_CLKTYPE_GATE,
+	.data = &(struct aml_clk_gate_data) {
+		.reg_offset = 0xc,
+		.bit_idx = 24,
+	},
+};
+
+#define A9_MCLK_CLK_NUM		8
+
+struct clk_hw_onecell_data a9_mclk_clk_hw_data = {
+	.hws = {
+		[A9_CLK_MCLK_0_PRE_DIV]	= &a9_mclk0_pre_div.hw,
+		[A9_CLK_MCLK_0_SEL]	= &a9_mclk0_sel.hw,
+		[A9_CLK_MCLK_0_DIV]	= &a9_mclk0_div.hw,
+		[A9_CLK_MCLK_0]		= &a9_mclk0.hw,
+		[A9_CLK_MCLK_1_PRE_DIV]	= &a9_mclk1_pre_div.hw,
+		[A9_CLK_MCLK_1_SEL]	= &a9_mclk1_sel.hw,
+		[A9_CLK_MCLK_1_DIV]	= &a9_mclk1_div.hw,
+		[A9_CLK_MCLK_1]		= &a9_mclk1.hw,
+	},
+	.num = A9_MCLK_CLK_NUM,
+};
+
+/*
+ * A diagram of the A9 mclk-ccu is as follows:
+ *        +--------------------------------------------------+
+ *        |              +-----+                             |
+ * divin--------------+->| div |-->|\                        |
+ *        |           |  +-----+   | |                       |
+ * clk0 -----------+-------------->| |   +-----+   +------+  |
+ *        |        |  |            | |-->| div |-->| gate |-----> mclk0
+ * clk1 --------+----------------->| |   +-----+   +------+  |
+ *        |     |  |  |            | |                       |
+ * clk2 -----+-------------------->|/                        |
+ *        |  |  |  |  |  +-----+                             |
+ *        |  |  |  |  +->| div |-->|\                        |
+ *        |  |  |  |     +-----+   | |                       |
+ *        |  |  |  +-------------->| |   +-----+   +------+  |
+ *        |  |  |                  | |-->| div |-->| gate |-----> mclk1
+ *        |  |  +----------------->| |   +-----+   +------+  |
+ *        |  |                     | |                       |
+ *        |  +-------------------->|/                        |
+ *        +--------------------------------------------------+
+ *
+ * The A9 mclk-ccu contains two identical mclk sub-modules. For each sub-module,
+ * channel 0 of the mux (mclk0/1_sel) has an independent pre-divider in front
+ * of it, while the clock sources for channels 1 to 3 are shared and identical.
+ */
+static int of_aml_clk_mclk_init_register(struct device *dev,
+					 struct clk_hw_onecell_data *hw_data)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct aml_clk *clk;
+	struct clk_parent_data pdata[5];
+	u8 pnum;
+	struct clk_init_data init;
+	int ret, clkid;
+
+	if (of_aml_clk_get_count(np) != A9_MCLK_CLK_NUM)
+		return -EINVAL;
+
+	for (clkid = 0; clkid < A9_MCLK_CLK_NUM; clkid++) {
+		if (clkid == A9_CLK_MCLK_0_PRE_DIV ||
+		    clkid == A9_CLK_MCLK_1_PRE_DIV) {
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 0, 0, pdata, &pnum);
+			if (ret)
+				return ret;
+		} else if (clkid == A9_CLK_MCLK_0_SEL ||
+			   clkid == A9_CLK_MCLK_1_SEL) {
+			ret = of_aml_clk_get_parent_data(dev, hw_data->hws,
+							 1, 4, &pdata[1], &pnum);
+			if (ret)
+				return ret;
+
+			/* Add the clock source for channel 0 of mclk0/1_sel */
+			pdata[0].hw = hw_data->hws[clkid - 1];
+			pnum += 1;
+
+			clk = to_aml_clk(hw_data->hws[clkid]);
+			ret = of_aml_clk_misc_set_parent_table(dev, clk, 1, 4);
+			if (ret)
+				return ret;
+		} else {
+			pnum = 1;
+			/*
+			 *The parent-child relationship among sub-clocks follows
+			 * this order:
+			 *  - hw_data->hws[0] -> ... -> hw_data->hws[n].
+			 */
+			pdata[0].hw = hw_data->hws[clkid - 1];
+		}
+
+		memcpy(&init, hw_data->hws[clkid]->init, sizeof(init));
+		init.name = of_aml_clk_get_name_index(np, clkid);
+		if (!init.name)
+			return -EINVAL;
+
+		init.num_parents = pnum;
+		init.parent_data = pdata;
+		hw_data->hws[clkid]->init = &init;
+		ret = of_aml_clk_register(dev, hw_data->hws[clkid], clkid);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int aml_clk_get_clk_data_size(enum aml_clk_type type)
+{
+	switch (type) {
+	case AML_CLKTYPE_MUX:
+		return sizeof(struct aml_clk_mux_data);
+
+	case AML_CLKTYPE_DIV:
+		return sizeof(struct aml_clk_divider_data);
+
+	case AML_CLKTYPE_GATE:
+		return sizeof(struct aml_clk_gate_data);
+
+	case AML_CLKTYPE_COMPOSITE:
+		return sizeof(struct aml_clk_composite_data);
+
+	case AML_CLKTYPE_NOGLITCH:
+		return sizeof(struct aml_clk_noglitch_data);
+
+	case AML_CLKTYPE_DUALDIV:
+		return sizeof(struct aml_clk_dualdiv_data);
+
+	case AML_CLKTYPE_PLL:
+		return sizeof(struct aml_pll_data);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct clk_hw_onecell_data *
+aml_clk_misc_alloc_hw_data(struct device *dev, struct regmap *regmap,
+			   struct clk_hw_onecell_data *hw_data_tmp)
+{
+	struct device_node *np = dev_of_node(dev);
+	int clk_num;
+	int ret, clkid;
+	struct clk_hw_onecell_data *hw_data;
+	struct aml_clk *clk;
+	struct aml_clk *clk_tmp;
+
+	clk_num = of_aml_clk_get_count(np);
+	if (clk_num != hw_data_tmp->num)
+		return ERR_PTR(-EINVAL);
+
+	hw_data = devm_kzalloc(dev, struct_size(hw_data, hws, clk_num),
+			       GFP_KERNEL);
+	if (!hw_data)
+		return ERR_PTR(-ENOMEM);
+
+	hw_data->num = clk_num;
+	for (clkid = 0; clkid < clk_num; clkid++) {
+		clk_tmp = to_aml_clk(hw_data_tmp->hws[clkid]);
+		clk = devm_kmemdup(dev, clk_tmp, sizeof(*clk), GFP_KERNEL);
+		if (!clk)
+			return ERR_PTR(-ENOMEM);
+
+		clk->map = regmap;
+		hw_data->hws[clkid] = &clk->hw;
+		ret = aml_clk_get_clk_data_size(clk_tmp->type);
+		if (ret < 0)
+			return ERR_PTR(ret);
+
+		clk->data = devm_kmemdup(dev, clk_tmp->data, ret, GFP_KERNEL);
+		if (!clk->data)
+			return ERR_PTR(-ENOMEM);
+	}
+
+	return hw_data;
+}
+
+static int of_aml_clk_misc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct regmap *regmap;
+	int ret;
+	struct clk_hw_onecell_data *hw_data, *hw_data_tmp;
+
+	/*
+	 * NOTE: Here, the pointer returned by of_device_get_match_data() points
+	 * to non-const data, and the hw_data parameter passed to
+	 * devm_of_clk_add_hw_provider() is also of type (void *).
+	 */
+	hw_data_tmp = (void *)of_device_get_match_data(dev);
+	if (!hw_data_tmp)
+		return -EFAULT;
+
+	regmap = aml_clk_regmap_init(pdev);
+	if (IS_ERR_OR_NULL(regmap))
+		return -EIO;
+
+	hw_data = aml_clk_misc_alloc_hw_data(dev, regmap, hw_data_tmp);
+	if (IS_ERR(hw_data))
+		return PTR_ERR(hw_data);
+
+	of_aml_clk_regs_init(dev);
+	if (hw_data_tmp == &a9_eth_clk_hw_data)
+		ret = of_aml_clk_eth_init_register(dev, hw_data);
+	else if (hw_data_tmp == &a9_dualdiv_clk_hw_data)
+		ret = of_aml_clk_dualdiv_init_register(dev, hw_data);
+	else if (hw_data_tmp == &a9_mclk_clk_hw_data)
+		ret = of_aml_clk_mclk_init_register(dev, hw_data);
+	else
+		ret = of_aml_clk_misc_init_register(dev, hw_data);
+	if (ret)
+		return ret;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
+					   hw_data);
+}
+
+static const struct of_device_id of_aml_clk_misc_match_table[] = {
+	{
+		.compatible = "amlogic,a9-sc-ccu",
+		.data = &a9_sc_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-ts-ccu",
+		.data = &a9_ts_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-genout-ccu",
+		.data = &a9_genout_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-clk12_24m-ccu",
+		.data = &a9_clk12_24m_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-vapb_ge2d-ccu",
+		.data = &a9_vapbge2d_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-di-ccu",
+		.data = &a9_di_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-eth-ccu",
+		.data = &a9_eth_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-dualdivmux-ccu",
+		.data = &a9_dualdiv_clk_hw_data,
+	},
+	{
+		.compatible = "amlogic,a9-mclk-ccu",
+		.data = &a9_mclk_clk_hw_data,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_aml_clk_misc_match_table);
+
+static struct platform_driver of_aml_clk_misc_driver = {
+	.probe		= of_aml_clk_misc_probe,
+	.driver		= {
+		.name	= "aml-misc-clk",
+		.of_match_table = of_aml_clk_misc_match_table,
+	},
+};
+module_platform_driver(of_aml_clk_misc_driver);
+
+MODULE_DESCRIPTION("Amlogic A9 Misc Clock Control Units Driver");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");

-- 
2.42.0



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

* [PATCH 13/13] clk: amlogic: Add support for building as combined kernel module
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (11 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 12/13] clk: amlogic: Add A9 misc clock control units driver Chuan Liu via B4 Relay
@ 2026-02-09  5:48 ` Chuan Liu via B4 Relay
  2026-02-11  8:34 ` [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Jerome Brunet
  13 siblings, 0 replies; 23+ messages in thread
From: Chuan Liu via B4 Relay @ 2026-02-09  5:48 UTC (permalink / raw)
  To: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel, Chuan Liu

From: Chuan Liu <chuan.liu@amlogic.com>

Some use cases require clock drivers to be built as kernel modules
and loaded using insmod after system initialization. This patch combines
multiple clock drivers into a single module to reduce system call
overhead caused by multiple insmod invocations during boot process.

Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
---
 drivers/clk/amlogic/Kconfig        | 17 ++++++------
 drivers/clk/amlogic/Makefile       |  1 +
 drivers/clk/amlogic/a9-misc-ccu.c  | 12 ++++++++-
 drivers/clk/amlogic/a9-model-ccu.c | 12 ++++++++-
 drivers/clk/amlogic/a9-pll.c       | 12 ++++++++-
 drivers/clk/amlogic/clk-module.c   | 42 ++++++++++++++++++++++++++++++
 drivers/clk/amlogic/clk-module.h   | 53 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 138 insertions(+), 11 deletions(-)

diff --git a/drivers/clk/amlogic/Kconfig b/drivers/clk/amlogic/Kconfig
index 502aca5332bc..1b60725b80d8 100644
--- a/drivers/clk/amlogic/Kconfig
+++ b/drivers/clk/amlogic/Kconfig
@@ -9,7 +9,7 @@ config COMMON_CLK_AMLOGIC
 	help
 	  This driver provides the basic clock infrastructure for Amlogic SoCs,
 	  offering read and write interfaces for various clock control units.
-	  Select Y if your target SoC needs clock driver support.
+	  Select M or Y if your target SoC needs clock driver support.
 
 config COMMON_CLK_AMLOGIC_MISC
 	tristate "Amlogic Misc Clock Control Units"
@@ -17,8 +17,8 @@ config COMMON_CLK_AMLOGIC_MISC
 	help
 	  Supports non-standard module clock control units in Amlogic SoC clock
 	  trees, such as sc-ccu (for smart card controller) and ts-ccu (for
-	  temperature sensor). Select Y if the current SoC contains these module
-	  clock control units.
+	  temperature sensor). Select M or Y if the current SoC contains these
+	  module clock control units.
 
 config COMMON_CLK_AMLOGIC_MODEL
 	tristate "Amlogic Standardized Model Clock Control Units"
@@ -27,8 +27,8 @@ config COMMON_CLK_AMLOGIC_MODEL
 	  Supports standardized model clock control units commonly used in Amlogic
 	  SoC clock trees, such as composite-ccu, noglitch-ccu, and sysbus-ccu.
 	  Most peripheral clock controllers in Amlogic SoCs are composed of
-	  these models. Select Y if the current SoC contains these clock control
-	  unit models.
+	  these models. Select M or Y if the current SoC contains these clock
+	  control unit models.
 
 config COMMON_CLK_AMLOGIC_PLL
 	tristate "Amlogic PLL Controller"
@@ -36,8 +36,8 @@ config COMMON_CLK_AMLOGIC_PLL
 	help
 	  Supports PLL controller used in Amlogic SoCs. The PLL supports dynamic
 	  configuration of output clock frequency, enabling flexible frequency
-	  settings to provide clocks for other modules. Select Y if the current
-	  SoC contains PLLs.
+	  settings to provide clocks for other modules. Select M or Y if the
+	  current SoC contains PLLs.
 
 config COMMON_CLK_AMLOGIC_A9
 	tristate "Amlogic A9 Family Clock Controller"
@@ -48,4 +48,5 @@ config COMMON_CLK_AMLOGIC_A9
 	select COMMON_CLK_AMLOGIC_PLL
 	help
 	  Support for the clock controller present on the Amlogic A9 family
-	  SoCs. Select Y if A9 family SoC needs to support clock controller.
+	  SoCs. Select M or Y if A9 family SoC needs to support clock
+	  controller.
diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile
index b174dce61ae9..2778d3859a5e 100644
--- a/drivers/clk/amlogic/Makefile
+++ b/drivers/clk/amlogic/Makefile
@@ -8,6 +8,7 @@ clk-amlogic-y += clk-composite.o
 clk-amlogic-y += clk-dualdiv.o
 clk-amlogic-y += clk-noglitch.o
 clk-amlogic-y += clk-pll.o
+clk-amlogic-y += clk-module.o
 
 ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MISC),)
 clk-amlogic-y += a9-misc-ccu.o
diff --git a/drivers/clk/amlogic/a9-misc-ccu.c b/drivers/clk/amlogic/a9-misc-ccu.c
index db130d84ccdd..6fd2c9ae3cd0 100644
--- a/drivers/clk/amlogic/a9-misc-ccu.c
+++ b/drivers/clk/amlogic/a9-misc-ccu.c
@@ -10,6 +10,7 @@
 #include "clk-basic.h"
 #include "clk-composite.h"
 #include "clk-dualdiv.h"
+#include "clk-module.h"
 #include "clk-noglitch.h"
 #include "clk-pll.h"
 
@@ -952,7 +953,16 @@ static struct platform_driver of_aml_clk_misc_driver = {
 		.of_match_table = of_aml_clk_misc_match_table,
 	},
 };
-module_platform_driver(of_aml_clk_misc_driver);
+
+int __init aml_clk_misc_driver_init(void)
+{
+	return platform_driver_register(&of_aml_clk_misc_driver);
+}
+
+void __exit aml_clk_misc_driver_exit(void)
+{
+	platform_driver_unregister(&of_aml_clk_misc_driver);
+}
 
 MODULE_DESCRIPTION("Amlogic A9 Misc Clock Control Units Driver");
 MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
diff --git a/drivers/clk/amlogic/a9-model-ccu.c b/drivers/clk/amlogic/a9-model-ccu.c
index 5d5bf1538f73..58babd42aca3 100644
--- a/drivers/clk/amlogic/a9-model-ccu.c
+++ b/drivers/clk/amlogic/a9-model-ccu.c
@@ -9,6 +9,7 @@
 #include "clk.h"
 #include "clk-basic.h"
 #include "clk-composite.h"
+#include "clk-module.h"
 #include "clk-noglitch.h"
 
 /*
@@ -457,7 +458,16 @@ static struct platform_driver of_aml_clk_model_driver = {
 		.of_match_table = of_aml_clk_model_match_table,
 	},
 };
-module_platform_driver(of_aml_clk_model_driver);
+
+int __init aml_clk_model_driver_init(void)
+{
+	return platform_driver_register(&of_aml_clk_model_driver);
+}
+
+void __exit aml_clk_model_driver_exit(void)
+{
+	platform_driver_unregister(&of_aml_clk_model_driver);
+}
 
 MODULE_DESCRIPTION("Amlogic A9 Standardized Model Clock Control Units Driver");
 MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
diff --git a/drivers/clk/amlogic/a9-pll.c b/drivers/clk/amlogic/a9-pll.c
index c4c695caa8ed..fe2a77382509 100644
--- a/drivers/clk/amlogic/a9-pll.c
+++ b/drivers/clk/amlogic/a9-pll.c
@@ -7,6 +7,7 @@
 #include <linux/module.h>
 
 #include "clk.h"
+#include "clk-module.h"
 #include "clk-pll.h"
 
 static const struct aml_pll_data a9_mclk_pll_data = {
@@ -138,7 +139,16 @@ static struct platform_driver of_aml_clk_pll_driver = {
 		.of_match_table = of_aml_clk_pll_match_table,
 	},
 };
-module_platform_driver(of_aml_clk_pll_driver);
+
+int __init aml_pll_driver_init(void)
+{
+	return platform_driver_register(&of_aml_clk_pll_driver);
+}
+
+void __exit aml_pll_driver_exit(void)
+{
+	platform_driver_unregister(&of_aml_clk_pll_driver);
+}
 
 MODULE_DESCRIPTION("Amlogic A9 PLL Controllers Driver");
 MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
diff --git a/drivers/clk/amlogic/clk-module.c b/drivers/clk/amlogic/clk-module.c
new file mode 100644
index 000000000000..506926c1f908
--- /dev/null
+++ b/drivers/clk/amlogic/clk-module.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/module.h>
+
+#include "clk-module.h"
+
+static int __init aml_clk_driver_init(void)
+{
+	int ret;
+
+	ret = aml_pll_driver_init();
+	if (ret)
+		return ret;
+
+	ret = aml_clk_model_driver_init();
+	if (ret)
+		return ret;
+
+	ret = aml_clk_misc_driver_init();
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void __exit aml_clk_driver_exit(void)
+{
+	aml_clk_misc_driver_exit();
+	aml_clk_model_driver_exit();
+	aml_pll_driver_exit();
+}
+
+module_init(aml_clk_driver_init);
+module_exit(aml_clk_driver_exit);
+
+MODULE_DESCRIPTION("Amlogic Clock Controllers Driver Register");
+MODULE_AUTHOR("Chuan Liu <chuan.liu@amlogic.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_AMLOGIC");
diff --git a/drivers/clk/amlogic/clk-module.h b/drivers/clk/amlogic/clk-module.h
new file mode 100644
index 000000000000..6091a50803df
--- /dev/null
+++ b/drivers/clk/amlogic/clk-module.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef __AML_CLK_MODULE_H
+#define __AML_CLK_MODULE_H
+
+#include <linux/kconfig.h>
+
+#if IS_ENABLED(CONFIG_COMMON_CLK_AMLOGIC_PLL)
+extern int aml_pll_driver_init(void);
+extern void aml_pll_driver_exit(void);
+#else /* CONFIG_COMMON_CLK_AMLOGIC_PLL */
+static inline int aml_pll_driver_init(void)
+{
+	return 0;
+}
+
+static inline void aml_pll_driver_exit(void)
+{
+}
+#endif /* CONFIG_COMMON_CLK_AMLOGIC_PLL */
+
+#if IS_ENABLED(CONFIG_COMMON_CLK_AMLOGIC_MODEL)
+extern int aml_clk_model_driver_init(void);
+extern void aml_clk_model_driver_exit(void);
+#else /* CONFIG_COMMON_CLK_AMLOGIC_MODEL */
+static inline int aml_clk_model_driver_init(void)
+{
+	return 0;
+}
+
+static inline void aml_clk_model_driver_exit(void)
+{
+}
+#endif /* CONFIG_COMMON_CLK_AMLOGIC_MODEL */
+
+#if IS_ENABLED(CONFIG_COMMON_CLK_AMLOGIC_MISC)
+extern int aml_clk_misc_driver_init(void);
+extern void aml_clk_misc_driver_exit(void);
+#else /* CONFIG_COMMON_CLK_AMLOGIC_MISC */
+static inline int aml_clk_misc_driver_init(void)
+{
+	return 0;
+}
+
+static inline void aml_clk_misc_driver_exit(void)
+{
+}
+#endif /* CONFIG_COMMON_CLK_AMLOGIC_MISC */
+
+#endif /* __AML_CLK_MODULE_H */

-- 
2.42.0



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

* Re: [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units
  2026-02-09  5:48 ` [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Chuan Liu via B4 Relay
@ 2026-02-09 13:14   ` Krzysztof Kozlowski
  2026-02-09 13:18   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-09 13:14 UTC (permalink / raw)
  To: chuan.liu, Neil Armstrong, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel

On 09/02/2026 06:48, Chuan Liu via B4 Relay wrote:
> From: Chuan Liu <chuan.liu@amlogic.com>
> 
> Add dt-binding documentation for standardized model clock control units
> in A9 SoC family.
> 
> Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
> ---
>  .../bindings/clock/amlogic,a9-model-ccu.yaml       | 435 +++++++++++++++++++++
>  1 file changed, 435 insertions(+)

Brief review, you still have to read basic guidelines to not repeat the
basic mistakes.

> 
> diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.yaml
> new file mode 100644
> index 000000000000..56c5cbe1b246
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.yaml
> @@ -0,0 +1,435 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright (C) 2026 Amlogic, Inc. All rights reserved
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/amlogic,a9-model-ccu.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Amlogic A9 Family Standardized Model Clock Control Unit
> +
> +maintainers:
> +  - Chuan Liu <chuan.liu@amlogic.com>
> +
> +description:
> +  The clock tree within the A9 is composed of numerous instances of these
> +  standardized model CCU (Clock Control Units).
> +
> +properties:
> +  compatible:
> +    oneOf:
> +      - items:

Drop

> +          - const: amlogic,a9-composite-ccu
> +        description: Supports clock source selection, frequency division, and
> +                     clock gating.

This is just one big enum. You can add comments if you insist...


> +      - items:
> +          - const: amlogic,a9-composite-ccu-mult
> +        description: Some modules have multiple input clocks and contain
> +                     multiple composite-ccus internally.
> +      - items:
> +          - const: amlogic,a9-noglitch-ccu
> +        description: Provides the same functionality as composite-ccu but
> +                     includes glitch suppression during frequency transitions.
> +      - items:
> +          - const: amlogic,a9-noglitch-ccu-mult
> +        description: Some modules have multiple input clocks and contain
> +                     multiple noglitch-ccus internally.
> +      - items:
> +          - const: amlogic,a9-sysbus-ccu
> +        description: Consists of multiple gating arrays, commonly used for
> +                     Amlogic's sys_clk and axi_clk.
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    minItems: 1
> +    maxItems: 16
> +
> +  clock-names:
> +    minItems: 1
> +    maxItems: 16
> +
> +  clock-output-names:
> +    minItems: 1
> +    # The sysbus-ccu of A9 supports up to 128 gates
> +    maxItems: 128
> +
> +  '#clock-cells':
> +    description:
> +      The clock controller of a module may contain one or more CCU(s). When a
> +      clock controller has multiple CCUs, an index is required to specify a
> +      particular CCU within the clock controller.

Drop

> +    oneOf:
> +      - const: 0
> +        description: Single clock output, no specifier needed
> +      - const: 1
> +        description: Multiple clocks, index selects specific output

Drop all descriptions. That's enum. Do not explain usu how DT works.

> +
> +  amlogic,clock-max-frequency:

Drop property. So many wrong things here... First, start from basic
guidelines like talks or docs in kernel and understand the suffixes.

Second not a DT property.

> +    description: |
> +      Each clock's maximum output frequency is constrained during hardware
> +      design to ensure proper timing requirements for the clock network. If the
> +      clock frequency configured exceeds this design limit, it can lead to
> +      abnormal behavior in modules relying on that clock and may even cause
> +      cross-talk that affects other modules.
> +
> +      In the driver, this property is parsed, and interface functions from the

Why would we care about driver?

This is binding, we talk about hardware.


> +      CCF are called to enforce the clock's maximum frequency, preventing
> +      potential issues caused by excessive clock frequency configurations.
> +    $ref: /schemas/types.yaml#/definitions/uint32-array
> +
> +  amlogic,reg-layout:

No, drop. Compatible defines it.

> +    description:
> +      These standardized model CCUs require register configuration for their
> +      clock functions. This property node describes the register layout
> +      parameters for each model's CCU.
> +    $ref: /schemas/types.yaml#/definitions/uint32-matrix
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          anyOf:
> +            - contains:
> +                const: amlogic,a9-composite-ccu
> +            - contains:
> +                const: amlogic,a9-noglitch-ccu
> +    then:
> +      properties:
> +        '#clock-cells':
> +          const: 0
> +        clock-output-names:
> +          minItems: 1
> +          maxItems: 1
> +    else:
> +      properties:
> +        '#clock-cells':
> +          const: 1
> +        clock-output-names:
> +          minItems: 2
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: amlogic,a9-composite-ccu
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 1
> +          items:
> +            - description: input clock source 0
> +            - description: input clock source 1 (optional)
> +            - description: input clock source 2 (optional)
> +            - description: input clock source 3 (optional)
> +            - description: input clock source 4 (optional)
> +            - description: input clock source 5 (optional)
> +            - description: input clock source 6 (optional)
> +            - description: input clock source 7 (optional)
> +        clock-names:
> +          minItems: 1
> +          items:
> +            - const: clkin0
> +            - const: clkin1
> +            - const: clkin2
> +            - const: clkin3
> +            - const: clkin4
> +            - const: clkin5
> +            - const: clkin6
> +            - const: clkin7
> +        amlogic,reg-layout:
> +          description: |
> +            composite-ccu contains three register layout parameters:
> +              * register offset
> +              * bit offset
> +              * divider effective bit width
> +      required:
> +        - amlogic,reg-layout
> +        - clock-names
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: amlogic,a9-composite-ccu-mult
> +    then:
> +      properties:
> +        clocks:
> +          description:
> +            Some clock controllers contain two composite-ccus (labeled
> +            composite_a and composite_b). In certain controllers, composite_a
> +            and composite_b share a common clock source, while in others they
> +            have independent clock sources.
> +          minItems: 1
> +          items:
> +            - description: composite_a/b's input clock source 0
> +            - description: composite_a/b's input clock source 1 (optional)
> +            - description: composite_a/b's input clock source 2 (optional)
> +            - description: composite_a/b's input clock source 3 (optional)
> +            - description: composite_a/b's input clock source 4 (optional)
> +            - description: composite_a/b's input clock source 5 (optional)
> +            - description: composite_a/b's input clock source 6 (optional)
> +            - description: composite_a/b's input clock source 7 (optional)
> +            - description: composite_b's input clock source 0 (optional)
> +            - description: composite_b's input clock source 1 (optional)
> +            - description: composite_b's input clock source 2 (optional)
> +            - description: composite_b's input clock source 3 (optional)
> +            - description: composite_b's input clock source 4 (optional)
> +            - description: composite_b's input clock source 5 (optional)
> +            - description: composite_b's input clock source 6 (optional)
> +            - description: composite_b's input clock source 7 (optional)
> +        clock-names:
> +          minItems: 1
> +          items:
> +            - const: clkin0
> +            - const: clkin1
> +            - const: clkin2
> +            - const: clkin3
> +            - const: clkin4
> +            - const: clkin5
> +            - const: clkin6
> +            - const: clkin7
> +            - const: bclkin0
> +            - const: bclkin1
> +            - const: bclkin2
> +            - const: bclkin3
> +            - const: bclkin4
> +            - const: bclkin5
> +            - const: bclkin6
> +            - const: bclkin7
> +        amlogic,reg-layout:
> +          description: |
> +            composite-ccu contains three register layout parameters:
> +              * register offset
> +              * bit offset
> +              * divider effective bit width
> +      required:
> +        - amlogic,reg-layout
> +        - clock-names
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: amlogic,a9-noglitch-ccu
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 1
> +          items:
> +            - description: input clock source 0
> +            - description: input clock source 1 (optional)
> +            - description: input clock source 2 (optional)
> +            - description: input clock source 3 (optional)
> +            - description: input clock source 4 (optional)
> +            - description: input clock source 5 (optional)
> +            - description: input clock source 6 (optional)
> +            - description: input clock source 7 (optional)
> +        clock-names:
> +          minItems: 1
> +          items:
> +            - const: clkin0
> +            - const: clkin1
> +            - const: clkin2
> +            - const: clkin3
> +            - const: clkin4
> +            - const: clkin5
> +            - const: clkin6
> +            - const: clkin7
> +      required:
> +        - clock-names
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: amlogic,a9-noglitch-ccu-mult
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 1

Why?

> +          items:
> +            - description: input clock source 0
> +            - description: input clock source 1 (optional)

Drop optional. Don't repeat constraints in free form text.

> +            - description: input clock source 2 (optional)
> +            - description: input clock source 3 (optional)
> +            - description: input clock source 4 (optional)
> +            - description: input clock source 5 (optional)
> +            - description: input clock source 6 (optional)
> +            - description: input clock source 7 (optional)
> +        clock-names:
> +          minItems: 1
> +          items:
> +            - const: clkin0
> +            - const: clkin1
> +            - const: clkin2
> +            - const: clkin3
> +            - const: clkin4
> +            - const: clkin5
> +            - const: clkin6
> +            - const: clkin7
> +        amlogic,reg-layout:
> +          description: |
> +            composite-ccu contains one register layout parameters:
> +              * register offset
> +      required:
> +        - amlogic,reg-layout
> +        - clock-names
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: amlogic,a9-sysbus-ccu
> +    then:
> +      properties:
> +        clocks:
> +          maxItems: 1
> +          description: input clock of sysbus-ccu

list the items with description instead.

> +        amlogic,reg-layout:
> +          description: |
> +            composite-ccu contains two register layout parameters:
> +              * register offset
> +              * bit offset
> +      required:
> +        - amlogic,reg-layout

This is huge and amount of ifs is clearly suggesting you combined way
too much into one file.


> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-output-names
> +  - "#clock-cells"
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    clk_dummy: clock-dummy {
> +        compatible = "fixed-clock";
> +        #clock-cells = <0>;
> +        clock-frequency = <0>;
> +        clock-output-names = "dummy";
> +        status = "disabled";
> +    };

Not relevant, drop entire node.

> +
> +    apb {
> +        #address-cells = <2>;
> +        #size-cells = <2>;
> +        clock-controller@350 {
> +            compatible = "amlogic,a9-composite-ccu";
> +            reg = <0x0 0x350 0x0 0x4>;
> +            #clock-cells = <0>;
> +            amlogic,clock-max-frequency = <50000000>;
> +            amlogic,reg-layout = <0x0 0  7>;
> +            clock-output-names = "sar_adc";
> +            clocks = <&xtal_24m>,
> +                     <&scmi_clk 17>;
> +            clock-names = "clkin0", "clkin1";
> +        };
> +
> +        clock-controller@290 {
> +            compatible = "amlogic,a9-composite-ccu-mult";
> +            reg = <0x0 0x290 0x0 0x8>;
> +            #clock-cells = <1>;
> +            amlogic,clock-max-frequency = <250000000>,
> +                                          <250000000>,
> +                                          <1200000000>;
> +            amlogic,reg-layout = <0x0 0  7>,
> +                                 <0x0 16 7>,
> +                                 <0x4 0  7>;
> +            clock-output-names = "sd_emmc_a",
> +                                 "sd_emmc_b",
> +                                 "sd_emmc_c";
> +            clocks = <&xtal_24m>,
> +                     <&scmi_clk 6>,
> +                     <&scmi_clk 10>;
> +            clock-names = "clkin0",
> +                          "clkin1",
> +                          "clkin2";
> +        };

One or two are enough. Drop the rest.

> 


Best regards,
Krzysztof

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

* Re: [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units
  2026-02-09  5:48 ` [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units Chuan Liu via B4 Relay
@ 2026-02-09 13:15   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-09 13:15 UTC (permalink / raw)
  To: chuan.liu, Neil Armstrong, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel

On 09/02/2026 06:48, Chuan Liu via B4 Relay wrote:
> +
> +  '#clock-cells':
> +    description:
> +      The clock controller of a module may contain one or more child clock(s).
> +      When a clock controller has multiple child clocks, an index is required
> +      to specify a particular clock within the clock controller.
> +    oneOf:
> +      - const: 0
> +        description: Single clock output, no specifier needed
> +      - const: 1
> +        description: Multiple clocks, index selects specific output
> +
> +  amlogic,clock-max-frequency:

No

> +    description: |
> +      Each clock's maximum output frequency is constrained during hardware
> +      design to ensure proper timing requirements for the clock network. If the
> +      clock frequency configured exceeds this design limit, it can lead to
> +      abnormal behavior in modules relying on that clock and may even cause
> +      cross-talk that affects other modules.
> +
> +      In the driver, this property is parsed, and interface functions from the
> +      CCF are called to enforce the clock's maximum frequency, preventing
> +      potential issues caused by excessive clock frequency configurations.
> +    $ref: /schemas/types.yaml#/definitions/uint32-array
> +
> +  amlogic,clock-init-regs:

NAK

...

> diff --git a/include/dt-bindings/clock/amlogic,a9-misc-ccu.h b/include/dt-bindings/clock/amlogic,a9-misc-ccu.h
> new file mode 100644
> index 000000000000..102aff8d68e4
> --- /dev/null
> +++ b/include/dt-bindings/clock/amlogic,a9-misc-ccu.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */

Use the same license as in binding doc.



Best regards,
Krzysztof

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

* Re: [PATCH 04/13] clk: amlogic: Add basic clock driver
  2026-02-09  5:48 ` [PATCH 04/13] clk: amlogic: Add basic clock driver Chuan Liu via B4 Relay
@ 2026-02-09 13:17   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-09 13:17 UTC (permalink / raw)
  To: chuan.liu, Neil Armstrong, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel

On 09/02/2026 06:48, Chuan Liu via B4 Relay wrote:
> From: Chuan Liu <chuan.liu@amlogic.com>
> 
> Implement core clock driver for Amlogic SoC platforms, supporting

So how did all existing Amlogic SoC platforms work so far without basic
clock driver? Really, how?

You are suppose to grow existing code, not add your completely new
"basic" driver just because you have it that way in downstream.

Best regards,
Krzysztof

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

* Re: [PATCH 05/13] clk: amlogic: Add composite clock driver
  2026-02-09  5:48 ` [PATCH 05/13] clk: amlogic: Add composite " Chuan Liu via B4 Relay
@ 2026-02-09 13:18   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-09 13:18 UTC (permalink / raw)
  To: chuan.liu, Neil Armstrong, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel

On 09/02/2026 06:48, Chuan Liu via B4 Relay wrote:
> From: Chuan Liu <chuan.liu@amlogic.com>
> 
> Implement clk_ops support for Amlogic composite clocks. Composite clocks
> are commonly used clock control units in Amlogic SoCs that integrate
> multiplexer, divider, and gate functionality into a single block.
> 
> Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
> ---
>  drivers/clk/amlogic/Makefile        |   1 +
>  drivers/clk/amlogic/clk-composite.c | 280 ++++++++++++++++++++++++++++++++++++
>  drivers/clk/amlogic/clk-composite.h |  20 +++
>  drivers/clk/amlogic/clk.c           |   7 +
>  drivers/clk/amlogic/clk.h           |   1 +

Why did you duplicate the directory? It's already there under name
meson. That's your vendor.

Best regards,
Krzysztof

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

* Re: [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units
  2026-02-09  5:48 ` [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Chuan Liu via B4 Relay
  2026-02-09 13:14   ` Krzysztof Kozlowski
@ 2026-02-09 13:18   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-09 13:18 UTC (permalink / raw)
  To: chuan.liu, Neil Armstrong, Michael Turquette, Stephen Boyd,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-amlogic, linux-clk, devicetree, linux-kernel

On 09/02/2026 06:48, Chuan Liu via B4 Relay wrote:
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    clk_dummy: clock-dummy {
> +        compatible = "fixed-clock";
> +        #clock-cells = <0>;
> +        clock-frequency = <0>;
> +        clock-output-names = "dummy";
> +        status = "disabled";

Eee, nope. Please review this internally so you won't be making such
trivial mistakes.

Best regards,
Krzysztof

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

* Re: [PATCH 08/13] clk: amlogic: Add PLL driver
  2026-02-09  5:48 ` [PATCH 08/13] clk: amlogic: Add PLL driver Chuan Liu via B4 Relay
@ 2026-02-09 15:37   ` kernel test robot
  2026-02-09 17:35   ` kernel test robot
  1 sibling, 0 replies; 23+ messages in thread
From: kernel test robot @ 2026-02-09 15:37 UTC (permalink / raw)
  To: Chuan Liu via B4 Relay, Neil Armstrong, Michael Turquette,
	Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: oe-kbuild-all, linux-amlogic, linux-clk, devicetree, linux-kernel,
	Chuan Liu

Hi Chuan,

kernel test robot noticed the following build errors:

[auto build test ERROR on 4d310797262f0ddf129e76c2aad2b950adaf1fda]

url:    https://github.com/intel-lab-lkp/linux/commits/Chuan-Liu-via-B4-Relay/dt-bindings-clock-Add-Amlogic-A9-standardized-model-clock-control-units/20260209-135334
base:   4d310797262f0ddf129e76c2aad2b950adaf1fda
patch link:    https://lore.kernel.org/r/20260209-a9_clock_driver-v1-8-a9198dc03d2a%40amlogic.com
patch subject: [PATCH 08/13] clk: amlogic: Add PLL driver
config: arm-randconfig-003-20260209 (https://download.01.org/0day-ci/archive/20260209/202602092354.9sJeoo5S-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260209/202602092354.9sJeoo5S-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602092354.9sJeoo5S-lkp@intel.com/

All errors (new ones prefixed by >>):

   arm-linux-gnueabi-ld: drivers/clk/amlogic/clk-pll.o: in function `aml_pll_available_rates_show':
>> drivers/clk/amlogic/clk-pll.c:554: undefined reference to `__aeabi_uldivmod'
>> arm-linux-gnueabi-ld: drivers/clk/amlogic/clk-pll.c:567: undefined reference to `__aeabi_uldivmod'
>> arm-linux-gnueabi-ld: drivers/clk/amlogic/clk-pll.c:554: undefined reference to `__aeabi_uldivmod'


vim +554 drivers/clk/amlogic/clk-pll.c

   536	
   537	static int aml_pll_get_best_rate(unsigned long rate, unsigned long step_rate,
   538					 u64 min_vco_rate, u64 max_vco_rate,
   539					 u8 od_max, enum round_type round,
   540					 unsigned long *out_rate)
   541	{
   542		int i;
   543		u64 vco_rate;
   544		unsigned long now_rate, best_rate = 0;
   545	
   546		for (i = 0; i <= od_max; i++) {
   547			vco_rate = rate << i;
   548			if (vco_rate < min_vco_rate)
   549				continue;
   550	
   551			if (vco_rate > max_vco_rate)
   552				break;
   553	
 > 554			if (vco_rate % step_rate == 0) {
   555				best_rate = rate;
   556	
   557				break;
   558			}
   559	
   560			if (round == ROUND_DOWN) {
   561				vco_rate = vco_rate - (vco_rate % step_rate);
   562				now_rate = vco_rate >> i;
   563				if ((rate - now_rate) < (rate - best_rate))
   564					best_rate = now_rate;
   565			} else {
   566				vco_rate = vco_rate + step_rate;
 > 567				vco_rate = vco_rate - (vco_rate % step_rate);
   568				now_rate = vco_rate >> i;
   569				if ((now_rate - rate) < (best_rate - rate))
   570					best_rate = now_rate;
   571			}
   572		}
   573	
   574		if (!best_rate)
   575			return -EINVAL;
   576	
   577		*out_rate = best_rate;
   578	
   579		return 0;
   580	}
   581	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH 08/13] clk: amlogic: Add PLL driver
  2026-02-09  5:48 ` [PATCH 08/13] clk: amlogic: Add PLL driver Chuan Liu via B4 Relay
  2026-02-09 15:37   ` kernel test robot
@ 2026-02-09 17:35   ` kernel test robot
  1 sibling, 0 replies; 23+ messages in thread
From: kernel test robot @ 2026-02-09 17:35 UTC (permalink / raw)
  To: Chuan Liu via B4 Relay, Neil Armstrong, Michael Turquette,
	Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: oe-kbuild-all, linux-amlogic, linux-clk, devicetree, linux-kernel,
	Chuan Liu

Hi Chuan,

kernel test robot noticed the following build errors:

[auto build test ERROR on 4d310797262f0ddf129e76c2aad2b950adaf1fda]

url:    https://github.com/intel-lab-lkp/linux/commits/Chuan-Liu-via-B4-Relay/dt-bindings-clock-Add-Amlogic-A9-standardized-model-clock-control-units/20260209-135334
base:   4d310797262f0ddf129e76c2aad2b950adaf1fda
patch link:    https://lore.kernel.org/r/20260209-a9_clock_driver-v1-8-a9198dc03d2a%40amlogic.com
patch subject: [PATCH 08/13] clk: amlogic: Add PLL driver
config: arm-randconfig-004-20260209 (https://download.01.org/0day-ci/archive/20260210/202602100115.ykL6A0lh-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 14.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260210/202602100115.ykL6A0lh-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602100115.ykL6A0lh-lkp@intel.com/

All errors (new ones prefixed by >>):

   arm-linux-gnueabi-ld: drivers/clk/amlogic/clk-pll.o: in function `aml_pll_get_rate_range':
>> drivers/clk/amlogic/clk-pll.c:554:(.text+0x700): undefined reference to `__aeabi_uldivmod'
>> arm-linux-gnueabi-ld: drivers/clk/amlogic/clk-pll.c:567:(.text+0x728): undefined reference to `__aeabi_uldivmod'
   arm-linux-gnueabi-ld: drivers/clk/amlogic/clk-pll.c:554:(.text+0x814): undefined reference to `__aeabi_uldivmod'


vim +554 drivers/clk/amlogic/clk-pll.c

   536	
   537	static int aml_pll_get_best_rate(unsigned long rate, unsigned long step_rate,
   538					 u64 min_vco_rate, u64 max_vco_rate,
   539					 u8 od_max, enum round_type round,
   540					 unsigned long *out_rate)
   541	{
   542		int i;
   543		u64 vco_rate;
   544		unsigned long now_rate, best_rate = 0;
   545	
   546		for (i = 0; i <= od_max; i++) {
   547			vco_rate = rate << i;
   548			if (vco_rate < min_vco_rate)
   549				continue;
   550	
   551			if (vco_rate > max_vco_rate)
   552				break;
   553	
 > 554			if (vco_rate % step_rate == 0) {
   555				best_rate = rate;
   556	
   557				break;
   558			}
   559	
   560			if (round == ROUND_DOWN) {
   561				vco_rate = vco_rate - (vco_rate % step_rate);
   562				now_rate = vco_rate >> i;
   563				if ((rate - now_rate) < (rate - best_rate))
   564					best_rate = now_rate;
   565			} else {
   566				vco_rate = vco_rate + step_rate;
 > 567				vco_rate = vco_rate - (vco_rate % step_rate);
   568				now_rate = vco_rate >> i;
   569				if ((now_rate - rate) < (best_rate - rate))
   570					best_rate = now_rate;
   571			}
   572		}
   573	
   574		if (!best_rate)
   575			return -EINVAL;
   576	
   577		*out_rate = best_rate;
   578	
   579		return 0;
   580	}
   581	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH 06/13] clk: amlogic: Add noglitch clock driver
  2026-02-09  5:48 ` [PATCH 06/13] clk: amlogic: Add noglitch " Chuan Liu via B4 Relay
@ 2026-02-09 21:51   ` Martin Blumenstingl
  0 siblings, 0 replies; 23+ messages in thread
From: Martin Blumenstingl @ 2026-02-09 21:51 UTC (permalink / raw)
  To: chuan.liu
  Cc: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-amlogic, linux-clk,
	devicetree, linux-kernel

Hi Chuan Liu,

On Mon, Feb 9, 2026 at 6:49 AM Chuan Liu via B4 Relay
<devnull+chuan.liu.amlogic.com@kernel.org> wrote:
[...]
> + * To prevent glitches from propagating to clk_out and affecting the normal
> + * operation of glitch-sensitive modules, the no-glitch clock must be configured
> + * following the specified sequence:
> + *   - When the clock gate is disabled: configure it as a normal composite clock
> + *     (any glitches generated will be blocked by the gate and will not
> + *     propagate to clk_out).
This part is easy and makes sense.

> + *   - When the clock gate is enabled: configure it according to the following
> + *     sequence to suppress glitches:
> + *       - Configure and enable the idle composite clock path of the
> + *         noglitch_mux with the target frequency/parent clock.
> + *       - Switch the noglitch_mux to the channel prepared in the previous step.
> + *       - Disable the clock of the original noglitch_mux channel.
> + */
From a previous discussion it seems that in reality things need to be
handled more carefully as you previously mentioned that
CLK_SET_RATE_GATE is not good enough (the description above is what
CLK_SET_RATE_GATE already achieves).
For the more careful handling Jerome suggested using the clock
protection logic: [0]
You wanted to try it out at some point: [1]
Is the verdict that Jerome's suggestion did not work? Can you please
share some details as to why it doesn't work.


Best regards,
Martin


[0] https://lore.kernel.org/linux-amlogic/1j1pnp5sg7.fsf@starbuckisacylon.baylibre.com/
[1] https://lore.kernel.org/linux-amlogic/1639bb9d-9cb7-409f-bbf8-bfe4a5d1b8bc@amlogic.com/

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

* Re: [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support
  2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
                   ` (12 preceding siblings ...)
  2026-02-09  5:48 ` [PATCH 13/13] clk: amlogic: Add support for building as combined kernel module Chuan Liu via B4 Relay
@ 2026-02-11  8:34 ` Jerome Brunet
  13 siblings, 0 replies; 23+ messages in thread
From: Jerome Brunet @ 2026-02-11  8:34 UTC (permalink / raw)
  To: Chuan Liu via B4 Relay
  Cc: Neil Armstrong, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, chuan.liu, linux-amlogic,
	linux-clk, devicetree, linux-kernel

On lun. 09 févr. 2026 at 13:48, Chuan Liu via B4 Relay <devnull+chuan.liu.amlogic.com@kernel.org> wrote:

> This patch adds driver support for Phase-Locked Loop (PLL) controllers
> and Clock Control Units (CCUs) in A9 SoC family.
>
> In the A9 SoC architecture, PLLs and clock control units are implemented
> as standardized hardware instances to reduce unnecessary differentiation
> across individual units.
>
> All A9 PLLs and CCUs are exposed as device tree nodes, providing an
> accurate representation of the SoC's internal clock hardware structure.
>
> These drivers are designed for reuse in subsequent SoC generations,
> guaranteeing code inheritance and maximizing reusability.
>
> Makefile rules compile A9 PLL and CCU drivers into clk-amlogic.o (see
> drivers/clk/amlogic/Makefile), simplifying deployment and enhancing load
> efficiency (single insmod for kernel module).
>
> Since the foundational A9 DTS hasn't been upstreamed yet, I'm temporarily
> pushing the PLL/CCU DTS files to github for driver comprehension [1].
> These patches will be included in a later release after the base A9 DTS
> is merged.

Creating a new vendor directory and re-inventing supported driver
without an explanation, coding style, build problems ... the list goes
on. You've outdone yourself !

So NACK.

Chuan has been warned numerous times about his submission to the kernel
mailing lists, without a noticeable effect. I can only suggest other
reviewers do not waste their time reviewing this.

>
> [1] https://github.com/torvalds/linux/commit/d6a82e4cce675fa5146c5f638c2a926c1c8cb1d9
>
> Signed-off-by: Chuan Liu <chuan.liu@amlogic.com>
> ---
> Chuan Liu (13):
>       dt-bindings: clock: Add Amlogic A9 standardized model clock control units
>       dt-bindings: clock: Add Amlogic A9 PLL controllers
>       dt-bindings: clock: Add Amlogic A9 misc clock control units
>       clk: amlogic: Add basic clock driver
>       clk: amlogic: Add composite clock driver
>       clk: amlogic: Add noglitch clock driver
>       clk: amlogic: Add duandiv clock driver
>       clk: amlogic: Add PLL driver
>       clk: amlogic: Add DT-based clock registration functions
>       clk: amlogic: Add A9 standardized model clock control units driver
>       clk: amlogic: Add A9 PLL controllers driver
>       clk: amlogic: Add A9 misc clock control units driver
>       clk: amlogic: Add support for building as combined kernel module
>
>  .../bindings/clock/amlogic,a9-misc-ccu.yaml        | 523 +++++++++++
>  .../bindings/clock/amlogic,a9-model-ccu.yaml       | 435 +++++++++
>  .../devicetree/bindings/clock/amlogic,a9-pll.yaml  | 134 +++
>  drivers/clk/Kconfig                                |   1 +
>  drivers/clk/Makefile                               |   1 +
>  drivers/clk/amlogic/Kconfig                        |  52 ++
>  drivers/clk/amlogic/Makefile                       |  23 +
>  drivers/clk/amlogic/a9-misc-ccu.c                  | 970 +++++++++++++++++++++
>  drivers/clk/amlogic/a9-model-ccu.c                 | 475 ++++++++++
>  drivers/clk/amlogic/a9-pll.c                       | 156 ++++
>  drivers/clk/amlogic/clk-basic.c                    | 219 +++++
>  drivers/clk/amlogic/clk-basic.h                    |  39 +
>  drivers/clk/amlogic/clk-composite.c                | 280 ++++++
>  drivers/clk/amlogic/clk-composite.h                |  20 +
>  drivers/clk/amlogic/clk-dualdiv.c                  | 365 ++++++++
>  drivers/clk/amlogic/clk-dualdiv.h                  |  27 +
>  drivers/clk/amlogic/clk-module.c                   |  42 +
>  drivers/clk/amlogic/clk-module.h                   |  53 ++
>  drivers/clk/amlogic/clk-noglitch.c                 | 584 +++++++++++++
>  drivers/clk/amlogic/clk-noglitch.h                 |  29 +
>  drivers/clk/amlogic/clk-pll.c                      | 701 +++++++++++++++
>  drivers/clk/amlogic/clk-pll.h                      |  43 +
>  drivers/clk/amlogic/clk.c                          | 464 ++++++++++
>  drivers/clk/amlogic/clk.h                          |  56 ++
>  include/dt-bindings/clock/amlogic,a9-misc-ccu.h    |  53 ++
>  25 files changed, 5745 insertions(+)
> ---
> base-commit: 4d310797262f0ddf129e76c2aad2b950adaf1fda
> change-id: 20260130-a9_clock_driver-ddd90357848c
>
> Best regards,

-- 
Jerome

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

end of thread, other threads:[~2026-02-11  8:34 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-09  5:48 [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Chuan Liu via B4 Relay
2026-02-09 13:14   ` Krzysztof Kozlowski
2026-02-09 13:18   ` Krzysztof Kozlowski
2026-02-09  5:48 ` [PATCH 02/13] dt-bindings: clock: Add Amlogic A9 PLL controllers Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units Chuan Liu via B4 Relay
2026-02-09 13:15   ` Krzysztof Kozlowski
2026-02-09  5:48 ` [PATCH 04/13] clk: amlogic: Add basic clock driver Chuan Liu via B4 Relay
2026-02-09 13:17   ` Krzysztof Kozlowski
2026-02-09  5:48 ` [PATCH 05/13] clk: amlogic: Add composite " Chuan Liu via B4 Relay
2026-02-09 13:18   ` Krzysztof Kozlowski
2026-02-09  5:48 ` [PATCH 06/13] clk: amlogic: Add noglitch " Chuan Liu via B4 Relay
2026-02-09 21:51   ` Martin Blumenstingl
2026-02-09  5:48 ` [PATCH 07/13] clk: amlogic: Add duandiv " Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 08/13] clk: amlogic: Add PLL driver Chuan Liu via B4 Relay
2026-02-09 15:37   ` kernel test robot
2026-02-09 17:35   ` kernel test robot
2026-02-09  5:48 ` [PATCH 09/13] clk: amlogic: Add DT-based clock registration functions Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 10/13] clk: amlogic: Add A9 standardized model clock control units driver Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 11/13] clk: amlogic: Add A9 PLL controllers driver Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 12/13] clk: amlogic: Add A9 misc clock control units driver Chuan Liu via B4 Relay
2026-02-09  5:48 ` [PATCH 13/13] clk: amlogic: Add support for building as combined kernel module Chuan Liu via B4 Relay
2026-02-11  8:34 ` [PATCH 00/13] clk: amlogic: Introduce A9 PLL and CCU driver support Jerome Brunet

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox