/****************************************************************************
 *
 * Copyright 2020 Samsung Electronics All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/

#include "mbed.h"
#include "s5js100_dcxo.h"
#include "s5js100_type.h"
#include "mbed_trace.h"
#define TRACE_GROUP "DCXO"

#ifndef S5JS100_DCXO_DBG_ON
#define S5JS100_DCXO_DBG_ON     0
#endif
#define S5JS100_DCXO_DBG        if (S5JS100_DCXO_DBG_ON) tr_info

#define DCXO_TEMPERATURE_RANGE  126 /* -40 ~ 85 */
#define DCXO_TSU_TABLE_MIN_TEMPERATURE (-40)
#define DCXO_TSU_TABLE_MAX_TEMPERATURE  85
#define DCXO_TSU_TABLE_MIN              10000
#define DCXO_TSU_TABLE_LENGTH           35
#define DCXO_TSU_TABLE_UNIT             5000

Thread *g_dcxo_tid;
static Semaphore dcxo_tbl_sem(1, 1);

static unsigned int tsu_code_to_temperature(unsigned int tsu_code);
static unsigned int tsu_temperature_to_code(unsigned int temp_celcius);

// table for KDS 9pF and SIDK board
// B constant : 3,380, T0 : 25'C
static const unsigned short g_dcxo_tsu_table[] = {
    49690,  //-40'C, 10,000
    48942,
    48194,  //-30'C, 20,000
    47446,
    46698,  //-20'C, 30,000
    45639,
    44316,  //-10'C, 40,000
    42776,
    41063,  //0'C, 50,000
    39214,
    37269,  //10'C, 60,000
    35261,
    33221,  //20'C, 70,000
    31177,
    29156,  //30'C, 80,000
    27180,
    25267,  //40'C, 90,000
    23436,
    21700,  //50'C, 100,000
    20069,
    18553,  //60'C, 110,000
    17155,
    15878,  //70'C, 120,000
    14721,
    13681,  //80'C, 130,000
    12751,
    11922,  //90'C, 140,000
    11181,
    10513,  //100'C, 150,000
    9899,
    9319,   //110'C, 160,000
    8749,
    8162,   //120'C, 170,000
    7527,
    6813,   //130'C, 180,000
};

static volatile int tsu_interrupt_flag;
static int dcxo_init_flag;
static Full_set_table_t *dcxo_tbl;

// return value : (celcius temperature)*1000 + 50,000
static unsigned int tsu_code_to_temperature(unsigned int tsu_code)
{
    unsigned int temp_code;
    int index, base, ratio;

    index = 0;

    while (index < DCXO_TSU_TABLE_LENGTH) {
        if (g_dcxo_tsu_table[index] <= tsu_code) {
            break;
        } else {
            index++;
        }
    }

    if (index <= 0) {
        index = 1;
    } else if (index >= DCXO_TSU_TABLE_LENGTH) {
        index = DCXO_TSU_TABLE_LENGTH - 1;
    }

    base = (index - 1) * DCXO_TSU_TABLE_UNIT + DCXO_TSU_TABLE_MIN;
    ratio = ((int)(g_dcxo_tsu_table[index - 1] - tsu_code) * DCXO_TSU_TABLE_UNIT)
            / (int)(g_dcxo_tsu_table[index - 1] - g_dcxo_tsu_table[index]);
    temp_code = base + ratio;
    return temp_code;
}

// temp_celcius : (celcius temperature)*1000 + 50,000
static unsigned int tsu_temperature_to_code(unsigned int temp_celcius)
{
    int index, ratio;

    index = (temp_celcius - DCXO_TSU_TABLE_MIN) / DCXO_TSU_TABLE_UNIT;

    if (temp_celcius <= DCXO_TSU_TABLE_MIN) {
        index = 0;
        ratio = (int)(DCXO_TSU_TABLE_MIN + DCXO_TSU_TABLE_UNIT * index - temp_celcius) * (int)(g_dcxo_tsu_table[index] - g_dcxo_tsu_table[index + 1]) / DCXO_TSU_TABLE_UNIT;
    } else if (index >= DCXO_TSU_TABLE_LENGTH - 1) {
        index = DCXO_TSU_TABLE_LENGTH - 1;
        ratio = (int)(DCXO_TSU_TABLE_MIN + DCXO_TSU_TABLE_UNIT * index - temp_celcius) * (int)(g_dcxo_tsu_table[index - 1] - g_dcxo_tsu_table[index]) / DCXO_TSU_TABLE_UNIT;
    } else {
        ratio = (int)(DCXO_TSU_TABLE_MIN + DCXO_TSU_TABLE_UNIT * index - temp_celcius) * (int)(g_dcxo_tsu_table[index] - g_dcxo_tsu_table[index + 1]) / DCXO_TSU_TABLE_UNIT;
    }

    //dbg("\nindex : %d , base : %d , ratio : %d\n", index, g_dcxo_tsu_table[index], ratio);

    return g_dcxo_tsu_table[index] + ratio;
}

