From nobody Tue Feb 10 04:14:01 2026 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 22CEF318B96; Mon, 9 Feb 2026 05:49:07 +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=1770616147; cv=none; b=Iv7INASTxMY7iQxiM8PEGcmZaYfMCXGkRG60t4r35o4TsFu/YP+UbgfTOdD/d58XVsnTw+cMG8Zr2mU279mkiBF4mRljUE6LARp7Gdh1X1j734ymIroUgJzUrxjW7CXJKQ5MfFRukruKb43Qbo6UIT1D16oWtC+iYMaN4JFFJBc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770616147; c=relaxed/simple; bh=WxRLnNceQ3wbnXmtcgHYv7mMpQB9lXHKbgRuGKvkBFE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SQ7AuO4WfNgmHL/O1KqWgO6sH1qtyRlO24AkmTgeVYYGz/gxi6iY4r88UNgAALKowzPW+zXqJF2PBn4ndBDV9NPQujN1ACpNrl0tIj0eCYPgP0/wXHvRMLM23WEz6i9Ouz7c+ILUGOXy3QnziS2aM92SPpYZ6NMaGEPk/yDrAa0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VBVaEPCg; 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="VBVaEPCg" Received: by smtp.kernel.org (Postfix) with ESMTPS id 04D32C2BCAF; Mon, 9 Feb 2026 05:49:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770616147; bh=WxRLnNceQ3wbnXmtcgHYv7mMpQB9lXHKbgRuGKvkBFE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=VBVaEPCgyHdjqYj2PuDB5KexwWq2sH2ScSNX+lBWXHB1V3VkYHKKeIWyq8i9htSdM tHuZwpfHOrSEiqNitdmiWuo16C9POjP0z1pi6CopobXQTe7cXnbhsKyLtKgOG8qEAf 80i99ISbODmYXRG2TDCNjKxXbk8if9gWL50MFNhBp21x+4ZHlMRcB5o6qZpj15CPfK in63Gl2wgkQ2SMMg7xNppBMo519cn/KpRBKmwFHlUp9FiUUa65A0MhTIoU0/Fj0Bxp VjLqB51ekIGQIMpwET5BS1cq67/C320dXE27PUWGYWZxD3pUJ97xe/ESA5VkDpdsUj haOpg0puuD+cw== 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 EDB98EF070B; Mon, 9 Feb 2026 05:49:06 +0000 (UTC) From: Chuan Liu via B4 Relay Date: Mon, 09 Feb 2026 13:48:53 +0800 Subject: [PATCH 07/13] clk: amlogic: Add duandiv clock driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260209-a9_clock_driver-v1-7-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=15230; i=chuan.liu@amlogic.com; s=20240902; h=from:subject:message-id; bh=lExIppsrGs3hG71peQNd4KEz/nuT8sZa6/ObaVooJnk=; b=B3IIxMGqKTCKBnUJMP9hnaQefxTiQhEaz/vXaE3Gja/L1P9mrB8tqWDw/nijoc8hQHiAgYaj6 93tMWtV6cioCtvGwL6ssxh4h6idEzcxNdZs4HaOwViWZeUPlsdLXdHI 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 clk_ops support for Amlogic dualdiv clocks. The dualdiv clock comprises two independent divider channels with counters. It achieves fractional division functionality by alternating between the two divided clocks. Amlogic dualdiv clocks are used for modules requiring fractional division without concern for clock phase or duty cycle, commonly generating 32.768KHz from a 24MHz crystal input for RTC applications. Signed-off-by: Chuan Liu --- drivers/clk/amlogic/Makefile | 1 + drivers/clk/amlogic/clk-dualdiv.c | 365 ++++++++++++++++++++++++++++++++++= ++++ drivers/clk/amlogic/clk-dualdiv.h | 27 +++ drivers/clk/amlogic/clk.c | 1 + drivers/clk/amlogic/clk.h | 1 + 5 files changed, 395 insertions(+) diff --git a/drivers/clk/amlogic/Makefile b/drivers/clk/amlogic/Makefile index 72cd2525e078..bc2b22b4d3c9 100644 --- a/drivers/clk/amlogic/Makefile +++ b/drivers/clk/amlogic/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_COMMON_CLK_AMLOGIC) +=3D clk-amlogic.o clk-amlogic-y +=3D clk.o clk-amlogic-y +=3D clk-basic.o clk-amlogic-y +=3D clk-composite.o +clk-amlogic-y +=3D clk-dualdiv.o clk-amlogic-y +=3D clk-noglitch.o diff --git a/drivers/clk/amlogic/clk-dualdiv.c b/drivers/clk/amlogic/clk-du= aldiv.c new file mode 100644 index 000000000000..1752b89ec0f2 --- /dev/null +++ b/drivers/clk/amlogic/clk-dualdiv.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (c) 2026 Amlogic, Inc. All rights reserved + */ + +#include + +#include "clk.h" +#include "clk-basic.h" +#include "clk-dualdiv.h" + +/* + * Amlogic dualdiv clock module: + * + * n0 m0 div_mode clkout_gate + * | | | | + * clkin_gate | n1 | m1 | force_clkout | + * | | | | | | | | + * +------|------------|----|-----|----|---|-----|--------|----+ + * | | \|/ | \|/ | \|/ | | | + * | | +-----+ | +-----+ | |\ | | | + * | | +-->| div |-o->| cnt |-o->| | \|/ | | + * | \|/ | +-----+ | +-----+ | | | |\ | | + * clk | +------+ | | | | | | | | | + * in ---->| gate |--+ +----+ +----+ | |-->| | \|/ | + * | +------+ | \|/ \|/ | | | | +------+ | + * | | +-----+ +-----+ | | | |-->| gate |----> c= lk + * | +-->| div |--->| cnt |--->| | | | +------+ | o= ut + * | | +-----+ +-----+ |/ | | | + * | +------------------------------>| | | + * | |/ | + * | | + * +-----------------------------------------------------------+ + * + * The Amlogic dualdiv clock is used to implement fractional division (e.g= ., a + * 24 MHz input clock with a 32.768 KHz output for the RTC). + * + * The functional description of these parameters is described below as fo= llows: + * - clkin_gate: clock input gate + * - n0: Divider value corresponding to channel 0 + * - n1: Divider value corresponding to channel 1 + * - m0: Number of output cycles per burst on channel 0 (switches to cha= nnel 1 + * after the cycles are output) + * - m1: Number of output cycles per burst on channel 0 (switches to cha= nnel 0 + * after the cycles are output) + * - div_mode: Division mode configuration: + * - div_mode =3D 0: Normal division mode: + * dualdiv_out =3D clk_in / n0; + * - div_mode =3D 1: Dualdiv (fractional) division mode: + * dualdiv_out =3D clk_in / ((n0 * m0 + n1 * m1) / (m0 + m1)); + * - force_clkout: force clock_out =3D clock_in + * - clkout_gate: clock output gate + * + * The fractional division principle of dualdiv is to alternately switch v= ia a + * mux between two integer-divided clocks, thereby achieving an effective + * fractional division. + * + * For example, when configuring a dualdiv ratio of 1.75, the clock wavefo= rm + * is illustrated below (n0 =3D 1, m0 =3D 1, n1 =3D 2, m1 =3D 3): + * + * _ _ _ _ _ _ _ _ _ _ _ _ + * clk in: | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| + * _ _ _ _ _ _ _ _ _ _ _ _ + * channel0: | |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| + * __ __ __ __ __ __ __ __ + * channel1: | |__| |__| |__| |__| |__| |__| |__| |__| + * _ __ __ __ _ __ __ __ _ + * clk out: | |_| |__| |__| |__| |_| |__| |__| |__| |_| + * |<->|<--------------->|<->|<--------------->|<->| + * m0 m1 m0 m1 m0 + */ + +#define AML_DUALDIV_REG0_OFFSET (0) +#define AML_DUALDIV_REG1_OFFSET (4) + +struct aml_dualdiv_reg_parms { + union { + struct { + u32 n0 :12; /* bit0 - bit11 */ + u32 n1 :12; /* bit12 - bit23 */ + u32 reserved0 :4; /* bit24 - bit27 */ + u32 div_mode :2; /* bit28 - bit29 */ + u32 clkout_gate :1; /* bit30 */ + u32 clkin_gate :1; /* bit31 */ + } bits; + u32 val; + } reg0; + union { + struct { + u32 m0 :12; /* bit0 - bit11 */ + u32 m1 :12; /* bit12 - bit23 */ + u32 force_clkout :1; /* bit24 */ + u32 reserved0 :5; /* bit25 - bit29 */ + u32 out_mux :2; /* bit30 - bit31 */ + + } bits; + u32 val; + } reg1; +}; + +static inline unsigned int _get_div_mult(unsigned int val) +{ + return val + 1; +} + +static unsigned long +aml_dualdiv_param_to_rate(unsigned long parent_rate, + const struct aml_clk_dualdiv_param *p) +{ + unsigned int n0, n1, m0, m1; + + n0 =3D _get_div_mult(p->n0); + if (!p->div_mode) + return DIV_ROUND_CLOSEST(parent_rate, n0); + + n1 =3D _get_div_mult(p->n1); + m0 =3D _get_div_mult(p->m0); + m1 =3D _get_div_mult(p->m1); + + return DIV_ROUND_CLOSEST(parent_rate * (m0 + m1), n0 * m0 + n1 * m1); +} + +static unsigned long aml_clk_dualdiv_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + struct aml_clk_dualdiv_param parm; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + ®.reg1.val); + if (reg.reg1.bits.force_clkout =3D=3D 1) + return parent_rate; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + parm.div_mode =3D reg.reg0.bits.div_mode; + parm.n0 =3D reg.reg0.bits.n0; + parm.n1 =3D reg.reg0.bits.n1; + parm.m0 =3D reg.reg1.bits.m0; + parm.m1 =3D reg.reg1.bits.m1; + + return aml_dualdiv_param_to_rate(parent_rate, &parm); +} + +/* + * NOTE: Dynamically calculating the divider parameters based on the target + * frequency has relatively high algorithmic complexity, and the number of + * frequency points that require dualdiv division in current use cases is + * limited. + * + * Therefore, only a table-based lookup method is supported to obtain the + * best-matched divider parameters at present. + */ +static const struct aml_clk_dualdiv_param * +aml_clk_dualdiv_get_setting(unsigned long rate, unsigned long parent_rate, + struct aml_clk_dualdiv_data *dualdiv) +{ + const struct aml_clk_dualdiv_param *table =3D dualdiv->table; + unsigned long best =3D 0, now =3D 0; + unsigned int i, best_i =3D 0; + + if (!table) + return NULL; + + for (i =3D 0; i < dualdiv->table_count; i++) { + now =3D aml_dualdiv_param_to_rate(parent_rate, &table[i]); + + /* If we get an exact match, don't bother any further */ + if (now =3D=3D rate) { + return &table[i]; + } else if (abs(now - rate) < abs(best - rate)) { + best =3D now; + best_i =3D i; + } + } + + return (struct aml_clk_dualdiv_param *)&table[best_i]; +} + +static int aml_clk_dualdiv_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + const struct aml_clk_dualdiv_param *setting; + + /* Just need to set "force_clkout" =3D 1 */ + if (req->rate =3D=3D req->best_parent_rate) { + req->rate =3D req->best_parent_rate; + + return 0; + } + + setting =3D aml_clk_dualdiv_get_setting(req->rate, req->best_parent_rate, + dualdiv); + if (setting) + req->rate =3D aml_dualdiv_param_to_rate(req->best_parent_rate, + setting); + else + req->rate =3D aml_clk_dualdiv_recalc_rate(hw, + req->best_parent_rate); + + return 0; +} + +static int aml_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + const struct aml_clk_dualdiv_param *setting; + + /* Just need to set "force_clkout" =3D 1 */ + if (rate =3D=3D parent_rate) { + regmap_read(clk->map, + dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + ®.reg1.val); + reg.reg1.bits.force_clkout =3D 1; + regmap_write(clk->map, + dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + reg.reg1.val); + + return 0; + } + + setting =3D aml_clk_dualdiv_get_setting(rate, parent_rate, dualdiv); + if (!setting) + return -EINVAL; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + ®.reg1.val); + reg.reg1.bits.force_clkout =3D 0; + + reg.reg0.bits.div_mode =3D setting->div_mode; + reg.reg0.bits.n0 =3D setting->n0; + reg.reg0.bits.n1 =3D setting->n1; + reg.reg1.bits.m0 =3D setting->m0; + reg.reg1.bits.m1 =3D setting->m1; + + regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + reg.reg0.val); + regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG1_OFFSET, + reg.reg1.val); + + return 0; +} + +static int aml_clk_dualdiv_enable(struct clk_hw *hw) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + reg.reg0.bits.clkin_gate =3D 1; + reg.reg0.bits.clkout_gate =3D 1; + + regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + reg.reg0.val); + + return 0; +} + +static void aml_clk_dualdiv_disable(struct clk_hw *hw) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + reg.reg0.bits.clkin_gate =3D 0; + reg.reg0.bits.clkout_gate =3D 0; + + regmap_write(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + reg.reg0.val); +} + +static int aml_clk_dualdiv_is_enabled(struct clk_hw *hw) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + struct aml_dualdiv_reg_parms reg; + + regmap_read(clk->map, dualdiv->reg_offset + AML_DUALDIV_REG0_OFFSET, + ®.reg0.val); + + if (reg.reg0.bits.clkin_gate && reg.reg0.bits.clkout_gate) + return 1; + else + return 0; +} + +#ifdef CONFIG_DEBUG_FS +#include + +static int aml_clk_dualdiv_available_rates_show(struct seq_file *s, void *= data) +{ + struct clk_hw *hw =3D s->private; + struct clk_hw *phw =3D clk_hw_get_parent(hw); + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_dualdiv_data *dualdiv =3D clk->data; + unsigned long rate, prate; + unsigned long core_min_rate, core_max_rate; + int i; + + if (!phw) { + pr_err("%s: can't get parent\n", clk_hw_get_name(hw)); + + return -ENOENT; + } + + prate =3D clk_hw_get_rate(phw); + clk_hw_get_rate_range(hw, &core_min_rate, &core_max_rate); + if (dualdiv->table) { + for (i =3D 0; i < dualdiv->table_count; i++) { + rate =3D aml_dualdiv_param_to_rate(prate, + &dualdiv->table[i]); + if (rate < core_min_rate || rate > core_max_rate) + continue; + + seq_printf(s, "%ld\n", (unsigned long)rate); + } + } else { + seq_printf(s, "%ld\n", prate); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(aml_clk_dualdiv_available_rates); + +static void aml_clk_dualdiv_debug_init(struct clk_hw *hw, struct dentry *d= entry) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + + debugfs_create_file("clk_type", 0444, dentry, hw, &aml_clk_type_fops); + if (clk->type =3D=3D AML_CLKTYPE_DUALDIV) + debugfs_create_file("clk_available_rates", 0444, dentry, hw, + &aml_clk_dualdiv_available_rates_fops); +} +#endif /* CONFIG_DEBUG_FS */ + +const struct clk_ops aml_clk_dualdiv_ops =3D { + .enable =3D aml_clk_dualdiv_enable, + .disable =3D aml_clk_dualdiv_disable, + .is_enabled =3D aml_clk_dualdiv_is_enabled, + .recalc_rate =3D aml_clk_dualdiv_recalc_rate, + .determine_rate =3D aml_clk_dualdiv_determine_rate, + .set_rate =3D aml_clk_dualdiv_set_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D aml_clk_dualdiv_debug_init, +#endif /* CONFIG_DEBUG_FS */ +}; +EXPORT_SYMBOL_NS_GPL(aml_clk_dualdiv_ops, "CLK_AMLOGIC"); + +MODULE_DESCRIPTION("Amlogic Dualdiv Driver"); +MODULE_AUTHOR("Chuan Liu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_AMLOGIC"); diff --git a/drivers/clk/amlogic/clk-dualdiv.h b/drivers/clk/amlogic/clk-du= aldiv.h new file mode 100644 index 000000000000..fbbae6686afd --- /dev/null +++ b/drivers/clk/amlogic/clk-dualdiv.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (c) 2026 Amlogic, Inc. All rights reserved + */ + +#ifndef __AML_CLK_DUALDIV_H +#define __AML_CLK_DUALDIV_H + +#include + +struct aml_clk_dualdiv_param { + unsigned int div_mode; + unsigned int n0; + unsigned int m0; + unsigned int n1; + unsigned int m1; +}; + +struct aml_clk_dualdiv_data { + unsigned int reg_offset; + struct aml_clk_dualdiv_param *table; + unsigned int table_count; +}; + +extern const struct clk_ops aml_clk_dualdiv_ops; + +#endif /* __AML_CLK_DUALDIV_MUX_H */ diff --git a/drivers/clk/amlogic/clk.c b/drivers/clk/amlogic/clk.c index 12e9596668d2..5431aa320dfa 100644 --- a/drivers/clk/amlogic/clk.c +++ b/drivers/clk/amlogic/clk.c @@ -23,6 +23,7 @@ static const struct { ENTRY(AML_CLKTYPE_GATE), ENTRY(AML_CLKTYPE_COMPOSITE), ENTRY(AML_CLKTYPE_NOGLITCH), + ENTRY(AML_CLKTYPE_DUALDIV), #undef ENTRY }; =20 diff --git a/drivers/clk/amlogic/clk.h b/drivers/clk/amlogic/clk.h index 14045fa109c0..c1d58a08e407 100644 --- a/drivers/clk/amlogic/clk.h +++ b/drivers/clk/amlogic/clk.h @@ -16,6 +16,7 @@ enum aml_clk_type { AML_CLKTYPE_GATE =3D 3, AML_CLKTYPE_COMPOSITE =3D 4, AML_CLKTYPE_NOGLITCH =3D 5, + AML_CLKTYPE_DUALDIV =3D 6, }; =20 struct aml_clk { --=20 2.42.0