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