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