/****************************************************************************
 * Function:  up_timerisr
 *
 * Description:
 *   The timer ISR will perform a variety of services for various portions
 *   of the systems.
 *
 ****************************************************************************/
int dcxo_tsu_isr(int irq, void *context, void *arg)
{
    NVIC_DisableIRQ((IRQn_Type)irq);

    tsu_interrupt_flag = 1;

    if (irq == S5JS100_IRQ_TSU0) {
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 1, 1);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 0x10, 0x10);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0, 0xFFFF, 0x0);
        //dbg("TSU0 interrupt\n");
    } else if (irq == S5JS100_IRQ_TSU1) {
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 2, 2);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 0x20, 0x20);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0, 0xFFFF0000, 0xFFFF0000);
        //dbg("TSU1 interrupt\n");
    }

    return 0;
}



/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Public Functions
 ****************************************************************************/
/****************************************************************************
 * Name: s5js100_dcxo_ctb_loop
 *
 * Description:
 * update DCXO value in every ms by parameter freq
 *
 * Returned Value:
 *   a NULL on failure
 *
 ****************************************************************************/
int s5js100_dcxo_tbl_dump(void)
{
    int i;
    for (i = 0; i < DCXO_TEMPERATURE_RANGE; i++) {
        S5JS100_DCXO_DBG("%d : CTB[%d]", i - 40, (dcxo_tbl + i)->ctb);
    }
    return 0;
}

void dcxo_ctb_sem_wait(void)
{
    dcxo_tbl_sem.try_acquire();
}

void dcxo_ctb_sem_release(void)
{
    dcxo_tbl_sem.release();
}

void s5js100_dcxo_ctb_loop(void)
{
    int tsu, index, ctb, ctb_prev;
    int ctb_gap;
    int loop_started = 0;

    //s5js100_dcxo_tbl_dump();

    while (1) {
        // refer dcxo table
        // read TSU
        tsu = (int) s5js100_tsu_get_temperature();
        index = (tsu - 10000) / 1000;

        dcxo_ctb_sem_wait();

        if (tsu < (50000 + DCXO_TSU_TABLE_MIN_TEMPERATURE * 1000)) {
            index = 0;
            ctb_gap = dcxo_tbl[index + 1].ctb - dcxo_tbl[index].ctb;
            ctb = dcxo_tbl[index].ctb + (ctb_gap * (tsu - (50000 + DCXO_TSU_TABLE_MIN_TEMPERATURE * 1000))) / 1000;
        } else if (tsu > (50000 + DCXO_TSU_TABLE_MAX_TEMPERATURE * 1000)) {
            index = DCXO_TEMPERATURE_RANGE - 1;
            ctb_gap = dcxo_tbl[index].ctb - dcxo_tbl[index - 1].ctb;
            ctb = dcxo_tbl[index].ctb + (ctb_gap * (tsu - (50000 + DCXO_TSU_TABLE_MAX_TEMPERATURE * 1000))) / 1000;
        } else {
            ctb_gap = dcxo_tbl[index + 1].ctb - dcxo_tbl[index].ctb;
            ctb = dcxo_tbl[index].ctb + ctb_gap * (tsu % 1000) / 1000;
        }

        if (ctb < 0) {
            ctb = 0;
        } else if (ctb > 16383) {
            ctb = 16383;
        }

        if (!loop_started) {
            S5JS100_DCXO_DBG("s5js100_dcxo_ctb_loop FIRST update temp(%d) coarse(%d -> %d)", tsu - 50000, s5js100_dcxo_get_coarse_tune_value, ctb);
            s5js100_dcxo_set_tune_value(ctb, 4096);
        } else {
            ctb_prev = s5js100_dcxo_get_coarse_tune_value();

            if (ctb > ctb_prev) {
                ctb_prev++;
            } else if (ctb < ctb_prev) {
                ctb_prev--;
            }
            s5js100_dcxo_set_tune_value(ctb_prev, 4096);
        }

        if (ctb == 0) {
            S5JS100_DCXO_DBG("[ERROR]CTB was 0x0[temp:%d]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", tsu);
        }

        loop_started = 1;
        dcxo_ctb_sem_release();
        osDelay(50);
    }
}

