From 9f7195da31fb0b0f83dbd6bbe7aa98c889fb865a Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 30 Aug 2018 14:50:06 +0300 Subject: power: reset: at91-poweroff: switch to slow clock before shutdown 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 Suggested-by: Patrice Vilchez Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 66 ++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 0206cce328b3..dd6297bd7b6a 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -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; -- cgit v1.2.3 From 4e018c1e9b05f722cdd2fecb36201376e3c58dda Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 30 Aug 2018 14:50:07 +0300 Subject: power: reset: at91-poweroff: use only one poweroff function Use only one poweroff function for sama5d2 and adapt it to work for both scenarios (having LPDDR or not). Signed-off-by: Claudiu Beznea Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index dd6297bd7b6a..94ad79d0d82e 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -109,18 +109,6 @@ 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); -} - -static void at91_lpddr_poweroff(void) { asm volatile( /* Align to cache lines */ @@ -130,16 +118,18 @@ static void at91_lpddr_poweroff(void) " ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" /* Power down SDRAM0 */ + " tst %0, #0\n\t" + " beq 1f\n\t" " 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" + "1: 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" + "2: ldr r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t" " tst r6, #" __stringify(AT91_PMC_MCKRDY) "\n\t" - " beq 1b\n\t" + " beq 2b\n\t" /* Shutdown CPU */ " str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" @@ -326,10 +316,8 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) 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)) { - pm_power_off = at91_lpddr_poweroff; - } else { + if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && + ddr_type != AT91_DDRSDRC_MD_LPDDR3) { iounmap(mpddrc_base); mpddrc_base = NULL; } @@ -348,8 +336,7 @@ static int __exit at91_shdwc_remove(struct platform_device *pdev) { struct shdwc *shdw = platform_get_drvdata(pdev); - if (pm_power_off == at91_poweroff || - pm_power_off == at91_lpddr_poweroff) + if (pm_power_off == at91_poweroff) pm_power_off = NULL; /* Reset values to disable wake-up features */ -- cgit v1.2.3 From 9be74f0d39c1c923185e184f3263ba29439ebad6 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 30 Aug 2018 14:50:08 +0300 Subject: power: reset: at91-poweroff: make mpddrc_base part of struct shdwc Make mpddrc_base part of struct shdwc since there is also only one instance of struct shdwc *at91_shdwc in system and to have all data specific to SHDWC grouped together. Signed-off-by: Claudiu Beznea Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 94ad79d0d82e..c9994103248e 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -71,6 +71,7 @@ struct shdwc_config { struct shdwc { const struct shdwc_config *cfg; void __iomem *at91_shdwc_base; + void __iomem *mpddrc_base; void __iomem *pmc_base; }; @@ -80,7 +81,6 @@ struct shdwc { */ static struct shdwc *at91_shdwc; static struct clk *sclk; -static void __iomem *mpddrc_base; static const unsigned long long sdwc_dbc_period[] = { 0, 3, 32, 512, 4096, 32768, @@ -136,7 +136,7 @@ static void at91_poweroff(void) " b .\n\t" : - : "r" (mpddrc_base), + : "r" (at91_shdwc->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), @@ -305,21 +305,22 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) goto unmap; } - mpddrc_base = of_iomap(np, 0); + at91_shdwc->mpddrc_base = of_iomap(np, 0); of_node_put(np); - if (!mpddrc_base) { + if (!at91_shdwc->mpddrc_base) { ret = -ENOMEM; goto unmap; } pm_power_off = at91_poweroff; - ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD; + ddr_type = readl(at91_shdwc->mpddrc_base + AT91_DDRSDRC_MDR) & + AT91_DDRSDRC_MD; if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && ddr_type != AT91_DDRSDRC_MD_LPDDR3) { - iounmap(mpddrc_base); - mpddrc_base = NULL; + iounmap(at91_shdwc->mpddrc_base); + at91_shdwc->mpddrc_base = NULL; } return 0; @@ -343,8 +344,8 @@ 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); + if (shdw->mpddrc_base) + iounmap(shdw->mpddrc_base); iounmap(shdw->pmc_base); clk_disable_unprepare(sclk); -- cgit v1.2.3 From 6764aca14fc85c5ae2bb0a63bece445a05dd4fff Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 30 Aug 2018 14:50:09 +0300 Subject: power: reset: at91-poweroff: make sclk part of struct shdwc Make sclk part of struct shdwc to have all the data specific to SHDWC grouped together in one structure. Signed-off-by: Claudiu Beznea Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index c9994103248e..945b41b4086a 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -70,6 +70,7 @@ struct shdwc_config { struct shdwc { const struct shdwc_config *cfg; + struct clk *sclk; void __iomem *at91_shdwc_base; void __iomem *mpddrc_base; void __iomem *pmc_base; @@ -80,7 +81,6 @@ struct shdwc { * since pm_power_off itself is global. */ static struct shdwc *at91_shdwc; -static struct clk *sclk; static const unsigned long long sdwc_dbc_period[] = { 0, 3, 32, 512, 4096, 32768, @@ -271,11 +271,11 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); at91_shdwc->cfg = match->data; - sclk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(sclk)) - return PTR_ERR(sclk); + at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(at91_shdwc->sclk)) + return PTR_ERR(at91_shdwc->sclk); - ret = clk_prepare_enable(sclk); + ret = clk_prepare_enable(at91_shdwc->sclk); if (ret) { dev_err(&pdev->dev, "Could not enable slow clock\n"); return ret; @@ -328,7 +328,7 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) unmap: iounmap(at91_shdwc->pmc_base); clk_disable: - clk_disable_unprepare(sclk); + clk_disable_unprepare(at91_shdwc->sclk); return ret; } @@ -348,7 +348,7 @@ static int __exit at91_shdwc_remove(struct platform_device *pdev) iounmap(shdw->mpddrc_base); iounmap(shdw->pmc_base); - clk_disable_unprepare(sclk); + clk_disable_unprepare(shdw->sclk); return 0; } -- cgit v1.2.3 From d12f84906bcb9384d0bf58726688fb60aac8a100 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 30 Aug 2018 14:50:10 +0300 Subject: power: reset: at91-poweroff: rename at91_shdwc_base member of struct shdwc Rename at91_shdwc_base member of struct shdwc to shdwc_base. There is already an "at91" string in at91_shdwc object. Signed-off-by: Claudiu Beznea Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 945b41b4086a..ad6e2796690a 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -71,7 +71,7 @@ struct shdwc_config { struct shdwc { const struct shdwc_config *cfg; struct clk *sclk; - void __iomem *at91_shdwc_base; + void __iomem *shdwc_base; void __iomem *mpddrc_base; void __iomem *pmc_base; }; @@ -92,7 +92,7 @@ static void __init at91_wakeup_status(struct platform_device *pdev) u32 reg; char *reason = "unknown"; - reg = readl(shdw->at91_shdwc_base + AT91_SHDW_SR); + reg = readl(shdw->shdwc_base + AT91_SHDW_SR); dev_dbg(&pdev->dev, "%s: status = %#x\n", __func__, reg); @@ -138,7 +138,7 @@ static void at91_poweroff(void) : : "r" (at91_shdwc->mpddrc_base), "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), - "r" (at91_shdwc->at91_shdwc_base), + "r" (at91_shdwc->shdwc_base), "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW), "r" (at91_shdwc->pmc_base) : "r6"); @@ -222,10 +222,10 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) mode |= SHDW_RTCWKEN(shdw->cfg); dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode); - writel(mode, shdw->at91_shdwc_base + AT91_SHDW_MR); + writel(mode, shdw->shdwc_base + AT91_SHDW_MR); input = at91_shdwc_get_wakeup_input(pdev, np); - writel(input, shdw->at91_shdwc_base + AT91_SHDW_WUIR); + writel(input, shdw->shdwc_base + AT91_SHDW_WUIR); } static const struct shdwc_config sama5d2_shdwc_config = { @@ -262,10 +262,10 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, at91_shdwc); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - at91_shdwc->at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(at91_shdwc->at91_shdwc_base)) { + at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(at91_shdwc->shdwc_base)) { dev_err(&pdev->dev, "Could not map reset controller address\n"); - return PTR_ERR(at91_shdwc->at91_shdwc_base); + return PTR_ERR(at91_shdwc->shdwc_base); } match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); @@ -341,8 +341,8 @@ static int __exit at91_shdwc_remove(struct platform_device *pdev) pm_power_off = NULL; /* Reset values to disable wake-up features */ - writel(0, shdw->at91_shdwc_base + AT91_SHDW_MR); - writel(0, shdw->at91_shdwc_base + AT91_SHDW_WUIR); + writel(0, shdw->shdwc_base + AT91_SHDW_MR); + writel(0, shdw->shdwc_base + AT91_SHDW_WUIR); if (shdw->mpddrc_base) iounmap(shdw->mpddrc_base); -- cgit v1.2.3 From 9f1e44774be578fb92776add95f1fcaf8284d692 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 30 Aug 2018 14:50:11 +0300 Subject: power: reset: at91-poweroff: do not procede if at91_shdwc is allocated There should be only one instance of struct shdwc in the system. This is referenced through at91_shdwc. Return in probe if at91_shdwc is already allocated. Signed-off-by: Claudiu Beznea Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index ad6e2796690a..2b686c55b717 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -255,6 +255,9 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) if (!pdev->dev.of_node) return -ENODEV; + if (at91_shdwc) + return -EBUSY; + at91_shdwc = devm_kzalloc(&pdev->dev, sizeof(*at91_shdwc), GFP_KERNEL); if (!at91_shdwc) return -ENOMEM; -- cgit v1.2.3