Re: [PATCH v6 2/2] clk: wpcm450: Add Nuvoton WPCM450 clock/reset controller driver

From: Christophe JAILLET
Date: Sat Apr 15 2023 - 08:16:23 EST


Le 15/04/2023 à 13:13, Jonathan Neuschäfer a écrit :
This driver implements the following features w.r.t. the clock and reset
controller in the WPCM450 SoC:

- It calculates the rates for all clocks managed by the clock controller
- It leaves the clock tree mostly unchanged, except that it enables/
disables clock gates based on usage.
- It exposes the reset lines managed by the controller using the
Generic Reset Controller subsystem

NOTE: If the driver and the corresponding devicetree node are present,
the driver will disable "unused" clocks. This is problem until
the clock relations are properly declared in the devicetree (in a
later patch). Until then, the clk_ignore_unused kernel parameter
can be used as a workaround.

Signed-off-by: Jonathan Neuschäfer <j.neuschaefer-hi6Y0CQ0nG0@xxxxxxxxxxxxxxxx>
---

[...]

+
+static void __init wpcm450_clk_init(struct device_node *clk_np)
+{
+ struct clk_hw_onecell_data *clk_data;
+ static struct clk_hw **hws;
+ static struct clk_hw *hw;
+ void __iomem *clk_base;
+ int i, ret;
+ struct reset_simple_data *reset;
+
+ clk_base = of_iomap(clk_np, 0);
+ if (!clk_base) {
+ pr_err("%pOFP: failed to map registers\n", clk_np);
+ of_node_put(clk_np);
+ return;
+ }
+ of_node_put(clk_np);
+
+ clk_data = kzalloc(struct_size(clk_data, hws, WPCM450_NUM_CLKS), GFP_KERNEL);
+ if (!clk_data)
+ goto err_unmap;
+
+ clk_data->num = WPCM450_NUM_CLKS;
+ hws = clk_data->hws;
+
+ for (i = 0; i < WPCM450_NUM_CLKS; i++)
+ hws[i] = ERR_PTR(-ENOENT);
+
+ // PLLs
+ for (i = 0; i < ARRAY_SIZE(pll_data); i++) {
+ const struct wpcm450_pll_data *data = &pll_data[i];
+
+ hw = wpcm450_clk_register_pll(clk_base + data->reg, data->name,
+ &data->parent, data->flags);
+ if (IS_ERR(hw)) {
+ pr_info("Failed to register PLL: %pe", hw);
+ goto err_free;
+ }
+ }
+
+ // Early divisors (REF/2)
+ for (i = 0; i < ARRAY_SIZE(clkdiv_data_early); i++) {
+ const struct wpcm450_clkdiv_data *data = &clkdiv_data_early[i];
+
+ hw = clk_hw_register_divider_table_parent_data(NULL, data->name, &data->parent,
+ data->flags, clk_base + REG_CLKDIV,
+ data->shift, data->width,
+ data->div_flags, data->table,
+ &wpcm450_clk_lock);
+ if (IS_ERR(hw)) {
+ pr_err("Failed to register div table: %pe\n", hw);
+ goto err_free;
+ }
+ }
+
+ // Selects/muxes
+ for (i = 0; i < ARRAY_SIZE(clksel_data); i++) {
+ const struct wpcm450_clksel_data *data = &clksel_data[i];
+
+ hw = clk_hw_register_mux_parent_data(NULL, data->name, data->parents,
+ data->num_parents, data->flags,
+ clk_base + REG_CLKSEL, data->shift,
+ data->width, 0,
+ &wpcm450_clk_lock);
+ if (IS_ERR(hw)) {
+ pr_err("Failed to register mux: %pe\n", hw);
+ goto err_free;
+ }
+ if (data->index >= 0)
+ clk_data->hws[data->index] = hw;
+ }
+
+ // Divisors
+ for (i = 0; i < ARRAY_SIZE(clkdiv_data); i++) {
+ const struct wpcm450_clkdiv_data *data = &clkdiv_data[i];
+
+ hw = clk_hw_register_divider_table_parent_data(NULL, data->name, &data->parent,
+ data->flags, clk_base + REG_CLKDIV,
+ data->shift, data->width,
+ data->div_flags, data->table,
+ &wpcm450_clk_lock);
+ if (IS_ERR(hw)) {
+ pr_err("Failed to register divider: %pe\n", hw);
+ goto err_free;
+ }
+ }
+
+ // Enables/gates
+ for (i = 0; i < ARRAY_SIZE(clken_data); i++) {
+ const struct wpcm450_clken_data *data = &clken_data[i];
+
+ hw = clk_hw_register_gate_parent_data(NULL, data->name, &data->parent, data->flags,
+ clk_base + REG_CLKEN, data->bitnum,
+ data->flags, &wpcm450_clk_lock);

If an error occures in the 'for' loop or after it, should this be clk_hw_unregister_gate()'ed somewhere?

CJ

+ if (IS_ERR(hw)) {
+ pr_err("Failed to register gate: %pe\n", hw);
+ goto err_free;
+ }
+ clk_data->hws[data->bitnum] = hw;
+ }
+
+ ret = of_clk_add_hw_provider(clk_np, of_clk_hw_onecell_get, clk_data);
+ if (ret)
+ pr_err("Failed to add DT provider: %d\n", ret);
+
+ // Reset controller
+ reset = kzalloc(sizeof(*reset), GFP_KERNEL);
+ if (!reset)
+ goto err_free;
+ reset->rcdev.owner = THIS_MODULE;
+ reset->rcdev.nr_resets = WPCM450_NUM_RESETS;
+ reset->rcdev.ops = &reset_simple_ops;
+ reset->rcdev.of_node = clk_np;
+ reset->membase = clk_base + REG_IPSRST;
+ ret = reset_controller_register(&reset->rcdev);
+ if (ret)
+ pr_err("Failed to register reset controller: %d\n", ret);
+
+ of_node_put(clk_np);
+ return;
+
+err_free:
+ kfree(clk_data->hws);
+err_unmap:
+ iounmap(clk_base);
+ of_node_put(clk_np);
+}
+
+CLK_OF_DECLARE(wpcm450_clk_init, "nuvoton,wpcm450-clk", wpcm450_clk_init);
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 2a52c990d4fec..16e111d213560 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -208,7 +208,7 @@ config RESET_SCMI

config RESET_SIMPLE
bool "Simple Reset Controller Driver" if COMPILE_TEST || EXPERT
- default ARCH_ASPEED || ARCH_BCMBCA || ARCH_BITMAIN || ARCH_REALTEK || ARCH_STM32 || (ARCH_INTEL_SOCFPGA && ARM64) || ARCH_SUNXI || ARC
+ default ARCH_ASPEED || ARCH_BCMBCA || ARCH_BITMAIN || ARCH_REALTEK || ARCH_STM32 || (ARCH_INTEL_SOCFPGA && ARM64) || ARCH_SUNXI || ARC || ARCH_WPCM450
depends on HAS_IOMEM
help
This enables a simple reset controller driver for reset lines that
--
2.39.2