int s5js100_tsu_get_code(void)
{
    return (getreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_STA0) & 0xFFFF);
}

int s5js100_dcxo_force_update_ctb(void)
{
    int tsu, index, ctb, code;
    int ctb_gap;

    // read TSU
    code = s5js100_tsu_get_code();
    tsu = (int) tsu_code_to_temperature(code);

    index = (tsu - 10000) / 1000;

    if (tsu < (50000 + DCXO_TSU_TABLE_MIN_TEMPERATURE * 1000)) {
        index = 0;
        ctb_gap = dcxo_tbl[index + 1].ctb - dcxo_tbl[index].ctb;
        ctb = dcxo_tbl[index].ctb + (ctb_gap * (tsu - (50000 + DCXO_TSU_TABLE_MIN_TEMPERATURE * 1000))) / 1000;
    } else if (tsu > (50000 + DCXO_TSU_TABLE_MAX_TEMPERATURE * 1000)) {
        index = DCXO_TEMPERATURE_RANGE - 1;
        ctb_gap = dcxo_tbl[index].ctb - dcxo_tbl[index - 1].ctb;
        ctb = dcxo_tbl[index].ctb + (ctb_gap * (tsu - (50000 + DCXO_TSU_TABLE_MAX_TEMPERATURE * 1000))) / 1000;
    } else {
        ctb_gap = dcxo_tbl[index + 1].ctb - dcxo_tbl[index].ctb;
        ctb = dcxo_tbl[index].ctb + ctb_gap * (tsu % 1000) / 1000;
    }

    if (ctb < 0) {
        ctb = 0;
    } else if (ctb > 16383) {
        ctb = 16383;
    }

    s5js100_dcxo_set_tune_value(ctb, 4096);

    if (ctb == 0) {
        S5JS100_DCXO_DBG("Force CTB was 0x0[temp:%d, code:%d]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", tsu, code);
        //asm("b .");
    }

    if ((tsu - 50000) > 0) {
        //dcxollvdbg("TSU : %d.%03d 'C, CTB : %d ( %d )\n", (tsu - 50000) / 1000, (tsu) % 1000, ctb, tsu);
    } else if ((tsu - 50000) > -1000) {
        //dcxollvdbg("TSU : -%d.%03d 'C, CTB : %d ( %d )\n", (tsu - 50000) / 1000, (1000 - (tsu) % 1000) % 1000, ctb, tsu);
    } else {
        //dcxollvdbg("TSU : %d.%03d 'C, CTB : %d ( %d )\n", (tsu - 50000) / 1000, (1000 - (tsu) % 1000) % 1000, ctb, tsu);
    }

    return 0;
}




/****************************************************************************
 * Name: s5js100_dcxo_initialize
 *
 * Description:
 *   Initialize the DCXO.
 *
 * Returned Value:
 *   a NULL on failure
 *
 ****************************************************************************/
