Skip to content

Commit

Permalink
power: reset: at91-poweroff: timely shutdown LPDDR memories
Browse files Browse the repository at this point in the history
LPDDR memories can only handle up to 400 uncontrolled power off. Ensure the
proper power off sequence is used before shutting down the platform.

Cc: <stable@vger.kernel.org> # 4.4+
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
  • Loading branch information
Alexandre Belloni authored and Sebastian Reichel committed Jan 16, 2017
1 parent e3f0a40 commit 0b04087
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 3 deletions.
2 changes: 1 addition & 1 deletion drivers/power/reset/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ config POWER_RESET_AT91_RESET

config POWER_RESET_AT91_SAMA5D2_SHDWC
tristate "Atmel AT91 SAMA5D2-Compatible shutdown controller driver"
depends on ARCH_AT91 || COMPILE_TEST
depends on ARCH_AT91
default SOC_SAMA5
help
This driver supports the alternate shutdown controller for some Atmel
Expand Down
54 changes: 53 additions & 1 deletion drivers/power/reset/at91-poweroff.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/printk.h>

#include <soc/at91/at91sam9_ddrsdr.h>

#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
#define AT91_SHDW_SHDW BIT(0) /* Shut Down command */
#define AT91_SHDW_KEY (0xa5 << 24) /* KEY Password */
Expand Down Expand Up @@ -50,6 +53,7 @@ static const char *shdwc_wakeup_modes[] = {

static void __iomem *at91_shdwc_base;
static struct clk *sclk;
static void __iomem *mpddrc_base;

static void __init at91_wakeup_status(void)
{
Expand All @@ -73,6 +77,29 @@ static void at91_poweroff(void)
writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR);
}

static void at91_lpddr_poweroff(void)
{
asm volatile(
/* Align to cache lines */
".balign 32\n\t"

/* Ensure AT91_SHDW_CR is in the TLB by reading it */
" ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"

/* Power down SDRAM0 */
" str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
/* Shutdown CPU */
" str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"

" b .\n\t"
:
: "r" (mpddrc_base),
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
: "r0");
}

static int at91_poweroff_get_wakeup_mode(struct device_node *np)
{
const char *pm;
Expand Down Expand Up @@ -124,6 +151,8 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
static int __init at91_poweroff_probe(struct platform_device *pdev)
{
struct resource *res;
struct device_node *np;
u32 ddr_type;
int ret;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Expand All @@ -150,19 +179,42 @@ static int __init at91_poweroff_probe(struct platform_device *pdev)

pm_power_off = at91_poweroff;

np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
if (!np)
return 0;

mpddrc_base = of_iomap(np, 0);
of_node_put(np);

if (!mpddrc_base)
return 0;

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
iounmap(mpddrc_base);

return 0;
}

static int __exit at91_poweroff_remove(struct platform_device *pdev)
{
if (pm_power_off == at91_poweroff)
if (pm_power_off == at91_poweroff ||
pm_power_off == at91_lpddr_poweroff)
pm_power_off = NULL;

clk_disable_unprepare(sclk);

return 0;
}

static const struct of_device_id at91_ramc_of_match[] = {
{ .compatible = "atmel,sama5d3-ddramc", },
{ /* sentinel */ }
};

static const struct of_device_id at91_poweroff_of_match[] = {
{ .compatible = "atmel,at91sam9260-shdwc", },
{ .compatible = "atmel,at91sam9rl-shdwc", },
Expand Down
49 changes: 48 additions & 1 deletion drivers/power/reset/at91-sama5d2_shdwc.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/printk.h>

#include <soc/at91/at91sam9_ddrsdr.h>

#define SLOW_CLOCK_FREQ 32768

#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
Expand Down Expand Up @@ -75,6 +78,7 @@ 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,
Expand Down Expand Up @@ -108,6 +112,29 @@ static void at91_poweroff(void)
at91_shdwc->at91_shdwc_base + AT91_SHDW_CR);
}

static void at91_lpddr_poweroff(void)
{
asm volatile(
/* Align to cache lines */
".balign 32\n\t"

/* Ensure AT91_SHDW_CR is in the TLB by reading it */
" ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"

/* Power down SDRAM0 */
" str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
/* Shutdown CPU */
" str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"

" b .\n\t"
:
: "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)
: "r0");
}

static u32 at91_shdwc_debouncer_value(struct platform_device *pdev,
u32 in_period_us)
{
Expand Down Expand Up @@ -212,6 +239,8 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
{
struct resource *res;
const struct of_device_id *match;
struct device_node *np;
u32 ddr_type;
int ret;

if (!pdev->dev.of_node)
Expand Down Expand Up @@ -249,14 +278,32 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)

pm_power_off = at91_poweroff;

np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
if (!np)
return 0;

mpddrc_base = of_iomap(np, 0);
of_node_put(np);

if (!mpddrc_base)
return 0;

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
iounmap(mpddrc_base);

return 0;
}

static int __exit at91_shdwc_remove(struct platform_device *pdev)
{
struct shdwc *shdw = platform_get_drvdata(pdev);

if (pm_power_off == at91_poweroff)
if (pm_power_off == at91_poweroff ||
pm_power_off == at91_lpddr_poweroff)
pm_power_off = NULL;

/* Reset values to disable wake-up features */
Expand Down

0 comments on commit 0b04087

Please sign in to comment.