#include "pll.h"

/********MCOM-02 REGMAP DEFINE*******************************************/
/***************************System Registers*****************************/
#define CMCTR_BASE              0x38094000
#define GATE_SYS_CTR            (*(volatile unsigned int*)(CMCTR_BASE + 0x04c))
    #define CLK_I2C2_EN                                         (1 << 18)
    #define CLK_I2C1_EN                                         (1 << 17)
    #define CLK_I2C0_EN                                         (1 << 16)
#define GATE_DSP_CTR            (*(volatile unsigned int*)(CMCTR_BASE + 0x068))
    #define DSPENC_EN                                           (1 << 3)
    #define DSPEXT_EN                                           (1 << 2)
    #define DSP1_EN                                             (1 << 1)
    #define DSP0_EN                                             (1 << 0)
#define SEL_APLL		(*(volatile unsigned int*)(CMCTR_BASE + 0x100))
#define SEL_CPLL		(*(volatile unsigned int*)(CMCTR_BASE + 0x104))
#define SEL_DPLL		(*(volatile unsigned int*)(CMCTR_BASE + 0x108))
#define SEL_SPLL		(*(volatile unsigned int*)(CMCTR_BASE + 0x10c))
#define SEL_VPLL		(*(volatile unsigned int*)(CMCTR_BASE + 0x110))
#define PLL_LOCK_BIT            (1 << 31)

/***************************GPIO******************************************/
#define GPIO0_BASE 0x38034000
#define GPIO0(a) (*(volatile unsigned int*)(GPIO0_BASE + (a)))
#define SWPORTA_DR					0x00
#define SWPORTA_DDR					0x04
#define SWPORTA_CTL					0x08
#define SWPORTB_DR					0x0c
#define SWPORTB_DDR					0x10
#define SWPORTB_CTL					0x14
#define SWPORTC_DR					0x18
#define SWPORTC_DDR					0x1c
#define SWPORTC_CTL					0x20
#define SWPORTD_DR					0x24
#define SWPORTD_DDR					0x28
#define SWPORTD_CTL					0x2c

#define GPIOA29_I2C0_SDA				(1 << 29)
#define GPIOA29_I2C0_SCL				(1 << 30)
#define GPIOD22_I2C1_SDA				(1 << 22)
#define GPIOD23_I2C1_SCL				(1 << 23)
#define GPIOD24_I2C2_SDA				(1 << 24)
#define GPIOD25_I2C2_SCL				(1 << 25)

/***FAN53555**************************************************************/
/***5 A, 2.4MHz, Digitally Programmable TinyBuck Regulator****************/

#define FAN53555_I2C_NUM                                0
#define FAN53555_I2C_SLAVE_ADDR                         0x60
#define FAN53555_VSEL0_REG                              0
#define FAN53555_OPTION4_VOLT_TO_SEL(mv)                (((mv) - 603)/12.826f + 1)

/***CPU FREQ TABLE********************************************************/
#define CPU_FREQ_VOLTAGE_SIZE                           3
const unsigned int CPU_FREQ_VOLTAGE[CPU_FREQ_VOLTAGE_SIZE][2] __attribute__ ((aligned(8))) = {
//   MHz,   mV
    {816, 1040},
    {912, 1103},
    {1008, 1205}
};

/***DSP FREQ TABLE********************************************************/
#define DSP_FREQ_VOLTAGE_SIZE                           3
const unsigned int DSP_FREQ_VOLTAGE[CPU_FREQ_VOLTAGE_SIZE][2] __attribute__ ((aligned(8))) = {
//   MHz,   mV
    {696, 1040},
    {768, 1103},
    {888, 1205}
};

/*I2C Settings*/
#define BASE_I2C0                                       0x3802c000
#define BASE_I2C1                                       0x3802d000
#define BASE_I2C2                                       0x3802e000

#define I2C_CMD_READ                                    (1 << 8)
#define I2C_CMD_WRITE                                   (0 << 8)
#define I2C_STATUS_TFE                                  (1 << 2)
#define I2C_STATUS_RFNE                                 (1 << 3)

