18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Atmel AT91 SAM9 SoCs reset code 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2007 Atmel Corporation. 58c2ecf20Sopenharmony_ci * Copyright (C) 2011 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> 68c2ecf20Sopenharmony_ci * Copyright (C) 2014 Free Electrons 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 98c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 108c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/clk.h> 148c2ecf20Sopenharmony_ci#include <linux/io.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/of_address.h> 188c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 198c2ecf20Sopenharmony_ci#include <linux/printk.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <soc/at91/at91sam9_ddrsdr.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */ 248c2ecf20Sopenharmony_ci#define AT91_SHDW_SHDW BIT(0) /* Shut Down command */ 258c2ecf20Sopenharmony_ci#define AT91_SHDW_KEY (0xa5 << 24) /* KEY Password */ 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */ 288c2ecf20Sopenharmony_ci#define AT91_SHDW_WKMODE0 GENMASK(2, 0) /* Wake-up 0 Mode Selection */ 298c2ecf20Sopenharmony_ci#define AT91_SHDW_CPTWK0_MAX 0xf /* Maximum Counter On Wake Up 0 */ 308c2ecf20Sopenharmony_ci#define AT91_SHDW_CPTWK0 (AT91_SHDW_CPTWK0_MAX << 4) /* Counter On Wake Up 0 */ 318c2ecf20Sopenharmony_ci#define AT91_SHDW_CPTWK0_(x) ((x) << 4) 328c2ecf20Sopenharmony_ci#define AT91_SHDW_RTTWKEN BIT(16) /* Real Time Timer Wake-up Enable */ 338c2ecf20Sopenharmony_ci#define AT91_SHDW_RTCWKEN BIT(17) /* Real Time Clock Wake-up Enable */ 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define AT91_SHDW_SR 0x08 /* Shut Down Status Register */ 368c2ecf20Sopenharmony_ci#define AT91_SHDW_WAKEUP0 BIT(0) /* Wake-up 0 Status */ 378c2ecf20Sopenharmony_ci#define AT91_SHDW_RTTWK BIT(16) /* Real-time Timer Wake-up */ 388c2ecf20Sopenharmony_ci#define AT91_SHDW_RTCWK BIT(17) /* Real-time Clock Wake-up [SAM9RL] */ 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cienum wakeup_type { 418c2ecf20Sopenharmony_ci AT91_SHDW_WKMODE0_NONE = 0, 428c2ecf20Sopenharmony_ci AT91_SHDW_WKMODE0_HIGH = 1, 438c2ecf20Sopenharmony_ci AT91_SHDW_WKMODE0_LOW = 2, 448c2ecf20Sopenharmony_ci AT91_SHDW_WKMODE0_ANYLEVEL = 3, 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic const char *shdwc_wakeup_modes[] = { 488c2ecf20Sopenharmony_ci [AT91_SHDW_WKMODE0_NONE] = "none", 498c2ecf20Sopenharmony_ci [AT91_SHDW_WKMODE0_HIGH] = "high", 508c2ecf20Sopenharmony_ci [AT91_SHDW_WKMODE0_LOW] = "low", 518c2ecf20Sopenharmony_ci [AT91_SHDW_WKMODE0_ANYLEVEL] = "any", 528c2ecf20Sopenharmony_ci}; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic struct shdwc { 558c2ecf20Sopenharmony_ci struct clk *sclk; 568c2ecf20Sopenharmony_ci void __iomem *shdwc_base; 578c2ecf20Sopenharmony_ci void __iomem *mpddrc_base; 588c2ecf20Sopenharmony_ci} at91_shdwc; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic void __init at91_wakeup_status(struct platform_device *pdev) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci const char *reason; 638c2ecf20Sopenharmony_ci u32 reg = readl(at91_shdwc.shdwc_base + AT91_SHDW_SR); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci /* Simple power-on, just bail out */ 668c2ecf20Sopenharmony_ci if (!reg) 678c2ecf20Sopenharmony_ci return; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci if (reg & AT91_SHDW_RTTWK) 708c2ecf20Sopenharmony_ci reason = "RTT"; 718c2ecf20Sopenharmony_ci else if (reg & AT91_SHDW_RTCWK) 728c2ecf20Sopenharmony_ci reason = "RTC"; 738c2ecf20Sopenharmony_ci else 748c2ecf20Sopenharmony_ci reason = "unknown"; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "Wake-Up source: %s\n", reason); 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic void at91_poweroff(void) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci asm volatile( 828c2ecf20Sopenharmony_ci /* Align to cache lines */ 838c2ecf20Sopenharmony_ci ".balign 32\n\t" 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* Ensure AT91_SHDW_CR is in the TLB by reading it */ 868c2ecf20Sopenharmony_ci " ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci /* Power down SDRAM0 */ 898c2ecf20Sopenharmony_ci " tst %0, #0\n\t" 908c2ecf20Sopenharmony_ci " beq 1f\n\t" 918c2ecf20Sopenharmony_ci " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t" 928c2ecf20Sopenharmony_ci /* Shutdown CPU */ 938c2ecf20Sopenharmony_ci "1: str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci " b .\n\t" 968c2ecf20Sopenharmony_ci : 978c2ecf20Sopenharmony_ci : "r" (at91_shdwc.mpddrc_base), 988c2ecf20Sopenharmony_ci "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), 998c2ecf20Sopenharmony_ci "r" (at91_shdwc.shdwc_base), 1008c2ecf20Sopenharmony_ci "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW) 1018c2ecf20Sopenharmony_ci : "r6"); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int at91_poweroff_get_wakeup_mode(struct device_node *np) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci const char *pm; 1078c2ecf20Sopenharmony_ci unsigned int i; 1088c2ecf20Sopenharmony_ci int err; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci err = of_property_read_string(np, "atmel,wakeup-mode", &pm); 1118c2ecf20Sopenharmony_ci if (err < 0) 1128c2ecf20Sopenharmony_ci return AT91_SHDW_WKMODE0_ANYLEVEL; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(shdwc_wakeup_modes); i++) 1158c2ecf20Sopenharmony_ci if (!strcasecmp(pm, shdwc_wakeup_modes[i])) 1168c2ecf20Sopenharmony_ci return i; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci return -ENODEV; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1248c2ecf20Sopenharmony_ci int wakeup_mode; 1258c2ecf20Sopenharmony_ci u32 mode = 0, tmp; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci wakeup_mode = at91_poweroff_get_wakeup_mode(np); 1288c2ecf20Sopenharmony_ci if (wakeup_mode < 0) { 1298c2ecf20Sopenharmony_ci dev_warn(&pdev->dev, "shdwc unknown wakeup mode\n"); 1308c2ecf20Sopenharmony_ci return; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "atmel,wakeup-counter", &tmp)) { 1348c2ecf20Sopenharmony_ci if (tmp > AT91_SHDW_CPTWK0_MAX) { 1358c2ecf20Sopenharmony_ci dev_warn(&pdev->dev, 1368c2ecf20Sopenharmony_ci "shdwc wakeup counter 0x%x > 0x%x reduce it to 0x%x\n", 1378c2ecf20Sopenharmony_ci tmp, AT91_SHDW_CPTWK0_MAX, AT91_SHDW_CPTWK0_MAX); 1388c2ecf20Sopenharmony_ci tmp = AT91_SHDW_CPTWK0_MAX; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci mode |= AT91_SHDW_CPTWK0_(tmp); 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) 1448c2ecf20Sopenharmony_ci mode |= AT91_SHDW_RTCWKEN; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) 1478c2ecf20Sopenharmony_ci mode |= AT91_SHDW_RTTWKEN; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci writel(wakeup_mode | mode, at91_shdwc.shdwc_base + AT91_SHDW_MR); 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic int __init at91_poweroff_probe(struct platform_device *pdev) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct resource *res; 1558c2ecf20Sopenharmony_ci struct device_node *np; 1568c2ecf20Sopenharmony_ci u32 ddr_type; 1578c2ecf20Sopenharmony_ci int ret; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1608c2ecf20Sopenharmony_ci at91_shdwc.shdwc_base = devm_ioremap_resource(&pdev->dev, res); 1618c2ecf20Sopenharmony_ci if (IS_ERR(at91_shdwc.shdwc_base)) 1628c2ecf20Sopenharmony_ci return PTR_ERR(at91_shdwc.shdwc_base); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci at91_shdwc.sclk = devm_clk_get(&pdev->dev, NULL); 1658c2ecf20Sopenharmony_ci if (IS_ERR(at91_shdwc.sclk)) 1668c2ecf20Sopenharmony_ci return PTR_ERR(at91_shdwc.sclk); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci ret = clk_prepare_enable(at91_shdwc.sclk); 1698c2ecf20Sopenharmony_ci if (ret) { 1708c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not enable slow clock\n"); 1718c2ecf20Sopenharmony_ci return ret; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci at91_wakeup_status(pdev); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci if (pdev->dev.of_node) 1778c2ecf20Sopenharmony_ci at91_poweroff_dt_set_wakeup_mode(pdev); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc"); 1808c2ecf20Sopenharmony_ci if (np) { 1818c2ecf20Sopenharmony_ci at91_shdwc.mpddrc_base = of_iomap(np, 0); 1828c2ecf20Sopenharmony_ci of_node_put(np); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci if (!at91_shdwc.mpddrc_base) { 1858c2ecf20Sopenharmony_ci ret = -ENOMEM; 1868c2ecf20Sopenharmony_ci goto clk_disable; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci ddr_type = readl(at91_shdwc.mpddrc_base + AT91_DDRSDRC_MDR) & 1908c2ecf20Sopenharmony_ci AT91_DDRSDRC_MD; 1918c2ecf20Sopenharmony_ci if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && 1928c2ecf20Sopenharmony_ci ddr_type != AT91_DDRSDRC_MD_LPDDR3) { 1938c2ecf20Sopenharmony_ci iounmap(at91_shdwc.mpddrc_base); 1948c2ecf20Sopenharmony_ci at91_shdwc.mpddrc_base = NULL; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci pm_power_off = at91_poweroff; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return 0; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ciclk_disable: 2038c2ecf20Sopenharmony_ci clk_disable_unprepare(at91_shdwc.sclk); 2048c2ecf20Sopenharmony_ci return ret; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic int __exit at91_poweroff_remove(struct platform_device *pdev) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci if (pm_power_off == at91_poweroff) 2108c2ecf20Sopenharmony_ci pm_power_off = NULL; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (at91_shdwc.mpddrc_base) 2138c2ecf20Sopenharmony_ci iounmap(at91_shdwc.mpddrc_base); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci clk_disable_unprepare(at91_shdwc.sclk); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci return 0; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic const struct of_device_id at91_poweroff_of_match[] = { 2218c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9260-shdwc", }, 2228c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9rl-shdwc", }, 2238c2ecf20Sopenharmony_ci { .compatible = "atmel,at91sam9x5-shdwc", }, 2248c2ecf20Sopenharmony_ci { /*sentinel*/ } 2258c2ecf20Sopenharmony_ci}; 2268c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, at91_poweroff_of_match); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic struct platform_driver at91_poweroff_driver = { 2298c2ecf20Sopenharmony_ci .remove = __exit_p(at91_poweroff_remove), 2308c2ecf20Sopenharmony_ci .driver = { 2318c2ecf20Sopenharmony_ci .name = "at91-poweroff", 2328c2ecf20Sopenharmony_ci .of_match_table = at91_poweroff_of_match, 2338c2ecf20Sopenharmony_ci }, 2348c2ecf20Sopenharmony_ci}; 2358c2ecf20Sopenharmony_cimodule_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ciMODULE_AUTHOR("Atmel Corporation"); 2388c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Shutdown driver for Atmel SoCs"); 2398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 240