/* 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(unsigned 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 */
    _metal_clock_call_all_callbacks(pll->clock._pre_rate_change_callback);

    /* 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 */
    _metal_clock_call_all_callbacks(pll->clock._post_rate_change_callback);
}

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 */

typedef int no_empty_translation_units;
