blob: 977f938eb443ff4f45a10c402d8c11b031b9a898 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <device/mmio.h>
#include <console/console.h>
#include <soc/clock.h>
#include <soc/addressmap.h>
#include <stdint.h>
// 33.33 Mhz after reset
#define FU540_BASE_FQY 33330
struct prci_ctlr {
u32 hfxosccfg; /* offset 0x00 */
u32 corepllcfg0; /* offset 0x04 */
u32 reserved08; /* offset 0x08 */
u32 ddrpllcfg0; /* offset 0x0c */
u32 ddrpllcfg1; /* offset 0x10 */
u32 reserved14; /* offset 0x14 */
u32 reserved18; /* offset 0x18 */
u32 gemgxlpllcfg0; /* offset 0x1c */
u32 gemgxlpllcfg1; /* offset 0x20 */
u32 coreclksel; /* offset 0x24 */
u32 devicesresetreg; /* offset 0x28 */
};
static struct prci_ctlr *prci = (void *)FU540_PRCI;
#define PRCI_CORECLK_MASK 1
#define PRCI_CORECLK_CORE_PLL 0
#define PRCI_CORECLK_HFCLK 1
#define PRCI_PLLCFG_LOCK (1u << 31)
#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_FSE_SHIFT 25
#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 (0x07 << PRCI_PLLCFG_RANGE_SHIFT)
#define PRCI_PLLCFG_BYPASS_MASK (0x1 << PRCI_PLLCFG_BYPASS_SHIFT)
#define PRCI_PLLCFG_FSE_MASK (0x1 << PRCI_PLLCFG_FSE_SHIFT)
#define PRCI_DDRPLLCFG1_MASK (1u << 31)
#define PRCI_GEMGXLPPLCFG1_MASK (1u << 31)
#define PRCI_CORECLKSEL_CORECLKSEL 1
#define PRCI_DEVICESRESET_DDR_CTRL_RST_N(x) (((x) & 0x1) << 0)
#define PRCI_DEVICESRESET_DDR_AXI_RST_N(x) (((x) & 0x1) << 1)
#define PRCI_DEVICESRESET_DDR_AHB_RST_N(x) (((x) & 0x1) << 2)
#define PRCI_DEVICESRESET_DDR_PHY_RST_N(x) (((x) & 0x1) << 3)
#define PRCI_DEVICESRESET_GEMGXL_RST_N(x) (((x) & 0x1) << 5)
/* Clock initialization should only be done in romstage. */
#if ENV_ROMSTAGE
struct pll_settings {
unsigned int divr:6;
unsigned int divf:9;
unsigned int divq:3;
unsigned int range:3;
unsigned int bypass:1;
unsigned int fse: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_FSE_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->fse << PRCI_PLLCFG_FSE_SHIFT));
write32(reg, c);
// Wait for PLL lock
while (!(read32(reg) & PRCI_PLLCFG_LOCK))
; /* TODO: implement a timeout */
}
/*
* Set coreclk according to the SiFive FU540-C000 Manual
* https://www.sifive.com/documentation/chips/freedom-u540-c000-manual/
*
* 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:
* For example, to setup COREPLL for 1 GHz operation, program divr = 0 (x1),
* divf = 59 (4000 MHz VCO), divq = 2 (/4 Output divider)
*/
static const struct pll_settings corepll_settings = {
.divr = 0,
.divf = 59,
.divq = 2,
.range = 4,
.bypass = 0,
.fse = 1,
};
/*
* Section 7.4.3: DDR and Ethernet Subsystem Clocking and Reset
*
* Unfortunately the documentation example doesn't match the HiFive
* Unleashed board settings.
* Configuration values taken from SiFive FSBL:
* https://github.com/sifive/freedom-u540-c000-bootloader/blob/master/fsbl/main.c
*
* DDRPLL is set up for 933 MHz output frequency.
* divr = 0, divf = 55 (3730 MHz VCO), divq = 2
*
* GEMGXLPLL is set up for 125 MHz output frequency.
* divr = 0, divf = 59 (4000 MHz VCO), divq = 5
*/
static const struct pll_settings ddrpll_settings = {
.divr = 0,
.divf = 55,
.divq = 2,
.range = 4,
.bypass = 0,
.fse = 1,
};
static const struct pll_settings gemgxlpll_settings = {
.divr = 0,
.divf = 59,
.divq = 5,
.range = 4,
.bypass = 0,
.fse = 1,
};
static void init_coreclk(void)
{
// switch coreclk to input reference frequency before modifying PLL
clrsetbits32(&prci->coreclksel, PRCI_CORECLK_MASK,
PRCI_CORECLK_HFCLK);
configure_pll(&prci->corepllcfg0, &corepll_settings);
// switch coreclk to use corepll
clrsetbits32(&prci->coreclksel, PRCI_CORECLK_MASK,
PRCI_CORECLK_CORE_PLL);
}
static void init_pll_ddr(void)
{
// disable ddr clock output before reconfiguring the PLL
u32 cfg1 = read32(&prci->ddrpllcfg1);
clrbits32(&cfg1, PRCI_DDRPLLCFG1_MASK);
write32(&prci->ddrpllcfg1, cfg1);
configure_pll(&prci->ddrpllcfg0, &ddrpll_settings);
// enable ddr clock output
setbits32(&cfg1, PRCI_DDRPLLCFG1_MASK);
write32(&prci->ddrpllcfg1, cfg1);
}
static void init_gemgxlclk(void)
{
u32 cfg1 = read32(&prci->gemgxlpllcfg1);
clrbits32(&cfg1, PRCI_GEMGXLPPLCFG1_MASK);
write32(&prci->gemgxlpllcfg1, cfg1);
configure_pll(&prci->gemgxlpllcfg0, &gemgxlpll_settings);
setbits32(&cfg1, PRCI_GEMGXLPPLCFG1_MASK);
write32(&prci->gemgxlpllcfg1, cfg1);
}
#define FU540_UART_DEVICES 2
#define FU540_UART_REG_DIV 0x18
#define FU540_UART_DIV_VAL 4
#define FU540_SPI_DIV 0x00
#define FU540_SPI_DIV_VAL 4
static void update_peripheral_clock_dividers(void)
{
write32((uint32_t *)(FU540_QSPI0 + FU540_SPI_DIV), FU540_SPI_DIV_VAL);
write32((uint32_t *)(FU540_QSPI1 + FU540_SPI_DIV), FU540_SPI_DIV_VAL);
write32((uint32_t *)(FU540_QSPI2 + FU540_SPI_DIV), FU540_SPI_DIV_VAL);
for (size_t i = 0; i < FU540_UART_DEVICES; i++)
write32((uint32_t *)(FU540_UART(i) + FU540_UART_REG_DIV), FU540_UART_DIV_VAL);
}
void clock_init(void)
{
/*
* Update the peripheral clock dividers of UART, SPI and I2C to safe
* values as we can't put them in reset before changing frequency.
*/
update_peripheral_clock_dividers();
init_coreclk();
// put DDR and ethernet in reset
write32(&prci->devicesresetreg, 0);
init_pll_ddr();
// The following code and its comments is mostly derived from the SiFive
// u540 bootloader.
// https://github.com/sifive/freedom-u540-c000-bootloader
// get DDR out of reset
write32(&prci->devicesresetreg, PRCI_DEVICESRESET_DDR_CTRL_RST_N(1));
// HACK to get the '1 full controller clock cycle'.
asm volatile ("fence");
// get DDR out of reset
write32(&prci->devicesresetreg,
PRCI_DEVICESRESET_DDR_CTRL_RST_N(1) |
PRCI_DEVICESRESET_DDR_AXI_RST_N(1) |
PRCI_DEVICESRESET_DDR_AHB_RST_N(1) |
PRCI_DEVICESRESET_DDR_PHY_RST_N(1));
// HACK to get the '1 full controller clock cycle'.
asm volatile ("fence");
// These take like 16 cycles to actually propagate. We can't go sending
// stuff before they come out of reset. So wait.
// TODO: Add a register to read the current reset states, or DDR Control
// device?
for (int i = 0; i < 256; i++)
asm volatile ("nop");
init_gemgxlclk();
write32(&prci->devicesresetreg,
PRCI_DEVICESRESET_DDR_CTRL_RST_N(1) |
PRCI_DEVICESRESET_DDR_AXI_RST_N(1) |
PRCI_DEVICESRESET_DDR_AHB_RST_N(1) |
PRCI_DEVICESRESET_DDR_PHY_RST_N(1) |
PRCI_DEVICESRESET_GEMGXL_RST_N(1));
asm volatile ("fence");
}
#endif /* ENV_ROMSTAGE */
/* Get the core clock's frequency, in KHz */
int clock_get_coreclk_khz(void)
{
if (read32(&prci->coreclksel) & PRCI_CORECLK_MASK)
return FU540_BASE_FQY;
u32 cfg = read32(&prci->corepllcfg0);
u32 divr = (cfg & PRCI_PLLCFG_DIVR_MASK)
>> PRCI_PLLCFG_DIVR_SHIFT;
u32 divf = (cfg & PRCI_PLLCFG_DIVF_MASK)
>> PRCI_PLLCFG_DIVF_SHIFT;
u32 divq = (cfg & PRCI_PLLCFG_DIVQ_MASK)
>> PRCI_PLLCFG_DIVQ_SHIFT;
printk(BIOS_SPEW, "clk: r=%d f=%d q=%d\n", divr, divf, divq);
return FU540_BASE_FQY
* 2 * (divf + 1)
/ (divr + 1)
/ (1ul << divq);
}
/* Get the TileLink clock's frequency, in KHz */
int clock_get_tlclk_khz(void)
{
/*
* The TileLink bus and most peripherals use tlclk, which is coreclk/2,
* as input.
*/
return clock_get_coreclk_khz() / 2;
}