From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A87A32DB794; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; cv=none; b=ug6obrDxR2Yj9wtXMO2rgnlAsW/bfsJCTE7uyt1xODYWJv/NJKc2HDSzzRfybluDnNEKtus4KQp8sZoXQo1P8CwEpqyOdl0JKqm3jVHHkpI+Pqy2wAWrwOmo9PdBUgMLlVuM5vO0Uq2NBYDrZR/dcvZKrIH0xcbCRPqtEsRxgaE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; c=relaxed/simple; bh=yOeClwznsdl7CGdcam/Szj8V4if7Hp60mh/Ckyb0HAI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pe5rOgYvRjJUHTha3wZ/X5uZnnrTH7IKnIbmCCR9adOVYyYhC6t5Uh0Cp3yzqzKNLfhb1VfvGC+r+yw5tR9SNC86/pDgqt/2TNVwBvbY6IUUJnEcITP8GzLFQDhIhJ3UUQsq7cvkqg4Jie8NRAt4esxuEji+oeXrG4LtnZSMxb8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=N79vCWwc; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="N79vCWwc" Received: by smtp.kernel.org (Postfix) with ESMTPS id 56ED3C4AF09; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616146; bh=yOeClwznsdl7CGdcam/Szj8V4if7Hp60mh/Ckyb0HAI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=N79vCWwcEjqYOIfAW4B/wJi81JrguR8ZVpqiMAtqDhDxAnDI/CHrjQ53PR2r8XuQG jLBaR+X/xA+KBW4jb7B7t8Xsi8bkhW3d315T4fB8sDHvWOXzfpdmsVPUU1JeHK5EBR i5klKc/kaxOhBxKC+xy/yaEoLUyDit2q+qSWtIgiifYSvvmVt2L9h8hAWON5P3dhdB FnkwoFUB47QXdO95canP8gGDtn9lt2XxpBGel1lJkgUYzpC5BOXHPJOrgs65zfiLTs aKU6MfLQ3C08AoHkuT6Mx2yV323ZlQ9SM7XQ27L3/RVk+fCyDcAGSg1FCB5GdKkujm gzPjFOFaKQ4qQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3BAF0EF06E5; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:47 +0800 Subject: [PATCH 01/13] dt-bindings: clock: Add Amlogic A9 standardized model clock control units Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-1-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=16188; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=LGR7t5zw1CkYLkatuNOHLf45xN42LhF2iAUlxglSJEw=; b=P8RRTNzfxAdNQaYHVPOQYUJftNvVqKauNV9azhek1IyBjv3EqHVBJbZm81g5dx731tDFkmUBZ 0D0Bv7aLiQZCfrXokH658fUGnlSAAoysQjIbNmal+G5fyQwM/LVF+GC X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu Add dt-binding documentation for standardized model clock control units in A9 SoC family. Signed-off-by: Chuan Liu --- .../bindings/clock/amlogic,a9-model-ccu.yaml | 435 +++++++++++++++++= ++++ 1 file changed, 435 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-model-ccu.y= aml 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 + +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 transiti= ons. + - 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). Whe= n 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. I= f 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 the= ir + 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, composit= e_a + and composite_b share a common clock source, while in others t= hey + 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 =3D "fixed-clock"; + #clock-cells =3D <0>; + clock-frequency =3D <0>; + clock-output-names =3D "dummy"; + status =3D "disabled"; + }; + + apb { + #address-cells =3D <2>; + #size-cells =3D <2>; + clock-controller@350 { + compatible =3D "amlogic,a9-composite-ccu"; + reg =3D <0x0 0x350 0x0 0x4>; + #clock-cells =3D <0>; + amlogic,clock-max-frequency =3D <50000000>; + amlogic,reg-layout =3D <0x0 0 7>; + clock-output-names =3D "sar_adc"; + clocks =3D <&xtal_24m>, + <&scmi_clk 17>; + clock-names =3D "clkin0", "clkin1"; + }; + + clock-controller@290 { + compatible =3D "amlogic,a9-composite-ccu-mult"; + reg =3D <0x0 0x290 0x0 0x8>; + #clock-cells =3D <1>; + amlogic,clock-max-frequency =3D <250000000>, + <250000000>, + <1200000000>; + amlogic,reg-layout =3D <0x0 0 7>, + <0x0 16 7>, + <0x4 0 7>; + clock-output-names =3D "sd_emmc_a", + "sd_emmc_b", + "sd_emmc_c"; + clocks =3D <&xtal_24m>, + <&scmi_clk 6>, + <&scmi_clk 10>; + clock-names =3D "clkin0", + "clkin1", + "clkin2"; + }; + + clock-controller@378 { + compatible =3D "amlogic,a9-composite-ccu-mult"; + reg =3D <0x0 0x378 0x0 0x4>; + #clock-cells =3D <1>; + amlogic,clock-max-frequency =3D <500000000>, + <667000000>; + amlogic,reg-layout =3D <0x0 0 7>, + <0x0 16 7>; + clock-output-names =3D "dptx_apb2", + "dptx_aud"; + clocks =3D <&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 =3D "clkin0", + "clkin1", + "clkin2", + "clkin3", + "clkin4", + "clkin5", + "clkin6", + "clkin7", + "bclkin0", + "bclkin1", + "bclkin2", + "bclkin3"; + }; + + clock-controller@420 { + compatible =3D "amlogic,a9-noglitch-ccu"; + reg =3D <0x0 0x420 0x0 0x4>; + #clock-cells =3D <0>; + amlogic,clock-max-frequency =3D <800000000>; + clock-output-names =3D "dspa"; + clocks =3D <&xtal_24m>, + <&scmi_clk 8>, + <&scmi_clk 10>; + clock-names =3D "clkin0", + "clkin1", + "clkin2"; + }; + + clock-controller@400 { + compatible =3D "amlogic,a9-noglitch-ccu-mult"; + reg =3D <0x0 0x400 0x0 0x8>; + #clock-cells =3D <1>; + /* + * If only one maximum frequency is specified, it is shared by= all + * clocks under the current device node. + */ + amlogic,clock-max-frequency =3D <1000000000>; + amlogic,reg-layout =3D <0x0>, + <0x4>; + clock-output-names =3D "mali", + "mali_stack"; + clocks =3D <&xtal_24m>, + <&scmi_clk 23>, + <&scmi_clk 6>; + clock-names =3D "clkin0", + "clkin1", + "clkin2"; + }; + + clock-sysbus@230 { + compatible =3D "amlogic,a9-sysbus-ccu"; + reg =3D <0x0 0x230 0x0 0x10>; + #clock-cells =3D <1>; + amlogic,reg-layout =3D <0 0>, + <0 1>, + <0 3>; + clock-output-names =3D "sys_am_axi", + "sys_dos", + "sys_mipi_dsi"; + clocks =3D <&scmi_clk 17>; + }; + }; --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A98E92FD7D3; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; cv=none; b=FqBJpY5YeTaSc/s1D95kpIzu8ZnYSI08+nRoYQL08YGYrbKMbkuE97NkWkGWYQsFJSHQ5abVD1QHy2UBKzWbg3bz/gObRu2d4+pt9sncAuEKhFt/CDfDQ/psp5skkzBJOKJrHRpaYZrOT0DneXiP8zFpbx0Nw6+hZjLBhI4Pvgw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; c=relaxed/simple; bh=snI8j+VY41wHmT+na+qjUXxcX28YmAHQsRCDu04k2ao=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FE6TG25pkqu/2wvgw5BagZSfJfvZCzaFbtaPh9a3FVFzulsHoiN5eK77eby3uNijyPfTAJZ0nIwYeQ7uT7Ik7KwWgRaiwisIwFBMK69xg5H/2xQRUcT2WuseqL7i5PIhAuyDLJ5UbHCXaUCyheafsy2gxjtXRX9h10kjd8sSobY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=REh4dKtp; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="REh4dKtp" Received: by smtp.kernel.org (Postfix) with ESMTPS id 60F97C2BCB0; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616146; bh=snI8j+VY41wHmT+na+qjUXxcX28YmAHQsRCDu04k2ao=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=REh4dKtpO4SQsgRXh+dIHbn/YP1W+XlZjubxD//vuWojHfhTkxX+9PFsDUrmEmQZF PIC9gQUzZKVTf1utw7v8qWhyXXdRHC5QKTk2qBZv0TExqZo7IF88opPTqlR36f4aCl D8z6msu7mo9s8SSe6FhEHBzWoFVpaeBEfyR9W48ASE2aQyj3SRAqFxtR8WAXr4YVt1 CHyr3OcIqMcOGmENHjoqHGHXZVr0N717z1uX5W3U1p6hK8YtT2fF7cjDHrltSznAg1 a1IVzWsq/guMq/jhvqgx35ZjSXesF9vEqg7FNogYCggg1kRhCwCdt5X0wjR5XgXR17 RfVH1dI8EMD6A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 53F9FEF070B; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:48 +0800 Subject: [PATCH 02/13] dt-bindings: clock: Add Amlogic A9 PLL controllers Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-2-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=5717; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=LyZ6UBT9Q6k7Fn7bjq6bnz37qzqmph54nv7OuWBpCck=; b=E1k8Q3OfqeBhGrzZfdznUd4MgKjLxFwgQJabHnnj2VIppvftGQ3dgu/guCoIoj6/G7cr+J5Dh 7+ocHR9fbibAqs4W++QyVU3wQHBA/zfIMByCSAL2MooWJS8sllZozlK X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu Add dt-binding documentation for PLL controllers used in A9 SoC family. Signed-off-by: Chuan Liu --- .../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 + +description: | + PLLs generate high-frequency clocks by frequency multiplication, feeding= them + into the clock tree where they can be configured as operational clocks f= or + 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. I= f 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 regis= ters + before becoming operational. This initialization configures internal= clock + drive characteristics, divider parameters, and PLL internal circuitr= y. + $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 =3D <2>; + #size-cells =3D <2>; + clock-mclk_pll@8330 { + compatible =3D "amlogic,a9-int-pll"; + reg =3D <0x0 0x8330 0x0 0xc>; + #clock-cells =3D <0>; + amlogic,clock-init-regs =3D <0x4 0x00402000 0>, + <0x8 0x60000100 0>; + amlogic,clock-max-frequency =3D <2800000000>; + clock-output-names =3D "mclk_pll"; + clocks =3D <&xtal_24m>; + }; + + clock-gp0_pll@8200 { + compatible =3D "amlogic,a9-frac-pll"; + reg =3D <0x0 0x8200 0x0 0x10>; + #clock-cells =3D <0>; + amlogic,clock-init-regs =3D <0x0 0x08010000 0>, + <0x4 0x11480000 0>, + <0x8 0x1219b010 0>, + <0xc 0x00008010 0>; + amlogic,clock-max-frequency =3D <2800000000>; + clock-output-names =3D "gp0_pll"; + clocks =3D <&xtal_24m>; + }; + + clock-hifi_pll@8280 { + compatible =3D "amlogic,a9-frac-step-pll"; + reg =3D <0x0 0x8280 0x0 0x10>; + #clock-cells =3D <0>; + amlogic,clock-init-regs =3D <0x0 0x08010000 0>, + <0x4 0x11480000 0>, + <0x8 0x1219b010 0>, + <0xc 0x00008010 0>; + amlogic,clock-max-frequency =3D <2800000000>; + clock-output-names =3D "hifi_pll"; + clocks =3D <&xtal_24m>; + }; + }; --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BB74A2FE07D; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; cv=none; b=T5VDhg7N+UaOZx7VlI+xk384nnSksKpUJYmxfQ+jx/Yl+iGKcM4rUqk551RIYPfaa7232juUggjvWATysb+rDJXrWsTBUsxTblrucVCJQGQWepz4EDmpfIDbCX6VlKUg4O8wrY//38cVVHtW5PBGVoP/5pJpTF7rSl6cze6JWh4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; c=relaxed/simple; bh=5esYusbyLxWjj8goWtgBSoRxDiOqg8OOeevMtpYWJis=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DP1vnCuPM7eBFzgcmrk3rDgddIvlZpSlPlO+5IOqpWvj5/gsMMmdSSRqdO8mL2Jx23XfF6YuShF9+zTfAO6mf/OX/+p+NHdsZHsct6wXmDXquf26G0JnvD9siq+MLeeOnHOlP5V7uKMa7PJdHMkAC6/pxbgc/GHccAUdWNeWV58= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=c24DetAn; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="c24DetAn" Received: by smtp.kernel.org (Postfix) with ESMTPS id 7E987C2BCB7; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616146; bh=5esYusbyLxWjj8goWtgBSoRxDiOqg8OOeevMtpYWJis=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=c24DetAngfmfCpgS7os9ijGtEEURSzFqwfvz5BjNqsuHCLPViNIb8h+AiZfzVZaP0 iajKNBMe+eTemtmfx5UmlIRY41bsYoqpupzxyR2yg40Opql/p6PGm6kNlPidEfbX7O 1ncC2Fh0TBs9W6QoAc6BZlgRnJnjrs6myB8zQDZPr/lVWb86JxZBOA41AuGY2r7ZX2 qYAaoQQOKI5Czd1nWIegOeuVVQn1gKCTfGDVEYbKFy8R3uFTYOTS0REDZPnmw7XiPa t+3yOiKKNCNTj4JlZ/DXxEcmMqeNqJ4Cv++Q+BNb5DHw7cp1EycHrsiSlQ3TmC/lxQ nGknVngLT0Ueg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6BE7CEF070E; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:49 +0800 Subject: [PATCH 03/13] dt-bindings: clock: Add Amlogic A9 misc clock control units Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-3-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=20139; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=7QE3RPkma3R9c3HdHltjAQG+gp4wGavYW1MtDbZrjh8=; b=jiL5zhA5poL2RgVqOpHMUn/gllWBLd8I905vvD+b/w0mlefG8/1pxAkWz6QgMtD28xKJgTgHv RlCfGtQ3nSyALhxxjDPWV2ITEHMS7uQNEKgBjBCOXGskv5mOyIGZr7P X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu Add dt-binding documentation for various miscellaneous peripheral clock control units in A9 SoC family. Signed-off-by: Chuan Liu --- .../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.ya= ml 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 + +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 ext= ernal + camera sensor + - items: + - const: amlogic,a9-dualdivmux-ccu + description: Clock control unit using dual divider channel togglin= g 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 requi= red + 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. I= f 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 regis= ters + before becoming operational. This initialization configures internal= clock + drive characteristics, divider parameters, and PLL internal circuitr= y. + $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 =3D <2>; + #size-cells =3D <2>; + clock-sc@370 { + compatible =3D "amlogic,a9-sc-ccu"; + reg =3D <0x0 0x370 0x0 0x4>; + #clock-cells =3D <1>; + clock-output-names =3D "sc_pre", + "sc"; + clocks =3D <&scmi_clk 6>, + <&scmi_clk 9>, + <&scmi_clk 14>, + <&xtal_24m>; + clock-names =3D "clkin0", + "clkin1", + "clkin2", + "clkin3"; + }; + + clock-ts@3a0 { + compatible =3D "amlogic,a9-ts-ccu"; + reg =3D <0x0 0x3a0 0x0 0x4>; + #clock-cells =3D <1>; + clock-output-names =3D "ts_div", + "ts"; + clocks =3D <&xtal_24m>; + }; + + clock-eth@3a4 { + compatible =3D "amlogic,a9-eth-ccu"; + reg =3D <0x0 0x3a4 0x0 0x4>; + #clock-cells =3D <1>; + clock-output-names =3D "eth_125m", + "eth_rmii"; + clocks =3D <&clk_eth_125m_div>, + <&scmi_clk 6>; + clock-names =3D "clk125m", + "clkin0"; + }; + + clock-mclk@833c { + compatible =3D "amlogic,a9-mclk-ccu"; + reg =3D <0x0 0x833c 0x0 0x8>; + #clock-cells =3D <1>; + amlogic,clock-init-regs =3D <0x0 0x04000400 0>, + <0x4 0x96169616 0>; + clock-output-names =3D "mclk0_pre_div", + "mclk0_sel", + "mclk0_div", + "mclk0", + "mclk1_pre_div", + "mclk1_sel", + "mclk1_div", + "mclk1"; + clocks =3D <&mclk_pll>, + <&xtal_24m>, + <&scmi_clk 4>; + clock-names =3D "divin", + "clkin1", + "clkin2"; + }; + }; + + aobus { + #address-cells =3D <2>; + #size-cells =3D <2>; + clkc_ao_cecb: clock-ao_cecb@38 { + compatible =3D "amlogic,a9-dualdivmux-ccu"; + reg =3D <0x0 0x38 0x0 0x8>; + #clock-cells =3D <1>; + + clock-output-names =3D "ao_cecb_dualdiv", + "ao_cecb"; + clocks =3D <&xtal>, + <&clkc_ao_cecb 0>, + <&clkc_rtc 1>; + clock-names =3D "divin", + "mux0", + "mux1"; + }; + + clkc_rtc: clock-rtc@8014c { + compatible =3D "amlogic,a9-dualdivmux-ccu"; + reg =3D <0x0 0x8014c 0x0 0x8>; + #clock-cells =3D <1>; + clock-output-names =3D "rtc_dualdiv", + "rtc"; + clocks =3D <&xtal>, + <&xtal>, + <&clkc_rtc 0>; + clock-names =3D "divin", + "mux0", + "mux1"; + }; + }; diff --git a/include/dt-bindings/clock/amlogic,a9-misc-ccu.h b/include/dt-b= indings/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 */ --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C4A262FE58C; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; cv=none; b=upnQ9Eg1yRMI+vuWqW3OJMDEncFPyEw2zJKO5yqncp3l5bFNAYZU8Qc4Cq/zjhVbebPJAMyvQ6D8uGD/tuyi6ekCq/It/xUS/LxrLxax5r+6lIARZOSGLji3L+5ay37kAM8zu5YN/3KTjixpDmBUYCSQEMQXj7cOZb+CYvC3/7w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616146; c=relaxed/simple; bh=Hvn6SKSwPDWdPvPQKGOu4bbXweaL0W0SayKB/5WUpmg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=BGxqJhnbhxkXMAjeh6xWMFUBnT8KIWf+0gj3OWSn8F/XFTU1VKw6Yrs6qeBYE1pn+KXRwatDzU3NHfgiRxqBSln4NPlyk5ESEIzYTYUMvMUBVrDqTfzS87X4yIoFLW+x2Jk+IqsnEObO7mjyBPkXZIO+owgSv40Fx9Z8AWYBlaA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=MXFNZdpp; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="MXFNZdpp" Received: by smtp.kernel.org (Postfix) with ESMTPS id 9F480C2BCB2; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616146; bh=Hvn6SKSwPDWdPvPQKGOu4bbXweaL0W0SayKB/5WUpmg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=MXFNZdppUl80U3M5Exi6H2scGIfu7aajaPRPa3rZrhXJQgpY64DstBz4+KNXhHg9i 4cOym9KrhMJQCdGu+MfBB5WJm61u6h19PDp/fAxnqsBuMdXKc2uk6d8hqp+7eXoIpn UUBll1BQyQ6Y07NKhJPkS0SQXzRZ6t40RtPXaGB/SBKwQcSAuieqNzwyqTcVkAdYSE OrJXP3G10QiYfqxFUj7HuLM3awjtjxV7ryJDotOmQk0U4EULbb8jFlvpVgO8OYJyi8 iqLrDbRS+CEim7AY26o8j25r8gOfxRIUkXb5ogCtxr4nGu+GjVGtN6uwexYC+z+7NC 7ZT3uAAbLFT9g== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 91CA6EF070B; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:50 +0800 Subject: [PATCH 04/13] clk: amlogic: Add basic clock driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-4-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=15565; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=kmFP3lJrdEH0H25Aq+4LfHk+ypLv7d+G/FDfILS/pz8=; b=oEGZBOLzaZUMxMxvxbTFD+u0g2mgIFNwDgtYUeLFL1aan+7dtG3j5qkat9xV9GazeSxHDhYzD hV+IiYX3B8YCP/0vV/Whsnnndu9xVtfAxrcDIf3IMEXA/Tv0ZBwZMQL X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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. =20 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) +=3D clk-xgene.o =20 # please keep this section sorted lexicographically by directory path name obj-y +=3D actions/ +obj-$(CONFIG_ARCH_MESON) +=3D amlogic/ obj-y +=3D analogbits/ obj-$(CONFIG_COMMON_CLK_AT91) +=3D at91/ obj-$(CONFIG_ARCH_ARTPEC) +=3D 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) +=3D clk-amlogic.o + +clk-amlogic-y +=3D clk.o +clk-amlogic-y +=3D clk-basic.o diff --git a/drivers/clk/amlogic/clk-basic.c b/drivers/clk/amlogic/clk-basi= c.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 +#include + +#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 th= e CCF. + */ + +static int aml_clk_gate_endisable(struct clk_hw *hw, int enable) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_gate_data *gate =3D clk->data; + int set =3D gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; + + set ^=3D 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 =3D to_aml_clk(hw); + struct aml_clk_gate_data *gate =3D clk->data; + unsigned int val; + + regmap_read(clk->map, gate->reg_offset, &val); + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + val ^=3D BIT(gate->bit_idx); + + val &=3D BIT(gate->bit_idx); + + return val ? 1 : 0; +} + +#ifdef CONFIG_DEBUG_FS +#include + +static void aml_clk_basic_debug_init(struct clk_hw *hw, struct dentry *den= try) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + + debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops); + if (clk->type =3D=3D 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 =3D { + .enable =3D aml_clk_gate_enable, + .disable =3D aml_clk_gate_disable, + .is_enabled =3D aml_clk_gate_is_enabled, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D to_aml_clk(hw); + struct aml_clk_divider_data *div =3D clk->data; + unsigned int val; + int ret; + + ret =3D regmap_read(clk->map, div->reg_offset, &val); + if (ret) + /* Gives a hint that something is wrong */ + return 0; + + val >>=3D div->shift; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_divider_data *div =3D clk->data; + unsigned int val; + int ret; + + /* if read only, just return current value */ + if (div->flags & CLK_DIVIDER_READ_ONLY) { + ret =3D regmap_read(clk->map, div->reg_offset, &val); + if (ret) + return ret; + + val >>=3D div->shift; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_divider_data *div =3D clk->data; + unsigned int val; + int ret; + + ret =3D divider_get_val(rate, parent_rate, div->table, div->width, + div->flags); + if (ret < 0) + return ret; + + val =3D (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 =3D { + .recalc_rate =3D aml_clk_div_recalc_rate, + .determine_rate =3D aml_clk_div_determine_rate, + .set_rate =3D aml_clk_div_set_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D { + .recalc_rate =3D aml_clk_div_recalc_rate, + .determine_rate =3D aml_clk_div_determine_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D to_aml_clk(hw); + struct aml_clk_mux_data *mux =3D clk->data; + unsigned int val; + int ret; + + ret =3D regmap_read(clk->map, mux->reg_offset, &val); + if (ret) + return ret; + + val >>=3D mux->shift; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_mux_data *mux =3D clk->data; + unsigned int val =3D 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 =3D to_aml_clk(hw); + struct aml_clk_mux_data *mux =3D clk->data; + + return clk_mux_determine_rate_flags(hw, req, mux->flags); +} + +const struct clk_ops aml_clk_mux_ops =3D { + .get_parent =3D aml_clk_mux_get_parent, + .set_parent =3D aml_clk_mux_set_parent, + .determine_rate =3D aml_clk_mux_determine_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D { + .get_parent =3D aml_clk_mux_get_parent, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); diff --git a/drivers/clk/amlogic/clk-basic.h b/drivers/clk/amlogic/clk-basi= c.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 + +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 + +#ifdef CONFIG_DEBUG_FS +#include + +#include "clk.h" +#include "clk-basic.h" + +static const struct { + unsigned int type; + const char *name; +} clk_types[] =3D { +#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 =3D s->private; + struct aml_clk *clk =3D to_aml_clk(hw); + int i; + + for (i =3D 0; i < ARRAY_SIZE(clk_types); i++) { + if (clk_types[i].type =3D=3D 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 =3D { + .owner =3D THIS_MODULE, + .open =3D aml_clk_type_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D 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 =3D s->private; + struct clk_hw *phw =3D clk_hw_get_parent(hw); + struct aml_clk *clk =3D 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 =3D 0; + const struct clk_div_table *div_table =3D NULL; + + if (!phw) { + pr_err("%s: Can't get parent\n", clk_hw_get_name(hw)); + + return -ENOENT; + } + + prate =3D clk_hw_get_rate(phw); + clk_hw_get_rate_range(hw, &range_min, &range_max); + max =3D prate; + if (clk->type =3D=3D AML_CLKTYPE_DIV) { + struct aml_clk_divider_data *div =3D clk->data; + + if (div->flags & CLK_DIVIDER_READ_ONLY) { + min =3D prate; + goto out_printf; + } else { + div_val =3D (1 << div->width) - 1; + div_table =3D div->table; + div_flags =3D div->flags; + div_width =3D div->width; + } + } else { + pr_err("%s: Unsupported clock type\n", clk_hw_get_name(hw)); + return -EINVAL; + } + + min =3D 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 =3D range_min; + + if (range_max < max) + max =3D range_max; + + min =3D divider_round_rate(hw, min, &prate, NULL, div_width, 0); + max =3D 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 fi= le *file) +{ + return single_open(file, aml_clk_div_available_rates_show, + inode->i_private); +} + +const struct file_operations aml_clk_div_available_rates_fops =3D { + .owner =3D THIS_MODULE, + .open =3D aml_clk_div_available_rates_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D 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 "); +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 +#include +#include + +enum aml_clk_type { + AML_CLKTYPE_MUX =3D 1, + AML_CLKTYPE_DIV =3D 2, + AML_CLKTYPE_GATE =3D 3, +}; + +struct aml_clk { + struct clk_hw hw; + enum aml_clk_type type; + struct regmap *map; + void *data; +}; + +#ifdef CONFIG_DEBUG_FS +#include + +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 */ --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E0294311967; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=jD1QFA+1pKfIFasEPOJ3Nk9lkphjqSGB/yazJAFirYXzmnOKzRHqBAP+vCE9zBwBRZp/izuT3AdEtqUIEB7mHsIbUqHA35MfnCkJ6aR2SGoRSMo88rxVmRlg2ADF5X2Oi87AYLXQaN4OE3d8vrscucobfjdrPw+GDdmqChx8ulA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=cazKsEPbGzq2zpjP2uQ9/zaf5UiPV4wveeJOBPeV8iE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=iymMRcDupf1ezffpbk7WYv4DWCrqteZGzmR/VkwNyyFMgZgREasu0WyFvxSLKJQFy5+UHBPE608g7lYMSx0fUBWy2pX0WhCewCVG+NGmF5zkbkf/opWj4ayuEFlrkhUMlvsltYr8VB1VeWW9+208sbqJAsK/VRkK8GZ34oz85Rc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lNY9tm45; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lNY9tm45" Received: by smtp.kernel.org (Postfix) with ESMTPS id B54CCC19422; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616146; bh=cazKsEPbGzq2zpjP2uQ9/zaf5UiPV4wveeJOBPeV8iE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=lNY9tm45lmhVclNkhpd8EaygbnPRsKdKUwjhG8Uf62ZclYbXvlkViSm4YMvCoLEn6 TXsXNRAK8dicE6c3Im9L7QjHycLgBb2GRFBOhlMBJsQEl+e/rZIf3dDTXjMwpzM9FH mDMzIvUxM2451sLQTnF2fIYpA3EtqvRYVSjXKmJQcX9RPE50xLhM3gY5wDSFYCkukb 1LWOU9cvS0YgmZMOW81PRTUzXjOeABCOBZZMuWqRm9HjmeJXdING62cK9RPhA2gwzk 3uk03th20BH0mvex89NQ4uif3ns770C8i7XtEqgjBpzWkLYanPe+hNIFzgtsT5bIDH /cwDBVfA+NRWw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id AC750EF06E5; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:51 +0800 Subject: [PATCH 05/13] clk: amlogic: Add composite clock driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-5-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=11745; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=TWWc+aiQqxihQg4vOjeQkVQxeCur6RMBVynIc8iopYo=; b=LNPPiCONB2hgxdPaHHpq2DgNzZgblos66T+EqeLDCwcYk7iU89TnQ/okG4bo8eeSjsgxjf7NO nA0Jw0+vCd3AIY1zLhReQmvd6osJODjnvPgBLzTXkXgy3v6MJzAJEkr X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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) +=3D clk-amlogic.o =20 clk-amlogic-y +=3D clk.o clk-amlogic-y +=3D clk-basic.o +clk-amlogic-y +=3D 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 +#include + +#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 =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + unsigned int val; + int ret; + + ret =3D regmap_read(clk->map, composite->reg_offset, &val); + if (ret) + return ret; + + val >>=3D CLK_COMPOSITE_MUX_SHIFT; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + unsigned int val =3D clk_mux_index_to_val(composite->table, 0, index); + int mux_shift =3D 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 =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + unsigned int val; + int ret; + + ret =3D regmap_read(clk->map, composite->reg_offset, &val); + if (ret) + /* Gives a hint that something is wrong */ + return 0; + + val >>=3D composite->bit_offset + CLK_COMPOSITE_DIV_SHIFT; + val &=3D 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 =3D to_aml_clk(rate_hw); + struct aml_clk_composite_data *composite =3D clk->data; + + req->best_parent_hw =3D parent_hw; + req->best_parent_rate =3D 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 =3D ULONG_MAX; + unsigned long best_rate =3D 0; + int i, ret; + + req->best_parent_hw =3D NULL; + + parent =3D clk_hw_get_parent(hw); + clk_hw_forward_rate_request(hw, req, parent, &tmp_req, req->rate); + ret =3D 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 =3D=3D tmp_req.rate || + (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT)) { + req->rate =3D tmp_req.rate; + req->best_parent_hw =3D tmp_req.best_parent_hw; + req->best_parent_rate =3D tmp_req.best_parent_rate; + + return 0; + } + + for (i =3D 0; i < clk_hw_get_num_parents(hw); i++) { + parent =3D clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + clk_hw_forward_rate_request(hw, req, parent, &tmp_req, + req->rate); + ret =3D aml_clk_composite_determine_rate_for_parent(hw, &tmp_req, + parent); + if (ret) + continue; + + if (req->rate >=3D tmp_req.rate) + rate_diff =3D req->rate - tmp_req.rate; + else + rate_diff =3D tmp_req.rate - req->rate; + + if (!rate_diff || !req->best_parent_hw || + best_rate_diff > rate_diff) { + req->best_parent_hw =3D parent; + req->best_parent_rate =3D tmp_req.best_parent_rate; + best_rate_diff =3D rate_diff; + best_rate =3D tmp_req.rate; + } + + if (!rate_diff) + return 0; + } + + req->rate =3D best_rate; + return 0; +} + +static int aml_clk_composite_set_rate(struct clk_hw *hw, unsigned long rat= e, + unsigned long parent_rate) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + unsigned int val; + int ret; + int div_shift =3D composite->bit_offset + CLK_COMPOSITE_DIV_SHIFT; + + ret =3D divider_get_val(rate, parent_rate, NULL, composite->div_width, 0); + if (ret < 0) + return ret; + + val =3D (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 =3D 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 =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + unsigned int val; + + regmap_read(clk->map, composite->reg_offset, &val); + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + u8 bit_idx =3D 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 =3D to_aml_clk(hw); + struct aml_clk_composite_data *composite =3D clk->data; + u8 bit_idx =3D composite->bit_offset + CLK_COMPOSITE_GATE_SHIFT; + + regmap_update_bits(clk->map, composite->reg_offset, + BIT(bit_idx), 0); +} + +#ifdef CONFIG_DEBUG_FS +#include + +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 =3D { + .get_parent =3D aml_clk_composite_get_parent, + .set_parent =3D aml_clk_composite_set_parent, + .determine_rate =3D aml_clk_composite_determine_rate, + .recalc_rate =3D aml_clk_composite_recalc_rate, + .set_rate =3D aml_clk_composite_set_rate, + .set_rate_and_parent =3D aml_clk_composite_set_rate_and_parent, + .enable =3D aml_clk_composite_enable, + .disable =3D aml_clk_composite_disable, + .is_enabled =3D aml_clk_composite_is_enabled, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 "); +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 + +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 @@ =20 #include "clk.h" #include "clk-basic.h" +#include "clk-composite.h" =20 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 }; =20 @@ -95,6 +97,11 @@ static int aml_clk_div_available_rates_show(struct seq_f= ile *s, void *data) div_flags =3D div->flags; div_width =3D div->width; } + } else if (clk->type =3D=3D AML_CLKTYPE_COMPOSITE) { + struct aml_clk_composite_data *composite =3D clk->data; + + div_val =3D (1 << composite->div_width) - 1; + div_width =3D 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 =3D 1, AML_CLKTYPE_DIV =3D 2, AML_CLKTYPE_GATE =3D 3, + AML_CLKTYPE_COMPOSITE =3D 4, }; =20 struct aml_clk { --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C616315D31; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=UWodv4l0EmcvMfOvJK1VRx6iVCM+Ca9pMxWhfsp4GqsfJiBgNMPWtkZ98qrOJfVU1doHigImXrU/nj1ShCwTMjqdh8NaoHvEBKaYzccdJ5suEfPvKlxvFSpUi33KDzcXoVkFSty6WjKvgSavVWNkQnFSWo/RzU9re7rZjOSGQvw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=e2cihnQveuTVgLjGD3IwfdOobSrsdpfwR1pkwTgP9g4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ioWIETzmOG6HsIPmu8Ngl+X3bq5FcTOkr0A4b09DK52Q8IMQSuoqLfhsSTPo40Sx9fEkpsiCKPy/NxH1V+vDCn+9iNupBuydXiln4LESV8x2K1zzWvFEhAiI0eIR+7YR4un5xdG7AXygltvN8QTPyjtECg30nnnQ6JDkPiSpwpc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ZqWy1a30; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ZqWy1a30" Received: by smtp.kernel.org (Postfix) with ESMTPS id DC36FC19424; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616146; bh=e2cihnQveuTVgLjGD3IwfdOobSrsdpfwR1pkwTgP9g4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ZqWy1a30ZziU6Qu+WqEyaFlfXzQCpSTZmxEyZ/V2lqptybBoah/x6+Tgpv4CwsKw8 LL9bCvdJlRwvatoFsSsVZC81DRhWlthef1lQUAaACQKc9z7rc5IgWc4pihJOAJo15G b+KKMhcbszf1E5KSOAuUcE09EinIpfue6wXy7+7eBU1UCg2hecev5OTcxI3ptCI2Ms QOgWAsnWnhNMAo9qlD84Q5FjHcoqq/HtZQs9Z/o1ZCcSJaKv+g+KTS78ylHGDPL2Tx sH1hot8n916uP5ttVBFdfLcEtKGVx0KgRmB1XlG2/4gM5rPLupwTUNoWa4JKLmVS2h y4HAXFxrniLMA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id C3CFBEF070D; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:52 +0800 Subject: [PATCH 06/13] clk: amlogic: Add noglitch clock driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-6-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=21782; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=s6jXfzSrDe9HlivCGNmzLi8+HeSP7kAfBpSJrL8xvzU=; b=N2LIWPf2DYfNglrm8VFeoMOvsdOpBiobXKAiLLEZ+D5tQegVgtwUiIF4pzrl6vM1wS8bBSl1V GFRD8WRXzpFBq9MD18g2yTlk0CQdOelNpQRMFWKtn1s6fFJfSUsuRyy X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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) +=3D clk-amlogic.o clk-amlogic-y +=3D clk.o clk-amlogic-y +=3D clk-basic.o clk-amlogic-y +=3D clk-composite.o +clk-amlogic-y +=3D clk-noglitch.o diff --git a/drivers/clk/amlogic/clk-noglitch.c b/drivers/clk/amlogic/clk-n= oglitch.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 +#include + +#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 gene= rated + * during mux switching. + * + * Its internal implementation consists of two Amlogic composite clocks an= d one + * noglitch_mux. The noglitch_mux filters out a certain number of clock cy= cles + * from both the original channel and the new channel, and then continuous= ly + * outputs the clock from the new channel. This prevents glitches caused b= y 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 conf= igured + * 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 follo= wing + * 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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + union aml_clk_noglitch_reg reg; + int ret; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + unsigned int val =3D clk_mux_index_to_val(noglitch->table, 0, index); + union aml_clk_noglitch_reg reg; + int ret; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D val; + reg.bits.gate0 =3D 1; + ret =3D 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 =3D 0; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + /* step3: Disable Channel 1 clock */ + reg.bits.gate1 =3D 0; + ret =3D 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 =3D val; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } + } else { /* mux0 */ + if (reg.bits.gate0) { + reg.bits.mux1 =3D val; + reg.bits.gate1 =3D 1; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + reg.bits.noglitch_mux =3D 1; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + reg.bits.gate0 =3D 0; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } else { + reg.bits.mux0 =3D val; + ret =3D 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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + union aml_clk_noglitch_reg reg; + int ret; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D parent_hw; + req->best_parent_rate =3D 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 =3D ULONG_MAX; + unsigned long best_rate =3D 0; + int i, ret; + + req->best_parent_hw =3D NULL; + + parent =3D clk_hw_get_parent(hw); + clk_hw_forward_rate_request(hw, req, parent, &tmp_req, req->rate); + ret =3D 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 =3D=3D tmp_req.rate || + (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT)) { + req->rate =3D tmp_req.rate; + req->best_parent_hw =3D tmp_req.best_parent_hw; + req->best_parent_rate =3D tmp_req.best_parent_rate; + + return 0; + } + + for (i =3D 0; i < clk_hw_get_num_parents(hw); i++) { + parent =3D clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + clk_hw_forward_rate_request(hw, req, parent, &tmp_req, + req->rate); + ret =3D aml_clk_noglitch_determine_rate_for_parent(hw, &tmp_req, + parent); + if (ret) + continue; + + if (req->rate >=3D tmp_req.rate) + rate_diff =3D req->rate - tmp_req.rate; + else + rate_diff =3D tmp_req.rate - req->rate; + + if (!rate_diff || !req->best_parent_hw || + best_rate_diff > rate_diff) { + req->best_parent_hw =3D parent; + req->best_parent_rate =3D tmp_req.best_parent_rate; + best_rate_diff =3D rate_diff; + best_rate =3D tmp_req.rate; + } + + if (!rate_diff) + return 0; + } + + req->rate =3D 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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + union aml_clk_noglitch_reg reg; + int ret, div; + + div =3D divider_get_val(rate, parent_rate, NULL, + CLK_NOGLITCH_DIV_WIDTH, 0); + if (div < 0) + return div; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D div; + reg.bits.gate0 =3D 1; + ret =3D 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 =3D 0; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + /* step3: Disable Channel 1 clock */ + reg.bits.gate1 =3D 0; + ret =3D 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 =3D div; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } + } else { /* mux0 */ + if (reg.bits.gate0) { + reg.bits.div1 =3D div; + reg.bits.gate1 =3D 1; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + reg.bits.noglitch_mux =3D 1; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + reg.bits.gate0 =3D 0; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } else { + reg.bits.div0 =3D div; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } + } + + regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + unsigned int parent_index =3D clk_mux_index_to_val(noglitch->table, 0, + index); + union aml_clk_noglitch_reg reg; + int ret, div; + + div =3D divider_get_val(rate, parent_rate, NULL, + CLK_NOGLITCH_DIV_WIDTH, 0); + if (div < 0) + return div; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D parent_index; + reg.bits.div0 =3D div; + reg.bits.gate0 =3D 1; + ret =3D 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 =3D 0; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + /* step3: Disable Channel 1 clock */ + reg.bits.gate1 =3D 0; + ret =3D 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 =3D parent_index; + reg.bits.div1 =3D div; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } + } else { /* mux0 */ + if (reg.bits.gate0) { + reg.bits.mux1 =3D parent_index; + reg.bits.div1 =3D div; + reg.bits.gate1 =3D 1; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + reg.bits.noglitch_mux =3D 1; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + + reg.bits.gate0 =3D 0; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } else { + reg.bits.mux0 =3D parent_index; + reg.bits.div0 =3D div; + ret =3D regmap_write(clk->map, noglitch->reg_offset, + reg.val); + if (ret) + return ret; + } + } + + regmap_read(clk->map, noglitch->reg_offset, ®.val); + + return 0; +} + +static int aml_clk_noglitch_is_enabled(struct clk_hw *hw) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + union aml_clk_noglitch_reg reg; + int ret; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + union aml_clk_noglitch_reg reg; + int ret; + + ret =3D regmap_read(clk->map, noglitch->reg_offset, ®.val); + if (ret) + return ret; + + if (reg.bits.noglitch_mux) /* mux1 */ + reg.bits.gate1 =3D 1; + else + reg.bits.gate0 =3D 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 =3D to_aml_clk(hw); + struct aml_clk_noglitch_data *noglitch =3D clk->data; + union aml_clk_noglitch_reg reg; + + if (regmap_read(clk->map, noglitch->reg_offset, ®.val)) + return; + + if (reg.bits.noglitch_mux) /* mux1 */ + reg.bits.gate1 =3D 0; + else + reg.bits.gate0 =3D 0; + + regmap_write(clk->map, noglitch->reg_offset, reg.val); +} + +#ifdef CONFIG_DEBUG_FS +#include + +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 =3D { + .get_parent =3D aml_clk_noglitch_get_parent, + .set_parent =3D aml_clk_noglitch_set_parent, + .determine_rate =3D aml_clk_noglitch_determine_rate, + .recalc_rate =3D aml_clk_noglitch_recalc_rate, + .set_rate =3D aml_clk_noglitch_set_rate, + .set_rate_and_parent =3D aml_clk_noglitch_set_rate_and_parent, + .enable =3D aml_clk_noglitch_enable, + .disable =3D aml_clk_noglitch_disable, + .is_enabled =3D aml_clk_noglitch_is_enabled, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); diff --git a/drivers/clk/amlogic/clk-noglitch.h b/drivers/clk/amlogic/clk-n= oglitch.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 + +/* + * NOTE: Currently, the divider of the AM no-glitch clock uses a fixed 7-b= it + * 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 def= ined + * 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" =20 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 }; =20 @@ -102,6 +104,9 @@ static int aml_clk_div_available_rates_show(struct seq_= file *s, void *data) =20 div_val =3D (1 << composite->div_width) - 1; div_width =3D composite->div_width; + } else if (clk->type =3D=3D AML_CLKTYPE_NOGLITCH) { + div_val =3D (1 << CLK_NOGLITCH_DIV_WIDTH) - 1; + div_width =3D 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 =3D 2, AML_CLKTYPE_GATE =3D 3, AML_CLKTYPE_COMPOSITE =3D 4, + AML_CLKTYPE_NOGLITCH =3D 5, }; =20 struct aml_clk { --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 22CEF318B96; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=Iv7INASTxMY7iQxiM8PEGcmZaYfMCXGkRG60t4r35o4TsFu/YP+UbgfTOdD/d58XVsnTw+cMG8Zr2mU279mkiBF4mRljUE6LARp7Gdh1X1j734ymIroUgJzUrxjW7CXJKQ5MfFRukruKb43Qbo6UIT1D16oWtC+iYMaN4JFFJBc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=WxRLnNceQ3wbnXmtcgHYv7mMpQB9lXHKbgRuGKvkBFE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SQ7AuO4WfNgmHL/O1KqWgO6sH1qtyRlO24AkmTgeVYYGz/gxi6iY4r88UNgAALKowzPW+zXqJF2PBn4ndBDV9NPQujN1ACpNrl0tIj0eCYPgP0/wXHvRMLM23WEz6i9Ouz7c+ILUGOXy3QnziS2aM92SPpYZ6NMaGEPk/yDrAa0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VBVaEPCg; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VBVaEPCg" Received: by smtp.kernel.org (Postfix) with ESMTPS id 04D32C2BCAF; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=WxRLnNceQ3wbnXmtcgHYv7mMpQB9lXHKbgRuGKvkBFE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=VBVaEPCgyHdjqYj2PuDB5KexwWq2sH2ScSNX+lBWXHB1V3VkYHKKeIWyq8i9htSdM tHuZwpfHOrSEiqNitdmiWuo16C9POjP0z1pi6CopobXQTe7cXnbhsKyLtKgOG8qEAf 80i99ISbODmYXRG2TDCNjKxXbk8if9gWL50MFNhBp21x+4ZHlMRcB5o6qZpj15CPfK in63Gl2wgkQ2SMMg7xNppBMo519cn/KpRBKmwFHlUp9FiUUa65A0MhTIoU0/Fj0Bxp VjLqB51ekIGQIMpwET5BS1cq67/C320dXE27PUWGYWZxD3pUJ97xe/ESA5VkDpdsUj haOpg0puuD+cw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id EDB98EF070B; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:53 +0800 Subject: [PATCH 07/13] clk: amlogic: Add duandiv clock driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-7-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=15230; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=lExIppsrGs3hG71peQNd4KEz/nuT8sZa6/ObaVooJnk=; b=B3IIxMGqKTCKBnUJMP9hnaQefxTiQhEaz/vXaE3Gja/L1P9mrB8tqWDw/nijoc8hQHiAgYaj6 93tMWtV6cioCtvGwL6ssxh4h6idEzcxNdZs4HaOwViWZeUPlsdLXdHI X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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) +=3D clk-amlogic.o clk-amlogic-y +=3D clk.o clk-amlogic-y +=3D clk-basic.o clk-amlogic-y +=3D clk-composite.o +clk-amlogic-y +=3D clk-dualdiv.o clk-amlogic-y +=3D clk-noglitch.o diff --git a/drivers/clk/amlogic/clk-dualdiv.c b/drivers/clk/amlogic/clk-du= aldiv.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 + +#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 |----> c= lk + * | +-->| div |--->| cnt |--->| | | | +------+ | o= ut + * | | +-----+ +-----+ |/ | | | + * | +------------------------------>| | | + * | |/ | + * | | + * +-----------------------------------------------------------+ + * + * 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 fo= llows: + * - 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 cha= nnel 1 + * after the cycles are output) + * - m1: Number of output cycles per burst on channel 0 (switches to cha= nnel 0 + * after the cycles are output) + * - div_mode: Division mode configuration: + * - div_mode =3D 0: Normal division mode: + * dualdiv_out =3D clk_in / n0; + * - div_mode =3D 1: Dualdiv (fractional) division mode: + * dualdiv_out =3D clk_in / ((n0 * m0 + n1 * m1) / (m0 + m1)); + * - force_clkout: force clock_out =3D clock_in + * - clkout_gate: clock output gate + * + * The fractional division principle of dualdiv is to alternately switch v= ia 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 wavefo= rm + * is illustrated below (n0 =3D 1, m0 =3D 1, n1 =3D 2, m1 =3D 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 =3D _get_div_mult(p->n0); + if (!p->div_mode) + return DIV_ROUND_CLOSEST(parent_rate, n0); + + n1 =3D _get_div_mult(p->n1); + m0 =3D _get_div_mult(p->m0); + m1 =3D _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 =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D 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, + ®.reg1.val); + if (reg.reg1.bits.force_clkout =3D=3D 1) + return parent_rate; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + parm.div_mode =3D reg.reg0.bits.div_mode; + parm.n0 =3D reg.reg0.bits.n0; + parm.n1 =3D reg.reg0.bits.n1; + parm.m0 =3D reg.reg1.bits.m0; + parm.m1 =3D 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 =3D dualdiv->table; + unsigned long best =3D 0, now =3D 0; + unsigned int i, best_i =3D 0; + + if (!table) + return NULL; + + for (i =3D 0; i < dualdiv->table_count; i++) { + now =3D aml_dualdiv_param_to_rate(parent_rate, &table[i]); + + /* If we get an exact match, don't bother any further */ + if (now =3D=3D rate) { + return &table[i]; + } else if (abs(now - rate) < abs(best - rate)) { + best =3D now; + best_i =3D 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 =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + const struct aml_clk_dualdiv_param *setting; + + /* Just need to set "force_clkout" =3D 1 */ + if (req->rate =3D=3D req->best_parent_rate) { + req->rate =3D req->best_parent_rate; + + return 0; + } + + setting =3D aml_clk_dualdiv_get_setting(req->rate, req->best_parent_rate, + dualdiv); + if (setting) + req->rate =3D aml_dualdiv_param_to_rate(req->best_parent_rate, + setting); + else + req->rate =3D 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 =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + const struct aml_clk_dualdiv_param *setting; + + /* Just need to set "force_clkout" =3D 1 */ + if (rate =3D=3D parent_rate) { + regmap_read(clk->map, + dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + ®.reg1.val); + reg.reg1.bits.force_clkout =3D 1; + regmap_write(clk->map, + dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + reg.reg1.val); + + return 0; + } + + setting =3D aml_clk_dualdiv_get_setting(rate, parent_rate, dualdiv); + if (!setting) + return -EINVAL; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + ®.reg1.val); + reg.reg1.bits.force_clkout =3D 0; + + reg.reg0.bits.div_mode =3D setting->div_mode; + reg.reg0.bits.n0 =3D setting->n0; + reg.reg0.bits.n1 =3D setting->n1; + reg.reg1.bits.m0 =3D setting->m0; + reg.reg1.bits.m1 =3D 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 =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + reg.reg0.bits.clkin_gate =3D 1; + reg.reg0.bits.clkout_gate =3D 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 =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + reg.reg0.bits.clkin_gate =3D 0; + reg.reg0.bits.clkout_gate =3D 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 =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + + if (reg.reg0.bits.clkin_gate && reg.reg0.bits.clkout_gate) + return 1; + else + return 0; +} + +#ifdef CONFIG_DEBUG_FS +#include + +static int aml_clk_dualdiv_available_rates_show(struct seq_file *s, void *= data) +{ + struct clk_hw *hw =3D s->private; + struct clk_hw *phw =3D clk_hw_get_parent(hw); + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D 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 =3D clk_hw_get_rate(phw); + clk_hw_get_rate_range(hw, &core_min_rate, &core_max_rate); + if (dualdiv->table) { + for (i =3D 0; i < dualdiv->table_count; i++) { + rate =3D 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 *d= entry) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + + debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops); + if (clk->type =3D=3D 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 =3D { + .enable =3D aml_clk_dualdiv_enable, + .disable =3D aml_clk_dualdiv_disable, + .is_enabled =3D aml_clk_dualdiv_is_enabled, + .recalc_rate =3D aml_clk_dualdiv_recalc_rate, + .determine_rate =3D aml_clk_dualdiv_determine_rate, + .set_rate =3D aml_clk_dualdiv_set_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); diff --git a/drivers/clk/amlogic/clk-dualdiv.h b/drivers/clk/amlogic/clk-du= aldiv.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 + +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 }; =20 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 =3D 3, AML_CLKTYPE_COMPOSITE =3D 4, AML_CLKTYPE_NOGLITCH =3D 5, + AML_CLKTYPE_DUALDIV =3D 6, }; =20 struct aml_clk { --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 54BF831A7F1; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=q4ao1ClJBr+xm5bhUjbDv348CoTW85fH8WpQ0vBmcXgr7dQiX1ifDnjU9MZK8an0AJ3VA7QBZ2Fg6TyTX+RtJGYim0kWy14IUi7WgiDXAH0zpkLt41gZKyjtzXinE4RisJvkta4JKZf81a5UIucZTbIAY9nVWREidFC9KtQXzdc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=NYS59aNe5O5qsFHxf0prnhv6212zsrcx2icCCdEi8gQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=p6wJCW/cmE4daGByh+b19vj6bl+9ltensXtOj50sYKOtEXkssDsdBTwRoJRN0ESPc6I0F9ePaT4J9lfZUvu6VfhqraevNPUW/6GOwdGKoKOjp07pEii5oJ8gp3hMSw8kMzSqFBfddtTCuuCp9vzU9Oqy0giDxzVa6PD6+h9LYjA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=OfP79/vR; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="OfP79/vR" Received: by smtp.kernel.org (Postfix) with ESMTPS id 33B77C19425; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=NYS59aNe5O5qsFHxf0prnhv6212zsrcx2icCCdEi8gQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=OfP79/vRuy7jA+PhIfs/nYszGNyByjOChoizkTUZPxXzB/LSnymH2Blo3QqF2e/q+ OrpUjXZp1lSta9CpMr/DCKrtxUWFlCsft4i+gbP5SmtpZebfnW5LsFOH8rRrARbnjh n6ZDP7xpbZhfpeFy3K+ajnjw57Vd3NYVPgd7aAcgmKRgEUjIoxfpT/KFAEAKQlO3zT icHGLMunShxYYrynU+nWpAQPi6HLWth1+3Cn90SZ8TNSeVpZRFLurNCC7oUHS9NDlQ hP2AgI1m6tk4ew4R/C1LW87q2VQl+sxd0R4tDyHGzzgwy7OTdLDoR4Y1BGUWRrQyDA RxF0c1sABxeuA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 25876EF06FF; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:54 +0800 Subject: [PATCH 08/13] clk: amlogic: Add PLL driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-8-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=22431; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=h4s1WneacZnynNJ97cjVm/IUIZdjLkeqOC9e13Hm0y4=; b=zMKvLBUsAXqfFzFc8wBPjICQl89isOkCRFhTbKX/SH3eDp0s/XmY7ia6TcA7WI3Om4PAP+aSW 5CSSZWtNj8cB6zAVvbm7HDhW8e1ZesyaYmzJnz84e1ojHY2IwiSPNzZ X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu Implement clk_ops support for Amlogic PLL. Signed-off-by: Chuan Liu --- 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 +=3D clk-basic.o clk-amlogic-y +=3D clk-composite.o clk-amlogic-y +=3D clk-dualdiv.o clk-amlogic-y +=3D clk-noglitch.o +clk-amlogic-y +=3D 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 +#include +#include +#include + +#include "clk.h" +#include "clk-pll.h" + +/* + * Amlogic PLL module: + * + * +------------------------------------------------------+ + * | +-------+ +-----+ | + * osc_in --------->| div N |----->| | +-----+ | + * | +-------+ | | | | +--------+ | + * | | |-->| VCO |-->| div OD |------>pl= l_out + * | +----------+ | | | | +--------+ | + * | +-->| M & frac |-->| | +-----+ | + * | | +----------+ +-----+ | | + * | | | | + * | +-------------------------------+ | + * | | + * +------------------------------------------------------+ + * + * PLL output frequency calculation formula: + * + * pll_out =3D ((osc_in * (M + (frac / frac_max))) >> N) >> OD + * + * NOTE: Some PLLs support fractional multiplication. 'frac_max' is the co= unter + * used for fractional multiplication. Currently, there are two design val= ues of + * 'frac_max' in Amlogic PLLs: + * - frac_max =3D 2^17: Mainly used for Amlogic general-purpose PLLs, su= ch as + * gp_pll. + * - frac_max =3D 100000: The PLL step is of integer type (which helps e= liminate + * accumulated errors in the driver), such as hifi_pll. + * + * Configuring 'N' for pre-division may affect the PLL bandwidth, phase ma= rgin, + * etc., resulting in increased PLL output jitter. Therefore, it is not + * recommended to arbitrarily configure 'N' for pre-division, and by defau= lt 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 =3D (u64)parent_rate * m; + + if (pll->flags & AML_PLL_M_EN0P5) + rate =3D rate >> 1; + + if (frac && pll->frac_max) { + u64 frac_rate =3D DIV_ROUND_UP_ULL((u64)parent_rate * frac, + pll->frac_max); + if (pll->flags & AML_PLL_M_EN0P5) + frac_rate =3D frac_rate >> 1; + + rate +=3D frac_rate; + } + + /* The 'n' divider has fixed power-of-two property */ + rate =3D rate >> n; + + rate =3D 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 =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + struct aml_pll_reg_parms regs; + + regmap_read(clk->map, AML_PLL_REG0_OFFSET, ®s.reg0.val); + regmap_read(clk->map, AML_PLL_REG1_OFFSET, ®s.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 <=3D 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 =3D 0; + + for (idx =3D 0; idx < pll->table_count; idx++) { + now =3D __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 =3D now; + best_idx =3D idx; + + if (now =3D=3D rate) + break; + } + } + + if (idx >=3D pll->table_count) + return -EINVAL; + + parm->m =3D pll->table[best_idx].m; + parm->n =3D pll->table[best_idx].n; + parm->frac =3D pll->table[best_idx].frac; + parm->od =3D pll->table[best_idx].od; + + *out_rate =3D 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 =3D 0; + unsigned long frac_step; + unsigned long now_rate, best_rate =3D 0; + unsigned int best_m, best_frac, best_od; + + if (pll->flags & AML_PLL_M_EN0P5) + parent_rate =3D parent_rate >> 1; + + /* + * NOTE: Configuring the 'n' divider may increase the PLL output + * jitter. Here fix 'n =3D 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 =3D 0; + + for (idx =3D 0; idx <=3D pll->od_max; idx++) { + req_vco_rate =3D (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)) <=3D pll->range.max) + t_m =3D DIV_ROUND_CLOSEST_ULL(req_vco_rate, parent_rate); + else + t_m =3D div_u64(req_vco_rate, parent_rate); + + vco_rate =3D (u64)parent_rate * t_m; + if (pll->frac_max) { + val =3D div_u64(req_vco_rate * pll->frac_max, + parent_rate); + val -=3D t_m * pll->frac_max; + frac =3D min((unsigned int)val, (pll->frac_max - 1)); + + frac_step =3D parent_rate / pll->frac_max; + vco_rate +=3D frac_step * frac; + + /* + * With AML_PLL_ROUND_CLOSEST configured, the condition + * req_vco_rate >=3D vco_rate is guaranteed to be true. + */ + val =3D req_vco_rate - vco_rate; + if (pll->flags & AML_PLL_ROUND_CLOSEST && + (abs(val - frac_step) < val) && + (vco_rate + frac_step <=3D pll->range.max)) { + frac +=3D 1; + vco_rate +=3D frac_step; + } + } + + if (vco_rate < pll->range.min) + continue; + + now_rate =3D vco_rate >> idx; + if (aml_pll_is_better(rate, best_rate, now_rate, pll)) { + best_rate =3D now_rate; + + best_m =3D t_m; + best_frac =3D frac; + best_od =3D idx; + + if (now_rate =3D=3D rate) + break; + } + } + +out: + if (!best_rate) + return -EINVAL; + + parm->m =3D best_m; + parm->frac =3D best_frac; + parm->od =3D best_od; + + *out_rate =3D 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 =3D 0, table_rate =3D 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 =3D table_parm.m; + parm->n =3D table_parm.n; + parm->frac =3D table_parm.frac; + parm->od =3D table_parm.od; + } + + if (out_rate) + *out_rate =3D table_rate; + } else { + if (parm) { + parm->m =3D range_parm.m; + parm->n =3D range_parm.n; + parm->frac =3D range_parm.frac; + parm->od =3D range_parm.od; + } + + if (out_rate) + *out_rate =3D range_rate; + } + + return 0; +} + +static int aml_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + int ret; + + if (pll->flags & AML_PLL_READ_ONLY) { + req->rate =3D clk_hw_get_rate(hw); + return 0; + } + + ret =3D 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 =3D to_aml_clk(hw); + int delay =3D 1000; + struct aml_pll_reg_parms regs; + + do { + regmap_read(clk->map, AML_PLL_REG0_OFFSET, ®s.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 =3D to_aml_clk(hw); + struct aml_pll_reg_parms regs; + + regmap_read(clk->map, AML_PLL_REG0_OFFSET, ®s.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 =3D to_aml_clk(hw); + struct aml_pll_reg_parms regs; + + regmap_read(clk->map, AML_PLL_REG0_OFFSET, ®s.reg0.val); + + /* Put the pll is in reset */ + regs.reg0.bits.rstn =3D 0; + regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val); + + /* Disable lock detect module */ + regs.reg0.bits.l_detect_en =3D 0; + regmap_write(clk->map, AML_PLL_REG0_OFFSET, regs.reg0.val); + + /* Disable the pll */ + regs.reg0.bits.en =3D 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 timin= g, 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 =3D to_aml_clk(hw); + struct aml_pll_reg_parms regs; + int retry =3D 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, ®s.reg0.val); + + /* Powers up PLL supply */ + regs.reg0.bits.en =3D 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 =3D 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 =3D 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 =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + struct aml_pll_reg_parms regs; + struct aml_pll_parms_table parm; + int enabled, ret; + + if (parent_rate =3D=3D 0 || rate =3D=3D 0) + return -EINVAL; + + ret =3D aml_pll_get_best_parms(rate, parent_rate, &parm, pll, NULL); + if (ret) + return ret; + + enabled =3D aml_pll_is_enabled(hw); + + regmap_read(clk->map, AML_PLL_REG0_OFFSET, ®s.reg0.val); + /* If neither m nor n is changed, there is no need to disable the PLL */ + if ((regs.reg0.bits.m !=3D parm.m || regs.reg0.bits.n !=3D parm.n) && + enabled) + aml_pll_disable(hw); + + regs.reg0.bits.m =3D parm.m; + regs.reg0.bits.n =3D 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, ®s.reg1.val); + regs.reg1.bits.frac =3D parm.frac; + regmap_write(clk->map, AML_PLL_REG1_OFFSET, regs.reg1.val); + } + + if (pll->od_max) { + regs.reg0.bits.od =3D 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 =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + unsigned long p_rate =3D clk_hw_get_rate(clk_hw_get_parent(hw)); + + pll->context_is_enabled =3D aml_pll_is_enabled(hw); + pll->context_rate =3D aml_pll_recalc_rate(hw, p_rate); + + return 0; +} + +static void aml_pll_restore_context(struct clk_hw *hw) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + unsigned long p_rate =3D 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" w= ill be + * created under the corresponding debugfs directory to assist with debugg= ing or + * testing. + */ +#ifdef CONFIG_DEBUG_FS +#include + +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 =3D parent_rate >> 1; + + if (pll->frac_max) + return parent_rate / pll->frac_max; + else + return parent_rate; +} + +enum round_type { + ROUND_DOWN =3D 0, + ROUND_UP +}; + +static int aml_pll_get_best_rate(unsigned long rate, unsigned long step_ra= te, + 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 =3D 0; + + for (i =3D 0; i <=3D od_max; i++) { + vco_rate =3D rate << i; + if (vco_rate < min_vco_rate) + continue; + + if (vco_rate > max_vco_rate) + break; + + if (vco_rate % step_rate =3D=3D 0) { + best_rate =3D rate; + + break; + } + + if (round =3D=3D ROUND_DOWN) { + vco_rate =3D vco_rate - (vco_rate % step_rate); + now_rate =3D vco_rate >> i; + if ((rate - now_rate) < (rate - best_rate)) + best_rate =3D now_rate; + } else { + vco_rate =3D vco_rate + step_rate; + vco_rate =3D vco_rate - (vco_rate % step_rate); + now_rate =3D vco_rate >> i; + if ((now_rate - rate) < (best_rate - rate)) + best_rate =3D now_rate; + } + } + + if (!best_rate) + return -EINVAL; + + *out_rate =3D 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 =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + unsigned long step =3D 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 =3D pll->range.min >> pll->od_max; + max_rate =3D pll->range.max; + + clk_hw_get_rate_range(hw, &core_min_rate, &core_max_rate); + if (min_rate < core_min_rate) + min_rate =3D core_min_rate; + + ret =3D 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 =3D core_max_rate; + + ret =3D 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 =3D s->private; + struct clk_hw *phw =3D clk_hw_get_parent(hw); + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_pll_data *pll =3D clk->data; + u64 rate, prate =3D 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 =3D 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 =3D 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 >>=3D 1; + + clk_hw_get_rate_range(hw, &min, &max); + + for (i =3D 0; pll->table[i].m !=3D 0; i++) { + rate =3D (prate * pll->table[i].m) >> pll->table[i].n; + + rate =3D 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 =3D { + .recalc_rate =3D aml_pll_recalc_rate, + .determine_rate =3D aml_pll_determine_rate, + .set_rate =3D aml_pll_set_rate, + .is_enabled =3D aml_pll_is_enabled, + .save_context =3D aml_pll_save_context, + .restore_context =3D aml_pll_restore_context, + .enable =3D aml_pll_enable, + .disable =3D aml_pll_disable, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D { + .recalc_rate =3D aml_pll_recalc_rate, + .is_enabled =3D aml_pll_is_enabled, +}; +EXPORT_SYMBOL_NS_GPL(aml_pll_ro_ops, "CLK_AMLOGIC"); + +MODULE_DESCRIPTION("Amlogic PLL Driver"); +MODULE_AUTHOR("Chuan Liu "); +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 +#include + +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 =3D 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 }; =20 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 =3D 4, AML_CLKTYPE_NOGLITCH =3D 5, AML_CLKTYPE_DUALDIV =3D 6, + AML_CLKTYPE_PLL =3D 7, }; =20 struct aml_clk { --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7454B31B810; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=OqEqjzeJqCk6snNTj9cjB271OQuuhUmWFmjRyB4geisqS0tnqSytNj+/CWmK0A2hsRiC6zp67kQ+dk2pe47NsEOfElx1GehP2bbsdkWFMAg/tsr+AnSvPrx5m8AIawI8daZhglyQvpp+Bl2DoTpy2CP6jB+7dAdHKP/rdTw0zMQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=N+uYCRMmJ55t9DThH54gsj/D58MY9yM6LLaPmYCM3BE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mynv/hgC3yzmiTd1qJyMmf9pU8E8W2k+mk+ICsEsRT8BCJWGVuxo8Otg2Ady+h8DNia4ESVoB127sRDzREfo3zq6Dyiw2hQwJqGDkCc4YXUkjqekaBDbWK6RgmJNGKMbGbgiAHL5ZM/QaJpMbG/pIPDjgFNdjyFnjz8YUZkXk6Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=X0wafZZH; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="X0wafZZH" Received: by smtp.kernel.org (Postfix) with ESMTPS id 4BDDFC19422; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=N+uYCRMmJ55t9DThH54gsj/D58MY9yM6LLaPmYCM3BE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=X0wafZZHYFTwBhS5LfNdZpSQi0ZQ5aJAQJLvIrjIBED8F2VmFd2QSVnlcCApS7sf8 +Gg+YHmUfEjqVCZ6zlQZWMGjW3XmcgDF6UU/9ePQE6fijSM/m6jvEPWLNh+SWtol1k LDe7zcPkeGfU+jv+FSt5IKxv/MA6z+V05QJTePCI4zPwgHPERRmDbiiuqDSQXtnmQx H4xQlu0nO8+MeeUXZwDfHLOesMSrPFFLXrjVmZ5ucCt1Q8RiknjgqatGacUA6bHdRa F1js68kQFMRCH4rL8rF7dEDV1j3XbMxSdCLQiK7SShIC0DkWIpmoFqTvPPhuPqolxs /hEh5xSIz8ehg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4469CEF06E5; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:55 +0800 Subject: [PATCH 09/13] clk: amlogic: Add DT-based clock registration functions Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-9-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616142; l=10590; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=bn1W4wBj9kR2bBMVc+TWzhW7ePYmeAS9HgD29Ygxs+k=; b=JptUihEMip98DRKPFWIBfkFKyU+hFUTh+CMKTUdN3dzRJIuVh140hOffNm6cPeACf4QLAZO1/ BCdBK4+aJjHB2TuZyJ2W02UXGWreD/Y33v+ALbk72q/b6HhZQP5BZGf X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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 */ =20 +#include #include +#include + +#include "clk.h" =20 #ifdef CONFIG_DEBUG_FS #include =20 -#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_ra= tes_fops =3D { EXPORT_SYMBOL_NS_GPL(aml_clk_div_available_rates_fops, "CLK_AMLOGIC"); #endif /* CONFIG_DEBUG_FS */ =20 +struct regmap *aml_clk_regmap_init(struct platform_device *pdev) +{ + void __iomem *base; + struct resource *res; + struct regmap_config clkc_regmap_config =3D { + .reg_bits =3D 32, + .val_bits =3D 32, + .reg_stride =3D 4, + }; + + base =3D devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return NULL; + + clkc_regmap_config.max_register =3D resource_size(res) - 4; + if (!clkc_regmap_config.max_register) + clkc_regmap_config.max_register_is_0 =3D 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 =3D dev_of_node(dev); + struct regmap *regmap =3D dev_get_regmap(dev, NULL); + int ret, reg_count; + struct reg_sequence *init_regs; + + ret =3D of_aml_clk_get_init_reg_count(dev_np); + if (ret < 0) + return 0; + + reg_count =3D ret; + init_regs =3D devm_kcalloc(dev, reg_count, sizeof(*init_regs), + GFP_KERNEL); + if (!init_regs) + return -ENOMEM; + + ret =3D of_aml_clk_get_init_reg(dev_np, reg_count, init_regs); + if (ret) + goto fail; + + ret =3D 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 =3D 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 =3D %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 =3D 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 =3D=3D 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 =3D dev_of_node(dev); + unsigned int pcnt =3D of_clk_get_parent_count(np); + int i, real_pcnt =3D 0; + + if (end_index < 0 || end_index >=3D pcnt) + /* Get the number of all "clocks" for the current device node */ + end_index =3D pcnt - 1; + + if (start_index > end_index || + start_index > pcnt) + return -EINVAL; + + for (i =3D start_index; i <=3D 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 =3D of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, + &out_args); + if (ret) + return ERR_PTR(ret); + + if (out_args.np =3D=3D 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 =3D dev_hws[out_args.args[0]]; + } else { + clk =3D of_clk_get_from_provider(&out_args); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) !=3D -EPROBE_DEFER) + pr_warn("clk: couldn't get clock for %pOF\n", + out_args.np); + + return ERR_CAST(clk); + } + + clk_hw =3D __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 =3D dev_of_node(dev); + unsigned int pcnt =3D of_clk_get_parent_count(np); + int i, real_pcnt; + + if (end_index < 0 || end_index >=3D pcnt) + /* Get the number of all "clocks" for the current device node */ + end_index =3D pcnt - 1; + + if (start_index > end_index || start_index > pcnt) + return -EINVAL; + + for (i =3D start_index, real_pcnt =3D 0; i <=3D end_index; i++) { + if (of_aml_clk_is_dummy_index(np, i)) + continue; + + out_pdatas[real_pcnt].hw =3D 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 =3D 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 =3D dev_of_node(dev); + bool has_ptab =3D false; + u32 *ptab; + unsigned int pcnt =3D of_clk_get_parent_count(np); + int i, real_pcnt, ptab_i; + + real_pcnt =3D 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 >=3D pcnt) + end_index =3D pcnt - 1; + + for (i =3D start_index, ptab_i =3D 0; i <=3D end_index; i++) { + /* dummy clock exist and ptab needs to be defined */ + if (of_aml_clk_is_dummy_index(np, i)) { + has_ptab =3D true; + break; + } + } + if (!has_ptab) + return NULL; + + ptab =3D devm_kcalloc(dev, real_pcnt, sizeof(*ptab), GFP_KERNEL); + if (!ptab) + return ERR_PTR(-ENOMEM); + + for (i =3D start_index, ptab_i =3D 0; i <=3D end_index; i++) { + if (!of_aml_clk_is_dummy_index(np, i)) + ptab[ptab_i++] =3D 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 =3D of_property_count_u32_elems(np, + "amlogic,clock-max-frequency"); + + if (count < 0) + return count; + else if (count =3D=3D 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 =3D 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 =3D dev_of_node(dev); + unsigned long max_rate; + int ret; + + ret =3D devm_clk_hw_register(dev, hw); + if (ret) + return ret; + + ret =3D of_aml_clk_get_max_rate(np, clkid, &max_rate); + if (ret) { + if (ret !=3D -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 "); 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); } =20 +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 */ --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 85F3831352D; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=hSKzo0JccDVU+18zQVwGbQkxTpqtpRcTn6LN8lcKAKsXuIIGdNPp8H5WotGciJcMk3T0/GM2qoz4TrBsM4Xnf6g7tH5RUDOLzjvV00sPjhBqN02biWQ9YOXWUZM1PDcVVlLkpnEgAehnj0mgv9cYFZZycuIjZZc2VT/n+FYdUNg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=MOHUGf7vTOMF6lwnsIj7fzyFlWDO1jYxaQ8jI4vnDS0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tXdzHuFOlrNMgJV7I53/P1z1VmlrXFbrsR46XEZ/UZf87jgslMyxbRk76BRKvhI25tp+qwAe/XH0gTSPIvC67J3fNNAvJenjl/FOG3THBt1A63zap6XiWJU0+MimVAHSLM7xsxgUsozQm0hFhkF3eapyJb5ux7/aeGRg8OEfY1s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=bD1yHKof; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="bD1yHKof" Received: by smtp.kernel.org (Postfix) with ESMTPS id 6A6BFC116C6; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=MOHUGf7vTOMF6lwnsIj7fzyFlWDO1jYxaQ8jI4vnDS0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=bD1yHKof6KshfPMre7sBQ9IezJ4GNYw3QRBNOSWzh22+RcVCRSgf1FdSOJL9xqF1Y G99188bOR2Zwj4/ABcfIRrnTtw9xQQcGrx09557UBiCma1oIt2E0a7bOKmKX81DtzZ lzFDELzS9WpvwtA+h2ZjVojZO9JNcyVCWGyQS4J/gXezcearCMzRVhHeQRw45+uOdu dBn+T5nGuahJUPj0T6zho+ioxHvDzKDrN2H+FZOE79pvvYTmLJPU7S8MiWa3X7p8BC La3elBc4nwyMY6l7H8zl/vY8X6b1EXL5oOUE+EpKmjZjdL3s14QKjcRgk2hY9EU8w3 N5Uwpf3h5m7mg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5D765EF070F; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:56 +0800 Subject: [PATCH 10/13] clk: amlogic: Add A9 standardized model clock control units driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-10-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616143; l=15986; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=LIC/rr157/JcmUmsWhff3I7WronpOmEnKJiamdc04jU=; b=Cn+IlwLfHrOKz84vKbMEjcJCgFP2t1fhGCknPdqWQzUKBbwq0EpLcNhUSHVvN+IZdvSD6B9HK uMBtyslg2V7BQypbVB3EUhsVYWfp1VsatS5ODnHB4Tv99kkrXFguZZS X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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 +=3D clk-composite.o clk-amlogic-y +=3D clk-dualdiv.o clk-amlogic-y +=3D clk-noglitch.o clk-amlogic-y +=3D clk-pll.o +ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MODEL),) +clk-amlogic-y +=3D a9-model-ccu.o +endif diff --git a/drivers/clk/amlogic/a9-model-ccu.c b/drivers/clk/amlogic/a9-mo= del-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 +#include + +#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 =3D 1, + CLK_MODEL_NOGLITCH =3D 2, + CLK_MODEL_SYSBUS =3D 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 =3D of_device_get_match_data(dev); + if (!data) + return -EFAULT; + + switch (data->type) { + case CLK_MODEL_COMPOSITE: + *out_type =3D AML_CLKTYPE_COMPOSITE; + return 0; + + case CLK_MODEL_NOGLITCH: + *out_type =3D AML_CLKTYPE_NOGLITCH; + return 0; + + case CLK_MODEL_SYSBUS: + *out_type =3D 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 =3D dev_of_node(dev); + struct aml_clk *clk; + struct aml_clk_composite_data *composite; + u32 reg_val[3]; + unsigned int pcnt =3D 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 =3D { 0 }; + int index; + int ret, clkid, i; + + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, 0, 7, pdata, &pnum); + if (ret) + return ret; + + ptab =3D 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 =3D of_aml_clk_get_parent_data(dev, hw_data->hws, 8, 15, + pdata_compb, &pnum_compb); + if (ret) + return ret; + + ptab_compb =3D of_aml_clk_get_parent_table(dev, 8, 15); + if (IS_ERR(ptab_compb)) + return PTR_ERR(ptab_compb); + } + + init.ops =3D &aml_clk_composite_ops; + for (clkid =3D 0; clkid < hw_data->num; clkid++) { + init.name =3D 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 =3D clkid * 3; + for (i =3D 0; i < 3; i++) { + ret =3D of_property_read_u32_index(np, + "amlogic,reg-layout", + index + i, ®_val[i]); + if (ret) + return ret; + } + + composite =3D devm_kzalloc(dev, sizeof(*composite), GFP_KERNEL); + if (!composite) + return -ENOMEM; + + composite->reg_offset =3D reg_val[0]; + composite->bit_offset =3D reg_val[1]; + composite->div_width =3D 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 =3D=3D 16" indicates that this + * CCU corresponds to composite-ccu_b. + */ + if (pcnt > MAX_AML_CLK_COMP_PARENTS && + composite->bit_offset =3D=3D 16) { /* composite-ccu_b */ + init.num_parents =3D pnum_compb; + init.parent_data =3D pdata_compb; + + if (ptab_compb) + composite->table =3D ptab_compb; + } else { /* composite-ccu_a */ + init.num_parents =3D pnum; + init.parent_data =3D pdata; + + if (ptab) + composite->table =3D ptab; + } + + clk =3D to_aml_clk(hw_data->hws[clkid]); + clk->data =3D composite; + + hw_data->hws[clkid]->init =3D &init; + ret =3D 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 =3D 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 =3D { 0 }; + int ret, clkid; + + /* noglitch-ccu supports up to eight parent clocks. */ + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, 0, 7, pdata, &pnum); + if (ret) + return ret; + + ptab =3D of_aml_clk_get_parent_table(dev, 0, 7); + if (IS_ERR(ptab)) + return PTR_ERR(ptab); + + init.ops =3D &aml_clk_noglitch_ops; + init.num_parents =3D pnum; + init.parent_data =3D pdata; + for (clkid =3D 0; clkid < hw_data->num; clkid++) { + init.name =3D of_aml_clk_get_name_index(np, clkid); + if (!init.name) + return -EINVAL; + + hw_data->hws[clkid]->init =3D &init; + + noglitch =3D devm_kzalloc(dev, sizeof(*noglitch), GFP_KERNEL); + if (!noglitch) + return -ENOMEM; + + ret =3D of_property_read_u32_index(np, "amlogic,reg-layout", + clkid, &noglitch->reg_offset); + if (ret) + return ret; + + if (ptab) + noglitch->table =3D ptab; + + clk =3D to_aml_clk(hw_data->hws[clkid]); + clk->data =3D noglitch; + + ret =3D 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 =3D 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 =3D { 0 }; + int index; + int ret, clkid, i; + + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, 0, 0, &pdata, &pnum= ); + if (ret) + return ret; + + init.ops =3D &aml_clk_gate_ops; + init.num_parents =3D pnum; + init.parent_data =3D &pdata; + for (clkid =3D 0; clkid < hw_data->num; clkid++) { + init.name =3D 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 =3D clkid * 2; + for (i =3D 0; i < 2; i++) { + ret =3D of_property_read_u32_index(np, + "amlogic,reg-layout", + index + i, ®_val[i]); + if (ret) + return ret; + } + + gate =3D devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return -ENOMEM; + + gate->reg_offset =3D reg_val[0]; + gate->bit_idx =3D reg_val[1]; + + clk =3D to_aml_clk(hw_data->hws[clkid]); + clk->data =3D gate; + + hw_data->hws[clkid]->init =3D &init; + ret =3D 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 =3D &pdev->dev; + const struct aml_clk_model_data *data; + struct device_node *np =3D 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 =3D of_device_get_match_data(dev); + if (!data) + return -EFAULT; + + clk_num =3D of_aml_clk_get_count(np); + if (clk_num =3D=3D 0) + return -EINVAL; + + regmap =3D aml_clk_regmap_init(pdev); + if (IS_ERR_OR_NULL(regmap)) + return -EIO; + + of_aml_clk_regs_init(dev); + + hw_data =3D devm_kzalloc(dev, struct_size(hw_data, hws, clk_num), + GFP_KERNEL); + if (!hw_data) + return -ENOMEM; + + hw_data->num =3D clk_num; + clk =3D devm_kcalloc(dev, clk_num, sizeof(*clk), GFP_KERNEL); + if (!clk) + return -ENOMEM; + + ret =3D of_aml_clk_model_get_type(dev, &clk_type); + if (ret) + return ret; + + for (i =3D 0; i < clk_num; i++) { + clk[i].map =3D regmap; + clk[i].type =3D clk_type; + hw_data->hws[i] =3D &clk[i].hw; + } + + if (data->type =3D=3D CLK_MODEL_COMPOSITE) + ret =3D of_aml_clk_model_composite_init_register(dev, hw_data); + else if (data->type =3D=3D CLK_MODEL_NOGLITCH) + ret =3D of_aml_clk_model_noglitch_init_register(dev, hw_data); + else if (data->type =3D=3D CLK_MODEL_SYSBUS) + ret =3D of_aml_clk_model_sysbus_init_register(dev, hw_data); + + if (clk_num =3D=3D 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 =3D { + .type =3D CLK_MODEL_COMPOSITE, +}; + +static const struct aml_clk_model_data aml_noglitch_dev_data =3D { + .type =3D CLK_MODEL_NOGLITCH, +}; + +static const struct aml_clk_model_data aml_sysbus_dev_data =3D { + .type =3D CLK_MODEL_SYSBUS, +}; + +static const struct of_device_id of_aml_clk_model_match_table[] =3D { + { + .compatible =3D "amlogic,a9-composite-ccu", + .data =3D &aml_composite_dev_data, + }, + { + .compatible =3D "amlogic,a9-composite-ccu-mult", + .data =3D &aml_composite_dev_data, + }, + { + .compatible =3D "amlogic,a9-noglitch-ccu", + .data =3D &aml_noglitch_dev_data, + }, + { + .compatible =3D "amlogic,a9-noglitch-ccu-mult", + .data =3D &aml_noglitch_dev_data, + }, + { + .compatible =3D "amlogic,a9-sysbus-ccu", + .data =3D &aml_sysbus_dev_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, of_aml_clk_model_match_table); + +static struct platform_driver of_aml_clk_model_driver =3D { + .probe =3D of_aml_clk_model_probe, + .driver =3D { + .name =3D "aml-clk-model", + .of_match_table =3D of_aml_clk_model_match_table, + }, +}; +module_platform_driver(of_aml_clk_model_driver); + +MODULE_DESCRIPTION("Amlogic A9 Standardized Model Clock Control Units Driv= er"); +MODULE_AUTHOR("Chuan Liu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B49B932142B; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=jiqdHCBi2zSttXruXvZaC+nFpX9HOhwHNqls8xTytR18GnDRknyAv8yIRKpDfPIXdo0QGfcHjsKXXJj7iV+/KQKM3QW0KG1JzV6lilJy+kaCWBMSqKXfUJzhEgLF+Y5RDpVT2m/K9VWut75+AgIiGl9gepLkR8wRMR0rdNgE6U4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=61hKp++ahkS/bhSUpuH4qzDCjqJwbzAeZrWYOlewBz4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=jPaJZa+7sSe5rAUkU+HhoEnrUM4qP5R4giXVUPuX6oHwqNxYxrTiCR04MmUA/N1+cgO1vhxKAcBpiQ1TYkGqpMGeCk3/B3XSXSqkIhll20BQJA+6MJeynj0QJBSY5GTdaAekuN8epUTgx8otS/BtzJe1mqko88HxxcVq1lstQkU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=uOP5AteV; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="uOP5AteV" Received: by smtp.kernel.org (Postfix) with ESMTPS id 8F5A6C2BC9E; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=61hKp++ahkS/bhSUpuH4qzDCjqJwbzAeZrWYOlewBz4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=uOP5AteVoiOQsHQWPUYjATE44etuwnaiASsuIfhJ1CKt4ti91MrT4JGWHsI+yseK3 pWaVOOdKeWX4JHVaGm6rIOebwcZ5b6hAJkd/0sYtg7OX5UGxvSq6F077APtM1NrhzA qeUTx5ZeRjLOquiL9lIyT+GuluuFUVHmEtsawh9gM1GRPB03qJ1djtA/n16Puxyl/5 AywUgQqmWrD8NFEYnoeH2iDgAIUAYs61cBJSIF8qCfxJWhnVIDls8j5Iq2sbeRio3B BZnuWRzbsol2q5aU9L47hv5s6IXuLz8PzCFxiy5d2lhDDXP/OUuo0ckpktalsP30yU soRJxjQ6BqzJg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 83DA6EF06FF; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:57 +0800 Subject: [PATCH 11/13] clk: amlogic: Add A9 PLL controllers driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-11-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616143; l=5614; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=HEJHyYkWOeBY3AEh9IomhZ/CgW0wq6tKl6L8+ZYrJkY=; b=YxZ2VKXTukTFNqFRgp2yd1g0kK4ojKAcyPYhRHq3251QQtz6MJw7UsWyfuGCAqJr/wb6ZtJ1U Bk6f4pdL3/PDjZc/jhiVU3RlnCb1BVOhQtIkaVshdBgmX+kXLGVgOH7 X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu Add PLL controllers driver for the Amlogic A9 SoC family. Signed-off-by: Chuan Liu --- 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. =20 +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 +=3D clk-pll.o ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MODEL),) clk-amlogic-y +=3D a9-model-ccu.o endif + +ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_PLL),) +clk-amlogic-y +=3D 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 +#include + +#include "clk.h" +#include "clk-pll.h" + +static const struct aml_pll_data a9_mclk_pll_data =3D { + .range =3D { + .min =3D 1400000000, + .max =3D 2800000000, + }, + .od_max =3D 4, +}; + +static const struct aml_pll_data a9_gp_pll_data =3D { + .range =3D { + .min =3D 1400000000, + .max =3D 2800000000, + }, + .frac_max =3D 131072, /* 2^17 */ + .od_max =3D 4, + .flags =3D AML_PLL_M_EN0P5, +}; + +static const struct aml_pll_data a9_hifi_pll_data =3D { + .range =3D { + .min =3D 1400000000, + .max =3D 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 =3D 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 =3D 100000, + .od_max =3D 4, + .flags =3D AML_PLL_M_EN0P5, +}; + +static int of_aml_clk_pll_init_register(struct device *dev, struct aml_clk= *pll) +{ + struct device_node *np =3D dev_of_node(dev); + struct clk_init_data init; + struct clk_parent_data pdata; + u8 pnum; + int ret; + + init.name =3D of_aml_clk_get_name_index(np, 0); + if (!init.name) + return -EINVAL; + + ret =3D of_aml_clk_get_parent_data(dev, NULL, 0, 0, &pdata, &pnum); + if (ret) + return ret; + + init.ops =3D &aml_pll_ops; + init.num_parents =3D pnum; + init.parent_data =3D &pdata; + + pll->hw.init =3D &init; + ret =3D 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 =3D &pdev->dev; + struct regmap *regmap; + struct aml_clk *pll; + struct aml_pll_data *pll_data_tmp; + int ret; + + pll_data_tmp =3D (void *)of_device_get_match_data(dev); + if (!pll_data_tmp) + return -EFAULT; + + pll =3D devm_kmalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return -ENOMEM; + + pll->data =3D devm_kmemdup(dev, pll_data_tmp, sizeof(*pll_data_tmp), + GFP_KERNEL); + if (!pll->data) + return -ENOMEM; + + regmap =3D aml_clk_regmap_init(pdev); + if (IS_ERR_OR_NULL(regmap)) + return -EIO; + + pll->map =3D regmap; + pll->type =3D AML_CLKTYPE_PLL; + ret =3D of_aml_clk_regs_init(dev); + if (ret) + return ret; + + ret =3D 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[] =3D { + { + .compatible =3D "amlogic,a9-int-pll", + .data =3D &a9_mclk_pll_data, + }, + { + .compatible =3D "amlogic,a9-frac-pll", + .data =3D &a9_gp_pll_data, + }, + { + .compatible =3D "amlogic,a9-frac-step-pll", + .data =3D &a9_hifi_pll_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, of_aml_clk_pll_match_table); + +static struct platform_driver of_aml_clk_pll_driver =3D { + .probe =3D of_aml_clk_pll_probe, + .driver =3D { + .name =3D "aml-pll", + .of_match_table =3D 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 "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D6974322C77; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; cv=none; b=LuTfnigkIbGxCD473aewKf9WbXABzWbmVV7+fZVeOcALRX0762g7MgaN1ubXY3Y1x20krYip/29Mn8QJ3RpcaQ53rfuDFzwi/gDhXVxdXCVipmrORKvRXnF5areSKM8R7k/gKb+h1HJ259slIGo9OzEtYlxpH+xFKKK0A5Ewgng= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=QxViedLmynRp82O87N7D2qyntM/kr801ltrwPP9Llkg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=WbpUlQdR4/unBjXsCagJQHdN7j/Jy/9z1+hlg3vyxD+yX+5Q15X+Knrf9A4Pw0BjwgMbMbDbKuzoxYJYs/57oAKSC01ATXJbXkb1UM5Ok9yEBWNTPSxP4xoJkZjmdeK45JOb4NW+c6NuNTwCQxJhyEQw/w59pQiYnKuu37GzbRU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=NdK8tK44; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="NdK8tK44" Received: by smtp.kernel.org (Postfix) with ESMTPS id B4448C116C6; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=QxViedLmynRp82O87N7D2qyntM/kr801ltrwPP9Llkg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=NdK8tK44zwbBNKywOR4wJ/dOwYNDSnRgpKPGpBJRNKUzZPS4LQ1m/KqH2rxbwdFlA Nzd1hNxA00lzR+hNcGrmUvkV8Jheaaq2j/f5AAYQe7BXjaOqBfPGjpEvLwiY2UHZ6Y aS329ufS75+ymmg5BoI5q1a+Ns48+YUZ823bDvuDw4wPCOPg6FHX6a1ufdWlMP5Kh5 pXebw7sWXx3WGYWRNhzEfy1ru/BBeD68tNhQTKQzxelEOxr6XORgUnVcKBB5Jm/bnA DfqZM4CQXmAETUJC7Cag8Dn6Qu5lhCsrG3Bg1Ja89+6uaegLHko/GpBO5+8HxMTLih FKnJzxdR/8emQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id A846EEF06FF; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:58 +0800 Subject: [PATCH 12/13] clk: amlogic: Add A9 misc clock control units driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-12-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616143; l=28812; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=LIyu9KL3MwJ2/G89sNwwVlFz7b01K2TvVo7VDEnv0sk=; b=exPV6T7eX9RsGLI/cLQJwlrSkP8NSV1/RcP2oFgjDTzRAGr3eZSt26V6E0L0YuqpYGjXP2ru8 vSDGZjg4NukAFz41hnW7UUb4na5kd455Eyh7C7pJCqyDzpkWTWIjdsf X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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. =20 +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 +=3D clk-composite.o clk-amlogic-y +=3D clk-dualdiv.o clk-amlogic-y +=3D clk-noglitch.o clk-amlogic-y +=3D clk-pll.o + +ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MISC),) +clk-amlogic-y +=3D a9-misc-ccu.o +endif + ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MODEL),) clk-amlogic-y +=3D a9-model-ccu.o endif diff --git a/drivers/clk/amlogic/a9-misc-ccu.c b/drivers/clk/amlogic/a9-mis= c-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 +#include + +#include "clk.h" +#include "clk-basic.h" +#include "clk-composite.h" +#include "clk-dualdiv.h" +#include "clk-noglitch.h" +#include "clk-pll.h" + +#include + +static struct aml_clk a9_sc_pre =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_composite_ops, + }, + .type =3D AML_CLKTYPE_COMPOSITE, + .data =3D &(struct aml_clk_composite_data) { + .div_width =3D 8, + }, +}; + +static struct aml_clk a9_sc =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_sc_pre.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .shift =3D 16, + .width =3D 4, + }, +}; + +struct clk_hw_onecell_data a9_sc_clk_hw_data =3D { + .hws =3D { + [A9_CLK_SC_PRE] =3D &a9_sc_pre.hw, + [A9_CLK_SC] =3D &a9_sc.hw, + }, + .num =3D 2, +}; + +static struct aml_clk a9_ts_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .shift =3D 0, + .width =3D 8, + }, +}; + +static struct aml_clk a9_ts =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_ts_div.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .bit_idx =3D 8, + }, +}; + +struct clk_hw_onecell_data a9_ts_clk_hw_data =3D { + .hws =3D { + [A9_CLK_TS_DIV] =3D &a9_ts_div.hw, + [A9_CLK_TS] =3D &a9_ts.hw, + }, + .num =3D 2, +}; + +static struct aml_clk a9_genout_sel =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_mux_ops, + }, + .type =3D AML_CLKTYPE_MUX, + .data =3D &(struct aml_clk_mux_data) { + .mask =3D 0x1f, + .shift =3D 12, + }, +}; + +static struct aml_clk a9_genout_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_genout_sel.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .shift =3D 0, + .width =3D 11, + }, +}; + +static struct aml_clk a9_genout =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_genout_div.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .bit_idx =3D 11, + }, +}; + +struct clk_hw_onecell_data a9_genout_clk_hw_data =3D { + .hws =3D { + [A9_CLK_GENOUT_SEL] =3D &a9_genout_sel.hw, + [A9_CLK_GENOUT_DIV] =3D &a9_genout_div.hw, + [A9_CLK_GENOUT] =3D &a9_genout.hw, + }, + .num =3D 3, +}; + +static struct aml_clk a9_clk24m_in =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .bit_idx =3D 11, + }, +}; + +static struct aml_clk a9_clk12_24m =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_clk24m_in.hw + }, + .num_parents =3D 1, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .shift =3D 10, + .width =3D 1, + }, +}; + +struct clk_hw_onecell_data a9_clk12_24m_clk_hw_data =3D { + .hws =3D { + [A9_CLK_24M_IN] =3D &a9_clk24m_in.hw, + [A9_CLK_12_24M] =3D &a9_clk12_24m.hw, + }, + .num =3D 2, +}; + +static struct aml_clk a9_vapb =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_noglitch_ops, + }, + .type =3D AML_CLKTYPE_NOGLITCH, + .data =3D &(struct aml_clk_composite_data) { + .div_width =3D 7, + }, +}; + +static struct aml_clk a9_ge2d =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_vapb.hw + }, + .num_parents =3D 1, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .bit_idx =3D 30, + }, +}; + +struct clk_hw_onecell_data a9_vapbge2d_clk_hw_data =3D { + .hws =3D { + [A9_CLK_VAPB] =3D &a9_vapb.hw, + [A9_CLK_GE2D] =3D &a9_ge2d.hw, + }, + .num =3D 2, +}; + +static struct aml_clk a9_vpu_clkb_temp =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_composite_ops, + }, + .type =3D AML_CLKTYPE_COMPOSITE, + .data =3D &(struct aml_clk_composite_data) { + .bit_offset =3D 16, + .div_width =3D 4, + }, +}; + +static struct aml_clk a9_vpu_clkb_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_vpu_clkb_temp.hw + }, + .num_parents =3D 1, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .shift =3D 0, + .width =3D 8, + }, +}; + +static struct aml_clk a9_vpu_clkb =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_vpu_clkb_div.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .bit_idx =3D 8, + }, +}; + +struct clk_hw_onecell_data a9_di_clk_hw_data =3D { + .hws =3D { + [A9_CLK_VPU_CLKB_TEMP] =3D &a9_vpu_clkb_temp.hw, + [A9_CLK_VPU_CLKB_DIV] =3D &a9_vpu_clkb_div.hw, + [A9_CLK_VPU_CLKB] =3D &a9_vpu_clkb.hw, + }, + .num =3D 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 =3D 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 =3D clk->data; + mux_data->table =3D ptab; + return 0; + + case AML_CLKTYPE_COMPOSITE: + comp_data =3D clk->data; + comp_data->table =3D ptab; + return 0; + + case AML_CLKTYPE_NOGLITCH: + noglitch_data =3D clk->data; + noglitch_data->table =3D 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 = =3D 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 =3D 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 !=3D of_aml_clk_get_count(np)) + return -EINVAL; + + ret =3D of_aml_clk_get_parent_num(dev, 0, -1); + if (ret < 0) + return ret; + + pnum =3D (u8)ret; + pdata =3D devm_kcalloc(dev, pnum, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + for (clkid =3D 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 =3D=3D 0). + */ + if (clkid =3D=3D 0) { + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, + 0, -1, pdata, &pnum); + if (ret) + goto out; + + clk =3D to_aml_clk(hw_data->hws[clkid]); + ret =3D of_aml_clk_misc_set_parent_table(dev, clk, 0, -1); + if (ret) + goto out; + } else { + pnum =3D 1; + /* + *The parent-child relationship among sub-clocks follows + * this order: + * - hw_data->hws[0] -> ... -> hw_data->hws[n]. + */ + pdata[0].hw =3D hw_data->hws[clkid - 1]; + } + + memcpy(&init, hw_data->hws[clkid]->init, sizeof(init)); + init.name =3D of_aml_clk_get_name_index(np, clkid); + if (!init.name) { + ret =3D -EINVAL; + goto out; + } + + init.num_parents =3D pnum; + init.parent_data =3D pdata; + hw_data->hws[clkid]->init =3D &init; + ret =3D 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 =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .bit_idx =3D 7, + }, +}; + +static struct aml_clk a9_eth_rmii =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_composite_ops, + }, + .type =3D AML_CLKTYPE_COMPOSITE, + .data =3D &(struct aml_clk_composite_data) { + .div_width =3D 7, + }, +}; + +struct clk_hw_onecell_data a9_eth_clk_hw_data =3D { + .hws =3D { + [A9_CLK_ETH_125M] =3D &a9_eth_125m.hw, + [A9_CLK_ETH_RMII] =3D &a9_eth_rmii.hw, + }, + .num =3D 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 =3D 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) !=3D A9_ETH_CLK_NUM) + return -EINVAL; + + for (clkid =3D 0; clkid < A9_ETH_CLK_NUM; clkid++) { + if (clkid =3D=3D A9_CLK_ETH_125M) { /* eth_125m */ + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, + 0, 0, pdata, &pnum); + if (ret) + return ret; + } else { /* eth_rmii */ + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, + 1, 8, pdata, &pnum); + if (ret) + return ret; + + clk =3D to_aml_clk(hw_data->hws[clkid]); + ret =3D 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 =3D of_aml_clk_get_name_index(np, clkid); + if (!init.name) + return -EINVAL; + + init.num_parents =3D pnum; + init.parent_data =3D pdata; + hw_data->hws[clkid]->init =3D &init; + ret =3D 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[] =3D { + { 1, 732, 7, 731, 10 }, /* 32.768k for rtc/cec */ +}; + +static struct aml_clk a9_dualdiv =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_dualdiv_ops, + }, + .type =3D AML_CLKTYPE_DUALDIV, + .data =3D &(struct aml_clk_dualdiv_data) { + .reg_offset =3D 0, + .table =3D a9_dualdiv_table, + .table_count =3D ARRAY_SIZE(a9_dualdiv_table), + }, +}; + +static struct aml_clk a9_dualdiv_sel =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &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 =3D CLK_OPS_PARENT_ENABLE, + }, + .type =3D AML_CLKTYPE_MUX, + .data =3D &(struct aml_clk_mux_data) { + .reg_offset =3D 4, + .mask =3D 0x3, + .shift =3D 30, + }, +}; + +struct clk_hw_onecell_data a9_dualdiv_clk_hw_data =3D { + .hws =3D { + [A9_CLK_DUALDIV] =3D &a9_dualdiv.hw, + [A9_CLK_DUALDIV_SEL] =3D &a9_dualdiv_sel.hw, + }, + .num =3D 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 whi= ch + * 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 =3D 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) !=3D A9_DUALDIVMUX_CLK_NUM) + return -EINVAL; + + for (clkid =3D 0; clkid < A9_DUALDIVMUX_CLK_NUM; clkid++) { + if (clkid =3D=3D A9_CLK_DUALDIV) { /* dualdiv */ + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, + 0, 0, pdata, &pnum); + if (ret) + return ret; + } else { /* dualdiv_sel */ + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, + 1, 4, pdata, &pnum); + if (ret) + return ret; + + clk =3D to_aml_clk(hw_data->hws[clkid]); + ret =3D 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 =3D of_aml_clk_get_name_index(np, clkid); + if (!init.name) + return -EINVAL; + + init.num_parents =3D pnum; + init.parent_data =3D pdata; + hw_data->hws[clkid]->init =3D &init; + ret =3D of_aml_clk_register(dev, hw_data->hws[clkid], clkid); + if (ret) + return ret; + } + + return 0; +} + +static struct aml_clk a9_mclk0_pre_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .num_parents =3D 1, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .reg_offset =3D 0xc, + .shift =3D 3, + .width =3D 5, + .flags =3D CLK_DIVIDER_MAX_AT_ZERO, + }, +}; + +static struct aml_clk a9_mclk0_sel =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_mux_ops, + }, + .type =3D AML_CLKTYPE_MUX, + .data =3D &(struct aml_clk_mux_data) { + .reg_offset =3D 0xc, + .mask =3D 0x3, + .shift =3D 12, + }, +}; + +static struct aml_clk a9_mclk0_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_mclk0_sel.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .reg_offset =3D 0xc, + .shift =3D 9, + .width =3D 1, + }, +}; + +static struct aml_clk a9_mclk0 =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_mclk0_div.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .reg_offset =3D 0xc, + .bit_idx =3D 8, + }, +}; + +static struct aml_clk a9_mclk1_pre_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .num_parents =3D 1, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .reg_offset =3D 0xc, + .shift =3D 19, + .width =3D 5, + .flags =3D CLK_DIVIDER_MAX_AT_ZERO, + }, +}; + +static struct aml_clk a9_mclk1_sel =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_mux_ops, + }, + .type =3D AML_CLKTYPE_MUX, + .data =3D &(struct aml_clk_mux_data) { + .reg_offset =3D 0xc, + .mask =3D 0x3, + .shift =3D 28, + }, +}; + +static struct aml_clk a9_mclk1_div =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_divider_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_mclk1_sel.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_DIV, + .data =3D &(struct aml_clk_divider_data) { + .reg_offset =3D 0xc, + .shift =3D 25, + .width =3D 1, + }, +}; + +static struct aml_clk a9_mclk1 =3D { + .hw.init =3D &(const struct clk_init_data) { + .ops =3D &aml_clk_gate_ops, + .parent_hws =3D (const struct clk_hw *[]) { + &a9_mclk1_div.hw + }, + .num_parents =3D 1, + .flags =3D CLK_SET_RATE_PARENT, + }, + .type =3D AML_CLKTYPE_GATE, + .data =3D &(struct aml_clk_gate_data) { + .reg_offset =3D 0xc, + .bit_idx =3D 24, + }, +}; + +#define A9_MCLK_CLK_NUM 8 + +struct clk_hw_onecell_data a9_mclk_clk_hw_data =3D { + .hws =3D { + [A9_CLK_MCLK_0_PRE_DIV] =3D &a9_mclk0_pre_div.hw, + [A9_CLK_MCLK_0_SEL] =3D &a9_mclk0_sel.hw, + [A9_CLK_MCLK_0_DIV] =3D &a9_mclk0_div.hw, + [A9_CLK_MCLK_0] =3D &a9_mclk0.hw, + [A9_CLK_MCLK_1_PRE_DIV] =3D &a9_mclk1_pre_div.hw, + [A9_CLK_MCLK_1_SEL] =3D &a9_mclk1_sel.hw, + [A9_CLK_MCLK_1_DIV] =3D &a9_mclk1_div.hw, + [A9_CLK_MCLK_1] =3D &a9_mclk1.hw, + }, + .num =3D 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-m= odule, + * channel 0 of the mux (mclk0/1_sel) has an independent pre-divider in fr= ont + * of it, while the clock sources for channels 1 to 3 are shared and ident= ical. + */ +static int of_aml_clk_mclk_init_register(struct device *dev, + struct clk_hw_onecell_data *hw_data) +{ + struct device_node *np =3D 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) !=3D A9_MCLK_CLK_NUM) + return -EINVAL; + + for (clkid =3D 0; clkid < A9_MCLK_CLK_NUM; clkid++) { + if (clkid =3D=3D A9_CLK_MCLK_0_PRE_DIV || + clkid =3D=3D A9_CLK_MCLK_1_PRE_DIV) { + ret =3D of_aml_clk_get_parent_data(dev, hw_data->hws, + 0, 0, pdata, &pnum); + if (ret) + return ret; + } else if (clkid =3D=3D A9_CLK_MCLK_0_SEL || + clkid =3D=3D A9_CLK_MCLK_1_SEL) { + ret =3D 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 =3D hw_data->hws[clkid - 1]; + pnum +=3D 1; + + clk =3D to_aml_clk(hw_data->hws[clkid]); + ret =3D of_aml_clk_misc_set_parent_table(dev, clk, 1, 4); + if (ret) + return ret; + } else { + pnum =3D 1; + /* + *The parent-child relationship among sub-clocks follows + * this order: + * - hw_data->hws[0] -> ... -> hw_data->hws[n]. + */ + pdata[0].hw =3D hw_data->hws[clkid - 1]; + } + + memcpy(&init, hw_data->hws[clkid]->init, sizeof(init)); + init.name =3D of_aml_clk_get_name_index(np, clkid); + if (!init.name) + return -EINVAL; + + init.num_parents =3D pnum; + init.parent_data =3D pdata; + hw_data->hws[clkid]->init =3D &init; + ret =3D 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 =3D 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 =3D of_aml_clk_get_count(np); + if (clk_num !=3D hw_data_tmp->num) + return ERR_PTR(-EINVAL); + + hw_data =3D devm_kzalloc(dev, struct_size(hw_data, hws, clk_num), + GFP_KERNEL); + if (!hw_data) + return ERR_PTR(-ENOMEM); + + hw_data->num =3D clk_num; + for (clkid =3D 0; clkid < clk_num; clkid++) { + clk_tmp =3D to_aml_clk(hw_data_tmp->hws[clkid]); + clk =3D devm_kmemdup(dev, clk_tmp, sizeof(*clk), GFP_KERNEL); + if (!clk) + return ERR_PTR(-ENOMEM); + + clk->map =3D regmap; + hw_data->hws[clkid] =3D &clk->hw; + ret =3D aml_clk_get_clk_data_size(clk_tmp->type); + if (ret < 0) + return ERR_PTR(ret); + + clk->data =3D 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 =3D &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 =3D (void *)of_device_get_match_data(dev); + if (!hw_data_tmp) + return -EFAULT; + + regmap =3D aml_clk_regmap_init(pdev); + if (IS_ERR_OR_NULL(regmap)) + return -EIO; + + hw_data =3D 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 =3D=3D &a9_eth_clk_hw_data) + ret =3D of_aml_clk_eth_init_register(dev, hw_data); + else if (hw_data_tmp =3D=3D &a9_dualdiv_clk_hw_data) + ret =3D of_aml_clk_dualdiv_init_register(dev, hw_data); + else if (hw_data_tmp =3D=3D &a9_mclk_clk_hw_data) + ret =3D of_aml_clk_mclk_init_register(dev, hw_data); + else + ret =3D 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[] =3D { + { + .compatible =3D "amlogic,a9-sc-ccu", + .data =3D &a9_sc_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-ts-ccu", + .data =3D &a9_ts_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-genout-ccu", + .data =3D &a9_genout_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-clk12_24m-ccu", + .data =3D &a9_clk12_24m_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-vapb_ge2d-ccu", + .data =3D &a9_vapbge2d_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-di-ccu", + .data =3D &a9_di_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-eth-ccu", + .data =3D &a9_eth_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-dualdivmux-ccu", + .data =3D &a9_dualdiv_clk_hw_data, + }, + { + .compatible =3D "amlogic,a9-mclk-ccu", + .data =3D &a9_mclk_clk_hw_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, of_aml_clk_misc_match_table); + +static struct platform_driver of_aml_clk_misc_driver =3D { + .probe =3D of_aml_clk_misc_probe, + .driver =3D { + .name =3D "aml-misc-clk", + .of_match_table =3D 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 "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); --=20 2.42.0 From nobody Tue Feb 10 00:59:08 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EBC233164DC; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616148; cv=none; b=XrpG6UYy2RlODD2ot31cORHV4JYYrDWN1gtu9H1GRwvPBhxXWERYGVNPJ61ylIaCWDlq+khTNYJjRqo4tJAhE5YeLBsgygv2NHb9w5I+6QMSrLnbubIoi2QON8YCmmdEJq6ReegGOSL6DF6TyAyuyn81gpm66Nyf+seSm2kc5cc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616148; c=relaxed/simple; bh=kUXClErnxJfpoi63zJu/2VA2hevTyPYJHfWGKISrFnU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ueTja77NdR3fMvz8Crl4LzhmhVEdDS+FjSWkD7EMo86436niBlS3KwvV0bsiSm/Zu9xtM9TzCvVFccFF10qLgPt5J8twW8L0ItiO2q9xP13d9tNSGF9E+ZLWEHXq/ztk2iEEaj7pylfTLBiRU0GV9Z4EuyaGKyUCYcJB8D+euYY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=NN+YWyA+; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="NN+YWyA+" Received: by smtp.kernel.org (Postfix) with ESMTPS id CE848C2BC86; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=kUXClErnxJfpoi63zJu/2VA2hevTyPYJHfWGKISrFnU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=NN+YWyA+qIGY3YaUnRcbMBZCfreM3uyGRGVzJWOJOLke8MaQU7G7e+T5uL9NRI7vo Bbim/pFLtK255n+2IYoQgyn4Gnka2o9JJSOWnX6o5WQcHErQ5z7+SQvkXprp47l7Bo jQ+w4YIsXKQ1HZbKjIEx2YKF29bAWH0V+33ZnTB1fqfC+Z4ZvxgniUjsmPMz5E58re 2LElyuhcQLzjxnvN3Uko7GRTtpDtQOKM8j/WTTH31OdcUKl+Gw0jVWdNXzIW5pFEfT Rug9hC2lRYLsox0eKHOoIRxgjPpg6+uKgN68NdJMf13XqxTLQ4m1PGJwOjN/DzRJ5x RfMHQPMhbAPIA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id BF661EF070F; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:59 +0800 Subject: [PATCH 13/13] clk: amlogic: Add support for building as combined kernel module Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-13-a9198dc03d2a@amlogic.com> References: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> In-Reply-To: <20260209-a9_clock_driver-v1-0-a9198dc03d2a@amlogic.com> To: Neil Armstrong , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Chuan Liu X-Mailer: b4 0.14.1 X-Developer-Signature: v=1; a=ed25519-sha256; t=1770616143; l=9091; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=ynvp9MD3ylfPiIkBw4uhgEanUvmWd8JB/qKY9SExV8o=; b=rIp87sPNTmv1EeoL0cQLnHC8zFT5RmCyigPuTBsDnhU7tDl4Li/EvTduSqy0K5WyL9GGTe1oW aK55O13JSACD8zJ8hG8x6xnyqaQiH3n7skHbz/TXYWbP7LGwiio0m40 X-Developer-Key: i=chuan.liu@amlogic.com; a=ed25519; pk=fnKDB+81SoWGKW2GJNFkKy/ULvsDmJZRGBE7pR5Xcpo= X-Endpoint-Received: by B4 Relay for chuan.liu@amlogic.com/20240902 with auth_id=203 X-Original-From: Chuan Liu Reply-To: chuan.liu@amlogic.com From: Chuan Liu 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 --- 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. =20 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. =20 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. =20 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. =20 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 +=3D clk-composite.o clk-amlogic-y +=3D clk-dualdiv.o clk-amlogic-y +=3D clk-noglitch.o clk-amlogic-y +=3D clk-pll.o +clk-amlogic-y +=3D clk-module.o =20 ifneq ($(CONFIG_COMMON_CLK_AMLOGIC_MISC),) clk-amlogic-y +=3D a9-misc-ccu.o diff --git a/drivers/clk/amlogic/a9-misc-ccu.c b/drivers/clk/amlogic/a9-mis= c-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" =20 @@ -952,7 +953,16 @@ static struct platform_driver of_aml_clk_misc_driver = =3D { .of_match_table =3D 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); +} =20 MODULE_DESCRIPTION("Amlogic A9 Misc Clock Control Units Driver"); MODULE_AUTHOR("Chuan Liu "); diff --git a/drivers/clk/amlogic/a9-model-ccu.c b/drivers/clk/amlogic/a9-mo= del-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" =20 /* @@ -457,7 +458,16 @@ static struct platform_driver of_aml_clk_model_driver = =3D { .of_match_table =3D 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); +} =20 MODULE_DESCRIPTION("Amlogic A9 Standardized Model Clock Control Units Driv= er"); MODULE_AUTHOR("Chuan Liu "); 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 =20 #include "clk.h" +#include "clk-module.h" #include "clk-pll.h" =20 static const struct aml_pll_data a9_mclk_pll_data =3D { @@ -138,7 +139,16 @@ static struct platform_driver of_aml_clk_pll_driver = =3D { .of_match_table =3D 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); +} =20 MODULE_DESCRIPTION("Amlogic A9 PLL Controllers Driver"); MODULE_AUTHOR("Chuan Liu "); diff --git a/drivers/clk/amlogic/clk-module.c b/drivers/clk/amlogic/clk-mod= ule.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 + +#include "clk-module.h" + +static int __init aml_clk_driver_init(void) +{ + int ret; + + ret =3D aml_pll_driver_init(); + if (ret) + return ret; + + ret =3D aml_clk_model_driver_init(); + if (ret) + return ret; + + ret =3D 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 "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); diff --git a/drivers/clk/amlogic/clk-module.h b/drivers/clk/amlogic/clk-mod= ule.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 + +#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 */ --=20 2.42.0