summaryrefslogtreecommitdiff
path: root/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c
diff options
context:
space:
mode:
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.c360
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 */