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