/* Copyright 2012 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. */ /* System module for Chrome EC : LM4 hardware specific implementation */ #include "clock.h" #include "common.h" #include "console.h" #include "cpu.h" #include "host_command.h" #include "panic.h" #include "registers.h" #include "system.h" #include "task.h" #include "timer.h" #include "util.h" /* Indices for hibernate data registers */ enum hibdata_index { HIBDATA_INDEX_SCRATCHPAD, /* General-purpose scratchpad */ HIBDATA_INDEX_WAKE, /* Wake reasons for hibernate */ HIBDATA_INDEX_SAVED_RESET_FLAGS, /* Saved reset flags */ #ifdef CONFIG_SOFTWARE_PANIC HIBDATA_INDEX_SAVED_PANIC_REASON, /* Saved panic reason */ HIBDATA_INDEX_SAVED_PANIC_INFO, /* Saved panic data */ HIBDATA_INDEX_SAVED_PANIC_EXCEPTION,/* Saved panic exception code */ HIBDATA_INDEX_SAVED_PANIC_FLAGS, /* Saved panic flags */ #endif }; /* Flags for HIBDATA_INDEX_WAKE */ #define HIBDATA_WAKE_RTC BIT(0) /* RTC alarm */ #define HIBDATA_WAKE_HARD_RESET BIT(1) /* Hard reset via short RTC alarm */ #define HIBDATA_WAKE_PIN BIT(2) /* Wake pin */ /* * Time to hibernate to trigger a power-on reset. 50 ms is sufficient for the * EC itself, but we need a longer delay to ensure the rest of the components * on the same power rail are reset and 5VALW has dropped. */ #define HIB_RESET_USEC 1000000 /* * Convert between microseconds and the hibernation module RTC subsecond * register which has 15-bit resolution. Divide down both numerator and * denominator to avoid integer overflow while keeping the math accurate. */ #define HIB_RTC_USEC_TO_SUBSEC(us) ((us) * (32768/64) / (1000000/64)) #define HIB_RTC_SUBSEC_TO_USEC(ss) ((ss) * (1000000/64) / (32768/64)) /** * Wait for a write to commit to a hibernate register. * * @return EC_SUCCESS if successful, non-zero if error. */ static int wait_for_hibctl_wc(void) { int i; /* Wait for write-capable */ for (i = 0; i < 1000000; i++) { if (LM4_HIBERNATE_HIBCTL & LM4_HIBCTL_WRC) return EC_SUCCESS; } return EC_ERROR_TIMEOUT; } /** * Read hibernate register at specified index. * * @return The value of the register or 0 if invalid index. */ static uint32_t hibdata_read(enum hibdata_index index) { if (index < 0 || index >= LM4_HIBERNATE_HIBDATA_ENTRIES) return 0; return LM4_HIBERNATE_HIBDATA[index]; } /** * Write hibernate register at specified index. * * @return nonzero if error. */ static int hibdata_write(enum hibdata_index index, uint32_t value) { int rv; if (index < 0 || index >= LM4_HIBERNATE_HIBDATA_ENTRIES) return EC_ERROR_INVAL; /* Wait for ok-to-write */ rv = wait_for_hibctl_wc(); if (rv != EC_SUCCESS) return rv; /* Write register */ LM4_HIBERNATE_HIBDATA[index] = value; /* Wait for write-complete */ return wait_for_hibctl_wc(); } uint32_t chip_read_reset_flags(void) { return hibdata_read(HIBDATA_INDEX_SAVED_RESET_FLAGS); } void chip_save_reset_flags(uint32_t flags) { hibdata_write(HIBDATA_INDEX_SAVED_RESET_FLAGS, flags); } static void check_reset_cause(void) { uint32_t hib_status = LM4_HIBERNATE_HIBRIS; uint32_t raw_reset_cause = LM4_SYSTEM_RESC; uint32_t hib_wake_flags = hibdata_read(HIBDATA_INDEX_WAKE); uint32_t flags = 0; /* Clear the reset causes now that we've read them */ LM4_SYSTEM_RESC = 0; wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIC = hib_status; hibdata_write(HIBDATA_INDEX_WAKE, 0); if (raw_reset_cause & 0x02) { /* * Full power-on reset of chip. This resets the flash * protection registers to their permanently-stored values. * Note that this is also triggered by hibernation, because * that de-powers the chip. */ flags |= EC_RESET_FLAG_POWER_ON; } else if (!flags && (raw_reset_cause & 0x01)) { /* * LM4 signals the reset pin in RESC for all power-on resets, * even though the external pin wasn't asserted. Make setting * this flag mutually-exclusive with power on flag, so we can * use it to indicate a keyboard-triggered reset. */ flags |= EC_RESET_FLAG_RESET_PIN; } if (raw_reset_cause & 0x04) flags |= EC_RESET_FLAG_BROWNOUT; if (raw_reset_cause & 0x10) flags |= EC_RESET_FLAG_SOFT; if (raw_reset_cause & 0x28) { /* Watchdog timer 0 or 1 */ flags |= EC_RESET_FLAG_WATCHDOG; } /* Handle other raw reset causes */ if (raw_reset_cause && !flags) flags |= EC_RESET_FLAG_OTHER; if ((hib_status & 0x09) && (hib_wake_flags & HIBDATA_WAKE_HARD_RESET)) { /* Hibernation caused by software-triggered hard reset */ flags |= EC_RESET_FLAG_HARD; /* Consume the hibernate reasons so we don't see them below */ hib_status &= ~0x09; } if ((hib_status & 0x01) && (hib_wake_flags & HIBDATA_WAKE_RTC)) flags |= EC_RESET_FLAG_RTC_ALARM; if ((hib_status & 0x08) && (hib_wake_flags & HIBDATA_WAKE_PIN)) flags |= EC_RESET_FLAG_WAKE_PIN; if (hib_status & 0x04) flags |= EC_RESET_FLAG_LOW_BATTERY; /* Restore then clear saved reset flags */ flags |= chip_read_reset_flags(); chip_save_reset_flags(0); system_set_reset_flags(flags); } /* * A3 and earlier chip stepping has a problem accessing flash during shutdown. * To work around that, we jump to RAM before hibernating. This function must * live in RAM. It must be called with interrupts disabled, cannot call other * functions, and can't be declared static (or else the compiler optimizes it * into the main hibernate function. */ void __attribute__((noinline)) __attribute__((section(".iram.text"))) __enter_hibernate(int hibctl) { LM4_HIBERNATE_HIBCTL = hibctl; while (1) ; } /** * Read the real-time clock. * * @param ss_ptr Destination for sub-seconds value, if not null. * * @return the real-time clock seconds value. */ uint32_t system_get_rtc_sec_subsec(uint32_t *ss_ptr) { uint32_t rtc, rtc2; uint32_t rtcss, rtcss2; /* * The hibernate module isn't synchronized, so need to read repeatedly * to guarantee a valid read. */ do { rtc = LM4_HIBERNATE_HIBRTCC; rtcss = LM4_HIBERNATE_HIBRTCSS & 0x7fff; rtcss2 = LM4_HIBERNATE_HIBRTCSS & 0x7fff; rtc2 = LM4_HIBERNATE_HIBRTCC; } while (rtc != rtc2 || rtcss != rtcss2); if (ss_ptr) *ss_ptr = rtcss; return rtc; } timestamp_t system_get_rtc(void) { uint32_t rtc, rtc_ss; timestamp_t time; rtc = system_get_rtc_sec_subsec(&rtc_ss); time.val = ((uint64_t)rtc) * SECOND + HIB_RTC_SUBSEC_TO_USEC(rtc_ss); return time; } /** * Set the real-time clock. * * @param seconds New clock value. */ void system_set_rtc(uint32_t seconds) { wait_for_hibctl_wc(); LM4_HIBERNATE_HIBRTCLD = seconds; wait_for_hibctl_wc(); } /** * Set the hibernate RTC match time at a given time from now * * @param seconds Number of seconds from now for RTC match * @param microseconds Number of microseconds from now for RTC match */ static void set_hibernate_rtc_match_time(uint32_t seconds, uint32_t microseconds) { uint32_t rtc, rtcss; /* * Make sure that the requested delay is not less then the * amount of time it takes to set the RTC match registers, * otherwise, the match event could be missed. */ if (seconds == 0 && microseconds < HIB_SET_RTC_MATCH_DELAY_USEC) microseconds = HIB_SET_RTC_MATCH_DELAY_USEC; /* Calculate the wake match */ rtc = system_get_rtc_sec_subsec(&rtcss) + seconds; rtcss += HIB_RTC_USEC_TO_SUBSEC(microseconds); if (rtcss > 0x7fff) { rtc += rtcss >> 15; rtcss &= 0x7fff; } /* Set RTC alarm match */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBRTCM0 = rtc; wait_for_hibctl_wc(); LM4_HIBERNATE_HIBRTCSS = rtcss << 16; wait_for_hibctl_wc(); } /** * Use hibernate module to set up an RTC interrupt at a given * time from now * * @param seconds Number of seconds before RTC interrupt * @param microseconds Number of microseconds before RTC interrupt */ void system_set_rtc_alarm(uint32_t seconds, uint32_t microseconds) { /* Clear pending interrupt */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIC = LM4_HIBERNATE_HIBRIS; /* Set match time */ set_hibernate_rtc_match_time(seconds, microseconds); /* Enable RTC interrupt on match */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIM = 1; /* * Wait for the write to commit. This ensures that the RTC interrupt * actually gets enabled. This is important if we're about to switch * the system to the 30 kHz oscillator, which might prevent the write * from committing. */ wait_for_hibctl_wc(); } /** * Disable and clear the RTC interrupt. */ void system_reset_rtc_alarm(void) { /* Disable hibernate interrupts */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIM = 0; /* Clear interrupts */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIC = LM4_HIBERNATE_HIBRIS; } /** * Hibernate module interrupt */ void __hibernate_irq(void) { system_reset_rtc_alarm(); } DECLARE_IRQ(LM4_IRQ_HIBERNATE, __hibernate_irq, 1); /** * Enable hibernate interrupt */ void system_enable_hib_interrupt(void) { task_enable_irq(LM4_IRQ_HIBERNATE); } /** * Internal hibernate function. * * @param seconds Number of seconds to sleep before RTC alarm * @param microseconds Number of microseconds to sleep before RTC alarm * @param flags Additional hibernate wake flags */ static void hibernate(uint32_t seconds, uint32_t microseconds, uint32_t flags) { uint32_t hibctl; /* Set up wake reasons and hibernate flags */ hibctl = LM4_HIBERNATE_HIBCTL | LM4_HIBCTL_PINWEN; if (flags & HIBDATA_WAKE_PIN) hibctl |= LM4_HIBCTL_PINWEN; else hibctl &= ~LM4_HIBCTL_PINWEN; if (seconds || microseconds) { hibctl |= LM4_HIBCTL_RTCWEN; flags |= HIBDATA_WAKE_RTC; set_hibernate_rtc_match_time(seconds, microseconds); /* Enable RTC interrupt on match */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIM = 1; } else { hibctl &= ~LM4_HIBCTL_RTCWEN; } wait_for_hibctl_wc(); LM4_HIBERNATE_HIBCTL = hibctl; /* Clear pending interrupt */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIC = LM4_HIBERNATE_HIBRIS; /* Store hibernate flags */ hibdata_write(HIBDATA_INDEX_WAKE, flags); __enter_hibernate(hibctl | LM4_HIBCTL_HIBREQ); } void system_hibernate(uint32_t seconds, uint32_t microseconds) { /* Flush console before hibernating */ cflush(); hibernate(seconds, microseconds, HIBDATA_WAKE_PIN); } void chip_pre_init(void) { /* Enable clocks to GPIO block C in run and sleep modes. */ clock_enable_peripheral(CGC_OFFSET_GPIO, 0x0004, CGC_MODE_ALL); /* * Ensure PC0:3 are set to JTAG function. They should be set this way * on a cold boot, but on a warm reboot a previous misbehaving image * could have set them differently. */ if (((LM4_GPIO_PCTL(LM4_GPIO_C) & 0x0000ffff) == 0x00001111) && ((LM4_GPIO_AFSEL(LM4_GPIO_C) & 0x0f) == 0x0f) && ((LM4_GPIO_DEN(LM4_GPIO_C) & 0x0f) == 0x0f) && ((LM4_GPIO_PUR(LM4_GPIO_C) & 0x0f) == 0x0f)) return; /* Already properly configured */ /* Unlock commit register for JTAG pins */ LM4_GPIO_LOCK(LM4_GPIO_C) = LM4_GPIO_LOCK_UNLOCK; LM4_GPIO_CR(LM4_GPIO_C) |= 0x0f; /* Reset JTAG pins */ LM4_GPIO_PCTL(LM4_GPIO_C) = (LM4_GPIO_PCTL(LM4_GPIO_C) & 0xffff0000) | 0x00001111; LM4_GPIO_AFSEL(LM4_GPIO_C) |= 0x0f; LM4_GPIO_DEN(LM4_GPIO_C) |= 0x0f; LM4_GPIO_PUR(LM4_GPIO_C) |= 0x0f; /* Set interrupt on either edge of the JTAG signals */ LM4_GPIO_IS(LM4_GPIO_C) &= ~0x0f; LM4_GPIO_IBE(LM4_GPIO_C) |= 0x0f; /* Re-lock commit register */ LM4_GPIO_CR(LM4_GPIO_C) &= ~0x0f; LM4_GPIO_LOCK(LM4_GPIO_C) = 0; } void system_pre_init(void) { uint32_t hibctl; #ifdef CONFIG_SOFTWARE_PANIC uint32_t reason, info; uint8_t exception, panic_flags; #endif /* * Enable clocks to the hibernation module in run, sleep, * and deep sleep modes. */ clock_enable_peripheral(CGC_OFFSET_HIB, 0x1, CGC_MODE_ALL); /* * Enable the hibernation oscillator, if it's not already enabled. * This should only need setting if the EC completely lost power (for * example, the battery was pulled). */ if (!(LM4_HIBERNATE_HIBCTL & LM4_HIBCTL_CLK32EN)) { int i; /* Enable clock to hibernate module */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBCTL |= LM4_HIBCTL_CLK32EN; /* Wait for write-complete */ for (i = 0; i < 1000000; i++) { if (LM4_HIBERNATE_HIBRIS & 0x10) break; } /* Enable and reset RTC */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBCTL |= LM4_HIBCTL_RTCEN; system_set_rtc(0); /* Clear all hibernate data entries */ for (i = 0; i < LM4_HIBERNATE_HIBDATA_ENTRIES; i++) hibdata_write(i, 0); } /* * Set wake reasons to RTC match and WAKE pin by default. * Before going in to hibernate, these may change. */ hibctl = LM4_HIBERNATE_HIBCTL; hibctl |= LM4_HIBCTL_RTCWEN; hibctl |= LM4_HIBCTL_PINWEN; wait_for_hibctl_wc(); LM4_HIBERNATE_HIBCTL = hibctl; /* * Initialize registers after reset to work around LM4 chip errata * (still present in A3 chip stepping). */ wait_for_hibctl_wc(); LM4_HIBERNATE_HIBRTCT = 0x7fff; wait_for_hibctl_wc(); LM4_HIBERNATE_HIBIM = 0; check_reset_cause(); #ifdef CONFIG_SOFTWARE_PANIC /* Restore then clear saved panic reason */ reason = hibdata_read(HIBDATA_INDEX_SAVED_PANIC_REASON); info = hibdata_read(HIBDATA_INDEX_SAVED_PANIC_INFO); exception = hibdata_read(HIBDATA_INDEX_SAVED_PANIC_EXCEPTION); panic_flags = hibdata_read(HIBDATA_INDEX_SAVED_PANIC_FLAGS); if (reason || info || exception) { panic_set_reason(reason, info, exception); panic_get_data()->flags = panic_flags; hibdata_write(HIBDATA_INDEX_SAVED_PANIC_REASON, 0); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_INFO, 0); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_EXCEPTION, 0); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_FLAGS, 0); } #endif /* Initialize bootcfg if needed */ if (LM4_SYSTEM_BOOTCFG != CONFIG_BOOTCFG_VALUE) { /* read-modify-write */ LM4_FLASH_FMD = (LM4_SYSTEM_BOOTCFG_MASK & LM4_SYSTEM_BOOTCFG) | (~LM4_SYSTEM_BOOTCFG_MASK & CONFIG_BOOTCFG_VALUE); LM4_FLASH_FMA = 0x75100000; LM4_FLASH_FMC = 0xa4420008; /* WRKEY | COMT */ while (LM4_FLASH_FMC & 0x08) ; } /* Brown-outs should trigger a reset */ LM4_SYSTEM_PBORCTL |= 0x02; } void system_reset(int flags) { uint32_t save_flags = 0; /* Disable interrupts to avoid task swaps during reboot */ interrupt_disable(); /* Save current reset reasons if necessary */ if (flags & SYSTEM_RESET_PRESERVE_FLAGS) save_flags = system_get_reset_flags() | EC_RESET_FLAG_PRESERVED; if (flags & SYSTEM_RESET_LEAVE_AP_OFF) save_flags |= EC_RESET_FLAG_AP_OFF; chip_save_reset_flags(save_flags); if (flags & SYSTEM_RESET_HARD) { #ifdef CONFIG_SOFTWARE_PANIC uint32_t reason, info; uint8_t exception, panic_flags; panic_flags = panic_get_data()->flags; /* Panic data will be wiped by hard reset, so save it */ panic_get_reason(&reason, &info, &exception); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_REASON, reason); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_INFO, info); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_EXCEPTION, exception); hibdata_write(HIBDATA_INDEX_SAVED_PANIC_FLAGS, panic_flags); #endif /* * Bounce through hibernate to trigger a hard reboot. Do * not wake on wake pin, since we need the full duration. */ hibernate(0, HIB_RESET_USEC, HIBDATA_WAKE_HARD_RESET); } else CPU_NVIC_APINT = 0x05fa0004; /* Spin and wait for reboot; should never return */ while (1) ; } int system_set_scratchpad(uint32_t value) { return hibdata_write(HIBDATA_INDEX_SCRATCHPAD, value); } int system_get_scratchpad(uint32_t *value) { *value = hibdata_read(HIBDATA_INDEX_SCRATCHPAD); return EC_SUCCESS; } const char *system_get_chip_vendor(void) { return "ti"; } static char to_hex(int x) { if (x >= 0 && x <= 9) return '0' + x; return 'a' + x - 10; } const char *system_get_chip_id_string(void) { static char str[15] = "Unknown-"; char *p = str + 8; uint32_t did = LM4_SYSTEM_DID1 >> 16; if (*p) return (const char *)str; *p = to_hex(did >> 12); *(p + 1) = to_hex((did >> 8) & 0xf); *(p + 2) = to_hex((did >> 4) & 0xf); *(p + 3) = to_hex(did & 0xf); *(p + 4) = '\0'; return (const char *)str; } const char *system_get_raw_chip_name(void) { switch ((LM4_SYSTEM_DID1 & 0xffff0000) >> 16) { case 0x10de: return "tm4e1g31h6zrb"; case 0x10e2: return "lm4fsxhh5bb"; case 0x10e3: return "lm4fs232h5bb"; case 0x10e4: return "lm4fs99h5bb"; case 0x10e6: return "lm4fs1ah5bb"; case 0x10ea: return "lm4fs1gh5bb"; default: return system_get_chip_id_string(); } } const char *system_get_chip_name(void) { const char *postfix = "-tm"; /* test mode */ static char str[20]; const char *raw_chip_name = system_get_raw_chip_name(); char *p = str; if (LM4_TEST_MODE_ENABLED) { /* Debug mode is enabled. Postfix chip name. */ while (*raw_chip_name) *(p++) = *(raw_chip_name++); while (*postfix) *(p++) = *(postfix++); *p = '\0'; return (const char *)str; } else { return raw_chip_name; } } int system_get_bbram(enum system_bbram_idx idx, uint8_t *value) { return EC_ERROR_UNIMPLEMENTED; } int system_set_bbram(enum system_bbram_idx idx, uint8_t value) { return EC_ERROR_UNIMPLEMENTED; } const char *system_get_chip_revision(void) { static char rev[3]; /* Extract the major[15:8] and minor[7:0] revisions. */ rev[0] = 'A' + ((LM4_SYSTEM_DID0 >> 8) & 0xff); rev[1] = '0' + (LM4_SYSTEM_DID0 & 0xff); rev[2] = 0; return rev; } /*****************************************************************************/ /* Console commands */ void print_system_rtc(enum console_channel ch) { uint32_t rtc; uint32_t rtcss; rtc = system_get_rtc_sec_subsec(&rtcss); cprintf(ch, "RTC: 0x%08x.%04x (%d.%06d s)\n", rtc, rtcss, rtc, HIB_RTC_SUBSEC_TO_USEC(rtcss)); } #ifdef CONFIG_CMD_RTC static int command_system_rtc(int argc, char **argv) { if (argc == 3 && !strcasecmp(argv[1], "set")) { char *e; uint32_t t = strtoi(argv[2], &e, 0); if (*e) return EC_ERROR_PARAM2; system_set_rtc(t); } else if (argc > 1) { return EC_ERROR_INVAL; } print_system_rtc(CC_COMMAND); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(rtc, command_system_rtc, "[set ]", "Get/set real-time clock"); #ifdef CONFIG_CMD_RTC_ALARM /** * Test the RTC alarm by setting an interrupt on RTC match. */ static int command_rtc_alarm_test(int argc, char **argv) { int s = 1, us = 0; char *e; ccprintf("Setting RTC alarm\n"); system_enable_hib_interrupt(); if (argc > 1) { s = strtoi(argv[1], &e, 10); if (*e) return EC_ERROR_PARAM1; } if (argc > 2) { us = strtoi(argv[2], &e, 10); if (*e) return EC_ERROR_PARAM2; } system_set_rtc_alarm(s, us); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test, "[seconds [microseconds]]", "Test alarm"); #endif /* CONFIG_CMD_RTC_ALARM */ #endif /* CONFIG_CMD_RTC */ /*****************************************************************************/ /* Host commands */ #ifdef CONFIG_HOSTCMD_RTC static enum ec_status system_rtc_get_value(struct host_cmd_handler_args *args) { struct ec_response_rtc *r = args->response; r->time = system_get_rtc_sec_subsec(NULL); args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_VALUE, system_rtc_get_value, EC_VER_MASK(0)); static enum ec_status system_rtc_set_value(struct host_cmd_handler_args *args) { const struct ec_params_rtc *p = args->params; system_set_rtc(p->time); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_VALUE, system_rtc_set_value, EC_VER_MASK(0)); #endif /* CONFIG_HOSTCMD_RTC */