/**
  * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd
  *
  * SPDX-License-Identifier: Apache-2.0
  ******************************************************************************
  * @file    drv_clock.c
  * @version V0.1
  * @brief   cru clock interface
  *
  * Change Logs:
  * Date           Author          Notes
  * 2019-07-11     Elaine.Zhang      first implementation
  *
  ******************************************************************************
  */

/** @addtogroup RKBSP_Driver_Reference
*  @{
*/

/** @addtogroup Clock
 *  @{
 */

/** @defgroup Clock_How_To_Use How To Use
 *  @{

The Clock driver use to configure clock frequency, enable/disable clock output, clock reset, power on/off power domain,
 it can be used in the following three scenarios:

- **Configure clock frequency**:
    - The device set clock rate by clk_set_rate(eCLOCK_Name clk_id, uint32_t rate);
    - The device get clock rate by clk_get_rate(eCLOCK_Name clk_id);

- **Enable/disable clock output**:
    - The device get clock by get_clk_gate_from_id(int clk_id);
    - The device set clock enable/disable by clk_enable(struct clk_gate *gate) or clk_disable(struct clk_gate *gate);

- **Power on/off power domain**:
    - The device get pd by get_pd_from_id(int pd_id);
    - The device power on/off pd by pd_power(struct pd *power, int on);

 @} */

#include <rtdevice.h>
#include <rtthread.h>

#if defined(RT_USING_CRU)

#include "hal_base.h"
#include "drv_clock.h"

static const struct clk_init *g_clk_init = RT_NULL;

static rt_slist_t clk_gate_list;

static struct rt_mutex clk_lock;
static struct rt_mutex gate_lock;
#if defined(RT_USING_PMU)
static struct rt_mutex pd_lock;
static rt_slist_t pd_list;
#endif

/********************* Public Function Definition ****************************/

/** @defgroup CLOCK_Public_Functions Public Functions
 *  @{
 */

/**
 * @brief  clk set enable.
 * @param  gate: get_clk_gate_from_id.
 * @retval -RT_EINVAL: struct gate is invalid argument
 * @retval -RT_ERROR: clk enable failed.
 */
rt_err_t clk_enable(struct clk_gate *gate)
{
    rt_err_t error = RT_EOK;
    HAL_Status ret;

    if (!gate)
    {
        return -RT_EINVAL;
    }

    rt_mutex_take(&gate_lock, RT_WAITING_FOREVER);

    if (gate->enable_count == 0)
    {
        ret = HAL_CRU_ClkEnable(gate->gate_id);
        if (ret != HAL_OK)
            error = -RT_ERROR;
    }
    gate->enable_count++;

    rt_mutex_release(&gate_lock);

    return error;
}

/**
 * @brief  clk set disable.
 * @param  gate: get_clk_gate_from_id.
 * @retval -RT_EINVAL: struct gate is invalid argument
 * @retval -RT_ERROR: clk disable failed.
 */
rt_err_t clk_disable(struct clk_gate *gate)
{
    rt_err_t error = RT_EOK;
    HAL_Status ret;

    if (!gate)
    {
        return -RT_EINVAL;
    }

    rt_mutex_take(&gate_lock, RT_WAITING_FOREVER);

    if (gate->enable_count == 0)
    {
        rt_kprintf("It may be wrong to used, make enable first.(gate_id = %d)\n", __func__, gate->gate_id);
        goto out;
    }

    if (--gate->enable_count > 0)
    {
        goto out;
    }
    ret = HAL_CRU_ClkDisable(gate->gate_id);
    if (ret != HAL_OK)
        error = -RT_ERROR;

out:
    rt_mutex_release(&gate_lock);

    return error;
}

/**
 * @brief  clk is enabled.
 * @param gate: get_clk_gate_from_id.
 * @retval 0: clk is disabled
 * @retval 1: clk is enabled
 */
int clk_is_enabled(struct clk_gate *gate)
{
    if (!gate)
    {
        return 0;
    }

    return HAL_CRU_ClkIsEnabled(gate->gate_id);
}

/**
 * @brief  get clk gate by id.
 * @param gate_id: clk gate id.
 * @return struct of type clk_gate
 */
struct clk_gate *get_clk_gate_from_id(int gate_id)
{
    struct clk_gate *clk_gate;

    rt_mutex_take(&gate_lock, RT_WAITING_FOREVER);

