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