CHROMIUM: exynos: log when watchdog timeout triggers a reset
[cascardo/linux.git] / arch / arm / mach-exynos / pmu.c
index 589f781..626e7fa 100644 (file)
@@ -13,6 +13,9 @@
 #include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>    /* for WDIOF_CARDRESET */
 
 #include <mach/regs-clock.h>
 #include <mach/pmu.h>
@@ -301,7 +304,7 @@ static struct exynos4_pmu_conf exynos5250_pmu_config[] = {
        { EXYNOS5_CMU_SYSCLK_MAU_SYS_PWR_REG,           { 0x1, 0x1, 0x0} },
        { EXYNOS5_CMU_SYSCLK_GPS_SYS_PWR_REG,           { 0x1, 0x0, 0x0} },
        { EXYNOS5_CMU_RESET_GSCL_SYS_PWR_REG,           { 0x1, 0x0, 0x0} },
-       { EXYNOS5_CMU_RESET_ISP_SYS_PWR_REG,            { 0x1, 0x0, 0x0} },
+       /* CMU_RESET_ISP_SYS_PWR_REG handled in exynos5250_disable_isp() */
        { EXYNOS5_CMU_RESET_MFC_SYS_PWR_REG,            { 0x1, 0x0, 0x0} },
        { EXYNOS5_CMU_RESET_G3D_SYS_PWR_REG,            { 0x1, 0x0, 0x0} },
        { EXYNOS5_CMU_RESET_DISP1_SYS_PWR_REG,          { 0x1, 0x0, 0x0} },
@@ -327,9 +330,48 @@ void __iomem *exynos5_list_both_cnt_feed[] = {
 void __iomem *exynos5_list_diable_wfi_wfe[] = {
        EXYNOS5_ARM_CORE1_OPTION,
        EXYNOS5_FSYS_ARM_OPTION,
-       EXYNOS5_ISP_ARM_OPTION,
 };
 
+
+/*
+ * RST_STAT bits:
+ *     power-on boot  will set bit 16
+ *     Watchdog reset will set bit 20
+ *     "warm" reboot  will set bit 29
+ */
+#define EXYNOS4_RST_STAT       (S3C_ADDR(0x10020000) + 0x0404)
+#define EXYNOS5_RST_STAT       (S3C_ADDR(0x02180000) + 0x0404)
+#define EXYNOS_WDTRESET                (1 << 20)
+
+static void exynos5_power_off(void)
+{
+       unsigned int tmp;
+
+       pr_info("Power down.\n");
+       tmp = __raw_readl(EXYNOS5_PS_HOLD_CONTROL);
+       tmp &= ~(1 << 8);
+       __raw_writel(tmp, EXYNOS5_PS_HOLD_CONTROL);
+
+       /* Wait a little so we don't give a false warning below */
+       mdelay(100);
+
+       pr_err("Power down failed, please power off system manually.\n");
+       while (1)
+               ;
+}
+
+static void exynos5_debug_enable_uart_wakeup(void)
+{
+#ifdef CONFIG_SAMSUNG_PM_DEBUG
+       unsigned int tmp;
+
+       /* Enable UART automatic wakeup for resume console output */
+       tmp = __raw_readl(S5P_PAD_RET_UART_OPTION);
+       tmp |= EXYNOS5_PAD_RET_UART_AUTOMATIC_WAKEUP;
+       __raw_writel(tmp, S5P_PAD_RET_UART_OPTION);
+#endif
+}
+
 static void exynos5_init_pmu(void)
 {
        unsigned int i;
@@ -363,6 +405,8 @@ static void exynos5_init_pmu(void)
                         EXYNOS5_OPTION_USE_STANDBYWFI);
                __raw_writel(tmp, exynos5_list_diable_wfi_wfe[i]);
        }
+
+       exynos5_debug_enable_uart_wakeup();
 }
 
 void exynos4_sys_powerdown_conf(enum sys_powerdown mode)
