lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20260209-a9_clock_driver-v1-4-a9198dc03d2a@amlogic.com>
Date: Mon, 09 Feb 2026 13:48:50 +0800
From: Chuan Liu via B4 Relay <devnull+chuan.liu.amlogic.com@...nel.org>
To: Neil Armstrong <neil.armstrong@...aro.org>, 
 Michael Turquette <mturquette@...libre.com>, 
 Stephen Boyd <sboyd@...nel.org>, Rob Herring <robh@...nel.org>, 
 Krzysztof Kozlowski <krzk+dt@...nel.org>, 
 Conor Dooley <conor+dt@...nel.org>
Cc: linux-amlogic@...ts.infradead.org, linux-clk@...r.kernel.org, 
 devicetree@...r.kernel.org, linux-kernel@...r.kernel.org, 
 Chuan Liu <chuan.liu@...ogic.com>
Subject: [PATCH 04/13] clk: amlogic: Add basic clock driver

From: Chuan Liu <chuan.liu@...ogic.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@...ogic.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@...ogic.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@...ogic.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



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