| /* 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 <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); |
| } |