summaryrefslogtreecommitdiff
path: root/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/drivers/sifive_fe310-g000_pll.c
blob: c91328565b13e20af9fd5b2266dacbe1748ecc67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
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 */