lkml.org 
[lkml]   [2024]   [Mar]   [10]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v10 3/4] clk: wpcm450: Add Nuvoton WPCM450 clock/reset controller driver
    Date
    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.


    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@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 | 10 +-
    drivers/clk/nuvoton/Makefile | 1 +
    drivers/clk/nuvoton/clk-wpcm450.c | 372 ++++++++++++++++++++++++++++++
    4 files changed, 383 insertions(+), 2 deletions(-)
    create mode 100644 drivers/clk/nuvoton/clk-wpcm450.c

    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..9100c4b8a56483
    --- /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);
    --
    2.43.0

    \
     
     \ /
      Last update: 2024-05-27 15:43    [W:2.862 / U:1.624 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site