[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