int s5js100_dcxo_initialize(void)
{
    if (dcxo_init_flag == 0) {
        // initialize DOTSU, clk:100kHz
        //putreg32(0x3E5, S5JS100_DCXO_CFG + DCXO_CFG_ADC_CFG);
        //putreg32(0x1BE7, S5JS100_DCXO_CFG + DCXO_CFG_ADC_CFG); //ATE setting value
        putreg32(0x13FE5, S5JS100_DCXO_CFG + DCXO_CFG_ADC_CFG); //Final setting value
        putreg32(0x3A, S5JS100_DCXO_CFG + DCXO_CFG_DIV_CFG);
        putreg32(0x30, S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1);
        putreg32(0xFFFF0000, S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0);

        // tsu sensing time by average unit
        // 0:10us, 1:40us, 2:160us, 3:640us, 4:2.56ms, 5:10.24ms, 6:81.92ms, 7:327.68 ms
        putreg32(0x3, S5JS100_DCXO_CFG + DCXO_CFG_AVR_CFG);
        //putreg32(0x7, S5JS100_DCXO_CFG + DCXO_CFG_AVR_CFG);

        // wait till update temperature
        while (!(getreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_STA1) & 0xFFFF0000)) {
        }

        // initialize DCXO
        // IDAC_SEL = 1
        modifyreg32(S5JS100_PMU_ALIVE + 0x520, 1 << 3, 1 << 3);

#if 0
        // IDAC_EX = 0xa0
        modifyreg32(S5JS100_PMU_ALIVE + 0x524, 0xff << 5, 0xa0 << 5);
#else
        // change tune parameter
        // IDAC_EX = 100
        modifyreg32(S5JS100_PMU_ALIVE + 0x524, 0xff << 5, 100 << 5);

        // BUF_SEL = 8
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0xf << 3, 8 << 3);
#endif

#if defined(DCXO_FINE_PMU_COARSE_DCXO)
        // Fine=FTUNE2, Coarse=DcxoFTune
        // FuneMuxSel = 1
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 1, 1);

        // FTSEL = 1
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 1 << 16, 1 << 16);

        // FTSWP = 1
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 1 << 15, 1 << 15);

#elif defined(DCXO_FINE_PMU_COARSE_PMU)
        // Fine=FTUNE2, Coarse=FTUNE
        // FuneMuxSel = 0
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 1, 0);

        // FTSEL = 1
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 1 << 16, 1 << 16);

        // FTSWP = 1
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 1 << 15, 1 << 15);
#elif defined(DCXO_FINE_DCXO_COARSE_PMU)
        // Fine=DcxoFTune, Coarse=FTUNE2
        // FuneMuxSel = 1
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 1, 1);

        // FTSEL = 1
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 1 << 16, 1 << 16);

        // FTSWP = 0
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 1 << 15, 0);
#endif
        dcxo_init_flag = 1;
    }

    return 1;
}

int s5js100_dcxo_start_ctb_loop(Full_set_table_t tbl[])
{
    dcxo_tbl = tbl;

    g_dcxo_tid = new rtos::Thread(osPriorityNormal, 2048, NULL, "ctbloop");
    g_dcxo_tid->start(s5js100_dcxo_ctb_loop);

    if (!g_dcxo_tid) {
        return -ESRCH;
    }

    return 0;
}

/****************************************************************************
 * Name: s5js100_tsu_get_temperature
 *
 * Description:
 *   Read temperature of TSX.
 *
 * Returned Value:
 *   celcius temperature
 *
 ****************************************************************************/
int s5js100_tsu_calculate_temperature(unsigned int code)
{
    return tsu_code_to_temperature(code);
}

int s5js100_tsu_convert_code(unsigned int temperature)
{
    return tsu_temperature_to_code(temperature);
}

int s5js100_tsu_get_temperature(void)
{
    return tsu_code_to_temperature(s5js100_tsu_get_code());
}

int s5js100_tsu_set_interrupt(int high_interrupt, int temp)
{
    unsigned int tsu_code, irq_no;

    if (temp < DCXO_TSU_TABLE_MIN_TEMPERATURE) {
        temp = DCXO_TSU_TABLE_MIN_TEMPERATURE;
    } else if (temp > DCXO_TSU_TABLE_MIN_TEMPERATURE + DCXO_TSU_TABLE_LENGTH - 1) {
        temp = DCXO_TSU_TABLE_MIN_TEMPERATURE + DCXO_TSU_TABLE_LENGTH - 1;
    }

    if (high_interrupt) {
        tsu_code = tsu_temperature_to_code(temp - 1);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 1, 1);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 1, 0);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0, 0xFFFF, tsu_code);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 4), 0);
        irq_no = S5JS100_IRQ_TSU0;
    } else {
        tsu_code = tsu_temperature_to_code(temp);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 2, 2);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 2, 0);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0, 0xFFFF0000, tsu_code << 16);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 5), 0);
        irq_no = S5JS100_IRQ_TSU1;
    }

    //dbg("\ns5js100_tsu_set_interrupt(%d), tsu_code : 0x%x", high_interrupt, tsu_code);

    NVIC_SetVector((IRQn_Type)irq_no, (uint32_t)dcxo_tsu_isr);
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
    SCB_InvalidateICache();
