Devicetree
 help / color / mirror / Atom feed
From: Kathiravan Thirumoorthy <kathiravan.thirumoorthy@oss.qualcomm.com>
To: Georgi Djakov <djakov@kernel.org>,
	Bjorn Andersson <andersson@kernel.org>,
	Abel Vesa <abelvesa@kernel.org>, Stephen Boyd <sboyd@kernel.org>,
	Brian Masney <bmasney+clk@redhat.com>,
	Jerome Brunet <jbrunet+clk@baylibre.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>
Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org,
	devicetree@vger.kernel.org,
	Kathiravan Thirumoorthy
	<kathiravan.thirumoorthy@oss.qualcomm.com>
Subject: [PATCH 4/4] clk: qcom: apss-ipq9650: Add IPQ9650 APSS clock controller driver
Date: Wed, 07 Oct 2026 12:20:47 +0530	[thread overview]
Message-ID: <20261007-ipq9650_apss_clk-v1-4-fd4c2b3cbdea@oss.qualcomm.com> (raw)
In-Reply-To: <20261007-ipq9650_apss_clk-v1-0-fd4c2b3cbdea@oss.qualcomm.com>

The APSS clock controller on IPQ9650 provides the CPU and L3 clocks.
The four Cortex-A55 cores (silver), the Cortex-A78 core (gold) and the
L3 are each clocked by their own Zonda PLL. Each clock is selected
through a glitch-free mux (GFM) whose parents are XO, GPLL0 and the
corresponding PLL. The gold mux can also select the PLL's post-divider
output.

The L3 needs to be scaled along with the CPUs, so register an icc-clk
provider in which both CPU clusters vote on the L3 core clock.

Signed-off-by: Kathiravan Thirumoorthy <kathiravan.thirumoorthy@oss.qualcomm.com>
---
 drivers/clk/qcom/Kconfig        |   9 +
 drivers/clk/qcom/Makefile       |   1 +
 drivers/clk/qcom/apss-ipq9650.c | 385 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 395 insertions(+)

diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index 27d0ab24d50a..a17f661c3102 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -599,6 +599,15 @@ config IPQ_APSS_6018
 	  Say Y if you want to support CPU frequency scaling on
 	  ipq based devices.
 
+config IPQ_APSS_9650
+	tristate "IPQ9650 APSS Clock Controller"
+	depends on ARM64 || COMPILE_TEST
+	default y if IPQ_GCC_9650
+	help
+	  Support for APSS Clock controller on Qualcomm IPQ9650 platform.
+	  Say Y if you want to support CPU frequency scaling on IPQ9650 based
+	  devices.
+
 config IPQ_CMN_PLL
 	tristate "IPQ CMN PLL Clock Controller"
 	depends on ARM64 || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 6affb5073f26..ec3d8512a812 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_CLK_QCM2290_GPUCC) += gpucc-qcm2290.o
 obj-$(CONFIG_IPQ_APSS_PLL) += apss-ipq-pll.o
 obj-$(CONFIG_IPQ_APSS_5424) += apss-ipq5424.o
 obj-$(CONFIG_IPQ_APSS_6018) += apss-ipq6018.o
+obj-$(CONFIG_IPQ_APSS_9650) += apss-ipq9650.o
 obj-$(CONFIG_IPQ_CMN_PLL) += ipq-cmn-pll.o
 obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o
 obj-$(CONFIG_IPQ_GCC_5018) += gcc-ipq5018.o
