254 lines
7.4 KiB
C
254 lines
7.4 KiB
C
/*
|
|
* Copyright (c) 2020 Nuvoton Technology Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nuvoton_npcx_pcc
|
|
|
|
#include <soc.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/dt-bindings/clock/npcx_clock.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(clock_control_npcx, LOG_LEVEL_ERR);
|
|
|
|
/* Driver config */
|
|
struct npcx_pcc_config {
|
|
/* cdcg device base address */
|
|
uintptr_t base_cdcg;
|
|
/* pmc device base address */
|
|
uintptr_t base_pmc;
|
|
};
|
|
|
|
/* Driver convenience defines */
|
|
#define HAL_CDCG_INST(dev) \
|
|
((struct cdcg_reg *)((const struct npcx_pcc_config *)(dev)->config)->base_cdcg)
|
|
|
|
#define HAL_PMC_INST(dev) \
|
|
((struct pmc_reg *)((const struct npcx_pcc_config *)(dev)->config)->base_pmc)
|
|
|
|
static uint8_t pddwn_ctl_val[] = {NPCX_PWDWN_CTL_INIT};
|
|
|
|
/* Clock controller local functions */
|
|
static inline int npcx_clock_control_on(const struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
struct npcx_clk_cfg *clk_cfg = (struct npcx_clk_cfg *)(sub_system);
|
|
const uint32_t pmc_base = ((const struct npcx_pcc_config *)dev->config)->base_pmc;
|
|
|
|
if (clk_cfg->ctrl >= NPCX_PWDWN_CTL_COUNT) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Clear related PD (Power-Down) bit of module to turn on clock */
|
|
NPCX_PWDWN_CTL(pmc_base, clk_cfg->ctrl) &= ~(BIT(clk_cfg->bit));
|
|
return 0;
|
|
}
|
|
|
|
static inline int npcx_clock_control_off(const struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
struct npcx_clk_cfg *clk_cfg = (struct npcx_clk_cfg *)(sub_system);
|
|
const uint32_t pmc_base = ((const struct npcx_pcc_config *)dev->config)->base_pmc;
|
|
|
|
if (clk_cfg->ctrl >= NPCX_PWDWN_CTL_COUNT) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set related PD (Power-Down) bit of module to turn off clock */
|
|
NPCX_PWDWN_CTL(pmc_base, clk_cfg->ctrl) |= BIT(clk_cfg->bit);
|
|
return 0;
|
|
}
|
|
|
|
static int npcx_clock_control_get_subsys_rate(const struct device *dev,
|
|
clock_control_subsys_t sub_system,
|
|
uint32_t *rate)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
struct npcx_clk_cfg *clk_cfg = (struct npcx_clk_cfg *)(sub_system);
|
|
|
|
switch (clk_cfg->bus) {
|
|
case NPCX_CLOCK_BUS_APB1:
|
|
*rate = NPCX_APB_CLOCK(1);
|
|
break;
|
|
case NPCX_CLOCK_BUS_APB2:
|
|
*rate = NPCX_APB_CLOCK(2);
|
|
break;
|
|
case NPCX_CLOCK_BUS_APB3:
|
|
*rate = NPCX_APB_CLOCK(3);
|
|
break;
|
|
#if defined(APB4DIV_VAL)
|
|
case NPCX_CLOCK_BUS_APB4:
|
|
*rate = NPCX_APB_CLOCK(4);
|
|
break;
|
|
#endif
|
|
case NPCX_CLOCK_BUS_AHB6:
|
|
*rate = CORE_CLK/(AHB6DIV_VAL + 1);
|
|
break;
|
|
case NPCX_CLOCK_BUS_FIU:
|
|
*rate = CORE_CLK/(FIUDIV_VAL + 1);
|
|
break;
|
|
#if defined(FIU1DIV_VAL)
|
|
case NPCX_CLOCK_BUS_FIU1:
|
|
*rate = CORE_CLK/(FIU1DIV_VAL + 1);
|
|
break;
|
|
#endif
|
|
case NPCX_CLOCK_BUS_CORE:
|
|
*rate = CORE_CLK;
|
|
break;
|
|
case NPCX_CLOCK_BUS_LFCLK:
|
|
*rate = LFCLK;
|
|
break;
|
|
case NPCX_CLOCK_BUS_FMCLK:
|
|
*rate = FMCLK;
|
|
break;
|
|
default:
|
|
*rate = 0U;
|
|
/* Invalid parameters */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Platform specific clock controller functions */
|
|
#if defined(CONFIG_PM)
|
|
void npcx_clock_control_turn_on_system_sleep(bool is_deep, bool is_instant)
|
|
{
|
|
const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
|
|
struct pmc_reg *const inst_pmc = HAL_PMC_INST(clk_dev);
|
|
/* Configure that ec enters system sleep mode if receiving 'wfi' */
|
|
uint8_t pm_flags = BIT(NPCX_PMCSR_IDLE);
|
|
|
|
/* Add 'Disable High-Frequency' flag (ie. 'deep sleep' mode) */
|
|
if (is_deep) {
|
|
pm_flags |= BIT(NPCX_PMCSR_DHF);
|
|
/* Add 'Instant Wake-up' flag if sleep time is within 200 ms */
|
|
if (is_instant)
|
|
pm_flags |= BIT(NPCX_PMCSR_DI_INSTW);
|
|
}
|
|
|
|
inst_pmc->PMCSR = pm_flags;
|
|
}
|
|
|
|
void npcx_clock_control_turn_off_system_sleep(void)
|
|
{
|
|
const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
|
|
struct pmc_reg *const inst_pmc = HAL_PMC_INST(clk_dev);
|
|
|
|
inst_pmc->PMCSR = 0;
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
/* Clock controller driver registration */
|
|
static struct clock_control_driver_api npcx_clock_control_api = {
|
|
.on = npcx_clock_control_on,
|
|
.off = npcx_clock_control_off,
|
|
.get_rate = npcx_clock_control_get_subsys_rate,
|
|
};
|
|
|
|
/* valid clock frequency check */
|
|
BUILD_ASSERT(OFMCLK <= MAX_OFMCLK, "Exceed maximum OFMCLK setting");
|
|
BUILD_ASSERT(CORE_CLK <= MAX_OFMCLK && CORE_CLK >= MHZ(4) &&
|
|
OFMCLK % CORE_CLK == 0 &&
|
|
OFMCLK / CORE_CLK <= 10,
|
|
"Invalid CORE_CLK setting");
|
|
BUILD_ASSERT(CORE_CLK / (FIUDIV_VAL + 1) <= (MAX_OFMCLK / 2) &&
|
|
CORE_CLK / (FIUDIV_VAL + 1) >= MHZ(4),
|
|
"Invalid FIUCLK setting");
|
|
#if defined(FIU1DIV_VAL)
|
|
BUILD_ASSERT(CORE_CLK / (FIU1DIV_VAL + 1) <= (MAX_OFMCLK / 2) &&
|
|
CORE_CLK / (FIU1DIV_VAL + 1) >= MHZ(4),
|
|
"Invalid FIU1CLK setting");
|
|
#endif
|
|
BUILD_ASSERT(CORE_CLK / (AHB6DIV_VAL + 1) <= (MAX_OFMCLK / 2) &&
|
|
CORE_CLK / (AHB6DIV_VAL + 1) >= MHZ(4),
|
|
"Invalid AHB6_CLK setting");
|
|
BUILD_ASSERT(APBSRC_CLK / (APB1DIV_VAL + 1) <= (MAX_OFMCLK / 2) &&
|
|
APBSRC_CLK / (APB1DIV_VAL + 1) >= MHZ(4) &&
|
|
(APB1DIV_VAL + 1) % (FPRED_VAL + 1) == 0,
|
|
"Invalid APB1_CLK setting");
|
|
BUILD_ASSERT(APBSRC_CLK / (APB2DIV_VAL + 1) <= (MAX_OFMCLK / 2) &&
|
|
APBSRC_CLK / (APB2DIV_VAL + 1) >= MHZ(8) &&
|
|
(APB2DIV_VAL + 1) % (FPRED_VAL + 1) == 0,
|
|
"Invalid APB2_CLK setting");
|
|
BUILD_ASSERT(APBSRC_CLK / (APB3DIV_VAL + 1) <= (MAX_OFMCLK / 2) &&
|
|
APBSRC_CLK / (APB3DIV_VAL + 1) >= KHZ(12500) &&
|
|
(APB3DIV_VAL + 1) % (FPRED_VAL + 1) == 0,
|
|
"Invalid APB3_CLK setting");
|
|
#if defined(APB4DIV_VAL)
|
|
BUILD_ASSERT(APBSRC_CLK / (APB4DIV_VAL + 1) <= MAX_OFMCLK &&
|
|
APBSRC_CLK / (APB4DIV_VAL + 1) >= MHZ(8) &&
|
|
(APB4DIV_VAL + 1) % (FPRED_VAL + 1) == 0,
|
|
"Invalid APB4_CLK setting");
|
|
#endif
|
|
|
|
static int npcx_clock_control_init(const struct device *dev)
|
|
{
|
|
struct cdcg_reg *const inst_cdcg = HAL_CDCG_INST(dev);
|
|
const uint32_t pmc_base = ((const struct npcx_pcc_config *)dev->config)->base_pmc;
|
|
|
|
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NPCX_EXTERNAL_SRC)) {
|
|
inst_cdcg->LFCGCTL2 |= BIT(NPCX_LFCGCTL2_XT_OSC_SL_EN);
|
|
}
|
|
|
|
/*
|
|
* Resetting the OFMCLK (even to the same value) will make the clock
|
|
* unstable for a little which can affect peripheral communication like
|
|
* eSPI. Skip this if not needed.
|
|
*/
|
|
if (inst_cdcg->HFCGN != HFCGN_VAL || inst_cdcg->HFCGML != HFCGML_VAL
|
|
|| inst_cdcg->HFCGMH != HFCGMH_VAL) {
|
|
/*
|
|
* Configure frequency multiplier M/N values according to
|
|
* the requested OFMCLK (Unit:Hz).
|
|
*/
|
|
inst_cdcg->HFCGN = HFCGN_VAL;
|
|
inst_cdcg->HFCGML = HFCGML_VAL;
|
|
inst_cdcg->HFCGMH = HFCGMH_VAL;
|
|
|
|
/* Load M and N values into the frequency multiplier */
|
|
inst_cdcg->HFCGCTRL |= BIT(NPCX_HFCGCTRL_LOAD);
|
|
/* Wait for stable */
|
|
while (IS_BIT_SET(inst_cdcg->HFCGCTRL, NPCX_HFCGCTRL_CLK_CHNG))
|
|
;
|
|
}
|
|
|
|
/* Set all clock prescalers of core and peripherals. */
|
|
inst_cdcg->HFCGP = VAL_HFCGP;
|
|
inst_cdcg->HFCBCD = VAL_HFCBCD;
|
|
inst_cdcg->HFCBCD1 = VAL_HFCBCD1;
|
|
inst_cdcg->HFCBCD2 = VAL_HFCBCD2;
|
|
|
|
/*
|
|
* Power-down (turn off clock) the modules initially for better
|
|
* power consumption.
|
|
*/
|
|
for (int i = 0; i < ARRAY_SIZE(pddwn_ctl_val); i++) {
|
|
NPCX_PWDWN_CTL(pmc_base, i) = pddwn_ctl_val[i];
|
|
}
|
|
|
|
/* Turn off the clock of the eSPI module only if eSPI isn't required */
|
|
if (!IS_ENABLED(CONFIG_ESPI)) {
|
|
NPCX_PWDWN_CTL(pmc_base, NPCX_PWDWN_CTL6) |= BIT(7);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct npcx_pcc_config pcc_config = {
|
|
.base_cdcg = DT_INST_REG_ADDR_BY_NAME(0, cdcg),
|
|
.base_pmc = DT_INST_REG_ADDR_BY_NAME(0, pmc),
|
|
};
|
|
|
|
DEVICE_DT_INST_DEFINE(0,
|
|
&npcx_clock_control_init,
|
|
NULL,
|
|
NULL, &pcc_config,
|
|
PRE_KERNEL_1,
|
|
CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
|
|
&npcx_clock_control_api);
|