| /* Copyright 2021 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* Clocks, PLL and power settings */ |
| |
| #include "clock.h" |
| #include "clock_s3.h" |
| #include "common.h" |
| #include "console.h" |
| #include "csr.h" |
| #include "ec_commands.h" |
| #include "power.h" |
| #include "registers.h" |
| #include "task.h" |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| #define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ##args) |
| #define CPRINTS(format, args...) cprints(CC_CLOCK, format, ##args) |
| |
| #ifdef BOARD_CHERRY_SCP_CORE1 |
| |
| void clock_init(void) |
| { |
| /* clock is controlled by core 0 */ |
| return; |
| } |
| |
| #else |
| |
| enum { |
| OPP_ULPOSC2_LOW_SPEED, |
| OPP_ULPOSC2_HIGH_SPEED, |
| }; |
| |
| static struct opp_ulposc_cfg { |
| uint32_t osc; |
| uint32_t div; |
| uint32_t fband; |
| uint32_t mod; |
| uint32_t cali; |
| uint32_t target_mhz; |
| uint32_t clk_div; |
| } opp[] = { |
| [OPP_ULPOSC2_LOW_SPEED] = { |
| .osc = 1, .target_mhz = 326, .clk_div = CLK_DIV_SEL2, .div = 19, |
| .fband = 10, .mod = 0, .cali = 64, /* 326MHz / 2 = 163MHz */ |
| }, |
| [OPP_ULPOSC2_HIGH_SPEED] = { |
| .osc = 1, .target_mhz = 360, .clk_div = CLK_DIV_SEL1, .div = 21, |
| .fband = 10, .mod = 0, .cali = 64, /* 360MHz / 1 = 360MHz */ |
| }, |
| }; |
| |
| static inline void clock_busy_udelay(int usec) |
| { |
| /* |
| * Delaying by busy-looping, for place that can't use udelay because of |
| * the clock not configured yet. The value 28 is chosen approximately |
| * from experiment. |
| * |
| * `volatile' in order to avoid compiler to optimize the function out |
| * (otherwise, the function will be eliminated). |
| */ |
| volatile int i = usec * 28; |
| |
| while (--i) |
| ; |
| } |
| |
| static void clock_ulposc_config_default(struct opp_ulposc_cfg *opp) |
| { |
| uint32_t val = 0; |
| |
| /* set mod, div2_en = 0, cp_en = 0 */ |
| val |= opp->mod << OSC_MOD_SHIFT; |
| /* set div */ |
| val |= opp->div << OSC_DIV_SHIFT; |
| /* set F-band, I-band = 82 */ |
| val |= (opp->fband << OSC_FBAND_SHIFT) | (82 << OSC_IBAND_SHIFT); |
| /* set calibration */ |
| val |= opp->cali; |
| /* set control register 0 */ |
| AP_ULPOSC_CON0(opp->osc) = val; |
| |
| clock_busy_udelay(50); |
| |
| /* bias = 65 */ |
| val = 65 << OSC_BIAS_SHIFT; |
| /* rsv2 = 0, rsv1 = 41, cali_32k = 0 */ |
| val |= 41 << OSC_RSV1_SHIFT; |
| /* set control register 1 */ |
| AP_ULPOSC_CON1(opp->osc) = val; |
| |
| /* set settle time */ |
| SCP_CLK_HIGH_VAL = (SCP_CLK_HIGH_VAL & ~CLK_HIGH_VAL_MASK) | |
| CLK_HIGH_VAL_VAL(2); |
| } |
| |
| static void clock_ulposc_config_cali(struct opp_ulposc_cfg *opp, |
| uint32_t cali_val) |
| { |
| uint32_t val; |
| |
| val = AP_ULPOSC_CON0(opp->osc); |
| val &= ~OSC_CALI_MASK; |
| val |= cali_val; |
| AP_ULPOSC_CON0(opp->osc) = val; |
| opp->cali = cali_val; |
| |
| clock_busy_udelay(50); |
| } |
| |
| static uint32_t clock_ulposc_measure_freq(uint32_t osc) |
| { |
| uint32_t result = 0; |
| int cnt; |
| uint32_t cali_0 = AP_CLK26CALI_0; |
| uint32_t cali_1 = AP_CLK26CALI_1; |
| uint32_t dbg_cfg = AP_CLK_DBG_CFG; |
| uint32_t misc_cfg = AP_CLK_MISC_CFG_0; |
| |
| /* Set ckgen_load_cnt: CLK26CALI_1[25:16] */ |
| AP_CLK26CALI_1 = CFG_CKGEN_LOAD_CNT; |
| |
| /* before select meter clock input, bit[1:0] = b00 */ |
| AP_CLK_DBG_CFG = (AP_CLK_DBG_CFG & ~DBG_MODE_MASK) | DBG_MODE_SET_CLOCK; |
| |
| /* select monclk_ext2fqmtr_sel: AP_CLK_DBG_CFG[14:8] */ |
| AP_CLK_DBG_CFG = |
| (AP_CLK_DBG_CFG & ~DBG_BIST_SOURCE_MASK) | |
| (osc == 0 ? DBG_BIST_SOURCE_ULPOSC1 : DBG_BIST_SOURCE_ULPOSC2); |
| |
| /* set meter divisor to 1, bit[31:24] = b00000000 */ |
| AP_CLK_MISC_CFG_0 = (AP_CLK_MISC_CFG_0 & ~MISC_METER_DIVISOR_MASK) | |
| MISC_METER_DIV_1; |
| |
| /* enable frequency meter, without start */ |
| AP_CLK26CALI_0 |= CFG_FREQ_METER_ENABLE; |
| |
| /* trigger frequency meter start */ |
| AP_CLK26CALI_0 |= CFG_FREQ_METER_RUN; |
| |
| clock_busy_udelay(45); |
| |
| for (cnt = 10000; cnt > 0; --cnt) { |
| clock_busy_udelay(10); |
| if (!(AP_CLK26CALI_0 & CFG_FREQ_METER_RUN)) { |
| result = CFG_FREQ_COUNTER(AP_CLK26CALI_1); |
| break; |
| } |
| } |
| |
| AP_CLK26CALI_0 = cali_0; |
| AP_CLK26CALI_1 = cali_1; |
| AP_CLK_DBG_CFG = dbg_cfg; |
| AP_CLK_MISC_CFG_0 = misc_cfg; |
| |
| /* disable freq meter */ |
| AP_CLK26CALI_0 &= ~CFG_FREQ_METER_ENABLE; |
| |
| return result; |
| } |
| |
| #define CAL_MIS_RATE 40 |
| static int clock_ulposc_is_calibrated(struct opp_ulposc_cfg *opp) |
| { |
| uint32_t curr, target; |
| |
| curr = clock_ulposc_measure_freq(opp->osc); |
| target = opp->target_mhz * 512 / 26; |
| |
| #ifdef DEBUG |
| CPRINTF("osc:%u, target=%uMHz, curr=%uMHz, cali:%u\n", opp->osc, |
| opp->target_mhz, (curr * 26) / 512, opp->cali); |
| #endif |
| |
| /* check if calibrated value is in the range of target value +- 4% */ |
| if (curr > (target * (1000 - CAL_MIS_RATE) / 1000) && |
| curr < (target * (1000 + CAL_MIS_RATE) / 1000)) |
| return 1; |
| else |
| return 0; |
| } |
| |
| static uint32_t clock_ulposc_process_cali(struct opp_ulposc_cfg *opp) |
| { |
| uint32_t current_val = 0; |
| uint32_t target_val = opp->target_mhz * 512 / 26; |
| uint32_t middle, min = 0, max = OSC_CALI_MASK; |
| uint32_t diff_by_min, diff_by_max, cal_result; |
| |
| do { |
| middle = (min + max) / 2; |
| if (middle == min) |
| break; |
| |
| clock_ulposc_config_cali(opp, middle); |
| current_val = clock_ulposc_measure_freq(opp->osc); |
| |
| if (current_val > target_val) |
| max = middle; |
| else |
| min = middle; |
| } while (min <= max); |
| |
| clock_ulposc_config_cali(opp, min); |
| current_val = clock_ulposc_measure_freq(opp->osc); |
| if (current_val > target_val) |
| diff_by_min = current_val - target_val; |
| else |
| diff_by_min = target_val - current_val; |
| |
| clock_ulposc_config_cali(opp, max); |
| current_val = clock_ulposc_measure_freq(opp->osc); |
| if (current_val > target_val) |
| diff_by_max = current_val - target_val; |
| else |
| diff_by_max = target_val - current_val; |
| |
| if (diff_by_min < diff_by_max) |
| cal_result = min; |
| else |
| cal_result = max; |
| |
| clock_ulposc_config_cali(opp, cal_result); |
| if (!clock_ulposc_is_calibrated(opp)) |
| assert(0); |
| |
| return cal_result; |
| } |
| |
| static void clock_high_enable(int osc) |
| { |
| /* enable high speed clock */ |
| SCP_CLK_ENABLE |= CLK_HIGH_EN; |
| |
| switch (osc) { |
| case 0: |
| /* after 150us, enable ULPOSC */ |
| clock_busy_udelay(150); |
| SCP_CLK_ENABLE |= CLK_HIGH_CG | CLK_HIGH_EN; |
| |
| /* topck ulposc1 clk gating off */ |
| AP_CLK_CFG_29_CLR = PDN_F_ULPOSC_CK; |
| /* select topck ulposc1 as scp clk parent */ |
| AP_CLK_CFG_29_CLR = ULPOSC1_CLK_SEL; |
| |
| AP_CLK_CFG_UPDATE3 = F_ULPOSC_CK_UPDATE; |
| clock_busy_udelay(50); |
| break; |
| case 1: |
| /* turn off ULPOSC2 high-core-disable switch */ |
| SCP_CLK_ON_CTRL &= ~HIGH_CORE_DIS_SUB; |
| /* after 150us, scp requests ULPOSC2 high core clock */ |
| clock_busy_udelay(150); |
| SCP_CLK_HIGH_CORE_CG |= HIGH_CORE_CG; |
| SCP_CLK_ENABLE &= ~CLK_HIGH_CG; |
| clock_busy_udelay(50); |
| |
| /* topck ulposc2 clk gating off */ |
| AP_CLK_CFG_29_CLR = PDN_F_ULPOSC_CORE_CK; |
| /* select topck ulposc2 as scp clk parent */ |
| AP_CLK_CFG_29_CLR = ULPOSC2_CLK_SEL; |
| |
| AP_CLK_CFG_UPDATE3 = F_ULPOSC_CORE_CK_UPDATE; |
| clock_busy_udelay(50); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void clock_high_disable(int osc) |
| { |
| switch (osc) { |
| case 0: |
| /* topck ulposc1 clk gating on */ |
| AP_CLK_CFG_29_SET = PDN_F_ULPOSC_CK; |
| AP_CLK_CFG_UPDATE3 = F_ULPOSC_CK_UPDATE; |
| clock_busy_udelay(50); |
| |
| /* scp doesn't request ulposc1 clk */ |
| SCP_CLK_ENABLE &= ~CLK_HIGH_CG; |
| clock_busy_udelay(50); |
| SCP_CLK_ENABLE &= ~CLK_HIGH_EN; |
| clock_busy_udelay(50); |
| break; |
| case 1: |
| /* topck ulposc2 clk gating on */ |
| AP_CLK_CFG_29_SET = PDN_F_ULPOSC_CORE_CK; |
| AP_CLK_CFG_UPDATE3 = F_ULPOSC_CORE_CK_UPDATE; |
| clock_busy_udelay(50); |
| |
| /* scp doesn't request ulposc2 clk */ |
| SCP_CLK_HIGH_CORE_CG &= ~HIGH_CORE_CG; |
| clock_busy_udelay(50); |
| SCP_CLK_ON_CTRL |= HIGH_CORE_DIS_SUB; |
| clock_busy_udelay(50); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void clock_calibrate_ulposc(struct opp_ulposc_cfg *opp) |
| { |
| /* |
| * ULPOSC1(osc=0) is already |
| * - calibrated |
| * - enabled in coreboot |
| * - used by pmic wrapper |
| */ |
| if (opp->osc != 0) { |
| clock_high_disable(opp->osc); |
| clock_ulposc_config_default(opp); |
| clock_high_enable(opp->osc); |
| } |
| |
| /* Calibrate only if it is not accurate enough. */ |
| if (!clock_ulposc_is_calibrated(opp)) |
| opp->cali = clock_ulposc_process_cali(opp); |
| } |
| |
| void clock_select_clock(enum scp_clock_source src) |
| { |
| uint32_t sel; |
| uint32_t div; |
| |
| switch (src) { |
| case SCP_CLK_SYSTEM: |
| div = CLK_DIV_SEL1; |
| sel = CLK_SW_SEL_SYSTEM; |
| break; |
| case SCP_CLK_32K: |
| div = CLK_DIV_SEL1; |
| sel = CLK_SW_SEL_32K; |
| break; |
| case SCP_CLK_ULPOSC1: |
| div = CLK_DIV_SEL1; |
| sel = CLK_SW_SEL_ULPOSC1; |
| break; |
| case SCP_CLK_ULPOSC2_LOW_SPEED: |
| /* parking at scp system clk until ulposc clk is ready */ |
| clock_select_clock(SCP_CLK_SYSTEM); |
| |
| clock_ulposc_config_cali(&opp[OPP_ULPOSC2_LOW_SPEED], |
| opp[OPP_ULPOSC2_LOW_SPEED].cali); |
| div = opp[OPP_ULPOSC2_LOW_SPEED].clk_div; |
| |
| sel = CLK_SW_SEL_ULPOSC2; |
| break; |
| case SCP_CLK_ULPOSC2_HIGH_SPEED: |
| /* parking at scp system clk until ulposc clk is ready */ |
| clock_select_clock(SCP_CLK_SYSTEM); |
| |
| clock_ulposc_config_cali(&opp[OPP_ULPOSC2_HIGH_SPEED], |
| opp[OPP_ULPOSC2_HIGH_SPEED].cali); |
| div = opp[OPP_ULPOSC2_HIGH_SPEED].clk_div; |
| |
| sel = CLK_SW_SEL_ULPOSC2; |
| break; |
| default: |
| div = CLK_DIV_SEL1; |
| sel = CLK_SW_SEL_SYSTEM; |
| break; |
| } |
| |
| SCP_CLK_DIV_SEL = div; |
| SCP_CLK_SW_SEL = sel; |
| } |
| |
| __override void |
| power_chipset_handle_host_sleep_event(enum host_sleep_event state, |
| struct host_sleep_event_context *ctx) |
| { |
| if (state == HOST_SLEEP_EVENT_S3_SUSPEND) { |
| CPRINTS("AP suspend"); |
| clock_select_clock(SCP_CLK_ULPOSC2_LOW_SPEED); |
| #ifdef HAS_TASK_SR |
| task_set_event(TASK_ID_SR, TASK_EVENT_SUSPEND); |
| #endif |
| } else if (state == HOST_SLEEP_EVENT_S3_RESUME) { |
| #ifdef HAS_TASK_SR |
| task_set_event(TASK_ID_SR, TASK_EVENT_RESUME); |
| #endif |
| clock_select_clock(SCP_CLK_ULPOSC2_HIGH_SPEED); |
| CPRINTS("AP resume"); |
| } |
| } |
| |
| void clock_init(void) |
| { |
| uint32_t i; |
| |
| /* select scp system clock (default 26MHz) */ |
| clock_select_clock(SCP_CLK_SYSTEM); |
| |
| /* set VREQ to HW mode */ |
| SCP_CPU_VREQ_CTRL = VREQ_SEL | VREQ_DVFS_SEL; |
| SCP_CLK_CTRL_GENERAL_CTRL &= ~VREQ_PMIC_WRAP_SEL; |
| SCP_SEC_CTRL &= ~VREQ_SECURE_DIS; |
| |
| /* set DDREN to auto mode */ |
| SCP_SYS_CTRL |= AUTO_DDREN; |
| |
| /* set settle time */ |
| SCP_CLK_SYS_VAL = (SCP_CLK_SYS_VAL & ~CLK_SYS_VAL_MASK) | |
| CLK_SYS_VAL_VAL(1); |
| SCP_CLK_HIGH_VAL = (SCP_CLK_HIGH_VAL & ~CLK_HIGH_VAL_MASK) | |
| CLK_HIGH_VAL_VAL(1); |
| SCP_SLEEP_CTRL = (SCP_SLEEP_CTRL & ~VREQ_COUNT_MASK) | |
| VREQ_COUNT_VAL(1); |
| |
| /* turn off ULPOSC2 */ |
| SCP_CLK_ON_CTRL |= HIGH_CORE_DIS_SUB; |
| |
| /* calibrate ULPOSC2 */ |
| for (i = 0; i < ARRAY_SIZE(opp); ++i) |
| clock_calibrate_ulposc(&opp[i]); |
| |
| /* select ULPOSC2 high speed SCP clock */ |
| clock_select_clock(SCP_CLK_ULPOSC2_HIGH_SPEED); |
| |
| /* select BCLK to use ULPOSC / 8 */ |
| SCP_BCLK_CK_SEL = BCLK_CK_SEL_ULPOSC_DIV8; |
| |
| /* enable default clock gate */ |
| SCP_SET_CLK_CG |= CG_DMA_CH3 | CG_DMA_CH2 | CG_DMA_CH1 | CG_DMA_CH0 | |
| CG_I2C_MCLK | CG_MAD_MCLK | CG_AP2P_MCLK; |
| } |
| |
| #ifdef DEBUG |
| int command_ulposc(int argc, const char *argv[]) |
| { |
| uint32_t osc; |
| |
| for (osc = 0; osc <= OPP_ULPOSC2_HIGH_SPEED; ++osc) |
| ccprintf("ULPOSC%u frequency: %u kHz\n", osc + 1, |
| clock_ulposc_measure_freq(osc) * 26 * 1000 / 512); |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(ulposc, command_ulposc, "[ulposc]", |
| "Measure ULPOSC frequency"); |
| #endif |
| test_mockable void clock_enable_module(enum module_id module, int enable) |
| { |
| } |
| #endif /* BOARD_CHERRY_SCP_CORE1 */ |