diff --git a/include/sbi/sbi_clk.h b/include/sbi/sbi_clk.h new file mode 100644 index 00000000..2e12f20b --- /dev/null +++ b/include/sbi/sbi_clk.h @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#ifndef __SBI_CLK_H__ +#define __SBI_CLK_H__ + +#include + +/* clock device */ +struct sbi_clk_device { + char name[32]; + int (*clk_set_rate)(const char *name, uint64_t rate); + uint64_t (*clk_get_rate)(const char *name); + int (*clk_enable)(const char *name); + int (*clk_disable)(const char *name); +}; + +int sbi_clk_set_rate(const char *name, uint64_t rate); +uint64_t sbi_clk_get_rate(const char *name); +int sbi_clk_enable(const char *name); +int sbi_clk_disable(const char *name); + +const struct sbi_clk_device *sbi_clk_get_device(const void *fdt); +void sbi_clk_set_device(const struct sbi_clk_device *dev); + +#endif diff --git a/include/sbi_utils/clk/fdt_clk.h b/include/sbi_utils/clk/fdt_clk.h new file mode 100644 index 00000000..d0505045 --- /dev/null +++ b/include/sbi_utils/clk/fdt_clk.h @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#ifndef __FDT_CLK_H__ +#define __FDT_CLK_H__ + +#include +#include + +#ifdef CONFIG_FDT_CLK + +void fdt_clk_init(const void *fdt); + +#else + +static inline void fdt_clk_init(const void *fdt) { } + +#endif + +#endif diff --git a/include/sbi_utils/clk/sg2044_clk.h b/include/sbi_utils/clk/sg2044_clk.h new file mode 100644 index 00000000..adb2be0a --- /dev/null +++ b/include/sbi_utils/clk/sg2044_clk.h @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#ifndef _SG2044_CLK_H_ +#define _SG2044_CLK_H_ + +#include +#include + +#define KHZ 1000UL +#define MHZ (KHZ * KHZ) + +#define SG2044_CLK_USE_INIT_VAL BIT(0) /* use default value */ +#define SG2044_CLK_USE_REG_VAL BIT(1) /* use reg divider value */ +#define SG2044_CLK_RO BIT(2) /* use reg divider value */ + +#define CLK_PLL BIT(0) +#define CLK_MUX BIT(1) + +#define PLL_CTRL_OFFSET 0xC4 +#define PLL_STAT_LOCK_OFFSET 0x10 +#define PLL_SELECT_OFFSET 0x2020 +#define CLK_MODE 0x4 +#define CLK_MODE_MASK 0x3 + +#define FBDIV_SHIFT 0 +#define FBDIV_WIDTH 12 +#define REFDIV_SHIFT 12 +#define REFDIV_WIDTH 6 +#define POSTDIV1_SHIFT 18 +#define POSTDIV1_WIDTH 3 +#define POSTDIV2_SHIFT 21 +#define POSTDIV2_WIDTH 3 + +#define REFDIV_MIN 1 +#define REFDIV_MAX 63 +#define FBDIV_MIN 8 +#define FBDIV_MAX 1066 + +#define PLL_FREQ_MIN (1600 * MHZ) +#define PLL_FREQ_MAX (3200 * MHZ) + +#define div_mask(width) ((1 << (width)) - 1) +#define TOP_PLL_CTRL(fbdiv, p1, p2, refdiv) \ + (BIT(30) | (0x2 << 27) | BIT(24) | (((p2 - 1) & 0x7) << 21) | \ + (((p1 - 1) & 0x7) << 18) | ((refdiv & 0x3f) << 12) | (fbdiv & 0xfff)) + +#define MPLL0_CLK 0 +#define MPLL1_CLK 1 +#define MPLL2_CLK 2 +#define MPLL3_CLK 3 +#define MPLL4_CLK 4 +#define MPLL5_CLK 5 +#define FPLL0_CLK 6 +#define FPLL1_CLK 7 +#define DPLL0_CLK 8 +#define DPLL1_CLK 9 +#define DPLL2_CLK 10 +#define DPLL3_CLK 11 +#define DPLL4_CLK 12 +#define DPLL5_CLK 13 +#define DPLL6_CLK 14 +#define DPLL7_CLK 15 + +static inline char is_power_of_2(unsigned long n) +{ + return (n != 0 && ((n & (n - 1)) == 0)); +} + +#define do_div(n, base) ({ \ + uint32_t __base = (base); \ + uint32_t __rem; \ + __rem = (uint32_t)(n) % __base; \ + (n) = (uint32_t)(n) / __base; \ + __rem; \ +}) + +struct sg2044_pll_ctrl { + unsigned int mode; + unsigned long freq; + + unsigned int fbdiv; + unsigned int postdiv1; + unsigned int postdiv2; + unsigned int refdiv; +}; + +struct sg2044_pll_clock { + unsigned int id; + char *name; + unsigned int status_offset; + unsigned int enable_offset; + uint64_t default_rate; + + struct sg2044_pll_ctrl pctrl_table[4]; +}; + +#endif diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk index 9cb28422..5405126a 100644 --- a/lib/sbi/objects.mk +++ b/lib/sbi/objects.mk @@ -99,3 +99,4 @@ libsbi-objs-y += sbi_trap_v_ldst.o libsbi-objs-y += sbi_unpriv.o libsbi-objs-y += sbi_expected_trap.o libsbi-objs-y += sbi_cppc.o +libsbi-objs-y += sbi_clk.o diff --git a/lib/sbi/sbi_clk.c b/lib/sbi/sbi_clk.c new file mode 100644 index 00000000..06bbb728 --- /dev/null +++ b/lib/sbi/sbi_clk.c @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#include +#include +#include + +static const struct sbi_clk_device *clk_dev = NULL; + +const struct sbi_clk_device *sbi_clk_get_device(const void *fdt) +{ + if (!clk_dev) + fdt_clk_init(fdt); + + return clk_dev ? clk_dev : NULL; +} + +void sbi_clk_set_device(const struct sbi_clk_device *dev) +{ + if (!dev || clk_dev) + return; + + clk_dev = dev; +} + +int sbi_clk_set_rate(const char *name, uint64_t rate) +{ + if (!clk_dev || !clk_dev->clk_set_rate) + return SBI_EFAIL; + + clk_dev->clk_set_rate(name, rate); + + return 0; +} + +uint64_t sbi_clk_get_rate(const char *name) +{ + if (!clk_dev || !clk_dev->clk_get_rate) + return SBI_EFAIL; + + return clk_dev->clk_get_rate(name); +} + +int sbi_clk_enable(const char *name) +{ + if (!clk_dev || !clk_dev->clk_enable) + return SBI_EFAIL; + + return clk_dev->clk_enable(name); +} + +int sbi_clk_disable(const char *name) +{ + if (!clk_dev || !clk_dev->clk_disable) + return SBI_EFAIL; + + return clk_dev->clk_disable(name); +} diff --git a/lib/utils/Kconfig b/lib/utils/Kconfig index 901ba564..77ade328 100644 --- a/lib/utils/Kconfig +++ b/lib/utils/Kconfig @@ -34,4 +34,6 @@ source "$(OPENSBI_SRC_DIR)/lib/utils/timer/Kconfig" source "$(OPENSBI_SRC_DIR)/lib/utils/mpxy/Kconfig" +source "$(OPENSBI_SRC_DIR)/lib/utils/clk/Kconfig" + endmenu diff --git a/lib/utils/clk/Kconfig b/lib/utils/clk/Kconfig new file mode 100644 index 00000000..3217019f --- /dev/null +++ b/lib/utils/clk/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: BSD-2-Clause + +menu "Clock Support" + +config FDT_CLK + bool "FDT based CLK drivers" + depends on FDT + default n + +if FDT_CLK + +config FDT_CLK_SG2044 + bool "SG2044 clock drivers" + default n + +endif + +endmenu diff --git a/lib/utils/clk/fdt_clk.c b/lib/utils/clk/fdt_clk.c new file mode 100644 index 00000000..7c2ebb0b --- /dev/null +++ b/lib/utils/clk/fdt_clk.c @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#include + +/* List of FDT clock drivers generated at compile time */ +extern const struct fdt_driver *const fdt_clk_drivers[]; + +void fdt_clk_init(const void *fdt) +{ + fdt_driver_init_all(fdt, fdt_clk_drivers); +} diff --git a/lib/utils/clk/fdt_clk_drivers.carray b/lib/utils/clk/fdt_clk_drivers.carray new file mode 100644 index 00000000..f0e8688e --- /dev/null +++ b/lib/utils/clk/fdt_clk_drivers.carray @@ -0,0 +1,3 @@ +HEADER: sbi_utils/clk/fdt_clk.h +TYPE: const struct fdt_driver +NAME: fdt_clk_drivers diff --git a/lib/utils/clk/objects.mk b/lib/utils/clk/objects.mk new file mode 100644 index 00000000..88fa899c --- /dev/null +++ b/lib/utils/clk/objects.mk @@ -0,0 +1,14 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2025 Sophgo Inc +# +# Authors: +# Haijiao Liu +# + +libsbiutils-objs-$(CONFIG_FDT_CLK) += clk/fdt_clk.o +libsbiutils-objs-$(CONFIG_FDT_CLK) += clk/fdt_clk_drivers.carray.o + +carray-fdt_clk_drivers-$(CONFIG_FDT_CLK_SG2044) += fdt_clk_sg2044 +libsbiutils-objs-$(CONFIG_FDT_CLK_SG2044) += clk/sg2044_clk.o diff --git a/lib/utils/clk/sg2044_clk.c b/lib/utils/clk/sg2044_clk.c new file mode 100644 index 00000000..696027e1 --- /dev/null +++ b/lib/utils/clk/sg2044_clk.c @@ -0,0 +1,553 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define POSTDIV_RESULT_INDEX 2 + +static u64 top_base_addr; + +static int sg2044_pll_mux[][2] = { + {MPLL0_CLK, 0}, {MPLL1_CLK, 1}, {MPLL2_CLK, 2}, {MPLL3_CLK, 3}, + {MPLL4_CLK, 4}, {MPLL5_CLK, 5}, {FPLL1_CLK, 6}, {DPLL0_CLK, 7}, + {DPLL1_CLK, 8}, {DPLL2_CLK, 9}, {DPLL3_CLK, 10}, {DPLL4_CLK, 11}, + {DPLL5_CLK, 12}, {DPLL6_CLK, 13}, {DPLL7_CLK, 14} +}; + +static int postdiv1_2[][3] = { + {2, 4, 8}, {3, 3, 9}, {2, 5, 10}, {2, 6, 12}, + {2, 7, 14}, {3, 5, 15}, {4, 4, 16}, {3, 6, 18}, + {4, 5, 20}, {3, 7, 21}, {4, 6, 24}, {5, 5, 25}, + {4, 7, 28}, {5, 6, 30}, {5, 7, 35}, {6, 6, 36}, + {6, 7, 42}, {7, 7, 49} +}; + +static inline unsigned long abs_diff(unsigned long a, unsigned long b) +{ + return (a > b) ? (a - b) : (b - a); +} + +static struct sg2044_pll_clock sg2044_root_pll_clks[] = { + { + .id = MPLL0_CLK, + .name = "mpll0-clock", + .default_rate = 2000 * MHZ, + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = MPLL1_CLK, + .name = "mpll1-clock", + .default_rate = 2000 * MHZ, + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = MPLL2_CLK, + .name = "mpll2-clock", + .default_rate = 1000 * MHZ, + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = MPLL3_CLK, + .name = "mpll3-clock", + .default_rate = 2000 * MHZ, + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = MPLL4_CLK, + .name = "mpll4-clock", + .default_rate = 1050 * MHZ, + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = MPLL5_CLK, + .name = "mpll5-clock", + .default_rate = 900 * MHZ, + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = FPLL0_CLK, + .name = "fpll0-clock", + }, { + .id = FPLL1_CLK, + .name = "fpll1-clock", + }, { + .id = DPLL0_CLK, + .name = "dpll0-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL1_CLK, + .name = "dpll1-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL2_CLK, + .name = "dpll2-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL3_CLK, + .name = "dpll3-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL4_CLK, + .name = "dpll4-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL5_CLK, + .name = "dpll5-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL6_CLK, + .name = "dpll6-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + }, { + .id = DPLL7_CLK, + .name = "dpll7-clock", + .status_offset = 0x98, + .enable_offset = 0x9c, + } +}; + +static inline void top_misc_read(uintptr_t offset, uint32_t *value) +{ + *value = readl((const volatile void *)top_base_addr + offset); +} + +static inline void top_misc_write(uintptr_t offset, uint32_t value) +{ + writel(value, (volatile void *)(top_base_addr + offset)); +} + +static void sg2044_pll_write_h(int id, int value) +{ + top_misc_write(PLL_CTRL_OFFSET + (id << 3), value); +} + +static void sg2044_pll_read_h(int id, uint32_t *pvalue) +{ + top_misc_read(PLL_CTRL_OFFSET + (id << 3), pvalue); +} + +static void sg2044_pll_write_l(int id, int value) +{ + top_misc_write(PLL_CTRL_OFFSET + (id << 3) - 4, value); +} + +static void sg2044_pll_read_l(int id, uint32_t *pvalue) +{ + top_misc_read(PLL_CTRL_OFFSET + (id << 3) - 4, pvalue); +} + +static int __pll_get_postdiv_1_2(uint64_t rate, uint64_t prate, + uint32_t fbdiv, uint32_t refdiv, + uint32_t *postdiv1, uint32_t *postdiv2) +{ + int index = 0; + int ret = 0; + uint64_t tmp0; + + /* + * calculate (parent_rate/refdiv) + * and result save to tmp0 + */ + tmp0 = prate; + do_div(tmp0, refdiv); + + /* + * calcuate ((parent_rate/REFDIV) x FBDIV) + * and result save to prate + */ + tmp0 *= fbdiv; + + /* + * calcuate (((parent_rate/REFDIV) x FBDIV)/input_rate) + * and result save to tmp0 + * here tmp0 is (POSTDIV1*POSTDIV2) + */ + do_div(tmp0, rate); + + /* calculate div1 and div2 value */ + if (tmp0 <= 7) { + /* (div1 * div2) <= 7, no need to use array search */ + *postdiv1 = tmp0; + *postdiv2 = 1; + } else { + /* (div1 * div2) > 7, use array search */ + for (index = 0; index < array_size(postdiv1_2); index++) { + if (tmp0 > postdiv1_2[index][POSTDIV_RESULT_INDEX]) { + continue; + } else { + /* found it */ + break; + } + } + if (index < array_size(postdiv1_2)) { + *postdiv1 = postdiv1_2[index][1]; + *postdiv2 = postdiv1_2[index][0]; + } else { + sbi_printf("%s out of postdiv array range!\n", __func__); + ret = -1; + } + } + + return ret; +} + +/* + * The function uses the following mapping for frequency range selection: + * - 2'd2 (bit[17:16] = 0b10): for frequencies from 1.6 GHz to 2.4 GHz. + * - 2'd3 (bit[17:16] = 0b11): for frequencies from 2.4 GHz to 3.2 GHz. + */ +static void __set_pll_vcosel(struct sg2044_pll_clock *sg2044_pll, uint64_t foutvco) +{ + int vcosel; + uint32_t value; + + if (foutvco < (2400 * MHZ)) + vcosel = 0x2; + else + vcosel = 0x3; + + sg2044_pll_read_l(sg2044_pll->id, &value); + value &= ~(0x3 << 16); + value |= (vcosel << 16); + sg2044_pll_write_l(sg2044_pll->id, value); +} + +static inline int sg2044_pll_id2shift(uint32_t id) +{ + for (int i = 0; i < 15; i++) { + if (sg2044_pll_mux[i][0] == id) + return sg2044_pll_mux[i][1]; + } + + return -1; +} + +static inline int sg2044_pll_switch_mux(struct sg2044_pll_clock *pll, char en) +{ + uint32_t value; + uint32_t id = pll->id; + int shift; + + shift = sg2044_pll_id2shift(id); + if (shift == -1) { + sbi_printf("%s Unable to find a suitable shift!\n", __func__); + return -1; + } + + top_misc_read(PLL_SELECT_OFFSET, &value); + if (en) + top_misc_write(PLL_SELECT_OFFSET, value & (~(1 << shift))); + else + top_misc_write(PLL_SELECT_OFFSET, value | (1 << shift)); + + return 0; +} + +static inline int sg2044_pll_enable(struct sg2044_pll_clock *pll, char en) +{ + uint32_t value; + uint64_t wait = 0; + uint32_t id = pll->id; + + if (en) { + /* wait pll lock */ + top_misc_read(pll->status_offset, &value); + while (!((value >> (PLL_STAT_LOCK_OFFSET + id)) & 0x1)) { + top_misc_read(pll->status_offset, &value); + wait++; + sbi_timer_udelay(10); + if (wait > 10000) + sbi_printf("%s not locked\n", pll->name); + } + + /* wait pll updating */ + wait = 0; + top_misc_read(pll->status_offset, &value); + while (((value >> id) & 0x1)) { + top_misc_read(pll->status_offset, &value); + wait++; + sbi_timer_udelay(10); + if (wait > 10000) + sbi_printf("%s still updating\n", pll->name); + } + + /* enable pll */ + top_misc_read(pll->enable_offset, &value); + top_misc_write(pll->enable_offset, value | (1 << id)); + } else { + /* disable pll */ + top_misc_read(pll->enable_offset, &value); + top_misc_write(pll->enable_offset, value & (~(1 << id))); + } + + return 0; +} + +/** + * The PLL output frequency calculation is based on the formula: + * FOUTPOSTDIV = FREF * FBDIV / REFDIV / (POSTDIV1 + 1) * (POSTDIV2 + 1) + * where: + * - FREF: Reference Clock Input (12MHz to 1600MHz). + * - FOUTPOSTDIV: Output Clock (25MHz to 3200MHz). + * - REFDIV: Reference divide value (1 to 63). + * - FBDIV: Feedback divide value (8 to 1066). + * - POSTDIV1: Post Divide 1 setting (0 to 7). + * - POSTDIV2: Post Divide 2 setting (0 to 7). + * + * Additionally, other check points inside PLL are listed here: + * 1.FOUTVCO = FREF * FBDIV / REFDIV (1600MHz to 3200MHz) + * - VCOSEL = 2'd2, FOUTVCO 1.6G~2.4G; + * - VCOSEL = 2'd3, FOUTVCO 2.4G~3.2G + * 2.POSTDIV1 >= POSTDIV2 + */ +static int __get_pll_ctl_setting(struct sg2044_pll_ctrl *best, + uint64_t req_rate, uint64_t parent_rate) +{ + int ret; + uint32_t fbdiv, refdiv, fref, postdiv1, postdiv2; + uint64_t tmp = 0, foutvco; + + fref = parent_rate; + + for (refdiv = REFDIV_MIN; refdiv < REFDIV_MAX + 1; refdiv++) { + for (fbdiv = FBDIV_MIN; fbdiv < FBDIV_MAX + 1; fbdiv++) { + foutvco = fref * fbdiv / refdiv; + /* check fpostdiv pfd */ + if (foutvco < PLL_FREQ_MIN || foutvco > PLL_FREQ_MAX + || (fref / refdiv) < 10) + continue; + + ret = __pll_get_postdiv_1_2(req_rate, fref, fbdiv, + refdiv, &postdiv1, &postdiv2); + if (ret) + continue; + + tmp = foutvco / (postdiv1 * postdiv2); + if (abs_diff(tmp, req_rate) < abs_diff(best->freq, req_rate)) { + best->freq = tmp; + best->refdiv = refdiv; + best->fbdiv = fbdiv; + best->postdiv1 = postdiv1; + best->postdiv2 = postdiv2; + if (tmp == req_rate) + return 0; + } + continue; + } + } + + return -1; +} + +/** + * The function performs the following steps: + * 1. Switch the PLL source to fpll before modifying settings. + * 2. Disable the MPLL to allow safe modifications to its configuration. + * 3. Calculate the new PLL settings based on the desired rate and the parent rate. + * 5. Set the frequency range based on the foutvco . + * 6. Write the new settings to the PLL control register. + * 7. Re-enable the PLL. + * 8. Switch back the PLL source to mpll after modifications. + */ +static int sg2044_clk_pll_set_rate(struct sg2044_pll_clock *sg2044_pll, + uint64_t rate, uint64_t parent_rate) +{ + int ret = 0; + uint32_t value; + uint64_t foutvco; + struct sg2044_pll_ctrl pctrl_table; + + sbi_memset(&pctrl_table, 0, sizeof(struct sg2044_pll_ctrl)); + + /* switch to fpll before modify mpll */ + ret = sg2044_pll_switch_mux(sg2044_pll, 1); + if (ret == -1) { + sbi_printf("switch to fpll fail!\n"); + goto out; + } + + if (sg2044_pll_enable(sg2044_pll, 0)) { + sbi_printf("Can't disable pll(%s), status error\n", sg2044_pll->name); + ret = -1; + goto out; + } + + sg2044_pll_read_h(sg2044_pll->id, &value); + __get_pll_ctl_setting(&pctrl_table, rate, parent_rate); + if (!pctrl_table.freq) { + sbi_printf("%s: Can't find a proper pll setting\n", sg2044_pll->name); + ret = -1; + goto out; + } + + value = TOP_PLL_CTRL(pctrl_table.fbdiv, pctrl_table.postdiv1, + pctrl_table.postdiv2, pctrl_table.refdiv); + + foutvco = parent_rate * pctrl_table.fbdiv / pctrl_table.refdiv; + __set_pll_vcosel(sg2044_pll, foutvco); + + /* write the value to top register */ + sg2044_pll_write_h(sg2044_pll->id, value); + sg2044_pll_enable(sg2044_pll, 1); + + /* switch back to mpll after modify mpll */ + ret = sg2044_pll_switch_mux(sg2044_pll, 0); + if (ret == -1) { + sbi_printf("switch back to mpll fail!\n"); + goto out; + } +out: + return ret; +} + +static uint64_t sg2044_clk_pll_get_rate(struct sg2044_pll_clock *sg2044_pll, + uint64_t parent_rate) +{ + uint32_t value; + uint64_t fref; + int refdiv, fbdiv; + int postdiv1, postdiv2; + + sg2044_pll_read_h(sg2044_pll->id, &value); + fref = parent_rate; + fbdiv = (value >> FBDIV_SHIFT) & div_mask(FBDIV_WIDTH); + refdiv = (value >> REFDIV_SHIFT) & div_mask(REFDIV_WIDTH); + postdiv1 = (value >> POSTDIV1_SHIFT) & div_mask(POSTDIV1_WIDTH); + postdiv2 = (value >> POSTDIV2_SHIFT) & div_mask(POSTDIV2_WIDTH); + + return fref * fbdiv / refdiv / (postdiv1 + 1) * (postdiv2 + 1); +} + +static struct sg2044_pll_clock *sg2044_get_clk_by_name(const char *name) +{ + int index; + + for (index = 0; index < array_size(sg2044_root_pll_clks); index++) { + if (!sbi_strcmp(name, sg2044_root_pll_clks[index].name)) + return &sg2044_root_pll_clks[index]; + } + + return 0; +} + +static int sg2044_clk_set_rate(const char *name, uint64_t rate) +{ + int ret; + struct sg2044_pll_clock *clk; + + clk = sg2044_get_clk_by_name(name); + if (!clk) + return SBI_EINVAL; + + ret = sg2044_clk_pll_set_rate(clk, rate, 25 * MHZ); + if (ret != 0) + sbi_printf("%s set rate fail, ret = %d\n", name, ret); + + return ret; +} + +static uint64_t sg2044_clk_get_rate(const char *name) +{ + struct sg2044_pll_clock *clk; + + clk = sg2044_get_clk_by_name(name); + if (!clk) + return SBI_EINVAL; + + return sg2044_clk_pll_get_rate(clk, 25 * MHZ); +} + +static int sg2044_clk_enable(const char *name) +{ + struct sg2044_pll_clock *clk; + + clk = sg2044_get_clk_by_name(name); + if (!clk) + return SBI_EINVAL; + + sg2044_pll_enable(clk, 1); + + return 0; +} + +static int sg2044_clk_disable(const char *name) +{ + struct sg2044_pll_clock *clk; + + clk = sg2044_get_clk_by_name(name); + if (!clk) + return SBI_EINVAL; + + sg2044_pll_enable(clk, 0); + + return 0; +} + +static struct sbi_clk_device sbi_sg2044_clk = { + .name = "sg2044-clk", + .clk_set_rate = sg2044_clk_set_rate, + .clk_get_rate = sg2044_clk_get_rate, + .clk_enable = sg2044_clk_enable, + .clk_disable = sg2044_clk_disable, +}; + +static int sg2044_clk_init(const void *fdt, int nodeoff, + const struct fdt_match *match) +{ + int top_offset, len; + const fdt32_t *phandle; + const fdt32_t *reg; + + if (top_base_addr) + return 0; + + phandle = fdt_getprop(fdt, nodeoff, "subctrl-syscon", &len); + if (!phandle) + return SBI_EINVAL; + + top_offset = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*phandle)); + if (top_offset < 0) + return top_offset; + + reg = fdt_getprop(fdt, top_offset, "reg", &len); + if (!reg || len <= 0) + return SBI_EINVAL; + + top_base_addr = fdt32_to_cpu(reg[0]); + top_base_addr = (top_base_addr << 32) | fdt32_to_cpu(reg[1]); + + sbi_clk_set_device(&sbi_sg2044_clk); + + return 0; +} + +static const struct fdt_match sg2044_clk_match[] = { + { .compatible = "sg2044, pll-clock" }, + {}, +}; + +struct fdt_driver fdt_clk_sg2044 = { + .match_table = sg2044_clk_match, + .init = sg2044_clk_init, +}; diff --git a/lib/utils/cppc/Kconfig b/lib/utils/cppc/Kconfig index 494f6894..2b0ea07f 100644 --- a/lib/utils/cppc/Kconfig +++ b/lib/utils/cppc/Kconfig @@ -14,6 +14,11 @@ config FDT_CPPC_RPMI depends on FDT_MAILBOX && RPMI_MAILBOX default n +config FDT_CPPC_SG2044 + bool "FDT SG2044 CPPC driver" + select FDT_CLK_SG2044 + default n + endif endmenu diff --git a/lib/utils/cppc/fdt_cppc_sg2044.c b/lib/utils/cppc/fdt_cppc_sg2044.c new file mode 100644 index 00000000..def9eab2 --- /dev/null +++ b/lib/utils/cppc/fdt_cppc_sg2044.c @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Sophgo Inc. + * + * Authors: + * Haijiao Liu + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CPPC_REGISTER_WIDTH 64 +#define CPPC_REGISTER_NOT_IMPLEMENTED 0 + +static char clock_names[64]; +static uint64_t clk_granularity; + +static int sg2044_cppc_read(unsigned long reg, u64 *val) +{ + int rc = SBI_SUCCESS; + + switch (reg) { + case SBI_CPPC_DESIRED_PERF: + *val = sbi_clk_get_rate(clock_names) / clk_granularity; + break; + default: + rc = SBI_ERR_NOT_SUPPORTED; + } + + return rc; +} + +static int sg2044_cppc_write(unsigned long reg, u64 val) +{ + int rc = SBI_SUCCESS; + + switch (reg) { + case SBI_CPPC_DESIRED_PERF: + rc = sbi_clk_set_rate(clock_names, val * clk_granularity); + break; + default: + rc = SBI_ERR_NOT_SUPPORTED; + } + + return rc; +} + +static int sg2044_cppc_probe(unsigned long reg) +{ + int rc; + + switch (reg) { + case SBI_CPPC_DESIRED_PERF: + rc = CPPC_REGISTER_WIDTH; + break; + default: + rc = CPPC_REGISTER_NOT_IMPLEMENTED; + } + + return rc; +} + +static struct sbi_cppc_device sbi_sg2044_cppc = { + .name = "sg2044-cppc", + .cppc_read = sg2044_cppc_read, + .cppc_write = sg2044_cppc_write, + .cppc_probe = sg2044_cppc_probe, +}; + +static int sg2044_cppc_cold_init(const void *fdt, int nodeoff, + const struct fdt_match *match) +{ + const struct sbi_clk_device *clk; + const fdt32_t *val; + const char *name; + int len; + + clk = sbi_clk_get_device(fdt); + if (!clk) + return SBI_ENODEV; + + val = fdt_getprop(fdt, nodeoff, "step", &len); + if (!val) + return SBI_EINVAL; + + clk_granularity = fdt32_to_cpu(val[0]); + clk_granularity = (clk_granularity << 32) | fdt32_to_cpu(val[1]); + + name = fdt_getprop(fdt, nodeoff, "clock-names", &len); + if (!name) + return SBI_EINVAL; + + sbi_strncpy(clock_names, name, MIN(sizeof(clock_names), len)); + + sbi_cppc_set_device(&sbi_sg2044_cppc); + + return 0; +} + +static const struct fdt_match sg2044_cppc_match[] = { + { .compatible = "sophgo,sg2044-cppc" }, + {}, +}; + +struct fdt_driver fdt_cppc_sg2044 = { + .match_table = sg2044_cppc_match, + .init = sg2044_cppc_cold_init, +}; diff --git a/lib/utils/cppc/objects.mk b/lib/utils/cppc/objects.mk index 07dc7d89..6a593170 100644 --- a/lib/utils/cppc/objects.mk +++ b/lib/utils/cppc/objects.mk @@ -12,3 +12,6 @@ libsbiutils-objs-$(CONFIG_FDT_CPPC) += cppc/fdt_cppc_drivers.carray.o carray-fdt_cppc_drivers-$(CONFIG_FDT_CPPC_RPMI) += fdt_cppc_rpmi libsbiutils-objs-$(CONFIG_FDT_CPPC_RPMI) += cppc/fdt_cppc_rpmi.o + +carray-fdt_cppc_drivers-$(CONFIG_FDT_CPPC_SG2044) += fdt_cppc_sg2044 +libsbiutils-objs-$(CONFIG_FDT_CPPC_SG2044) += cppc/fdt_cppc_sg2044.o diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig index e23b38b2..d7fa3b14 100644 --- a/platform/generic/configs/defconfig +++ b/platform/generic/configs/defconfig @@ -8,6 +8,9 @@ CONFIG_PLATFORM_STARFIVE_JH7110=y CONFIG_PLATFORM_THEAD=y CONFIG_FDT_CPPC=y CONFIG_FDT_CPPC_RPMI=y +CONFIG_FDT_CPPC_SG2044=y +CONFIG_FDT_CLK=y +CONFIG_FDT_CLK_SG2044=y CONFIG_FDT_GPIO=y CONFIG_FDT_GPIO_DESIGNWARE=y CONFIG_FDT_GPIO_SIFIVE=y