[PATCH v6 2/2] clk: wpcm450: Add Nuvoton WPCM450 clock/reset controller driver
Christophe JAILLET
christophe.jaillet at wanadoo.fr
Sat Apr 15 22:16:09 AEST 2023
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 at public.gmane.org>
> ---
[...]
> +
> +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
>
>
More information about the openbmc
mailing list