diff --git a/drivers/clk/qcom/apss-ipq9650.c b/drivers/clk/qcom/apss-ipq9650.c
new file mode 100644
index 000000000000..7602f3896fe3
--- /dev/null
+++ b/drivers/clk/qcom/apss-ipq9650.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/interconnect-provider.h>
+
+#include <dt-bindings/clock/qcom,apss-ipq.h>
+#include <dt-bindings/interconnect/qcom,ipq9650.h>
+
+#include "clk-alpha-pll.h"
+#include "clk-branch.h"
+#include "clk-regmap.h"
+#include "common.h"
+
+enum {
+	DT_XO,
+	DT_GPLL0,
+};
+
+static const struct pll_vco silver_pll_vco[] = {
+	{ 816000000, 1968000000, 0 },
+};
+
+static const struct pll_vco gold_pll_vco[] = {
+	{ 696000000, 1992000000, 0 },
+};
+
+static const struct pll_vco l3_pll_vco[] = {
+	{ 696000000, 1512000000, 0 },
+};
+
+static const struct alpha_pll_config silver_apss_pll_config = {
+	.l = 0x23,
+	.config_ctl_val = 0x08200920,
+	.config_ctl_hi_val = 0x05008001,
+	.config_ctl_hi1_val = 0x04000000,
+	.user_ctl_val = 0x01000009,
+};
+
+static const struct alpha_pll_config gold_apss_pll_config = {
+	.l = 0x26,
+	.config_ctl_val = 0x08200920,
+	.config_ctl_hi_val = 0x05008001,
+	.config_ctl_hi1_val = 0x04000000,
+	.user_ctl_val = 0x01000009,
+};
+
+static const struct alpha_pll_config l3_pll_config = {
+	.l = 0x1e,
+	.config_ctl_val = 0x08200920,
+	.config_ctl_hi_val = 0x05008001,
+	.config_ctl_hi1_val = 0x04000000,
+	.user_ctl_val = 0x01000009,
+};
+
+static struct clk_alpha_pll silver_apss_pll = {
+	.offset = 0x0,
+	.config = &silver_apss_pll_config,
+	.vco_table = silver_pll_vco,
+	.num_vco = ARRAY_SIZE(silver_pll_vco),
+	.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+	.flags = SUPPORTS_DYNAMIC_UPDATE,
+	.clkr = {
+		.enable_reg = 0x0,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "silver_apss_pll",
+			.parent_data = &(const struct clk_parent_data){
+				.index = DT_XO,
+			},
+			.num_parents = 1,
+			.flags = CLK_IS_CRITICAL,
+			.ops = &clk_alpha_pll_zonda_ops,
+		},
+	},
+};
+
+static struct clk_alpha_pll gold_apss_pll = {
+	.offset = 0x10000,
+	.config = &gold_apss_pll_config,
+	.vco_table = gold_pll_vco,
+	.num_vco = ARRAY_SIZE(gold_pll_vco),
+	.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+	.flags = SUPPORTS_DYNAMIC_UPDATE,
+	.clkr = {
+		.enable_reg = 0x10000,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "gold_apss_pll",
+			.parent_data = &(const struct clk_parent_data){
+				.index = DT_XO,
+			},
+			.num_parents = 1,
+			.flags = CLK_IS_CRITICAL,
+			.ops = &clk_alpha_pll_zonda_ops,
+		},
+	},
+};
+
+static const struct clk_div_table gold_apss_pll_post_div_table[] = {
+	{ 0x0, 1 },
+	{ 0x1, 2 },
+	{ }
+};
+
+static struct clk_alpha_pll_postdiv gold_apss_pll_postdiv = {
+	.offset = 0x10000,
+	.post_div_shift = 8,
+	.post_div_table = gold_apss_pll_post_div_table,
+	.num_post_div = ARRAY_SIZE(gold_apss_pll_post_div_table),
+	.width = 2,
+	.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+	.clkr.hw.init = &(struct clk_init_data){
+		.name = "gold_apss_pll_postdiv",
+		.parent_hws = (const struct clk_hw*[]){
+			&gold_apss_pll.clkr.hw,
+		},
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_alpha_pll_postdiv_zonda_ops,
+	},
+};
+
+static struct clk_alpha_pll l3_pll = {
+	.offset = 0x20000,
+	.config = &l3_pll_config,
+	.vco_table = l3_pll_vco,
+	.num_vco = ARRAY_SIZE(l3_pll_vco),
+	.regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+	.flags = SUPPORTS_DYNAMIC_UPDATE,
+	.clkr = {
+		.enable_reg = 0x20000,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data){
+			.name = "l3_pll",
+			.parent_data = &(const struct clk_parent_data){
+				.index = DT_XO,
+			},
+			.num_parents = 1,
+			.flags = CLK_IS_CRITICAL,
+			.ops = &clk_alpha_pll_zonda_ops,
+		},
+	},
+};
+
+struct clk_gfm {
+	u32 mux_reg;
+	u32 mux_mask;
+	struct clk_hw hw;
+	void __iomem *base;
+};
+
+#define to_clk_gfm(_hw) container_of(_hw, struct clk_gfm, hw)
+
+static u8 clk_gfm_get_parent(struct clk_hw *hw)
+{
+	struct clk_gfm *gfm = to_clk_gfm(hw);
+	u32 val;
+
+	val = readl(gfm->base + gfm->mux_reg);
+	return val & gfm->mux_mask;
+}
+
+static int clk_gfm_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct clk_gfm *gfm = to_clk_gfm(hw);
+	u32 val;
+
+	val = readl(gfm->base + gfm->mux_reg);
+	val &= ~gfm->mux_mask;
+	val |= (index & gfm->mux_mask);
+	writel(val, gfm->base + gfm->mux_reg);
+
+	return 0;
+}
+
+static const struct clk_ops clk_gfm_ops = {
+	.get_parent = clk_gfm_get_parent,
+	.set_parent = clk_gfm_set_parent,
+	.determine_rate = __clk_mux_determine_rate_closest,
+};
+
+static struct clk_gfm silver_apss_pll_gfmux = {
+	.mux_reg = 0x84,
+	.mux_mask = 0xf,
+	.hw.init = &(struct clk_init_data) {
+		.name = "silver_apss_pll_gfmux",
+		.parent_data = (const struct clk_parent_data[]) {
+			{ .index = DT_XO },
+			{ .index = DT_GPLL0 },
+			{ .hw = &silver_apss_pll.clkr.hw },
+		},
+		.num_parents = 3,
+		.ops = &clk_gfm_ops,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+};
+
+static struct clk_gfm gold_apss_pll_gfmux = {
+	.mux_reg = 0x10084,
+	.mux_mask = 0xf,
+	.hw.init = &(struct clk_init_data) {
+		.name = "gold_apss_pll_gfmux",
+		.parent_data = (const struct clk_parent_data[]) {
+			{ .index = DT_XO },
+			{ .index = DT_GPLL0 },
+			{ .hw = &gold_apss_pll.clkr.hw },
+			{ .hw = &gold_apss_pll_postdiv.clkr.hw },
+		},
+		.num_parents = 4,
+		.ops = &clk_gfm_ops,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+};
+
+static struct clk_gfm l3_pll_gfmux = {
+	.mux_reg = 0x20084,
+	.mux_mask = 0xf,
+	.hw.init = &(struct clk_init_data) {
+		.name = "l3_pll_gfmux",
+		.parent_data = (const struct clk_parent_data[]) {
+			{ .index = DT_XO },
+			{ .index = DT_GPLL0 },
+			{ .hw = &l3_pll.clkr.hw },
+		},
+		.num_parents = 3,
+		.ops = &clk_gfm_ops,
+		.flags = CLK_SET_RATE_PARENT,
+	},
+};
+
+static struct clk_branch apss_silver_core_clk = {
+	.halt_reg = 0x8c,
+	.clkr = {
+		.enable_reg = 0x8c,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data) {
+			.name = "apss_silver_clk",
+			.parent_hws = (const struct clk_hw*[]) {
+				&silver_apss_pll_gfmux.hw,
+			},
+			.num_parents = 1,
+			.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+			.ops = &clk_branch2_ops,
+		},
+	},
+};
+
+static struct clk_branch apss_gold_core_clk = {
+	.halt_reg = 0x1008c,
+	.clkr = {
+		.enable_reg = 0x1008c,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data) {
+			.name = "apss_gold_clk",
+			.parent_hws = (const struct clk_hw*[]) {
+				&gold_apss_pll_gfmux.hw,
+			},
+			.num_parents = 1,
+			.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+			.ops = &clk_branch2_ops,
+		},
+	},
+};
+
+static struct clk_branch l3_core_clk = {
+	.halt_reg = 0x2008c,
+	.clkr = {
+		.enable_reg = 0x2008c,
+		.enable_mask = BIT(0),
+		.hw.init = &(struct clk_init_data) {
+			.name = "l3_core_clk",
+			.parent_hws = (const struct clk_hw*[]) {
+				&l3_pll_gfmux.hw,
+			},
+			.num_parents = 1,
+			.flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+			.ops = &clk_branch2_ops,
+		},
+	},
+};
+
+static struct clk_hw *apss_hws[] = {
+	&silver_apss_pll_gfmux.hw,
+	&l3_pll_gfmux.hw,
+	&gold_apss_pll_gfmux.hw,
+};
+
+static struct clk_regmap *apss_clks[] = {
+	[APSS_PLL_EARLY] = &silver_apss_pll.clkr,
+	[APSS_SILVER_CORE_CLK] = &apss_silver_core_clk.clkr,
+	[L3_PLL] = &l3_pll.clkr,
+	[L3_CORE_CLK] = &l3_core_clk.clkr,
+	[APSS_GOLD_PLL] = &gold_apss_pll.clkr,
+	[APSS_GOLD_PLL_POSTDIV] = &gold_apss_pll_postdiv.clkr,
+	[APSS_GOLD_CORE_CLK] = &apss_gold_core_clk.clkr,
+};
+
+static const struct regmap_config apss_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = 0x2fffc,
+	.fast_io = true,
+};
+
+static struct clk_alpha_pll *ipq9650_apss_plls[] = {
+	&silver_apss_pll,
+	&gold_apss_pll,
+	&l3_pll,
+};
+
+static const struct qcom_cc_driver_data ipq9650_apss_driver_data = {
+	.alpha_plls = ipq9650_apss_plls,
+	.num_alpha_plls = ARRAY_SIZE(ipq9650_apss_plls),
+};
+
+#define IPQ_APPS_PLL_ID			(9650 * 3)	/* some unique value */
+
+static const struct qcom_icc_hws_data icc_cpu_l3[] = {
+	{ MASTER_CPU_SILVER, SLAVE_L3, L3_CORE_CLK },
+	{ MASTER_CPU_GOLD, SLAVE_L3, L3_CORE_CLK },
+};
+
+static const struct qcom_cc_desc apss_desc = {
+	.config = &apss_regmap_config,
+	.clks = apss_clks,
+	.num_clks = ARRAY_SIZE(apss_clks),
+	.icc_hws = icc_cpu_l3,
+	.num_icc_hws = ARRAY_SIZE(icc_cpu_l3),
+	.icc_first_node_id = IPQ_APPS_PLL_ID,
+	.clk_hws = apss_hws,
+	.num_clk_hws = ARRAY_SIZE(apss_hws),
+	.driver_data = &ipq9650_apss_driver_data,
+};
+
+static int apss_ipq9650_probe(struct platform_device *pdev)
+{
+	const struct qcom_cc_desc *desc = &apss_desc;
+	struct regmap *regmap;
+	struct clk_gfm *gfm;
+	void __iomem *base;
+	int i;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	regmap = devm_regmap_init_mmio(&pdev->dev, base,
+				       &apss_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	for (i = 0; i < desc->num_clk_hws; i++) {
+		gfm = to_clk_gfm(apss_hws[i]);
+		gfm->base = base;
+	}
+
+	return qcom_cc_really_probe(&pdev->dev, desc, regmap);
+}
+
+static const struct of_device_id apss_ipq9650_match_table[] = {
+	{ .compatible = "qcom,ipq9650-apss-clk" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, apss_ipq9650_match_table);
+
+static struct platform_driver apss_ipq9650_driver = {
+	.probe = apss_ipq9650_probe,
+	.driver = {
+		.name = "qcom,ipq9650-apss-clk",
+		.of_match_table = apss_ipq9650_match_table,
+		.sync_state = icc_sync_state,
+	},
+};
+
+module_platform_driver(apss_ipq9650_driver);
+
+MODULE_DESCRIPTION("Qualcomm IPQ9650 APSS Clock Driver");
+MODULE_LICENSE("GPL");

-- 
2.34.1


      parent reply	other threads:[~2026-10-07  6:51 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-07  6:50 [PATCH 0/4] Add Qualcomm's IPQ9650 APSS Clock Driver Support Kathiravan Thirumoorthy
2026-10-07  6:50 ` [PATCH 1/4] interconnect: icc-clk: support N:1 master-to-slave topology Kathiravan Thirumoorthy
2026-10-07  7:05   ` sashiko-bot
2026-10-07  6:50 ` [PATCH 2/4] clk: qcom: clk-alpha-pll: Skip Zonda PLL configuration if already enabled Kathiravan Thirumoorthy
2026-10-09 18:23   ` Abel Vesa
2026-10-07  6:50 ` [PATCH 3/4] dt-bindings: clock: ipq9650-apss-clk: Add IPQ9650 apss clock controller Kathiravan Thirumoorthy
2026-10-07  6:50 ` Kathiravan Thirumoorthy [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261007-ipq9650_apss_clk-v1-4-fd4c2b3cbdea@oss.qualcomm.com \
    --to=kathiravan.thirumoorthy@oss.qualcomm.com \
    --cc=abelvesa@kernel.org \
    --cc=andersson@kernel.org \
    --cc=bmasney+clk@redhat.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=djakov@kernel.org \
    --cc=jbrunet+clk@baylibre.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox