clk: zte: Add Clock registration infrastructure

The next patches will implement the regmap clocks and PLL driver. The
actual hardware specific clock listing will live in a separate module.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
Signed-off-by: Brian Masney <bmasney@redhat.com>
This commit is contained in:
Stefan Dösinger 2026-09-27 23:13:34 +03:00 committed by Brian Masney
parent 244539a22f
commit 8a9b53cc13
9 changed files with 404 additions and 0 deletions

View File

@ -3914,6 +3914,7 @@ F: Documentation/devicetree/bindings/clock/zte,zx297520v3-matrixcrm.yaml
F: Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
F: drivers/clk/zte/
F: include/dt-bindings/clock/zte,zx297520v3-clk.h
F: include/dt-bindings/phy/zte,zx297520v3-topcrm.h
F: include/dt-bindings/reset/zte,zx297520v3-reset.h

View File

@ -552,6 +552,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

View File

@ -183,5 +183,6 @@ ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_X86) += x86/
endif
obj-y += xilinx/
obj-$(CONFIG_COMMON_CLK_ZTE) += zte/
obj-$(CONFIG_ARCH_ZYNQ) += zynq/
obj-$(CONFIG_COMMON_CLK_ZYNQMP) += zynqmp/

17
drivers/clk/zte/Kconfig Normal file
View File

@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# ZTE Clock Drivers
#
config COMMON_CLK_ZTE
tristate "Clock driver for ZTE SoCs"
depends on HAS_IOMEM
depends on ARCH_ZTE || COMPILE_TEST
default ARCH_ZTE
select MFD_SYSCON
help
This option selects common clock infrastructure for ZTE based SoCs.
You will need to enable one or more SoC specific drivers to make use
of this.
Enable this if you are building a kernel for a ZTE designed board.

5
drivers/clk/zte/Makefile Normal file
View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_COMMON_CLK_ZTE) += clk-zte.o
clk-zte-y += clk-zx.o pll-zx.o clk-regmap.o

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2026 Stefan Dösinger
*/
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/regmap.h>
#include "clk-zx.h"
struct clk_hw *zx_clk_register_gate(struct device *dev, struct regmap *regmap,
const struct zx_gate_desc *desc,
struct clk_hw * const *clocks)
{
return ERR_PTR(-ENODEV);
}
struct clk_hw *zx_clk_register_divider(struct device *dev, struct regmap *regmap,
const struct zx_div_desc *desc,
struct clk_hw * const *clocks)
{
return ERR_PTR(-ENODEV);
}
struct clk_hw *zx_clk_register_mux(struct device *dev, struct regmap *regmap,
const struct zx_mux_desc *desc,
struct clk_hw * const *clocks)
{
return ERR_PTR(-ENODEV);
}

192
drivers/clk/zte/clk-zx.c Normal file
View File

