From mboxrd@z Thu Jan 1 00:00:00 1970 From: Chuan Liu Date: Mon, 09 Feb 2026 13:48:50 +0800 Subject: [PATCH 04/13] clk: amlogic: Add basic clock driver MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit 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 List-Id: B4 Relay Submissions 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. 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 +#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 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 + +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 "); +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 + +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[] = { +#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 "); +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 = 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 + +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 From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 8932FEF06FF for ; Mon, 9 Feb 2026 05:49:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:Reply-To:List-Subscribe:List-Help: List-Post:List-Archive:List-Unsubscribe:List-Id:Cc:To:In-Reply-To:References: Message-Id:MIME-Version:Subject:Date:From:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=yhiTf5MUfaFnfdwXdSnyFPvk1bS6YvJlgcqzGJP2mBY=; b=vVMKytoaL7UDbN r/BuU1mpWmVbtCwCYtVdIaeempGNRmCNwd6WATdxN20tJiUXzWR3ehmzeG8Qu+AEYsoNQxFZI04mF yEybbYe/ASBqCrRrzDbCukhLlAx4pa2QRDt4A4yNHXTa5LQg5gwXm3M6YV3FOHxmzS4SGqiOAfI9Y KZqC5VmNkaeV+2qTYBwC8r7I/lSECYsw5b0dDWrNU04iG4VeHILzHVW9p3i66sRTFMQ00bUjFK41G W8wUROJfFSlge6WXOeq6NYlgah2kAW8ZHzwsJxFWwsJ12Q+TSIzkTgmLgPGe8SJCUVegR8SKQo8yH c6CrlMKluuaN6eQIJQNA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98.2 #2 (Red Hat Linux)) id 1vpK92-0000000Erg1-2isF; Mon, 09 Feb 2026 05:49:12 +0000 Received: from sea.source.kernel.org ([2600:3c0a:e001:78e:0:1991:8:25]) by bombadil.infradead.org with esmtps (Exim 4.98.2 #2 (Red Hat Linux)) id 1vpK8y-0000000Erba-1Nm9 for linux-amlogic@lists.infradead.org; Mon, 09 Feb 2026 05:49:10 +0000 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by sea.source.kernel.org (Postfix) with ESMTP id C5EBD44558; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) 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 MIME-Version: 1.0 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 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260208_214908_463603_1FAF8F74 X-CRM114-Status: GOOD ( 21.21 ) X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: chuan.liu@amlogic.com Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+linux-amlogic=archiver.kernel.org@lists.infradead.org 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. 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 +#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 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 + +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 "); +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 + +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[] = { +#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 "); +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 = 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 + +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 _______________________________________________ linux-amlogic mailing list linux-amlogic@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-amlogic From mboxrd@z Thu Jan 1 00:00:00 1970 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-clk@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit 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. 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 +#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 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 + +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 "); +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 + +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[] = { +#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 "); +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 = 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 + +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