From nobody Tue Feb 10 04:14:16 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 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-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-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. =20 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) +=3D clk-xgene.o =20 # please keep this section sorted lexicographically by directory path name obj-y +=3D actions/ +obj-$(CONFIG_ARCH_MESON) +=3D amlogic/ obj-y +=3D analogbits/ obj-$(CONFIG_COMMON_CLK_AT91) +=3D at91/ obj-$(CONFIG_ARCH_ARTPEC) +=3D 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) +=3D clk-amlogic.o + +clk-amlogic-y +=3D clk.o +clk-amlogic-y +=3D clk-basic.o diff --git a/drivers/clk/amlogic/clk-basic.c b/drivers/clk/amlogic/clk-basi= c.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 th= e CCF. + */ + +static int aml_clk_gate_endisable(struct clk_hw *hw, int enable) +{ + struct aml_clk *clk =3D to_aml_clk(hw); + struct aml_clk_gate_data *gate =3D clk->data; + int set =3D gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; + + set ^=3D 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 =3D to_aml_clk(hw); + struct aml_clk_gate_data *gate =3D clk->data; + unsigned int val; + + regmap_read(clk->map, gate->reg_offset, &val); + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + val ^=3D BIT(gate->bit_idx); + + val &=3D 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 *den= try) +{ + 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_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 =3D { + .enable =3D aml_clk_gate_enable, + .disable =3D aml_clk_gate_disable, + .is_enabled =3D aml_clk_gate_is_enabled, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D to_aml_clk(hw); + struct aml_clk_divider_data *div =3D clk->data; + unsigned int val; + int ret; + + ret =3D regmap_read(clk->map, div->reg_offset, &val); + if (ret) + /* Gives a hint that something is wrong */ + return 0; + + val >>=3D div->shift; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_divider_data *div =3D clk->data; + unsigned int val; + int ret; + + /* if read only, just return current value */ + if (div->flags & CLK_DIVIDER_READ_ONLY) { + ret =3D regmap_read(clk->map, div->reg_offset, &val); + if (ret) + return ret; + + val >>=3D div->shift; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_divider_data *div =3D clk->data; + unsigned int val; + int ret; + + ret =3D divider_get_val(rate, parent_rate, div->table, div->width, + div->flags); + if (ret < 0) + return ret; + + val =3D (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 =3D { + .recalc_rate =3D aml_clk_div_recalc_rate, + .determine_rate =3D aml_clk_div_determine_rate, + .set_rate =3D aml_clk_div_set_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D { + .recalc_rate =3D aml_clk_div_recalc_rate, + .determine_rate =3D aml_clk_div_determine_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D to_aml_clk(hw); + struct aml_clk_mux_data *mux =3D clk->data; + unsigned int val; + int ret; + + ret =3D regmap_read(clk->map, mux->reg_offset, &val); + if (ret) + return ret; + + val >>=3D mux->shift; + val &=3D 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 =3D to_aml_clk(hw); + struct aml_clk_mux_data *mux =3D clk->data; + unsigned int val =3D 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 =3D to_aml_clk(hw); + struct aml_clk_mux_data *mux =3D clk->data; + + return clk_mux_determine_rate_flags(hw, req, mux->flags); +} + +const struct clk_ops aml_clk_mux_ops =3D { + .get_parent =3D aml_clk_mux_get_parent, + .set_parent =3D aml_clk_mux_set_parent, + .determine_rate =3D aml_clk_mux_determine_rate, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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 =3D { + .get_parent =3D aml_clk_mux_get_parent, +#ifdef CONFIG_DEBUG_FS + .debug_init =3D 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-basi= c.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[] =3D { +#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 =3D s->private; + struct aml_clk *clk =3D to_aml_clk(hw); + int i; + + for (i =3D 0; i < ARRAY_SIZE(clk_types); i++) { + if (clk_types[i].type =3D=3D 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 =3D { + .owner =3D THIS_MODULE, + .open =3D aml_clk_type_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D 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 =3D s->private; + struct clk_hw *phw =3D clk_hw_get_parent(hw); + struct aml_clk *clk =3D 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 =3D 0; + const struct clk_div_table *div_table =3D NULL; + + 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, &range_min, &range_max); + max =3D prate; + if (clk->type =3D=3D AML_CLKTYPE_DIV) { + struct aml_clk_divider_data *div =3D clk->data; + + if (div->flags & CLK_DIVIDER_READ_ONLY) { + min =3D prate; + goto out_printf; + } else { + div_val =3D (1 << div->width) - 1; + div_table =3D div->table; + div_flags =3D div->flags; + div_width =3D div->width; + } + } else { + pr_err("%s: Unsupported clock type\n", clk_hw_get_name(hw)); + return -EINVAL; + } + + min =3D 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 =3D range_min; + + if (range_max < max) + max =3D range_max; + + min =3D divider_round_rate(hw, min, &prate, NULL, div_width, 0); + max =3D 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 fi= le *file) +{ + return single_open(file, aml_clk_div_available_rates_show, + inode->i_private); +} + +const struct file_operations aml_clk_div_available_rates_fops =3D { + .owner =3D THIS_MODULE, + .open =3D aml_clk_div_available_rates_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D 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 =3D 1, + AML_CLKTYPE_DIV =3D 2, + AML_CLKTYPE_GATE =3D 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 */ --=20 2.42.0