@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2026 Stefan Dösinger
*/
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include "clk-zx.h"
static struct clk_hw *zx_clk_register_fixed_div(struct device *dev,
const struct zx_fixed_divider_desc *desc,
struct clk_hw * const *clocks)
{
const struct zx_parent_desc *p = &desc->parent;
switch (p->type) {
case ZX_PARENT_FW:
return devm_clk_hw_register_fixed_factor_fwname(dev, NULL, desc->name, p->fw_name,
CLK_SET_RATE_PARENT, 1, desc->div);
case ZX_PARENT_ID:
return devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name, clocks[p->id],
CLK_SET_RATE_PARENT, 1,
desc->div);
}
WARN_ON_ONCE(1);
return ERR_PTR(-EINVAL);
}
static int zx_clk_validate(struct device *dev, struct device_node *of_node,
const struct zx_clk_data *data)
{
const struct zx_parent_desc *parents;
unsigned int i, p, num_parents;
struct clk *clk;
/*
* Sanity check: Make sure all parents are there and write a clear message rather than
* leave potential orphans.
*
* This check is unusual in clock drivers. It is in this driver because the clock tree is
* based on manual reverse engineering rather than generated from a SoC datasheet.
*/
for (i = 0; i < data->num_clocks; ++i) {
switch (data->clocks[i].type) {
case ZX_CLOCK_PLL:
parents = data->clocks[i].pll.parents;
num_parents = data->clocks[i].pll.num_parents;
break;
case ZX_CLOCK_FIXED_DIV:
parents = &data->clocks[i].fixed_div.parent;
num_parents = 1;
break;
case ZX_CLOCK_MUX:
parents = data->clocks[i].mux.parents;
num_parents = data->clocks[i].mux.num_parents;
break;
case ZX_CLOCK_DIV:
parents = &data->clocks[i].div.parent;
num_parents = 1;
break;
case ZX_CLOCK_GATE:
parents = &data->clocks[i].gate.parent;
num_parents = 1;
break;
default:
return dev_err_probe(dev, -EINVAL, "Invalid clock entry %u\n", i);
}
for (p = 0; p < num_parents; ++p) {
switch (parents[p].type) {
case ZX_PARENT_FW:
clk = of_clk_get_by_name(of_node, parents[p].fw_name);
if (IS_ERR(clk))
return dev_err_probe(dev, PTR_ERR(clk),
"Input clk %s failure\n",
parents[p].fw_name);
clk_put(clk);
break;
case ZX_PARENT_ID:
if (parents[p].id >= i)
return dev_err_probe(dev, -EINVAL,
"Clock %u has parent %u\n",
i, parents[p].id);
break;
default:
return dev_err_probe(dev, -EINVAL,
"Clock %u has unexpected parent of type %u\n",
i, parents[p].type);
}
}
}
return 0;
}
int zx_clk_common_probe(struct device *dev, struct device_node *of_node,
const struct zx_clk_data *data)
{
struct clk_hw_onecell_data *exports;
struct clk_hw **clocks;
struct regmap *map;
unsigned int i;
int res;
res = zx_clk_validate(dev, of_node, data);
if (res)
return res;
map = device_node_to_regmap(of_node);
if (IS_ERR(map))
return PTR_ERR(map);
clocks = devm_kcalloc(dev, data->num_clocks, sizeof(*clocks), GFP_KERNEL);
if (!clocks)
return -ENOMEM;
if (data->init) {
res = data->init(map);
if (res)
return res;
}
for (i = 0; i < data->num_clocks; ++i) {
struct clk_hw *hw;
switch (data->clocks[i].type) {
case ZX_CLOCK_PLL:
hw = zx_clk_register_pll(dev, map, &data->clocks[i].pll, clocks);
break;
case ZX_CLOCK_FIXED_DIV:
hw = zx_clk_register_fixed_div(dev, &data->clocks[i].fixed_div, clocks);
break;
case ZX_CLOCK_MUX:
hw = zx_clk_register_mux(dev, map, &data->clocks[i].mux, clocks);
break;
case ZX_CLOCK_DIV:
hw = zx_clk_register_divider(dev, map, &data->clocks[i].div, clocks);
break;
case ZX_CLOCK_GATE:
hw = zx_clk_register_gate(dev, map, &data->clocks[i].gate, clocks);
break;
default:
return -EINVAL;
}
if (IS_ERR(hw))
return dev_err_probe(dev, PTR_ERR(hw), "Failed to register clk %u\n", i);
clocks[i] = hw;
}
exports = devm_kzalloc(dev, struct_size(exports, hws, data->num_exports), GFP_KERNEL);
if (!exports)
return -ENOMEM;
exports->num = data->num_exports;
for (i = 0; i < data->num_exports; ++i) {
if (data->exports[i] >= data->num_clocks)
return dev_err_probe(dev, -EINVAL,
"Export %u points to out of range clock %u\n",
i, data->exports[i]);
exports->hws[i] = clocks[data->exports[i]];
}
devm_kfree(dev, clocks);
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, exports);
}
EXPORT_SYMBOL_NS_GPL(zx_clk_common_probe, "ZTE_CLK");
MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
MODULE_DESCRIPTION("ZTE common clock driver");
MODULE_LICENSE("GPL");

