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 - A few clocks are marked as critical because they don't have consumers in the common clock framework: - CPU clock - timer clocks, because the timer-npcm7xx driver (in its current shape) can't depend on a platform driver (clk-wpcm450), but the timer clocks must not be disabled 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@xxxxxxx> Reviewed-by: Joel Stanley <joel@xxxxxxxxx> --- v12: - Switch ref clock inputs to .fw_name = "ref" - Use .hw to refer to internal clocks - Remove unnecessary pr_fmt definition - Convert to a platform driver, to avoid use of .name; use devm_ API - Mark timer0/1 clocks as critical - Update copyright year to 2024 - Split reset controller initialization into a separate function - Change MODULE_LICENSE to "GPL" according to commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity") v11: - no changes v10: - select RESET_{CONTROLLER,SIMPLE} from CLK_WPCM450 instead of messing with the 'default' statement v9: - Apply comments made by Stephen Boyd - Move to drivers/clk/nuvoton/ directory - Update SPDX license identifier from GPL-2.0 to GPL-2.0-only - Rename clk_np variable to np - Use of_clk_hw_register - Refer to clock parents by .fw_name v8: - https://lore.kernel.org/lkml/20230428190226.1304326-3-j.neuschaefer@xxxxxxx/ - Use %pe format specifier throughout the driver, as suggested by Philipp Zabel - Add Joel's R-b v7: - https://lore.kernel.org/lkml/20230422220240.322572-3-j.neuschaefer@xxxxxxx/ - Simplify error handling by not deallocating resources v6: - Enable RESET_SIMPLE based on ARCH_WPCM450, not ARCH_NPCM, as suggested by Tomer Maimon v5: - https://lore.kernel.org/lkml/20221104161850.2889894-6-j.neuschaefer@xxxxxxx/ - Switch to using clk_parent_data v4: - Fix reset controller initialization v3: - Change reference clock name from "refclk" to "ref" - Remove unused variable in return path of wpcm450_clk_register_pll - Remove unused divisor tables v2: - no changes --- drivers/clk/Makefile | 2 +- drivers/clk/nuvoton/Kconfig | 10 +- drivers/clk/nuvoton/Makefile | 1 + drivers/clk/nuvoton/clk-wpcm450.c | 455 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 466 insertions(+), 2 deletions(-) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 14fa8d4ecc1fbe..cdeb2ecf3a8e99 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -107,7 +107,7 @@ endif obj-y += mstar/ obj-y += mvebu/ obj-$(CONFIG_ARCH_MXS) += mxs/ -obj-$(CONFIG_ARCH_MA35) += nuvoton/ +obj-y += nuvoton/ obj-$(CONFIG_COMMON_CLK_NXP) += nxp/ obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pistachio/ obj-$(CONFIG_COMMON_CLK_PXA) += pxa/ diff --git a/drivers/clk/nuvoton/Kconfig b/drivers/clk/nuvoton/Kconfig index fe4b7f62f46704..908881654b2e91 100644 --- a/drivers/clk/nuvoton/Kconfig +++ b/drivers/clk/nuvoton/Kconfig @@ -3,7 +3,7 @@ config COMMON_CLK_NUVOTON bool "Nuvoton clock controller common support" - depends on ARCH_MA35 || COMPILE_TEST + depends on ARCH_MA35 || ARCH_NPCM || COMPILE_TEST default y help Say y here to enable common clock controller for Nuvoton platforms. @@ -16,4 +16,12 @@ config CLK_MA35D1 help Build the clock controller driver for MA35D1 SoC. +config CLK_WPCM450 + bool "Nuvoton WPCM450 clock/reset controller support" + default y + select RESET_CONTROLLER + select RESET_SIMPLE + help + Build the clock and reset controller driver for the WPCM450 SoC. + endif diff --git a/drivers/clk/nuvoton/Makefile b/drivers/clk/nuvoton/Makefile index c3c59dd9f2aaab..b130f0d3889ca0 100644 --- a/drivers/clk/nuvoton/Makefile +++ b/drivers/clk/nuvoton/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_CLK_MA35D1) += clk-ma35d1.o obj-$(CONFIG_CLK_MA35D1) += clk-ma35d1-divider.o obj-$(CONFIG_CLK_MA35D1) += clk-ma35d1-pll.o +obj-$(CONFIG_CLK_WPCM450) += clk-wpcm450.o diff --git a/drivers/clk/nuvoton/clk-wpcm450.c b/drivers/clk/nuvoton/clk-wpcm450.c new file mode 100644 index 00000000000000..9a0f9c0bd8e6c8 --- /dev/null +++ b/drivers/clk/nuvoton/clk-wpcm450.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Nuvoton WPCM450 clock and reset controller driver. + * + * Copyright (C) 2024 Jonathan Neuschäfer <j.neuschaefer@xxxxxxx> + */ + +#include <linux/bitfield.h> +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/reset-controller.h> +#include <linux/reset/reset-simple.h> +#include <linux/slab.h> +#include <linux/platform_device.h> + +#include <dt-bindings/clock/nuvoton,wpcm450-clk.h> + +struct wpcm450_clk_pll { + struct clk_hw hw; + void __iomem *pllcon; + u8 flags; +}; + +#define to_wpcm450_clk_pll(_hw) container_of(_hw, struct wpcm450_clk_pll, hw) + +#define PLLCON_FBDV GENMASK(24, 16) +#define PLLCON_PRST BIT(13) +#define PLLCON_PWDEN BIT(12) +#define PLLCON_OTDV GENMASK(10, 8) +#define PLLCON_INDV GENMASK(5, 0) + +static unsigned long wpcm450_clk_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct wpcm450_clk_pll *pll = to_wpcm450_clk_pll(hw); + unsigned long fbdv, indv, otdv; + u64 rate; + u32 pllcon; + + if (parent_rate == 0) + return 0; + + pllcon = readl_relaxed(pll->pllcon); + + indv = FIELD_GET(PLLCON_INDV, pllcon) + 1; + fbdv = FIELD_GET(PLLCON_FBDV, pllcon) + 1; + otdv = FIELD_GET(PLLCON_OTDV, pllcon) + 1; + + rate = (u64)parent_rate * fbdv; + do_div(rate, indv * otdv); + + return rate; +} + +static int wpcm450_clk_pll_is_enabled(struct clk_hw *hw) +{ + struct wpcm450_clk_pll *pll = to_wpcm450_clk_pll(hw); + u32 pllcon; + + pllcon = readl_relaxed(pll->pllcon); + + return !(pllcon & PLLCON_PRST); +} + +static void wpcm450_clk_pll_disable(struct clk_hw *hw) +{ + struct wpcm450_clk_pll *pll = to_wpcm450_clk_pll(hw); + u32 pllcon; + + pllcon = readl_relaxed(pll->pllcon); + pllcon |= PLLCON_PRST | PLLCON_PWDEN; + writel(pllcon, pll->pllcon); +} + +static const struct clk_ops wpcm450_clk_pll_ops = { + .recalc_rate = wpcm450_clk_pll_recalc_rate, + .is_enabled = wpcm450_clk_pll_is_enabled, + .disable = wpcm450_clk_pll_disable +}; + +static struct clk_hw * +wpcm450_clk_register_pll(struct device *dev, void __iomem *pllcon, const char *name, + const struct clk_parent_data *parent, unsigned long flags) +{ + struct wpcm450_clk_pll *pll; + struct clk_init_data init = {}; + int ret; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &wpcm450_clk_pll_ops; + init.parent_data = parent; + init.num_parents = 1; + init.flags = flags; + + pll->pllcon = pllcon; + pll->hw.init = &init; + + ret = devm_clk_hw_register(dev, &pll->hw); + if (ret) { + devm_kfree(dev, pll); + return ERR_PTR(ret); + } + + return &pll->hw; +} + +// Additional clock indexes for internal use. +enum { + WPCM450_CLK_REF = -1, + WPCM450_CLK_REFDIV2 = WPCM450_NUM_CLKS, + WPCM450_CLK_PLL0, + WPCM450_CLK_PLL1, + WPCM450_CLK_CPUSEL, + WPCM450_CLK_CLKOUT, + WPCM450_CLK_UARTSEL, + WPCM450_CLK_HUARTSEL, + WPCM450_CLK_CPU, + WPCM450_CLK_ADCDIV, + WPCM450_CLK_APB, + WPCM450_CLK_AHB, + WPCM450_CLK_AHB3, + WPCM450_CLK_UART, + WPCM450_NUM_CLKS_TOTAL +}; + +static struct clk_parent_data index_to_parent_data(struct clk_hw **hws, int index) +{ + struct clk_parent_data parent_data = {}; + + if (index == WPCM450_CLK_REF) + parent_data.fw_name = "ref"; + else + parent_data.hw = hws[index]; + + return parent_data; +} + +static size_t index_to_parent_data_array(struct clk_hw **hws, + const int *indexes, size_t num_indexes, + struct clk_parent_data *parent_data, + size_t num_parent_data) +{ + size_t i; + + for (i = 0; i < min(num_parent_data, num_indexes); i++) + parent_data[i] = index_to_parent_data(hws, indexes[i]); + + return i; +} + +#define REG_CLKEN 0x00 +#define REG_CLKSEL 0x04 +#define REG_CLKDIV 0x08 +#define REG_PLLCON0 0x0c +#define REG_PLLCON1 0x10 +#define REG_PMCON 0x14 +#define REG_IRQWAKECON 0x18 +#define REG_IRQWAKEFLAG 0x1c +#define REG_IPSRST 0x20 + +struct wpcm450_pll_data { + const char *name; + int index; + int parent; + unsigned int reg; + unsigned long flags; +}; + +static const struct wpcm450_pll_data pll_data[] = { + { "pll0", WPCM450_CLK_PLL0, WPCM450_CLK_REF, REG_PLLCON0, 0 }, + { "pll1", WPCM450_CLK_PLL1, WPCM450_CLK_REF, REG_PLLCON1, 0 }, +}; + +struct wpcm450_clksel_data { + const char *name; + int index; + const int *parents; + unsigned int num_parents; + const u32 *table; + int shift; + int width; + unsigned long flags; +}; + +static const u32 parent_table[] = { 0, 1, 2 }; + +static const int default_parents[] = { + WPCM450_CLK_PLL0, + WPCM450_CLK_PLL1, + WPCM450_CLK_REF, +}; + +static const int huart_parents[] = { + WPCM450_CLK_REF, + WPCM450_CLK_REFDIV2, +}; + +static const struct wpcm450_clksel_data clksel_data[] = { + { "cpusel", WPCM450_CLK_CPUSEL, default_parents, ARRAY_SIZE(default_parents), + parent_table, 0, 2, CLK_IS_CRITICAL }, + { "clkout", WPCM450_CLK_CLKOUT, default_parents, ARRAY_SIZE(default_parents), + parent_table, 2, 2, 0 }, + { "usbphy", WPCM450_CLK_USBPHY, default_parents, ARRAY_SIZE(default_parents), + parent_table, 6, 2, 0 }, + { "uartsel", WPCM450_CLK_UARTSEL, default_parents, ARRAY_SIZE(default_parents), + parent_table, 8, 2, 0 }, + { "huartsel", WPCM450_CLK_HUARTSEL, huart_parents, ARRAY_SIZE(huart_parents), + parent_table, 10, 1, 0 }, +}; + +static const struct clk_div_table div_fixed2[] = { + { .val = 0, .div = 2 }, + { } +}; + +struct wpcm450_clkdiv_data { + const char *name; + int index; + int parent; + int div_flags; + const struct clk_div_table *table; + int shift; + int width; + unsigned long flags; +}; + +static struct wpcm450_clkdiv_data clkdiv_data_early[] = { + { "ref/2", WPCM450_CLK_REFDIV2, WPCM450_CLK_REF, 0, div_fixed2, 0, 0 }, +}; + +static const struct wpcm450_clkdiv_data clkdiv_data[] = { + { "cpu", WPCM450_CLK_CPU, WPCM450_CLK_CPUSEL, 0, div_fixed2, 0, 0, CLK_IS_CRITICAL }, + { "adcdiv", WPCM450_CLK_ADCDIV, WPCM450_CLK_REF, CLK_DIVIDER_POWER_OF_TWO, NULL, 28, 2, 0 }, + { "ahb", WPCM450_CLK_AHB, WPCM450_CLK_CPU, CLK_DIVIDER_POWER_OF_TWO, NULL, 24, 2, 0 }, + { "apb", WPCM450_CLK_APB, WPCM450_CLK_AHB, CLK_DIVIDER_POWER_OF_TWO, NULL, 26, 2, 0 }, + { "uart", WPCM450_CLK_UART, WPCM450_CLK_UARTSEL, 0, NULL, 16, 4, 0 }, + { "ahb3", WPCM450_CLK_AHB3, WPCM450_CLK_AHB, CLK_DIVIDER_POWER_OF_TWO, NULL, 8, 2, 0 }, +}; + +struct wpcm450_clken_data { + const char *name; + int index; + int parent; + unsigned long flags; +}; + +static const struct wpcm450_clken_data clken_data[] = { + { "fiu", WPCM450_CLK_FIU, WPCM450_CLK_AHB3, 0 }, + { "xbus", WPCM450_CLK_XBUS, WPCM450_CLK_AHB3, 0 }, + { "kcs", WPCM450_CLK_KCS, WPCM450_CLK_APB, 0 }, + { "shm", WPCM450_CLK_SHM, WPCM450_CLK_AHB3, 0 }, + { "usb1", WPCM450_CLK_USB1, WPCM450_CLK_AHB, 0 }, + { "emc0", WPCM450_CLK_EMC0, WPCM450_CLK_AHB, 0 }, + { "emc1", WPCM450_CLK_EMC1, WPCM450_CLK_AHB, 0 }, + { "usb0", WPCM450_CLK_USB0, WPCM450_CLK_AHB, 0 }, + { "peci", WPCM450_CLK_PECI, WPCM450_CLK_APB, 0 }, + { "aes", WPCM450_CLK_AES, WPCM450_CLK_APB, 0 }, + { "uart0", WPCM450_CLK_UART0, WPCM450_CLK_UART, 0 }, + { "uart1", WPCM450_CLK_UART1, WPCM450_CLK_UART, 0 }, + { "smb2", WPCM450_CLK_SMB2, WPCM450_CLK_APB, 0 }, + { "smb3", WPCM450_CLK_SMB3, WPCM450_CLK_APB, 0 }, + { "smb4", WPCM450_CLK_SMB4, WPCM450_CLK_APB, 0 }, + { "smb5", WPCM450_CLK_SMB5, WPCM450_CLK_APB, 0 }, + { "huart", WPCM450_CLK_HUART, WPCM450_CLK_HUARTSEL, 0 }, + { "pwm", WPCM450_CLK_PWM, WPCM450_CLK_APB, 0 }, + { "timer0", WPCM450_CLK_TIMER0, WPCM450_CLK_REFDIV2, CLK_IS_CRITICAL }, + { "timer1", WPCM450_CLK_TIMER1, WPCM450_CLK_REFDIV2, CLK_IS_CRITICAL }, + { "timer2", WPCM450_CLK_TIMER2, WPCM450_CLK_REFDIV2, 0 }, + { "timer3", WPCM450_CLK_TIMER3, WPCM450_CLK_REFDIV2, 0 }, + { "timer4", WPCM450_CLK_TIMER4, WPCM450_CLK_REFDIV2, 0 }, + { "mft0", WPCM450_CLK_MFT0, WPCM450_CLK_APB, 0 }, + { "mft1", WPCM450_CLK_MFT1, WPCM450_CLK_APB, 0 }, + { "wdt", WPCM450_CLK_WDT, WPCM450_CLK_REFDIV2, 0 }, + { "adc", WPCM450_CLK_ADC, WPCM450_CLK_ADCDIV, 0 }, + { "sdio", WPCM450_CLK_SDIO, WPCM450_CLK_AHB, 0 }, + { "sspi", WPCM450_CLK_SSPI, WPCM450_CLK_APB, 0 }, + { "smb0", WPCM450_CLK_SMB0, WPCM450_CLK_APB, 0 }, + { "smb1", WPCM450_CLK_SMB1, WPCM450_CLK_APB, 0 }, +}; + +static DEFINE_SPINLOCK(wpcm450_clk_lock); + +static int wpcm450_reset_probe(struct platform_device *pdev, void __iomem *clk_base) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct reset_simple_data *reset; + + reset = devm_kzalloc(dev, sizeof(*reset), GFP_KERNEL); + if (!reset) + return -ENOMEM; + + reset->rcdev.owner = THIS_MODULE; + reset->rcdev.nr_resets = WPCM450_NUM_RESETS; + reset->rcdev.ops = &reset_simple_ops; + reset->rcdev.of_node = np; + reset->membase = clk_base + REG_IPSRST; + return devm_reset_controller_register(dev, &reset->rcdev); +} + +static int wpcm450_clk_probe(struct platform_device *pdev) +{ + struct clk_hw_onecell_data *clk_data; + struct clk_hw **hws; + struct clk_hw *hw; + void __iomem *clk_base; + int i, ret; + + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + resource_size_t map_size; + + clk_base = devm_of_iomap(dev, np, 0, &map_size); + if (IS_ERR(clk_base)) { + dev_err(dev, "failed to map registers\n"); + return PTR_ERR(clk_base); + } + + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, WPCM450_NUM_CLKS_TOTAL), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->num = WPCM450_NUM_CLKS; + hws = clk_data->hws; + + for (i = 0; i < WPCM450_NUM_CLKS_TOTAL; 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]; + struct clk_parent_data parent = index_to_parent_data(hws, data->parent); + + hw = wpcm450_clk_register_pll(dev, clk_base + data->reg, data->name, + &parent, data->flags); + if (IS_ERR(hw)) { + dev_err(dev, "Failed to register PLL: %pe\n", hw); + return PTR_ERR(hw); + } + clk_data->hws[data->index] = hw; + } + + /* Early divisors (REF/2) */ + for (i = 0; i < ARRAY_SIZE(clkdiv_data_early); i++) { + const struct wpcm450_clkdiv_data *data = &clkdiv_data_early[i]; + struct clk_parent_data parent = index_to_parent_data(hws, data->parent); + + hw = devm_clk_hw_register_divider_table_parent_data(dev, data->name, &parent, + data->flags, + clk_base + REG_CLKDIV, + data->shift, data->width, + data->div_flags, data->table, + &wpcm450_clk_lock); + + if (IS_ERR(hw)) { + dev_err(dev, "Failed to register div table: %pe\n", hw); + return PTR_ERR(hw); + } + clk_data->hws[data->index] = hw; + } + + /* Selects/muxes */ + for (i = 0; i < ARRAY_SIZE(clksel_data); i++) { + const struct wpcm450_clksel_data *data = &clksel_data[i]; + struct clk_parent_data parents[4]; + size_t num_parents = index_to_parent_data_array(hws, + data->parents, data->num_parents, parents, + ARRAY_SIZE(parents)); + + hw = devm_clk_hw_register_mux_parent_data_table(dev, data->name, parents, + num_parents, data->flags, + clk_base + REG_CLKSEL, data->shift, + data->width, 0, NULL, + &wpcm450_clk_lock); + + if (IS_ERR(hw)) { + dev_err(dev, "Failed to register mux: %pe\n", hw); + return PTR_ERR(hw); + } + 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]; + struct clk_parent_data parent = index_to_parent_data(hws, data->parent); + + hw = devm_clk_hw_register_divider_table_parent_data(dev, data->name, &parent, + data->flags, + clk_base + REG_CLKDIV, + data->shift, data->width, + data->div_flags, data->table, + &wpcm450_clk_lock); + + if (IS_ERR(hw)) { + dev_err(dev, "Failed to register divider: %pe\n", hw); + return PTR_ERR(hw); + } + clk_data->hws[data->index] = hw; + } + + /* Enables/gates */ + for (i = 0; i < ARRAY_SIZE(clken_data); i++) { + const struct wpcm450_clken_data *data = &clken_data[i]; + struct clk_parent_data parent = index_to_parent_data(hws, data->parent); + + hw = devm_clk_hw_register_gate_parent_data(dev, data->name, &parent, data->flags, + clk_base + REG_CLKEN, data->index, + data->flags, &wpcm450_clk_lock); + if (IS_ERR(hw)) { + dev_err(dev, "Failed to register gate: %pe\n", hw); + return PTR_ERR(hw); + } + clk_data->hws[data->index] = hw; + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); + if (ret) + dev_err(dev, "Failed to add DT provider: %pe\n", ERR_PTR(ret)); + + ret = wpcm450_reset_probe(pdev, clk_base); + if (ret) + dev_err(dev, "Failed to register reset controller: %pe\n", ERR_PTR(ret)); + + return 0; +} + +static const struct of_device_id wpcm450_of_match[] = { + { .compatible = "nuvoton,wpcm450-clk" }, + {} +}; +MODULE_DEVICE_TABLE(of, wpcm450_of_match); + +static struct platform_driver wpcm450_clk_driver = { + .driver = { + .name = "wpcm450-clk", + .of_match_table = wpcm450_of_match, + }, + .probe = wpcm450_clk_probe, +}; +module_platform_driver(wpcm450_clk_driver); + +MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@xxxxxxx>"); +MODULE_DESCRIPTION("Nuvoton WPCM450 clock and reset controller driver"); +MODULE_LICENSE("GPL"); -- 2.43.0