7a9744e169
RCX and RC32K oscillators are not precisely trimmed. This code allows to measure actual frequency of those two oscillators. Device tree binding were extended to specify calibration interval. This interval (in seconds) is used to periodically call work that will perform oscillator frequency measurement. For XTAL32K settle time can be provided in device tree. After this time (depending on actual oscillator used) XTAL32K is assumed to be stable and low power clock driven by XTAL32K is considered OK for precise usage in bluetooth. Signed-off-by: Jerzy Kasenberg <jerzy.kasenberg@codecoup.pl>
469 lines
13 KiB
C
469 lines
13 KiB
C
/*
|
|
* Copyright (c) 2022 Renesas Electronics Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <soc.h>
|
|
#include <zephyr/sys/onoff.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/smartbond_clock_control.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <da1469x_clock.h>
|
|
|
|
LOG_MODULE_REGISTER(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
|
|
|
#define DT_DRV_COMPAT smartbond_clock
|
|
|
|
struct lpc_clock_state {
|
|
uint8_t rcx_started : 1;
|
|
uint8_t rcx_ready : 1;
|
|
uint8_t rc32k_started : 1;
|
|
uint8_t rc32k_ready : 1;
|
|
uint8_t xtal32k_started : 1;
|
|
uint8_t xtal32k_ready : 1;
|
|
uint32_t rcx_freq;
|
|
uint32_t rc32k_freq;
|
|
} lpc_clock_state = {
|
|
.rcx_freq = DT_PROP(DT_NODELABEL(rcx), clock_frequency),
|
|
.rc32k_freq = DT_PROP(DT_NODELABEL(rc32k), clock_frequency),
|
|
};
|
|
|
|
#define CALIBRATION_INTERVAL (DT_NODE_HAS_STATUS(DT_NODELABEL(rcx), okay) ? \
|
|
DT_PROP(DT_NODELABEL(rcx), calibration_interval) : \
|
|
DT_PROP(DT_NODELABEL(rc32k), calibration_interval))
|
|
|
|
static void calibration_work_cb(struct k_work *work);
|
|
static void xtal32k_settle_work_cb(struct k_work *work);
|
|
|
|
static K_WORK_DELAYABLE_DEFINE(calibration_work, calibration_work_cb);
|
|
static K_WORK_DELAYABLE_DEFINE(xtal32k_settle_work, xtal32k_settle_work_cb);
|
|
|
|
static void calibration_work_cb(struct k_work *work)
|
|
{
|
|
if (lpc_clock_state.rcx_started) {
|
|
da1469x_clock_lp_rcx_calibrate();
|
|
lpc_clock_state.rcx_ready = true;
|
|
lpc_clock_state.rcx_freq = da1469x_clock_lp_rcx_freq_get();
|
|
k_work_schedule(&calibration_work,
|
|
K_MSEC(1000 * CALIBRATION_INTERVAL));
|
|
LOG_DBG("RCX calibration done, RCX freq: %d",
|
|
(int)lpc_clock_state.rcx_freq);
|
|
} else if (lpc_clock_state.rc32k_started) {
|
|
da1469x_clock_lp_rc32k_calibrate();
|
|
lpc_clock_state.rc32k_ready = true;
|
|
lpc_clock_state.rc32k_freq = da1469x_clock_lp_rc32k_freq_get();
|
|
k_work_schedule(&calibration_work,
|
|
K_MSEC(1000 * CALIBRATION_INTERVAL));
|
|
LOG_DBG("RC32K calibration done, RC32K freq: %d",
|
|
(int)lpc_clock_state.rc32k_freq);
|
|
}
|
|
}
|
|
|
|
static void xtal32k_settle_work_cb(struct k_work *work)
|
|
{
|
|
if (lpc_clock_state.xtal32k_started && !lpc_clock_state.xtal32k_ready) {
|
|
LOG_DBG("XTAL32K settled.");
|
|
lpc_clock_state.xtal32k_ready = true;
|
|
}
|
|
}
|
|
|
|
static void smartbond_start_rc32k(void)
|
|
{
|
|
if ((CRG_TOP->CLK_RC32K_REG & CRG_TOP_CLK_RC32K_REG_RC32K_ENABLE_Msk) == 0) {
|
|
CRG_TOP->CLK_RC32K_REG |= CRG_TOP_CLK_RC32K_REG_RC32K_ENABLE_Msk;
|
|
lpc_clock_state.rc32k_started = true;
|
|
}
|
|
if (!lpc_clock_state.rc32k_ready && (CALIBRATION_INTERVAL > 0)) {
|
|
if (!k_work_is_pending(&calibration_work.work)) {
|
|
k_work_schedule(&calibration_work,
|
|
K_MSEC(1000 * CALIBRATION_INTERVAL));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void smartbond_start_rcx(void)
|
|
{
|
|
if (!lpc_clock_state.rcx_started) {
|
|
lpc_clock_state.rcx_ready = false;
|
|
da1469x_clock_lp_rcx_enable();
|
|
lpc_clock_state.rcx_started = true;
|
|
}
|
|
if (!lpc_clock_state.rcx_ready && (CALIBRATION_INTERVAL > 0)) {
|
|
if (!k_work_is_pending(&calibration_work.work)) {
|
|
k_work_schedule(&calibration_work,
|
|
K_MSEC(1000 * CALIBRATION_INTERVAL));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void smartbond_start_xtal32k(void)
|
|
{
|
|
if (!lpc_clock_state.xtal32k_started) {
|
|
lpc_clock_state.xtal32k_ready = false;
|
|
da1469x_clock_lp_xtal32k_enable();
|
|
lpc_clock_state.xtal32k_started = true;
|
|
k_work_schedule(&xtal32k_settle_work,
|
|
K_MSEC(DT_PROP(DT_NODELABEL(xtal32k),
|
|
settle_time)));
|
|
}
|
|
}
|
|
|
|
static inline int smartbond_clock_control_on(const struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
enum smartbond_clock clk = (enum smartbond_clock)(sub_system);
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
switch (clk) {
|
|
case SMARTBOND_CLK_RC32K:
|
|
smartbond_start_rc32k();
|
|
break;
|
|
case SMARTBOND_CLK_RCX:
|
|
smartbond_start_rcx();
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32K:
|
|
smartbond_start_xtal32k();
|
|
break;
|
|
case SMARTBOND_CLK_RC32M:
|
|
CRG_TOP->CLK_RC32M_REG |= CRG_TOP_CLK_RC32M_REG_RC32M_ENABLE_Msk;
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32M:
|
|
da1469x_clock_sys_xtal32m_init();
|
|
da1469x_clock_sys_xtal32m_enable();
|
|
break;
|
|
case SMARTBOND_CLK_PLL96M:
|
|
if ((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_RUNNING_AT_PLL96M_Msk) == 0) {
|
|
if ((CRG_TOP->CLK_CTRL_REG &
|
|
CRG_TOP_CLK_CTRL_REG_RUNNING_AT_XTAL32M_Msk) == 0) {
|
|
da1469x_clock_sys_xtal32m_init();
|
|
da1469x_clock_sys_xtal32m_enable();
|
|
da1469x_clock_sys_xtal32m_wait_to_settle();
|
|
}
|
|
da1469x_clock_sys_pll_enable();
|
|
}
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int smartbond_clock_control_off(const struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
enum smartbond_clock clk = (enum smartbond_clock)(sub_system);
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
switch (clk) {
|
|
case SMARTBOND_CLK_RC32K:
|
|
if (((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk) >>
|
|
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos) != 0) {
|
|
CRG_TOP->CLK_RC32K_REG &= ~CRG_TOP_CLK_RC32K_REG_RC32K_ENABLE_Msk;
|
|
lpc_clock_state.rc32k_ready = false;
|
|
lpc_clock_state.rc32k_started = false;
|
|
}
|
|
break;
|
|
case SMARTBOND_CLK_RCX:
|
|
if (((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk) >>
|
|
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos) != 1) {
|
|
CRG_TOP->CLK_RCX_REG &= ~CRG_TOP_CLK_RCX_REG_RCX_ENABLE_Msk;
|
|
lpc_clock_state.rcx_ready = false;
|
|
lpc_clock_state.rcx_started = false;
|
|
}
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32K:
|
|
if (((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk) >>
|
|
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos) > 1) {
|
|
CRG_TOP->CLK_XTAL32K_REG &= ~CRG_TOP_CLK_XTAL32K_REG_XTAL32K_ENABLE_Msk;
|
|
lpc_clock_state.xtal32k_ready = false;
|
|
lpc_clock_state.xtal32k_started = false;
|
|
}
|
|
break;
|
|
case SMARTBOND_CLK_RC32M:
|
|
/* Disable rc32m only if not used as system clock */
|
|
if ((CRG_TOP->CLK_CTRL_REG & CRG_TOP_CLK_CTRL_REG_RUNNING_AT_RC32M_Msk) == 0) {
|
|
da1469x_clock_sys_rc32m_disable();
|
|
}
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32M:
|
|
da1469x_clock_sys_xtal32m_init();
|
|
da1469x_clock_sys_xtal32m_enable();
|
|
break;
|
|
case SMARTBOND_CLK_PLL96M:
|
|
da1469x_clock_sys_pll_disable();
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum smartbond_clock smartbond_source_clock(enum smartbond_clock clk)
|
|
{
|
|
static const enum smartbond_clock lp_clk_src[] = {
|
|
SMARTBOND_CLK_RC32K,
|
|
SMARTBOND_CLK_RCX,
|
|
SMARTBOND_CLK_XTAL32K,
|
|
SMARTBOND_CLK_XTAL32K,
|
|
};
|
|
static const enum smartbond_clock sys_clk_src[] = {
|
|
SMARTBOND_CLK_XTAL32M,
|
|
SMARTBOND_CLK_RC32M,
|
|
SMARTBOND_CLK_LP_CLK,
|
|
SMARTBOND_CLK_PLL96M,
|
|
};
|
|
|
|
if (clk == SMARTBOND_CLK_SYS_CLK) {
|
|
clk = sys_clk_src[CRG_TOP->CLK_CTRL_REG &
|
|
CRG_TOP_CLK_CTRL_REG_SYS_CLK_SEL_Msk];
|
|
}
|
|
/* System clock can be low power clock, so next check is not in else */
|
|
if (clk == SMARTBOND_CLK_LP_CLK) {
|
|
clk = lp_clk_src[(CRG_TOP->CLK_CTRL_REG &
|
|
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk) >>
|
|
CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos];
|
|
}
|
|
return clk;
|
|
}
|
|
|
|
static int smartbond_clock_get_rate(enum smartbond_clock clk, uint32_t *rate)
|
|
{
|
|
clk = smartbond_source_clock(clk);
|
|
switch (clk) {
|
|
case SMARTBOND_CLK_RC32K:
|
|
*rate = lpc_clock_state.rc32k_freq;
|
|
break;
|
|
case SMARTBOND_CLK_RCX:
|
|
*rate = lpc_clock_state.rcx_freq;
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32K:
|
|
*rate = DT_PROP(DT_NODELABEL(xtal32k), clock_frequency);
|
|
break;
|
|
case SMARTBOND_CLK_RC32M:
|
|
*rate = DT_PROP(DT_NODELABEL(rc32m), clock_frequency);
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32M:
|
|
*rate = DT_PROP(DT_NODELABEL(xtal32m), clock_frequency);
|
|
break;
|
|
case SMARTBOND_CLK_PLL96M:
|
|
*rate = DT_PROP(DT_NODELABEL(pll), clock_frequency);
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smartbond_clock_control_get_rate(const struct device *dev,
|
|
clock_control_subsys_t sub_system,
|
|
uint32_t *rate)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return smartbond_clock_get_rate((enum smartbond_clock)(sub_system), rate);
|
|
}
|
|
|
|
static enum smartbond_clock smartbond_dt_ord_to_clock(uint32_t dt_ord)
|
|
{
|
|
switch (dt_ord) {
|
|
case DT_DEP_ORD(DT_NODELABEL(rc32k)):
|
|
return SMARTBOND_CLK_RC32K;
|
|
case DT_DEP_ORD(DT_NODELABEL(rcx)):
|
|
return SMARTBOND_CLK_RCX;
|
|
case DT_DEP_ORD(DT_NODELABEL(xtal32k)):
|
|
return SMARTBOND_CLK_XTAL32K;
|
|
case DT_DEP_ORD(DT_NODELABEL(rc32m)):
|
|
return SMARTBOND_CLK_RC32M;
|
|
case DT_DEP_ORD(DT_NODELABEL(xtal32m)):
|
|
return SMARTBOND_CLK_XTAL32M;
|
|
case DT_DEP_ORD(DT_NODELABEL(pll)):
|
|
return SMARTBOND_CLK_PLL96M;
|
|
default:
|
|
return SMARTBOND_CLK_NONE;
|
|
}
|
|
}
|
|
|
|
static void smartbond_clock_control_on_by_ord(const struct device *dev,
|
|
uint32_t clock_id)
|
|
{
|
|
enum smartbond_clock clk = smartbond_dt_ord_to_clock(clock_id);
|
|
|
|
smartbond_clock_control_on(dev, (clock_control_subsys_rate_t)clk);
|
|
}
|
|
|
|
static void smartbond_clock_control_off_by_ord(const struct device *dev,
|
|
uint32_t clock_id)
|
|
{
|
|
enum smartbond_clock clk = smartbond_dt_ord_to_clock(clock_id);
|
|
|
|
smartbond_clock_control_off(dev, (clock_control_subsys_rate_t)clk);
|
|
}
|
|
|
|
static void
|
|
qspi_set_read_pipe_delay(uint8_t delay)
|
|
{
|
|
QSPIC->QSPIC_CTRLMODE_REG =
|
|
(QSPIC->QSPIC_CTRLMODE_REG & ~QSPIC_QSPIC_CTRLMODE_REG_QSPIC_PCLK_MD_Msk) |
|
|
(delay << QSPIC_QSPIC_CTRLMODE_REG_QSPIC_PCLK_MD_Pos) |
|
|
QSPIC_QSPIC_CTRLMODE_REG_QSPIC_RPIPE_EN_Msk;
|
|
}
|
|
|
|
static void
|
|
qspi_set_cs_delay(uint32_t sys_clock_freq, uint32_t read_delay_ns, uint32_t erase_delay_ns)
|
|
{
|
|
sys_clock_freq /= 100000;
|
|
uint32_t read_delay_cyc = ((read_delay_ns * sys_clock_freq) + 9999) / 10000;
|
|
uint32_t erase_delay_cyc = ((erase_delay_ns * sys_clock_freq) + 9999) / 10000;
|
|
|
|
QSPIC->QSPIC_BURSTCMDB_REG =
|
|
(QSPIC->QSPIC_BURSTCMDB_REG & ~QSPIC_QSPIC_BURSTCMDB_REG_QSPIC_CS_HIGH_MIN_Msk) |
|
|
read_delay_cyc << QSPIC_QSPIC_BURSTCMDB_REG_QSPIC_CS_HIGH_MIN_Pos;
|
|
QSPIC->QSPIC_ERASECMDB_REG =
|
|
(QSPIC->QSPIC_ERASECMDB_REG & ~QSPIC_QSPIC_ERASECMDB_REG_QSPIC_ERS_CS_HI_Msk) |
|
|
(erase_delay_cyc << QSPIC_QSPIC_ERASECMDB_REG_QSPIC_ERS_CS_HI_Pos);
|
|
}
|
|
|
|
int z_smartbond_select_lp_clk(enum smartbond_clock lp_clk)
|
|
{
|
|
int rc = 0;
|
|
uint32_t clk_sel = 0;
|
|
uint32_t clk_sel_msk = CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Msk;
|
|
|
|
switch (lp_clk) {
|
|
case SMARTBOND_CLK_RC32K:
|
|
clk_sel = 0;
|
|
break;
|
|
case SMARTBOND_CLK_RCX:
|
|
clk_sel = 1 << CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos;
|
|
break;
|
|
case SMARTBOND_CLK_XTAL32K:
|
|
clk_sel = 2 << CRG_TOP_CLK_CTRL_REG_LP_CLK_SEL_Pos;
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
CRG_TOP->CLK_CTRL_REG = (CRG_TOP->CLK_CTRL_REG & ~clk_sel_msk) | clk_sel;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int z_smartbond_select_sys_clk(enum smartbond_clock sys_clk)
|
|
{
|
|
uint32_t sys_clock_freq;
|
|
uint32_t clk_sel;
|
|
uint32_t clk_sel_msk = CRG_TOP_CLK_CTRL_REG_SYS_CLK_SEL_Msk;
|
|
int res;
|
|
|
|
res = smartbond_clock_get_rate(sys_clk, &sys_clock_freq);
|
|
if (res != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* When PLL is selected as system clock qspi read pipe delay must be set to 7 */
|
|
if (sys_clock_freq > 32000000) {
|
|
qspi_set_read_pipe_delay(7);
|
|
qspi_set_cs_delay(SystemCoreClock,
|
|
DT_PROP(DT_NODELABEL(flash_controller),
|
|
read_cs_idle_delay),
|
|
DT_PROP(DT_NODELABEL(flash_controller),
|
|
erase_cs_idle_delay));
|
|
}
|
|
|
|
if (sys_clk == SMARTBOND_CLK_RC32M) {
|
|
clk_sel = 1 << CRG_TOP_CLK_CTRL_REG_SYS_CLK_SEL_Pos;
|
|
CRG_TOP->CLK_CTRL_REG = (CRG_TOP->CLK_CTRL_REG & ~clk_sel_msk) | clk_sel;
|
|
SystemCoreClock = sys_clock_freq;
|
|
} else if (sys_clk == SMARTBOND_CLK_PLL96M) {
|
|
da1469x_clock_pll_wait_to_lock();
|
|
da1469x_clock_sys_pll_switch();
|
|
} else if (sys_clk == SMARTBOND_CLK_XTAL32M) {
|
|
da1469x_clock_sys_xtal32m_switch_safe();
|
|
}
|
|
|
|
/* When switching back from PLL to 32MHz read pipe delay may be set to 2 */
|
|
if (SystemCoreClock <= 32000000) {
|
|
qspi_set_read_pipe_delay(2);
|
|
qspi_set_cs_delay(SystemCoreClock,
|
|
DT_PROP(DT_NODELABEL(flash_controller),
|
|
read_cs_idle_delay),
|
|
DT_PROP(DT_NODELABEL(flash_controller),
|
|
erase_cs_idle_delay));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize clocks for the Smartbond
|
|
*
|
|
* This routine is called to enable and configure the clocks and PLL
|
|
* of the soc on the board.
|
|
*
|
|
* @param dev clocks device struct
|
|
*
|
|
* @return 0
|
|
*/
|
|
int smartbond_clocks_init(const struct device *dev)
|
|
{
|
|
uint32_t clk_id;
|
|
enum smartbond_clock lp_clk;
|
|
enum smartbond_clock sys_clk;
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
#define ENABLE_OSC(clock) smartbond_clock_control_on_by_ord(dev, DT_DEP_ORD(clock))
|
|
#define DISABLE_OSC(clock) if (DT_NODE_HAS_STATUS(clock, disabled)) { \
|
|
smartbond_clock_control_off_by_ord(dev, DT_DEP_ORD(clock)); \
|
|
}
|
|
|
|
/* Enable all oscillators with status "okay" */
|
|
DT_FOREACH_CHILD_STATUS_OKAY_SEP(DT_PATH(crg, osc), ENABLE_OSC, (;));
|
|
|
|
/* Make sure that selected sysclock is enabled */
|
|
BUILD_ASSERT(DT_NODE_HAS_STATUS(DT_PROP(DT_NODELABEL(sys_clk), clock_src), okay),
|
|
"Clock selected as system clock no enabled in DT");
|
|
BUILD_ASSERT(DT_NODE_HAS_STATUS(DT_PROP(DT_NODELABEL(lp_clk), clock_src), okay),
|
|
"Clock selected as LP clock no enabled in DT");
|
|
BUILD_ASSERT(DT_NODE_HAS_STATUS(DT_NODELABEL(pll), disabled) ||
|
|
DT_NODE_HAS_STATUS(DT_NODELABEL(xtal32m), okay),
|
|
"PLL enabled in DT but XTAL32M is disabled");
|
|
|
|
clk_id = DT_DEP_ORD(DT_PROP(DT_NODELABEL(lp_clk), clock_src));
|
|
lp_clk = smartbond_dt_ord_to_clock(clk_id);
|
|
z_smartbond_select_lp_clk(lp_clk);
|
|
|
|
clk_id = DT_DEP_ORD(DT_PROP(DT_NODELABEL(sys_clk), clock_src));
|
|
sys_clk = smartbond_dt_ord_to_clock(clk_id);
|
|
smartbond_clock_control_on(dev,
|
|
(clock_control_subsys_rate_t)smartbond_source_clock(sys_clk));
|
|
z_smartbond_select_sys_clk(sys_clk);
|
|
|
|
/* Disable unwanted oscillators */
|
|
DT_FOREACH_CHILD_SEP(DT_PATH(crg, osc), DISABLE_OSC, (;));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct clock_control_driver_api smartbond_clock_control_api = {
|
|
.on = smartbond_clock_control_on,
|
|
.off = smartbond_clock_control_off,
|
|
.get_rate = smartbond_clock_control_get_rate,
|
|
};
|
|
|
|
DEVICE_DT_DEFINE(DT_NODELABEL(osc),
|
|
&smartbond_clocks_init,
|
|
NULL,
|
|
NULL, NULL,
|
|
PRE_KERNEL_1,
|
|
CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
|
|
&smartbond_clock_control_api);
|