diff options
author | Sheng-Liang Song <ssl@chromium.org> | 2014-12-17 10:58:11 -0800 |
---|---|---|
committer | ChromeOS Commit Bot <chromeos-commit-bot@chromium.org> | 2015-03-21 00:29:14 +0000 |
commit | c12181d9af8fbe0b61ac522e813bec463571dda5 (patch) | |
tree | 62e0288eee74af528acbc28f6aa24eacfab2c8b5 /chip | |
parent | 3f061f1864b59cd77d6c6d2fd6fdf8eb57393d51 (diff) | |
download | chrome-ec-c12181d9af8fbe0b61ac522e813bec463571dda5.tar.gz |
cr50: Added PMU driver
- Porting from cosmo code base.
- Support clock initialization
BRANCH=none
BUG=chrome-os-partner:33813
TEST="make buildall -j; Verified on RevA1 Chip"
Change-Id: I59e2bb133968d408acde44a3082e1b3b8f4bbbff
Signed-off-by: Sheng-Liang Song <ssl@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/236394
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
Diffstat (limited to 'chip')
-rw-r--r-- | chip/g/build.mk | 1 | ||||
-rw-r--r-- | chip/g/clock.c | 350 | ||||
-rw-r--r-- | chip/g/pmu.c | 619 | ||||
-rw-r--r-- | chip/g/pmu.h | 113 |
4 files changed, 767 insertions, 316 deletions
diff --git a/chip/g/build.mk b/chip/g/build.mk index 5c5a9a1034..ac626e0381 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -17,4 +17,5 @@ CPPFLAGS+= -DGC_REVISION="$(ver_str)" # Required chip modules chip-y=clock.o gpio.o hwtimer.o jtag.o system.o uart.o +chip-y+= pmu.o chip-$(CONFIG_WATCHDOG)+=watchdog.o diff --git a/chip/g/clock.c b/chip/g/clock.c index 72ed2ca820..098c6a4aa7 100644 --- a/chip/g/clock.c +++ b/chip/g/clock.c @@ -5,327 +5,45 @@ #include "clock.h" #include "registers.h" - -/* Clock initialization taken from some example code */ - -#define RESOLUTION 12 -#define LOAD_VAL (0x1 << 11) -#define MAX_TRIM (7*16) - -static void switch_osc_to_xtl(void); - -static void clock_on_xo0(void) -{ - /* turn on xo0 clock */ - /* don't know which control word it might be in */ -#ifdef GC_PMU_PERICLKSET0_DXO0_MASK - GR_PMU_PERICLKSET0 = GC_PMU_PERICLKSET0_DXO0_MASK; -#endif - -#ifdef GC_PMU_PERICLKSET1_DXO0_MASK - GR_PMU_PERICLKSET1 = GC_PMU_PERICLKSET1_DXO0_MASK; -#endif -} - -/* Converts an integer setting to the RC trim code format (7, 4-bit values) */ -static unsigned val_to_trim_code(unsigned val) -{ - unsigned base = val / 7; - unsigned mod = val % 7; - unsigned code = 0x0; - int digit; - - /* Increasing count from right to left */ - for (digit = 0; digit < 7; digit++) { - /* Check for mod */ - if (digit <= mod) - code |= ((base & 0xF) << 4 * digit); - else - code |= (((base - 1) & 0xF) << 4 * digit); - } - - return code; -} - -static unsigned calib_rc_trim(void) -{ - unsigned size, iter; - signed mid; - signed diff; - - /* Switch to crystal for calibration. This should work since we are - * technically on an uncalibrated RC trim clock. */ - switch_osc_to_xtl(); - - clock_on_xo0(); - - /* Clear the HOLD signal on dxo */ - GR_XO_OSC_CLRHOLD = GC_XO_OSC_CLRHOLD_RC_TRIM_MASK; - - /* Reset RC calibration counters */ - GR_XO_OSC_RC_CAL_RSTB = 0x0; - GR_XO_OSC_RC_CAL_RSTB = 0x1; - - /* Write the LOAD val */ - GR_XO_OSC_RC_CAL_LOAD = LOAD_VAL; - - /* Begin binary search */ - mid = 0; - size = MAX_TRIM / 2; - for (iter = 0; iter <= 7; iter++) { - /* Set the trim value */ - GR_XO_OSC_RC = val_to_trim_code(mid) << GC_XO_OSC_RC_TRIM_LSB; - - /* Do a calibration */ - GR_XO_OSC_RC_CAL_START = 0x1; - - /* NOTE: There is a small race condition because of the delay - * in dregfile. The start doesn't actually appear for 2 clock - * cycles after the write. So, poll until done goes low. */ - while (GR_XO_OSC_RC_CAL_DONE) - ; - - /* Wait until it's done */ - while (!GR_XO_OSC_RC_CAL_DONE) - ; - - /* Check the counter value */ - diff = LOAD_VAL - GR_XO_OSC_RC_CAL_COUNT; - - /* Test to see whether we are still outside of our desired - * resolution */ - if ((diff < -RESOLUTION) || (diff > RESOLUTION)) - mid = (diff > 0) ? (mid - size / 2) : (mid + size / 2); - - size = (size + 1) >> 1; /* round up before division */ - } - - /* Set the final trim value */ - GR_XO_OSC_RC = (val_to_trim_code(mid) << GC_XO_OSC_RC_TRIM_LSB) | - (0x1 << GC_XO_OSC_RC_EN_LSB); - - /* Set the HOLD signal on dxo */ - GR_XO_OSC_SETHOLD = GC_XO_OSC_SETHOLD_RC_TRIM_MASK; - - /* Switch back to the RC trim now that we are calibrated */ - GR_PMU_OSC_HOLD_CLR = 0x1; /* make sure the hold signal is clear */ - GR_PMU_OSC_SELECT = GC_PMU_OSC_SELECT_RC_TRIM; - /* Make sure the hold signal is set for future power downs */ - GR_PMU_OSC_HOLD_SET = 0x1; - - return mid; -} - -static void switch_osc_to_rc_trim(void) -{ - unsigned trimmed; - unsigned saved_trim, fuse_trim, default_trim; - unsigned trim_code; - - /* check which clock we are running on */ - unsigned osc_sel = GR_PMU_OSC_SELECT_STAT; - - if (osc_sel == GC_PMU_OSC_SELECT_RC_TRIM) { - /* already using the rc_trim so nothing to do here */ - /* make sure the hold signal is set for future power downs */ - GR_PMU_OSC_HOLD_SET = 0x1; - return; - } - - /* Turn on DXO clock so we can write in the trim code in */ - clock_on_xo0(); - - /* disable the RC_TRIM Clock */ - REG_WRITE_MLV(GR_PMU_OSC_CTRL, - GC_PMU_OSC_CTRL_RC_TRIM_READYB_MASK, - GC_PMU_OSC_CTRL_RC_TRIM_READYB_LSB, 1); - - /* power up the clock if not already powered up */ - GR_PMU_CLRDIS = 1 << GC_PMU_SETDIS_RC_TRIM_LSB; - - /* Try to find the trim code */ - saved_trim = GR_XO_OSC_RC_STATUS; - fuse_trim = GR_PMU_FUSE_RD_RC_OSC_26MHZ; - default_trim = GR_XO_OSC_RC; - - /* Check for the trim code in the always-on domain before looking at - * the fuse */ - if (saved_trim & GC_XO_OSC_RC_STATUS_EN_MASK) { - trim_code = (saved_trim & GC_XO_OSC_RC_STATUS_TRIM_MASK) - >> GC_XO_OSC_RC_STATUS_TRIM_LSB; - trimmed = 1; - } else if (fuse_trim & GC_PMU_FUSE_RD_RC_OSC_26MHZ_EN_MASK) { - trim_code = (fuse_trim & GC_PMU_FUSE_RD_RC_OSC_26MHZ_TRIM_MASK) - >> GC_PMU_FUSE_RD_RC_OSC_26MHZ_TRIM_LSB; - trimmed = 1; - } else { - trim_code = (default_trim & GC_XO_OSC_RC_TRIM_MASK) - >> GC_XO_OSC_RC_TRIM_LSB; - trimmed = 0; - } - - /* Write the trim code to dxo */ - if (trimmed) { - /* clear the hold signal */ - GR_XO_OSC_CLRHOLD = 1 << GC_XO_OSC_CLRHOLD_RC_TRIM_LSB; - GR_XO_OSC_RC = (trim_code << GC_XO_OSC_RC_TRIM_LSB) | - ((trimmed & 0x1) << GC_XO_OSC_RC_EN_LSB); - /* set the hold signal */ - GR_XO_OSC_SETHOLD = 1 << GC_XO_OSC_SETHOLD_RC_TRIM_LSB; - } - - /* enable the RC_TRIM Clock */ - REG_WRITE_MLV(GR_PMU_OSC_CTRL, GC_PMU_OSC_CTRL_RC_TRIM_READYB_MASK, - GC_PMU_OSC_CTRL_RC_TRIM_READYB_LSB, 0); - - /* Switch the select signal */ - GR_PMU_OSC_HOLD_CLR = 0x1; /* make sure the hold signal is clear */ - GR_PMU_OSC_SELECT = GC_PMU_OSC_SELECT_RC_TRIM; - /* make sure the hold signal is set for future power downs */ - GR_PMU_OSC_HOLD_SET = 0x1; - - /* If we didn't find a valid trim code, then we need to calibrate */ - if (!trimmed) - calib_rc_trim(); - /* We saved the trim code and went back to the RC trim inside - * calib_rc_trim */ -} - -static void switch_osc_to_xtl(void) -{ - unsigned int saved_trim, fuse_trim, trim_code, final_trim; - unsigned int fsm_status, max_trim; - unsigned int fsm_done; - /* check which clock we are running on */ - unsigned int osc_sel = GR_PMU_OSC_SELECT_STAT; - - if (osc_sel == GC_PMU_OSC_SELECT_XTL) { - /* already using the crystal so nothing to do here */ - /* make sure the hold signal is set for future power downs */ - GR_PMU_OSC_HOLD_SET = 0x1; - return; - } - - if (osc_sel == GC_PMU_OSC_SELECT_RC) - /* RC untrimmed clock. We must go through the trimmed clock - * first to avoid glitching */ - switch_osc_to_rc_trim(); - - /* disable the XTL Clock */ - REG_WRITE_MLV(GR_PMU_OSC_CTRL, GC_PMU_OSC_CTRL_XTL_READYB_MASK, - GC_PMU_OSC_CTRL_XTL_READYB_LSB, 1); - - /* power up the clock if not already powered up */ - GR_PMU_CLRDIS = 1 << GC_PMU_SETDIS_XTL_LSB; - - /* Try to find the trim code */ - trim_code = 0; - saved_trim = GR_XO_OSC_XTL_TRIM_STAT; - fuse_trim = GR_PMU_FUSE_RD_XTL_OSC_26MHZ; - - /* Check for the trim code in the always-on domain before looking at - * the fuse */ - if (saved_trim & GC_XO_OSC_XTL_TRIM_STAT_EN_MASK) { - /* nothing to do */ - /* trim_code = (saved_trim & GR_XO_OSC_XTL_TRIM_STAT_CODE_MASK) - >> GR_XO_OSC_XTL_TRIM_STAT_CODE_LSB; */ - /* print_trickbox_message("XTL TRIM CODE FOUND IN 3P3"); */ - } else if (fuse_trim & GC_PMU_FUSE_RD_XTL_OSC_26MHZ_EN_MASK) { - /* push the fuse trim code as the saved trim code */ - /* print_trickbox_message("XTL TRIM CODE FOUND IN FUSE"); */ - trim_code = (fuse_trim & GC_PMU_FUSE_RD_XTL_OSC_26MHZ_TRIM_MASK) - >> GC_PMU_FUSE_RD_XTL_OSC_26MHZ_TRIM_LSB; - /* make sure the hold signal is clear */ - GR_XO_OSC_CLRHOLD = 0x1 << GC_XO_OSC_CLRHOLD_XTL_LSB; - GR_XO_OSC_XTL_TRIM = - (trim_code << GC_XO_OSC_XTL_TRIM_CODE_LSB) | - (0x1 << GC_XO_OSC_XTL_TRIM_EN_LSB); - } else - /* print_trickbox_message("XTL TRIM CODE NOT FOUND"); */ - ; - - /* Run the crystal FSM to calibrate the crystal trim */ - fsm_done = GR_XO_OSC_XTL_FSM; - if (fsm_done & GC_XO_OSC_XTL_FSM_DONE_MASK) { - /* If FSM done is high, it means we already ran it so let's not - * run it again */ - /* DO NOTHING */ - } else { - GR_XO_OSC_XTL_FSM_EN = 0x0; /* reset FSM */ - GR_XO_OSC_XTL_FSM_EN = GC_XO_OSC_XTL_FSM_EN_KEY; - while (!(fsm_done & GC_XO_OSC_XTL_FSM_DONE_MASK)) - fsm_done = GR_XO_OSC_XTL_FSM; - } - - /* Check the status and final trim value */ - max_trim = (GR_XO_OSC_XTL_FSM_CFG & GC_XO_OSC_XTL_FSM_CFG_TRIM_MAX_MASK) - >> GC_XO_OSC_XTL_FSM_CFG_TRIM_MAX_LSB; - final_trim = (fsm_done & GC_XO_OSC_XTL_FSM_TRIM_MASK) - >> GC_XO_OSC_XTL_FSM_TRIM_LSB; - fsm_status = (fsm_done & GC_XO_OSC_XTL_FSM_STATUS_MASK) - >> GC_XO_OSC_XTL_FSM_STATUS_LSB; - - /* Check status bit and trim value */ - if (fsm_status) { - if (final_trim >= max_trim) - /* print_trickbox_error("ERROR: XTL FSM status was - high, but final XTL trim is greater than or equal to - max trim"); */ - ; - } else { - if (final_trim != max_trim) - /* print_trickbox_error("ERROR: XTL FSM status was low, - but final XTL trim does not equal max trim"); */ - ; - } - - /* save the trim for future powerups */ - /* make sure the hold signal is clear (may have already been cleared) */ - GR_XO_OSC_CLRHOLD = 0x1 << GC_XO_OSC_CLRHOLD_XTL_LSB; - GR_XO_OSC_XTL_TRIM = - (final_trim << GC_XO_OSC_XTL_TRIM_CODE_LSB) | - (0x1 << GC_XO_OSC_XTL_TRIM_EN_LSB); - /* make sure the hold signal is set for future power downs */ - GR_XO_OSC_SETHOLD = 0x1 << GC_XO_OSC_SETHOLD_XTL_LSB; - - /* enable the XTL Clock */ - REG_WRITE_MLV(GR_PMU_OSC_CTRL, GC_PMU_OSC_CTRL_XTL_READYB_MASK, - GC_PMU_OSC_CTRL_XTL_READYB_LSB, 0); - - /* Switch the select signal */ - GR_PMU_OSC_HOLD_CLR = 0x1; /* make sure the hold signal is clear */ - GR_PMU_OSC_SELECT = GC_PMU_OSC_SELECT_XTL; - /* make sure the hold signal is set for future power downs */ - GR_PMU_OSC_HOLD_SET = 0x1; -} +#include "pmu.h" void clock_init(void) { - /* - * TODO(crosbug.com/p/33813): The following comment was in the example - * code, but the function that's called doesn't match what it says. - * Investigate further. - */ - - /* Switch to crystal clock since RC clock not accurate enough */ - switch_osc_to_rc_trim(); + pmu_clock_en(PERIPH_PERI0); + pmu_clock_en(PERIPH_TIMEHS0); + pmu_clock_en(PERIPH_TIMEHS1); + pmu_clock_switch_xo(); } void clock_enable_module(enum module_id module, int enable) { - if (module != MODULE_UART) - return; - - /* don't know which control word it might be in */ -#ifdef GC_PMU_PERICLKSET0_DUART0_LSB - REG_WRITE_MLV(GR_PMU_PERICLKSET0, - GC_PMU_PERICLKSET0_DUART0_MASK, - GC_PMU_PERICLKSET0_DUART0_LSB, enable); -#endif - -#ifdef GR_PMU_PERICLKSET1_DUART0_LSB - REG_WRITE_MLV(GR_PMU_PERICLKSET1, - GC_PMU_PERICLKSET1_DUART0_MASK, - GC_PMU_PERICLKSET0_DUART1_LSB, enable); -#endif + pmu_clock_func clock_func; + clock_func = (enable) ? pmu_clock_en : pmu_clock_dis; + + switch (module) { + case MODULE_UART: + clock_func(PERIPH_UART0); + break; + case MODULE_I2C: + clock_func(PERIPH_I2C0); + clock_func(PERIPH_I2C1); + break; + case MODULE_SPI_MASTER: + clock_func(PERIPH_SPI); + break; + case MODULE_SPI: + clock_func(PERIPH_SPS); + break; + case MODULE_USB: + clock_func(PERIPH_USB0); + clock_func(PERIPH_USB0_USB_PHY); + pmu_enable_clock_doubler(); + break; + case MODULE_PMU: + clock_func(PERIPH_PMU); + break; + default: + break; + } + return; } diff --git a/chip/g/pmu.c b/chip/g/pmu.c new file mode 100644 index 0000000000..b2841fe289 --- /dev/null +++ b/chip/g/pmu.c @@ -0,0 +1,619 @@ +/* Copyright (c) 2014 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. + */ + +#include "pmu.h" +#include "task.h" + +/* + * RC Trim constants + */ +#define RCTRIM_RESOLUTION (12) +#define RCTRIM_LOAD_VAL (1 << 11) +#define RCTRIM_RANGE_MAX (7 * 7) +#define RCTRIM_RANGE_MIN (-8 * 7) +#define RCTRIM_RANGE (RCTRIM_RANGE_MAX - RCTRIM_RANGE_MIN + 1) + +/* + * Enable peripheral clock + * @param perih Peripheral from @ref uint32_t + */ +void pmu_clock_en(uint32_t periph) +{ + if (periph <= 31) + GR_PMU_PERICLKSET0 = (1 << periph); + else + GR_PMU_PERICLKSET1 = (1 << (periph - 32)); +} + +/* + * Disable peripheral clock + * @param perih Peripheral from @ref uint32_t + */ +void pmu_clock_dis(uint32_t periph) +{ + if (periph <= 31) + GR_PMU_PERICLKCLR0 = (1 << periph); + else + GR_PMU_PERICLKCLR1 = (1 << (periph - 32)); +} + +/* + * Peripheral reset + * @param periph Peripheral from @ref uint32_t + */ +void pmu_peripheral_rst(uint32_t periph) +{ + /* Reset high */ + if (periph <= 31) + GR_PMU_RST0 = 1 << periph; + else + GR_PMU_RST1 = 1 << (periph - 32); +} + +/* + * Internal helper to convert value to trim code + */ +static uint32_t _pmu_value_to_trim(uint32_t val) +{ + uint32_t base = val / 7; + uint32_t mod = val % 7; + uint32_t code = 0x0; + uint32_t digit; + + /* Increasing count from right to left */ + for (digit = 0; digit < 7; digit++) { + if (digit < mod) + code |= (((base + 1) & 0xF) << (4 * digit)); + else + code |= ((base & 0xF) << (4 * digit)); + } + + return code; +} + +/* + * Run the RC calibration counters + * This must be used otherwise the counters may get stuck + */ +static uint32_t _pmu_run_rc_counters(uint32_t load_val, uint32_t trim_val) +{ + uint32_t trim_code; + + /* Convert value to trim code */ + trim_code = _pmu_value_to_trim(trim_val); + + /* Set the trim value */ + GWRITE_FIELD(XO, OSC_RC, TRIM, trim_code); + + /* Reset counters */ + GR_XO_OSC_RC_CAL_RSTB = 0x0; + GR_XO_OSC_RC_CAL_RSTB = 0x1; + + /* Load */ + GR_XO_OSC_RC_CAL_LOAD = load_val; + + /* Do calibration */ + GR_XO_OSC_RC_CAL_START = 0x1; + + /* + * There is a small race condition because of the delay in dregfile. + * The start doesn't actually appear for 2 clock cycles after the write. + * So, poll until done goes low. + */ + while (GR_XO_OSC_RC_CAL_DONE) + ; + + /* Wait until it's done */ + while (!GR_XO_OSC_RC_CAL_DONE) + ; + + /* Calculate the difference */ + return GR_XO_OSC_RC_CAL_LOAD - GR_XO_OSC_RC_CAL_COUNT; +} + +/* + * Calibrate RC trim + */ +uint32_t pmu_calibrate_rc_trim(void) +{ + uint32_t size, iter; + uint32_t mid; + uint32_t diff; + + /* + * Switch to crystal for calibration + * This should work since we are on an uncalibrated RC trim clock + */ + pmu_clock_switch_xo(); + + /* Clear the HOLD signal on dxo */ + GR_XO_OSC_CLRHOLD = GC_XO_OSC_CLRHOLD_RC_TRIM_MASK; + + /* Clear EN bit while iterating through codes */ + GWRITE_FIELD(XO, OSC_RC, EN, 0); + + /* Begin binary search */ + mid = RCTRIM_RANGE_MAX - (RCTRIM_RANGE / 2); + size = RCTRIM_RANGE / 2; + for (iter = 0; iter < 8; iter++) { + /* Run the counters */ + diff = _pmu_run_rc_counters(RCTRIM_LOAD_VAL, mid); + + /* + * Test to see whether we are still outside of + * our desired resolution + */ + if ((diff < -RCTRIM_RESOLUTION) + || (diff > RCTRIM_RESOLUTION)) { + if (diff > 0) + mid -= size / 2; + else + mid += size / 2; + } + + /* Move to next range, round up */ + size = (size + 1) >> 1; + } + + /* Set the final trim value, set EN bit to lock in the code */ + GR_XO_OSC_RC = (_pmu_value_to_trim(mid) << GC_XO_OSC_RC_TRIM_LSB) + | (0x1 << GC_XO_OSC_RC_EN_LSB); + + /* Set EN bit to lock in this trim_code */ + /* GWRITE_FIELD(XO, OSC_RC, EN, 1); */ + + /* Set the HOLD signal on dxo */ + GR_XO_OSC_SETHOLD = GC_XO_OSC_SETHOLD_RC_TRIM_MASK; + + /* Switch back to the RC trim now that we are calibrated */ + /* pmu_clock_switch_rc_trim(); */ + + return _pmu_value_to_trim(mid); +} + +/* + * Switch system clock to RC no trim + */ +uint32_t pmu_clock_switch_rc_notrim(void) +{ + uint32_t osc_sel; + + /* check which clock we are running on */ + osc_sel = GR_PMU_OSC_SELECT_STAT; + + if (osc_sel == GC_PMU_OSC_SELECT_RC) { + /* Already on untrimmed RC */ + return 0; + } else if (osc_sel == GC_PMU_OSC_SELECT_XTL) { + /* Need to switch to RC trimmed first */ + pmu_clock_switch_rc_trim(1); + } + + /* Turn on XO clock */ + pmu_clock_en(PERIPH_XO); + + /* Power up RC notrim clock if it's currently off */ + GWRITE_FIELD(PMU, CLRDIS, RC_NOTRIM, 1); + + /* Switch to the clock */ + GR_PMU_OSC_HOLD_CLR = 0x1; /* make sure the hold signal is clear */ + GR_PMU_OSC_SELECT = GC_PMU_OSC_SELECT_RC; + /* make sure the hold signal is set for future power downs */ + GR_PMU_OSC_HOLD_SET = 0x1; + + return 0; +} + +/* + * enable clock doubler for USB purposes + */ +void pmu_enable_clock_doubler(void) +{ + /* enable stuff */ + GREG32(XO, OSC_ADC_CAL_FREQ2X) = + (GC_XO_OSC_ADC_CAL_FREQ2X_CNTL_DEFAULT + << GC_XO_OSC_ADC_CAL_FREQ2X_CNTL_LSB) | + (1 << GC_XO_OSC_ADC_CAL_FREQ2X_EN_LSB); + /* enable more stuff */ + GREG32(XO, OSC_CLKOUT) = + (1 << GC_XO_OSC_CLKOUT_ADC_EN_LSB) | + (1 << GC_XO_OSC_CLKOUT_PLL_EN_LSB) | + (1 << GC_XO_OSC_CLKOUT_BADC_EN_LSB) | + (1 << GC_XO_OSC_CLKOUT_USB_EN_LSB); + + /* make sure doubled clock is selected */ + GREG32(XO, OSC_24_48B_SEL) = GC_XO_OSC_24_48B_SEL_DEFAULT; +} + +/* + * Switch system clock to RC trim + */ +uint32_t pmu_clock_switch_rc_trim(uint32_t skip_calibration) +{ + uint32_t trimmed; + uint32_t trim_code; + uint32_t osc_sel; + + /* check which clock we are running on */ + osc_sel = GREG32(PMU, OSC_SELECT_STAT); + + if (osc_sel == GC_PMU_OSC_SELECT_RC_TRIM) { + /* + * already using the rc_trim so nothing to do here + * make sure the hold signal is set for future power downs + */ + GREG32(PMU, OSC_HOLD_SET) = 0x1; + return 0; + } + + /* Turn on DXO clock so we can write in the trim code in */ + pmu_clock_en(PERIPH_XO); + + /* Disable the RC Trim flops in the glitchless switch */ + GWRITE_FIELD(PMU, OSC_CTRL, RC_TRIM_READYB, 0x1); + + /* Power up the clock if not already powered up */ + GREG32(PMU, CLRDIS) = 1 << GC_PMU_SETDIS_RC_TRIM_LSB; + + /* Check for the trim code in the always-on domain + * before looking at the fuse + */ + if (GREAD_FIELD(XO, OSC_RC_STATUS, EN)) { + trim_code = GREAD_FIELD(XO, OSC_RC_STATUS, TRIM); + trimmed = 1; + } else if (GREAD_FIELD(PMU, FUSE_RD_RC_OSC_26MHZ, EN)) { + trim_code = GREAD_FIELD(PMU, FUSE_RD_RC_OSC_26MHZ, TRIM); + trimmed = 1; + } else { + if (skip_calibration) { + trim_code = GREAD_FIELD(XO, OSC_RC, TRIM); + trimmed = 0; + } else { + trim_code = pmu_calibrate_rc_trim(); + trimmed = 1; + } + } + + /* Write the trim code to dxo */ + if (trimmed) { + /* clear the hold signal */ + GREG32(XO, OSC_CLRHOLD) = GC_XO_OSC_CLRHOLD_RC_TRIM_MASK; + + /* Write the trim code and enable the trim code */ + GREG32(XO, OSC_RC) = (trim_code << GC_XO_OSC_RC_TRIM_LSB) | + (1 << GC_XO_OSC_RC_EN_LSB); + + /* set the hold signal */ + GREG32(XO, OSC_SETHOLD) = 1 << GC_XO_OSC_SETHOLD_RC_TRIM_LSB; + } + + /* Enable the flops for RC TRIM in the glitchless switch */ + GWRITE_FIELD(PMU, OSC_CTRL, RC_TRIM_READYB, 0x0); + + /* + * Switch the select signal + * make sure the hold signal is clear + */ + GREG32(PMU, OSC_HOLD_CLR) = 0x1; + GREG32(PMU, OSC_SELECT) = GC_PMU_OSC_SELECT_RC_TRIM; + + /* make sure the hold signal is set for future power downs */ + GREG32(PMU, OSC_HOLD_SET) = 0x1; + + return !trimmed; +} + +/* + * Switch system clock to XO + * @returns The value of XO_OSC_XTL_FSM_STATUS. 0 = okay, 1 = error. + */ +uint32_t pmu_clock_switch_xo(void) +{ + uint32_t osc_sel; + uint32_t trim_code, final_trim, fsm_done, fsm_status; + + /* check which clock we are running on */ + osc_sel = GREG32(PMU, OSC_SELECT_STAT); + + if (osc_sel == GC_PMU_OSC_SELECT_XTL) { + /* + * already using the crystal so nothing to do here + * make sure the hold signal is set for future power downs + */ + GREG32(PMU, OSC_HOLD_SET) = 0x1; + return 0; + } else if (osc_sel == GC_PMU_OSC_SELECT_RC) { + /* + * RC untrimmed clock. We must go through + * the trimmed clock first to avoid glitching + */ + pmu_clock_switch_rc_trim(1); + } + + /* Turn on DXO clock so we can write in the trim code in */ + pmu_clock_en(PERIPH_XO); + + /* Disable the XTL Clock */ + GWRITE_FIELD(PMU, OSC_CTRL, XTL_READYB, 0x1); + + /* Power up the clock if not already powered up */ + GREG32(PMU, CLRDIS) = 1 << GC_PMU_CLRDIS_XTL_LSB; + + /* Try to find the trim code */ + trim_code = 0; + + /* + * Check for the trim code in the always-on domain + * before looking at the fuse + */ + if (GREAD_FIELD(XO, OSC_XTL_TRIM_STAT, EN)) { + /* nothing to do */ + trim_code = GREAD_FIELD(XO, OSC_XTL_TRIM_STAT, CODE); + + } else if (GREAD_FIELD(PMU, FUSE_RD_XTL_OSC_26MHZ, EN)) { + + /* push the fuse trim code as the saved trim code */ + trim_code = GREAD_FIELD(PMU, FUSE_RD_XTL_OSC_26MHZ, TRIM); + + /* make sure the hold signal is clear */ + GREG32(XO, OSC_CLRHOLD) = 1 << GC_XO_OSC_CLRHOLD_XTL_LSB; + GREG32(XO, OSC_XTL_TRIM) = + (trim_code << GC_XO_OSC_XTL_TRIM_CODE_LSB) + | (0x1 << GC_XO_OSC_XTL_TRIM_EN_LSB); + } else { + /* Run the crystal FSM to calibrate the crystal trim */ + fsm_done = GREG32(XO, OSC_XTL_FSM); + if (fsm_done & GC_XO_OSC_XTL_FSM_DONE_MASK) { + /* + * If FSM done is high, it means we already ran it + * so let's not run it again + * DO NOTHING + */ + } else { + /* reset FSM */ + GREG32(XO, OSC_XTL_FSM_EN) = 0x0; + GREG32(XO, OSC_XTL_FSM_EN) = GC_XO_OSC_XTL_FSM_EN_KEY; + while (!(fsm_done & GC_XO_OSC_XTL_FSM_DONE_MASK)) + fsm_done = GREG32(XO, OSC_XTL_FSM); + } + } + + /* Check the status and final trim value */ + /* max_trim = GREAD_FIELD(XO, OSC_XTL_FSM_CFG, TRIM_MAX); */ + final_trim = GREAD_FIELD(XO, OSC_XTL_FSM, TRIM); + fsm_status = GREAD_FIELD(XO, OSC_XTL_FSM, STATUS); + + /* + * Save the trim for future powerups + * make sure the hold signal is clear (may have already been cleared) + */ + GREG32(XO, OSC_CLRHOLD) = 1 << GC_XO_OSC_CLRHOLD_XTL_LSB; + GREG32(XO, OSC_XTL_TRIM) = (final_trim << GC_XO_OSC_XTL_TRIM_CODE_LSB) | + (1 << GC_XO_OSC_XTL_TRIM_EN_LSB); + + /* make sure the hold signal is set for future power downs */ + GREG32(XO, OSC_SETHOLD) = 1 << GC_XO_OSC_SETHOLD_XTL_LSB; + + /* Enable the flops for XTL in the glitchless switch */ + GWRITE_FIELD(PMU, OSC_CTRL, XTL_READYB, 0x0); + + /* + * Switch the select signal + * make sure the hold signal is clear + */ + GREG32(PMU, OSC_HOLD_CLR) = 0x1; + GREG32(PMU, OSC_SELECT) = GC_PMU_OSC_SELECT_XTL; + + /* make sure the hold signal is set for future power downs */ + GREG32(PMU, OSC_HOLD_SET) = 0x1; + + return !fsm_status; +} + +/* + * Enter sleep mode and handle exiting from sleep mode + * @warning The CPU must be in RC no trim mode before calling this function + */ +void pmu_sleep(void) +{ + uint32_t val; + + /* Enable PMU sleep interrupts */ + GREG32(PMU, ICTRL) = 1 << GC_PMU_ICTRL_SLEEP_LSB; + + /* nvic_irq_en(GC_IRQNUM_PMU_PMUINT); */ + + /* Enable CPU SLEEPDEEP */ + val = GREG32(M3, SCR); + GREG32(M3, SCR) = val | 0x4; + + /* Enable WIC mode */ + GREG32(PMU, SETWIC) = 1 << GC_PMU_SETWIC_PROC0_LSB; + + /* Disable power domains for entering sleep mode */ + GREG32(PMU, SETDIS) = (1 << GC_PMU_SETDIS_START_LSB) | + (1 << GC_PMU_SETDIS_VDDL_LSB) | + (1 << GC_PMU_SETDIS_VDDA_LSB) | + (1 << GC_PMU_SETDIS_VDDSRM_LSB) | + (1 << GC_PMU_SETDIS_BGAP_LSB) | + (1 << GC_PMU_SETDIS_VDDXO_LSB) | + (1 << GC_PMU_SETDIS_VDDXOLP_LSB) | + (1 << GC_PMU_SETDIS_XTL_LSB) | + (1 << GC_PMU_SETDIS_RC_TRIM_LSB) | + (1 << GC_PMU_SETDIS_RC_NOTRIM_LSB) | + (1 << GC_PMU_SETDIS_BATMON_LSB) | + (1 << GC_PMU_SETDIS_FST_BRNOUT_PWR_LSB) | + (1 << GC_PMU_SETDIS_FST_BRNOUT_LSB); + + /* Wait for exit interrupt + * @todo Add code to disable all non-PMU interrupts. + */ + __asm__("wfi"); + + /* Disable WIC mode */ + GREG32(PMU, CLRWIC) = 1 << GC_PMU_CLRWIC_PROC0_LSB; + + /* Disable CPU SLEEPDEEP */ + val = GREG32(M3, SCR); + GREG32(M3, SCR) = val & (~0x4); + + /* Re-enable power domains */ + GREG32(PMU, CLRDIS) = (1 << GC_PMU_CLRDIS_START_LSB) | + (1 << GC_PMU_CLRDIS_VDDL_LSB) | + (1 << GC_PMU_CLRDIS_VDDA_LSB) | + (1 << GC_PMU_CLRDIS_VDDSRM_LSB) | + (1 << GC_PMU_CLRDIS_VDDIOF_LSB) | + (1 << GC_PMU_CLRDIS_BGAP_LSB) | + (1 << GC_PMU_CLRDIS_VDDXO_LSB); + +#ifdef __FIX_ME__ + GREG32(PMU, CLRDIS) = (1 << GC_PMU_CLRDIS_START_LSB) | + (1 << GC_PMU_CLRDIS_VDDL_LSB) | + (1 << GC_PMU_CLRDIS_VDDA_LSB) | + (1 << GC_PMU_CLRDIS_VDDSRM_LSB) | + (1 << GC_PMU_CLRDIS_BGAP_LSB) | + (1 << GC_PMU_CLRDIS_VDDXO_LSB) | + (1 << GC_PMU_CLRDIS_VDDXOLP_LSB) | + (1 << GC_PMU_CLRDIS_XTL_LSB) | + (1 << GC_PMU_CLRDIS_RC_TRIM_LSB) | + (1 << GC_PMU_CLRDIS_RC_NOTRIM_LSB) | + (1 << GC_PMU_CLRDIS_BATMON_LSB) | + (1 << GC_PMU_CLRDIS_FST_BRNOUT_PWR_LSB) | + (1 << GC_PMU_CLRDIS_FST_BRNOUT_LSB); +#endif +} + +/* + * Enter hibernate mode + * This function does not return. The powerdown exit event will + * cause the CPU to begin executing the system / app bootloader. + * @warning The CPU must be in RC no trim mode + */ +void pmu_hibernate(void) +{ + /* Turn off power to everything except retention domains */ + GREG32(PMU, SETDIS) = (1 << GC_PMU_SETDIS_START_LSB) | + (1 << GC_PMU_SETDIS_VDDL_LSB) | + (1 << GC_PMU_SETDIS_VDDA_LSB) | + (1 << GC_PMU_SETDIS_VDDSRM_LSB) | + (1 << GC_PMU_SETDIS_VDDIOF_LSB) | + (1 << GC_PMU_SETDIS_VDDLK_LSB) | + (1 << GC_PMU_SETDIS_VDDSK_LSB) | + (1 << GC_PMU_SETDIS_BIAS_LSB) | + (1 << GC_PMU_SETDIS_BGAP_LSB) | + (1 << GC_PMU_SETDIS_VDDXO_LSB) | + (1 << GC_PMU_SETDIS_VDDXOLP_LSB) | + (1 << GC_PMU_SETDIS_XTL_LSB) | + (1 << GC_PMU_SETDIS_RC_TRIM_LSB) | + (1 << GC_PMU_SETDIS_RC_NOTRIM_LSB) | + (1 << GC_PMU_SETDIS_BATMON_LSB) | + (1 << GC_PMU_SETDIS_FST_BRNOUT_PWR_LSB) | + (1 << GC_PMU_SETDIS_FST_BRNOUT_LSB); + + /* Wait for powerdown */ + for (;;) + __asm__("wfi"); +} + +/* + * Exit hibernate mode + * This function should be called after a powerdown exit event. + * It handles turning the power domains back on. + * Clocks will be left in RC no trim. + */ +void pmu_hibernate_exit(void) +{ + /* Turn on power to everything */ + GREG32(PMU, CLRDIS) = (1 << GC_PMU_CLRDIS_START_LSB) | + (1 << GC_PMU_CLRDIS_VDDL_LSB) | + (1 << GC_PMU_CLRDIS_VDDA_LSB) | + (1 << GC_PMU_CLRDIS_VDDSRM_LSB) | + (1 << GC_PMU_CLRDIS_VDDIOF_LSB) | + (1 << GC_PMU_CLRDIS_VDDLK_LSB) | + (1 << GC_PMU_CLRDIS_VDDSK_LSB) | + (1 << GC_PMU_CLRDIS_BIAS_LSB) | + (1 << GC_PMU_CLRDIS_BGAP_LSB) | + (1 << GC_PMU_CLRDIS_VDDXO_LSB) | + (1 << GC_PMU_CLRDIS_VDDXOLP_LSB) | + (1 << GC_PMU_CLRDIS_XTL_LSB) | + (1 << GC_PMU_CLRDIS_RC_TRIM_LSB) | + (1 << GC_PMU_CLRDIS_RC_NOTRIM_LSB) | + (1 << GC_PMU_CLRDIS_BATMON_LSB) | + (1 << GC_PMU_CLRDIS_FST_BRNOUT_PWR_LSB) | + (1 << GC_PMU_CLRDIS_FST_BRNOUT_LSB); +} + +/* + * Enter powerdown mode + * This function does not return. The powerdown exit event will + * cause the CPU to begin executing the system / app bootloader. + * @warning The CPU must be in RC no trim mode + */ +void pmu_powerdown(void) +{ + /* Turn off power to everything */ + GREG32(PMU, SETDIS) = (1 << GC_PMU_SETDIS_START_LSB) | + (1 << GC_PMU_SETDIS_VDDL_LSB) | + (1 << GC_PMU_SETDIS_VDDA_LSB) | + (1 << GC_PMU_SETDIS_VDDSRM_LSB) | + (1 << GC_PMU_SETDIS_VDDIOF_LSB) | + (1 << GC_PMU_SETDIS_VDDLK_LSB) | + (1 << GC_PMU_SETDIS_VDDSK_LSB) | + (1 << GC_PMU_SETDIS_VDDSRK_LSB) | + (1 << GC_PMU_SETDIS_RETCOMPREF_LSB) | + (1 << GC_PMU_SETDIS_BIAS_LSB) | + (1 << GC_PMU_SETDIS_BGAP_LSB) | + (1 << GC_PMU_SETDIS_VDDXO_LSB) | + (1 << GC_PMU_SETDIS_VDDXOLP_LSB) | + (1 << GC_PMU_SETDIS_XTL_LSB) | + (1 << GC_PMU_SETDIS_RC_TRIM_LSB) | + (1 << GC_PMU_SETDIS_RC_NOTRIM_LSB) | + (1 << GC_PMU_SETDIS_BATMON_LSB) | + (1 << GC_PMU_SETDIS_FST_BRNOUT_PWR_LSB) | + (1 << GC_PMU_SETDIS_FST_BRNOUT_LSB); + + /* Wait for powerdown */ + for (;;) + __asm__("wfi"); +} + +/* + * Exit powerdown mode + * This function should be called after a powerdown exit event. + * It handles turning the power domains back on. + * Clocks will be left in RC no trim. + */ +void pmu_powerdown_exit(void) +{ + /* Turn on power to everything */ + GREG32(PMU, CLRDIS) = (1 << GC_PMU_CLRDIS_START_LSB) | + (1 << GC_PMU_CLRDIS_VDDL_LSB) | + (1 << GC_PMU_CLRDIS_VDDA_LSB) | + (1 << GC_PMU_CLRDIS_VDDSRM_LSB) | + (1 << GC_PMU_CLRDIS_VDDIOF_LSB) | + (1 << GC_PMU_CLRDIS_VDDLK_LSB) | + (1 << GC_PMU_CLRDIS_VDDSK_LSB) | + (1 << GC_PMU_CLRDIS_VDDSRK_LSB) | + (1 << GC_PMU_CLRDIS_RETCOMPREF_LSB) | + (1 << GC_PMU_CLRDIS_BIAS_LSB) | + (1 << GC_PMU_CLRDIS_BGAP_LSB) | + (1 << GC_PMU_CLRDIS_VDDXO_LSB) | + (1 << GC_PMU_CLRDIS_VDDXOLP_LSB) | + (1 << GC_PMU_CLRDIS_XTL_LSB) | + (1 << GC_PMU_CLRDIS_RC_TRIM_LSB) | + (1 << GC_PMU_CLRDIS_RC_NOTRIM_LSB) | + (1 << GC_PMU_CLRDIS_BATMON_LSB) | + (1 << GC_PMU_CLRDIS_FST_BRNOUT_PWR_LSB) | + (1 << GC_PMU_CLRDIS_FST_BRNOUT_LSB); +} + +/** + * Handle PMU interrupt + */ +void pmu_interrupt(void) +{ + /* TBD */ +} +DECLARE_IRQ(GC_IRQNUM_PMU_PMUINT, pmu_interrupt, 1); diff --git a/chip/g/pmu.h b/chip/g/pmu.h new file mode 100644 index 0000000000..7411735850 --- /dev/null +++ b/chip/g/pmu.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2014 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. + */ + +#ifndef INC_PMU_H_ +#define INC_PMU_H_ + +#include "common.h" +#include "registers.h" + +enum { + PERIPH_AES = 0x0, + PERIPH_AES0 = 0x0, + PERIPH_AES1 = 0x1, + + PERIPH_CAMO = 0x2, + PERIPH_CAMO0 = 0x2, + + PERIPH_FLASH = 0x3, + PERIPH_FLASH0 = 0x3, + + GLOBALSEC = 0x4, + GLOBALSEC0 = 0x4, + + PERIPH_GPIO = 0x5, + PERIPH_GPIO0 = 0x5, + PERIPH_GPIO1 = 0x6, + + PERIPH_I2C = 0x7, + PERIPH_I2C0 = 0x7, + PERIPH_I2C1 = 0x8, + + PERIPH_I2CS = 0x9, + PERIPH_I2CS0 = 0x9, + + PERIPH_MAU = 0xa, + PERIPH_PAU = 0xb, + PERIPH_PINMUX = 0xc, + PERIPH_PMU = 0xd, + + PERIPH_RBOX = 0xe, + PERIPH_RBOX0 = 0xe, + + PERIPH_RTC = 0xf, + PERIPH_RTC0 = 0xf, + + PERIPH_SHA = 0x10, + PERIPH_SHA0 = 0x10, + + PERIPH_SPI = 0x11, + PERIPH_SPI0 = 0x11, + + PERIPH_SPS = 0x12, + PERIPH_SPS0 = 0x12, + + PERIPH_SWDP = 0x13, + PERIPH_SWDP0 = 0x13, + + PERIPH_TEMP = 0x14, + PERIPH_TEMP0 = 0x14, + + PERIPH_TIMEHS = 0x15, + PERIPH_TIMEHS0 = 0x15, + PERIPH_TIMEHS1 = 0x16, + + PERIPH_TIMELS = 0x17, + PERIPH_TIMELS0 = 0x17, + + PERIPH_TRNG = 0x18, + PERIPH_TRNG0 = 0x18, + + PERIPH_UART = 0x19, + PERIPH_UART0 = 0x19, + PERIPH_UART1 = 0x1a, + PERIPH_UART2 = 0x1b, + + PERIPH_USB = 0x1c, + PERIPH_USB0 = 0x1c, + PERIPH_USB0_USB_PHY = 0x1d, + + PERIPH_WATCHDOG = 0x1e, + PERIPH_WATCHDOG0 = 0x1e, + + PERIPH_XO = 0x1f, + PERIPH_XO0 = 0x1f, + + PERIPH_PERI = 0x20, + PERIPH_PERI0 = 0x20, + PERIPH_PERI1 = 0x21, + + PERIPH_PERI_MATRIX = 0x22, +}; + +typedef void (*pmu_clock_func)(uint32_t periph); +extern void pmu_clock_en(uint32_t periph); +extern void pmu_clock_dis(uint32_t periph); +extern void pmu_peripheral_rst(uint32_t periph); +extern uint32_t pmu_calibrate_rc_trim(void); +extern uint32_t pmu_clock_switch_rc_notrim(void); +extern uint32_t pmu_clock_switch_rc_trim(uint32_t skip_calibration); +extern uint32_t pmu_clock_switch_xo(void); +extern void pmu_sleep(void); +extern void pmu_hibernate(void); +extern void pmu_hibernate_exit(void); +extern void pmu_powerdown(void); +extern void pmu_powerdown_exit(void); + +/* + * enable clock doubler for USB purposes + */ +void pmu_enable_clock_doubler(void); +#endif /* INC_PMU_H_ */ |