mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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:
parent
244539a22f
commit
8a9b53cc13
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
17
drivers/clk/zte/Kconfig
Normal 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
5
drivers/clk/zte/Makefile
Normal 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
|
||||
32
drivers/clk/zte/clk-regmap.c
Normal file
32
drivers/clk/zte/clk-regmap.c
Normal 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
192
drivers/clk/zte/clk-zx.c
Normal 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
137
drivers/clk/zte/clk-zx.h
Normal 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
18
drivers/clk/zte/pll-zx.c
Normal 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);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user