From f44fc1cd47acdd9db434dc11e4206f41c8c728b9 Mon Sep 17 00:00:00 2001 From: Zheng Zhang Date: Wed, 11 Mar 2026 15:32:23 +0800 Subject: [PATCH] board: spacemit-k3: Add ccu driver to support spl related clocks --- configs/k3_defconfig | 2 + drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/spacemit/Kconfig | 11 + drivers/clk/spacemit/Makefile | 6 + drivers/clk/spacemit/ccu-k3.c | 308 ++++++++++++ drivers/clk/spacemit/ccu-k3.h | 22 + drivers/clk/spacemit/ccu_mix.c | 453 ++++++++++++++++++ drivers/clk/spacemit/ccu_mix.h | 368 ++++++++++++++ drivers/clk/spacemit/ccu_plla.c | 267 +++++++++++ drivers/clk/spacemit/ccu_plla.h | 76 +++ drivers/clk/spacemit/common.h | 82 ++++ include/dt-bindings/clock/spacemit-k3-clock.h | 302 ++++++++++++ 13 files changed, 1899 insertions(+) create mode 100644 drivers/clk/spacemit/Kconfig create mode 100644 drivers/clk/spacemit/Makefile create mode 100644 drivers/clk/spacemit/ccu-k3.c create mode 100644 drivers/clk/spacemit/ccu-k3.h create mode 100644 drivers/clk/spacemit/ccu_mix.c create mode 100644 drivers/clk/spacemit/ccu_mix.h create mode 100644 drivers/clk/spacemit/ccu_plla.c create mode 100644 drivers/clk/spacemit/ccu_plla.h create mode 100644 drivers/clk/spacemit/common.h create mode 100644 include/dt-bindings/clock/spacemit-k3-clock.h diff --git a/configs/k3_defconfig b/configs/k3_defconfig index cc6f894aa7d..3c672a34d9b 100644 --- a/configs/k3_defconfig +++ b/configs/k3_defconfig @@ -2,3 +2,5 @@ CONFIG_RISCV=y CONFIG_TARGET_SPACEMIT_K3=y CONFIG_ARCH_RV64I=y CONFIG_RISCV_SMODE=y +CONFIG_SPL_CLK=y +CONFIG_SPACEMIT_K3_CCU=y diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index b884a02bdeb..bf3b3f5d978 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -284,5 +284,6 @@ source "drivers/clk/tegra/Kconfig" source "drivers/clk/ti/Kconfig" source "drivers/clk/thead/Kconfig" source "drivers/clk/uniphier/Kconfig" +source "drivers/clk/spacemit/Kconfig" endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 5f0c0d8a5c2..5ea072a4662 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -61,3 +61,4 @@ obj-$(CONFIG_MACH_PIC32) += clk_pic32.o obj-$(CONFIG_SANDBOX_CLK_CCF) += clk_sandbox_ccf.o obj-$(CONFIG_SANDBOX) += clk_sandbox.o obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o +obj-$(CONFIG_TARGET_SPACEMIT_K3) += spacemit/ diff --git a/drivers/clk/spacemit/Kconfig b/drivers/clk/spacemit/Kconfig new file mode 100644 index 00000000000..81ef6178bfd --- /dev/null +++ b/drivers/clk/spacemit/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# common clock support for SPACEMIT SoC family. + +config SPACEMIT_K3_CCU + tristate "Clock support for Spacemit k3 SoCs" + depends on TARGET_SPACEMIT_K3 + select CLK + select CLK_CCF + select SPL_CLK_CCF if SPL + help + Build the driver for Spacemit K3 Clock Driver. diff --git a/drivers/clk/spacemit/Makefile b/drivers/clk/spacemit/Makefile new file mode 100644 index 00000000000..e335030acaa --- /dev/null +++ b/drivers/clk/spacemit/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Spacemit Clock specific Makefile +# +#SoC support +obj-$(CONFIG_SPACEMIT_K3_CCU) += ccu-k3.o ccu_mix.o ccu_plla.o diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c new file mode 100644 index 00000000000..06c7aa90b82 --- /dev/null +++ b/drivers/clk/spacemit/ccu-k3.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Spacemit k3 clock controller driver + * + * Copyright (c) 2023, spacemit Corporation. + * + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "ccu_mix.h" +#include "ccu_plla.h" +#include "ccu-k3.h" + + +struct spacemit_ccu_clk k3_clock_controller; +struct clk vctcxo_24, vctcxo_3, vctcxo_1, pll1_vco, osc_32k, clk_dummy; + +static SPACEMIT_CCU_FACTOR(pll1_2457p6_vco, "pll1_2457p6_vco", "pll1_vco", + 1, 100); + +static const struct ccu_plla_rate_tbl pll2_rate_tbl[] = { + PLLA_RATE(3000000000UL, 0x0b3e2000, 0x00000000, 0xa0558c8c), +}; + +static SPACEMIT_CCU_PLLA(pll2, "pll2", &pll2_rate_tbl, ARRAY_SIZE(pll2_rate_tbl), + BASE_TYPE_APBS, APBS_PLL2_SWCR1, APBS_PLL2_SWCR2, APBS_PLL2_SWCR3, + MPMU_POSR, POSR_PLL2_LOCK, 1, + 0); + +static SPACEMIT_CCU_GATE_FACTOR(pll1_d5, "pll1_d5", "pll1_2457p6_vco", + BASE_TYPE_APBS, APBS_PLL1_SWCR2, + BIT(4), BIT(4), 0x0, + 5, 1, 0); +static SPACEMIT_CCU_GATE_FACTOR(pll1_d6, "pll1_d6", "pll1_2457p6_vco", + BASE_TYPE_APBS, APBS_PLL1_SWCR2, + BIT(5), BIT(5), 0x0, + 6, 1, 0); +static SPACEMIT_CCU_GATE_FACTOR(pll1_d8, "pll1_d8", "pll1_2457p6_vco", + BASE_TYPE_APBS, APBS_PLL1_SWCR2, + BIT(7), BIT(7), 0x0, + 8, 1, 0); + +static SPACEMIT_CCU_DIV_GATE(pll1_dx, "pll1_dx", "pll1_2457p6_vco", + BASE_TYPE_APBS, APBS_PLL1_SWCR2, + 23, 5, BIT(22), BIT(22), 0x0, + 0); + +static SPACEMIT_CCU_GATE_FACTOR(pll2_d8, "pll2_d8", "pll2", + BASE_TYPE_APBS, APBS_PLL2_SWCR2, + BIT(7), BIT(7), 0x0, + 8, 1, 0); + +static SPACEMIT_CCU_GATE(pll1_d8_307p2, "pll1_d8_307p2", "pll1_d8", + BASE_TYPE_MPMU, MPMU_ACGR, + BIT(13), BIT(13), 0x0, + 0); +static SPACEMIT_CCU_GATE(pll1_d6_409p6, "pll1_d6_409p6", "pll1_d6", + BASE_TYPE_MPMU, MPMU_ACGR, + BIT(0), BIT(0), 0x0, + 0); +static SPACEMIT_CCU_GATE(pll1_d5_491p52, "pll1_d5_491p52", "pll1_d5", + BASE_TYPE_MPMU, MPMU_ACGR, + BIT(21), BIT(21), 0x0, + 0); +static SPACEMIT_CCU_GATE_FACTOR(pll1_d10_245p76, "pll1_d10_245p76", "pll1_d5", + BASE_TYPE_MPMU, MPMU_ACGR, + BIT(18), BIT(18), 0x0, + 2, 1, 0); + +static const char * const qspi_parent_names[] = { + "pll1_d6_409p6", "pll2_d8", "pll1_d8_307p2", "pll1_d10_245p76", + "clk_dummy", "pll1_dx", "pll1_d5_491p52", "clk_dummy" +}; + +static SPACEMIT_CCU_DIV_MFC_MUX_GATE(qspi_clk, "qspi_clk", qspi_parent_names, + BASE_TYPE_APMU, APMU_QSPI_CLK_RES_CTRL, + 9, 3, BIT(12), + 6, 3, BIT(4), BIT(4), 0x0, + 0); + +static SPACEMIT_CCU_GATE(qspi_bus_clk, "qspi_bus_clk", "clk_dummy", + BASE_TYPE_APMU, APMU_QSPI_CLK_RES_CTRL, + BIT(3), BIT(3), 0x0, + 0); + +static struct spacemit_clk_table spacemit_k3_clks = { + .clks = { + [CLK_PLL1_2457P6] = &pll1_2457p6_vco.common.clk, + [CLK_PLL2] = &pll2.common.clk, + [CLK_PLL1_D5] = &pll1_d5.common.clk, + [CLK_PLL1_D6] = &pll1_d6.common.clk, + [CLK_PLL1_D8] = &pll1_d8.common.clk, + [CLK_PLL1_DX] = &pll1_dx.common.clk, + [CLK_PLL2_D8] = &pll2_d8.common.clk, + [CLK_PLL1_307P2] = &pll1_d8_307p2.common.clk, + [CLK_PLL1_409P6] = &pll1_d6_409p6.common.clk, + [CLK_PLL1_491] = &pll1_d5_491p52.common.clk, + [CLK_PLL1_245P76] = &pll1_d10_245p76.common.clk, + [CLK_QSPI] = &qspi_clk.common.clk, + [CLK_QSPI_BUS] = &qspi_bus_clk.common.clk, + }, + .num = CLK_MAX_NO, +}; + +static int ccu_of_xlate(struct clk *clk, + struct ofnode_phandle_args *args) +{ + if (args->args_count > 1) { + pr_debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + if (args->args_count) { + clk->id = args->args[0]; + if (clk->id >= spacemit_k3_clks.num || + !spacemit_k3_clks.clks[clk->id]) { + pr_debug("Unsupported clock id: %u\n", args->args[0]); + return -EINVAL; + } + } else { + clk->id = 0; + } + + clk->data = 0; + return 0; +} + +static ulong ccu_clk_round_rate(struct clk *clk, ulong rate) +{ + struct clk *c; + int err = clk_get_by_id(clk->id, &c); + + if (err) + return err; + + return clk_round_rate(c, rate); +} + +const struct clk_ops ccu_clk_ops = { + .get_rate = ccf_clk_get_rate, + .round_rate = ccu_clk_round_rate, + .set_parent = ccf_clk_set_parent, + .disable = ccf_clk_disable, + .set_rate = ccf_clk_set_rate, + .enable = ccf_clk_enable, + .of_xlate = ccu_of_xlate, +}; + +int ccu_common_init(struct clk * clk, struct spacemit_ccu_clk *clk_info, struct spacemit_clk_table *clks) +{ + struct ccu_common *common = clk_to_ccu_common(clk); + int ret; + + if (!common) + return -1; + + switch(common->base_type){ + case BASE_TYPE_MPMU: + common->base = clk_info->mpmu_base; + break; + case BASE_TYPE_APMU: + common->base = clk_info->apmu_base; + break; + case BASE_TYPE_APBC: + common->base = clk_info->apbc_base; + break; + case BASE_TYPE_APBS: + common->base = clk_info->apbs_base; + break; + case BASE_TYPE_CIU: + common->base = clk_info->ciu_base; + break; + case BASE_TYPE_DCIU: + common->base = clk_info->dciu_base; + break; + case BASE_TYPE_DDRC: + common->base = clk_info->ddrc_base; + break; + case BASE_TYPE_AUDC: + common->base = clk_info->audio_ctrl_base; + break; + case BASE_TYPE_APBC2: + common->base = clk_info->apbc2_base; + break; + case BASE_TYPE_RCPU: + common->base = clk_info->rcpu_base; + break; + default: + common->base = clk_info->apbc_base; + break; + + } + common->clk_tbl = clks; + if (common->is_pll) { + struct ccu_plla *pll = clk_to_ccu_plla(clk); + + pll->pll.lock_base = clk_info->mpmu_base; + } + + if(common->parent_name == NULL && common->parent_names != NULL) + common->parent_name = common->parent_names[ccu_mix_get_parent(clk)]; + + ret = clk_register(clk, common->driver_name, common->name, common->parent_name); + + return ret; +} + +int spacemit_ccu_probe(struct spacemit_ccu_clk *clk_info, + struct spacemit_clk_table *clks) +{ + int i; + + for (i = CLK_PLL1_2457P6; i < clks->num ; i++) { + struct clk *clk = clks->clks[i]; + int ret; + + if (!clk) + continue; + + if (i >= CLK_VCTCXO_24) + continue; + + clk->id = i; + ret = ccu_common_init(clk, clk_info, clks); + if (ret) + return ret; + } + return 0; +} + +static inline void ccu_clk_dm(ulong id, struct clk *clk) +{ + if (!IS_ERR(clk)){ + clk->id = id; + spacemit_k3_clks.clks[id] = clk; + } +} + +static int spacemit_k3_ccu_probe(struct udevice *dev) +{ + int ret = 0; + struct spacemit_ccu_clk *clk_info = &k3_clock_controller; + struct spacemit_clk_table *clks = &spacemit_k3_clks; + + pr_debug("init clock start \n"); + + clk_info->mpmu_base = (void __iomem *)dev_remap_addr_index(dev, 0); + if (!clk_info->mpmu_base) { + pr_err("failed to map mpmu registers\n"); + goto out; + } + + clk_info->apmu_base = (void __iomem *)dev_remap_addr_index(dev, 1); + if (!clk_info->apmu_base) { + pr_err("failed to map apmu registers\n"); + goto out; + } + + clk_info->apbc_base = (void __iomem *)dev_remap_addr_index(dev, 2); + if (!clk_info->apbc_base) { + pr_err("failed to map apbc registers\n"); + goto out; + } + + clk_info->apbs_base = (void __iomem *)dev_remap_addr_index(dev, 3); + if (!clk_info->apbs_base) { + pr_err("failed to map apbs registers\n"); + goto out; + } + clk_get_by_name(dev, "vctcxo_24", &vctcxo_24); + ccu_clk_dm(CLK_VCTCXO_24, dev_get_clk_ptr(vctcxo_24.dev)); + clk_get_by_name(dev, "vctcxo_3", &vctcxo_3); + ccu_clk_dm(CLK_VCTCXO_3, dev_get_clk_ptr(vctcxo_3.dev)); + clk_get_by_name(dev, "vctcxo_1", &vctcxo_1); + ccu_clk_dm(CLK_VCTCXO_1, dev_get_clk_ptr(vctcxo_1.dev)); + clk_get_by_name(dev, "pll1_vco", &pll1_vco); + ccu_clk_dm(CLK_PLL1, dev_get_clk_ptr(pll1_vco.dev)); + clk_get_by_name(dev, "osc_32k", &osc_32k); + ccu_clk_dm(OSC_32K, dev_get_clk_ptr(osc_32k.dev)); + clk_get_by_name(dev, "clk_dummy", &clk_dummy); + ccu_clk_dm(CLK_DUMMY, dev_get_clk_ptr(clk_dummy.dev)); + + ret = spacemit_ccu_probe(clk_info, clks); + pr_debug("init clock finish ret=%d \n", ret); + if (!ret) + return 0; +out: + return -1; +} + +static const struct udevice_id ccu_clk_ids[] = { + { .compatible = "spacemit,k3-ccu" }, + { }, +}; + +U_BOOT_DRIVER(spacemit_k3_ccu) = { + .name = "k3-ccu", + .id = UCLASS_CLK, + .of_match = ccu_clk_ids, + .ops = &ccu_clk_ops, + .probe = spacemit_k3_ccu_probe, +}; diff --git a/drivers/clk/spacemit/ccu-k3.h b/drivers/clk/spacemit/ccu-k3.h new file mode 100644 index 00000000000..398a22e6a0e --- /dev/null +++ b/drivers/clk/spacemit/ccu-k3.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025, spacemit Corporation. + * + */ + +#ifndef _CCU_SPACEMIT_K3_H_ +#define _CCU_SPACEMIT_K3_H_ + +#define APBS_PLL1_SWCR2 0x104 +#define APBS_PLL2_SWCR1 0x118 +#define APBS_PLL2_SWCR2 0x11c +#define APBS_PLL2_SWCR3 0x120 + +#define MPMU_FCCR 0x0008 +#define MPMU_POSR 0x0010 +#define POSR_PLL2_LOCK BIT(25) +#define MPMU_ACGR 0x1024 + +#define APMU_QSPI_CLK_RES_CTRL 0x060 + +#endif /* _CCU_SPACEMIT_K3_H_ */ diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c new file mode 100644 index 00000000000..bfd88d66444 --- /dev/null +++ b/drivers/clk/spacemit/ccu_mix.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Spacemit clock type mix(div/mux/gate/factor) + * + * Copyright (c) 2023, spacemit Corporation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccu_mix.h" + +#define TIMEOUT_LIMIT (20000) /* max timeout 10000us */ + +static unsigned long ccu_rate_delta(unsigned long rate1, unsigned long rate2) +{ + return rate1 > rate2 ? rate1 - rate2 : rate2 - rate1; +} + +static int ccu_mix_trigger_fc(struct clk *clk) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + unsigned long val = 0; + + int ret = 0, timeout = 50; + + if (common->reg_type == CLK_DIV_TYPE_1REG_FC_V2 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4 || + common->reg_type == CLK_DIV_TYPE_1REG_FC_DIV_V5 || + common->reg_type == CLK_DIV_TYPE_1REG_FC_MUX_V6) { + timeout = 50; + val = readl(common->base + common->reg_ctrl); + val |= common->fc; + writel(val, common->base + common->reg_ctrl); + + do { + val = readl(common->base + common->reg_ctrl); + timeout--; + if (!(val & (common->fc))) + break; + } while (timeout); + + if (timeout == 0) { + timeout = 5000; + do { + val = readl(common->base + common->reg_ctrl); + timeout--; + if (!(val & (common->fc))) + break; + } while (timeout); + if (timeout != 0) { + ret = 0; + + } else { + ret = -1; + } + } + } + + return ret; +} + +static int ccu_mix_disable(struct clk *clk) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_gate_config *gate = mix->gate; + u32 tmp; + + if (!gate) + return 0; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + tmp = readl(common->base + common->reg_sel); + else + tmp = readl(common->base + common->reg_ctrl); + + tmp &= ~gate->gate_mask; + tmp |= gate->val_disable; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + writel(tmp, common->base + common->reg_sel); + else + writel(tmp, common->base + common->reg_ctrl); + + if (gate->flags & SPACEMIT_CLK_GATE_NEED_DELAY) { + udelay(200); + } + + return 0; +} + +static ulong ccu_mix_round_rate(struct clk *clk, ulong rate) +{ + return rate; +} + +static int ccu_mix_set_parent(struct clk *clk, struct clk *parent) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_mux_config *mux = mix->mux; + int index = -ENOENT; + u32 reg, i; + int ret; + + if (!parent) + return -EINVAL; + + for (i = 0; i < common->num_parents; i++) { + if (!strcmp(parent->dev->name, common->parent_names[i])) { + index = i; + break; + } + } + + if (index < 0) { + pr_info("Could not fetch index\n"); + return index; + } + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + reg = readl(common->base + common->reg_sel); + else + reg = readl(common->base + common->reg_ctrl); + + reg &= ~GENMASK(mux->width + mux->shift - 1, mux->shift); + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + writel(reg | (index << mux->shift), + common->base + common->reg_sel); + else + writel(reg | (index << mux->shift), + common->base + common->reg_ctrl); + + if (common->reg_type == CLK_DIV_TYPE_1REG_FC_V2 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4 || + common->reg_type == CLK_DIV_TYPE_1REG_FC_MUX_V6) { + ret = ccu_mix_trigger_fc(clk); + if (ret) + pr_info("%s of %s timeout\n", __func__, clk->dev->name); + } + + return 0; +} + +static int ccu_mix_enable(struct clk *clk) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_gate_config *gate = mix->gate; + u32 tmp; + u32 val = 0; + int timeout_power = 1; + + if (!gate) + return 0; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + tmp = readl(common->base + common->reg_sel); + else + tmp = readl(common->base + common->reg_ctrl); + + tmp &= ~gate->gate_mask; + tmp |= gate->val_enable; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + writel(tmp, common->base + common->reg_sel); + else + writel(tmp, common->base + common->reg_ctrl); + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + val = readl(common->base + common->reg_sel); + else + val = readl(common->base + common->reg_ctrl); + + while ((val & gate->gate_mask) != gate->val_enable && + (timeout_power < TIMEOUT_LIMIT)) { + udelay(timeout_power); + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + val = readl(common->base + common->reg_sel); + else + val = readl(common->base + common->reg_ctrl); + timeout_power *= 10; + } + + if (timeout_power > 1) { + if (val == tmp) + pr_info("write clk_gate %s timeout occur, read pass after %d us delay\n", + clk_hw_get_name(&common->clk), timeout_power); + else + pr_info("write clk_gate %s timeout after %d us!\n", + clk_hw_get_name(&common->clk), timeout_power); + } + + if (gate->flags & SPACEMIT_CLK_GATE_NEED_DELAY) { + udelay(200); + } + + return 0; +} + +static ulong ccu_mix_get_rate(struct clk *clk) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_div_config *div = mix->div; + unsigned long parent_rate = clk_get_parent_rate(clk); + unsigned long val; + u32 reg; + + if (!div) { + if (mix->factor) + val = parent_rate * mix->factor->mul / mix->factor->div; + else + val = parent_rate; + return val; + } + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + reg = readl(common->base + common->reg_sel); + else + reg = readl(common->base + common->reg_ctrl); + + val = reg >> div->shift; + val &= (1 << div->width) - 1; + + val = divider_recalc_rate(clk, parent_rate, val, div->table, div->flags, + div->width); + + return val; +} + +unsigned long ccu_mix_calc_best_rate(struct clk *clk, unsigned long rate, + u32 *mux_val, u32 *div_val, u32 *parent_id) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_div_config *div = mix->div ? mix->div : NULL; + struct ccu_mux_config *mux = mix->mux ? mix->mux : NULL; + struct clk *parent = NULL; + unsigned long parent_rate = 0, best_rate = 0, best_delta = ULONG_MAX; + u32 i, j, p_id, div_max; + + if (mux) { + for (i = 0; i < common->num_parents; i++) { + parent = NULL; + for (p_id = 0; p_id < common->clk_tbl->num; p_id++) { + parent = common->clk_tbl->clks[p_id]; + if (!parent) + continue; + if (!strcmp(parent->dev->name, + common->parent_names[i])) { + break; + } + parent = NULL; + } + + if (!parent) + continue; + + parent_rate = clk_get_rate(parent); + if (!parent_rate) + continue; + + if (div) + div_max = 1 << div->width; + else + div_max = 1; + for (j = 1; j <= div_max; j++) { + unsigned long candidate_rate = + DIV_ROUND_UP_ULL(parent_rate, j); + unsigned long delta = + ccu_rate_delta(candidate_rate, rate); + + if (delta < best_delta) { + best_rate = candidate_rate; + best_delta = delta; + *mux_val = i; + *div_val = j - 1; + *parent_id = p_id; + } + } + } + } else { + parent_rate = clk_get_parent_rate(clk); + if (!parent_rate) + return 0; + + if (div) + div_max = 1 << div->width; + else + div_max = 1; + for (j = 1; j <= div_max; j++) { + unsigned long candidate_rate = + DIV_ROUND_UP_ULL(parent_rate, j); + unsigned long delta = ccu_rate_delta(candidate_rate, + rate); + + if (delta < best_delta) { + best_rate = candidate_rate; + best_delta = delta; + *div_val = j - 1; + } + } + } + return best_rate; +} + +static ulong ccu_mix_set_rate(struct clk *clk, unsigned long rate) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_div_config *div_config = mix->div ? mix->div : NULL; + struct ccu_mux_config *mux_config = mix->mux ? mix->mux : NULL; + unsigned long best_rate = 0; + u32 cur_mux, cur_div, mux_val = 0, div_val = 0, parent_id = 0; + struct clk *parent; + unsigned long val; + u32 reg; + int ret; + + if (!div_config && !mux_config) { + return 0; + } + + best_rate = ccu_mix_calc_best_rate(clk, rate, &mux_val, &div_val, + &parent_id); + if (!best_rate) + return -EINVAL; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + reg = readl(common->base + common->reg_sel); + else + reg = readl(common->base + common->reg_ctrl); + + if (mux_config) { + cur_mux = reg >> mux_config->shift; + cur_mux &= (1 << mux_config->width) - 1; + if (cur_mux != mux_val) { + parent = common->clk_tbl->clks[parent_id]; + if (!clk_valid(parent)) + return -EINVAL; + + ret = clk_set_parent(clk, parent); + if (ret) + return ret; + } + } + if (div_config) { + cur_div = reg >> div_config->shift; + cur_div &= (1 << div_config->width) - 1; + if (cur_div == div_val) + return 0; + } else { + return 0; + } + val = div_val; + if (val > BIT(div_config->width) - 1) + return 0; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + reg = readl(common->base + common->reg_sel); + else + reg = readl(common->base + common->reg_ctrl); + + reg &= ~GENMASK(div_config->width + div_config->shift - 1, + div_config->shift); + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + writel(reg | (val << div_config->shift), + common->base + common->reg_sel); + else + writel(reg | (val << div_config->shift), + common->base + common->reg_ctrl); + + if (common->reg_type == CLK_DIV_TYPE_1REG_FC_V2 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4 || + common->reg_type == CLK_DIV_TYPE_1REG_FC_DIV_V5 || + common->reg_type == CLK_DIV_TYPE_1REG_FC_MUX_V6) { + ret = ccu_mix_trigger_fc(clk); + if (ret) + pr_info("%s of %s timeout\n", __func__, clk->dev->name); + } + return 0; +} + +unsigned int ccu_mix_get_parent(struct clk *clk) +{ + struct ccu_mix *mix = clk_to_ccu_mix(clk); + struct ccu_common *common = &mix->common; + struct ccu_mux_config *mux = mix->mux; + u32 reg; + unsigned int parent; + + if (!mux) + return 0; + + if (common->reg_type == CLK_DIV_TYPE_2REG_NOFC_V3 || + common->reg_type == CLK_DIV_TYPE_2REG_FC_V4) + reg = readl(common->base + common->reg_sel); + else + reg = readl(common->base + common->reg_ctrl); + + parent = reg >> mux->shift; + parent &= (1 << mux->width) - 1; + + if (mux->table) { + int num_parents = common->num_parents; + int i; + + for (i = 0; i < num_parents; i++) + if (mux->table[i] == parent) + return i; + } + return parent; +} + +const struct clk_ops ccu_mix_ops = { + .disable = ccu_mix_disable, + .round_rate = ccu_mix_round_rate, + .set_parent = ccu_mix_set_parent, + .enable = ccu_mix_enable, + .get_rate = ccu_mix_get_rate, + .set_rate = ccu_mix_set_rate, +}; + +U_BOOT_DRIVER(ccu_clk_mix) = { + .name = CCU_CLK_MIX, + .id = UCLASS_CLK, + .ops = &ccu_mix_ops, +}; diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h new file mode 100644 index 00000000000..b11ec565e62 --- /dev/null +++ b/drivers/clk/spacemit/ccu_mix.h @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, spacemit Corporation. + * + */ + +#ifndef _CCU_MIX_H_ +#define _CCU_MIX_H_ + +#include "ccu-k3.h" +#include "common.h" + +#define CCU_CLK_MIX "ccu_clk_mix" +#define SPACEMIT_CLK_GATE_NEED_DELAY BIT(0) + +struct ccu_gate_config { + u32 gate_mask; + u32 val_enable; + u32 val_disable; + u32 flags; +}; + +struct ccu_factor_config { + u32 div; + u32 mul; +}; + +struct ccu_mux_config { + u8 shift; + u8 width; + const u8 *table; + u32 flags; +}; + +struct ccu_div_config { + u8 shift; + u8 width; + u32 max; + u32 offset; + u32 flags; + struct clk_div_table *table; +}; + +struct ccu_mix { + struct ccu_gate_config *gate; + struct ccu_factor_config *factor; + struct ccu_div_config *div; + struct ccu_mux_config *mux; + struct ccu_common common; +}; + +#define CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, _flags) \ + (&(struct ccu_gate_config) { \ + .gate_mask = _gate_mask, \ + .val_enable = _val_enable, \ + .val_disable = _val_disable, \ + .flags = _flags, \ + }) + +#define CCU_FACTOR_INIT(_div, _mul) \ + (&(struct ccu_factor_config) { \ + .div = _div, \ + .mul = _mul, \ + }) + + +#define CCU_MUX_INIT(_shift, _width, _table, _flags) \ + (&(struct ccu_mux_config) { \ + .shift = _shift, \ + .width = _width, \ + .table = _table, \ + .flags = _flags, \ + }) + +#define CCU_DIV_INIT(_shift, _width, _table, _flags) \ + (&(struct ccu_div_config) { \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ + .table = _table, \ + }) + +#define SPACEMIT_CCU_GATE(_struct, _name, _parent, _base_type, _reg, \ + _gate_mask, _val_enable, _val_disable, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_name = _parent, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } +#define SPACEMIT_CCU_GATE_NO_PARENT(_struct, _name, _parent, _base_type, _reg, \ + _gate_mask, _val_enable, _val_disable, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_name = SPACEMIT_CLK_NO_PARENT, \ + .num_parents = 0, \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } + +#define SPACEMIT_CCU_FACTOR(_struct, _name, _parent, \ + _div, _mul) \ + struct ccu_mix _struct = { \ + .factor = CCU_FACTOR_INIT(_div, _mul), \ + .common = { \ + .name = _name, \ + .parent_name = _parent, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_MIX, \ + } \ + } + +#define SPACEMIT_CCU_MUX(_struct, _name, _parents, _base_type, _reg, \ + _shift, _width, _flags) \ + struct ccu_mix _struct = { \ + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } + +#define SPACEMIT_CCU_DIV(_struct, _name, _parent, _base_type, _reg, \ + _shift, _width, _flags) \ + struct ccu_mix _struct = { \ + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_name = _parent, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } + +#define SPACEMIT_CCU_GATE_FACTOR(_struct, _name, _parent, _base_type, _reg, \ + _gate_mask, _val_enable, _val_disable, \ + _div, _mul, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .factor = CCU_FACTOR_INIT(_div, _mul), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_name = _parent, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } + + +#define SPACEMIT_CCU_MUX_GATE(_struct, _name, _parents, _base_type, _reg, \ + _shift, _width, _gate_mask, _val_enable, _val_disable, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } + +#define SPACEMIT_CCU_DIV_GATE(_struct, _name, _parent, _base_type, _reg, \ + _shift, _width, _gate_mask, _val_enable, _val_disable, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_name = _parent, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + } \ + } + + +#define SPACEMIT_CCU_DIV_MUX_GATE(_struct, _name, _parents, \ + _base_type, _reg_ctrl, \ + _mshift, _mwidth, \ + _muxshift, _muxwidth, \ + _gate_mask, _val_enable, _val_disable, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +#define SPACEMIT_CCU_DIV2_FC_MUX_GATE(_struct, _name, _parents, _base_type, _reg_ctrl, _reg_sel, \ + _mshift, _mwidth, _fc, _muxshift, _muxwidth, _gate_mask, _val_enable, _val_disable, \ + _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_2REG_FC_V4, \ + .reg_ctrl = _reg_ctrl, \ + .reg_sel = _reg_sel, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + + +#define SPACEMIT_CCU_DIV_FC_MUX_GATE(_struct, _name, _parents, _base_type, _reg_ctrl, \ + _mshift, _mwidth, _fc, _muxshift, _muxwidth, _gate_mask, _val_enable, _val_disable, \ + _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_1REG_FC_V2, \ + .reg_ctrl = _reg_ctrl, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +#define SPACEMIT_CCU_DIV_MFC_MUX_GATE(_struct, _name, _parents, _base_type, _reg_ctrl, \ + _mshift, _mwidth, _fc, _muxshift, _muxwidth, _gate_mask, _val_enable, _val_disable, \ + _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_1REG_FC_MUX_V6, \ + .reg_ctrl = _reg_ctrl, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +#define SPACEMIT_CCU_DIV_FC_WITH_GATE(_struct, _name, _parent, _base_type, _reg_ctrl, \ + _mshift, _mwidth, _fc, _gate_mask, _val_enable, _val_disable, \ + _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_1REG_FC_V2, \ + .reg_ctrl = _reg_ctrl, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_name = _parent, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +#define SPACEMIT_CCU_DIV_FC_MUX(_struct, _name, _parents, _base_type, _reg_ctrl, \ + _mshift, _mwidth, _fc, _muxshift, _muxwidth, _flags) \ + struct ccu_mix _struct = { \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_1REG_FC_V2, \ + .reg_ctrl = _reg_ctrl, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +#define SPACEMIT_CCU_MUX_FC_GATE(_struct, _name, _parents, _base_type, _reg_ctrl, \ + _fc, _muxshift, _muxwidth, _gate_mask, _val_enable, _val_disable, _flags) \ + struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, _val_disable, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_1REG_FC_V2, \ + .reg_ctrl = _reg_ctrl, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +#define SPACEMIT_CCU_MUX_FC(_struct, _name, _parents, _base_type, _reg_ctrl, \ + _fc, _muxshift, _muxwidth, _flags) \ + struct ccu_mix _struct = { \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_type = CLK_DIV_TYPE_1REG_FC_V2, \ + .reg_ctrl = _reg_ctrl, \ + .fc = _fc, \ + .base_type = _base_type, \ + .name = _name, \ + .parent_names = _parents, \ + .num_parents = ARRAY_SIZE(_parents), \ + .driver_name = CCU_CLK_MIX, \ + .flags = _flags, \ + }, \ + } + +static inline struct ccu_mix *clk_to_ccu_mix(struct clk *clk) +{ + struct ccu_common *common = clk_to_ccu_common(clk); + + return container_of(common, struct ccu_mix, common); +} + +unsigned int ccu_mix_get_parent(struct clk *clk); + +#endif /* _CCU_DIV_H_ */ diff --git a/drivers/clk/spacemit/ccu_plla.c b/drivers/clk/spacemit/ccu_plla.c new file mode 100644 index 00000000000..d404f1d18a2 --- /dev/null +++ b/drivers/clk/spacemit/ccu_plla.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 SpacemiT Technology Co. Ltd + * Copyright (c) 2024-2025 Haylen Chu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ccu_plla.h" + +#define PLL_MIN_FREQ 600000000 +#define PLL_MAX_FREQ 3400000000 +#define PLL_DELAYTIME 590 //(590*5)us + +#define PLLA_SWCR2_MASK GENMASK(15, 8) + +#define plla_readl(reg) readl(reg) +#define plla_readl_pll_swcr1(p) plla_readl(p.base + p.reg_ctrl) +#define plla_readl_pll_swcr2(p) plla_readl(p.base + p.reg_sel) +#define plla_readl_pll_swcr3(p) plla_readl(p.base + p.reg_xtc) + +#define plla_writel(val, reg) writel(val, reg) +#define plla_writel_pll_swcr1(val, p) plla_writel(val, p.base + p.reg_ctrl) +#define plla_writel_pll_swcr2(val, p) plla_writel(val, p.base + p.reg_sel) +#define plla_writel_pll_swcr3(val, p) plla_writel(val, p.base + p.reg_xtc) + +/* unified pllax_swcr1 for plla */ +union pllax_swcr1 { + struct { + unsigned int reg1:8; + unsigned int reg2:8; + unsigned int reg3:8; + unsigned int reg4:8; + } b; + unsigned int v; +}; + +/* unified pllax_swcr2 for plla */ +union pllax_swcr2 { + struct { + unsigned int div1_en:1; + unsigned int div2_en:1; + unsigned int div3_en:1; + unsigned int div4_en:1; + unsigned int div5_en:1; + unsigned int div6_en:1; + unsigned int div7_en:1; + unsigned int div8_en:1; + unsigned int reg0:8; + unsigned int pll_en:1; + unsigned int mon_cfg:4; + unsigned int div10_en:1; + unsigned int mmd_en:1; + unsigned int mmd:5; + unsigned int reserved2:3; + unsigned int div64_en:1; + } b; + unsigned int v; +}; + +/* unified pllax_swcr3 for plla */ +union pllax_swcr3{ + struct { + unsigned int reg5:8; + unsigned int reg6:8; + unsigned int reg7:8; + unsigned int reg8:8; + } b; + + unsigned int v; +}; + +static int ccu_plla_is_enabled(struct clk *clk) +{ + struct ccu_plla *p = clk_to_ccu_plla(clk); + union pllax_swcr2 swcr2; + unsigned int enabled; + + swcr2.v = plla_readl_pll_swcr2(p->common); + enabled = swcr2.b.pll_en; + + return enabled; +} + +/* frequency unit Mhz, return pll vco freq */ +static ulong __get_vco_freq(struct clk *clk) +{ + unsigned int size, i; + struct ccu_plla_rate_tbl *freq_pll_regs_table; + struct ccu_plla *p = clk_to_ccu_plla(clk); + union pllax_swcr1 swcr1; + union pllax_swcr2 swcr2; + union pllax_swcr3 swcr3; + + swcr1.v = plla_readl_pll_swcr1(p->common); + swcr2.v = plla_readl_pll_swcr2(p->common); + swcr2.v &= PLLA_SWCR2_MASK; + swcr3.v = plla_readl_pll_swcr3(p->common); + + freq_pll_regs_table = p->pll.rate_tbl; + size = p->pll.tbl_size; + + for (i = 0; i < size; i++) { + if ((freq_pll_regs_table[i].swcr1 == swcr1.v) && + (freq_pll_regs_table[i].swcr2 == swcr2.v) && + (freq_pll_regs_table[i].swcr3 == swcr3.v)) + return freq_pll_regs_table[i].rate; + } + + pr_info("Unknown rate for clock\n"); + return 0; +} + +static int ccu_plla_enable(struct clk *clk) +{ + unsigned int delaytime = PLL_DELAYTIME; + struct ccu_plla *p = clk_to_ccu_plla(clk); + union pllax_swcr2 swcr2; + + if (ccu_plla_is_enabled(clk)) + return 0; + + swcr2.v = plla_readl_pll_swcr2(p->common); + swcr2.b.pll_en = 1; + plla_writel_pll_swcr2(swcr2.v, p->common); + + /* check lock status */ + udelay(50); + + while ((!(readl(p->pll.lock_base + p->pll.reg_lock) & p->pll.lock_enable_bit)) + && delaytime) { + udelay(5); + delaytime--; + } + if (unlikely(!delaytime)) + pr_err("ccu_pll_enable enabling didn't get stable within 3000us!!!\n" ); + + return 0; +} + +static int ccu_plla_disable(struct clk *clk) +{ + struct ccu_plla *p = clk_to_ccu_plla(clk); + union pllax_swcr2 swcr2; + + swcr2.v = plla_readl_pll_swcr2(p->common); + swcr2.b.pll_en = 0; + plla_writel_pll_swcr2(swcr2.v, p->common); + return 0; + +} + +/* + * pll rate change requires sequence: + * clock off -> change rate setting -> clock on + * This function doesn't really change rate, but cache the config + */ +static ulong ccu_plla_set_rate(struct clk *clk, ulong rate) +{ + unsigned int i, reg0 = 0; + struct ccu_plla *p = clk_to_ccu_plla(clk); + struct ccu_plla_config *params = &p->pll; + union pllax_swcr1 swcr1; + union pllax_swcr1 swcr2; + union pllax_swcr3 swcr3; + bool found = false; + bool pll_enabled = false; + + if (ccu_plla_is_enabled(clk)) { + pll_enabled = true; + ccu_plla_disable(clk); + } + + /* setp 1: calculate fbd frcd kvco and band */ + if (params->rate_tbl) { + for (i = 0; i < params->tbl_size; i++) { + if (rate == params->rate_tbl[i].rate) { + found = true; + + swcr1.v = params->rate_tbl[i].swcr1; + reg0 = params->rate_tbl[i].swcr2; + swcr3.v = params->rate_tbl[i].swcr3; + break; + } + } + } else { + pr_err("don't find freq table for pll\n"); + if (pll_enabled) + ccu_plla_enable(clk); + return -EINVAL; + } + + if (!found) { + pr_err("unsupported pll rate %lu\n", rate); + if (pll_enabled) + ccu_plla_enable(clk); + return -EINVAL; + } + + /* setp 2: set pll kvco/band and fbd/frcd setting */ + plla_writel_pll_swcr1(swcr1.v, p->common); + + swcr2.v = plla_readl_pll_swcr2(p->common); + swcr2.v &= ~PLLA_SWCR2_MASK; + swcr2.v |= reg0; + plla_writel_pll_swcr2(swcr2.v, p->common); + + plla_writel_pll_swcr3(swcr3.v, p->common); + + if (pll_enabled) + ccu_plla_enable(clk); + return 0; +} + +static ulong ccu_plla_get_rate(struct clk *clk) +{ + ulong val; + val = __get_vco_freq(clk); + return val; +} + +static ulong ccu_plla_round_rate(struct clk *clk, ulong rate) +{ + struct ccu_plla *p = clk_to_ccu_plla(clk); + unsigned long max_rate = 0; + unsigned int i; + struct ccu_plla_config *params = &p->pll; + + if (rate > PLL_MAX_FREQ || rate < PLL_MIN_FREQ) { + pr_err("%lu rate out of range!\n", rate); + return -EINVAL; + } + + if (params->rate_tbl) { + for (i = 0; i < params->tbl_size; i++) { + if (params->rate_tbl[i].rate <= rate) { + if (max_rate < params->rate_tbl[i].rate) + max_rate = params->rate_tbl[i].rate; + } + } + } else { + pr_info("don't find freq table for pll\n"); + } + return max_rate; +} + +const struct clk_ops ccu_plla_ops = { + .enable = ccu_plla_enable, + .disable = ccu_plla_disable, + .set_rate = ccu_plla_set_rate, + .get_rate = ccu_plla_get_rate, + .round_rate = ccu_plla_round_rate, +}; + +U_BOOT_DRIVER(ccu_clk_plla) = { + .name = CCU_CLK_PLLA, + .id = UCLASS_CLK, + .ops = &ccu_plla_ops, +}; diff --git a/drivers/clk/spacemit/ccu_plla.h b/drivers/clk/spacemit/ccu_plla.h new file mode 100644 index 00000000000..9db15edee35 --- /dev/null +++ b/drivers/clk/spacemit/ccu_plla.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024 SpacemiT Technology Co. Ltd + * Copyright (c) 2024-2025 Haylen Chu + */ + +#ifndef _CCU_PLLA_H_ +#define _CCU_PLLA_H_ + +#include "common.h" + +#define CCU_CLK_PLLA "ccu_clk_plla" +struct ccu_plla_rate_tbl { + unsigned long long rate; + u32 swcr1; + u32 swcr2; + u32 swcr3; +}; + +struct ccu_plla_config { + struct ccu_plla_rate_tbl *rate_tbl; + u32 tbl_size; + void __iomem *lock_base; + u32 reg_lock; + u32 lock_enable_bit; +}; + +#define PLLA_RATE(_rate, _swcr1, _swcr2, _swcr3) \ + { \ + .rate = _rate, \ + .swcr1 = _swcr1, \ + .swcr2 = _swcr2, \ + .swcr3 = _swcr3, \ + } + +struct ccu_plla { + struct ccu_plla_config pll; + struct ccu_common common; +}; + +#define _SPACEMIT_CCU_PLLA_CONFIG(_table, _size, _reg_lock, _lock_enable_bit) \ + { \ + .rate_tbl = (struct ccu_plla_rate_tbl *)_table, \ + .tbl_size = _size, \ + .reg_lock = _reg_lock, \ + .lock_enable_bit = _lock_enable_bit, \ + } + +#define SPACEMIT_CCU_PLLA(_struct, _name, _table, _size, \ + _base_type, _reg_ctrl, _reg_sel, _reg_xtc, \ + _reg_lock, _lock_enable_bit, _is_pll, \ + _flags) \ + struct ccu_plla _struct = { \ + .pll = _SPACEMIT_CCU_PLLA_CONFIG(_table, _size, _reg_lock, _lock_enable_bit), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .reg_sel = _reg_sel, \ + .reg_xtc = _reg_xtc, \ + .base_type = _base_type, \ + .is_pll = _is_pll, \ + .name = _name, \ + .parent_name = SPACEMIT_CLK_NO_PARENT, \ + .num_parents = 1, \ + .driver_name = CCU_CLK_PLLA, \ + } \ + } + + +static inline struct ccu_plla *clk_to_ccu_plla(struct clk *clk) +{ + struct ccu_common *common = clk_to_ccu_common(clk); + + return container_of(common, struct ccu_plla, common); +} + +#endif diff --git a/drivers/clk/spacemit/common.h b/drivers/clk/spacemit/common.h new file mode 100644 index 00000000000..2dfc057ce69 --- /dev/null +++ b/drivers/clk/spacemit/common.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, spacemit Corporation. + * + */ + +#ifndef _CCU_SPACEMIT_COMMON_H_ +#define _CCU_SPACEMIT_COMMON_H_ + +#include +#include + +#include + +#define SPACEMIT_CLK_NO_PARENT "clk_dummy" + +enum ccu_base_type{ + BASE_TYPE_MPMU = 0, + BASE_TYPE_APMU = 1, + BASE_TYPE_APBC = 2, + BASE_TYPE_APBS = 3, + BASE_TYPE_CIU = 4, + BASE_TYPE_DCIU = 5, + BASE_TYPE_DDRC = 6, + BASE_TYPE_AUDC = 7, + BASE_TYPE_APBC2 = 8, + BASE_TYPE_RCPU = 9, +}; + +enum { + CLK_DIV_TYPE_1REG_NOFC_V1 = 0, + CLK_DIV_TYPE_1REG_FC_V2, + CLK_DIV_TYPE_2REG_NOFC_V3, + CLK_DIV_TYPE_2REG_FC_V4, + CLK_DIV_TYPE_1REG_FC_DIV_V5, + CLK_DIV_TYPE_1REG_FC_MUX_V6, +}; + +struct ccu_common { + void __iomem *base; + enum ccu_base_type base_type; + u32 reg_type; + u32 reg_ctrl; + u32 reg_sel; + u32 reg_xtc; + u32 fc; + bool is_pll; + const char *name; + const char *driver_name; + const char *parent_name; + const char * const *parent_names; + u8 num_parents; + unsigned long flags; + struct clk clk; + struct spacemit_clk_table * clk_tbl; +}; + +struct spacemit_ccu_clk { + void __iomem *mpmu_base; + void __iomem *apmu_base; + void __iomem *apbc_base; + void __iomem *apbs_base; + void __iomem *ciu_base; + void __iomem *dciu_base; + void __iomem *ddrc_base; + void __iomem *audio_ctrl_base; + void __iomem *apbc2_base; + void __iomem *rcpu_base; + u32 pll2_freq; +}; + +struct spacemit_clk_table{ + struct clk* clks[CLK_MAX_NO]; + unsigned int num; +}; + +static inline struct ccu_common *clk_to_ccu_common(struct clk *clk) +{ + return container_of(clk, struct ccu_common, clk); +} + +#endif /* _CCU_SPACEMIT_COMMON_H_ */ diff --git a/include/dt-bindings/clock/spacemit-k3-clock.h b/include/dt-bindings/clock/spacemit-k3-clock.h new file mode 100644 index 00000000000..4c6d67008bf --- /dev/null +++ b/include/dt-bindings/clock/spacemit-k3-clock.h @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: (GPL-2.0+ or MIT) + +#ifndef _DT_BINDINGS_CLK_SPACEMIT_K3_H_ +#define _DT_BINDINGS_CLK_SPACEMIT_K3_H_ + +#define CLK_PLL1_2457P6 0 +#define CLK_PLL2 1 +#define CLK_PLL3 2 +#define CLK_PLL4 3 +#define CLK_PLL5 4 +#define CLK_PLL6 5 +#define CLK_PLL7 6 +#define CLK_PLL8 7 +#define CLK_PLL1_D2 8 +#define CLK_PLL1_D3 9 +#define CLK_PLL1_D4 10 +#define CLK_PLL1_D5 11 +#define CLK_PLL1_D6 12 +#define CLK_PLL1_D7 13 +#define CLK_PLL1_D8 14 +#define CLK_PLL1_DX 15 +#define CLK_PLL1_D64 16 +#define CLK_PLL1_D10_AUD 17 +#define CLK_PLL1_D100_AUD 18 +#define CLK_PLL2_D1 19 +#define CLK_PLL2_D2 20 +#define CLK_PLL2_D3 21 +#define CLK_PLL2_D4 22 +#define CLK_PLL2_D5 23 +#define CLK_PLL2_D6 24 +#define CLK_PLL2_D7 25 +#define CLK_PLL2_D8 26 +#define CLK_PLL2_66 27 +#define CLK_PLL2_33 28 +#define CLK_PLL2_50 29 +#define CLK_PLL2_25 30 +#define CLK_PLL2_20 31 +#define CLK_PLL2_D24_125 32 +#define CLK_PLL2_D120_25 33 +#define CLK_PLL3_D1 34 +#define CLK_PLL3_D2 35 +#define CLK_PLL3_D3 36 +#define CLK_PLL3_D4 37 +#define CLK_PLL3_D5 38 +#define CLK_PLL3_D6 39 +#define CLK_PLL3_D7 40 +#define CLK_PLL3_D8 41 +#define CLK_PLL4_D1 42 +#define CLK_PLL4_D2 43 +#define CLK_PLL4_D3 44 +#define CLK_PLL4_D4 45 +#define CLK_PLL4_D5 46 +#define CLK_PLL4_D6 47 +#define CLK_PLL4_D7 48 +#define CLK_PLL4_D8 49 +#define CLK_PLL5_D1 50 +#define CLK_PLL5_D2 51 +#define CLK_PLL5_D3 52 +#define CLK_PLL5_D4 53 +#define CLK_PLL5_D5 54 +#define CLK_PLL5_D6 55 +#define CLK_PLL5_D7 56 +#define CLK_PLL5_D8 57 +#define CLK_PLL6_D1 58 +#define CLK_PLL6_D2 59 +#define CLK_PLL6_D3 60 +#define CLK_PLL6_D4 61 +#define CLK_PLL6_D5 62 +#define CLK_PLL6_D6 63 +#define CLK_PLL6_D7 64 +#define CLK_PLL6_D8 65 +#define CLK_PLL6_80 66 +#define CLK_PLL6_40 67 +#define CLK_PLL6_20 68 +#define CLK_PLL7_D1 69 +#define CLK_PLL7_D2 70 +#define CLK_PLL7_D3 71 +#define CLK_PLL7_D4 72 +#define CLK_PLL7_D5 73 +#define CLK_PLL7_D6 74 +#define CLK_PLL7_D7 75 +#define CLK_PLL7_D8 76 +#define CLK_PLL8_D1 77 +#define CLK_PLL8_D2 78 +#define CLK_PLL8_D3 79 +#define CLK_PLL8_D4 80 +#define CLK_PLL8_D5 81 +#define CLK_PLL8_D6 82 +#define CLK_PLL8_D7 83 +#define CLK_PLL8_D8 84 +#define CLK_PLL1_307P2 85 +#define CLK_PLL1_76P8 86 +#define CLK_PLL1_61P44 87 +#define CLK_PLL1_153P6 88 +#define CLK_PLL1_102P4 89 +#define CLK_PLL1_51P2 90 +#define CLK_PLL1_51P2_AP 91 +#define CLK_PLL1_57P6 92 +#define CLK_PLL1_25P6 93 +#define CLK_PLL1_12P8 94 +#define CLK_PLL1_12P8_WDT 95 +#define CLK_PLL1_6P4 96 +#define CLK_PLL1_3P2 97 +#define CLK_PLL1_1P6 98 +#define CLK_PLL1_0P8 99 +#define CLK_PLL1_351 100 +#define CLK_PLL1_409P6 101 +#define CLK_PLL1_204P8 102 +#define CLK_PLL1_491 103 +#define CLK_PLL1_245P76 104 +#define CLK_PLL1_614 105 +#define CLK_PLL1_47P26 106 +#define CLK_PLL1_31P5 107 +#define CLK_PLL1_819 108 +#define CLK_PLL1_1228 109 +#define CLK_APB 110 +#define CLK_SLOW_UART1 111 +#define CLK_SLOW_UART2 112 +#define CLK_WDT 113 +#define CLK_RIPC 114 +#define CLK_UART1 115 +#define CLK_UART2 116 +#define CLK_UART3 117 +#define CLK_UART4 118 +#define CLK_UART5 119 +#define CLK_UART6 120 +#define CLK_UART7 121 +#define CLK_UART8 122 +#define CLK_UART9 123 +#define CLK_UART10 124 +#define CLK_GPIO 125 +#define CLK_PWM0 126 +#define CLK_PWM1 127 +#define CLK_PWM2 128 +#define CLK_PWM3 129 +#define CLK_PWM4 130 +#define CLK_PWM5 131 +#define CLK_PWM6 132 +#define CLK_PWM7 133 +#define CLK_PWM8 134 +#define CLK_PWM9 135 +#define CLK_PWM10 136 +#define CLK_PWM11 137 +#define CLK_PWM12 138 +#define CLK_PWM13 139 +#define CLK_PWM14 140 +#define CLK_PWM15 141 +#define CLK_PWM16 142 +#define CLK_PWM17 143 +#define CLK_PWM18 144 +#define CLK_PWM19 145 +#define CLK_SPI0 146 +#define CLK_SPI1 147 +#define CLK_SPI3 148 +#define CLK_RTC 149 +#define CLK_TWSI0 150 +#define CLK_TWSI1 151 +#define CLK_TWSI2 152 +#define CLK_TWSI4 153 +#define CLK_TWSI5 154 +#define CLK_TWSI6 155 +#define CLK_TWSI8 156 +#define CLK_TIMERS0 157 +#define CLK_TIMERS1 158 +#define CLK_TIMERS2 159 +#define CLK_TIMERS3 160 +#define CLK_TIMERS4 161 +#define CLK_TIMERS5 162 +#define CLK_TIMERS6 163 +#define CLK_TIMERS7 164 +#define CLK_AIB 165 +#define CLK_ONEWIRE 166 +#define CLK_I2S0 167 +#define CLK_I2S1 168 +#define CLK_I2S2 169 +#define CLK_I2S3 170 +#define CLK_I2S4 171 +#define CLK_I2S5 172 +#define CLK_DRO 173 +#define CLK_IR0 174 +#define CLK_IR1 175 +#define CLK_TSEN 176 +#define CLK_IPC_AP2RCPU 177 +#define CLK_CAN0 178 +#define CLK_CAN1 179 +#define CLK_CAN2 180 +#define CLK_CAN3 181 +#define CLK_CAN4 182 +#define CLK_CAN0_BUS 183 +#define CLK_CAN1_BUS 184 +#define CLK_CAN2_BUS 185 +#define CLK_CAN3_BUS 186 +#define CLK_CAN4_BUS 187 +#define CLK_AXICLK 188 +#define CLK_CCI550 189 +#define CLK_CPU_C1_PLL_SRC 190 +#define CLK_CPU_C3_PLL_SRC 191 +#define CLK_CPU_C0_CORE 192 +#define CLK_CPU_C1_CORE 193 +#define CLK_CPU_C2_CORE 194 +#define CLK_CPU_C3_CORE 195 +#define CLK_CCIC2PHY 196 +#define CLK_CCIC3PHY 197 +#define CLK_CSI 198 +#define CLK_ISP_BUS 199 +#define CLK_D1P_1228P8 200 +#define CLK_D1P_819P2 201 +#define CLK_D1P_614P4 202 +#define CLK_D1P_491P52 203 +#define CLK_D1P_409P6 204 +#define CLK_D1P_307P2 205 +#define CLK_D1P_245P76 206 +#define CLK_V2D 207 +#define CLK_DSI_ESC 208 +#define CLK_LCD_HCLK 209 +#define CLK_LCD_DSC 210 +#define CLK_LCD_PXCLK 211 +#define CLK_LCD_MCLK 212 +#define CLK_CCIC_4X 213 +#define CLK_CCIC1PHY 214 +#define CLK_SC2_HCLK 215 +#define CLK_SDH_AXI 216 +#define CLK_SDH0 217 +#define CLK_SDH1 218 +#define CLK_SDH2 219 +#define CLK_USB2_BUS 220 +#define CLK_USB3_PORTA_BUS 221 +#define CLK_USB3_PORTB_BUS 222 +#define CLK_USB3_PORTC_BUS 223 +#define CLK_USB3_PORTD_BUS 224 +#define CLK_QSPI 225 +#define CLK_QSPI_BUS 226 +#define CLK_DMA 227 +#define CLK_AES_WTM 228 +#define CLK_VPU 229 +#define CLK_DTC 230 +#define CLK_GPU 231 +#define CLK_MC_AHB 232 +#define CLK_TOP_DCLK 233 +#define CLK_UCIE 234 +#define CLK_UCIE_SBCLK 235 +#define CLK_RCPU 236 +#define CLK_DSI4LN2_DSI_ESC 237 +#define CLK_DSI4LN2_LCD_DSC 238 +#define CLK_DSI4LN2_LCD_PXCLK 239 +#define CLK_DSI4LN2_LCD_MCLK 240 +#define CLK_DSI4LN2_DPU_ACLK 241 +#define CLK_DPU_ACLK 242 +#define CLK_UFS_ACLK 243 +#define CLK_EDP0_PXCLK 244 +#define CLK_EDP1_PXCLK 245 +#define CLK_PCIEA 246 +#define CLK_PCIEB 247 +#define CLK_PCIEC 248 +#define CLK_PCIED 249 +#define CLK_PCIEE 250 +#define CLK_EMAC0_BUS 251 +#define CLK_EMAC0_REF 252 +#define CLK_EMAC0_1588 253 +#define CLK_EMAC0_RGMII_TX 254 +#define CLK_EMAC1_BUS 255 +#define CLK_EMAC1_REF 256 +#define CLK_EMAC1_1588 257 +#define CLK_EMAC1_RGMII_TX 258 +#define CLK_EMAC2_BUS 259 +#define CLK_EMAC2_REF 260 +#define CLK_EMAC2_1588 261 +#define CLK_EMAC2_RGMII_TX 262 +#define CLK_ESPI_SCLK_SRC 263 +#define CLK_ESPI_SCLK 264 +#define CLK_ESPI_MCLK 265 +#define CLK_CAM_SRC1 266 +#define CLK_CAM_SRC2 267 +#define CLK_CAM_SRC3 268 +#define CLK_CAM_SRC4 269 +#define CLK_ISIM_VCLK0 270 +#define CLK_ISIM_VCLK1 271 +#define CLK_ISIM_VCLK2 272 +#define CLK_ISIM_VCLK3 273 +#define CLK_HDMA 274 +#define CLK_DMA350 275 +#define CLK_C2_TCM_PIPE 276 +#define CLK_C3_TCM_PIPE 277 +#define CLK_RCPU_RT24_CORE0 278 +#define CLK_RCPU_RT24_CORE1 279 +#define CLK_SEC_UART1 280 +#define CLK_SEC_SSP2 281 +#define CLK_SEC_TWSI3 282 +#define CLK_SEC_RTC 283 +#define CLK_SEC_TIMERS0 284 +#define CLK_SEC_GPIO 285 + +#define CLK_VCTCXO_24 286 +#define CLK_VCTCXO_3 287 +#define CLK_VCTCXO_1 288 +#define CLK_PLL1 289 +#define OSC_32K 290 +#define CLK_DUMMY 291 + +#define CLK_MAX_NO 292 + +#endif /* _DT_BINDINGS_CLK_SPACEMIT_K3_H_ */