const unsigned int I2C_GPIO[3][3] __attribute__ ((aligned(8))) = {
    { SWPORTA_CTL, GPIOA29_I2C0_SDA, GPIOA29_I2C0_SCL},
    { SWPORTD_CTL, GPIOD22_I2C1_SDA, GPIOD23_I2C1_SCL},
    { SWPORTD_CTL, GPIOD24_I2C2_SDA, GPIOD25_I2C2_SCL},
};

typedef struct {
    volatile unsigned int IC_CON;
    volatile unsigned int IC_TAR;
    volatile unsigned int IC_SAR;
    volatile unsigned int IC_HS_MADDR;
    volatile unsigned int IC_DATA_CMD;
    volatile unsigned int IC_SS_SCL_HCNT;
    volatile unsigned int IC_SS_SCL_LCNT;
    volatile unsigned int IC_FS_SCL_HCNT;
    volatile unsigned int IC_FS_SCL_LCNT;
    volatile unsigned int IC_HS_SCL_HCNT;
    volatile unsigned int IC_HS_SCL_LCNT;
    volatile unsigned int IC_INTR_STAT;
    volatile unsigned int IC_INTR_MASK;
    volatile unsigned int IC_RAW_INTR_STAT;
    volatile unsigned int IC_RX_TL;
    volatile unsigned int IC_TX_TL;
    volatile unsigned int IC_CLR_INTR;
    volatile unsigned int IC_CLR_RX_UNDER;
    volatile unsigned int IC_CLR_RX_OVER;
    volatile unsigned int IC_CLR_TX_OVER;
    volatile unsigned int IC_CLR_RD_REQ;
    volatile unsigned int IC_CLR_TX_ABRT;
    volatile unsigned int IC_CLR_RX_DONE;
    volatile unsigned int IC_CLR_ACTIVITY;
    volatile unsigned int IC_CLR_STOP_DET;
    volatile unsigned int IC_CLR_START_DET;
    volatile unsigned int IC_CLR_GEN_CALL;
    volatile unsigned int IC_ENABLE;
    volatile unsigned int IC_STATUS;
    volatile unsigned int IC_TXFLR;
    volatile unsigned int IC_RXFLR;
    volatile unsigned int IC_SDA_HOLD;
    volatile unsigned int IC_TX_ABRT_SOURCE;
    volatile unsigned int IC_SLV_DATA_NACK_ONLY;
    volatile unsigned int IC_DMA_CR;
    volatile unsigned int IC_DMA_TDLR;
    volatile unsigned int IC_DMA_RDLR;
    volatile unsigned int IC_SDA_SETUP;
    volatile unsigned int IC_ACK_GENERAL_CALL;
    volatile unsigned int IC_ENABLE_STATUS;
    volatile unsigned int IC_FS_SPKLEN;
    volatile unsigned int IC_HS_SPKLEN;
    volatile unsigned char RESERVED0[76];
    volatile unsigned int IC_COMP_PARAM_1;
    volatile unsigned int IC_COMP_VERSION;
    volatile unsigned int IC_COMP_TYPE;
} i2c_reg_t;

typedef union
{
    struct
    {
        volatile unsigned int NSEL0 : 6;
        volatile unsigned int MODE0 : 1;
        volatile unsigned int BUCK_EN0 : 1;
        volatile unsigned int reserved : 24;
    };
    volatile unsigned int regval;
}vsel0_reg_t;

enum dw_i2c_speed_mode {
    I2C_SPEED_STANDART  = 0x1,          // standard speed (100 kbps)
    I2C_SPEED_FAST      = 0x2,          // fast speed (400 kbps)
    I2C_SPEED_HIGH      = 0x3           // high speed (3400 kbps)
};
enum dw_i2c_address_mode {
    I2C_7BIT_ADDRESS    = 0x0,          // 7-bit address mode.  Only the 7 LSBs
                                        // of the slave and/or target address
                                        // are relevant.
    I2C_10BIT_ADDRESS   = 0x1           // 10-bit address mode.  The 10 LSBs of
                                        // the slave and/or target address are
                                        // relevant.
};
enum dw_i2c_tx_mode {
    I2C_TX_TARGET       = 0x0,          // normal transfer using target address
    I2C_TX_GEN_CALL     = 0x1,          // issue a general call
    I2C_TX_START_BYTE   = 0x3           // issue a start byte I2C command
};