#endif
    NVIC_EnableIRQ((IRQn_Type)irq_no);

    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 2), (1 << 2));
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 2), 0);

    return 1;
}

int s5js100_tsu_set_interrupt_code(int high_interrupt, int tsu_code, int check)
{
    unsigned int irq_no;
    int ret = 1;

    //dbg("s5js100_tsu_set_interrupt(%d), tsu_code : 0x%x\n", high_interrupt, tsu_code);

    if (check) {
        tsu_interrupt_flag = 0;
    }

    if (!high_interrupt) {
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 1, 1);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 1, 0);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0, 0xFFFF, tsu_code);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 4), 0);
        irq_no = S5JS100_IRQ_TSU0;
    } else {
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 2, 2);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, 2, 0);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG0, 0xFFFF0000, tsu_code << 16);
        modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 5), 0);
        irq_no = S5JS100_IRQ_TSU1;
    }

    NVIC_SetVector((IRQn_Type)irq_no, (uint32_t)dcxo_tsu_isr);
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
    SCB_InvalidateICache();
#endif
    NVIC_EnableIRQ((IRQn_Type)irq_no);

    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 2), (1 << 2));
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_IRQ_CFG1, (1 << 2), 0);

    if (check) {
        //sleep(1);

        if (!tsu_interrupt_flag) {
            ret = 0;
        }
    }

    return ret;
}

int s5js100_dcxo_get_coarse_tune_value(void)
{
    int coarse_tune;

#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    // Fine=DcxoFTune, Coarse=FTUNE2
    coarse_tune = (getreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0) >> 1) & 0xFFFFF;
    coarse_tune >>= 6;
#elif defined(DCXO_FINE_PMU_COARSE_PMU)
    // Fine=FTUNE2, Coarse=FTUNE

    // Coarse=FTUNE 14bit
    // EVT0 : 0x81000524[26:13], EVT1 : 0x8100000C[13:0]
    if (is_evt0()) {
        coarse_tune = (getreg32(S5JS100_PMU_ALIVE + 0x524) >> 13) & 0x3FFF;
    } else {
        coarse_tune = (getreg32(S5JS100_PMU_ALIVE + 0xC) >> 0) & 0x3FFF;
    }
#elif defined(DCXO_FINE_DCXO_COARSE_PMU)
    // Fine=DcxoFTune, Coarse=FTUNE2

    // Coarse=FTUNE2 14bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]
    if (is_evt0()) {
        coarse_tune = (getreg32(S5JS100_PMU_ALIVE + 0x528) >> 17) & 0x3FFF;
    } else {
        coarse_tune = (getreg32(S5JS100_PMU_ALIVE + 0xC) >> 14) & 0x3FFF;
    }
#endif

    return coarse_tune;
}


int s5js100_dcxo_set_tune_value(unsigned int coarse_value, unsigned int fine_value)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    // Fine=DcxoFTune, Coarse=FTUNE2
    coarse_value <<= 6;

    // Coarse = DcxoFTune 20bit
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 0xFFFFF << 1, coarse_value << 1);

    // Fine=FTUNE2 14bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x3FFF << 17, fine_value << 17);
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 14, fine_value << 14);
    }
#elif defined(DCXO_FINE_PMU_COARSE_PMU)
    // Fine=FTUNE2, Coarse=FTUNE

    // Fine = FTUNE2 13bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x3FFF << 17, fine_value << 17);
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 14, fine_value << 14);
    }

    // Coarse=FTUNE 14bit
    // EVT0 : 0x81000524[26:13], EVT1 : 0x8100000C[13:0]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x524, (0x3FFF << 13), (coarse_value << 13));
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 0, coarse_value << 0);
    }
#elif defined(DCXO_FINE_DCXO_COARSE_PMU)
    // Fine=DcxoFTune, Coarse=FTUNE2
    fine_value <<= 6;

    // Fine = DcxoFTune 20bit
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 0xFFFFF << 1, fine_value << 1);

    // Coarse=FTUNE2 14bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x3FFF << 17, coarse_value << 17);
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 14, coarse_value << 14);
    }
#endif

    return 1;
}