    rt_slist_for_each_entry(clk_gate, &clk_gate_list, node)
    {
        if (clk_gate->gate_id == gate_id)
        {
            goto out;
        }
    }

    clk_gate = rt_calloc(1, sizeof(struct clk_gate));
    clk_gate->gate_id = gate_id;
    clk_gate->enable_count = 0;
    rt_slist_insert(&clk_gate_list, &clk_gate->node);

out:
    clk_gate->ref_count++;
    rt_mutex_release(&gate_lock);

    return clk_gate;
}

/**
 * @brief  put clk gate.
 * @param gate: get_clk_gate_from_id.
 */
void put_clk_gate(struct clk_gate *gate)
{
    if (!gate)
        return;

    rt_mutex_take(&gate_lock, RT_WAITING_FOREVER);

    if (--gate->ref_count > 0)
    {
        goto out;
    }
    rt_slist_remove(&clk_gate_list, &gate->node);
    rt_free(gate);

out:
    rt_mutex_release(&gate_lock);
}

/**
 * @brief  clk get rate.
 * @param clk_id: clk id.
 * @return the return value of HAL_CRU_ClkGetFreq, which returns the frequency value in unit hz.
 */
uint32_t clk_get_rate(eCLOCK_Name clk_id)
{
    uint32_t rate;

    rt_mutex_take(&clk_lock, RT_WAITING_FOREVER);

    rate = HAL_CRU_ClkGetFreq(clk_id);

    rt_mutex_release(&clk_lock);

    return rate;
}

/**
 * @brief  clk set rate.
 * @param clk_id: clk id.
 * @param rate: frequency value hz.
 * @retval RT_EOK: clk set successful
 * @retval HAL_OK: HAL_CRU_ClkSetFreq set frequency successfully
 * @retval HAL_ERROR: HAL_CRU_ClkSetFreq set frequency failed
 * @retval HAL_INVAL: HAL_CRU_ClkSetFreq set frequency unsupported
 */
rt_err_t clk_set_rate(eCLOCK_Name clk_id, uint32_t rate)
{
    rt_err_t error = RT_EOK;

    if (rate == clk_get_rate(clk_id))
        return error;

    rt_mutex_take(&clk_lock, RT_WAITING_FOREVER);

    error = HAL_CRU_ClkSetFreq(clk_id, rate);

    rt_mutex_release(&clk_lock);

    return error;
}

#if defined(RT_USING_PMU)

/**
 * @brief  pd power on.
 * @param power: get_pd_from_id.
 * @retval -RT_EINVAL: struct pd is invalid argument
 * @retval -RT_ERROR: pd power on failed.
 * @retval RT_EOK: pd power on success.
 */
rt_err_t pd_on(struct pd *power)
{
    rt_err_t error = RT_EOK;
    HAL_Status ret;

    if (!power)
    {
        return -RT_EINVAL;
    }

    rt_mutex_take(&pd_lock, RT_WAITING_FOREVER);

    if (power->enable_count == 0)
    {
        ret = HAL_PD_On(power->pd_id);
        if (ret != HAL_OK)
            error = -RT_ERROR;
    }
    power->enable_count++;
    rt_mutex_release(&pd_lock);

    return error;
}

/**
 * @brief  pd power off.
 * @param power: get_pd_from_id.
 * @retval -RT_EINVAL: struct pd is invalid argument
 * @retval -RT_ERROR: pd power off failed.
 * @retval RT_EOK: pd power off success.
 */
rt_err_t pd_off(struct pd *power)
{
    rt_err_t error = RT_EOK;
    HAL_Status ret;

    if (!power)
    {
        return -RT_EINVAL;
    }

    rt_mutex_take(&pd_lock, RT_WAITING_FOREVER);

    if (--power->enable_count > 0)
    {
        goto out;
    }
    ret = HAL_PD_Off(power->pd_id);
    if (ret != HAL_OK)
        error = -RT_ERROR;

out:
    rt_mutex_release(&pd_lock);

    return error;
}

/**
 * @brief  get pd by id.
 * @param pd_id: pd id.
 * @return struct of type pd
 */
struct pd *get_pd_from_id(ePD_Id pd_id)
{
    struct pd *pd;

    if (!pd_id)
        return NULL;

    rt_mutex_take(&pd_lock, RT_WAITING_FOREVER);

