at91-sama5d2_shdwc.c (11273B)
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 Public 11 * License version 2. This program is licensed "as is" without any 12 * 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 alarm 17 * - Serial RX wake-up alarm 18 * - low power debouncer 19 */ 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 32768 33 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 24 40#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 16 46#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 16 54#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 driver 102 * 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 __init 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_MD 274 }, 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_MD 291 }, 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 __init at91_shdwc_probe(struct platform_device *pdev) 333{ 334 struct resource *res; 335 const struct of_device_id *match; 336 struct device_node *np; 337 u32 ddr_type; 338 int ret; 339 340 if (!pdev->dev.of_node) 341 return -ENODEV; 342 343 if (at91_shdwc) 344 return -EBUSY; 345 346 at91_shdwc = devm_kzalloc(&pdev->dev, sizeof(*at91_shdwc), GFP_KERNEL); 347 if (!at91_shdwc) 348 return -ENOMEM; 349 350 platform_set_drvdata(pdev, at91_shdwc); 351 352 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 353 at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res); 354 if (IS_ERR(at91_shdwc->shdwc_base)) 355 return PTR_ERR(at91_shdwc->shdwc_base); 356 357 match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); 358 at91_shdwc->rcfg = match->data; 359 360 at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL); 361 if (IS_ERR(at91_shdwc->sclk)) 362 return PTR_ERR(at91_shdwc->sclk); 363 364 ret = clk_prepare_enable(at91_shdwc->sclk); 365 if (ret) { 366 dev_err(&pdev->dev, "Could not enable slow clock\n"); 367 return ret; 368 } 369 370 at91_wakeup_status(pdev); 371 372 at91_shdwc_dt_configure(pdev); 373 374 np = of_find_matching_node(NULL, at91_pmc_ids); 375 if (!np) { 376 ret = -ENODEV; 377 goto clk_disable; 378 } 379 380 at91_shdwc->pmc_base = of_iomap(np, 0); 381 of_node_put(np); 382 383 if (!at91_shdwc->pmc_base) { 384 ret = -ENOMEM; 385 goto clk_disable; 386 } 387 388 if (at91_shdwc->rcfg->ddrc.type_mask) { 389 np = of_find_compatible_node(NULL, NULL, 390 "atmel,sama5d3-ddramc"); 391 if (!np) { 392 ret = -ENODEV; 393 goto unmap; 394 } 395 396 at91_shdwc->mpddrc_base = of_iomap(np, 0); 397 of_node_put(np); 398 399 if (!at91_shdwc->mpddrc_base) { 400 ret = -ENOMEM; 401 goto unmap; 402 } 403 404 ddr_type = readl(at91_shdwc->mpddrc_base + 405 at91_shdwc->rcfg->ddrc.type_offset) & 406 at91_shdwc->rcfg->ddrc.type_mask; 407 if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && 408 ddr_type != AT91_DDRSDRC_MD_LPDDR3) { 409 iounmap(at91_shdwc->mpddrc_base); 410 at91_shdwc->mpddrc_base = NULL; 411 } 412 } 413 414 pm_power_off = at91_poweroff; 415 416 return 0; 417 418unmap: 419 iounmap(at91_shdwc->pmc_base); 420clk_disable: 421 clk_disable_unprepare(at91_shdwc->sclk); 422 423 return ret; 424} 425 426static int __exit at91_shdwc_remove(struct platform_device *pdev) 427{ 428 struct shdwc *shdw = platform_get_drvdata(pdev); 429 430 if (pm_power_off == at91_poweroff) 431 pm_power_off = NULL; 432 433 /* Reset values to disable wake-up features */ 434 writel(0, shdw->shdwc_base + AT91_SHDW_MR); 435 writel(0, shdw->shdwc_base + AT91_SHDW_WUIR); 436 437 if (shdw->mpddrc_base) 438 iounmap(shdw->mpddrc_base); 439 iounmap(shdw->pmc_base); 440 441 clk_disable_unprepare(shdw->sclk); 442 443 return 0; 444} 445 446static struct platform_driver at91_shdwc_driver = { 447 .remove = __exit_p(at91_shdwc_remove), 448 .driver = { 449 .name = "at91-shdwc", 450 .of_match_table = at91_shdwc_of_match, 451 }, 452}; 453module_platform_driver_probe(at91_shdwc_driver, at91_shdwc_probe); 454 455MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); 456MODULE_DESCRIPTION("Atmel shutdown controller driver"); 457MODULE_LICENSE("GPL v2");