18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Atmel SAMA5D2-Compatible Shutdown Controller (SHDWC) driver. 38c2ecf20Sopenharmony_ci * Found on some SoCs as the sama5d2 (obviously). 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015 Atmel Corporation, 68c2ecf20Sopenharmony_ci * Nicolas Ferre <nicolas.ferre@atmel.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Evolved from driver at91-poweroff.c. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 118c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 128c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * TODO: 158c2ecf20Sopenharmony_ci * - addition to status of other wake-up inputs [1 - 15] 168c2ecf20Sopenharmony_ci * - Analog Comparator wake-up alarm 178c2ecf20Sopenharmony_ci * - Serial RX wake-up alarm 188c2ecf20Sopenharmony_ci * - low power debouncer 198c2ecf20Sopenharmony_ci */ 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <linux/clk.h> 228c2ecf20Sopenharmony_ci#include <linux/clk/at91_pmc.h> 238c2ecf20Sopenharmony_ci#include <linux/io.h> 248c2ecf20Sopenharmony_ci#include <linux/module.h> 258c2ecf20Sopenharmony_ci#include <linux/of.h> 268c2ecf20Sopenharmony_ci#include <linux/of_address.h> 278c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 288c2ecf20Sopenharmony_ci#include <linux/printk.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <soc/at91/at91sam9_ddrsdr.h> 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define SLOW_CLOCK_FREQ 32768 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */ 358c2ecf20Sopenharmony_ci#define AT91_SHDW_SHDW BIT(0) /* Shut Down command */ 368c2ecf20Sopenharmony_ci#define AT91_SHDW_KEY (0xa5UL << 24) /* KEY Password */ 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */ 398c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPDBC_SHIFT 24 408c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPDBC_MASK GENMASK(26, 24) 418c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPDBC(x) (((x) << AT91_SHDW_WKUPDBC_SHIFT) \ 428c2ecf20Sopenharmony_ci & AT91_SHDW_WKUPDBC_MASK) 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define AT91_SHDW_SR 0x08 /* Shut Down Status Register */ 458c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPIS_SHIFT 16 468c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPIS_MASK GENMASK(31, 16) 478c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPIS(x) ((1 << (x)) << AT91_SHDW_WKUPIS_SHIFT \ 488c2ecf20Sopenharmony_ci & AT91_SHDW_WKUPIS_MASK) 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#define AT91_SHDW_WUIR 0x0c /* Shutdown Wake-up Inputs Register */ 518c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPEN_MASK GENMASK(15, 0) 528c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPEN(x) ((1 << (x)) & AT91_SHDW_WKUPEN_MASK) 538c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPT_SHIFT 16 548c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPT_MASK GENMASK(31, 16) 558c2ecf20Sopenharmony_ci#define AT91_SHDW_WKUPT(x) ((1 << (x)) << AT91_SHDW_WKUPT_SHIFT \ 568c2ecf20Sopenharmony_ci & AT91_SHDW_WKUPT_MASK) 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input)) 598c2ecf20Sopenharmony_ci#define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1) 608c2ecf20Sopenharmony_ci#define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1) 618c2ecf20Sopenharmony_ci#define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift)) 628c2ecf20Sopenharmony_ci#define SHDW_RTTWKEN(cfg) (1 << ((cfg)->mr_rttwk_shift)) 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci#define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \ 658c2ecf20Sopenharmony_ci SLOW_CLOCK_FREQ) 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define SHDW_CFG_NOT_USED (32) 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistruct shdwc_reg_config { 708c2ecf20Sopenharmony_ci u8 wkup_pin_input; 718c2ecf20Sopenharmony_ci u8 mr_rtcwk_shift; 728c2ecf20Sopenharmony_ci u8 mr_rttwk_shift; 738c2ecf20Sopenharmony_ci u8 sr_rtcwk_shift; 748c2ecf20Sopenharmony_ci u8 sr_rttwk_shift; 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistruct pmc_reg_config { 788c2ecf20Sopenharmony_ci u8 mckr; 798c2ecf20Sopenharmony_ci}; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistruct reg_config { 828c2ecf20Sopenharmony_ci struct shdwc_reg_config shdwc; 838c2ecf20Sopenharmony_ci struct pmc_reg_config pmc; 848c2ecf20Sopenharmony_ci}; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistruct shdwc { 878c2ecf20Sopenharmony_ci const struct reg_config *rcfg; 888c2ecf20Sopenharmony_ci struct clk *sclk; 898c2ecf20Sopenharmony_ci void __iomem *shdwc_base; 908c2ecf20Sopenharmony_ci void __iomem *mpddrc_base; 918c2ecf20Sopenharmony_ci void __iomem *pmc_base; 928c2ecf20Sopenharmony_ci}; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci/* 958c2ecf20Sopenharmony_ci * Hold configuration here, cannot be more than one instance of the driver 968c2ecf20Sopenharmony_ci * since pm_power_off itself is global. 978c2ecf20Sopenharmony_ci */ 988c2ecf20Sopenharmony_cistatic struct shdwc *at91_shdwc; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic const unsigned long long sdwc_dbc_period[] = { 1018c2ecf20Sopenharmony_ci 0, 3, 32, 512, 4096, 32768, 1028c2ecf20Sopenharmony_ci}; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic void __init at91_wakeup_status(struct platform_device *pdev) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct shdwc *shdw = platform_get_drvdata(pdev); 1078c2ecf20Sopenharmony_ci const struct reg_config *rcfg = shdw->rcfg; 1088c2ecf20Sopenharmony_ci u32 reg; 1098c2ecf20Sopenharmony_ci char *reason = "unknown"; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci reg = readl(shdw->shdwc_base + AT91_SHDW_SR); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "%s: status = %#x\n", __func__, reg); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci /* Simple power-on, just bail out */ 1168c2ecf20Sopenharmony_ci if (!reg) 1178c2ecf20Sopenharmony_ci return; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (SHDW_WK_PIN(reg, &rcfg->shdwc)) 1208c2ecf20Sopenharmony_ci reason = "WKUP pin"; 1218c2ecf20Sopenharmony_ci else if (SHDW_RTCWK(reg, &rcfg->shdwc)) 1228c2ecf20Sopenharmony_ci reason = "RTC"; 1238c2ecf20Sopenharmony_ci else if (SHDW_RTTWK(reg, &rcfg->shdwc)) 1248c2ecf20Sopenharmony_ci reason = "RTT"; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci pr_info("AT91: Wake-Up source: %s\n", reason); 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic void at91_poweroff(void) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci asm volatile( 1328c2ecf20Sopenharmony_ci /* Align to cache lines */ 1338c2ecf20Sopenharmony_ci ".balign 32\n\t" 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* Ensure AT91_SHDW_CR is in the TLB by reading it */ 1368c2ecf20Sopenharmony_ci " ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* Power down SDRAM0 */ 1398c2ecf20Sopenharmony_ci " tst %0, #0\n\t" 1408c2ecf20Sopenharmony_ci " beq 1f\n\t" 1418c2ecf20Sopenharmony_ci " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t" 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* Switch the master clock source to slow clock. */ 1448c2ecf20Sopenharmony_ci "1: ldr r6, [%4, %5]\n\t" 1458c2ecf20Sopenharmony_ci " bic r6, r6, #" __stringify(AT91_PMC_CSS) "\n\t" 1468c2ecf20Sopenharmony_ci " str r6, [%4, %5]\n\t" 1478c2ecf20Sopenharmony_ci /* Wait for clock switch. */ 1488c2ecf20Sopenharmony_ci "2: ldr r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t" 1498c2ecf20Sopenharmony_ci " tst r6, #" __stringify(AT91_PMC_MCKRDY) "\n\t" 1508c2ecf20Sopenharmony_ci " beq 2b\n\t" 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* Shutdown CPU */ 1538c2ecf20Sopenharmony_ci " str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci " b .\n\t" 1568c2ecf20Sopenharmony_ci : 1578c2ecf20Sopenharmony_ci : "r" (at91_shdwc->mpddrc_base), 1588c2ecf20Sopenharmony_ci "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), 1598c2ecf20Sopenharmony_ci "r" (at91_shdwc->shdwc_base), 1608c2ecf20Sopenharmony_ci "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW), 1618c2ecf20Sopenharmony_ci "r" (at91_shdwc->pmc_base), 1628c2ecf20Sopenharmony_ci "r" (at91_shdwc->rcfg->pmc.mckr) 1638c2ecf20Sopenharmony_ci : "r6"); 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic u32 at91_shdwc_debouncer_value(struct platform_device *pdev, 1678c2ecf20Sopenharmony_ci u32 in_period_us) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci int i; 1708c2ecf20Sopenharmony_ci int max_idx = ARRAY_SIZE(sdwc_dbc_period) - 1; 1718c2ecf20Sopenharmony_ci unsigned long long period_us; 1728c2ecf20Sopenharmony_ci unsigned long long max_period_us = DBC_PERIOD_US(sdwc_dbc_period[max_idx]); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (in_period_us > max_period_us) { 1758c2ecf20Sopenharmony_ci dev_warn(&pdev->dev, 1768c2ecf20Sopenharmony_ci "debouncer period %u too big, reduced to %llu us\n", 1778c2ecf20Sopenharmony_ci in_period_us, max_period_us); 1788c2ecf20Sopenharmony_ci return max_idx; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci for (i = max_idx - 1; i > 0; i--) { 1828c2ecf20Sopenharmony_ci period_us = DBC_PERIOD_US(sdwc_dbc_period[i]); 1838c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "%s: ref[%d] = %llu\n", 1848c2ecf20Sopenharmony_ci __func__, i, period_us); 1858c2ecf20Sopenharmony_ci if (in_period_us > period_us) 1868c2ecf20Sopenharmony_ci break; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return i + 1; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev, 1938c2ecf20Sopenharmony_ci struct device_node *np) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci struct device_node *cnp; 1968c2ecf20Sopenharmony_ci u32 wk_input_mask; 1978c2ecf20Sopenharmony_ci u32 wuir = 0; 1988c2ecf20Sopenharmony_ci u32 wk_input; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci for_each_child_of_node(np, cnp) { 2018c2ecf20Sopenharmony_ci if (of_property_read_u32(cnp, "reg", &wk_input)) { 2028c2ecf20Sopenharmony_ci dev_warn(&pdev->dev, "reg property is missing for %pOF\n", 2038c2ecf20Sopenharmony_ci cnp); 2048c2ecf20Sopenharmony_ci continue; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci wk_input_mask = 1 << wk_input; 2088c2ecf20Sopenharmony_ci if (!(wk_input_mask & AT91_SHDW_WKUPEN_MASK)) { 2098c2ecf20Sopenharmony_ci dev_warn(&pdev->dev, 2108c2ecf20Sopenharmony_ci "wake-up input %d out of bounds ignore\n", 2118c2ecf20Sopenharmony_ci wk_input); 2128c2ecf20Sopenharmony_ci continue; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci wuir |= wk_input_mask; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci if (of_property_read_bool(cnp, "atmel,wakeup-active-high")) 2178c2ecf20Sopenharmony_ci wuir |= AT91_SHDW_WKUPT(wk_input); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "%s: (child %d) wuir = %#x\n", 2208c2ecf20Sopenharmony_ci __func__, wk_input, wuir); 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci return wuir; 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cistatic void at91_shdwc_dt_configure(struct platform_device *pdev) 2278c2ecf20Sopenharmony_ci{ 2288c2ecf20Sopenharmony_ci struct shdwc *shdw = platform_get_drvdata(pdev); 2298c2ecf20Sopenharmony_ci const struct reg_config *rcfg = shdw->rcfg; 2308c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 2318c2ecf20Sopenharmony_ci u32 mode = 0, tmp, input; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (!np) { 2348c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "device node not found\n"); 2358c2ecf20Sopenharmony_ci return; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "debounce-delay-us", &tmp)) 2398c2ecf20Sopenharmony_ci mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp)); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) 2428c2ecf20Sopenharmony_ci mode |= SHDW_RTCWKEN(&rcfg->shdwc); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) 2458c2ecf20Sopenharmony_ci mode |= SHDW_RTTWKEN(&rcfg->shdwc); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode); 2488c2ecf20Sopenharmony_ci writel(mode, shdw->shdwc_base + AT91_SHDW_MR); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci input = at91_shdwc_get_wakeup_input(pdev, np); 2518c2ecf20Sopenharmony_ci writel(input, shdw->shdwc_base + AT91_SHDW_WUIR); 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic const struct reg_config sama5d2_reg_config = { 2558c2ecf20Sopenharmony_ci .shdwc = { 2568c2ecf20Sopenharmony_ci .wkup_pin_input = 0, 2578c2ecf20Sopenharmony_ci .mr_rtcwk_shift = 17, 2588c2ecf20Sopenharmony_ci .mr_rttwk_shift = SHDW_CFG_NOT_USED, 2598c2ecf20Sopenharmony_ci .sr_rtcwk_shift = 5, 2608c2ecf20Sopenharmony_ci .sr_rttwk_shift = SHDW_CFG_NOT_USED, 2618c2ecf20Sopenharmony_ci }, 2628c2ecf20Sopenharmony_ci .pmc = { 2638c2ecf20Sopenharmony_ci .mckr = 0x30, 2648c2ecf20Sopenharmony_ci }, 2658c2ecf20Sopenharmony_ci}; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic const struct reg_config sam9x60_reg_config = { 2688c2ecf20Sopenharmony_ci .shdwc = { 2698c2ecf20Sopenharmony_ci .wkup_pin_input = 0, 2708c2ecf20Sopenharmony_ci .mr_rtcwk_shift = 17, 2718c2ecf20Sopenharmony_ci .mr_rttwk_shift = 16, 2728c2ecf20Sopenharmony_ci .sr_rtcwk_shift = 5, 2738c2ecf20Sopenharmony_ci .sr_rttwk_shift = 4, 2748c2ecf20Sopenharmony_ci }, 2758c2ecf20Sopenharmony_ci .pmc = { 2768c2ecf20Sopenharmony_ci .mckr = 0x28, 2778c2ecf20Sopenharmony_ci }, 2788c2ecf20Sopenharmony_ci}; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic const struct of_device_id at91_shdwc_of_match[] = { 2818c2ecf20Sopenharmony_ci { 2828c2ecf20Sopenharmony_ci .compatible = "atmel,sama5d2-shdwc", 2838c2ecf20Sopenharmony_ci .data = &sama5d2_reg_config, 2848c2ecf20Sopenharmony_ci }, 2858c2ecf20Sopenharmony_ci { 2868c2ecf20Sopenharmony_ci .compatible = "microchip,sam9x60-shdwc", 2878c2ecf20Sopenharmony_ci .data = &sam9x60_reg_config, 2888c2ecf20Sopenharmony_ci }, { 2898c2ecf20Sopenharmony_ci /*sentinel*/ 2908c2ecf20Sopenharmony_ci } 2918c2ecf20Sopenharmony_ci}; 2928c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, at91_shdwc_of_match); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cistatic const struct of_device_id at91_pmc_ids[] = { 2958c2ecf20Sopenharmony_ci { .compatible = "atmel,sama5d2-pmc" }, 2968c2ecf20Sopenharmony_ci { .compatible = "microchip,sam9x60-pmc" }, 2978c2ecf20Sopenharmony_ci { /* Sentinel. */ } 2988c2ecf20Sopenharmony_ci}; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cistatic int __init at91_shdwc_probe(struct platform_device *pdev) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci struct resource *res; 3038c2ecf20Sopenharmony_ci const struct of_device_id *match; 3048c2ecf20Sopenharmony_ci struct device_node *np; 3058c2ecf20Sopenharmony_ci u32 ddr_type; 3068c2ecf20Sopenharmony_ci int ret; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci if (!pdev->dev.of_node) 3098c2ecf20Sopenharmony_ci return -ENODEV; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci if (at91_shdwc) 3128c2ecf20Sopenharmony_ci return -EBUSY; 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci at91_shdwc = devm_kzalloc(&pdev->dev, sizeof(*at91_shdwc), GFP_KERNEL); 3158c2ecf20Sopenharmony_ci if (!at91_shdwc) 3168c2ecf20Sopenharmony_ci return -ENOMEM; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, at91_shdwc); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 3218c2ecf20Sopenharmony_ci at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res); 3228c2ecf20Sopenharmony_ci if (IS_ERR(at91_shdwc->shdwc_base)) { 3238c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not map reset controller address\n"); 3248c2ecf20Sopenharmony_ci return PTR_ERR(at91_shdwc->shdwc_base); 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); 3288c2ecf20Sopenharmony_ci at91_shdwc->rcfg = match->data; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL); 3318c2ecf20Sopenharmony_ci if (IS_ERR(at91_shdwc->sclk)) 3328c2ecf20Sopenharmony_ci return PTR_ERR(at91_shdwc->sclk); 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci ret = clk_prepare_enable(at91_shdwc->sclk); 3358c2ecf20Sopenharmony_ci if (ret) { 3368c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not enable slow clock\n"); 3378c2ecf20Sopenharmony_ci return ret; 3388c2ecf20Sopenharmony_ci } 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci at91_wakeup_status(pdev); 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci at91_shdwc_dt_configure(pdev); 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci np = of_find_matching_node(NULL, at91_pmc_ids); 3458c2ecf20Sopenharmony_ci if (!np) { 3468c2ecf20Sopenharmony_ci ret = -ENODEV; 3478c2ecf20Sopenharmony_ci goto clk_disable; 3488c2ecf20Sopenharmony_ci } 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci at91_shdwc->pmc_base = of_iomap(np, 0); 3518c2ecf20Sopenharmony_ci of_node_put(np); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci if (!at91_shdwc->pmc_base) { 3548c2ecf20Sopenharmony_ci ret = -ENOMEM; 3558c2ecf20Sopenharmony_ci goto clk_disable; 3568c2ecf20Sopenharmony_ci } 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc"); 3598c2ecf20Sopenharmony_ci if (!np) { 3608c2ecf20Sopenharmony_ci ret = -ENODEV; 3618c2ecf20Sopenharmony_ci goto unmap; 3628c2ecf20Sopenharmony_ci } 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci at91_shdwc->mpddrc_base = of_iomap(np, 0); 3658c2ecf20Sopenharmony_ci of_node_put(np); 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci if (!at91_shdwc->mpddrc_base) { 3688c2ecf20Sopenharmony_ci ret = -ENOMEM; 3698c2ecf20Sopenharmony_ci goto unmap; 3708c2ecf20Sopenharmony_ci } 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci pm_power_off = at91_poweroff; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci ddr_type = readl(at91_shdwc->mpddrc_base + AT91_DDRSDRC_MDR) & 3758c2ecf20Sopenharmony_ci AT91_DDRSDRC_MD; 3768c2ecf20Sopenharmony_ci if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && 3778c2ecf20Sopenharmony_ci ddr_type != AT91_DDRSDRC_MD_LPDDR3) { 3788c2ecf20Sopenharmony_ci iounmap(at91_shdwc->mpddrc_base); 3798c2ecf20Sopenharmony_ci at91_shdwc->mpddrc_base = NULL; 3808c2ecf20Sopenharmony_ci } 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci return 0; 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ciunmap: 3858c2ecf20Sopenharmony_ci iounmap(at91_shdwc->pmc_base); 3868c2ecf20Sopenharmony_ciclk_disable: 3878c2ecf20Sopenharmony_ci clk_disable_unprepare(at91_shdwc->sclk); 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci return ret; 3908c2ecf20Sopenharmony_ci} 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_cistatic int __exit at91_shdwc_remove(struct platform_device *pdev) 3938c2ecf20Sopenharmony_ci{ 3948c2ecf20Sopenharmony_ci struct shdwc *shdw = platform_get_drvdata(pdev); 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci if (pm_power_off == at91_poweroff) 3978c2ecf20Sopenharmony_ci pm_power_off = NULL; 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci /* Reset values to disable wake-up features */ 4008c2ecf20Sopenharmony_ci writel(0, shdw->shdwc_base + AT91_SHDW_MR); 4018c2ecf20Sopenharmony_ci writel(0, shdw->shdwc_base + AT91_SHDW_WUIR); 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci if (shdw->mpddrc_base) 4048c2ecf20Sopenharmony_ci iounmap(shdw->mpddrc_base); 4058c2ecf20Sopenharmony_ci iounmap(shdw->pmc_base); 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci clk_disable_unprepare(shdw->sclk); 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci return 0; 4108c2ecf20Sopenharmony_ci} 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_cistatic struct platform_driver at91_shdwc_driver = { 4138c2ecf20Sopenharmony_ci .remove = __exit_p(at91_shdwc_remove), 4148c2ecf20Sopenharmony_ci .driver = { 4158c2ecf20Sopenharmony_ci .name = "at91-shdwc", 4168c2ecf20Sopenharmony_ci .of_match_table = at91_shdwc_of_match, 4178c2ecf20Sopenharmony_ci }, 4188c2ecf20Sopenharmony_ci}; 4198c2ecf20Sopenharmony_cimodule_platform_driver_probe(at91_shdwc_driver, at91_shdwc_probe); 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); 4228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Atmel shutdown controller driver"); 4238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 424