diff options
| author | Chen Wang <unicorn_wang@outlook.com> | 2023-11-24 14:15:16 +0800 |
|---|---|---|
| committer | Chen Wang <unicorn_wang@outlook.com> | 2024-06-14 14:49:40 +0800 |
| commit | 48cf7e01386e7e35ea12255bc401bdd484c34e7d (patch) | |
| tree | 1e97f5ba442d4bb111486b33868403b343f3bce4 | |
| parent | 5911423798b2eba65d6ea0aff6505280b3eb55e6 (diff) | |
| download | linux-48cf7e01386e7e35ea12255bc401bdd484c34e7d.tar.gz linux-48cf7e01386e7e35ea12255bc401bdd484c34e7d.tar.bz2 linux-48cf7e01386e7e35ea12255bc401bdd484c34e7d.zip | |
clk: sophgo: Add SG2042 clock driver
Add a driver for the SOPHGO SG2042 clocks.
Signed-off-by: Chen Wang <unicorn_wang@outlook.com>
| -rw-r--r-- | drivers/clk/sophgo/Kconfig | 28 | ||||
| -rw-r--r-- | drivers/clk/sophgo/Makefile | 4 | ||||
| -rw-r--r-- | drivers/clk/sophgo/clk-sg2042-clkgen.c | 1152 | ||||
| -rw-r--r-- | drivers/clk/sophgo/clk-sg2042-pll.c | 570 | ||||
| -rw-r--r-- | drivers/clk/sophgo/clk-sg2042-rpgate.c | 291 | ||||
| -rw-r--r-- | drivers/clk/sophgo/clk-sg2042.h | 18 |
6 files changed, 2063 insertions, 0 deletions
diff --git a/drivers/clk/sophgo/Kconfig b/drivers/clk/sophgo/Kconfig index 1cc49be71bdb..8b1367e3a95e 100644 --- a/drivers/clk/sophgo/Kconfig +++ b/drivers/clk/sophgo/Kconfig @@ -9,3 +9,31 @@ config CLK_SOPHGO_CV1800 The driver require a 25MHz Oscillator to function generate clock. It includes PLLs, common clock function and some vendor clock for IPs of CV18XX series SoC + +config CLK_SOPHGO_SG2042_PLL + tristate "Sophgo SG2042 PLL clock support" + depends on ARCH_SOPHGO || COMPILE_TEST + help + This driver supports the PLL clock controller on the + Sophgo SG2042 SoC. This clock IP uses three oscillators with + frequency of 25 MHz as input, which are used for Main/Fixed + PLL, DDR PLL 0 and DDR PLL 1 respectively. + +config CLK_SOPHGO_SG2042_CLKGEN + tristate "Sophgo SG2042 Clock Generator support" + depends on CLK_SOPHGO_SG2042_PLL + help + This driver supports the Clock Generator on the + Sophgo SG2042 SoC. This clock IP depends on SG2042 PLL clock + because it uses PLL clocks as input. + This driver provides clock function such as DIV/Mux/Gate. + +config CLK_SOPHGO_SG2042_RPGATE + tristate "Sophgo SG2042 RP subsystem clock controller support" + depends on CLK_SOPHGO_SG2042_CLKGEN + help + This driver supports the RP((Riscv Processors)) subsystem clock + controller on the Sophgo SG2042 SoC. + This clock IP depends on SG2042 Clock Generator because it uses + clock from Clock Generator IP as input. + This driver provides Gate function for RP. diff --git a/drivers/clk/sophgo/Makefile b/drivers/clk/sophgo/Makefile index a50320764200..53506845a044 100644 --- a/drivers/clk/sophgo/Makefile +++ b/drivers/clk/sophgo/Makefile @@ -5,3 +5,7 @@ clk-sophgo-cv1800-y += clk-cv1800.o clk-sophgo-cv1800-y += clk-cv18xx-common.o clk-sophgo-cv1800-y += clk-cv18xx-ip.o clk-sophgo-cv1800-y += clk-cv18xx-pll.o + +obj-$(CONFIG_CLK_SOPHGO_SG2042_CLKGEN) += clk-sg2042-clkgen.o +obj-$(CONFIG_CLK_SOPHGO_SG2042_PLL) += clk-sg2042-pll.o +obj-$(CONFIG_CLK_SOPHGO_SG2042_RPGATE) += clk-sg2042-rpgate.o diff --git a/drivers/clk/sophgo/clk-sg2042-clkgen.c b/drivers/clk/sophgo/clk-sg2042-clkgen.c new file mode 100644 index 000000000000..a334963e83ce --- /dev/null +++ b/drivers/clk/sophgo/clk-sg2042-clkgen.c @@ -0,0 +1,1152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sophgo SG2042 Clock Generator Driver + * + * Copyright (C) 2024 Sophgo Technology Inc. + * Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <asm/div64.h> + +#include <dt-bindings/clock/sophgo,sg2042-clkgen.h> + +#include "clk-sg2042.h" + +/* Registers defined in SYS_CTRL */ +#define R_PLL_BEGIN 0xC0 +#define R_PLL_STAT (0xC0 - R_PLL_BEGIN) +#define R_PLL_CLKEN_CONTROL (0xC4 - R_PLL_BEGIN) +#define R_MPLL_CONTROL (0xE8 - R_PLL_BEGIN) +#define R_FPLL_CONTROL (0xF4 - R_PLL_BEGIN) +#define R_DPLL0_CONTROL (0xF8 - R_PLL_BEGIN) +#define R_DPLL1_CONTROL (0xFC - R_PLL_BEGIN) + +/* Registers defined in CLOCK */ +#define R_CLKENREG0 0x00 +#define R_CLKENREG1 0x04 +#define R_CLKSELREG0 0x20 +#define R_CLKDIVREG0 0x40 +#define R_CLKDIVREG1 0x44 +#define R_CLKDIVREG2 0x48 +#define R_CLKDIVREG3 0x4C +#define R_CLKDIVREG4 0x50 +#define R_CLKDIVREG5 0x54 +#define R_CLKDIVREG6 0x58 +#define R_CLKDIVREG7 0x5C +#define R_CLKDIVREG8 0x60 +#define R_CLKDIVREG9 0x64 +#define R_CLKDIVREG10 0x68 +#define R_CLKDIVREG11 0x6C +#define R_CLKDIVREG12 0x70 +#define R_CLKDIVREG13 0x74 +#define R_CLKDIVREG14 0x78 +#define R_CLKDIVREG15 0x7C +#define R_CLKDIVREG16 0x80 +#define R_CLKDIVREG17 0x84 +#define R_CLKDIVREG18 0x88 +#define R_CLKDIVREG19 0x8C +#define R_CLKDIVREG20 0x90 +#define R_CLKDIVREG21 0x94 +#define R_CLKDIVREG22 0x98 +#define R_CLKDIVREG23 0x9C +#define R_CLKDIVREG24 0xA0 +#define R_CLKDIVREG25 0xA4 +#define R_CLKDIVREG26 0xA8 +#define R_CLKDIVREG27 0xAC +#define R_CLKDIVREG28 0xB0 +#define R_CLKDIVREG29 0xB4 +#define R_CLKDIVREG30 0xB8 + +/* All following shift value are the same for all DIV registers */ +#define SHIFT_DIV_RESET_CTRL 0 +#define SHIFT_DIV_FACTOR_SEL 3 +#define SHIFT_DIV_FACTOR 16 + +/** + * struct sg2042_divider_clock - Divider clock + * @hw: clk_hw for initialization + * @id: used to map clk_onecell_data + * @reg: used for readl/writel. + * **NOTE**: DIV registers are ALL in CLOCK! + * @lock: spinlock to protect register access, modification of + * frequency can only be served one at the time + * @offset_ctrl: offset of divider control registers + * @shift: shift of "Clock Divider Factor" in divider control register + * @width: width of "Clock Divider Factor" in divider control register + * @div_flags: private flags for this clock, not for framework-specific + * @initval: In the divider control register, we can configure whether + * to use the value of "Clock Divider Factor" or just use + * the initial value pre-configured by IC. BIT[3] controls + * this and by default (value is 0), means initial value + * is used. + * **NOTE** that we cannot read the initial value (default + * value when poweron) and default value of "Clock Divider + * Factor" is zero, which I think is a hardware design flaw + * and should be sync-ed with the initial value. So in + * software we have to add a configuration item (initval) + * to manually configure this value and use it when BIT[3] + * is zero. + */ +struct sg2042_divider_clock { + struct clk_hw hw; + + unsigned int id; + + void __iomem *reg; + /* protect register access */ + spinlock_t *lock; + + u32 offset_ctrl; + u8 shift; + u8 width; + u8 div_flags; + u32 initval; +}; + +#define to_sg2042_clk_divider(_hw) \ + container_of(_hw, struct sg2042_divider_clock, hw) + +/** + * struct sg2042_gate_clock - Gate clock + * @hw: clk_hw for initialization + * @id: used to map clk_onecell_data + * @offset_enable: offset of gate enable registers + * @bit_idx: which bit in the register controls gating of this clock + */ +struct sg2042_gate_clock { + struct clk_hw hw; + + unsigned int id; + + u32 offset_enable; + u8 bit_idx; +}; + +/** + * struct sg2042_mux_clock - Mux clock + * @hw: clk_hw for initialization + * @id: used to map clk_onecell_data + * @offset_select: offset of mux selection registers + * **NOTE**: MUX registers are ALL in CLOCK! + * @shift: shift of "Clock Select" in mux selection register + * @width: width of "Clock Select" in mux selection register + * @clk_nb: used for notification + * @original_index: set by notifier callback + */ +struct sg2042_mux_clock { + struct clk_hw hw; + + unsigned int id; + + u32 offset_select; + u8 shift; + u8 width; + + struct notifier_block clk_nb; + u8 original_index; +}; + +#define to_sg2042_mux_nb(_nb) container_of(_nb, struct sg2042_mux_clock, clk_nb) + +static unsigned long sg2042_clk_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw); + unsigned long ret_rate; + u32 val; + + if (!(readl(divider->reg) & BIT(SHIFT_DIV_FACTOR_SEL))) { + val = divider->initval; + } else { + val = readl(divider->reg) >> divider->shift; + val &= clk_div_mask(divider->width); + } + + ret_rate = divider_recalc_rate(hw, parent_rate, val, NULL, + divider->div_flags, divider->width); + + pr_debug("--> %s: divider_recalc_rate: ret_rate = %ld\n", + clk_hw_get_name(hw), ret_rate); + return ret_rate; +} + +static long sg2042_clk_divider_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *prate) +{ + struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw); + unsigned long ret_rate; + u32 bestdiv; + + /* if read only, just return current value */ + if (divider->div_flags & CLK_DIVIDER_READ_ONLY) { + if (!(readl(divider->reg) & BIT(SHIFT_DIV_FACTOR_SEL))) { + bestdiv = divider->initval; + } else { + bestdiv = readl(divider->reg) >> divider->shift; + bestdiv &= clk_div_mask(divider->width); + } + ret_rate = DIV_ROUND_UP_ULL((u64)*prate, bestdiv); + } else { + ret_rate = divider_round_rate(hw, rate, prate, NULL, + divider->width, divider->div_flags); + } + + pr_debug("--> %s: divider_round_rate: val = %ld\n", + clk_hw_get_name(hw), ret_rate); + return ret_rate; +} + +static int sg2042_clk_divider_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct sg2042_divider_clock *divider = to_sg2042_clk_divider(hw); + unsigned long flags = 0; + u32 val, val2, value; + + value = divider_get_val(rate, parent_rate, NULL, + divider->width, divider->div_flags); + + if (divider->lock) + spin_lock_irqsave(divider->lock, flags); + else + __acquire(divider->lock); + + /* + * The sequence of clock frequency modification is: + * Assert to reset divider. + * Modify the value of Clock Divide Factor (and High Wide if needed). + * De-assert to restore divided clock with new frequency. + */ + val = readl(divider->reg); + + /* assert */ + val &= ~BIT(SHIFT_DIV_RESET_CTRL); + writel(val, divider->reg); + + if (divider->div_flags & CLK_DIVIDER_HIWORD_MASK) { + val = clk_div_mask(divider->width) << (divider->shift + 16); + } else { + val = readl(divider->reg); + val &= ~(clk_div_mask(divider->width) << divider->shift); + } + val |= value << divider->shift; + val |= BIT(SHIFT_DIV_FACTOR_SEL); + writel(val, divider->reg); + val2 = val; + + /* de-assert */ + val |= BIT(SHIFT_DIV_RESET_CTRL); + writel(val, divider->reg); + + if (divider->lock) + spin_unlock_irqrestore(divider->lock, flags); + else + __release(divider->lock); + + pr_debug("--> %s: divider_set_rate: register val = 0x%x\n", + clk_hw_get_name(hw), val2); + return 0; +} + +static const struct clk_ops sg2042_clk_divider_ops = { + .recalc_rate = sg2042_clk_divider_recalc_rate, + .round_rate = sg2042_clk_divider_round_rate, + .set_rate = sg2042_clk_divider_set_rate, +}; + +static const struct clk_ops sg2042_clk_divider_ro_ops = { + .recalc_rate = sg2042_clk_divider_recalc_rate, + .round_rate = sg2042_clk_divider_round_rate, +}; + +/* + * Clock initialization macro naming rules: + * FW: use CLK_HW_INIT_FW_NAME + * HW: use CLK_HW_INIT_HW + * HWS: use CLK_HW_INIT_HWS + * RO: means Read-Only + */ +#define SG2042_DIV_FW(_id, _name, _parent, \ + _r_ctrl, _shift, _width, \ + _div_flag, _initval) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_FW_NAME( \ + _name, \ + _parent, \ + &sg2042_clk_divider_ops, \ + 0), \ + .offset_ctrl = _r_ctrl, \ + .shift = _shift, \ + .width = _width, \ + .div_flags = _div_flag, \ + .initval = _initval, \ + } + +#define SG2042_DIV_FW_RO(_id, _name, _parent, \ + _r_ctrl, _shift, _width, \ + _div_flag, _initval) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_FW_NAME( \ + _name, \ + _parent, \ + &sg2042_clk_divider_ro_ops, \ + 0), \ + .offset_ctrl = _r_ctrl, \ + .shift = _shift, \ + .width = _width, \ + .div_flags = (_div_flag) | CLK_DIVIDER_READ_ONLY, \ + .initval = _initval, \ + } + +#define SG2042_DIV_HW(_id, _name, _parent, \ + _r_ctrl, _shift, _width, \ + _div_flag, _initval) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_HW( \ + _name, \ + _parent, \ + &sg2042_clk_divider_ops, \ + 0), \ + .offset_ctrl = _r_ctrl, \ + .shift = _shift, \ + .width = _width, \ + .div_flags = _div_flag, \ + .initval = _initval, \ + } + +#define SG2042_DIV_HW_RO(_id, _name, _parent, \ + _r_ctrl, _shift, _width, \ + _div_flag, _initval) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_HW( \ + _name, \ + _parent, \ + &sg2042_clk_divider_ro_ops, \ + 0), \ + .offset_ctrl = _r_ctrl, \ + .shift = _shift, \ + .width = _width, \ + .div_flags = (_div_flag) | CLK_DIVIDER_READ_ONLY, \ + .initval = _initval, \ + } + +#define SG2042_DIV_HWS(_id, _name, _parent, \ + _r_ctrl, _shift, _width, \ + _div_flag, _initval) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_HWS( \ + _name, \ + _parent, \ + &sg2042_clk_divider_ops, \ + 0), \ + .offset_ctrl = _r_ctrl, \ + .shift = _shift, \ + .width = _width, \ + .div_flags = _div_flag, \ + .initval = _initval, \ + } + +#define SG2042_DIV_HWS_RO(_id, _name, _parent, \ + _r_ctrl, _shift, _width, \ + _div_flag, _initval) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_HWS( \ + _name, \ + _parent, \ + &sg2042_clk_divider_ro_ops, \ + 0), \ + .offset_ctrl = _r_ctrl, \ + .shift = _shift, \ + .width = _width, \ + .div_flags = (_div_flag) | CLK_DIVIDER_READ_ONLY, \ + .initval = _initval, \ + } + +#define SG2042_GATE_HWS(_id, _name, _parent, _flags, \ + _r_enable, _bit_idx) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_HWS( \ + _name, \ + _parent, \ + NULL, \ + _flags), \ + .offset_enable = _r_enable, \ + .bit_idx = _bit_idx, \ + } + +#define SG2042_GATE_HW(_id, _name, _parent, _flags, \ + _r_enable, _bit_idx) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_HW( \ + _name, \ + _parent, \ + NULL, \ + _flags), \ + .offset_enable = _r_enable, \ + .bit_idx = _bit_idx, \ + } + +#define SG2042_GATE_FW(_id, _name, _parent, _flags, \ + _r_enable, _bit_idx) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_FW_NAME( \ + _name, \ + _parent, \ + NULL, \ + _flags), \ + .offset_enable = _r_enable, \ + .bit_idx = _bit_idx, \ + } + +#define SG2042_MUX(_id, _name, _parents, _flags, _r_select, _shift, _width) { \ + .id = _id, \ + .hw.init = CLK_HW_INIT_PARENTS_HW( \ + _name, \ + _parents, \ + NULL, \ + _flags), \ + .offset_select = _r_select, \ + .shift = _shift, \ + .width = _width, \ + } + +/* + * Clock items in the array are sorted according to the clock-tree diagram, + * from top to bottom, from upstream to downstream. Read TRM for details. + */ + +/* updated during probe/registration */ +static const struct clk_hw *clk_gate_ddr01_div0[] = { NULL }; +static const struct clk_hw *clk_gate_ddr01_div1[] = { NULL }; +static const struct clk_hw *clk_gate_ddr23_div0[] = { NULL }; +static const struct clk_hw *clk_gate_ddr23_div1[] = { NULL }; +static const struct clk_hw *clk_gate_rp_cpu_normal_div0[] = { NULL }; +static const struct clk_hw *clk_gate_rp_cpu_normal_div1[] = { NULL }; +static const struct clk_hw *clk_gate_axi_ddr_div0[] = { NULL }; +static const struct clk_hw *clk_gate_axi_ddr_div1[] = { NULL }; + +static const struct sg2042_gate_clock sg2042_gate_clks_level_1[] = { + SG2042_GATE_FW(GATE_CLK_DDR01_DIV0, "clk_gate_ddr01_div0", "dpll0", + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + R_CLKDIVREG27, 4), + SG2042_GATE_FW(GATE_CLK_DDR01_DIV1, "clk_gate_ddr01_div1", "fpll", + CLK_IS_CRITICAL, + R_CLKDIVREG28, 4), + + SG2042_GATE_FW(GATE_CLK_DDR23_DIV0, "clk_gate_ddr23_div0", "dpll1", + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + R_CLKDIVREG29, 4), + SG2042_GATE_FW(GATE_CLK_DDR23_DIV1, "clk_gate_ddr23_div1", "fpll", + CLK_IS_CRITICAL, + R_CLKDIVREG30, 4), + + SG2042_GATE_FW(GATE_CLK_RP_CPU_NORMAL_DIV0, + "clk_gate_rp_cpu_normal_div0", "mpll", + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKDIVREG0, 4), + SG2042_GATE_FW(GATE_CLK_RP_CPU_NORMAL_DIV1, + "clk_gate_rp_cpu_normal_div1", "fpll", + CLK_IS_CRITICAL, + R_CLKDIVREG1, 4), + + SG2042_GATE_FW(GATE_CLK_AXI_DDR_DIV0, "clk_gate_axi_ddr_div0", "mpll", + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKDIVREG25, 4), + SG2042_GATE_FW(GATE_CLK_AXI_DDR_DIV1, "clk_gate_axi_ddr_div1", "fpll", + CLK_IS_CRITICAL, + R_CLKDIVREG26, 4), +}; + +#define DEF_DIVFLAG (CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO) + +static struct sg2042_divider_clock sg2042_div_clks_level_1[] = { + SG2042_DIV_HWS_RO(DIV_CLK_DPLL0_DDR01_0, + "clk_div_ddr01_0", clk_gate_ddr01_div0, + R_CLKDIVREG27, 16, 5, DEF_DIVFLAG, 1), + SG2042_DIV_HWS_RO(DIV_CLK_FPLL_DDR01_1, + "clk_div_ddr01_1", clk_gate_ddr01_div1, + R_CLKDIVREG28, 16, 5, DEF_DIVFLAG, 1), + + SG2042_DIV_HWS_RO(DIV_CLK_DPLL1_DDR23_0, + "clk_div_ddr23_0", clk_gate_ddr23_div0, + R_CLKDIVREG29, 16, 5, DEF_DIVFLAG, 1), + SG2042_DIV_HWS_RO(DIV_CLK_FPLL_DDR23_1, + "clk_div_ddr23_1", clk_gate_ddr23_div1, + R_CLKDIVREG30, 16, 5, DEF_DIVFLAG, 1), + + SG2042_DIV_HWS(DIV_CLK_MPLL_RP_CPU_NORMAL_0, + "clk_div_rp_cpu_normal_0", clk_gate_rp_cpu_normal_div0, + R_CLKDIVREG0, 16, 5, DEF_DIVFLAG, 1), + SG2042_DIV_HWS(DIV_CLK_FPLL_RP_CPU_NORMAL_1, + "clk_div_rp_cpu_normal_1", clk_gate_rp_cpu_normal_div1, + R_CLKDIVREG1, 16, 5, DEF_DIVFLAG, 1), + + SG2042_DIV_HWS(DIV_CLK_MPLL_AXI_DDR_0, + "clk_div_axi_ddr_0", clk_gate_axi_ddr_div0, + R_CLKDIVREG25, 16, 5, DEF_DIVFLAG, 2), + SG2042_DIV_HWS(DIV_CLK_FPLL_AXI_DDR_1, + "clk_div_axi_ddr_1", clk_gate_axi_ddr_div1, + R_CLKDIVREG26, 16, 5, DEF_DIVFLAG, 1), +}; + +/* + * Note: regarding names for mux clock, "0/1" or "div0/div1" means the + * first/second parent input source, not the register value. + * For example: + * "clk_div_ddr01_0" is the name of Clock divider 0 control of DDR01, and + * "clk_gate_ddr01_div0" is the gate clock in front of the "clk_div_ddr01_0", + * they are both controlled by register CLKDIVREG27; + * "clk_div_ddr01_1" is the name of Clock divider 1 control of DDR01, and + * "clk_gate_ddr01_div1" is the gate clock in front of the "clk_div_ddr01_1", + * they are both controlled by register CLKDIVREG28; + * While for register value of mux selection, use Clock Select for DDR01’s clock + * as example, see CLKSELREG0, bit[2]. + * 1: Select in_dpll0_clk as clock source, correspondng to the parent input + * source from "clk_div_ddr01_0". + * 0: Select in_fpll_clk as clock source, corresponding to the parent input + * source from "clk_div_ddr01_1". + * So we need a table to define the array of register values corresponding to + * the parent index and tell CCF about this when registering mux clock. + */ +static const u32 sg2042_mux_table[] = {1, 0}; + +/* Aliases just for easy reading */ +#define clk_div_ddr01_0 (&sg2042_div_clks_level_1[0].hw) +#define clk_div_ddr01_1 (&sg2042_div_clks_level_1[1].hw) +#define clk_div_ddr23_0 (&sg2042_div_clks_level_1[2].hw) +#define clk_div_ddr23_1 (&sg2042_div_clks_level_1[3].hw) +#define clk_div_rp_cpu_normal_0 (&sg2042_div_clks_level_1[4].hw) +#define clk_div_rp_cpu_normal_1 (&sg2042_div_clks_level_1[5].hw) +#define clk_div_axi_ddr_0 (&sg2042_div_clks_level_1[6].hw) +#define clk_div_axi_ddr_1 (&sg2042_div_clks_level_1[7].hw) + +static const struct clk_hw *clk_mux_ddr01_p[] = { + clk_div_ddr01_0, + clk_div_ddr01_1, +}; + +static const struct clk_hw *clk_mux_ddr23_p[] = { + clk_div_ddr23_0, + clk_div_ddr23_1, +}; + +static const struct clk_hw *clk_mux_rp_cpu_normal_p[] = { + clk_div_rp_cpu_normal_0, + clk_div_rp_cpu_normal_1, +}; + +static const struct clk_hw *clk_mux_axi_ddr_p[] = { + clk_div_axi_ddr_0, + clk_div_axi_ddr_1, +}; + +/* Mux clocks to be updated during probe/registration */ +static const struct clk_hw *clk_mux_ddr01[] = { NULL }; +static const struct clk_hw *clk_mux_ddr23[] = { NULL }; +static const struct clk_hw *clk_mux_rp_cpu_normal[] = { NULL }; +static const struct clk_hw *clk_mux_axi_ddr[] = { NULL }; + +static struct sg2042_mux_clock sg2042_mux_clks[] = { + SG2042_MUX(MUX_CLK_DDR01, "clk_mux_ddr01", clk_mux_ddr01_p, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT | CLK_MUX_READ_ONLY, + R_CLKSELREG0, 2, 1), + SG2042_MUX(MUX_CLK_DDR23, "clk_mux_ddr23", clk_mux_ddr23_p, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT | CLK_MUX_READ_ONLY, + R_CLKSELREG0, 3, 1), + SG2042_MUX(MUX_CLK_RP_CPU_NORMAL, "clk_mux_rp_cpu_normal", clk_mux_rp_cpu_normal_p, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + R_CLKSELREG0, 0, 1), + SG2042_MUX(MUX_CLK_AXI_DDR, "clk_mux_axi_ddr", clk_mux_axi_ddr_p, + CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + R_CLKSELREG0, 1, 1), +}; + +/* Aliases just for easy reading */ +#define clk_div_top_rp_cmn_div2 (&sg2042_div_clks_level_2[0].hw) +#define clk_div_50m_a53 (&sg2042_div_clks_level_2[1].hw) +#define clk_div_timer1 (&sg2042_div_clks_level_2[2].hw) +#define clk_div_timer2 (&sg2042_div_clks_level_2[3].hw) +#define clk_div_timer3 (&sg2042_div_clks_level_2[4].hw) +#define clk_div_timer4 (&sg2042_div_clks_level_2[5].hw) +#define clk_div_timer5 (&sg2042_div_clks_level_2[6].hw) +#define clk_div_timer6 (&sg2042_div_clks_level_2[7].hw) +#define clk_div_timer7 (&sg2042_div_clks_level_2[8].hw) +#define clk_div_timer8 (&sg2042_div_clks_level_2[9].hw) +#define clk_div_uart_500m (&sg2042_div_clks_level_2[10].hw) +#define clk_div_ahb_lpc (&sg2042_div_clks_level_2[11].hw) +#define clk_div_efuse (&sg2042_div_clks_level_2[12].hw) +#define clk_div_tx_eth0 (&sg2042_div_clks_level_2[13].hw) +#define clk_div_ptp_ref_i_eth0 (&sg2042_div_clks_level_2[14].hw) +#define clk_div_ref_eth0 (&sg2042_div_clks_level_2[15].hw) +#define clk_div_emmc (&sg2042_div_clks_level_2[16].hw) +#define clk_div_sd (&sg2042_div_clks_level_2[17].hw) +#define clk_div_top_axi0 (&sg2042_div_clks_level_2[18].hw) +#define clk_div_100k_emmc (&sg2042_div_clks_level_2[19].hw) +#define clk_div_100k_sd (&sg2042_div_clks_level_2[20].hw) +#define clk_div_gpio_db (&sg2042_div_clks_level_2[21].hw) +#define clk_div_top_axi_hsperi (&sg2042_div_clks_level_2[22].hw) + +static struct sg2042_divider_clock sg2042_div_clks_level_2[] = { + SG2042_DIV_HWS(DIV_CLK_FPLL_TOP_RP_CMN_DIV2, + "clk_div_top_rp_cmn_div2", clk_mux_rp_cpu_normal, + R_CLKDIVREG3, 16, 16, DEF_DIVFLAG, 2), + + SG2042_DIV_FW(DIV_CLK_FPLL_50M_A53, "clk_div_50m_a53", "fpll", + R_CLKDIVREG2, 16, 8, DEF_DIVFLAG, 20), + /* downstream of div_50m_a53 */ + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER1, "clk_div_timer1", clk_div_50m_a53, + R_CLKDIVREG6, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER2, "clk_div_timer2", clk_div_50m_a53, + R_CLKDIVREG7, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER3, "clk_div_timer3", clk_div_50m_a53, + R_CLKDIVREG8, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER4, "clk_div_timer4", clk_div_50m_a53, + R_CLKDIVREG9, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER5, "clk_div_timer5", clk_div_50m_a53, + R_CLKDIVREG10, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER6, "clk_div_timer6", clk_div_50m_a53, + R_CLKDIVREG11, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER7, "clk_div_timer7", clk_div_50m_a53, + R_CLKDIVREG12, 16, 16, DEF_DIVFLAG, 1), + SG2042_DIV_HW(DIV_CLK_FPLL_DIV_TIMER8, "clk_div_timer8", clk_div_50m_a53, + R_CLKDIVREG13, 16, 16, DEF_DIVFLAG, 1), + + /* + * Set clk_div_uart_500m as RO, because the width of CLKDIVREG4 is too + * narrow for us to produce 115200. Use UART internal divider directly. + */ + SG2042_DIV_FW_RO(DIV_CLK_FPLL_UART_500M, "clk_div_uart_500m", "fpll", + R_CLKDIVREG4, 16, 7, DEF_DIVFLAG, 2), + SG2042_DIV_FW(DIV_CLK_FPLL_AHB_LPC, "clk_div_ahb_lpc", "fpll", + R_CLKDIVREG5, 16, 16, DEF_DIVFLAG, 5), + SG2042_DIV_FW(DIV_CLK_FPLL_EFUSE, "clk_div_efuse", "fpll", + R_CLKDIVREG14, 16, 7, DEF_DIVFLAG, 40), + SG2042_DIV_FW(DIV_CLK_FPLL_TX_ETH0, "clk_div_tx_eth0", "fpll", + R_CLKDIVREG16, 16, 11, DEF_DIVFLAG, 8), + SG2042_DIV_FW(DIV_CLK_FPLL_PTP_REF_I_ETH0, + "clk_div_ptp_ref_i_eth0", "fpll", + R_CLKDIVREG17, 16, 8, DEF_DIVFLAG, 20), + SG2042_DIV_FW(DIV_CLK_FPLL_REF_ETH0, "clk_div_ref_eth0", "fpll", + R_CLKDIVREG18, 16, 8, DEF_DIVFLAG, 40), + SG2042_DIV_FW(DIV_CLK_FPLL_EMMC, "clk_div_emmc", "fpll", + R_CLKDIVREG19, 16, 5, DEF_DIVFLAG, 10), + SG2042_DIV_FW(DIV_CLK_FPLL_SD, "clk_div_sd", "fpll", + R_CLKDIVREG21, 16, 5, DEF_DIVFLAG, 10), + + SG2042_DIV_FW(DIV_CLK_FPLL_TOP_AXI0, "clk_div_top_axi0", "fpll", + R_CLKDIVREG23, 16, 5, DEF_DIVFLAG, 10), + /* downstream of div_top_axi0 */ + SG2042_DIV_HW(DIV_CLK_FPLL_100K_EMMC, "clk_div_100k_emmc", clk_div_top_axi0, + R_CLKDIVREG20, 16, 16, DEF_DIVFLAG, 1000), + SG2042_DIV_HW(DIV_CLK_FPLL_100K_SD, "clk_div_100k_sd", clk_div_top_axi0, + R_CLKDIVREG22, 16, 16, DEF_DIVFLAG, 1000), + SG2042_DIV_HW(DIV_CLK_FPLL_GPIO_DB, "clk_div_gpio_db", clk_div_top_axi0, + R_CLKDIVREG15, 16, 16, DEF_DIVFLAG, 1000), + + SG2042_DIV_FW(DIV_CLK_FPLL_TOP_AXI_HSPERI, + "clk_div_top_axi_hsperi", "fpll", + R_CLKDIVREG24, 16, 5, DEF_DIVFLAG, 4), +}; + +/* Gate clocks to be updated during probe/registration */ +static const struct clk_hw *clk_gate_rp_cpu_normal[] = { NULL }; +static const struct clk_hw *clk_gate_top_rp_cmn_div2[] = { NULL }; + +static const struct sg2042_gate_clock sg2042_gate_clks_level_2[] = { + SG2042_GATE_HWS(GATE_CLK_DDR01, "clk_gate_ddr01", clk_mux_ddr01, + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKENREG1, 14), + + SG2042_GATE_HWS(GATE_CLK_DDR23, "clk_gate_ddr23", clk_mux_ddr23, + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKENREG1, 15), + + SG2042_GATE_HWS(GATE_CLK_RP_CPU_NORMAL, + "clk_gate_rp_cpu_normal", clk_mux_rp_cpu_normal, + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKENREG0, 0), + + SG2042_GATE_HWS(GATE_CLK_AXI_DDR, "clk_gate_axi_ddr", clk_mux_axi_ddr, + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKENREG1, 13), + + /* upon are gate clocks directly downstream of muxes */ + + /* downstream of clk_div_top_rp_cmn_div2 */ + SG2042_GATE_HW(GATE_CLK_TOP_RP_CMN_DIV2, + "clk_gate_top_rp_cmn_div2", clk_div_top_rp_cmn_div2, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG0, 2), + SG2042_GATE_HWS(GATE_CLK_HSDMA, "clk_gate_hsdma", clk_gate_top_rp_cmn_div2, + CLK_SET_RATE_PARENT, R_CLKENREG1, 10), + + /* + * downstream of clk_gate_rp_cpu_normal + * + * FIXME: there should be one 1/2 DIV between clk_gate_rp_cpu_normal + * and clk_gate_axi_pcie0/clk_gate_axi_pcie1. + * But the 1/2 DIV is fixed and no configurable register exported, so + * when reading from these two clocks, the rate value are still the + * same as that of clk_gate_rp_cpu_normal, it's not correct. + * This just affects the value read. + */ + SG2042_GATE_HWS(GATE_CLK_AXI_PCIE0, + "clk_gate_axi_pcie0", clk_gate_rp_cpu_normal, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG1, 8), + SG2042_GATE_HWS(GATE_CLK_AXI_PCIE1, + "clk_gate_axi_pcie1", clk_gate_rp_cpu_normal, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG1, 9), + + /* downstream of div_50m_a53 */ + SG2042_GATE_HW(GATE_CLK_A53_50M, "clk_gate_a53_50m", clk_div_50m_a53, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG0, 1), + SG2042_GATE_HW(GATE_CLK_TIMER1, "clk_gate_timer1", clk_div_timer1, + CLK_SET_RATE_PARENT, R_CLKENREG0, 12), + SG2042_GATE_HW(GATE_CLK_TIMER2, "clk_gate_timer2", clk_div_timer2, + CLK_SET_RATE_PARENT, R_CLKENREG0, 13), + SG2042_GATE_HW(GATE_CLK_TIMER3, "clk_gate_timer3", clk_div_timer3, + CLK_SET_RATE_PARENT, R_CLKENREG0, 14), + SG2042_GATE_HW(GATE_CLK_TIMER4, "clk_gate_timer4", clk_div_timer4, + CLK_SET_RATE_PARENT, R_CLKENREG0, 15), + SG2042_GATE_HW(GATE_CLK_TIMER5, "clk_gate_timer5", clk_div_timer5, + CLK_SET_RATE_PARENT, R_CLKENREG0, 16), + SG2042_GATE_HW(GATE_CLK_TIMER6, "clk_gate_timer6", clk_div_timer6, + CLK_SET_RATE_PARENT, R_CLKENREG0, 17), + SG2042_GATE_HW(GATE_CLK_TIMER7, "clk_gate_timer7", clk_div_timer7, + CLK_SET_RATE_PARENT, R_CLKENREG0, 18), + SG2042_GATE_HW(GATE_CLK_TIMER8, "clk_gate_timer8", clk_div_timer8, + CLK_SET_RATE_PARENT, R_CLKENREG0, 19), + + /* gate clocks downstream from div clocks one-to-one */ + SG2042_GATE_HW(GATE_CLK_UART_500M, "clk_gate_uart_500m", clk_div_uart_500m, + CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, R_CLKENREG0, 4), + SG2042_GATE_HW(GATE_CLK_AHB_LPC, "clk_gate_ahb_lpc", clk_div_ahb_lpc, + CLK_SET_RATE_PARENT, R_CLKENREG0, 7), + SG2042_GATE_HW(GATE_CLK_EFUSE, "clk_gate_efuse", clk_div_efuse, + CLK_SET_RATE_PARENT, R_CLKENREG0, 20), + SG2042_GATE_HW(GATE_CLK_TX_ETH0, "clk_gate_tx_eth0", clk_div_tx_eth0, + CLK_SET_RATE_PARENT, R_CLKENREG0, 30), + SG2042_GATE_HW(GATE_CLK_PTP_REF_I_ETH0, + "clk_gate_ptp_ref_i_eth0", clk_div_ptp_ref_i_eth0, + CLK_SET_RATE_PARENT, R_CLKENREG1, 0), + SG2042_GATE_HW(GATE_CLK_REF_ETH0, "clk_gate_ref_eth0", clk_div_ref_eth0, + CLK_SET_RATE_PARENT, R_CLKENREG1, 1), + SG2042_GATE_HW(GATE_CLK_EMMC_100M, "clk_gate_emmc", clk_div_emmc, + CLK_SET_RATE_PARENT, R_CLKENREG1, 3), + SG2042_GATE_HW(GATE_CLK_SD_100M, "clk_gate_sd", clk_div_sd, + CLK_SET_RATE_PARENT, R_CLKENREG1, 6), + + /* downstream of clk_div_top_axi0 */ + SG2042_GATE_HW(GATE_CLK_AHB_ROM, "clk_gate_ahb_rom", clk_div_top_axi0, + 0, R_CLKENREG0, 8), + SG2042_GATE_HW(GATE_CLK_AHB_SF, "clk_gate_ahb_sf", clk_div_top_axi0, + 0, R_CLKENREG0, 9), + SG2042_GATE_HW(GATE_CLK_AXI_SRAM, "clk_gate_axi_sram", clk_div_top_axi0, + CLK_IGNORE_UNUSED, R_CLKENREG0, 10), + SG2042_GATE_HW(GATE_CLK_APB_TIMER, "clk_gate_apb_timer", clk_div_top_axi0, + CLK_IGNORE_UNUSED, R_CLKENREG0, 11), + SG2042_GATE_HW(GATE_CLK_APB_EFUSE, "clk_gate_apb_efuse", clk_div_top_axi0, + 0, R_CLKENREG0, 21), + SG2042_GATE_HW(GATE_CLK_APB_GPIO, "clk_gate_apb_gpio", clk_div_top_axi0, + 0, R_CLKENREG0, 22), + SG2042_GATE_HW(GATE_CLK_APB_GPIO_INTR, + "clk_gate_apb_gpio_intr", clk_div_top_axi0, + CLK_IS_CRITICAL, R_CLKENREG0, 23), + SG2042_GATE_HW(GATE_CLK_APB_I2C, "clk_gate_apb_i2c", clk_div_top_axi0, + 0, R_CLKENREG0, 26), + SG2042_GATE_HW(GATE_CLK_APB_WDT, "clk_gate_apb_wdt", clk_div_top_axi0, + 0, R_CLKENREG0, 27), + SG2042_GATE_HW(GATE_CLK_APB_PWM, "clk_gate_apb_pwm", clk_div_top_axi0, + 0, R_CLKENREG0, 28), + SG2042_GATE_HW(GATE_CLK_APB_RTC, "clk_gate_apb_rtc", clk_div_top_axi0, + 0, R_CLKENREG0, 29), + SG2042_GATE_HW(GATE_CLK_TOP_AXI0, "clk_gate_top_axi0", clk_div_top_axi0, + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKENREG1, 11), + /* downstream of DIV clocks which are sourced from clk_div_top_axi0 */ + SG2042_GATE_HW(GATE_CLK_GPIO_DB, "clk_gate_gpio_db", clk_div_gpio_db, + CLK_SET_RATE_PARENT, R_CLKENREG0, 24), + SG2042_GATE_HW(GATE_CLK_100K_EMMC, "clk_gate_100k_emmc", clk_div_100k_emmc, + CLK_SET_RATE_PARENT, R_CLKENREG1, 4), + SG2042_GATE_HW(GATE_CLK_100K_SD, "clk_gate_100k_sd", clk_div_100k_sd, + CLK_SET_RATE_PARENT, R_CLKENREG1, 7), + + /* downstream of clk_div_top_axi_hsperi */ + SG2042_GATE_HW(GATE_CLK_SYSDMA_AXI, + "clk_gate_sysdma_axi", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG0, 3), + SG2042_GATE_HW(GATE_CLK_APB_UART, + "clk_gate_apb_uart", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG0, 5), + SG2042_GATE_HW(GATE_CLK_AXI_DBG_I2C, + "clk_gate_axi_dbg_i2c", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG0, 6), + SG2042_GATE_HW(GATE_CLK_APB_SPI, + "clk_gate_apb_spi", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG0, 25), + SG2042_GATE_HW(GATE_CLK_AXI_ETH0, + "clk_gate_axi_eth0", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG0, 31), + SG2042_GATE_HW(GATE_CLK_AXI_EMMC, + "clk_gate_axi_emmc", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG1, 2), + SG2042_GATE_HW(GATE_CLK_AXI_SD, + "clk_gate_axi_sd", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT, R_CLKENREG1, 5), + SG2042_GATE_HW(GATE_CLK_TOP_AXI_HSPERI, + "clk_gate_top_axi_hsperi", clk_div_top_axi_hsperi, + CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + R_CLKENREG1, 12), +}; + +static DEFINE_SPINLOCK(sg2042_clk_lock); + +static int sg2042_clk_register_divs(struct device *dev, + struct sg2042_clk_data *clk_data, + struct sg2042_divider_clock div_clks[], + int num_div_clks) +{ + struct sg2042_divider_clock *div; + struct clk_hw *hw; + int i, ret = 0; + + for (i = 0; i < num_div_clks; i++) { + div = &div_clks[i]; + + if (div->div_flags & CLK_DIVIDER_HIWORD_MASK) { + if (div->width + div->shift > 16) { + pr_warn("divider value exceeds LOWORD field\n"); + ret = -EINVAL; + break; + } + } + + div->reg = clk_data->iobase + div->offset_ctrl; + div->lock = &sg2042_clk_lock; + + hw = &div->hw; + ret = devm_clk_hw_register(dev, hw); + if (ret) { + pr_err("failed to register clock %s\n", div->hw.init->name); + break; + } + + clk_data->onecell_data.hws[div->id] = hw; + } + + return ret; +} + +static int sg2042_clk_register_gates(struct device *dev, + struct sg2042_clk_data *clk_data, + const struct sg2042_gate_clock gate_clks[], + int num_gate_clks) +{ + const struct sg2042_gate_clock *gate; + struct clk_hw *hw; + int i, ret = 0; + + for (i = 0; i < num_gate_clks; i++) { + gate = &gate_clks[i]; + hw = __devm_clk_hw_register_gate + (dev, + NULL, + gate->hw.init->name, + NULL, + gate->hw.init->parent_hws[0], + NULL, + gate->hw.init->flags, + clk_data->iobase + gate->offset_enable, + gate->bit_idx, + 0, + &sg2042_clk_lock); + if (IS_ERR(hw)) { + pr_err("failed to register clock %s\n", gate->hw.init->name); + ret = PTR_ERR(hw); + break; + } + + clk_data->onecell_data.hws[gate->id] = hw; + + /* Updated some clocks which take the role of parent */ + switch (gate->id) { + case GATE_CLK_RP_CPU_NORMAL: + *clk_gate_rp_cpu_normal = hw; + break; + case GATE_CLK_TOP_RP_CMN_DIV2: + *clk_gate_top_rp_cmn_div2 = hw; + break; + } + } + + return ret;< |
