diff options
author | Maximilian Brune <maximilian.brune@9elements.com> | 2024-01-14 21:59:27 +0600 |
---|---|---|
committer | ron minnich <rminnich@gmail.com> | 2024-03-03 21:20:03 +0000 |
commit | 2ccb8e7891f429c3b72773860521a2b943a049be (patch) | |
tree | 2acf887ac1555980342f31f0fda35dbdefda915e /src/soc/sifive/fu740/clock.c | |
parent | ec7b48076009cfe82e5ee91050f5fc66c4850193 (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.c | 448 |
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); +} |