void setCPUPLLFreq(unsigned int MHz);
void setDSPPLLFreq(unsigned int MHz);
int setCoreVoltage(unsigned int voltage);

i2c_reg_t* getI2CHandler(unsigned int i2cNum)
{
    i2cNum &= 0x3;
    return (i2c_reg_t*) (BASE_I2C0 + 0x1000 * i2cNum);
}
void initI2CTx(unsigned int i2cNum, unsigned int slave) {
    volatile unsigned int temp;
    i2c_reg_t* i2c;
    i2cNum &= 0x3;

    //Enable clock
    GATE_SYS_CTR |= CLK_I2C0_EN << i2cNum;
    //Enable GPIO
    GPIO0(I2C_GPIO[i2cNum][0]) |= I2C_GPIO[i2cNum][1] | I2C_GPIO[i2cNum][2];

    i2c = getI2CHandler(i2cNum);

    i2c->IC_ENABLE = 0;                     //Disable i2c;
    i2c->IC_INTR_MASK = 0;                  //Mask interrupts
    temp = i2c->IC_CLR_INTR;                //Clear interrupts
    (void) temp;
    i2c->IC_CON = 1                         //Enable master mode
            | (I2C_SPEED_STANDART << 1)     //Setup speed
            | (I2C_7BIT_ADDRESS << 3)       //Set address bit-mode slave
            | (1 << 5)                      //Restart enable
            | (1 << 6);                     //Disable slave mode
    i2c->IC_TAR = (slave & 0x3FF)           //Set slave address
            | (I2C_TX_TARGET << 10)         //Set TX mode
            | (I2C_7BIT_ADDRESS << 12);     //Set address bit-mode master
    //i2c->IC_INTR_MASK = 0xFFF;              //Unmask interrupts
    i2c->IC_ENABLE = 1;                     //Enable i2c;
}
unsigned int readI2CReg(unsigned int i2cNum, unsigned int regNum)
{
    i2cNum &= 0x3;
    regNum &= 0xFF;
    i2c_reg_t* i2c = getI2CHandler(i2cNum);

    //Send register number
    i2c->IC_DATA_CMD = regNum | I2C_CMD_WRITE;
    //Wait sending
    while(!(i2c->IC_STATUS & I2C_STATUS_TFE));

    //Read answer
    i2c->IC_DATA_CMD = I2C_CMD_READ;
    //Wait data
    while(!(i2c->IC_STATUS & I2C_STATUS_RFNE));

    return (i2c->IC_DATA_CMD & 0xFF);
}
void writeI2CReg(unsigned int i2cNum, unsigned int regNum, unsigned int regVal)
{
    i2cNum &= 0x3;
    regNum &= 0xFF;
    regVal &= 0xFF;
    i2c_reg_t* i2c = getI2CHandler(i2cNum);

    //Send register number
    i2c->IC_DATA_CMD = regNum | I2C_CMD_WRITE;
    //Send register value
    i2c->IC_DATA_CMD = regVal | I2C_CMD_WRITE;
    //Wait sending
    while(!(i2c->IC_STATUS & I2C_STATUS_TFE));
}
void i2c_delay()
{
    volatile int i;
    for(i = 0; i < 10000; i++)
    {
        asm volatile("nop");
    }
}
int setCPUFreq(unsigned int MHz)
{
    unsigned int dsp_voltage, last_freq, voltage;

    if(MHz < MIN_CPU_FREQ_MHZ || MHz > MAX_CPU_FREQ_MHZ
            || (voltage = getCPUVoltage(MHz)) == 0)
    {
        return 1;
    }
    //Check DSP freq and voltage
    dsp_voltage = getDSPVoltage(getCurrentDSPFreq());

    if(dsp_voltage > voltage)
        voltage = dsp_voltage;  //Set max voltage

    //Save current CPU freq
    last_freq = getCurrentCPUFreq();
    //Set default CPU freq
    setCPUPLLFreq(DEFAULT_XTI_CLOCK);

    //Set Core Voltage
    if(setCoreVoltage(voltage))
    {
        //Restore CPU freq
        setCPUPLLFreq(last_freq);
        return 2;
    };
    //Set CPU freq
    setCPUPLLFreq(MHz);

    return 0;
}
int setDSPFreq(unsigned int MHz)
{
    unsigned int cpu_voltage, last_freq, voltage;

    if(MHz < MIN_DSP_FREQ_MHZ || MHz > MAX_DSP_FREQ_MHZ
            || (voltage = getDSPVoltage(MHz)) == 0)
    {
        return 1;
    }
    //Enable DSP_CLK
    GATE_DSP_CTR |=
              DSP0_EN
            | DSP1_EN
            | DSPEXT_EN
            | DSPENC_EN
            ;
    //Check CPU freq and voltage
    cpu_voltage = getCPUVoltage(getCurrentCPUFreq());

    if(cpu_voltage > voltage)
        voltage = cpu_voltage;  //Set max voltage

    //Save current DSP freq
    last_freq = getCurrentDSPFreq();
    //Set default DSP freq
    setDSPPLLFreq(DEFAULT_XTI_CLOCK);

    //Set Core Voltage
    if(setCoreVoltage(voltage))
    {
        //Restore DSP freq
        setDSPPLLFreq(last_freq);
        return 2;
    };
    //Set DSP freq
    setDSPPLLFreq(MHz);

    return 0;
}
int setCoreVoltage(unsigned int voltage)
{
    vsel0_reg_t sel0_reg;

    sel0_reg.regval = 0;
    sel0_reg.NSEL0 = FAN53555_OPTION4_VOLT_TO_SEL(voltage); //Set voltage
    sel0_reg.MODE0 = 0;                                     //Allow Auto-PFM mode
    sel0_reg.BUCK_EN0 = 1;                                  //Software buck enable

    initI2CTx(FAN53555_I2C_NUM, FAN53555_I2C_SLAVE_ADDR);
    writeI2CReg(FAN53555_I2C_NUM, FAN53555_VSEL0_REG, sel0_reg.regval);
    i2c_delay();
    if(readI2CReg(FAN53555_I2C_NUM, FAN53555_VSEL0_REG) != sel0_reg.regval)
    {
        return 1;
    }
    return 0;
}
unsigned int getCPUVoltage(unsigned int MHz)
{
    int i;
    for(i = 0; i < CPU_FREQ_VOLTAGE_SIZE; i++)
    {
        if(MHz <= CPU_FREQ_VOLTAGE[i][0])
        {
            return CPU_FREQ_VOLTAGE[i][1];
        }
    }
    return 0;
}
unsigned int getDSPVoltage(unsigned int MHz)
{
    int i;
    for(i = 0; i < DSP_FREQ_VOLTAGE_SIZE; i++)
    {
        if(MHz <= DSP_FREQ_VOLTAGE[i][0])
        {
            return DSP_FREQ_VOLTAGE[i][1];
        }
    }
    return 0;
}
unsigned int getCurrentCPUFreq()
{
    int sel = (SEL_APLL & 0xFF);
    if(sel > 0x3D) sel = 0x3D;
    return (sel + 1) * DEFAULT_XTI_CLOCK;
}
unsigned int getCurrentDSPFreq()
{
    int sel = (SEL_DPLL & 0xFF);
    if(sel > 0x3D) sel = 0x3D;
    return (sel + 1) * DEFAULT_XTI_CLOCK;
}
void setCPUPLLFreq(unsigned int MHz)
{
    int sel = (MHz / DEFAULT_XTI_CLOCK) - 1;
    if(sel < 0) sel = 0;
    SEL_APLL = sel;
    while(!(SEL_APLL & PLL_LOCK_BIT));
}
void setDSPPLLFreq(unsigned int MHz)
{
    int sel = (MHz / DEFAULT_XTI_CLOCK) - 1;
    if(sel < 0) sel = 0;
    SEL_DPLL = sel;
    while(!(SEL_DPLL & PLL_LOCK_BIT));
}