summaryrefslogtreecommitdiff
path: root/chip/mt_scp/mt8183/clock.c
diff options
context:
space:
mode:
authorTzung-Bi Shih <tzungbi@chromium.org>2021-06-24 15:05:32 +0800
committerCommit Bot <commit-bot@chromium.org>2021-06-25 03:42:23 +0000
commitd18d88b3576bd159e26753321d8fb5e2e280d42a (patch)
treecc6d37c5a8b5eede4adbfda7bb9826a158eed88e /chip/mt_scp/mt8183/clock.c
parentfd85931c6db4d236917d6fadcff2ccfd48109ab0 (diff)
downloadchrome-ec-d18d88b3576bd159e26753321d8fb5e2e280d42a.tar.gz
chip/mt_scp: move mt8183 specific to sub-folder
BRANCH=none BUG=b:191835814 TEST=make BOARD=kukui_scp Signed-off-by: Tzung-Bi Shih <tzungbi@chromium.org> Change-Id: Ic8387200a741a4e7ef99e13772231a0ec0bc1fc1 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2984255 Reviewed-by: Eric Yilun Lin <yllin@google.com>
Diffstat (limited to 'chip/mt_scp/mt8183/clock.c')
-rw-r--r--chip/mt_scp/mt8183/clock.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/chip/mt_scp/mt8183/clock.c b/chip/mt_scp/mt8183/clock.c
new file mode 100644
index 0000000000..a6f1883d31
--- /dev/null
+++ b/chip/mt_scp/mt8183/clock.c
@@ -0,0 +1,374 @@
+/* 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_use_clock(enum scp_clock_source src)
+{
+ /*
+ * DIV2 divider takes precedence over clock selection to prevent
+ * over-clocking.
+ */
+ if (src == SCP_CLK_ULPOSC1)
+ SCP_CLK_DIV_SEL = CLK_DIV2;
+
+ SCP_CLK_SEL = src;
+
+ if (src != SCP_CLK_ULPOSC1)
+ SCP_CLK_DIV_SEL = CLK_DIV1;
+}
+
+void scp_enable_clock(void)
+{
+ /* Select default CPU clock */
+ scp_use_clock(SCP_CLK_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_use_clock(SCP_CLK_ULPOSC2);
+
+ /* 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");