137
drivers/clk/zte/clk-zx.h Normal file
View File

@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2026 Stefan Dösinger
*/
#ifndef __DRV_CLK_ZX_H
#define __DRV_CLK_ZX_H
#include <linux/clk-provider.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>
#define CLK_ZX_PLL_PREPARE_IS_ENABLE 1
#define CLK_ZX_MAX_PARENTS 8
enum zx_parent_type {
ZX_PARENT_FW,
ZX_PARENT_ID,
};
struct zx_parent_desc {
enum zx_parent_type type;
union {
const char *fw_name;
unsigned int id;
};
};
#define PARENT_FW(_name) { .type = ZX_PARENT_FW, .fw_name = (_name) }
#define PARENT_ID(_id) { .type = ZX_PARENT_ID, .id = (_id) }
static inline struct clk_parent_data zx_get_parent(const struct zx_parent_desc *p,
struct clk_hw * const *clocks)
{
struct clk_parent_data ret = {};
switch (p->type) {
case ZX_PARENT_FW:
ret.fw_name = p->fw_name;
ret.index = -1;
return ret;
case ZX_PARENT_ID:
ret.hw = clocks[p->id];
ret.index = -1;
return ret;
}
WARN_ON_ONCE(1);
return ret;
}
struct zx_pll_desc {
const char *name;
const struct zx_parent_desc *parents;
unsigned int num_parents;
unsigned long rate;
u16 reg;
u16 flags;
};
struct zx_fixed_divider_desc {
const char *name;
struct zx_parent_desc parent;
unsigned int div;
};
struct zx_mux_desc {
const char *name;
const struct zx_parent_desc *parents;
unsigned int num_parents;
u16 reg;
u8 shift, size;
};
struct zx_div_desc {
const char *name;
struct zx_parent_desc parent;
u16 reg;
u8 shift, size;
};
struct zx_gate_desc {
const char *name;
struct zx_parent_desc parent;
unsigned long flags;
u16 reg;
u8 shift;
};
enum zx_clock_type {
ZX_CLOCK_INVALID = 0,
ZX_CLOCK_PLL,
ZX_CLOCK_FIXED_DIV,
ZX_CLOCK_MUX,
ZX_CLOCK_DIV,
ZX_CLOCK_GATE,
};
struct zx_clock {
enum zx_clock_type type;
union {
struct zx_pll_desc pll;
struct zx_fixed_divider_desc fixed_div;
struct zx_mux_desc mux;
struct zx_div_desc div;
struct zx_gate_desc gate;
};
};
struct clk_hw *zx_clk_register_pll(struct device *dev, struct regmap *regmap,
const struct zx_pll_desc *desc,
struct clk_hw * const *clocks);
struct clk_hw *zx_clk_register_mux(struct device *dev, struct regmap *regmap,
const struct zx_mux_desc *desc,
struct clk_hw * const *clocks);
struct clk_hw *zx_clk_register_divider(struct device *dev, struct regmap *regmap,
const struct zx_div_desc *desc,
struct clk_hw * const *clocks);
struct clk_hw *zx_clk_register_gate(struct device *dev, struct regmap *regmap,
const struct zx_gate_desc *desc,
struct clk_hw * const *clocks);
struct zx_clk_data {
int (*init)(struct regmap *map);
const struct zx_clock *clocks;
unsigned int num_clocks;
const unsigned int *exports;
unsigned int num_exports;
};
int zx_clk_common_probe(struct device *dev, struct device_node *of_node,
const struct zx_clk_data *data);
#endif /* __DRV_CLK_ZX_H */

18
drivers/clk/zte/pll-zx.c Normal file
View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2026 Stefan Dösinger
*/
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/regmap.h>
#include "clk-zx.h"
struct clk_hw *zx_clk_register_pll(struct device *dev, struct regmap *regmap,
const struct zx_pll_desc *desc,
struct clk_hw * const *clocks)
{
return ERR_PTR(-ENODEV);
}