/* Copyright 2018 The Chromium OS Authors. All rights reserved. * 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_chip.h" #include "common.h" #include "console.h" #include "registers.h" #include "task.h" #include "timer.h" #include "util.h" #define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args) #define ULPOSC_DIV_MAX (1 << OSC_DIV_BITS) #define ULPOSC_CALI_MAX (1 << OSC_CALI_BITS) void clock_init(void) { /* Set VREQ to HW mode */ SCP_CPU_VREQ = CPU_VREQ_HW_MODE; SCP_SECURE_CTRL &= ~ENABLE_SPM_MASK_VREQ; /* Set DDREN auto mode */ SCP_SYS_CTRL |= AUTO_DDREN; /* Initialize 26MHz system clock counter reset value to 1. */ SCP_CLK_SYS_VAL = (SCP_CLK_SYS_VAL & ~CLK_SYS_VAL_MASK) | CLK_SYS_VAL(1); /* Initialize high frequency ULPOSC counter reset value to 1. */ SCP_CLK_HIGH_VAL = (SCP_CLK_HIGH_VAL & ~CLK_HIGH_VAL_MASK) | CLK_HIGH_VAL(1); /* Initialize sleep mode control VREQ counter. */ SCP_CLK_SLEEP_CTRL = (SCP_CLK_SLEEP_CTRL & ~VREQ_COUNTER_MASK) | VREQ_COUNTER_VAL(1); /* Set normal wake clock */ SCP_WAKE_CKSW &= ~WAKE_CKSW_SEL_NORMAL_MASK; /* Enable fast wakeup support */ SCP_CLK_SLEEP = 0; SCP_CLK_ON_CTRL = (SCP_CLK_ON_CTRL & ~HIGH_FINAL_VAL_MASK) | HIGH_FINAL_VAL_DEFAULT; SCP_FAST_WAKE_CNT_END = (SCP_FAST_WAKE_CNT_END & ~FAST_WAKE_CNT_END_MASK) | FAST_WAKE_CNT_END_DEFAULT; /* Set slow wake clock */ SCP_WAKE_CKSW = (SCP_WAKE_CKSW & ~WAKE_CKSW_SEL_SLOW_MASK) | WAKE_CKSW_SEL_SLOW_DEFAULT; /* Select CLK_HIGH as wakeup clock */ SCP_CLK_SLOW_SEL = (SCP_CLK_SLOW_SEL & ~(CKSW_SEL_SLOW_MASK | CKSW_SEL_SLOW_DIV_MASK)) | CKSW_SEL_SLOW_ULPOSC2_CLK; /* * Set legacy wakeup * - disable SPM sleep control * - disable SCP sleep mode */ SCP_CLK_SLEEP_CTRL &= ~(EN_SLEEP_CTRL | SPM_SLEEP_MODE); task_enable_irq(SCP_IRQ_CLOCK); task_enable_irq(SCP_IRQ_CLOCK2); } static void scp_ulposc_config(int osc, uint32_t osc_div, uint32_t osc_cali) { uint32_t val; /* Clear all bits */ val = 0; /* Enable CP */ val |= OSC_CP_EN; /* Set div */ val |= osc_div << 17; /* F-band = 0, I-band = 4 */ val |= 4 << 6; /* Set calibration */ val |= osc_cali; /* Set control register 1 */ AP_ULPOSC_CON02(osc) = val; /* Set control register 2, enable div2 */ AP_ULPOSC_CON13(osc) |= OSC_DIV2_EN; } static inline void 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 int i = usec * 28; while (i--) ; } static unsigned int scp_measure_ulposc_freq(int osc) { unsigned int result = 0; int 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 source, bit[21:16] = clk_src */ 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_SCP_CFG_0 |= CFG_FREQ_METER_ENABLE; /* Trigger frequency meter start */ AP_SCP_CFG_0 |= CFG_FREQ_METER_RUN; /* * Frequency meter counts cycles in 1 / (26 * 1024) second period. * freq_in_hz = freq_counter * 26 * 1024 * * The hardware takes 38us to count cycles. Delay up to 100us, * as busy_udelay may not be accurate when sysclk is not 26Mhz * (e.g. when recalibrating/measuring after boot). */ for (cnt = 100; cnt; cnt--) { busy_udelay(1); if (!(AP_SCP_CFG_0 & CFG_FREQ_METER_RUN)) { result = CFG_FREQ_COUNTER(AP_SCP_CFG_1); break; } } /* Disable freq meter */ AP_SCP_CFG_0 &= ~CFG_FREQ_METER_ENABLE; return result; } static inline int signum(int v) { return (v > 0) - (v < 0); } static inline int abs(int v) { return (v >= 0) ? v : -v; } static int scp_ulposc_config_measure(int osc, int div, int cali) { int freq; scp_ulposc_config(osc, div, cali); freq = scp_measure_ulposc_freq(osc); CPRINTF("ULPOSC%d: %d %d %d (%dkHz)\n", osc + 1, div, cali, freq, freq * 26 * 1000 / 1024); return freq; } /** * Calibrate ULPOSC to target frequency. * * @param osc 0:ULPOSC1, 1:ULPOSC2 * @param target_mhz Target frequency to set * @return Frequency counter output * */ static int scp_calibrate_ulposc(int osc, int target_mhz) { int target_freq = DIV_ROUND_NEAREST(target_mhz * 1024, 26); struct ulposc { int div; /* frequency divisor/multiplier */ int cali; /* variable resistor calibrator */ int freq; /* frequency counter measure result */ } curr, prev = {0}; enum { STAGE_DIV, STAGE_CALI } stage = STAGE_DIV; int param, param_max; curr.div = ULPOSC_DIV_MAX / 2; curr.cali = ULPOSC_CALI_MAX / 2; param = curr.div; param_max = ULPOSC_DIV_MAX; /* * In the loop below, linear search closest div value to get desired * frequency counter value. Then adjust cali to get a better result. * Note that this doesn't give optimal output frequency, but it's * usually close enough. * TODO(b:120176040): See if we can efficiently calibrate the clock with * more precision by exploring more of the cali/div space. * * The frequency function follows. Note that f is positively correlated * with both div and cali: * f(div, cali) = k1 * (div + k2) / R(cali) * C * Where: * R(cali) = k3 / (1 + k4 * (cali - k4)) */ while (1) { curr.freq = scp_ulposc_config_measure(osc, curr.div, curr.cali); if (!curr.freq) return 0; /* * If previous and current are on either side of the desired * frequency, pick the closest one. */ if (prev.freq && signum(target_freq - curr.freq) != signum(target_freq - prev.freq)) { if (abs(target_freq - prev.freq) < abs(target_freq - curr.freq)) curr = prev; if (stage == STAGE_CALI) break; /* Switch to optimizing cali */ stage = STAGE_CALI; param = curr.cali; param_max = ULPOSC_CALI_MAX; } prev = curr; param += signum(target_freq - curr.freq); if (param < 0 || param >= param_max) return 0; if (stage == STAGE_DIV) curr.div = param; else curr.cali = param; } /* * It's possible we end up using prev, so reset the configuration and * measure again. */ return scp_ulposc_config_measure(osc, curr.div, curr.cali); } static void scp_clock_high_enable(int osc) { /* Enable high speed clock */ SCP_CLK_EN |= EN_CLK_HIGH; switch (osc) { case 0: /* After 25ms, enable ULPOSC */ busy_udelay(25 * MSEC); SCP_CLK_EN |= CG_CLK_HIGH; break; case 1: /* Turn off ULPOSC2 high-core-disable switch */ SCP_CLK_ON_CTRL &= ~HIGH_CORE_DIS_SUB; /* After 25ms, turn on ULPOSC2 high core clock gate */ busy_udelay(25 * MSEC); SCP_CLK_HIGH_CORE |= CLK_HIGH_CORE_CG; break; default: break; } } void scp_enable_clock(void) { /* Select default CPU clock */ SCP_CLK_SEL = CLK_SEL_SYS_26M; /* VREQ */ SCP_CPU_VREQ = 0x10001; SCP_SECURE_CTRL &= ~ENABLE_SPM_MASK_VREQ; /* DDREN auto mode */ SCP_SYS_CTRL |= AUTO_DDREN; /* Set settle time */ SCP_CLK_SYS_VAL = 1; /* System clock */ SCP_CLK_HIGH_VAL = 1; /* ULPOSC */ SCP_CLK_SLEEP_CTRL = (SCP_CLK_SLEEP_CTRL & ~VREQ_COUNTER_MASK) | 2; /* Disable slow wake */ SCP_CLK_SLEEP = SLOW_WAKE_DISABLE; /* Disable SPM sleep control, disable sleep mode */ SCP_CLK_SLEEP_CTRL &= ~(SPM_SLEEP_MODE | EN_SLEEP_CTRL); /* Turn off ULPOSC2 */ SCP_CLK_ON_CTRL |= HIGH_CORE_DIS_SUB; scp_ulposc_config(0, 12, 32); scp_clock_high_enable(0); /* Turn on ULPOSC1 */ scp_ulposc_config(1, 16, 32); scp_clock_high_enable(1); /* Turn on ULPOSC2 */ /* Calibrate ULPOSC */ scp_calibrate_ulposc(0, ULPOSC1_CLOCK_MHZ); scp_calibrate_ulposc(1, ULPOSC2_CLOCK_MHZ); /* Select ULPOSC2 high speed CPU clock */ SCP_CLK_SEL = CLK_SEL_ULPOSC_2; /* Enable default clock gate */ SCP_CLK_GATE |= CG_DMA_CH3 | CG_DMA_CH2 | CG_DMA_CH1 | CG_DMA_CH0 | CG_I2C_M | CG_MAD_M | CG_AP2P_M; /* Select pwrap_ulposc */ AP_CLK_CFG_5 = (AP_CLK_CFG_5 & ~PWRAP_ULPOSC_MASK) | OSC_D16; /* Enable pwrap_ulposc clock gate */ AP_CLK_CFG_5_CLR = PWRAP_ULPOSC_CG; } void clock_control_irq(void) { /* Read ack CLK_IRQ */ (SCP_CLK_IRQ_ACK); task_clear_pending_irq(SCP_IRQ_CLOCK); } DECLARE_IRQ(SCP_IRQ_CLOCK, clock_control_irq, 3); void clock_fast_wakeup_irq(void) { /* Ack fast wakeup */ SCP_SLEEP_IRQ2 = 1; task_clear_pending_irq(SCP_IRQ_CLOCK2); } DECLARE_IRQ(SCP_IRQ_CLOCK2, clock_fast_wakeup_irq, 3); /* Console command */ int command_ulposc(int argc, char *argv[]) { if (argc > 1 && !strncmp(argv[1], "cal", 3)) { scp_calibrate_ulposc(0, ULPOSC1_CLOCK_MHZ); scp_calibrate_ulposc(1, ULPOSC2_CLOCK_MHZ); } /* SCP clock meter counts every (26MHz / 1024) tick */ ccprintf("ULPOSC1 frequency: %u kHz\n", scp_measure_ulposc_freq(0) * 26 * 1000 / 1024); ccprintf("ULPOSC2 frequency: %u kHz\n", scp_measure_ulposc_freq(1) * 26 * 1000 / 1024); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(ulposc, command_ulposc, "[calibrate]", "Calibrate ULPOSC frequency");