| /* |
| * Copyright (c) 2020 Mohamed ElShahawi. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT espressif_esp32_rtc |
| |
| #include <dt-bindings/clock/esp32_clock.h> |
| #include <esp_bit_defs.h> |
| #include <soc/dport_reg.h> |
| #include <esp32/rom/uart.h> |
| #include <esp32/rom/rtc.h> |
| #include <soc/rtc.h> |
| #include <soc/rtc_cntl_reg.h> |
| #include <drivers/uart.h> |
| #include <soc/apb_ctrl_reg.h> |
| |
| #include <soc.h> |
| #include <drivers/clock_control.h> |
| #include <sys/util.h> |
| #include "clock_control_esp32.h" |
| #include "driver/periph_ctrl.h" |
| #include <xtensa/core-macros.h> |
| |
| struct esp32_clock_config { |
| uint32_t clk_src_sel; |
| uint32_t cpu_freq; |
| uint32_t xtal_freq_sel; |
| uint32_t xtal_div; |
| }; |
| |
| struct control_regs { |
| /** Peripheral control register */ |
| uint32_t clk; |
| /** Peripheral reset register */ |
| uint32_t rst; |
| }; |
| |
| struct bbpll_cfg { |
| uint8_t div_ref; |
| uint8_t div7_0; |
| uint8_t div10_8; |
| uint8_t lref; |
| uint8_t dcur; |
| uint8_t bw; |
| }; |
| |
| struct pll_cfg { |
| uint8_t dbias_wak; |
| uint8_t endiv5; |
| uint8_t bbadc_dsmp; |
| struct bbpll_cfg bbpll[2]; |
| }; |
| |
| |
| #define PLL_APB_CLK_FREQ 80 |
| |
| #define RTC_PLL_FREQ_320M 0 |
| #define RTC_PLL_FREQ_480M 1 |
| |
| #define DPORT_CPUPERIOD_SEL_80 0 |
| #define DPORT_CPUPERIOD_SEL_160 1 |
| #define DPORT_CPUPERIOD_SEL_240 2 |
| |
| #define DEV_CFG(dev) ((struct esp32_clock_config *)(dev->config)) |
| #define GET_REG_BANK(module_id) ((uint32_t)module_id / 32U) |
| #define GET_REG_OFFSET(module_id) ((uint32_t)module_id % 32U) |
| |
| #define CLOCK_REGS_BANK_COUNT 3 |
| |
| const struct control_regs clock_control_regs[CLOCK_REGS_BANK_COUNT] = { |
| [0] = { .clk = DPORT_PERIP_CLK_EN_REG, .rst = DPORT_PERIP_RST_EN_REG }, |
| [1] = { .clk = DPORT_PERI_CLK_EN_REG, .rst = DPORT_PERI_RST_EN_REG }, |
| [2] = { .clk = DPORT_WIFI_CLK_EN_REG, .rst = DPORT_CORE_RST_EN_REG } |
| }; |
| |
| static uint32_t const xtal_freq[] = { |
| [ESP32_CLK_XTAL_40M] = 40, |
| [ESP32_CLK_XTAL_26M] = 26 |
| }; |
| |
| const struct pll_cfg pll_config[] = { |
| [RTC_PLL_FREQ_320M] = { |
| .dbias_wak = 0, |
| .endiv5 = BBPLL_ENDIV5_VAL_320M, |
| .bbadc_dsmp = BBPLL_BBADC_DSMP_VAL_320M, |
| .bbpll[ESP32_CLK_XTAL_40M] = { |
| /* 40mhz */ |
| .div_ref = 0, |
| .div7_0 = 32, |
| .div10_8 = 0, |
| .lref = 0, |
| .dcur = 6, |
| .bw = 3, |
| }, |
| .bbpll[ESP32_CLK_XTAL_26M] = { |
| /* 26mhz */ |
| .div_ref = 12, |
| .div7_0 = 224, |
| .div10_8 = 4, |
| .lref = 1, |
| .dcur = 0, |
| .bw = 1, |
| } |
| }, |
| [RTC_PLL_FREQ_480M] = { |
| .dbias_wak = 0, |
| .endiv5 = BBPLL_ENDIV5_VAL_480M, |
| .bbadc_dsmp = BBPLL_BBADC_DSMP_VAL_480M, |
| .bbpll[ESP32_CLK_XTAL_40M] = { |
| /* 40mhz */ |
| .div_ref = 0, |
| .div7_0 = 28, |
| .div10_8 = 0, |
| .lref = 0, |
| .dcur = 6, |
| .bw = 3, |
| }, |
| .bbpll[ESP32_CLK_XTAL_26M] = { |
| /* 26mhz */ |
| .div_ref = 12, |
| .div7_0 = 144, |
| .div10_8 = 4, |
| .lref = 1, |
| .dcur = 0, |
| .bw = 1, |
| } |
| } |
| }; |
| |
| static void bbpll_configure(rtc_xtal_freq_t xtal_freq, uint32_t pll_freq) |
| { |
| uint8_t dbias_wak = 0; |
| |
| const struct pll_cfg *cfg = &pll_config[pll_freq]; |
| const struct bbpll_cfg *bb_cfg = &pll_config[pll_freq].bbpll[xtal_freq]; |
| |
| /* Enable PLL, Clear PowerDown (_PD) flags */ |
| CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, |
| RTC_CNTL_BIAS_I2C_FORCE_PD | |
| RTC_CNTL_BB_I2C_FORCE_PD | |
| RTC_CNTL_BBPLL_FORCE_PD | |
| RTC_CNTL_BBPLL_I2C_FORCE_PD); |
| |
| /* reset BBPLL configuration */ |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_IR_CAL_DELAY, BBPLL_IR_CAL_DELAY_VAL); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_IR_CAL_EXT_CAP, BBPLL_IR_CAL_EXT_CAP_VAL); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_OC_ENB_FCAL, BBPLL_OC_ENB_FCAL_VAL); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_OC_ENB_VCON, BBPLL_OC_ENB_VCON_VAL); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_BBADC_CAL_7_0, BBPLL_BBADC_CAL_7_0_VAL); |
| |
| /* voltage needs to be changed for CPU@240MHz or |
| * 80MHz Flash (because of internal flash regulator) |
| */ |
| if (pll_freq == RTC_PLL_FREQ_320M) { |
| dbias_wak = DIG_DBIAS_80M_160M; |
| } else { /* RTC_PLL_FREQ_480M */ |
| dbias_wak = DIG_DBIAS_240M; |
| } |
| |
| /* Configure the voltage */ |
| REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, dbias_wak); |
| esp32_rom_ets_delay_us(DELAY_PLL_DBIAS_RAISE); |
| |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_ENDIV5, cfg->endiv5); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_BBADC_DSMP, cfg->bbadc_dsmp); |
| |
| uint8_t i2c_bbpll_lref = (bb_cfg->lref << 7) | (bb_cfg->div10_8 << 4) | (bb_cfg->div_ref); |
| |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_OC_LREF, i2c_bbpll_lref); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_OC_DIV_7_0, bb_cfg->div7_0); |
| I2C_WRITEREG_RTC(I2C_BBPLL, I2C_BBPLL_OC_DCUR, ((bb_cfg->bw << 6) | bb_cfg->dcur)); |
| } |
| |
| static inline uint32_t clk_val_to_reg_val(uint32_t val) |
| { |
| return (val & UINT16_MAX) | ((val & UINT16_MAX) << 16); |
| } |
| |
| int IRAM_ATTR esp_clk_cpu_freq(void) |
| { |
| return MHZ(esp32_rom_g_ticks_per_us_pro); |
| } |
| |
| int IRAM_ATTR esp_clk_apb_freq(void) |
| { |
| return MHZ(MIN(esp32_rom_g_ticks_per_us_pro, 80)); |
| } |
| |
| void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us) |
| { |
| /* Update scale factors used by ets_delay_us */ |
| esp32_rom_g_ticks_per_us_pro = ticks_per_us; |
| esp32_rom_g_ticks_per_us_app = ticks_per_us; |
| } |
| |
| static void esp32_cpu_freq_to_xtal(int freq, int div) |
| { |
| ets_update_cpu_frequency(freq); |
| |
| uint32_t apb_freq = MHZ(freq); |
| WRITE_PERI_REG(RTC_APB_FREQ_REG, clk_val_to_reg_val(apb_freq >> 12)); |
| /* set divider from XTAL to APB clock */ |
| REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, div - 1); |
| /* adjust ref_tick */ |
| REG_WRITE(APB_CTRL_XTAL_TICK_CONF_REG, MHZ(freq) / REF_CLK_FREQ - 1); |
| /* switch clock source */ |
| REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_XTL); |
| |
| /* lower the voltage */ |
| if (freq <= 2) { |
| REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_2M); |
| } else { |
| REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, DIG_DBIAS_XTAL); |
| } |
| } |
| |
| static void cpuclk_pll_configure(uint32_t xtal_freq, uint32_t cpu_freq) |
| { |
| uint32_t pll_freq = RTC_PLL_FREQ_320M; |
| uint32_t cpu_period_sel = DPORT_CPUPERIOD_SEL_80; |
| |
| switch (cpu_freq) { |
| case ESP32_CLK_CPU_80M: |
| pll_freq = RTC_PLL_FREQ_320M; |
| cpu_period_sel = DPORT_CPUPERIOD_SEL_80; |
| break; |
| case ESP32_CLK_CPU_160M: |
| pll_freq = RTC_PLL_FREQ_320M; |
| cpu_period_sel = DPORT_CPUPERIOD_SEL_160; |
| break; |
| case ESP32_CLK_CPU_240M: |
| pll_freq = RTC_PLL_FREQ_480M; |
| cpu_period_sel = DPORT_CPUPERIOD_SEL_240; |
| break; |
| } |
| |
| /* Configure PLL based on XTAL Value */ |
| bbpll_configure(xtal_freq, pll_freq); |
| /* Set CPU Speed (80,160,240) */ |
| DPORT_REG_WRITE(DPORT_CPU_PER_CONF_REG, cpu_period_sel); |
| /* Set PLL as CPU Clock Source */ |
| REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_PLL); |
| |
| ets_update_cpu_frequency(cpu_freq); |
| |
| /* |
| * Update REF_Tick, |
| * if PLL is the cpu clock source, APB frequency is always 80MHz |
| */ |
| REG_WRITE(APB_CTRL_PLL_TICK_CONF_REG, PLL_APB_CLK_FREQ - 1); |
| } |
| |
| static int clock_control_esp32_on(const struct device *dev, |
| clock_control_subsys_t sys) |
| { |
| ARG_UNUSED(dev); |
| uint32_t bank = GET_REG_BANK(sys); |
| uint32_t offset = GET_REG_OFFSET(sys); |
| |
| __ASSERT_NO_MSG(bank < CLOCK_REGS_BANK_COUNT); |
| |
| esp32_set_mask32(BIT(offset), clock_control_regs[bank].clk); |
| esp32_clear_mask32(BIT(offset), clock_control_regs[bank].rst); |
| return 0; |
| } |
| |
| static int clock_control_esp32_off(const struct device *dev, |
| clock_control_subsys_t sys) |
| { |
| ARG_UNUSED(dev); |
| uint32_t bank = GET_REG_BANK(sys); |
| uint32_t offset = GET_REG_OFFSET(sys); |
| |
| __ASSERT_NO_MSG(bank < CLOCK_REGS_BANK_COUNT); |
| |
| esp32_clear_mask32(BIT(offset), clock_control_regs[bank].clk); |
| esp32_set_mask32(BIT(offset), clock_control_regs[bank].rst); |
| return 0; |
| } |
| |
| static enum clock_control_status clock_control_esp32_get_status(const struct device *dev, |
| clock_control_subsys_t sys) |
| { |
| ARG_UNUSED(dev); |
| uint32_t bank = GET_REG_BANK(sys); |
| uint32_t offset = GET_REG_OFFSET(sys); |
| |
| if (DPORT_GET_PERI_REG_MASK(clock_control_regs[bank].clk, BIT(offset))) { |
| return CLOCK_CONTROL_STATUS_ON; |
| } |
| return CLOCK_CONTROL_STATUS_OFF; |
| } |
| |
| static int clock_control_esp32_get_rate(const struct device *dev, |
| clock_control_subsys_t sub_system, |
| uint32_t *rate) |
| { |
| ARG_UNUSED(sub_system); |
| |
| uint32_t xtal_freq_sel = DEV_CFG(dev)->xtal_freq_sel; |
| uint32_t soc_clk_sel = REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL); |
| |
| switch (soc_clk_sel) { |
| case RTC_CNTL_SOC_CLK_SEL_XTL: |
| *rate = xtal_freq[xtal_freq_sel]; |
| return 0; |
| case RTC_CNTL_SOC_CLK_SEL_PLL: |
| *rate = MHZ(80); |
| return 0; |
| default: |
| *rate = 0; |
| return -ENOTSUP; |
| } |
| } |
| |
| static int clock_control_esp32_init(const struct device *dev) |
| { |
| struct esp32_clock_config *cfg = DEV_CFG(dev); |
| |
| /* Wait for UART first before changing freq to avoid garbage on console */ |
| esp32_rom_uart_tx_wait_idle(0); |
| |
| switch (cfg->clk_src_sel) { |
| case ESP32_CLK_SRC_XTAL: |
| REG_SET_FIELD(APB_CTRL_SYSCLK_CONF_REG, APB_CTRL_PRE_DIV_CNT, cfg->xtal_div); |
| /* adjust ref_tick */ |
| REG_WRITE(APB_CTRL_XTAL_TICK_CONF_REG, xtal_freq[cfg->xtal_freq_sel] - 1); |
| /* switch clock source */ |
| REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, RTC_CNTL_SOC_CLK_SEL_XTL); |
| break; |
| case ESP32_CLK_SRC_PLL: |
| esp32_cpu_freq_to_xtal(xtal_freq[cfg->xtal_freq_sel], 1); |
| cpuclk_pll_configure(cfg->xtal_freq_sel, cfg->cpu_freq); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* Enable RNG clock. */ |
| periph_module_enable(PERIPH_RNG_MODULE); |
| |
| /* Re-calculate the CCOUNT register value to make time calculation correct. |
| * This should be updated on each frequency change |
| * New CCOUNT = Current CCOUNT * (new freq / old freq) |
| */ |
| XTHAL_SET_CCOUNT((uint64_t)XTHAL_GET_CCOUNT() * cfg->cpu_freq / xtal_freq[cfg->xtal_freq_sel]); |
| return 0; |
| } |
| |
| static const struct clock_control_driver_api clock_control_esp32_api = { |
| .on = clock_control_esp32_on, |
| .off = clock_control_esp32_off, |
| .get_rate = clock_control_esp32_get_rate, |
| .get_status = clock_control_esp32_get_status, |
| }; |
| |
| static const struct esp32_clock_config esp32_clock_config0 = { |
| .clk_src_sel = DT_PROP(DT_INST(0, cadence_tensilica_xtensa_lx6), clock_source), |
| .cpu_freq = DT_PROP(DT_INST(0, cadence_tensilica_xtensa_lx6), clock_frequency), |
| .xtal_freq_sel = DT_INST_PROP(0, xtal_freq), |
| .xtal_div = DT_INST_PROP(0, xtal_div), |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, |
| &clock_control_esp32_init, |
| device_pm_control_nop, |
| NULL, &esp32_clock_config0, |
| PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS, |
| &clock_control_esp32_api); |
| |
| BUILD_ASSERT((CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC) == MHZ(DT_PROP(DT_INST(0, cadence_tensilica_xtensa_lx6), clock_frequency)), |
| "SYS_CLOCK_HW_CYCLES_PER_SEC Value must be equal to CPU_Freq"); |
| |
| BUILD_ASSERT(DT_NODE_HAS_PROP(DT_INST(0, cadence_tensilica_xtensa_lx6), clock_source), |
| "CPU clock-source property must be set to ESP32_CLK_SRC_XTAL or ESP32_CLK_SRC_PLL"); |