Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 063d30f1 authored by Alexandre Belloni's avatar Alexandre Belloni Committed by Greg Kroah-Hartman
Browse files

power: reset: at91-poweroff: timely shutdown LPDDR memories



commit 0b0408745e7ff24757cbfd571d69026c0ddb803c upstream.

LPDDR memories can only handle up to 400 uncontrolled power off. Ensure the
proper power off sequence is used before shutting down the platform.

Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@free-electrons.com>
Signed-off-by: default avatarSebastian Reichel <sre@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 42462d23
Loading
Loading
Loading
Loading
+53 −1
Original line number Diff line number Diff line
@@ -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 */
@@ -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)
{
@@ -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;
@@ -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);
@@ -150,12 +179,30 @@ 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);
@@ -163,6 +210,11 @@ static int __exit at91_poweroff_remove(struct platform_device *pdev)
	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", },