You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
8.2 KiB
278 lines
8.2 KiB
/* |
|
* 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, CONFIG_CLOCK_CONTROL_LOG_LEVEL); |
|
|
|
#if defined(CONFIG_NPCX_SOC_VARIANT_NPCXN) |
|
#define NPCX_PWDWN_CTL_START_OFFSET NPCX_PWDWN_CTL1 |
|
#elif defined(CONFIG_NPCX_SOC_VARIANT_NPCKN) |
|
#define NPCX_PWDWN_CTL_START_OFFSET NPCX_PWDWN_CTL0 |
|
#endif |
|
|
|
/* 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; |
|
case NPCX_CLOCK_BUS_MCLKD: |
|
*rate = OFMCLK/(MCLKD_SL + 1); |
|
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 DEVICE_API(clock_control, 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(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC == OFMCLK / (APB2DIV_VAL + 1), |
|
"SYS_CLOCK_HW_CYCLES_PER_SEC must equal to OFMCLK/APB2DIV_VAL"); |
|
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 |
|
#if defined(CONFIG_I3C_NPCX) |
|
BUILD_ASSERT(OFMCLK / (MCLKD_SL + 1) <= MHZ(50) && |
|
OFMCLK / (MCLKD_SL + 1) >= MHZ(40), |
|
"Invalid MCLKD_SL setting"); |
|
BUILD_ASSERT(APBSRC_CLK / (APB4DIV_VAL + 1) >= MHZ(20), |
|
"Invalid PDMA 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 defined(CONFIG_NPCX_SOC_VARIANT_NPCXN) |
|
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NPCX_EXTERNAL_SRC)) { |
|
inst_cdcg->LFCGCTL2 |= BIT(NPCX_LFCGCTL2_XT_OSC_SL_EN); |
|
} |
|
#endif |
|
|
|
/* |
|
* 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; |
|
#if defined(CONFIG_SOC_SERIES_NPCX4) |
|
inst_cdcg->HFCBCD3 = VAL_HFCBCD3; |
|
#endif |
|
|
|
/* |
|
* 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 + NPCX_PWDWN_CTL_START_OFFSET) = 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);
|
|
|