162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Atmel SAMA5D2-Compatible Shutdown Controller (SHDWC) driver.
362306a36Sopenharmony_ci * Found on some SoCs as the sama5d2 (obviously).
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2015 Atmel Corporation,
662306a36Sopenharmony_ci *                    Nicolas Ferre <nicolas.ferre@atmel.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Evolved from driver at91-poweroff.c.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
1162306a36Sopenharmony_ci * License version 2.  This program is licensed "as is" without any
1262306a36Sopenharmony_ci * warranty of any kind, whether express or implied.
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * TODO:
1562306a36Sopenharmony_ci * - addition to status of other wake-up inputs [1 - 15]
1662306a36Sopenharmony_ci * - Analog Comparator wake-up alarm
1762306a36Sopenharmony_ci * - Serial RX wake-up alarm
1862306a36Sopenharmony_ci * - low power debouncer
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/clk.h>
2262306a36Sopenharmony_ci#include <linux/clk/at91_pmc.h>
2362306a36Sopenharmony_ci#include <linux/io.h>
2462306a36Sopenharmony_ci#include <linux/module.h>
2562306a36Sopenharmony_ci#include <linux/of.h>
2662306a36Sopenharmony_ci#include <linux/of_address.h>
2762306a36Sopenharmony_ci#include <linux/platform_device.h>
2862306a36Sopenharmony_ci#include <linux/printk.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#include <soc/at91/at91sam9_ddrsdr.h>
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define SLOW_CLOCK_FREQ	32768
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define AT91_SHDW_CR	0x00		/* Shut Down Control Register */
3562306a36Sopenharmony_ci#define AT91_SHDW_SHDW		BIT(0)			/* Shut Down command */
3662306a36Sopenharmony_ci#define AT91_SHDW_KEY		(0xa5UL << 24)		/* KEY Password */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define AT91_SHDW_MR	0x04		/* Shut Down Mode Register */
3962306a36Sopenharmony_ci#define AT91_SHDW_WKUPDBC_SHIFT	24
4062306a36Sopenharmony_ci#define AT91_SHDW_WKUPDBC_MASK	GENMASK(26, 24)
4162306a36Sopenharmony_ci#define AT91_SHDW_WKUPDBC(x)	(((x) << AT91_SHDW_WKUPDBC_SHIFT) \
4262306a36Sopenharmony_ci						& AT91_SHDW_WKUPDBC_MASK)
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#define AT91_SHDW_SR	0x08		/* Shut Down Status Register */
4562306a36Sopenharmony_ci#define AT91_SHDW_WKUPIS_SHIFT	16
4662306a36Sopenharmony_ci#define AT91_SHDW_WKUPIS_MASK	GENMASK(31, 16)
4762306a36Sopenharmony_ci#define AT91_SHDW_WKUPIS(x)	((1 << (x)) << AT91_SHDW_WKUPIS_SHIFT \
4862306a36Sopenharmony_ci						& AT91_SHDW_WKUPIS_MASK)
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#define AT91_SHDW_WUIR	0x0c		/* Shutdown Wake-up Inputs Register */
5162306a36Sopenharmony_ci#define AT91_SHDW_WKUPEN_MASK	GENMASK(15, 0)
5262306a36Sopenharmony_ci#define AT91_SHDW_WKUPEN(x)	((1 << (x)) & AT91_SHDW_WKUPEN_MASK)
5362306a36Sopenharmony_ci#define AT91_SHDW_WKUPT_SHIFT	16
5462306a36Sopenharmony_ci#define AT91_SHDW_WKUPT_MASK	GENMASK(31, 16)
5562306a36Sopenharmony_ci#define AT91_SHDW_WKUPT(x)	((1 << (x)) << AT91_SHDW_WKUPT_SHIFT \
5662306a36Sopenharmony_ci						& AT91_SHDW_WKUPT_MASK)
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define SHDW_WK_PIN(reg, cfg)	((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input))
5962306a36Sopenharmony_ci#define SHDW_RTCWK(reg, cfg)	(((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1)
6062306a36Sopenharmony_ci#define SHDW_RTTWK(reg, cfg)	(((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1)
6162306a36Sopenharmony_ci#define SHDW_RTCWKEN(cfg)	(1 << ((cfg)->mr_rtcwk_shift))
6262306a36Sopenharmony_ci#define SHDW_RTTWKEN(cfg)	(1 << ((cfg)->mr_rttwk_shift))
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#define DBC_PERIOD_US(x)	DIV_ROUND_UP_ULL((1000000 * (x)), \
6562306a36Sopenharmony_ci							SLOW_CLOCK_FREQ)
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#define SHDW_CFG_NOT_USED	(32)
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistruct shdwc_reg_config {
7062306a36Sopenharmony_ci	u8 wkup_pin_input;
7162306a36Sopenharmony_ci	u8 mr_rtcwk_shift;
7262306a36Sopenharmony_ci	u8 mr_rttwk_shift;
7362306a36Sopenharmony_ci	u8 sr_rtcwk_shift;
7462306a36Sopenharmony_ci	u8 sr_rttwk_shift;
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistruct pmc_reg_config {
7862306a36Sopenharmony_ci	u8 mckr;
7962306a36Sopenharmony_ci};
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistruct ddrc_reg_config {
8262306a36Sopenharmony_ci	u32 type_offset;
8362306a36Sopenharmony_ci	u32 type_mask;
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistruct reg_config {
8762306a36Sopenharmony_ci	struct shdwc_reg_config shdwc;
8862306a36Sopenharmony_ci	struct pmc_reg_config pmc;
8962306a36Sopenharmony_ci	struct ddrc_reg_config ddrc;
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistruct shdwc {
9362306a36Sopenharmony_ci	const struct reg_config *rcfg;
9462306a36Sopenharmony_ci	struct clk *sclk;
9562306a36Sopenharmony_ci	void __iomem *shdwc_base;
9662306a36Sopenharmony_ci	void __iomem *mpddrc_base;
9762306a36Sopenharmony_ci	void __iomem *pmc_base;
9862306a36Sopenharmony_ci};
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci/*
10162306a36Sopenharmony_ci * Hold configuration here, cannot be more than one instance of the driver
10262306a36Sopenharmony_ci * since pm_power_off itself is global.
10362306a36Sopenharmony_ci */
10462306a36Sopenharmony_cistatic struct shdwc *at91_shdwc;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic const unsigned long long sdwc_dbc_period[] = {
10762306a36Sopenharmony_ci	0, 3, 32, 512, 4096, 32768,
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic void __init at91_wakeup_status(struct platform_device *pdev)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct shdwc *shdw = platform_get_drvdata(pdev);
11362306a36Sopenharmony_ci	const struct reg_config *rcfg = shdw->rcfg;
11462306a36Sopenharmony_ci	u32 reg;
11562306a36Sopenharmony_ci	char *reason = "unknown";
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	reg = readl(shdw->shdwc_base + AT91_SHDW_SR);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "%s: status = %#x\n", __func__, reg);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	/* Simple power-on, just bail out */
12262306a36Sopenharmony_ci	if (!reg)
12362306a36Sopenharmony_ci		return;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (SHDW_WK_PIN(reg, &rcfg->shdwc))
12662306a36Sopenharmony_ci		reason = "WKUP pin";
12762306a36Sopenharmony_ci	else if (SHDW_RTCWK(reg, &rcfg->shdwc))
12862306a36Sopenharmony_ci		reason = "RTC";
12962306a36Sopenharmony_ci	else if (SHDW_RTTWK(reg, &rcfg->shdwc))
13062306a36Sopenharmony_ci		reason = "RTT";
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	pr_info("AT91: Wake-Up source: %s\n", reason);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic void at91_poweroff(void)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	asm volatile(
13862306a36Sopenharmony_ci		/* Align to cache lines */
13962306a36Sopenharmony_ci		".balign 32\n\t"
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		/* Ensure AT91_SHDW_CR is in the TLB by reading it */
14262306a36Sopenharmony_ci		"	ldr	r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		/* Power down SDRAM0 */
14562306a36Sopenharmony_ci		"	tst	%0, #0\n\t"
14662306a36Sopenharmony_ci		"	beq	1f\n\t"
14762306a36Sopenharmony_ci		"	str	%1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci		/* Switch the master clock source to slow clock. */
15062306a36Sopenharmony_ci		"1:	ldr	r6, [%4, %5]\n\t"
15162306a36Sopenharmony_ci		"	bic	r6, r6,  #" __stringify(AT91_PMC_CSS) "\n\t"
15262306a36Sopenharmony_ci		"	str	r6, [%4, %5]\n\t"
15362306a36Sopenharmony_ci		/* Wait for clock switch. */
15462306a36Sopenharmony_ci		"2:	ldr	r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t"
15562306a36Sopenharmony_ci		"	tst	r6, #"	    __stringify(AT91_PMC_MCKRDY) "\n\t"
15662306a36Sopenharmony_ci		"	beq	2b\n\t"
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		/* Shutdown CPU */
15962306a36Sopenharmony_ci		"	str	%3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		"	b	.\n\t"
16262306a36Sopenharmony_ci		:
16362306a36Sopenharmony_ci		: "r" (at91_shdwc->mpddrc_base),
16462306a36Sopenharmony_ci		  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
16562306a36Sopenharmony_ci		  "r" (at91_shdwc->shdwc_base),
16662306a36Sopenharmony_ci		  "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW),
16762306a36Sopenharmony_ci		  "r" (at91_shdwc->pmc_base),
16862306a36Sopenharmony_ci		  "r" (at91_shdwc->rcfg->pmc.mckr)
16962306a36Sopenharmony_ci		: "r6");
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic u32 at91_shdwc_debouncer_value(struct platform_device *pdev,
17362306a36Sopenharmony_ci				      u32 in_period_us)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	int i;
17662306a36Sopenharmony_ci	int max_idx = ARRAY_SIZE(sdwc_dbc_period) - 1;
17762306a36Sopenharmony_ci	unsigned long long period_us;
17862306a36Sopenharmony_ci	unsigned long long max_period_us = DBC_PERIOD_US(sdwc_dbc_period[max_idx]);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (in_period_us > max_period_us) {
18162306a36Sopenharmony_ci		dev_warn(&pdev->dev,
18262306a36Sopenharmony_ci			 "debouncer period %u too big, reduced to %llu us\n",
18362306a36Sopenharmony_ci			 in_period_us, max_period_us);
18462306a36Sopenharmony_ci		return max_idx;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	for (i = max_idx - 1; i > 0; i--) {
18862306a36Sopenharmony_ci		period_us = DBC_PERIOD_US(sdwc_dbc_period[i]);
18962306a36Sopenharmony_ci		dev_dbg(&pdev->dev, "%s: ref[%d] = %llu\n",
19062306a36Sopenharmony_ci						__func__, i, period_us);
19162306a36Sopenharmony_ci		if (in_period_us > period_us)
19262306a36Sopenharmony_ci			break;
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return i + 1;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev,
19962306a36Sopenharmony_ci				       struct device_node *np)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct device_node *cnp;
20262306a36Sopenharmony_ci	u32 wk_input_mask;
20362306a36Sopenharmony_ci	u32 wuir = 0;
20462306a36Sopenharmony_ci	u32 wk_input;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	for_each_child_of_node(np, cnp) {
20762306a36Sopenharmony_ci		if (of_property_read_u32(cnp, "reg", &wk_input)) {
20862306a36Sopenharmony_ci			dev_warn(&pdev->dev, "reg property is missing for %pOF\n",
20962306a36Sopenharmony_ci				 cnp);
21062306a36Sopenharmony_ci			continue;
21162306a36Sopenharmony_ci		}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		wk_input_mask = 1 << wk_input;
21462306a36Sopenharmony_ci		if (!(wk_input_mask & AT91_SHDW_WKUPEN_MASK)) {
21562306a36Sopenharmony_ci			dev_warn(&pdev->dev,
21662306a36Sopenharmony_ci				 "wake-up input %d out of bounds ignore\n",
21762306a36Sopenharmony_ci				 wk_input);
21862306a36Sopenharmony_ci			continue;
21962306a36Sopenharmony_ci		}
22062306a36Sopenharmony_ci		wuir |= wk_input_mask;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		if (of_property_read_bool(cnp, "atmel,wakeup-active-high"))
22362306a36Sopenharmony_ci			wuir |= AT91_SHDW_WKUPT(wk_input);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		dev_dbg(&pdev->dev, "%s: (child %d) wuir = %#x\n",
22662306a36Sopenharmony_ci						__func__, wk_input, wuir);
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	return wuir;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic void at91_shdwc_dt_configure(struct platform_device *pdev)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	struct shdwc *shdw = platform_get_drvdata(pdev);
23562306a36Sopenharmony_ci	const struct reg_config *rcfg = shdw->rcfg;
23662306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
23762306a36Sopenharmony_ci	u32 mode = 0, tmp, input;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (!np) {
24062306a36Sopenharmony_ci		dev_err(&pdev->dev, "device node not found\n");
24162306a36Sopenharmony_ci		return;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (!of_property_read_u32(np, "debounce-delay-us", &tmp))
24562306a36Sopenharmony_ci		mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp));
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
24862306a36Sopenharmony_ci		mode |= SHDW_RTCWKEN(&rcfg->shdwc);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
25162306a36Sopenharmony_ci		mode |= SHDW_RTTWKEN(&rcfg->shdwc);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);
25462306a36Sopenharmony_ci	writel(mode, shdw->shdwc_base + AT91_SHDW_MR);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	input = at91_shdwc_get_wakeup_input(pdev, np);
25762306a36Sopenharmony_ci	writel(input, shdw->shdwc_base + AT91_SHDW_WUIR);
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_cistatic const struct reg_config sama5d2_reg_config = {
26162306a36Sopenharmony_ci	.shdwc = {
26262306a36Sopenharmony_ci		.wkup_pin_input = 0,
26362306a36Sopenharmony_ci		.mr_rtcwk_shift = 17,
26462306a36Sopenharmony_ci		.mr_rttwk_shift	= SHDW_CFG_NOT_USED,
26562306a36Sopenharmony_ci		.sr_rtcwk_shift = 5,
26662306a36Sopenharmony_ci		.sr_rttwk_shift = SHDW_CFG_NOT_USED,
26762306a36Sopenharmony_ci	},
26862306a36Sopenharmony_ci	.pmc = {
26962306a36Sopenharmony_ci		.mckr		= 0x30,
27062306a36Sopenharmony_ci	},
27162306a36Sopenharmony_ci	.ddrc = {
27262306a36Sopenharmony_ci		.type_offset	= AT91_DDRSDRC_MDR,
27362306a36Sopenharmony_ci		.type_mask	= AT91_DDRSDRC_MD
27462306a36Sopenharmony_ci	},
27562306a36Sopenharmony_ci};
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic const struct reg_config sam9x60_reg_config = {
27862306a36Sopenharmony_ci	.shdwc = {
27962306a36Sopenharmony_ci		.wkup_pin_input = 0,
28062306a36Sopenharmony_ci		.mr_rtcwk_shift = 17,
28162306a36Sopenharmony_ci		.mr_rttwk_shift = 16,
28262306a36Sopenharmony_ci		.sr_rtcwk_shift = 5,
28362306a36Sopenharmony_ci		.sr_rttwk_shift = 4,
28462306a36Sopenharmony_ci	},
28562306a36Sopenharmony_ci	.pmc = {
28662306a36Sopenharmony_ci		.mckr		= 0x28,
28762306a36Sopenharmony_ci	},
28862306a36Sopenharmony_ci	.ddrc = {
28962306a36Sopenharmony_ci		.type_offset	= AT91_DDRSDRC_MDR,
29062306a36Sopenharmony_ci		.type_mask	= AT91_DDRSDRC_MD
29162306a36Sopenharmony_ci	},
29262306a36Sopenharmony_ci};
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic const struct reg_config sama7g5_reg_config = {
29562306a36Sopenharmony_ci	.shdwc = {
29662306a36Sopenharmony_ci		.wkup_pin_input = 0,
29762306a36Sopenharmony_ci		.mr_rtcwk_shift = 17,
29862306a36Sopenharmony_ci		.mr_rttwk_shift = 16,
29962306a36Sopenharmony_ci		.sr_rtcwk_shift = 5,
30062306a36Sopenharmony_ci		.sr_rttwk_shift = 4,
30162306a36Sopenharmony_ci	},
30262306a36Sopenharmony_ci	.pmc = {
30362306a36Sopenharmony_ci		.mckr		= 0x28,
30462306a36Sopenharmony_ci	},
30562306a36Sopenharmony_ci};
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic const struct of_device_id at91_shdwc_of_match[] = {
30862306a36Sopenharmony_ci	{
30962306a36Sopenharmony_ci		.compatible = "atmel,sama5d2-shdwc",
31062306a36Sopenharmony_ci		.data = &sama5d2_reg_config,
31162306a36Sopenharmony_ci	},
31262306a36Sopenharmony_ci	{
31362306a36Sopenharmony_ci		.compatible = "microchip,sam9x60-shdwc",
31462306a36Sopenharmony_ci		.data = &sam9x60_reg_config,
31562306a36Sopenharmony_ci	},
31662306a36Sopenharmony_ci	{
31762306a36Sopenharmony_ci		.compatible = "microchip,sama7g5-shdwc",
31862306a36Sopenharmony_ci		.data = &sama7g5_reg_config,
31962306a36Sopenharmony_ci	}, {
32062306a36Sopenharmony_ci		/*sentinel*/
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci};
32362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, at91_shdwc_of_match);
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_cistatic const struct of_device_id at91_pmc_ids[] = {
32662306a36Sopenharmony_ci	{ .compatible = "atmel,sama5d2-pmc" },
32762306a36Sopenharmony_ci	{ .compatible = "microchip,sam9x60-pmc" },
32862306a36Sopenharmony_ci	{ .compatible = "microchip,sama7g5-pmc" },
32962306a36Sopenharmony_ci	{ /* Sentinel. */ }
33062306a36Sopenharmony_ci};
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistatic int __init at91_shdwc_probe(struct platform_device *pdev)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci	const struct of_device_id *match;
33562306a36Sopenharmony_ci	struct device_node *np;
33662306a36Sopenharmony_ci	u32 ddr_type;
33762306a36Sopenharmony_ci	int ret;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	if (!pdev->dev.of_node)
34062306a36Sopenharmony_ci		return -ENODEV;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	if (at91_shdwc)
34362306a36Sopenharmony_ci		return -EBUSY;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	at91_shdwc = devm_kzalloc(&pdev->dev, sizeof(*at91_shdwc), GFP_KERNEL);
34662306a36Sopenharmony_ci	if (!at91_shdwc)
34762306a36Sopenharmony_ci		return -ENOMEM;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	platform_set_drvdata(pdev, at91_shdwc);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	at91_shdwc->shdwc_base = devm_platform_ioremap_resource(pdev, 0);
35262306a36Sopenharmony_ci	if (IS_ERR(at91_shdwc->shdwc_base))
35362306a36Sopenharmony_ci		return PTR_ERR(at91_shdwc->shdwc_base);
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node);
35662306a36Sopenharmony_ci	at91_shdwc->rcfg = match->data;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL);
35962306a36Sopenharmony_ci	if (IS_ERR(at91_shdwc->sclk))
36062306a36Sopenharmony_ci		return PTR_ERR(at91_shdwc->sclk);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	ret = clk_prepare_enable(at91_shdwc->sclk);
36362306a36Sopenharmony_ci	if (ret) {
36462306a36Sopenharmony_ci		dev_err(&pdev->dev, "Could not enable slow clock\n");
36562306a36Sopenharmony_ci		return ret;
36662306a36Sopenharmony_ci	}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	at91_wakeup_status(pdev);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	at91_shdwc_dt_configure(pdev);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	np = of_find_matching_node(NULL, at91_pmc_ids);
37362306a36Sopenharmony_ci	if (!np) {
37462306a36Sopenharmony_ci		ret = -ENODEV;
37562306a36Sopenharmony_ci		goto clk_disable;
37662306a36Sopenharmony_ci	}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	at91_shdwc->pmc_base = of_iomap(np, 0);
37962306a36Sopenharmony_ci	of_node_put(np);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	if (!at91_shdwc->pmc_base) {
38262306a36Sopenharmony_ci		ret = -ENOMEM;
38362306a36Sopenharmony_ci		goto clk_disable;
38462306a36Sopenharmony_ci	}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	if (at91_shdwc->rcfg->ddrc.type_mask) {
38762306a36Sopenharmony_ci		np = of_find_compatible_node(NULL, NULL,
38862306a36Sopenharmony_ci					     "atmel,sama5d3-ddramc");
38962306a36Sopenharmony_ci		if (!np) {
39062306a36Sopenharmony_ci			ret = -ENODEV;
39162306a36Sopenharmony_ci			goto unmap;
39262306a36Sopenharmony_ci		}
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci		at91_shdwc->mpddrc_base = of_iomap(np, 0);
39562306a36Sopenharmony_ci		of_node_put(np);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci		if (!at91_shdwc->mpddrc_base) {
39862306a36Sopenharmony_ci			ret = -ENOMEM;
39962306a36Sopenharmony_ci			goto unmap;
40062306a36Sopenharmony_ci		}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci		ddr_type = readl(at91_shdwc->mpddrc_base +
40362306a36Sopenharmony_ci				 at91_shdwc->rcfg->ddrc.type_offset) &
40462306a36Sopenharmony_ci				 at91_shdwc->rcfg->ddrc.type_mask;
40562306a36Sopenharmony_ci		if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 &&
40662306a36Sopenharmony_ci		    ddr_type != AT91_DDRSDRC_MD_LPDDR3) {
40762306a36Sopenharmony_ci			iounmap(at91_shdwc->mpddrc_base);
40862306a36Sopenharmony_ci			at91_shdwc->mpddrc_base = NULL;
40962306a36Sopenharmony_ci		}
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	pm_power_off = at91_poweroff;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	return 0;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ciunmap:
41762306a36Sopenharmony_ci	iounmap(at91_shdwc->pmc_base);
41862306a36Sopenharmony_ciclk_disable:
41962306a36Sopenharmony_ci	clk_disable_unprepare(at91_shdwc->sclk);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	return ret;
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic int __exit at91_shdwc_remove(struct platform_device *pdev)
42562306a36Sopenharmony_ci{
42662306a36Sopenharmony_ci	struct shdwc *shdw = platform_get_drvdata(pdev);
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	if (pm_power_off == at91_poweroff)
42962306a36Sopenharmony_ci		pm_power_off = NULL;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	/* Reset values to disable wake-up features  */
43262306a36Sopenharmony_ci	writel(0, shdw->shdwc_base + AT91_SHDW_MR);
43362306a36Sopenharmony_ci	writel(0, shdw->shdwc_base + AT91_SHDW_WUIR);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	if (shdw->mpddrc_base)
43662306a36Sopenharmony_ci		iounmap(shdw->mpddrc_base);
43762306a36Sopenharmony_ci	iounmap(shdw->pmc_base);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	clk_disable_unprepare(shdw->sclk);
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	return 0;
44262306a36Sopenharmony_ci}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_cistatic struct platform_driver at91_shdwc_driver = {
44562306a36Sopenharmony_ci	.remove = __exit_p(at91_shdwc_remove),
44662306a36Sopenharmony_ci	.driver = {
44762306a36Sopenharmony_ci		.name = "at91-shdwc",
44862306a36Sopenharmony_ci		.of_match_table = at91_shdwc_of_match,
44962306a36Sopenharmony_ci	},
45062306a36Sopenharmony_ci};
45162306a36Sopenharmony_cimodule_platform_driver_probe(at91_shdwc_driver, at91_shdwc_probe);
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ciMODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
45462306a36Sopenharmony_ciMODULE_DESCRIPTION("Atmel shutdown controller driver");
45562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
456