492 lines
10 KiB
C
492 lines
10 KiB
C
/**
|
|
* 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
|
|
|
|
/** @} */
|
|
|
|
/** @} */
|