power: reset: at91-poweroff: switch to slow clock before shutdown
authorClaudiu Beznea <claudiu.beznea@microchip.com>
Thu, 30 Aug 2018 11:50:06 +0000 (14:50 +0300)
committerSebastian Reichel <sre@kernel.org>
Sun, 16 Sep 2018 10:31:48 +0000 (12:31 +0200)
The SAMA5D2 NRST input signal is resynchronized with the SLCK clock and it
can take up to 2 SLCK cycles (about 90us) for the internal reset to be
effective. During this delay, the VDDCORE current consumption may still be
high (application-dependent) with the VDDCORE regulator already OFF. Under
such conditions, VDDCORE may operate below its operating range leading to
potential register corruption.

To prevent such situation, it is recommended to decrease significantly the
power consumption of the device once the voltage regulator is  turned-off.
This can be achieved by operating the device at a much lower low frequency.

To solve this switch the master clock to slock clock just before writing
shutdown command to shutdown controller.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Suggested-by: Patrice Vilchez <patrice.vilchez@microchip.com>
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
arch/arm/mach-at91/pm_suspend.S
drivers/power/reset/at91-sama5d2_shdwc.c

index a7c6ae13c9453eb36d54ea3c44bf8bf4430867e2..bfe1c4d0690108bce0a250eca54c81e01e7f49fb 100644 (file)
@@ -149,6 +149,14 @@ exit_suspend:
 ENDPROC(at91_pm_suspend_in_sram)
 
 ENTRY(at91_backup_mode)
+       /* Switch the master clock source to slow clock. */
+       ldr     pmc, .pmc_base
+       ldr     tmp1, [pmc, #AT91_PMC_MCKR]
+       bic     tmp1, tmp1, #AT91_PMC_CSS
+       str     tmp1, [pmc, #AT91_PMC_MCKR]
+
+       wait_mckrdy
+
        /*BUMEN*/
        ldr     r0, .sfr
        mov     tmp1, #0x1
index 0206cce328b3da69feb45917114debc5c849d577..dd6297bd7b6a6566f2717c59866b870b0f335724 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 #include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/of.h>
@@ -70,6 +71,7 @@ struct shdwc_config {
 struct shdwc {
        const struct shdwc_config *cfg;
        void __iomem *at91_shdwc_base;
+       void __iomem *pmc_base;
 };
 
 /*
@@ -108,6 +110,12 @@ static void __init at91_wakeup_status(struct platform_device *pdev)
 
 static void at91_poweroff(void)
 {
+       /* Switch the master clock source to slow clock. */
+       writel(readl(at91_shdwc->pmc_base + AT91_PMC_MCKR) & ~AT91_PMC_CSS,
+              at91_shdwc->pmc_base + AT91_PMC_MCKR);
+       while (!(readl(at91_shdwc->pmc_base + AT91_PMC_SR) & AT91_PMC_MCKRDY))
+               ;
+
        writel(AT91_SHDW_KEY | AT91_SHDW_SHDW,
               at91_shdwc->at91_shdwc_base + AT91_SHDW_CR);
 }
@@ -123,6 +131,16 @@ static void at91_lpddr_poweroff(void)
 
                /* Power down SDRAM0 */
                "       str     %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
+
+               /* Switch the master clock source to slow clock. */
+               "       ldr     r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t"
+               "       bic     r6, r6,  #" __stringify(AT91_PMC_CSS) "\n\t"
+               "       str     r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t"
+               /* Wait for clock switch. */
+               "1:     ldr     r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t"
+               "       tst     r6, #"      __stringify(AT91_PMC_MCKRDY) "\n\t"
+               "       beq     1b\n\t"
+
                /* Shutdown CPU */
                "       str     %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
 
@@ -131,7 +149,8 @@ static void at91_lpddr_poweroff(void)
                : "r" (mpddrc_base),
                  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
                  "r" (at91_shdwc->at91_shdwc_base),
-                 "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
+                 "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW),
+                 "r" (at91_shdwc->pmc_base)
                : "r6");
 }
 
@@ -276,26 +295,53 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
 
        at91_shdwc_dt_configure(pdev);
 
-       pm_power_off = at91_poweroff;
+       np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-pmc");
+       if (!np) {
+               ret = -ENODEV;
+               goto clk_disable;
+       }
+
+       at91_shdwc->pmc_base = of_iomap(np, 0);
+       of_node_put(np);
+
+       if (!at91_shdwc->pmc_base) {
+               ret = -ENOMEM;
+               goto clk_disable;
+       }
 
        np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
-       if (!np)
-               return 0;
+       if (!np) {
+               ret = -ENODEV;
+               goto unmap;
+       }
 
        mpddrc_base = of_iomap(np, 0);
        of_node_put(np);
 
-       if (!mpddrc_base)
-               return 0;
+       if (!mpddrc_base) {
+               ret = -ENOMEM;
+               goto unmap;
+       }
+
+       pm_power_off = at91_poweroff;
 
        ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
        if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
-           (ddr_type == AT91_DDRSDRC_MD_LPDDR3))
+           (ddr_type == AT91_DDRSDRC_MD_LPDDR3)) {
                pm_power_off = at91_lpddr_poweroff;
-       else
+       } else {
                iounmap(mpddrc_base);
+               mpddrc_base = NULL;
+       }
 
        return 0;
+
+unmap:
+       iounmap(at91_shdwc->pmc_base);
+clk_disable:
+       clk_disable_unprepare(sclk);
+
+       return ret;
 }
 
 static int __exit at91_shdwc_remove(struct platform_device *pdev)
@@ -310,6 +356,10 @@ static int __exit at91_shdwc_remove(struct platform_device *pdev)
        writel(0, shdw->at91_shdwc_base + AT91_SHDW_MR);
        writel(0, shdw->at91_shdwc_base + AT91_SHDW_WUIR);
 
+       if (mpddrc_base)
+               iounmap(mpddrc_base);
+       iounmap(shdw->pmc_base);
+
        clk_disable_unprepare(sclk);
 
        return 0;