int s5js100_dcxo_set_tune_value_20bit(unsigned int coarse_value, unsigned int fine_value)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    // Coarse=DcxoFTune, Fine=FTUNE2

    // Coarse = DcxoFTune 20bit
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 0xFFFFF << 1, coarse_value << 1);

    // Fine=FTUNE2 14bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x3FFF << 17, fine_value << 17);
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 14, fine_value << 14);
    }

    // toggle oSwRstDSM
    //modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1<<3), 0);
    //modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1<<3), (1<<3));
#elif defined(DCXO_FINE_PMU_COARSE_PMU)
    // Fine=FTUNE2, Coarse=FTUNE

    coarse_value >>= 6;

    // Fine = FTUNE2 13bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]

    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x3FFF << 17, fine_value << 17);
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 14, fine_value << 14);
    }

    // Coarse=FTUNE 14bit
    // EVT0 : 0x81000524[26:13], EVT1 : 0x8100000C[13:0]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x524, (0x3FFF << 13), (coarse_value << 13));
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 0, coarse_value << 0);
    }
#elif defined(DCXO_FINE_DCXO_COARSE_PMU)
    // Fine=DcxoFTune, Coarse=FTUNE2

    coarse_value >>= 6;

    // Fine = DcxoFTune 20bit
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG0, 0xFFFFF << 1, fine_value << 1);

    // Coarse=FTUNE2 14bit
    // EVT0 : 0x81000528[31:17], EVT1 : 0x8100000C[28:14]
    if (is_evt0()) {
        modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x3FFF << 17, coarse_value << 17);
    } else {
        modifyreg32(S5JS100_PMU_ALIVE + 0xC, 0x3FFF << 14, coarse_value << 14);
    }
#endif

    return 1;
}

int s5js100_dcxo_set_idac_ex(int value)
{
    modifyreg32(S5JS100_PMU_ALIVE + 0x524, 0xff << 5, value << 5);
    return 1;
}

int s5js100_dcxo_set_fb_res(int value)
{
    modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0x7 << 0, value << 0);
    return 1;
}

int s5js100_dcxo_set_buf_sel(int value)
{
    modifyreg32(S5JS100_PMU_ALIVE + 0x528, 0xf << 3, value << 3);
    return 1;
}

int s5js100_dcxo_set_dsmdither(unsigned int dither_bit_sel)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    // set dither_bit_sel, iDsmType=1, iEnDsmDither=0, iPrbsSel=0, iEnDsm=1
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG1, 0x7F, (1 << 6) + (dither_bit_sel << 2) + 1);

    // toggle oSwRstDSM
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), 0);
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), (1 << 3));
#else
#endif
    return 1;
}

int s5js100_dcxo_set_dither_bit_sel(unsigned int dither_bit_sel)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG1, (7 << 2), (dither_bit_sel << 2));
    // toggle oSwRstDSM
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), 0);
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), (1 << 3));
#else
#endif
    return 1;
}

int s5js100_dcxo_set_dsm_type(unsigned int dsm_type)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG1, (1 << 6), (dsm_type << 6));
    // toggle oSwRstDSM
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), 0);
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), (1 << 3));
#else
#endif
    return 1;
}

int s5js100_dcxo_set_en_dsm_dither(unsigned int en_dsm_dither)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG1, (1 << 5), (en_dsm_dither << 5));
    // toggle oSwRstDSM
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), 0);
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), (1 << 3));
#else
#endif
    return 1;
}

int s5js100_dcxo_set_prbs_sel(unsigned int prbs_sel)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG1, (1 << 1), (prbs_sel << 1));
    // toggle oSwRstDSM
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), 0);
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), (1 << 3));
#else
#endif
    return 1;
}

int s5js100_dcxo_en_dsm(unsigned int en_dsm)
{
#if defined(DCXO_FINE_PMU_COARSE_DCXO)
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_DSM_CFG1, (1 << 0), (en_dsm << 0));
    // toggle oSwRstDSM
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), 0);
    modifyreg32(S5JS100_DCXO_CFG + DCXO_CFG_SW_RESETN, (1 << 3), (1 << 3));
#else
#endif
    return 1;
}

extern "C" {
    int s5js100_dcxo_force_initialize(void)
    {
        dcxo_init_flag = 0;
        s5js100_dcxo_initialize();
        s5js100_dcxo_force_update_ctb();

        return 0;
    }
}
