blob: 91e79925be58643d46f70703b8a2f60662864875 [file] [log] [blame]
/* 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);
}