diff options
Diffstat (limited to 'FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c')
-rw-r--r-- | FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c b/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c new file mode 100644 index 000000000..c91328565 --- /dev/null +++ b/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c @@ -0,0 +1,360 @@ +/* Copyright 2018 SiFive, Inc */ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include <metal/machine/platform.h> + +#ifdef METAL_SIFIVE_FE310_G000_PLL + +#include <stdio.h> +#include <limits.h> + +#include <metal/machine.h> +#include <metal/drivers/sifive_fe310-g000_pll.h> +#include <stdlib.h> + +#define PLL_R 0x00000007UL +#define PLL_F 0x000003F0UL +#define PLL_Q 0x00000C00UL +#define PLL_SEL 0x00010000UL +#define PLL_REFSEL 0x00020000UL +#define PLL_BYPASS 0x00040000UL +#define PLL_LOCK 0x80000000UL + +#define DIV_DIV 0x0000003FUL +#define DIV_1 0x00000100UL + +#define PLL_R_SHIFT(r) ((r << 0) & PLL_R) +#define PLL_F_SHIFT(f) ((f << 4) & PLL_F) +#define PLL_Q_SHIFT(q) ((q << 10) & PLL_Q) +#define PLL_DIV_SHIFT(d) ((d << 0) & DIV_DIV) + +struct pll_config_t { + unsigned long multiplier; + unsigned long divisor; + unsigned long min_input_rate; + unsigned long max_input_rate; + unsigned long r; + unsigned long f; + unsigned long q; + long d; /* < 0 if disabled */ +}; + +static const struct pll_config_t pll_configs[] = { + /* + * multiplier + * ^ divisor + * | ^ min_input_rate + * | | ^ max_input_rate + * | | | ^ r + * | | | | ^ f + * | | | | | ^ q + * | | | | | | ^ d + * | | | | | | | ^ + * | | | | | | | | */ + { 1, 32, 12000000, 24000000, 1, 31, 3, 63}, + { 1, 32, 24000000, 48000000, 3, 31, 2, 63}, + { 1, 16, 6000000, 12000000, 0, 31, 3, 63}, + { 1, 16, 12000000, 24000000, 1, 31, 2, 63}, + { 1, 16, 24000000, 48000000, 3, 31, 2, 31}, + { 1, 8, 6000000, 12000000, 0, 31, 3, 31}, + { 1, 8, 12000000, 24000000, 1, 31, 2, 31}, + { 1, 8, 24000000, 48000000, 3, 31, 2, 15}, + { 1, 4, 6000000, 12000000, 0, 31, 3, 15}, + { 1, 4, 12000000, 24000000, 1, 31, 2, 15}, + { 1, 4, 24000000, 48000000, 3, 31, 2, 7}, + { 1, 2, 6000000, 12000000, 0, 31, 2, 15}, + { 1, 2, 12000000, 24000000, 1, 31, 1, 15}, + { 1, 2, 24000000, 48000000, 3, 31, 1, 7}, + { 2, 1, 6000000, 12000000, 0, 31, 1, 7}, + { 2, 1, 12000000, 24000000, 1, 31, 1, 3}, + { 2, 1, 24000000, 48000000, 3, 31, 3, -1}, + { 4, 1, 6000000, 12000000, 0, 31, 3, 0}, + { 4, 1, 12000000, 24000000, 1, 31, 3, -1}, + { 4, 1, 24000000, 48000000, 3, 31, 2, -1}, + { 6, 1, 6000000, 10666666, 0, 35, 1, 2}, + { 6, 1, 10666666, 12000000, 0, 23, 3, -1}, + { 6, 1, 12000000, 16000000, 1, 47, 3, -1}, + { 6, 1, 16000000, 18000000, 1, 23, 2, -1}, + { 6, 1, 18000000, 21333333, 2, 35, 2, -1}, + { 8, 1, 6000000, 12000000, 0, 31, 3, -1}, + { 8, 1, 12000000, 24000000, 1, 31, 2, -1}, + { 8, 1, 24000000, 48000000, 3, 31, 1, -1}, + {10, 1, 6000000, 9600000, 0, 39, 3, -1}, + {10, 1, 9600000, 12000000, 0, 19, 2, -1}, + {10, 1, 12000000, 19200000, 1, 39, 2, -1}, + {10, 1, 19200000, 24000000, 1, 19, 1, -1}, + {10, 1, 24000000, 38400000, 3, 39, 1, -1}, + {12, 1, 6000000, 8000000, 0, 47, 3, -1}, + {12, 1, 8000000, 12000000, 0, 23, 2, -1}, + {12, 1, 12000000, 16000000, 1, 47, 2, -1}, + {12, 1, 16000000, 24000000, 1, 23, 1, -1}, + {12, 1, 24000000, 30000000, 3, 47, 1, -1}, + {12, 1, 30000000, 32000000, 3, 47, 1, -1}, + {14, 1, 6000000, 6857142, 0, 55, 3, -1}, + {14, 1, 6857143, 12000000, 0, 27, 2, -1}, + {14, 1, 12000000, 13714285, 1, 55, 2, -1}, + {14, 1, 13714286, 24000000, 1, 27, 1, -1}, + {14, 1, 24000000, 27428571, 3, 55, 1, -1}, + {16, 1, 6000000, 12000000, 0, 31, 2, -1}, + {16, 1, 12000000, 24000000, 1, 31, 1, -1}, + {18, 1, 6000000, 10666666, 0, 35, 2, -1}, + {18, 1, 10666667, 12000000, 0, 17, 1, -1}, + {18, 1, 12000000, 21333333, 1, 35, 1, -1}, + {20, 1, 6000000, 9600000, 0, 39, 2, -1}, + {20, 1, 9600000, 12000000, 0, 19, 1, -1}, + {20, 1, 12000000, 19200000, 1, 39, 1, -1}, + {22, 1, 6000000, 8727272, 0, 43, 2, -1}, + {22, 1, 8727273, 12000000, 0, 21, 1, -1}, + {22, 1, 12000000, 17454545, 1, 43, 1, -1}, + {24, 1, 6000000, 8000000, 0, 47, 2, -1}, + {24, 1, 8000000, 12000000, 0, 23, 1, -1}, + {24, 1, 12000000, 16000000, 1, 47, 1, -1}, + {26, 1, 6000000, 7384615, 0, 51, 2, -1}, + {26, 1, 7384616, 12000000, 0, 25, 1, -1}, + {26, 1, 12000000, 14768230, 1, 51, 1, -1}, + {28, 1, 6000000, 6857142, 0, 55, 2, -1}, + {28, 1, 6857143, 12000000, 0, 27, 1, -1}, + {28, 1, 12000000, 13714285, 1, 55, 1, -1}, + {30, 1, 6000000, 6400000, 0, 59, 2, -1}, + {30, 1, 6400000, 12000000, 0, 29, 1, -1}, + {30, 1, 12000000, 12800000, 1, 59, 1, -1}, + {32, 1, 6000000, 12000000, 0, 31, 1, -1} +}; + +#define PLL_CONFIG_NOT_VALID -1 + +void __metal_driver_sifive_fe310_g000_pll_init(struct __metal_driver_sifive_fe310_g000_pll *pll); + +/* Given the rate of the PLL input frequency and a PLL configuration, what + * will the resulting PLL output frequency be? + * Arguments: + * - pll_input_rate the PLL input frequency in hertz + * - config the PLL configuration + * Returns: + * - PLL_CONFIG_NOT_VALID if the configuration is not valid for the input frequency + * - the output frequency, in hertz */ +static long get_pll_config_freq(long pll_input_rate, const struct pll_config_t *config) +{ + if(pll_input_rate < config->min_input_rate || pll_input_rate > config->max_input_rate) + return PLL_CONFIG_NOT_VALID; + + return pll_input_rate * config->multiplier / config->divisor; +} + +#ifdef __METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE + +static void metal_sifive_fe310_g000_pll_init(void) __attribute__((constructor)); +static void metal_sifive_fe310_g000_pll_init(void) { + long init_rate = __metal_driver_sifive_fe310_g000_pll_init_rate(); + /* If the PLL init_rate is zero, don't initialize the PLL */ + if(init_rate != 0) + __metal_driver_sifive_fe310_g000_pll_init(__METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE); +} + +#endif /* __METAL_DT_SIFIVE_FE310_G000__PLL_HANDLE */ + +void __metal_driver_sifive_fe310_g000_pll_init(struct __metal_driver_sifive_fe310_g000_pll *pll) { + struct metal_clock *pllref = __metal_driver_sifive_fe310_g000_pll_pllref(&(pll->clock)); + long init_rate = __metal_driver_sifive_fe310_g000_pll_init_rate(); + long config_offset = __metal_driver_sifive_fe310_g000_pll_config_offset(); + long base = __metal_driver_sifive_fe310_g000_prci_base(); + + __metal_io_u32 *pllcfg = (__metal_io_u32 *) (base + config_offset); + + /* If the PLL clock has had a _pre_rate_change_callback configured, call it */ + if(pll->clock._pre_rate_change_callback != NULL) + pll->clock._pre_rate_change_callback(pll->clock._pre_rate_change_callback_priv); + + /* If we're running off of the PLL, switch off before we start configuring it*/ + if((__METAL_ACCESS_ONCE(pllcfg) & PLL_SEL) == 0) + __METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_SEL); + + /* Make sure we're running off of the external oscillator for stability */ + if(pllref != NULL) + __METAL_ACCESS_ONCE(pllcfg) |= PLL_REFSEL; + + /* Configure the PLL to run at the requested init frequency. + * Using the vtable instead of the user API because we want to control + * when the callbacks occur. */ + pll->clock.vtable->set_rate_hz(&(pll->clock), init_rate); + + /* If the PLL clock has had a rate_change_callback configured, call it */ + if(pll->clock._post_rate_change_callback != NULL) + pll->clock._post_rate_change_callback(pll->clock._post_rate_change_callback_priv); +} + +long __metal_driver_sifive_fe310_g000_pll_get_rate_hz(const struct metal_clock *clock) +{ + struct metal_clock *pllref = __metal_driver_sifive_fe310_g000_pll_pllref(clock); + struct metal_clock *pllsel0 = __metal_driver_sifive_fe310_g000_pll_pllsel0(clock); + long config_offset = __metal_driver_sifive_fe310_g000_pll_config_offset(clock); + struct __metal_driver_sifive_fe310_g000_prci *config_base = + __metal_driver_sifive_fe310_g000_pll_config_base(clock); + long divider_offset = __metal_driver_sifive_fe310_g000_pll_divider_offset(clock); + struct __metal_driver_sifive_fe310_g000_prci *divider_base = + __metal_driver_sifive_fe310_g000_pll_divider_base(clock); + const struct __metal_driver_vtable_sifive_fe310_g000_prci *vtable = + __metal_driver_sifive_fe310_g000_prci_vtable(); + + long cfg = vtable->get_reg(config_base, config_offset); + long div = vtable->get_reg(divider_base, divider_offset); + + /* At the end of the PLL there's one big mux: it either selects the HFROSC + * (bypassing the PLL entirely) or uses the PLL. */ + if (__METAL_GET_FIELD(cfg, PLL_SEL) == 0) + return metal_clock_get_rate_hz(pllsel0); + + /* There's a clock mux before the PLL that selects between the HFROSC adn + * the HFXOSC as the PLL's input clock. */ + long ref_hz = metal_clock_get_rate_hz(__METAL_GET_FIELD(cfg, PLL_REFSEL) ? pllref : pllsel0); + + /* It's possible to bypass the PLL, which is an internal bpyass. This + * still obays the PLL's input clock mu. */ + if (__METAL_GET_FIELD(cfg, PLL_BYPASS)) + return ref_hz; + + /* Logically the PLL is a three stage div-mul-div. */ + long div_r = __METAL_GET_FIELD(cfg, PLL_R) + 1; + long mul_f = 2 * (__METAL_GET_FIELD(cfg, PLL_F) + 1); + if (__METAL_GET_FIELD(cfg, PLL_Q) == 0) + return -1; + long div_q = 1 << __METAL_GET_FIELD(cfg, PLL_Q); + + /* In addition to the dividers inherent in the PLL, there's an additional + * clock divider that lives after the PLL and lets us pick a more + * interesting range of frequencies. */ + long pllout = (((ref_hz / div_r) * mul_f) / div_q); + if (__METAL_GET_FIELD(div, DIV_1)) + return pllout; + + return pllout / (2 * (__METAL_GET_FIELD(div, DIV_DIV) + 1)); +} + +/* Find a valid configuration for the PLL which is closest to the desired + * output frequency. + * Arguments: + * - ref_hz PLL input frequency + * - rate desired PLL output frequency + * Returns: + * -1 if no valid configuration is available + * the index into pll_configs of a valid configuration */ +static int find_closest_config(long ref_hz, long rate) +{ + int closest_index = -1; + long closest_diff = LONG_MAX; + + /* We're probably trying for a fast output frequency, so start from + * the high end of the configs. */ + for(int i = (sizeof(pll_configs) / sizeof(pll_configs[0])) - 1; i >= 0; i--) + { + long config_freq = get_pll_config_freq(ref_hz, &(pll_configs[i])); + if(config_freq != PLL_CONFIG_NOT_VALID) + { + long freq_diff = abs(config_freq - rate); + if(freq_diff < closest_diff) + { + closest_index = i; + closest_diff = freq_diff; + } + } + } + + return closest_index; +} + +/* Configure the PLL and wait for it to lock */ +static void configure_pll(__metal_io_u32 *pllcfg, __metal_io_u32 *plloutdiv, const struct pll_config_t *config) +{ + __METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_R); + __METAL_ACCESS_ONCE(pllcfg) |= PLL_R_SHIFT(config->r); + + __METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_F); + __METAL_ACCESS_ONCE(pllcfg) |= PLL_F_SHIFT(config->f); + + __METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_Q); + __METAL_ACCESS_ONCE(pllcfg) |= PLL_Q_SHIFT(config->q); + + if(config->d < 0) + { + /* disable final divider */ + __METAL_ACCESS_ONCE(plloutdiv) |= DIV_1; + + __METAL_ACCESS_ONCE(plloutdiv) &= ~(DIV_DIV); + __METAL_ACCESS_ONCE(plloutdiv) |= PLL_DIV_SHIFT(1); + } + else + { + __METAL_ACCESS_ONCE(plloutdiv) &= ~(DIV_1); + + __METAL_ACCESS_ONCE(plloutdiv) &= ~(DIV_DIV); + __METAL_ACCESS_ONCE(plloutdiv) |= PLL_DIV_SHIFT(config->d); + } + + __METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_BYPASS); + + /* Wait for PLL to lock */ + while((__METAL_ACCESS_ONCE(pllcfg) & PLL_LOCK) == 0) ; +} + +long __metal_driver_sifive_fe310_g000_pll_set_rate_hz(struct metal_clock *clock, long rate) +{ + struct metal_clock *pllref = __metal_driver_sifive_fe310_g000_pll_pllref(clock); + struct metal_clock *pllsel0 = __metal_driver_sifive_fe310_g000_pll_pllsel0(clock); + long config_offset = __metal_driver_sifive_fe310_g000_pll_config_offset(clock); + long divider_offset = __metal_driver_sifive_fe310_g000_pll_divider_offset(clock); + long base = __metal_driver_sifive_fe310_g000_prci_base(); + + __metal_io_u32 *pllcfg = (__metal_io_u32 *) (base + config_offset); + __metal_io_u32 *plloutdiv = (__metal_io_u32 *) (base + divider_offset); + + /* We can't modify the PLL if coreclk is driven by it, so switch it off */ + if (__METAL_ACCESS_ONCE(pllcfg) & PLL_SEL) + __METAL_ACCESS_ONCE(pllcfg) &= ~(PLL_SEL); + + /* There's a clock mux before the PLL that selects between the HFROSC and + * the HFXOSC as the PLL's input clock. */ + long ref_hz = metal_clock_get_rate_hz(__METAL_ACCESS_ONCE(pllcfg) & PLL_REFSEL ? pllref : pllsel0); + + /* if the desired rate is within 75%-125% of the input clock, bypass the PLL */ + if((ref_hz * 3 / 4) <= rate && (ref_hz * 5 / 4) >= rate) + { + __METAL_ACCESS_ONCE(pllcfg) |= PLL_BYPASS; + } + else + { + int config_index = find_closest_config(ref_hz, rate); + if(config_index != -1) + { + configure_pll(pllcfg, plloutdiv, &(pll_configs[config_index])); + } + else + { + /* unable to find a valid configuration */ + __METAL_ACCESS_ONCE(pllcfg) |= PLL_BYPASS; + } + } + + /* Enable the PLL */ + __METAL_ACCESS_ONCE(pllcfg) |= PLL_SEL; + + return __metal_driver_sifive_fe310_g000_pll_get_rate_hz(clock); +} + +#ifdef __METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE +static void use_hfxosc(void) __attribute__((constructor)); +static void use_hfxosc(void) +{ + long init_rate = __metal_driver_sifive_fe310_g000_pll_init_rate(); + metal_clock_set_rate_hz( + &__METAL_DT_SIFIVE_FE310_G000_PLL_HANDLE->clock, init_rate + ); +} +#endif + +__METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_fe310_g000_pll) = { + .init = __metal_driver_sifive_fe310_g000_pll_init, + .clock.get_rate_hz = __metal_driver_sifive_fe310_g000_pll_get_rate_hz, + .clock.set_rate_hz = __metal_driver_sifive_fe310_g000_pll_set_rate_hz, +}; + +#endif /* METAL_SIFIVE_FE310_G000_PLL */ |