summaryrefslogtreecommitdiff
path: root/src/soc/sifive/fu740/clock.c
diff options
context:
space:
mode:
authorMaximilian Brune <maximilian.brune@9elements.com>2024-01-14 21:59:27 +0600
committerron minnich <rminnich@gmail.com>2024-03-03 21:20:03 +0000
commit2ccb8e7891f429c3b72773860521a2b943a049be (patch)
tree2acf887ac1555980342f31f0fda35dbdefda915e /src/soc/sifive/fu740/clock.c
parentec7b48076009cfe82e5ee91050f5fc66c4850193 (diff)
soc/sifive/fu740: Add FU740 SOC
Signed-off-by: Maximilian Brune <maximilian.brune@9elements.com> Change-Id: I4a8fe02ef0adcb939aa65377a35874715c5ee58a Reviewed-on: https://review.coreboot.org/c/coreboot/+/76689 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: ron minnich <rminnich@gmail.com>
Diffstat (limited to 'src/soc/sifive/fu740/clock.c')
-rw-r--r--src/soc/sifive/fu740/clock.c448
1 files changed, 448 insertions, 0 deletions
diff --git a/src/soc/sifive/fu740/clock.c b/src/soc/sifive/fu740/clock.c
new file mode 100644
index 0000000000..419a0e2936
--- /dev/null
+++ b/src/soc/sifive/fu740/clock.c
@@ -0,0 +1,448 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+// This file is used for setting up clocks and get devices out of reset
+// For more Information see FU740-C000 Manual Chapter 7 Clocking and Reset
+
+#include <console/console.h>
+#include <delay.h>
+#include <device/mmio.h>
+#include <soc/addressmap.h>
+#include <soc/clock.h>
+#include <soc/gpio.h>
+#include <gpio.h>
+#include <stdint.h>
+
+// Clock frequencies for the cores, ddr and the peripherals are all derived from the hfclk (high frequency clock) and it is always 26 MHz
+#define FU740_HFCLK_FREQ (26 * MHz)
+
+struct prci_ctlr {
+ u32 hfxosccfg; // offset 0x00
+ u32 core_pllcfg; // offset 0x04
+ u32 core_plloutdiv; // offset 0x08
+ u32 ddr_pllcfg; // offset 0x0c
+ u32 ddr_plloutdiv; // offset 0x10
+ u32 pcieaux_plloutdiv; // offset 0x14 (undocumented)
+ u32 reserved18; // offset 0x18
+ u32 gemgxl_pllcfg; // offset 0x1c
+ u32 gemgxl_plloutdiv; // offset 0x20
+ u32 core_clk_sel_reg; // offset 0x24
+ u32 devices_reset_n; // offset 0x28
+ u32 clk_mux_status; // offset 0x2C
+ u32 cltx_pllcfg; // offset 0x30 chiplink (undocumented)
+ u32 cltx_plloutdiv; // offset 0x34 chiplink (undocumented)
+ u32 dvfs_core_pllcfg; // offset 0x38
+ u32 dvfs_core_plloutdiv; // offset 0x3C
+ u32 corepllsel; // offset 0x40 (undocumented, but probably same as last gen)
+ u8 reserved44[12]; // offset 0x44
+ u32 hfpclk_pllcfg; // offset 0x50
+ u32 hfpclk_plloutdiv; // offset 0x54
+ u32 hfpclkpllsel; // offset 0x58 (undocumented, but probably same as last gen)
+ u32 hfpclk_div_reg; // offset 0x5C
+ u8 reserved60[128]; // offset 0x60
+ u32 prci_plls; // offset 0xE0
+ u8 reservedE4[12]; // offset 0xE4
+ u32 procmoncfg_core_clock; // offset 0xF0 (undocumented)
+} __packed;
+
+static struct prci_ctlr *prci = (void *)FU740_PRCI;
+
+// =================================
+// clock selections
+// =================================
+
+#define PRCI_COREPLLSEL_MASK 1
+#define PRCI_COREPLLSEL_COREPLL 0
+#define PRCI_COREPLLSEL_DVFSCOREPLL 1
+
+#define PRCI_CORECLKSEL_MASK 1
+#define PRCI_CORECLKSEL_CORECLKPLL 0
+#define PRCI_CORECLKSEL_HFCLK 1
+
+#define PRCI_HFPCLKSEL_MASK 1
+#define PRCI_HFPCLKSEL_PLL 0
+#define PRCI_HFPCLKSEL_HFCLK 1
+
+// ===================================
+// pllcfg register format is used by all PLLs
+// ===================================
+
+#define PRCI_PLLCFG_DIVR_SHIFT 0
+#define PRCI_PLLCFG_DIVF_SHIFT 6
+#define PRCI_PLLCFG_DIVQ_SHIFT 15
+#define PRCI_PLLCFG_RANGE_SHIFT 18
+#define PRCI_PLLCFG_BYPASS_SHIFT 24
+#define PRCI_PLLCFG_FSEBYPASS_SHIFT 25
+#define PRCI_PLLCFG_LOCK_SHIFT 31
+
+#define PRCI_PLLCFG_DIVR_MASK (0x03f << PRCI_PLLCFG_DIVR_SHIFT)
+#define PRCI_PLLCFG_DIVF_MASK (0x1ff << PRCI_PLLCFG_DIVF_SHIFT)
+#define PRCI_PLLCFG_DIVQ_MASK (0x007 << PRCI_PLLCFG_DIVQ_SHIFT)
+#define PRCI_PLLCFG_RANGE_MASK (0x007 << PRCI_PLLCFG_RANGE_SHIFT)
+#define PRCI_PLLCFG_BYPASS_MASK (0x001 << PRCI_PLLCFG_BYPASS_SHIFT)
+#define PRCI_PLLCFG_FSEBYPASS_MASK (0x001 << PRCI_PLLCFG_FSEBYPASS_SHIFT)
+#define PRCI_PLLCFG_LOCK_MASK (0x001 << PRCI_PLLCFG_LOCK_SHIFT)
+
+// ===================================
+// plloutdiv register formats
+// ===================================
+
+// registered are used to enable/disable PLLs
+#define PRCI_DVFSCORE_PLLOUTDIV_MASK (1 << 24) // Note: u-boot and fu740 manual differ here ...
+#define PRCI_HFPCLK_PLLOUTDIV_MASK (1 << 31) // Note: according to u-boot it is (1 << 24) but if I use that it gets stuck
+#define PRCI_DDR_PLLOUTDIV_MASK (1 << 31)
+#define PRCI_GEMGXL_PLLOUTDIV_MASK (1 << 31)
+#define PRCI_CLTX_PLLOUTDIV_MASK (1 << 24) // undocumented (chiplink tx)
+#define PRCI_PCIEAUX_PLLOUTDIV_MASK (1 << 0) // undocumented
+#define PRCI_CORE_PLLOUTDIV_MASK (1 << 31) // undocumented
+
+// ===================================
+// devicereset register formats
+// ===================================
+
+// used to get devices in or out of reset
+#define PRCI_DEVICES_RESET_DDR_CTRL_RST (1 << 0) // DDR Controller
+#define PRCI_DEVICES_RESET_DDR_AXI_RST (1 << 1) // DDR Controller AXI Interface
+#define PRCI_DEVICES_RESET_DDR_AHB_RST (1 << 2) // DDR Controller AHB Interface
+#define PRCI_DEVICES_RESET_DDR_PHY_RST (1 << 3) // DDR PHY
+#define PRCI_DEVICES_RESET_PCIEAUX_RST (1 << 4)
+#define PRCI_DEVICES_RESET_GEMGXL_RST (1 << 5) // Gigabit Ethernet Subsystem
+#define PRCI_DEVICES_RESET_CLTX_RST (1 << 6) // chiplink reset (undocumented)
+
+// ===================================
+// prci_plls register format
+// ===================================
+
+// used to check if certain PLLs are present in the SOC
+#define PRCI_PLLS_CLTXPLL (1 << 0)
+#define PRCI_PLLS_GEMGXLPLL (1 << 1)
+#define PRCI_PLLS_DDRPLL (1 << 2)
+#define PRCI_PLLS_HFPCLKPLL (1 << 3)
+#define PRCI_PLLS_DVFSCOREPLL (1 << 4)
+#define PRCI_PLLS_COREPLL (1 << 5)
+
+// ===================================
+// clk_mux_status register format
+// ===================================
+
+// read only register which is used to set some clock multiplex settings
+// the value of this register depends on the state of pins connected to the FU740 SOC
+// on the hifive-unmatched board the state of the pins is set by a hardware switch
+#define PRCI_CLK_MUX_STATUS_CORECLKPLLSEL (1 << 0)
+ // 0 - HFCLK or CORECLK
+ // 1 - only HFCLK
+#define PRCI_CLK_MUX_STATUS_TLCLKSEL (1 << 1)
+ // 0 - CORECLK/2
+ // 1 - CORECLK
+#define PRCI_CLK_MUX_STATUS_RTCXSEL (1 << 2)
+ // 0 - use HFXCLK for RTC
+ // 1 - use RTCXALTCLKIN for RTC
+#define PRCI_CLK_MUX_STATUS_DDRCTRLCLKSEL (1 << 3)
+#define PRCI_CLK_MUX_STATUS_DDRPHYCLKSEL (1 << 4)
+#define PRCI_CLK_MUX_STATUS_RESERVED (1 << 5)
+#define PRCI_CLK_MUX_STATUS_GEMGXLCLKSEL (1 << 6)
+#define PRCI_CLK_MUX_STATUS_MAINMEMCLKSEL (1 << 7)
+
+// ===================================
+// hfxosccfg register format
+// ===================================
+
+#define PRCI_HFXOSCCFG_HFXOSEN (1 << 30) // Crystal oscillator enable
+ // Note: I guess (it is not documented)
+ // 0 - XTAL PADS
+ // 1 - OSC PADS
+#define PRCI_HFXOSCCFG_HFXOSCRDY (1 << 31) // Crystal oscillator ready
+
+struct pll_settings {
+ unsigned int divr:6; // divider before PLL loop (reference), equal to divr + 1
+ unsigned int divf:9; // VCO feedback divider value, equal to 2 * (divf + 1)
+ unsigned int divq:3; // divider after PLL loop, equal to 2^divq
+ // PLL filter range (TODO documentation is not really clear on how to set it)
+ unsigned int range:3;
+ unsigned int bypass:1; // probably used to bypass the PLL
+ // internal or external input path (internal = 1, external = 0)
+ //WARN this is only a guess since it is undocumented
+ unsigned int fsebypass:1;
+};
+
+static void configure_pll(u32 *reg, const struct pll_settings *s)
+{
+ // Write the settings to the register
+ u32 c = read32(reg);
+ clrsetbits32(&c, PRCI_PLLCFG_DIVR_MASK
+ | PRCI_PLLCFG_DIVF_MASK
+ | PRCI_PLLCFG_DIVQ_MASK
+ | PRCI_PLLCFG_RANGE_MASK
+ | PRCI_PLLCFG_BYPASS_MASK
+ | PRCI_PLLCFG_FSEBYPASS_MASK,
+ (s->divr << PRCI_PLLCFG_DIVR_SHIFT)
+ | (s->divf << PRCI_PLLCFG_DIVF_SHIFT)
+ | (s->divq << PRCI_PLLCFG_DIVQ_SHIFT)
+ | (s->range << PRCI_PLLCFG_RANGE_SHIFT)
+ | (s->bypass << PRCI_PLLCFG_BYPASS_SHIFT)
+ | (s->fsebypass << PRCI_PLLCFG_FSEBYPASS_SHIFT));
+ write32(reg, c);
+
+ // Wait for PLL lock
+ while (!(read32(reg) & PRCI_PLLCFG_LOCK_MASK))
+ ;
+}
+
+/*
+ * Section 7.1 recommends a frequency of 1.0 GHz (up to 1.5 GHz is possible)
+ * Section 7.4.2 provides the necessary values
+ *
+ * COREPLL is set up for ~1 GHz output frequency.
+ * divr = 0 (x1), divf = 76 (x154) => (4004 MHz VCO), divq = 2 (/4 Output divider)
+ */
+static const struct pll_settings corepll_settings = {
+ .divr = 0,
+ .divf = 76,
+ .divq = 2,
+ .range = 4,
+ .bypass = 0,
+ .fsebypass = 1, // external feedback mode is not supported
+};
+
+/*
+ * Section 7.4.3: DDR and Ethernet Subsystem Clocking and Reset
+ *
+ * DDRPLL is set up for 933 MHz output frequency.
+ * divr = 0 (x1), divf = 71 (x144) => (3744 MHz VCO), divq = 2 (/4 output divider)
+ */
+static const struct pll_settings ddrpll_settings = {
+ .divr = 0,
+ .divf = 71,
+ .divq = 2,
+ .range = 4,
+ .bypass = 0,
+ .fsebypass = 1, // external feedback mode is not supported
+};
+
+/*
+ * GEMGXLPLL is set up for 125 MHz output frequency.
+ * divr = 0 (x1), divf = 76 (x154) => (4004 MHz VCO), divq = 5 (/32 output divider)
+ */
+static const struct pll_settings gemgxlpll_settings = {
+ .divr = 0,
+ .divf = 76,
+ .divq = 5,
+ .range = 4,
+ .bypass = 0,
+ .fsebypass = 1, // external feedback mode is not supported
+};
+
+/*
+ * HFPCLKPLL is set up for 520 MHz output frequency.
+ * TODO a lower value should also suffice as well as safe some power
+ * divr = 1 (/2), divf = 39 (x80) => (2080 MHz VCO), divq = 2 (/4 output divider)
+ */
+static const struct pll_settings hfpclkpll_settings = {
+ .divr = 1,
+ //.divf = 122,
+ .divf = 39,
+ .divq = 2,
+ .range = 4,
+ .bypass = 0,
+ .fsebypass = 1, // external feedback mode is not supported
+};
+
+/*
+ * CLTXCLKPLL is set up for 520 MHz output frequency.
+ * divr = 1 (/2), divf = 122 (x154) => (4004 MHz VCO), divq = 2 (/4 output divider)
+ */
+static const struct pll_settings cltxpll_settings = {
+ .divr = 1,
+ .divf = 39,
+ .divq = 2,
+ .range = 4,
+ .bypass = 0,
+ .fsebypass = 1, // external feedback mode is not supported
+};
+
+static void init_coreclk(void)
+{
+ // we can't modify the coreclk PLL while we are running on it, so let coreclk devise
+ // its clock from hfclk before modifying PLL
+ clrsetbits32(&prci->core_clk_sel_reg, PRCI_CORECLKSEL_MASK, PRCI_CORECLKSEL_HFCLK);
+
+ // only configure pll if it is present
+ if (!(read32(&prci->prci_plls) & PRCI_PLLS_COREPLL)) {
+ return;
+ }
+
+ configure_pll(&prci->core_pllcfg, &corepll_settings);
+
+ // switch coreclk multiplexer to use corepll as clock source again
+ clrsetbits32(&prci->core_clk_sel_reg, PRCI_CORECLKSEL_MASK, PRCI_CORECLKSEL_CORECLKPLL);
+}
+
+static void init_ddrclk(void)
+{
+ // only configure pll if it is present
+ if (!(read32(&prci->prci_plls) & PRCI_PLLS_DDRPLL)) {
+ return;
+ }
+
+ // disable ddr clock output before reconfiguring the PLL
+ u32 cfg1 = read32(&prci->ddr_plloutdiv);
+ clrbits32(&cfg1, PRCI_DDR_PLLOUTDIV_MASK);
+ write32(&prci->ddr_plloutdiv, cfg1);
+
+ configure_pll(&prci->ddr_pllcfg, &ddrpll_settings);
+
+ // PLL is ready/locked so enable it (its gated)
+ setbits32(&cfg1, PRCI_DDR_PLLOUTDIV_MASK);
+ write32(&prci->ddr_plloutdiv, cfg1);
+}
+
+static void init_gemgxlclk(void)
+{
+ // only configure pll if it is present
+ if (!(read32(&prci->prci_plls) & PRCI_PLLS_GEMGXLPLL)) {
+ return;
+ }
+
+ // disable gemgxl clock output before reconfiguring the PLL
+ u32 cfg1 = read32(&prci->gemgxl_plloutdiv);
+ clrbits32(&cfg1, PRCI_GEMGXL_PLLOUTDIV_MASK);
+ write32(&prci->gemgxl_plloutdiv, cfg1);
+
+ configure_pll(&prci->gemgxl_pllcfg, &gemgxlpll_settings);
+
+ // PLL is ready/locked so enable it (its gated)
+ setbits32(&cfg1, PRCI_GEMGXL_PLLOUTDIV_MASK);
+ write32(&prci->gemgxl_plloutdiv, cfg1);
+}
+
+/*
+ * Configure High Frequency peripheral clock which is used by
+ * UART, SPI, GPIO, I2C and PWM subsystem
+ */
+static void init_hfpclk(void)
+{
+ // we can't modify the hfpclk PLL while we are running on it, so let pclk devise
+ // its clock from hfclk before modifying PLL
+ u32 hfpclksel = read32(&prci->hfpclkpllsel);
+ hfpclksel |= PRCI_HFPCLKSEL_HFCLK;
+ write32(&prci->hfpclkpllsel, hfpclksel);
+
+ configure_pll(&prci->hfpclk_pllcfg, &hfpclkpll_settings);
+
+ // PLL is ready/locked so enable it (its gated)
+ u32 hfpclk_plloutdiv = read32(&prci->hfpclk_plloutdiv);
+ hfpclk_plloutdiv |= PRCI_HFPCLK_PLLOUTDIV_MASK;
+ write32(&prci->hfpclk_plloutdiv, hfpclk_plloutdiv);
+
+ mdelay(1);
+
+ // switch to using PLL for hfpclk
+ clrbits32(&prci->hfpclkpllsel, PRCI_HFPCLKSEL_MASK);
+
+ udelay(70);
+}
+
+static void reset_deassert(u8 reset_index)
+{
+ u32 device_reset = read32(&prci->devices_reset_n);
+ device_reset |= reset_index;
+ write32(&prci->devices_reset_n, device_reset);
+}
+
+static void init_cltx(void)
+{
+ // disable hfpclkpll before configuring it
+ u32 cfg1 = read32(&prci->cltx_plloutdiv);
+ clrbits32(&cfg1, PRCI_CLTX_PLLOUTDIV_MASK);
+ write32(&prci->cltx_plloutdiv, cfg1);
+
+ configure_pll(&prci->cltx_pllcfg, &cltxpll_settings);
+
+ // PLL is ready/locked so enable it (its gated)
+ setbits32(&cfg1, PRCI_CLTX_PLLOUTDIV_MASK);
+ write32(&prci->cltx_plloutdiv, cfg1);
+
+ // get chiplink out of reset
+ reset_deassert(PRCI_DEVICES_RESET_CLTX_RST);
+
+ udelay(70);
+}
+
+void clock_init(void)
+{
+ // first configure the coreclk (used by HARTs) to get maximum speed early on
+ init_coreclk();
+
+ // put all devices in reset (e.g. DDR, ethernet, pcie) before configuring their clocks
+ write32(&prci->devices_reset_n, 0);
+
+ // initialize clock used by DDR subsystem
+ init_ddrclk();
+
+ // get DDR controller out of reset
+ reset_deassert(PRCI_DEVICES_RESET_DDR_CTRL_RST);
+
+ // wait at least one full DDR controller clock cycle
+ asm volatile ("fence");
+
+ // get DDR controller (register interface) out of reset
+ // get DDR subsystem PHY out of reset
+ reset_deassert(PRCI_DEVICES_RESET_DDR_AXI_RST |
+ PRCI_DEVICES_RESET_DDR_AHB_RST |
+ PRCI_DEVICES_RESET_DDR_PHY_RST);
+
+ // we need to wait 256 full ddrctrl clock cycles until we can interact with the DDR subsystem
+ for (int i = 0; i < 256; i++)
+ asm volatile ("nop");
+
+ if (read32(&prci->prci_plls) & PRCI_PLLS_HFPCLKPLL) {
+ // set hfclk as reference for peripheral clock since we don't have the PLL
+ //clrsetbits32(&prci->hfpclkpllsel, PRCI_HFPCLKSEL_MASK, PRCI_HFPCLKSEL_HFCLK);
+ init_hfpclk();
+ } else if (read32(&prci->prci_plls) & PRCI_PLLS_CLTXPLL) {
+ // Note: this path has never been tested since the platforms tested with
+ // always have HFPCLKPLL
+ init_cltx();
+ // get chiplink out of reset
+ reset_deassert(PRCI_DEVICES_RESET_CLTX_RST);
+ }
+
+ // GEMGXL init VSC8541 PHY reset sequence;
+ gpio_set_direction(GEMGXL_RST, GPIO_OUTPUT);
+ gpio_set(GEMGXL_RST, 1);
+
+ udelay(1);
+
+ /* Reset PHY again to enter unmanaged mode */
+ gpio_set(GEMGXL_RST, 0);
+ udelay(1);
+ gpio_set(GEMGXL_RST, 1);
+ mdelay(15);
+
+ init_gemgxlclk();
+
+ // get ethernet out of reset
+ reset_deassert(PRCI_DEVICES_RESET_GEMGXL_RST);
+}
+
+// get the peripheral clock frequency used by UART (probably also SPI, GPIO, I2C and PWM)
+int clock_get_pclk(void)
+{
+ u64 pclk = FU740_HFCLK_FREQ;
+
+ // check if hfpclkpll is present and
+ // check if hfpclkpll is selected in the multiplexer TODO
+ // check if hpfclkpll is enabled
+ if ((read32(&prci->prci_plls) & PRCI_PLLS_HFPCLKPLL) &&
+ (read32(&prci->hfpclk_plloutdiv) & PRCI_HFPCLK_PLLOUTDIV_MASK)) {
+ int hfpclk_pllcfg = read32(&prci->hfpclk_pllcfg);
+ int divr = (hfpclk_pllcfg & PRCI_PLLCFG_DIVR_MASK) >> PRCI_PLLCFG_DIVR_SHIFT;
+ int divf = (hfpclk_pllcfg & PRCI_PLLCFG_DIVF_MASK) >> PRCI_PLLCFG_DIVF_SHIFT;
+ int divq = (hfpclk_pllcfg & PRCI_PLLCFG_DIVQ_MASK) >> PRCI_PLLCFG_DIVQ_SHIFT;
+ pclk /= (divr + 1); // reference divider
+ pclk *= (2 * (divf + 1)); // feedback divider
+ pclk /= (1 << divq); // output divider
+ }
+
+ // divider value before pclk seems to be (hfpclkdiv + 2). Not mentioned in fu740 manual though.
+ return pclk / (read32(&prci->hfpclk_div_reg) + 2);
+}