brintos

brintos / linux-shallow public Read only

0
0
Text · 10.9 KiB · 959ce0d Raw
455 lines · c
1/*2 * Atmel SAMA5D2-Compatible Shutdown Controller (SHDWC) driver.3 * Found on some SoCs as the sama5d2 (obviously).4 *5 * Copyright (C) 2015 Atmel Corporation,6 *                    Nicolas Ferre <nicolas.ferre@atmel.com>7 *8 * Evolved from driver at91-poweroff.c.9 *10 * This file is licensed under the terms of the GNU General Public11 * License version 2.  This program is licensed "as is" without any12 * warranty of any kind, whether express or implied.13 *14 * TODO:15 * - addition to status of other wake-up inputs [1 - 15]16 * - Analog Comparator wake-up alarm17 * - Serial RX wake-up alarm18 * - low power debouncer19 */20 21#include <linux/clk.h>22#include <linux/clk/at91_pmc.h>23#include <linux/io.h>24#include <linux/module.h>25#include <linux/of.h>26#include <linux/of_address.h>27#include <linux/platform_device.h>28#include <linux/printk.h>29 30#include <soc/at91/at91sam9_ddrsdr.h>31 32#define SLOW_CLOCK_FREQ	3276833 34#define AT91_SHDW_CR	0x00		/* Shut Down Control Register */35#define AT91_SHDW_SHDW		BIT(0)			/* Shut Down command */36#define AT91_SHDW_KEY		(0xa5UL << 24)		/* KEY Password */37 38#define AT91_SHDW_MR	0x04		/* Shut Down Mode Register */39#define AT91_SHDW_WKUPDBC_SHIFT	2440#define AT91_SHDW_WKUPDBC_MASK	GENMASK(26, 24)41#define AT91_SHDW_WKUPDBC(x)	(((x) << AT91_SHDW_WKUPDBC_SHIFT) \42						& AT91_SHDW_WKUPDBC_MASK)43 44#define AT91_SHDW_SR	0x08		/* Shut Down Status Register */45#define AT91_SHDW_WKUPIS_SHIFT	1646#define AT91_SHDW_WKUPIS_MASK	GENMASK(31, 16)47#define AT91_SHDW_WKUPIS(x)	((1 << (x)) << AT91_SHDW_WKUPIS_SHIFT \48						& AT91_SHDW_WKUPIS_MASK)49 50#define AT91_SHDW_WUIR	0x0c		/* Shutdown Wake-up Inputs Register */51#define AT91_SHDW_WKUPEN_MASK	GENMASK(15, 0)52#define AT91_SHDW_WKUPEN(x)	((1 << (x)) & AT91_SHDW_WKUPEN_MASK)53#define AT91_SHDW_WKUPT_SHIFT	1654#define AT91_SHDW_WKUPT_MASK	GENMASK(31, 16)55#define AT91_SHDW_WKUPT(x)	((1 << (x)) << AT91_SHDW_WKUPT_SHIFT \56						& AT91_SHDW_WKUPT_MASK)57 58#define SHDW_WK_PIN(reg, cfg)	((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input))59#define SHDW_RTCWK(reg, cfg)	(((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1)60#define SHDW_RTTWK(reg, cfg)	(((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1)61#define SHDW_RTCWKEN(cfg)	(1 << ((cfg)->mr_rtcwk_shift))62#define SHDW_RTTWKEN(cfg)	(1 << ((cfg)->mr_rttwk_shift))63 64#define DBC_PERIOD_US(x)	DIV_ROUND_UP_ULL((1000000 * (x)), \65							SLOW_CLOCK_FREQ)66 67#define SHDW_CFG_NOT_USED	(32)68 69struct shdwc_reg_config {70	u8 wkup_pin_input;71	u8 mr_rtcwk_shift;72	u8 mr_rttwk_shift;73	u8 sr_rtcwk_shift;74	u8 sr_rttwk_shift;75};76 77struct pmc_reg_config {78	u8 mckr;79};80 81struct ddrc_reg_config {82	u32 type_offset;83	u32 type_mask;84};85 86struct reg_config {87	struct shdwc_reg_config shdwc;88	struct pmc_reg_config pmc;89	struct ddrc_reg_config ddrc;90};91 92struct shdwc {93	const struct reg_config *rcfg;94	struct clk *sclk;95	void __iomem *shdwc_base;96	void __iomem *mpddrc_base;97	void __iomem *pmc_base;98};99 100/*101 * Hold configuration here, cannot be more than one instance of the driver102 * since pm_power_off itself is global.103 */104static struct shdwc *at91_shdwc;105 106static const unsigned long long sdwc_dbc_period[] = {107	0, 3, 32, 512, 4096, 32768,108};109 110static void at91_wakeup_status(struct platform_device *pdev)111{112	struct shdwc *shdw = platform_get_drvdata(pdev);113	const struct reg_config *rcfg = shdw->rcfg;114	u32 reg;115	char *reason = "unknown";116 117	reg = readl(shdw->shdwc_base + AT91_SHDW_SR);118 119	dev_dbg(&pdev->dev, "%s: status = %#x\n", __func__, reg);120 121	/* Simple power-on, just bail out */122	if (!reg)123		return;124 125	if (SHDW_WK_PIN(reg, &rcfg->shdwc))126		reason = "WKUP pin";127	else if (SHDW_RTCWK(reg, &rcfg->shdwc))128		reason = "RTC";129	else if (SHDW_RTTWK(reg, &rcfg->shdwc))130		reason = "RTT";131 132	pr_info("AT91: Wake-Up source: %s\n", reason);133}134 135static void at91_poweroff(void)136{137	asm volatile(138		/* Align to cache lines */139		".balign 32\n\t"140 141		/* Ensure AT91_SHDW_CR is in the TLB by reading it */142		"	ldr	r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"143 144		/* Power down SDRAM0 */145		"	tst	%0, #0\n\t"146		"	beq	1f\n\t"147		"	str	%1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"148 149		/* Switch the master clock source to slow clock. */150		"1:	ldr	r6, [%4, %5]\n\t"151		"	bic	r6, r6,  #" __stringify(AT91_PMC_CSS) "\n\t"152		"	str	r6, [%4, %5]\n\t"153		/* Wait for clock switch. */154		"2:	ldr	r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t"155		"	tst	r6, #"	    __stringify(AT91_PMC_MCKRDY) "\n\t"156		"	beq	2b\n\t"157 158		/* Shutdown CPU */159		"	str	%3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"160 161		"	b	.\n\t"162		:163		: "r" (at91_shdwc->mpddrc_base),164		  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),165		  "r" (at91_shdwc->shdwc_base),166		  "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW),167		  "r" (at91_shdwc->pmc_base),168		  "r" (at91_shdwc->rcfg->pmc.mckr)169		: "r6");170}171 172static u32 at91_shdwc_debouncer_value(struct platform_device *pdev,173				      u32 in_period_us)174{175	int i;176	int max_idx = ARRAY_SIZE(sdwc_dbc_period) - 1;177	unsigned long long period_us;178	unsigned long long max_period_us = DBC_PERIOD_US(sdwc_dbc_period[max_idx]);179 180	if (in_period_us > max_period_us) {181		dev_warn(&pdev->dev,182			 "debouncer period %u too big, reduced to %llu us\n",183			 in_period_us, max_period_us);184		return max_idx;185	}186 187	for (i = max_idx - 1; i > 0; i--) {188		period_us = DBC_PERIOD_US(sdwc_dbc_period[i]);189		dev_dbg(&pdev->dev, "%s: ref[%d] = %llu\n",190						__func__, i, period_us);191		if (in_period_us > period_us)192			break;193	}194 195	return i + 1;196}197 198static u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev,199				       struct device_node *np)200{201	struct device_node *cnp;202	u32 wk_input_mask;203	u32 wuir = 0;204	u32 wk_input;205 206	for_each_child_of_node(np, cnp) {207		if (of_property_read_u32(cnp, "reg", &wk_input)) {208			dev_warn(&pdev->dev, "reg property is missing for %pOF\n",209				 cnp);210			continue;211		}212 213		wk_input_mask = 1 << wk_input;214		if (!(wk_input_mask & AT91_SHDW_WKUPEN_MASK)) {215			dev_warn(&pdev->dev,216				 "wake-up input %d out of bounds ignore\n",217				 wk_input);218			continue;219		}220		wuir |= wk_input_mask;221 222		if (of_property_read_bool(cnp, "atmel,wakeup-active-high"))223			wuir |= AT91_SHDW_WKUPT(wk_input);224 225		dev_dbg(&pdev->dev, "%s: (child %d) wuir = %#x\n",226						__func__, wk_input, wuir);227	}228 229	return wuir;230}231 232static void at91_shdwc_dt_configure(struct platform_device *pdev)233{234	struct shdwc *shdw = platform_get_drvdata(pdev);235	const struct reg_config *rcfg = shdw->rcfg;236	struct device_node *np = pdev->dev.of_node;237	u32 mode = 0, tmp, input;238 239	if (!np) {240		dev_err(&pdev->dev, "device node not found\n");241		return;242	}243 244	if (!of_property_read_u32(np, "debounce-delay-us", &tmp))245		mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp));246 247	if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))248		mode |= SHDW_RTCWKEN(&rcfg->shdwc);249 250	if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))251		mode |= SHDW_RTTWKEN(&rcfg->shdwc);252 253	dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);254	writel(mode, shdw->shdwc_base + AT91_SHDW_MR);255 256	input = at91_shdwc_get_wakeup_input(pdev, np);257	writel(input, shdw->shdwc_base + AT91_SHDW_WUIR);258}259 260static const struct reg_config sama5d2_reg_config = {261	.shdwc = {262		.wkup_pin_input = 0,263		.mr_rtcwk_shift = 17,264		.mr_rttwk_shift	= SHDW_CFG_NOT_USED,265		.sr_rtcwk_shift = 5,266		.sr_rttwk_shift = SHDW_CFG_NOT_USED,267	},268	.pmc = {269		.mckr		= 0x30,270	},271	.ddrc = {272		.type_offset	= AT91_DDRSDRC_MDR,273		.type_mask	= AT91_DDRSDRC_MD274	},275};276 277static const struct reg_config sam9x60_reg_config = {278	.shdwc = {279		.wkup_pin_input = 0,280		.mr_rtcwk_shift = 17,281		.mr_rttwk_shift = 16,282		.sr_rtcwk_shift = 5,283		.sr_rttwk_shift = 4,284	},285	.pmc = {286		.mckr		= 0x28,287	},288	.ddrc = {289		.type_offset	= AT91_DDRSDRC_MDR,290		.type_mask	= AT91_DDRSDRC_MD291	},292};293 294static const struct reg_config sama7g5_reg_config = {295	.shdwc = {296		.wkup_pin_input = 0,297		.mr_rtcwk_shift = 17,298		.mr_rttwk_shift = 16,299		.sr_rtcwk_shift = 5,300		.sr_rttwk_shift = 4,301	},302	.pmc = {303		.mckr		= 0x28,304	},305};306 307static const struct of_device_id at91_shdwc_of_match[] = {308	{309		.compatible = "atmel,sama5d2-shdwc",310		.data = &sama5d2_reg_config,311	},312	{313		.compatible = "microchip,sam9x60-shdwc",314		.data = &sam9x60_reg_config,315	},316	{317		.compatible = "microchip,sama7g5-shdwc",318		.data = &sama7g5_reg_config,319	}, {320		/*sentinel*/321	}322};323MODULE_DEVICE_TABLE(of, at91_shdwc_of_match);324 325static const struct of_device_id at91_pmc_ids[] = {326	{ .compatible = "atmel,sama5d2-pmc" },327	{ .compatible = "microchip,sam9x60-pmc" },328	{ .compatible = "microchip,sama7g5-pmc" },329	{ /* Sentinel. */ }330};331 332static int at91_shdwc_probe(struct platform_device *pdev)333{334	const struct of_device_id *match;335	struct device_node *np;336	u32 ddr_type;337	int ret;338 339	if (!pdev->dev.of_node)340		return -ENODEV;341 342	if (at91_shdwc)343		return -EBUSY;344 345	at91_shdwc = devm_kzalloc(&pdev->dev, sizeof(*at91_shdwc), GFP_KERNEL);346	if (!at91_shdwc)347		return -ENOMEM;348 349	platform_set_drvdata(pdev, at91_shdwc);350 351	at91_shdwc->shdwc_base = devm_platform_ioremap_resource(pdev, 0);352	if (IS_ERR(at91_shdwc->shdwc_base))353		return PTR_ERR(at91_shdwc->shdwc_base);354 355	match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node);356	at91_shdwc->rcfg = match->data;357 358	at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL);359	if (IS_ERR(at91_shdwc->sclk))360		return PTR_ERR(at91_shdwc->sclk);361 362	ret = clk_prepare_enable(at91_shdwc->sclk);363	if (ret) {364		dev_err(&pdev->dev, "Could not enable slow clock\n");365		return ret;366	}367 368	at91_wakeup_status(pdev);369 370	at91_shdwc_dt_configure(pdev);371 372	np = of_find_matching_node(NULL, at91_pmc_ids);373	if (!np) {374		ret = -ENODEV;375		goto clk_disable;376	}377 378	at91_shdwc->pmc_base = of_iomap(np, 0);379	of_node_put(np);380 381	if (!at91_shdwc->pmc_base) {382		ret = -ENOMEM;383		goto clk_disable;384	}385 386	if (at91_shdwc->rcfg->ddrc.type_mask) {387		np = of_find_compatible_node(NULL, NULL,388					     "atmel,sama5d3-ddramc");389		if (!np) {390			ret = -ENODEV;391			goto unmap;392		}393 394		at91_shdwc->mpddrc_base = of_iomap(np, 0);395		of_node_put(np);396 397		if (!at91_shdwc->mpddrc_base) {398			ret = -ENOMEM;399			goto unmap;400		}401 402		ddr_type = readl(at91_shdwc->mpddrc_base +403				 at91_shdwc->rcfg->ddrc.type_offset) &404				 at91_shdwc->rcfg->ddrc.type_mask;405		if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 &&406		    ddr_type != AT91_DDRSDRC_MD_LPDDR3) {407			iounmap(at91_shdwc->mpddrc_base);408			at91_shdwc->mpddrc_base = NULL;409		}410	}411 412	pm_power_off = at91_poweroff;413 414	return 0;415 416unmap:417	iounmap(at91_shdwc->pmc_base);418clk_disable:419	clk_disable_unprepare(at91_shdwc->sclk);420 421	return ret;422}423 424static void at91_shdwc_remove(struct platform_device *pdev)425{426	struct shdwc *shdw = platform_get_drvdata(pdev);427 428	if (pm_power_off == at91_poweroff)429		pm_power_off = NULL;430 431	/* Reset values to disable wake-up features  */432	writel(0, shdw->shdwc_base + AT91_SHDW_MR);433	writel(0, shdw->shdwc_base + AT91_SHDW_WUIR);434 435	if (shdw->mpddrc_base)436		iounmap(shdw->mpddrc_base);437	iounmap(shdw->pmc_base);438 439	clk_disable_unprepare(shdw->sclk);440}441 442static struct platform_driver at91_shdwc_driver = {443	.probe = at91_shdwc_probe,444	.remove_new = at91_shdwc_remove,445	.driver = {446		.name = "at91-shdwc",447		.of_match_table = at91_shdwc_of_match,448	},449};450module_platform_driver(at91_shdwc_driver);451 452MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");453MODULE_DESCRIPTION("Atmel shutdown controller driver");454MODULE_LICENSE("GPL v2");455