From: Archana Sriram <archana.sriram@xxxxxx> Errata Id:i582 PER Domain reset issue after Domain-OFF/OSWR Wakeup. Issue: When Peripheral Power Domain (PER-PD) is woken up from OFF / OSWR state while Core Power Domain (CORE-PD) is kept active, write or read access to some internal memory (e.g., UART3 FIFO) does not work correctly. Workaround : * Prevent PER from going off when CORE has not gone to off. * When both CORE-PD and PER-PD goes into OSWR/OFF, PER-PD should be brought to active before CORE-PD.This can be done by configuring a wakeup dependency so that CORE-PD and PER-PD will wake up at the same time. Acked-by: Tero Kristo <tero.kristo@xxxxxxxxx> Tested-by: Eduardo Valentin <eduardo.valentin@xxxxxxxxx> Tested-by: Westerberg Mika <ext-mika.1.westerberg@xxxxxxxxx> [vishwanath.bs@xxxxxx: initial code and suggestions] Signed-off-by: Vishwanath BS <vishwanath.bs@xxxxxx> [nm@xxxxxx: forward ported to 2.6.37-rc2 and suggestions] Signed-off-by: Nishanth Menon <nm@xxxxxx> Signed-off-by: Archana Sriram <archana.sriram@xxxxxx> --- arch/arm/mach-omap2/pm34xx.c | 41 +++++++++++++++- arch/arm/mach-omap2/serial.c | 80 ++++++++++++++++++++++++++++++ arch/arm/plat-omap/include/plat/serial.h | 4 ++ 3 files changed, 124 insertions(+), 1 deletions(-) diff --git a/arch/arm/mach-omap2/pm34xx.c b/arch/arm/mach-omap2/pm34xx.c index c7e2db0..218d0b0 100644 --- a/arch/arm/mach-omap2/pm34xx.c +++ b/arch/arm/mach-omap2/pm34xx.c @@ -84,6 +84,10 @@ static struct powerdomain *cam_pwrdm; static int secure_ram_save_status; static int secure_ram_saved; +#define PER_WAKEUP_ERRATA_i582 (1 << 0) +static u16 pm34xx_errata; +#define IS_PM34XX_ERRATA(id) (pm34xx_errata & (id)) + static inline void omap3_per_save_context(void) { omap_gpio_save_context(); @@ -371,7 +375,8 @@ void omap_sram_idle(void) int mpu_next_state = PWRDM_POWER_ON; int per_next_state = PWRDM_POWER_ON; int core_next_state = PWRDM_POWER_ON; - int core_prev_state, per_prev_state; + int core_prev_state = PWRDM_POWER_ON; + int per_prev_state = PWRDM_POWER_ON; u32 sdrc_pwr = 0; if (!_omap_sram_idle) @@ -496,6 +501,23 @@ void omap_sram_idle(void) omap3_per_restore_context(); omap_uart_resume_idle(2); omap_uart_resume_idle(3); + if (IS_PM34XX_ERRATA(PER_WAKEUP_ERRATA_i582) && + omap_uart_check_per_uarts_used() && + (core_prev_state == PWRDM_POWER_ON) && + (per_prev_state == PWRDM_POWER_OFF)) { + /* + * We dont seem to have a real recovery other than reset + * Errata i582:Alternative available here is to do a + * reboot OR go to per off/core off, we will just print + * and cause uart to be in an unstable state and + * continue on till we hit the next off transition. + * Reboot of the device due to this corner case is + * undesirable. + */ + if (omap_uart_per_errata()) + pr_err("%s: PER UART hit with Errata i582 " + "Corner case.\n", __func__); + } } /* Disable IO-PAD and IO-CHAIN wakeup */ @@ -1021,15 +1043,27 @@ void omap_push_sram_idle(void) save_secure_ram_context_sz); } +static void pm_errata_configure(void) +{ + if (cpu_is_omap34xx()) { + pm34xx_errata |= PER_WAKEUP_ERRATA_i582; + if (cpu_is_omap3630() && (omap_rev() > OMAP3630_REV_ES1_1)) + pm34xx_errata &= ~PER_WAKEUP_ERRATA_i582; + } +} + static int __init omap3_pm_init(void) { struct power_state *pwrst, *tmp; struct clockdomain *neon_clkdm, *per_clkdm, *mpu_clkdm, *core_clkdm; + struct clockdomain *wkup_clkdm; int ret; if (!cpu_is_omap34xx()) return -ENODEV; + pm_errata_configure(); + printk(KERN_ERR "Power Management for TI OMAP3.\n"); /* XXX prcm_setup_regs needs to be before enabling hw @@ -1067,6 +1101,7 @@ static int __init omap3_pm_init(void) neon_clkdm = clkdm_lookup("neon_clkdm"); mpu_clkdm = clkdm_lookup("mpu_clkdm"); per_clkdm = clkdm_lookup("per_clkdm"); + wkup_clkdm = clkdm_lookup("wkup_clkdm"); core_clkdm = clkdm_lookup("core_clkdm"); omap_push_sram_idle(); @@ -1077,6 +1112,10 @@ static int __init omap3_pm_init(void) pm_idle = omap3_pm_idle; omap3_idle_init(); + /* Allow per to wakeup the system if errata is applicable */ + if (IS_PM34XX_ERRATA(PER_WAKEUP_ERRATA_i582) && cpu_is_omap34xx()) + clkdm_add_wkdep(per_clkdm, wkup_clkdm); + clkdm_add_wkdep(neon_clkdm, mpu_clkdm); omap3_save_scratchpad_contents(); diff --git a/arch/arm/mach-omap2/serial.c b/arch/arm/mach-omap2/serial.c index becf0e3..43c2ec4 100644 --- a/arch/arm/mach-omap2/serial.c +++ b/arch/arm/mach-omap2/serial.c @@ -273,6 +273,86 @@ static void omap_uart_restore_context(struct omap_uart_state *uart) /* UART 16x mode */ serial_write_reg(uart, UART_OMAP_MDR1, 0x00); } + +static inline int _is_per_uart(struct omap_uart_state *uart) +{ + if (cpu_is_omap34xx() && (uart->num == 2 || uart->num == 3)) + return 1; + return 0; +} + +int omap_uart_check_per_uarts_used(void) +{ + struct omap_uart_state *uart; + + list_for_each_entry(uart, &uart_list, node) { + if (_is_per_uart(uart)) + return 1; + } + return 0; +} + +/* + * Errata i582 affects PER UARTS..Loop back test is done to + * check the UART state when the corner case is encountered + */ +static int omap_uart_loopback_test(struct omap_uart_state *uart) +{ + u8 loopbk_rhr = 0; + + omap_uart_save_context(uart); + serial_write_reg(uart, UART_OMAP_MDR1, 0x7); + serial_write_reg(uart, UART_LCR, 0xBF); /* Config B mode */ + serial_write_reg(uart, UART_DLL, uart->dll); + serial_write_reg(uart, UART_DLM, uart->dlh); + serial_write_reg(uart, UART_LCR, 0x0); /* Operational mode */ + /* configure uart3 in UART mode */ + serial_write_reg(uart, UART_OMAP_MDR1, 0x00); /* UART 16x mode */ + serial_write_reg(uart, UART_LCR, 0x80); + /* Enable internal loop back mode by setting MCR_REG[4].LOOPBACK_EN */ + serial_write_reg(uart, UART_MCR, 0x10); + + /* write to THR,read RHR and compare */ + /* Tx output is internally looped back to Rx input in loop back mode */ + serial_write_reg(uart, UART_LCR_DLAB, 0x00); + /* Enables write to THR and read from RHR */ + serial_write_reg(uart, UART_TX, 0xCC); /* writing data to THR */ + /* reading data from RHR */ + loopbk_rhr = (serial_read_reg(uart, UART_RX) & 0xFF); + if (loopbk_rhr == 0xCC) { + /* compare passes,try comparing with different data */ + serial_write_reg(uart, UART_TX, 0x69); + loopbk_rhr = (serial_read_reg(uart, UART_RX) & 0xFF); + if (loopbk_rhr == 0x69) { + /* compare passes,reset UART3 and re-configure module */ + omap_uart_reset(uart); + omap_uart_restore_context(uart); + return 0; + } + } else { /* requires warm reset */ + return -ENODEV; + } + return 0; +} + +int omap_uart_per_errata(void) +{ + struct omap_uart_state *uart; + + /* For all initialised UART modules that are in PER domain + * do loopback test + */ + list_for_each_entry(uart, &uart_list, node) { + if (_is_per_uart(uart)) { + if (omap_uart_loopback_test(uart)) + return -ENODEV; + else + return 0; + } + } + return 0; +} + #else static inline void omap_uart_save_context(struct omap_uart_state *uart) {} static inline void omap_uart_restore_context(struct omap_uart_state *uart) {} diff --git a/arch/arm/plat-omap/include/plat/serial.h b/arch/arm/plat-omap/include/plat/serial.h index 19145f5..81169b2 100644 --- a/arch/arm/plat-omap/include/plat/serial.h +++ b/arch/arm/plat-omap/include/plat/serial.h @@ -102,6 +102,10 @@ extern void omap_uart_prepare_suspend(void); extern void omap_uart_prepare_idle(int num); extern void omap_uart_resume_idle(int num); extern void omap_uart_enable_irqs(int enable); +#ifdef CONFIG_PM +extern int omap_uart_per_errata(void); +extern int omap_uart_check_per_uarts_used(void); +#endif #endif #endif -- 1.6.3.3 -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html