@@ -377,8 +421,74 @@ void exynos4_sys_powerdown_conf(enum sys_powerdown mode)
                                exynos4_pmu_config[i].reg);
 }
 
+#define ISP_DISABLE_TRIES                      10
+
+/*
+ * Disable the image signal processor.
+ *
+ * We currently have no code in the kernel to manage the state of the ISP.
+ *
+ * The ISP's power sequencing code needs to be run in a very specific order
+ * and shouldn't necessarily be intertwined with the power on/power off code
+ * of the main CPU core.  Until there is kernel code to manage the ISP, we'll
+ * just hardcode powering off the ISP here.
+ */
+static void exynos5250_disable_isp(void)
+{
+       int i;
+
+       /* Make sure ISP ARM is disabled; don't use WFI or WFE */
+       __raw_writel(0, EXYNOS5_ISP_ARM_OPTION);
+
+       /* Put the ISP ARM in reset */
+       __raw_writel(0x0, EXYNOS5_ISP_ARM_CONFIGURATION);
+       for (i = 0; i < ISP_DISABLE_TRIES; i++) {
+               if (!(__raw_readl(EXYNOS5_ISP_ARM_STATUS) & 0x1))
+                       break;
+               usleep_range(80, 100);
+       }
+       WARN_ON(i == ISP_DISABLE_TRIES);
+
+       /* Reset the ISP CMU block in power-off/low power state */
+       __raw_writel(0x0, EXYNOS5_CMU_RESET_ISP_SYS_PWR_REG);
+
+       /* Turn off power to the ISP in normal mode */
+       __raw_writel(0x0, EXYNOS5_ISP_CONFIGURATION);
+       for (i = 0; i < ISP_DISABLE_TRIES; i++) {
+               if (!(__raw_readl(EXYNOS5_ISP_STATUS) & 0x7))
+                       break;
+               usleep_range(80, 100);
+       }
+       WARN_ON(i == ISP_DISABLE_TRIES);
+}
+
+
+/*
+ * exynos_get_bootstatus() supports generic WDIOC_GETBOOTSTATUS ioctl.
+ * See Documentation/watchdog/watchdog-api.txt for user API.
+ * See usage by drivers/watchdog/s3c2410_wdt.c
+ *
+ * Other subsystems might need to set bits too.
+ * (e.g. WDIOF_OVERHEAT or WDIOF_FANFAULT).
+ */
+unsigned int exynos_get_bootstatus(void)
+{
+       unsigned int rst_stat;
+
+       if (soc_is_exynos5250())
+               rst_stat = readl(EXYNOS5_RST_STAT);
+       else if (soc_is_exynos4210() || soc_is_exynos4212())
+               rst_stat = readl(EXYNOS4_RST_STAT);
+       else
+               return 0;
+
+       return (rst_stat & EXYNOS_WDTRESET) ? WDIOF_CARDRESET : 0;
+}
+
 static int __init exynos4_pmu_init(void)
 {
+       unsigned int bootstatus;
+
        exynos4_pmu_config = exynos4210_pmu_config;
 
        if (soc_is_exynos4210()) {
@@ -388,12 +498,19 @@ static int __init exynos4_pmu_init(void)
                exynos4_pmu_config = exynos4212_pmu_config;
                pr_info("EXYNOS4212 PMU Initialize\n");
        } else if (soc_is_exynos5250()) {
+               exynos5250_disable_isp();
+
                exynos4_pmu_config = exynos5250_pmu_config;
+               pm_power_off = exynos5_power_off;
                pr_info("EXYNOS5250 PMU Initialize\n");
        } else {
                pr_info("EXYNOS4: PMU not supported\n");
        }
 
+       bootstatus = exynos_get_bootstatus();
+       if (bootstatus & WDIOF_CARDRESET)
+               pr_warning("EXYNOS Watchdog timed out - caused last reboot!");
+
        return 0;
 }
 arch_initcall(exynos4_pmu_init);