From: "Jonathan Neuschäfer" <j.neuschaefer@gmx.net> To: linux-clk@vger.kernel.org, openbmc@lists.ozlabs.org Cc: linux-kernel@vger.kernel.org, linux-watchdog@vger.kernel.org, devicetree@vger.kernel.org, "Jonathan Neuschäfer" <j.neuschaefer@gmx.net>, "Michael Turquette" <mturquette@baylibre.com>, "Stephen Boyd" <sboyd@kernel.org>, "Rob Herring" <robh+dt@kernel.org>, "Krzysztof Kozlowski" <krzysztof.kozlowski+dt@linaro.org>, "Avi Fishman" <avifishman70@gmail.com>, "Tomer Maimon" <tmaimon77@gmail.com>, "Tali Perry" <tali.perry1@gmail.com>, "Patrick Venture" <venture@google.com>, "Nancy Yuen" <yuenn@google.com>, "Benjamin Fair" <benjaminfair@google.com>, "Daniel Lezcano" <daniel.lezcano@linaro.org>, "Thomas Gleixner" <tglx@linutronix.de>, "Philipp Zabel" <p.zabel@pengutronix.de>, "Wim Van Sebroeck" <wim@linux-watchdog.org>, "Guenter Roeck" <linux@roeck-us.net>, "Christophe JAILLET" <christophe.jaillet@wanadoo.fr>, "Conor Dooley" <conor+dt@kernel.org>, "Joel Stanley" <joel@jms.id.au>, "Arnd Bergmann" <arnd@arndb.de>, "Jacky Huang" <ychuang3@nuvoton.com>, "Krzysztof Kozlowski" <krzysztof.kozlowski@linaro.org> Subject: [PATCH v9 3/3] clk: wpcm450: Add Nuvoton WPCM450 clock/reset controller driver Date: Sat, 23 Sep 2023 16:34:38 +0200 [thread overview] Message-ID: <20230923143438.1895461-4-j.neuschaefer@gmx.net> (raw) In-Reply-To: <20230923143438.1895461-1-j.neuschaefer@gmx.net> 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@gmx.net> Reviewed-by: Joel Stanley <joel@jms.id.au> --- I have considered converting this driver to a platform driver instead of using CLK_OF_DECLARE, because platform drivers are generally the way forward. However, the timer-npcm7xx driver used on the same platform requires is initialized with TIMER_OF_DECLARE and thus requires the clocks to be available earlier than a platform driver can provide them. 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@gmx.net/ - 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@gmx.net/ - 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@gmx.net/ - 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 | 8 +- drivers/clk/nuvoton/Makefile | 1 + drivers/clk/nuvoton/clk-wpcm450.c | 372 ++++++++++++++++++++++++++++++ drivers/reset/Kconfig | 2 +- 5 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 drivers/clk/nuvoton/clk-wpcm450.c diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 18969cbd4bb1e..de51182ef630d 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -106,7 +106,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 fe4b7f62f4670..22c5ab409d235 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,10 @@ config CLK_MA35D1 help Build the clock controller driver for MA35D1 SoC. +config CLK_WPCM450 + bool "Nuvoton WPCM450 clock/reset controller support" + default y + 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 c3c59dd9f2aaa..b130f0d3889ca 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 0000000000000..9100c4b8a5648 --- /dev/null +++ b/drivers/clk/nuvoton/clk-wpcm450.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Nuvoton WPCM450 clock and reset controller driver. + * + * Copyright (C) 2022 Jonathan Neuschäfer <j.neuschaefer@gmx.net> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitfield.h> +#include <linux/clk-provider.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 <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_node *np, 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 = kzalloc(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 = of_clk_hw_register(np, &pll->hw); + if (ret) { + kfree(pll); + return ERR_PTR(ret); + } + + return &pll->hw; +} + +#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; + struct clk_parent_data parent; + unsigned int reg; + unsigned long flags; +}; + +static const struct wpcm450_pll_data pll_data[] = { + { "pll0", { .fw_name = "ref" }, REG_PLLCON0, 0 }, + { "pll1", { .fw_name = "ref" }, REG_PLLCON1, 0 }, +}; + +struct wpcm450_clksel_data { + const char *name; + const struct clk_parent_data *parents; + unsigned int num_parents; + const u32 *table; + int shift; + int width; + int index; + unsigned long flags; +}; + +static const u32 parent_table[] = { 0, 1, 2 }; + +static const struct clk_parent_data default_parents[] = { + { .name = "pll0" }, + { .name = "pll1" }, + { .name = "ref" }, +}; + +static const struct clk_parent_data huart_parents[] = { + { .fw_name = "ref" }, + { .name = "refdiv2" }, +}; + +static const struct wpcm450_clksel_data clksel_data[] = { + { "cpusel", default_parents, ARRAY_SIZE(default_parents), + parent_table, 0, 2, -1, CLK_IS_CRITICAL }, + { "clkout", default_parents, ARRAY_SIZE(default_parents), + parent_table, 2, 2, -1, 0 }, + { "usbphy", default_parents, ARRAY_SIZE(default_parents), + parent_table, 6, 2, -1, 0 }, + { "uartsel", default_parents, ARRAY_SIZE(default_parents), + parent_table, 8, 2, WPCM450_CLK_USBPHY, 0 }, + { "huartsel", huart_parents, ARRAY_SIZE(huart_parents), + parent_table, 10, 1, -1, 0 }, +}; + +static const struct clk_div_table div_fixed2[] = { + { .val = 0, .div = 2 }, + { } +}; + +struct wpcm450_clkdiv_data { + const char *name; + struct clk_parent_data 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[] = { + { "refdiv2", { .name = "ref" }, 0, div_fixed2, 0, 0 }, +}; + +static const struct wpcm450_clkdiv_data clkdiv_data[] = { + { "cpu", { .name = "cpusel" }, 0, div_fixed2, 0, 0, CLK_IS_CRITICAL }, + { "adcdiv", { .name = "ref" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 28, 2, 0 }, + { "apb", { .name = "ahb" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 26, 2, 0 }, + { "ahb", { .name = "cpu" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 24, 2, 0 }, + { "uart", { .name = "uartsel" }, 0, NULL, 16, 4, 0 }, + { "ahb3", { .name = "ahb" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 8, 2, 0 }, +}; + +struct wpcm450_clken_data { + const char *name; + struct clk_parent_data parent; + int bitnum; + unsigned long flags; +}; + +static const struct wpcm450_clken_data clken_data[] = { + { "fiu", { .name = "ahb3" }, WPCM450_CLK_FIU, 0 }, + { "xbus", { .name = "ahb3" }, WPCM450_CLK_XBUS, 0 }, + { "kcs", { .name = "apb" }, WPCM450_CLK_KCS, 0 }, + { "shm", { .name = "ahb3" }, WPCM450_CLK_SHM, 0 }, + { "usb1", { .name = "ahb" }, WPCM450_CLK_USB1, 0 }, + { "emc0", { .name = "ahb" }, WPCM450_CLK_EMC0, 0 }, + { "emc1", { .name = "ahb" }, WPCM450_CLK_EMC1, 0 }, + { "usb0", { .name = "ahb" }, WPCM450_CLK_USB0, 0 }, + { "peci", { .name = "apb" }, WPCM450_CLK_PECI, 0 }, + { "aes", { .name = "apb" }, WPCM450_CLK_AES, 0 }, + { "uart0", { .name = "uart" }, WPCM450_CLK_UART0, 0 }, + { "uart1", { .name = "uart" }, WPCM450_CLK_UART1, 0 }, + { "smb2", { .name = "apb" }, WPCM450_CLK_SMB2, 0 }, + { "smb3", { .name = "apb" }, WPCM450_CLK_SMB3, 0 }, + { "smb4", { .name = "apb" }, WPCM450_CLK_SMB4, 0 }, + { "smb5", { .name = "apb" }, WPCM450_CLK_SMB5, 0 }, + { "huart", { .name = "huartsel" }, WPCM450_CLK_HUART, 0 }, + { "pwm", { .name = "apb" }, WPCM450_CLK_PWM, 0 }, + { "timer0", { .name = "refdiv2" }, WPCM450_CLK_TIMER0, 0 }, + { "timer1", { .name = "refdiv2" }, WPCM450_CLK_TIMER1, 0 }, + { "timer2", { .name = "refdiv2" }, WPCM450_CLK_TIMER2, 0 }, + { "timer3", { .name = "refdiv2" }, WPCM450_CLK_TIMER3, 0 }, + { "timer4", { .name = "refdiv2" }, WPCM450_CLK_TIMER4, 0 }, + { "mft0", { .name = "apb" }, WPCM450_CLK_MFT0, 0 }, + { "mft1", { .name = "apb" }, WPCM450_CLK_MFT1, 0 }, + { "wdt", { .name = "refdiv2" }, WPCM450_CLK_WDT, 0 }, + { "adc", { .name = "adcdiv" }, WPCM450_CLK_ADC, 0 }, + { "sdio", { .name = "ahb" }, WPCM450_CLK_SDIO, 0 }, + { "sspi", { .name = "apb" }, WPCM450_CLK_SSPI, 0 }, + { "smb0", { .name = "apb" }, WPCM450_CLK_SMB0, 0 }, + { "smb1", { .name = "apb" }, WPCM450_CLK_SMB1, 0 }, +}; + +static DEFINE_SPINLOCK(wpcm450_clk_lock); + +/* + * NOTE: Error handling is very rudimentary here. If the clock driver initial- + * ization fails, the system is probably in bigger trouble than what is caused + * by a few leaked resources. + */ + +static void __init wpcm450_clk_init(struct device_node *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(np, 0); + if (!clk_base) { + pr_err("%pOFP: failed to map registers\n", np); + of_node_put(np); + return; + } + of_node_put(np); + + clk_data = kzalloc(struct_size(clk_data, hws, WPCM450_NUM_CLKS), GFP_KERNEL); + if (!clk_data) + return; + + 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(np, clk_base + data->reg, data->name, + &data->parent, data->flags); + if (IS_ERR(hw)) { + pr_info("Failed to register PLL: %pe\n", hw); + return; + } + } + + /* 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); + return; + } + } + + /* 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); + return; + } + 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); + return; + } + } + + /* 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 (IS_ERR(hw)) { + pr_err("Failed to register gate: %pe\n", hw); + return; + } + clk_data->hws[data->bitnum] = hw; + } + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data); + if (ret) + pr_err("Failed to add DT provider: %pe\n", ERR_PTR(ret)); + + /* Reset controller */ + reset = kzalloc(sizeof(*reset), GFP_KERNEL); + if (!reset) + return; + 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; + ret = reset_controller_register(&reset->rcdev); + if (ret) + pr_err("Failed to register reset controller: %pe\n", ERR_PTR(ret)); + + of_node_put(np); +} + +CLK_OF_DECLARE(wpcm450_clk_init, "nuvoton,wpcm450-clk", wpcm450_clk_init); diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index ccd59ddd76100..1975e2f2e9e84 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -213,7 +213,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.40.1
WARNING: multiple messages have this Message-ID (diff)
From: "Jonathan Neuschäfer" <j.neuschaefer@gmx.net> To: linux-clk@vger.kernel.org, openbmc@lists.ozlabs.org Cc: "Tomer Maimon" <tmaimon77@gmail.com>, "Michael Turquette" <mturquette@baylibre.com>, "Jacky Huang" <ychuang3@nuvoton.com>, "Tali Perry" <tali.perry1@gmail.com>, "Krzysztof Kozlowski" <krzysztof.kozlowski+dt@linaro.org>, "Benjamin Fair" <benjaminfair@google.com>, "Daniel Lezcano" <daniel.lezcano@linaro.org>, "Joel Stanley" <joel@jms.id.au>, "Guenter Roeck" <linux@roeck-us.net>, devicetree@vger.kernel.org, "Conor Dooley" <conor+dt@kernel.org>, linux-watchdog@vger.kernel.org, "Arnd Bergmann" <arnd@arndb.de>, "Jonathan Neuschäfer" <j.neuschaefer@gmx.net>, "Rob Herring" <robh+dt@kernel.org>, "Christophe JAILLET" <christophe.jaillet@wanadoo.fr>, "Thomas Gleixner" <tglx@linutronix.de>, "Wim Van Sebroeck" <wim@linux-watchdog.org>, "Stephen Boyd" <sboyd@kernel.org>, "Patrick Venture" <venture@google.com>, linux-kernel@vger.kernel.org, "Avi Fishman" <avifishman70@gmail.com>, "Krzysztof Kozlowski" <krzysztof.kozlowski@linaro.org>, "Philipp Zabel" <p.zabel@pengutronix.de> Subject: [PATCH v9 3/3] clk: wpcm450: Add Nuvoton WPCM450 clock/reset controller driver Date: Sat, 23 Sep 2023 16:34:38 +0200 [thread overview] Message-ID: <20230923143438.1895461-4-j.neuschaefer@gmx.net> (raw) In-Reply-To: <20230923143438.1895461-1-j.neuschaefer@gmx.net> 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@gmx.net> Reviewed-by: Joel Stanley <joel@jms.id.au> --- I have considered converting this driver to a platform driver instead of using CLK_OF_DECLARE, because platform drivers are generally the way forward. However, the timer-npcm7xx driver used on the same platform requires is initialized with TIMER_OF_DECLARE and thus requires the clocks to be available earlier than a platform driver can provide them. 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@gmx.net/ - 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@gmx.net/ - 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@gmx.net/ - 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 | 8 +- drivers/clk/nuvoton/Makefile | 1 + drivers/clk/nuvoton/clk-wpcm450.c | 372 ++++++++++++++++++++++++++++++ drivers/reset/Kconfig | 2 +- 5 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 drivers/clk/nuvoton/clk-wpcm450.c diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 18969cbd4bb1e..de51182ef630d 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -106,7 +106,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 fe4b7f62f4670..22c5ab409d235 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,10 @@ config CLK_MA35D1 help Build the clock controller driver for MA35D1 SoC. +config CLK_WPCM450 + bool "Nuvoton WPCM450 clock/reset controller support" + default y + 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 c3c59dd9f2aaa..b130f0d3889ca 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 0000000000000..9100c4b8a5648 --- /dev/null +++ b/drivers/clk/nuvoton/clk-wpcm450.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Nuvoton WPCM450 clock and reset controller driver. + * + * Copyright (C) 2022 Jonathan Neuschäfer <j.neuschaefer@gmx.net> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitfield.h> +#include <linux/clk-provider.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 <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_node *np, 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 = kzalloc(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 = of_clk_hw_register(np, &pll->hw); + if (ret) { + kfree(pll); + return ERR_PTR(ret); + } + + return &pll->hw; +} + +#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; + struct clk_parent_data parent; + unsigned int reg; + unsigned long flags; +}; + +static const struct wpcm450_pll_data pll_data[] = { + { "pll0", { .fw_name = "ref" }, REG_PLLCON0, 0 }, + { "pll1", { .fw_name = "ref" }, REG_PLLCON1, 0 }, +}; + +struct wpcm450_clksel_data { + const char *name; + const struct clk_parent_data *parents; + unsigned int num_parents; + const u32 *table; + int shift; + int width; + int index; + unsigned long flags; +}; + +static const u32 parent_table[] = { 0, 1, 2 }; + +static const struct clk_parent_data default_parents[] = { + { .name = "pll0" }, + { .name = "pll1" }, + { .name = "ref" }, +}; + +static const struct clk_parent_data huart_parents[] = { + { .fw_name = "ref" }, + { .name = "refdiv2" }, +}; + +static const struct wpcm450_clksel_data clksel_data[] = { + { "cpusel", default_parents, ARRAY_SIZE(default_parents), + parent_table, 0, 2, -1, CLK_IS_CRITICAL }, + { "clkout", default_parents, ARRAY_SIZE(default_parents), + parent_table, 2, 2, -1, 0 }, + { "usbphy", default_parents, ARRAY_SIZE(default_parents), + parent_table, 6, 2, -1, 0 }, + { "uartsel", default_parents, ARRAY_SIZE(default_parents), + parent_table, 8, 2, WPCM450_CLK_USBPHY, 0 }, + { "huartsel", huart_parents, ARRAY_SIZE(huart_parents), + parent_table, 10, 1, -1, 0 }, +}; + +static const struct clk_div_table div_fixed2[] = { + { .val = 0, .div = 2 }, + { } +}; + +struct wpcm450_clkdiv_data { + const char *name; + struct clk_parent_data 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[] = { + { "refdiv2", { .name = "ref" }, 0, div_fixed2, 0, 0 }, +}; + +static const struct wpcm450_clkdiv_data clkdiv_data[] = { + { "cpu", { .name = "cpusel" }, 0, div_fixed2, 0, 0, CLK_IS_CRITICAL }, + { "adcdiv", { .name = "ref" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 28, 2, 0 }, + { "apb", { .name = "ahb" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 26, 2, 0 }, + { "ahb", { .name = "cpu" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 24, 2, 0 }, + { "uart", { .name = "uartsel" }, 0, NULL, 16, 4, 0 }, + { "ahb3", { .name = "ahb" }, CLK_DIVIDER_POWER_OF_TWO, NULL, 8, 2, 0 }, +}; + +struct wpcm450_clken_data { + const char *name; + struct clk_parent_data parent; + int bitnum; + unsigned long flags; +}; + +static const struct wpcm450_clken_data clken_data[] = { + { "fiu", { .name = "ahb3" }, WPCM450_CLK_FIU, 0 }, + { "xbus", { .name = "ahb3" }, WPCM450_CLK_XBUS, 0 }, + { "kcs", { .name = "apb" }, WPCM450_CLK_KCS, 0 }, + { "shm", { .name = "ahb3" }, WPCM450_CLK_SHM, 0 }, + { "usb1", { .name = "ahb" }, WPCM450_CLK_USB1, 0 }, + { "emc0", { .name = "ahb" }, WPCM450_CLK_EMC0, 0 }, + { "emc1", { .name = "ahb" }, WPCM450_CLK_EMC1, 0 }, + { "usb0", { .name = "ahb" }, WPCM450_CLK_USB0, 0 }, + { "peci", { .name = "apb" }, WPCM450_CLK_PECI, 0 }, + { "aes", { .name = "apb" }, WPCM450_CLK_AES, 0 }, + { "uart0", { .name = "uart" }, WPCM450_CLK_UART0, 0 }, + { "uart1", { .name = "uart" }, WPCM450_CLK_UART1, 0 }, + { "smb2", { .name = "apb" }, WPCM450_CLK_SMB2, 0 }, + { "smb3", { .name = "apb" }, WPCM450_CLK_SMB3, 0 }, + { "smb4", { .name = "apb" }, WPCM450_CLK_SMB4, 0 }, + { "smb5", { .name = "apb" }, WPCM450_CLK_SMB5, 0 }, + { "huart", { .name = "huartsel" }, WPCM450_CLK_HUART, 0 }, + { "pwm", { .name = "apb" }, WPCM450_CLK_PWM, 0 }, + { "timer0", { .name = "refdiv2" }, WPCM450_CLK_TIMER0, 0 }, + { "timer1", { .name = "refdiv2" }, WPCM450_CLK_TIMER1, 0 }, + { "timer2", { .name = "refdiv2" }, WPCM450_CLK_TIMER2, 0 }, + { "timer3", { .name = "refdiv2" }, WPCM450_CLK_TIMER3, 0 }, + { "timer4", { .name = "refdiv2" }, WPCM450_CLK_TIMER4, 0 }, + { "mft0", { .name = "apb" }, WPCM450_CLK_MFT0, 0 }, + { "mft1", { .name = "apb" }, WPCM450_CLK_MFT1, 0 }, + { "wdt", { .name = "refdiv2" }, WPCM450_CLK_WDT, 0 }, + { "adc", { .name = "adcdiv" }, WPCM450_CLK_ADC, 0 }, + { "sdio", { .name = "ahb" }, WPCM450_CLK_SDIO, 0 }, + { "sspi", { .name = "apb" }, WPCM450_CLK_SSPI, 0 }, + { "smb0", { .name = "apb" }, WPCM450_CLK_SMB0, 0 }, + { "smb1", { .name = "apb" }, WPCM450_CLK_SMB1, 0 }, +}; + +static DEFINE_SPINLOCK(wpcm450_clk_lock); + +/* + * NOTE: Error handling is very rudimentary here. If the clock driver initial- + * ization fails, the system is probably in bigger trouble than what is caused + * by a few leaked resources. + */ + +static void __init wpcm450_clk_init(struct device_node *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(np, 0); + if (!clk_base) { + pr_err("%pOFP: failed to map registers\n", np); + of_node_put(np); + return; + } + of_node_put(np); + + clk_data = kzalloc(struct_size(clk_data, hws, WPCM450_NUM_CLKS), GFP_KERNEL); + if (!clk_data) + return; + + 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(np, clk_base + data->reg, data->name, + &data->parent, data->flags); + if (IS_ERR(hw)) { + pr_info("Failed to register PLL: %pe\n", hw); + return; + } + } + + /* 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); + return; + } + } + + /* 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); + return; + } + 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); + return; + } + } + + /* 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 (IS_ERR(hw)) { + pr_err("Failed to register gate: %pe\n", hw); + return; + } + clk_data->hws[data->bitnum] = hw; + } + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data); + if (ret) + pr_err("Failed to add DT provider: %pe\n", ERR_PTR(ret)); + + /* Reset controller */ + reset = kzalloc(sizeof(*reset), GFP_KERNEL); + if (!reset) + return; + 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; + ret = reset_controller_register(&reset->rcdev); + if (ret) + pr_err("Failed to register reset controller: %pe\n", ERR_PTR(ret)); + + of_node_put(np); +} + +CLK_OF_DECLARE(wpcm450_clk_init, "nuvoton,wpcm450-clk", wpcm450_clk_init); diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index ccd59ddd76100..1975e2f2e9e84 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -213,7 +213,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.40.1
next prev parent reply other threads:[~2023-09-23 14:35 UTC|newest] Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top 2023-09-23 14:34 [PATCH v9 0/3] Nuvoton WPCM450 clock and reset driver Jonathan Neuschäfer 2023-09-23 14:34 ` Jonathan Neuschäfer 2023-09-23 14:34 ` [PATCH v9 1/3] dt-bindings: clock: Add Nuvoton WPCM450 clock/reset controller Jonathan Neuschäfer 2023-09-23 14:34 ` Jonathan Neuschäfer 2023-09-23 14:34 ` [PATCH v9 2/3] ARM: dts: wpcm450: Remove clock-output-names from reference clock node Jonathan Neuschäfer 2023-09-23 14:34 ` Jonathan Neuschäfer 2023-09-23 14:34 ` Jonathan Neuschäfer [this message] 2023-09-23 14:34 ` [PATCH v9 3/3] clk: wpcm450: Add Nuvoton WPCM450 clock/reset controller driver Jonathan Neuschäfer 2023-09-23 22:17 ` kernel test robot 2023-09-23 22:17 ` kernel test robot 2023-09-23 23:11 ` kernel test robot 2023-09-23 23:11 ` kernel test robot 2023-09-24 7:51 ` J. Neuschäfer 2023-09-24 7:51 ` J. Neuschäfer
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20230923143438.1895461-4-j.neuschaefer@gmx.net \ --to=j.neuschaefer@gmx.net \ --cc=arnd@arndb.de \ --cc=avifishman70@gmail.com \ --cc=benjaminfair@google.com \ --cc=christophe.jaillet@wanadoo.fr \ --cc=conor+dt@kernel.org \ --cc=daniel.lezcano@linaro.org \ --cc=devicetree@vger.kernel.org \ --cc=joel@jms.id.au \ --cc=krzysztof.kozlowski+dt@linaro.org \ --cc=krzysztof.kozlowski@linaro.org \ --cc=linux-clk@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=linux-watchdog@vger.kernel.org \ --cc=linux@roeck-us.net \ --cc=mturquette@baylibre.com \ --cc=openbmc@lists.ozlabs.org \ --cc=p.zabel@pengutronix.de \ --cc=robh+dt@kernel.org \ --cc=sboyd@kernel.org \ --cc=tali.perry1@gmail.com \ --cc=tglx@linutronix.de \ --cc=tmaimon77@gmail.com \ --cc=venture@google.com \ --cc=wim@linux-watchdog.org \ --cc=ychuang3@nuvoton.com \ --cc=yuenn@google.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.