[PATCH v2 06/24] clk: zte: Add ZX279128S CRM clock and reset driver
Philipp Zabel
p.zabel at pengutronix.de
Wed Oct 14 06:21:11 PDT 2026
On Mi, 2026-10-14 at 08:20 +0100, Navid Ghahremani wrote:
> Support the top CRM and low-speed peripheral clock controllers of the
> ZTE ZX279128S SoC. Implement custom reset controller ops mapping
> logical reset IDs to internal hardware register offsets, and provide
> CRM2 clock gating and reset controller support for PCIe.
>
> Signed-off-by: Navid Ghahremani <ghahramani.navid at gmail.com>
> ---
> drivers/clk/Kconfig | 1 +
> drivers/clk/Makefile | 1 +
> drivers/clk/zte/Kconfig | 14 +
> drivers/clk/zte/Makefile | 2 +
> drivers/clk/zte/clk-zx279128s.c | 758 ++++++++++++++++++++++++++++++++
> 5 files changed, 776 insertions(+)
> create mode 100644 drivers/clk/zte/Kconfig
> create mode 100644 drivers/clk/zte/Makefile
> create mode 100644 drivers/clk/zte/clk-zx279128s.c
>
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index f9592fd9ec..5200de26c0 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -547,6 +547,7 @@ source "drivers/clk/uniphier/Kconfig"
> source "drivers/clk/visconti/Kconfig"
> source "drivers/clk/x86/Kconfig"
> source "drivers/clk/xilinx/Kconfig"
> +source "drivers/clk/zte/Kconfig"
> source "drivers/clk/zynqmp/Kconfig"
>
> # Kunit test cases
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index b18af485d7..07393019c3 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -181,5 +181,6 @@ ifeq ($(CONFIG_COMMON_CLK), y)
> obj-$(CONFIG_X86) += x86/
> endif
> obj-y += xilinx/
> +obj-y += zte/
> obj-$(CONFIG_ARCH_ZYNQ) += zynq/
> obj-$(CONFIG_COMMON_CLK_ZYNQMP) += zynqmp/
> diff --git a/drivers/clk/zte/Kconfig b/drivers/clk/zte/Kconfig
> new file mode 100644
> index 0000000000..92fd27ea42
> --- /dev/null
> +++ b/drivers/clk/zte/Kconfig
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config CLK_ZX279128S
> + bool "ZTE zx279128s clock driver"
> + depends on SOC_ZX279128S || COMPILE_TEST
> + depends on OF
> + default SOC_ZX279128S
> + select RESET_CONTROLLER
> + select RESET_SIMPLE
Don't select RESET_SIMPLE without using it.
> + help
> + This driver supports the clocks of the ZTE zx279128s SoC: the
> + top clock and reset module (CRM) with the PLLs, the CPU, bus and
> + peripheral group clocks, and the CRPMs of the two groups of low
> + speed peripherals (UARTs, SPI flash controller, GPIO, MDIO).
> + Say Y if you build a kernel for a board with this SoC.
> diff --git a/drivers/clk/zte/Makefile b/drivers/clk/zte/Makefile
> new file mode 100644
> index 0000000000..a55ffbfaf1
> --- /dev/null
> +++ b/drivers/clk/zte/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_CLK_ZX279128S) += clk-zx279128s.o
> diff --git a/drivers/clk/zte/clk-zx279128s.c b/drivers/clk/zte/clk-zx279128s.c
> new file mode 100644
> index 0000000000..892e0c22e5
> --- /dev/null
> +++ b/drivers/clk/zte/clk-zx279128s.c
> @@ -0,0 +1,758 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * ZTE zx279128s clocks: the top CRM and the CRPMs of the two groups of low
> + * speed peripherals
> + *
> + * Copyright (C) 2026 Navid Ghahremani <ghahramani.navid at gmail.com>
> + *
> + * The boot loader sets up the PLLs and the CPU and bus clocks, and the driver
> + * leaves them as they are: it reads them once and registers their rates. The
> + * gates, and the muxes and dividers of the peripherals, are under its
> + * control.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/reset-controller.h>
> +
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <dt-bindings/clock/zte,zx279128s-crm.h>
> +#include <dt-bindings/reset/zte,zx279128s-reset.h>
> +
> +/* Top CRM */
> +#define TOP_CLK_SEL 0x0c
> +#define TOP_MATRIX_SEL_SHIFT 15
> +#define TOP_CPU_SEL_SHIFT 13
> +#define TOP_CLK_EN 0x14
> +#define TOP_PLL_A9 0x18
> +#define TOP_PLL_AUDIO 0x28
> +#define TOP_USB_EN 0x48
> +
> +#define TOP_NUM_CLKS (ZX279128S_TOP_USB_REF + 1)
> +
> +struct zx_reset_line {
> + u16 reg;
> + u8 bit;
> +};
> +
> +static const struct zx_reset_line zx279128s_top_resets[] = {
> + [ZX279128S_RESET_USB_B9] = { 0x4c, 9 },
> + [ZX279128S_RESET_USB_B10] = { 0x4c, 10 },
> + [ZX279128S_RESET_USB_B11] = { 0x4c, 11 },
> + [ZX279128S_RESET_USB_B14] = { 0x4c, 14 },
Why store reg in zx_reset_line when it's all the same value?
On the other hand, keeping reg and adding a bool active_low field would
allow to share ops between the two reset controllers.
> +};
> +
> +static DEFINE_SPINLOCK(zx_top_lock);
Why share the lock? Each reset controller has its own register.
> +
> +struct zx_reset_data {
> + struct reset_controller_dev rcdev;
> + void __iomem *base;
> + spinlock_t *lock;
> +};
> +
> +static inline struct zx_reset_data *to_zx_reset_data(struct reset_controller_dev *rcdev)
> +{
> + return container_of(rcdev, struct zx_reset_data, rcdev);
> +}
> +
> +static int zx_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> + struct zx_reset_data *data = to_zx_reset_data(rcdev);
> + const struct zx_reset_line *line;
> + unsigned long flags;
> + u32 val;
> +
> + if (id >= ARRAY_SIZE(zx279128s_top_resets))
> + return -EINVAL;
This check is unnecessary, fwnode_reset_simple_xlate() already checked
this before the reset control was created.
> + line = &zx279128s_top_resets[id];
> + spin_lock_irqsave(data->lock, flags);
> + val = readl(data->base + line->reg);
> + val &= ~BIT(line->bit);
> + writel(val, data->base + line->reg);
> + spin_unlock_irqrestore(data->lock, flags);
Consider sharing a read-modify-write helper between implementations.
> + return 0;
> +}
> +
> +static int zx_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> + struct zx_reset_data *data = to_zx_reset_data(rcdev);
> + const struct zx_reset_line *line;
> + unsigned long flags;
> + u32 val;
> +
> + if (id >= ARRAY_SIZE(zx279128s_top_resets))
> + return -EINVAL;
> +
> + line = &zx279128s_top_resets[id];
> + spin_lock_irqsave(data->lock, flags);
> + val = readl(data->base + line->reg);
> + val |= BIT(line->bit);
> + writel(val, data->base + line->reg);
> + spin_unlock_irqrestore(data->lock, flags);
> + return 0;
> +}
> +
> +static int zx_reset_status(struct reset_controller_dev *rcdev, unsigned long id)
> +{
> + struct zx_reset_data *data = to_zx_reset_data(rcdev);
> + const struct zx_reset_line *line;
> + u32 val;
> +
> + if (id >= ARRAY_SIZE(zx279128s_top_resets))
> + return -EINVAL;
> +
> + line = &zx279128s_top_resets[id];
> + val = readl(data->base + line->reg);
> + return !(val & BIT(line->bit));
> +}
> +
> +static const struct reset_control_ops zx_reset_ops = {
> + .assert = zx_reset_assert,
> + .deassert = zx_reset_deassert,
> + .status = zx_reset_status,
> +};
> +
> +/*
> + * Reset bits are cleared to hold the reset. The registers also hold other
> + * controls, but no other driver writes the reset registers.
> + */
> +static int __init zx_top_reset_init(struct device_node *np,
> + void __iomem *base)
Why is this initialized from CLK_OF_DECLARE_DRIVER()?
Surely USB resets are not required early?
> +{
> + struct zx_reset_data *rst;
> + int ret;
> +
> + rst = kzalloc_obj(*rst, GFP_KERNEL);
> + if (!rst)
> + return -ENOMEM;
> +
> + rst->lock = &zx_top_lock;
> + rst->base = base;
> + rst->rcdev.ops = &zx_reset_ops;
> + rst->rcdev.owner = THIS_MODULE;
> + rst->rcdev.nr_resets = ARRAY_SIZE(zx279128s_top_resets);
> + rst->rcdev.of_node = np;
> +
> + ret = reset_controller_register(&rst->rcdev);
> + if (ret)
> + kfree(rst);
> +
> + return ret;
> +}
[...]
> +static void __init zx279128s_topcrm_init(struct device_node *np)
> +{
[...]
> +}
> +
> +CLK_OF_DECLARE_DRIVER(zx279128s_topcrm, "zte,zx279128s-topcrm",
> + zx279128s_topcrm_init);
[...]
> +struct zx_crm2_reset_data {
> + struct reset_controller_dev rcdev;
> + void __iomem *base;
> + spinlock_t *lock;
> +};
This is exactly the same as zx_reset_data, why duplicate it?
Same comments as above apply here.
regards
Philipp
More information about the linux-arm-kernel
mailing list