    rt_slist_for_each_entry(pd, &pd_list, node)
    {
        if (pd->pd_id == pd_id)
        {
            goto out;
        }
    }
    pd = rt_calloc(1, sizeof(struct pd));
    pd->pd_id = pd_id;
    pd->enable_count = 0;
    rt_slist_insert(&pd_list, &pd->node);

out:
    pd->ref_count++;
    rt_mutex_release(&pd_lock);

    return pd;
}

/**
 * @brief  put pd.
 * @param power: get_pd_from_id.
 */
void put_pd(struct pd *power)
{
    if (!power)
        return;

    rt_mutex_take(&pd_lock, RT_WAITING_FOREVER);

    if (--power->ref_count > 0)
    {
        goto out;
    }
    rt_slist_remove(&pd_list, &power->node);
    rt_free(power);

out:
    rt_mutex_release(&pd_lock);
}
#endif

/**
 * @brief  clock dev init.
 * @return RT_EOK
 */
int clock_dev_init(void)
{
    if (rt_mutex_init(&(clk_lock), "clkLock", RT_IPC_FLAG_FIFO) != RT_EOK)
    {
        RT_ASSERT(0);
    }
    if (rt_mutex_init(&(gate_lock), "gateLock", RT_IPC_FLAG_FIFO) != RT_EOK)
    {
        RT_ASSERT(0);
    }
    rt_slist_init(&clk_gate_list);
#if defined(RT_USING_PMU)
    if (rt_mutex_init(&(pd_lock), "pdLock", RT_IPC_FLAG_FIFO) != RT_EOK)
    {
        RT_ASSERT(0);
    }
    rt_slist_init(&pd_list);
#endif

    return RT_EOK;
}
INIT_BOARD_EXPORT(clock_dev_init);

/**
 * @brief  clock init frequency.
 * @param clk_inits: some need init clks.
 * @param clk_dump: if need printf clk get freq after setting.
 */
void clk_init(const struct clk_init *clk_inits, bool clk_dump)
{
    const struct clk_init *clks = clk_inits;

    while (clks->name)
    {
        if (clks->init_rate)
        {
            HAL_CRU_ClkSetFreq(clks->clk_id, clks->init_rate);
        }
        if (clk_dump)
            rt_kprintf("%s: %s = %d\n", __func__, clks->name, HAL_CRU_ClkGetFreq(clks->clk_id));
        clks++;
    }
    g_clk_init = clk_inits;
}

/**
 * @brief  clock disable unused.
 * @param clks_unused: disable some not needed clks.
 */
void clk_disable_unused(const struct clk_unused *clks_unused)
{
    const struct clk_unused *clks = clks_unused;

    while (clks->gate_val)
    {
        if (clks->is_pmucru)
        {
#if defined(CRU_PMU_CLKGATE_CON0_OFFSET)
            CRU->PMU_CLKGATE_CON[clks->gate_con] = clks->gate_val;
#endif
        }
        else
        {
            CRU->CRU_CLKGATE_CON[clks->gate_con] = clks->gate_val;
        }
        clks++;
    }
}

#if defined(RT_USING_CRU_DUMP)

/**
 * @brief  clock dump frequency, dump cru registers, used for debug.
 */
static void clk_dump(void)
{
    const struct clk_init *clks = g_clk_init;
    int i;

    if (clks)
    {
        while (clks->name)
        {
            rt_kprintf("%s: %s[%x] = %d\n", __func__, clks->name, clks->clk_id,
                       HAL_CRU_ClkGetFreq(clks->clk_id));
            clks++;
        }
    }
    for (i = 0; i < HAL_ARRAY_SIZE(CRU->CRU_CLKSEL_CON); i++)
    {
        rt_kprintf("%s: cru_sel_con[%d] = %lx\n", __func__, i, CRU->CRU_CLKSEL_CON[i]);
    }
    for (i = 0; i < HAL_ARRAY_SIZE(CRU->CRU_CLKGATE_CON); i++)
    {
        rt_kprintf("%s: cru_gate_con[%d] = %lx\n", __func__, i, CRU->CRU_CLKGATE_CON[i]);
    }
    for (i = 0; i < HAL_ARRAY_SIZE(CRU->CRU_SOFTRST_CON); i++)
    {
        rt_kprintf("%s: cru_softreset_con[%d] = %lx\n", __func__, i, CRU->CRU_SOFTRST_CON[i]);
    }
}

#ifdef RT_USING_FINSH
#include <finsh.h>
MSH_CMD_EXPORT(clk_dump, cru drive test. e.g: clk_dump);
#endif
#endif

/** @} */

#endif

/** @} */

/** @} */