| .. | .. |
|---|
| 19 | 19 | */ |
|---|
| 20 | 20 | |
|---|
| 21 | 21 | #include <linux/clk.h> |
|---|
| 22 | +#include <linux/clk/at91_pmc.h> |
|---|
| 22 | 23 | #include <linux/io.h> |
|---|
| 23 | 24 | #include <linux/module.h> |
|---|
| 24 | 25 | #include <linux/of.h> |
|---|
| .. | .. |
|---|
| 56 | 57 | |
|---|
| 57 | 58 | #define SHDW_WK_PIN(reg, cfg) ((reg) & AT91_SHDW_WKUPIS((cfg)->wkup_pin_input)) |
|---|
| 58 | 59 | #define SHDW_RTCWK(reg, cfg) (((reg) >> ((cfg)->sr_rtcwk_shift)) & 0x1) |
|---|
| 60 | +#define SHDW_RTTWK(reg, cfg) (((reg) >> ((cfg)->sr_rttwk_shift)) & 0x1) |
|---|
| 59 | 61 | #define SHDW_RTCWKEN(cfg) (1 << ((cfg)->mr_rtcwk_shift)) |
|---|
| 62 | +#define SHDW_RTTWKEN(cfg) (1 << ((cfg)->mr_rttwk_shift)) |
|---|
| 60 | 63 | |
|---|
| 61 | 64 | #define DBC_PERIOD_US(x) DIV_ROUND_UP_ULL((1000000 * (x)), \ |
|---|
| 62 | 65 | SLOW_CLOCK_FREQ) |
|---|
| 63 | 66 | |
|---|
| 64 | | -struct shdwc_config { |
|---|
| 67 | +#define SHDW_CFG_NOT_USED (32) |
|---|
| 68 | + |
|---|
| 69 | +struct shdwc_reg_config { |
|---|
| 65 | 70 | u8 wkup_pin_input; |
|---|
| 66 | 71 | u8 mr_rtcwk_shift; |
|---|
| 72 | + u8 mr_rttwk_shift; |
|---|
| 67 | 73 | u8 sr_rtcwk_shift; |
|---|
| 74 | + u8 sr_rttwk_shift; |
|---|
| 75 | +}; |
|---|
| 76 | + |
|---|
| 77 | +struct pmc_reg_config { |
|---|
| 78 | + u8 mckr; |
|---|
| 79 | +}; |
|---|
| 80 | + |
|---|
| 81 | +struct reg_config { |
|---|
| 82 | + struct shdwc_reg_config shdwc; |
|---|
| 83 | + struct pmc_reg_config pmc; |
|---|
| 68 | 84 | }; |
|---|
| 69 | 85 | |
|---|
| 70 | 86 | struct shdwc { |
|---|
| 71 | | - const struct shdwc_config *cfg; |
|---|
| 72 | | - void __iomem *at91_shdwc_base; |
|---|
| 87 | + const struct reg_config *rcfg; |
|---|
| 88 | + struct clk *sclk; |
|---|
| 89 | + void __iomem *shdwc_base; |
|---|
| 90 | + void __iomem *mpddrc_base; |
|---|
| 91 | + void __iomem *pmc_base; |
|---|
| 73 | 92 | }; |
|---|
| 74 | 93 | |
|---|
| 75 | 94 | /* |
|---|
| .. | .. |
|---|
| 77 | 96 | * since pm_power_off itself is global. |
|---|
| 78 | 97 | */ |
|---|
| 79 | 98 | static struct shdwc *at91_shdwc; |
|---|
| 80 | | -static struct clk *sclk; |
|---|
| 81 | | -static void __iomem *mpddrc_base; |
|---|
| 82 | 99 | |
|---|
| 83 | 100 | static const unsigned long long sdwc_dbc_period[] = { |
|---|
| 84 | 101 | 0, 3, 32, 512, 4096, 32768, |
|---|
| .. | .. |
|---|
| 87 | 104 | static void __init at91_wakeup_status(struct platform_device *pdev) |
|---|
| 88 | 105 | { |
|---|
| 89 | 106 | struct shdwc *shdw = platform_get_drvdata(pdev); |
|---|
| 107 | + const struct reg_config *rcfg = shdw->rcfg; |
|---|
| 90 | 108 | u32 reg; |
|---|
| 91 | 109 | char *reason = "unknown"; |
|---|
| 92 | 110 | |
|---|
| 93 | | - reg = readl(shdw->at91_shdwc_base + AT91_SHDW_SR); |
|---|
| 111 | + reg = readl(shdw->shdwc_base + AT91_SHDW_SR); |
|---|
| 94 | 112 | |
|---|
| 95 | 113 | dev_dbg(&pdev->dev, "%s: status = %#x\n", __func__, reg); |
|---|
| 96 | 114 | |
|---|
| .. | .. |
|---|
| 98 | 116 | if (!reg) |
|---|
| 99 | 117 | return; |
|---|
| 100 | 118 | |
|---|
| 101 | | - if (SHDW_WK_PIN(reg, shdw->cfg)) |
|---|
| 119 | + if (SHDW_WK_PIN(reg, &rcfg->shdwc)) |
|---|
| 102 | 120 | reason = "WKUP pin"; |
|---|
| 103 | | - else if (SHDW_RTCWK(reg, shdw->cfg)) |
|---|
| 121 | + else if (SHDW_RTCWK(reg, &rcfg->shdwc)) |
|---|
| 104 | 122 | reason = "RTC"; |
|---|
| 123 | + else if (SHDW_RTTWK(reg, &rcfg->shdwc)) |
|---|
| 124 | + reason = "RTT"; |
|---|
| 105 | 125 | |
|---|
| 106 | 126 | pr_info("AT91: Wake-Up source: %s\n", reason); |
|---|
| 107 | 127 | } |
|---|
| 108 | 128 | |
|---|
| 109 | 129 | static void at91_poweroff(void) |
|---|
| 110 | | -{ |
|---|
| 111 | | - writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, |
|---|
| 112 | | - at91_shdwc->at91_shdwc_base + AT91_SHDW_CR); |
|---|
| 113 | | -} |
|---|
| 114 | | - |
|---|
| 115 | | -static void at91_lpddr_poweroff(void) |
|---|
| 116 | 130 | { |
|---|
| 117 | 131 | asm volatile( |
|---|
| 118 | 132 | /* Align to cache lines */ |
|---|
| .. | .. |
|---|
| 122 | 136 | " ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" |
|---|
| 123 | 137 | |
|---|
| 124 | 138 | /* Power down SDRAM0 */ |
|---|
| 139 | + " tst %0, #0\n\t" |
|---|
| 140 | + " beq 1f\n\t" |
|---|
| 125 | 141 | " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t" |
|---|
| 142 | + |
|---|
| 143 | + /* Switch the master clock source to slow clock. */ |
|---|
| 144 | + "1: ldr r6, [%4, %5]\n\t" |
|---|
| 145 | + " bic r6, r6, #" __stringify(AT91_PMC_CSS) "\n\t" |
|---|
| 146 | + " str r6, [%4, %5]\n\t" |
|---|
| 147 | + /* Wait for clock switch. */ |
|---|
| 148 | + "2: ldr r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t" |
|---|
| 149 | + " tst r6, #" __stringify(AT91_PMC_MCKRDY) "\n\t" |
|---|
| 150 | + " beq 2b\n\t" |
|---|
| 151 | + |
|---|
| 126 | 152 | /* Shutdown CPU */ |
|---|
| 127 | 153 | " str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t" |
|---|
| 128 | 154 | |
|---|
| 129 | 155 | " b .\n\t" |
|---|
| 130 | 156 | : |
|---|
| 131 | | - : "r" (mpddrc_base), |
|---|
| 157 | + : "r" (at91_shdwc->mpddrc_base), |
|---|
| 132 | 158 | "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), |
|---|
| 133 | | - "r" (at91_shdwc->at91_shdwc_base), |
|---|
| 134 | | - "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW) |
|---|
| 159 | + "r" (at91_shdwc->shdwc_base), |
|---|
| 160 | + "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW), |
|---|
| 161 | + "r" (at91_shdwc->pmc_base), |
|---|
| 162 | + "r" (at91_shdwc->rcfg->pmc.mckr) |
|---|
| 135 | 163 | : "r6"); |
|---|
| 136 | 164 | } |
|---|
| 137 | 165 | |
|---|
| .. | .. |
|---|
| 198 | 226 | static void at91_shdwc_dt_configure(struct platform_device *pdev) |
|---|
| 199 | 227 | { |
|---|
| 200 | 228 | struct shdwc *shdw = platform_get_drvdata(pdev); |
|---|
| 229 | + const struct reg_config *rcfg = shdw->rcfg; |
|---|
| 201 | 230 | struct device_node *np = pdev->dev.of_node; |
|---|
| 202 | 231 | u32 mode = 0, tmp, input; |
|---|
| 203 | 232 | |
|---|
| .. | .. |
|---|
| 210 | 239 | mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp)); |
|---|
| 211 | 240 | |
|---|
| 212 | 241 | if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) |
|---|
| 213 | | - mode |= SHDW_RTCWKEN(shdw->cfg); |
|---|
| 242 | + mode |= SHDW_RTCWKEN(&rcfg->shdwc); |
|---|
| 243 | + |
|---|
| 244 | + if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) |
|---|
| 245 | + mode |= SHDW_RTTWKEN(&rcfg->shdwc); |
|---|
| 214 | 246 | |
|---|
| 215 | 247 | dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode); |
|---|
| 216 | | - writel(mode, shdw->at91_shdwc_base + AT91_SHDW_MR); |
|---|
| 248 | + writel(mode, shdw->shdwc_base + AT91_SHDW_MR); |
|---|
| 217 | 249 | |
|---|
| 218 | 250 | input = at91_shdwc_get_wakeup_input(pdev, np); |
|---|
| 219 | | - writel(input, shdw->at91_shdwc_base + AT91_SHDW_WUIR); |
|---|
| 251 | + writel(input, shdw->shdwc_base + AT91_SHDW_WUIR); |
|---|
| 220 | 252 | } |
|---|
| 221 | 253 | |
|---|
| 222 | | -static const struct shdwc_config sama5d2_shdwc_config = { |
|---|
| 223 | | - .wkup_pin_input = 0, |
|---|
| 224 | | - .mr_rtcwk_shift = 17, |
|---|
| 225 | | - .sr_rtcwk_shift = 5, |
|---|
| 254 | +static const struct reg_config sama5d2_reg_config = { |
|---|
| 255 | + .shdwc = { |
|---|
| 256 | + .wkup_pin_input = 0, |
|---|
| 257 | + .mr_rtcwk_shift = 17, |
|---|
| 258 | + .mr_rttwk_shift = SHDW_CFG_NOT_USED, |
|---|
| 259 | + .sr_rtcwk_shift = 5, |
|---|
| 260 | + .sr_rttwk_shift = SHDW_CFG_NOT_USED, |
|---|
| 261 | + }, |
|---|
| 262 | + .pmc = { |
|---|
| 263 | + .mckr = 0x30, |
|---|
| 264 | + }, |
|---|
| 265 | +}; |
|---|
| 266 | + |
|---|
| 267 | +static const struct reg_config sam9x60_reg_config = { |
|---|
| 268 | + .shdwc = { |
|---|
| 269 | + .wkup_pin_input = 0, |
|---|
| 270 | + .mr_rtcwk_shift = 17, |
|---|
| 271 | + .mr_rttwk_shift = 16, |
|---|
| 272 | + .sr_rtcwk_shift = 5, |
|---|
| 273 | + .sr_rttwk_shift = 4, |
|---|
| 274 | + }, |
|---|
| 275 | + .pmc = { |
|---|
| 276 | + .mckr = 0x28, |
|---|
| 277 | + }, |
|---|
| 226 | 278 | }; |
|---|
| 227 | 279 | |
|---|
| 228 | 280 | static const struct of_device_id at91_shdwc_of_match[] = { |
|---|
| 229 | 281 | { |
|---|
| 230 | 282 | .compatible = "atmel,sama5d2-shdwc", |
|---|
| 231 | | - .data = &sama5d2_shdwc_config, |
|---|
| 283 | + .data = &sama5d2_reg_config, |
|---|
| 284 | + }, |
|---|
| 285 | + { |
|---|
| 286 | + .compatible = "microchip,sam9x60-shdwc", |
|---|
| 287 | + .data = &sam9x60_reg_config, |
|---|
| 232 | 288 | }, { |
|---|
| 233 | 289 | /*sentinel*/ |
|---|
| 234 | 290 | } |
|---|
| 235 | 291 | }; |
|---|
| 236 | 292 | MODULE_DEVICE_TABLE(of, at91_shdwc_of_match); |
|---|
| 293 | + |
|---|
| 294 | +static const struct of_device_id at91_pmc_ids[] = { |
|---|
| 295 | + { .compatible = "atmel,sama5d2-pmc" }, |
|---|
| 296 | + { .compatible = "microchip,sam9x60-pmc" }, |
|---|
| 297 | + { /* Sentinel. */ } |
|---|
| 298 | +}; |
|---|
| 237 | 299 | |
|---|
| 238 | 300 | static int __init at91_shdwc_probe(struct platform_device *pdev) |
|---|
| 239 | 301 | { |
|---|
| .. | .. |
|---|
| 256 | 318 | platform_set_drvdata(pdev, at91_shdwc); |
|---|
| 257 | 319 | |
|---|
| 258 | 320 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| 259 | | - at91_shdwc->at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res); |
|---|
| 260 | | - if (IS_ERR(at91_shdwc->at91_shdwc_base)) { |
|---|
| 321 | + at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res); |
|---|
| 322 | + if (IS_ERR(at91_shdwc->shdwc_base)) { |
|---|
| 261 | 323 | dev_err(&pdev->dev, "Could not map reset controller address\n"); |
|---|
| 262 | | - return PTR_ERR(at91_shdwc->at91_shdwc_base); |
|---|
| 324 | + return PTR_ERR(at91_shdwc->shdwc_base); |
|---|
| 263 | 325 | } |
|---|
| 264 | 326 | |
|---|
| 265 | 327 | match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); |
|---|
| 266 | | - at91_shdwc->cfg = match->data; |
|---|
| 328 | + at91_shdwc->rcfg = match->data; |
|---|
| 267 | 329 | |
|---|
| 268 | | - sclk = devm_clk_get(&pdev->dev, NULL); |
|---|
| 269 | | - if (IS_ERR(sclk)) |
|---|
| 270 | | - return PTR_ERR(sclk); |
|---|
| 330 | + at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL); |
|---|
| 331 | + if (IS_ERR(at91_shdwc->sclk)) |
|---|
| 332 | + return PTR_ERR(at91_shdwc->sclk); |
|---|
| 271 | 333 | |
|---|
| 272 | | - ret = clk_prepare_enable(sclk); |
|---|
| 334 | + ret = clk_prepare_enable(at91_shdwc->sclk); |
|---|
| 273 | 335 | if (ret) { |
|---|
| 274 | 336 | dev_err(&pdev->dev, "Could not enable slow clock\n"); |
|---|
| 275 | 337 | return ret; |
|---|
| .. | .. |
|---|
| 279 | 341 | |
|---|
| 280 | 342 | at91_shdwc_dt_configure(pdev); |
|---|
| 281 | 343 | |
|---|
| 282 | | - pm_power_off = at91_poweroff; |
|---|
| 344 | + np = of_find_matching_node(NULL, at91_pmc_ids); |
|---|
| 345 | + if (!np) { |
|---|
| 346 | + ret = -ENODEV; |
|---|
| 347 | + goto clk_disable; |
|---|
| 348 | + } |
|---|
| 283 | 349 | |
|---|
| 284 | | - np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc"); |
|---|
| 285 | | - if (!np) |
|---|
| 286 | | - return 0; |
|---|
| 287 | | - |
|---|
| 288 | | - mpddrc_base = of_iomap(np, 0); |
|---|
| 350 | + at91_shdwc->pmc_base = of_iomap(np, 0); |
|---|
| 289 | 351 | of_node_put(np); |
|---|
| 290 | 352 | |
|---|
| 291 | | - if (!mpddrc_base) |
|---|
| 292 | | - return 0; |
|---|
| 353 | + if (!at91_shdwc->pmc_base) { |
|---|
| 354 | + ret = -ENOMEM; |
|---|
| 355 | + goto clk_disable; |
|---|
| 356 | + } |
|---|
| 293 | 357 | |
|---|
| 294 | | - ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD; |
|---|
| 295 | | - if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) || |
|---|
| 296 | | - (ddr_type == AT91_DDRSDRC_MD_LPDDR3)) |
|---|
| 297 | | - pm_power_off = at91_lpddr_poweroff; |
|---|
| 298 | | - else |
|---|
| 299 | | - iounmap(mpddrc_base); |
|---|
| 358 | + np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc"); |
|---|
| 359 | + if (!np) { |
|---|
| 360 | + ret = -ENODEV; |
|---|
| 361 | + goto unmap; |
|---|
| 362 | + } |
|---|
| 363 | + |
|---|
| 364 | + at91_shdwc->mpddrc_base = of_iomap(np, 0); |
|---|
| 365 | + of_node_put(np); |
|---|
| 366 | + |
|---|
| 367 | + if (!at91_shdwc->mpddrc_base) { |
|---|
| 368 | + ret = -ENOMEM; |
|---|
| 369 | + goto unmap; |
|---|
| 370 | + } |
|---|
| 371 | + |
|---|
| 372 | + pm_power_off = at91_poweroff; |
|---|
| 373 | + |
|---|
| 374 | + ddr_type = readl(at91_shdwc->mpddrc_base + AT91_DDRSDRC_MDR) & |
|---|
| 375 | + AT91_DDRSDRC_MD; |
|---|
| 376 | + if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && |
|---|
| 377 | + ddr_type != AT91_DDRSDRC_MD_LPDDR3) { |
|---|
| 378 | + iounmap(at91_shdwc->mpddrc_base); |
|---|
| 379 | + at91_shdwc->mpddrc_base = NULL; |
|---|
| 380 | + } |
|---|
| 300 | 381 | |
|---|
| 301 | 382 | return 0; |
|---|
| 383 | + |
|---|
| 384 | +unmap: |
|---|
| 385 | + iounmap(at91_shdwc->pmc_base); |
|---|
| 386 | +clk_disable: |
|---|
| 387 | + clk_disable_unprepare(at91_shdwc->sclk); |
|---|
| 388 | + |
|---|
| 389 | + return ret; |
|---|
| 302 | 390 | } |
|---|
| 303 | 391 | |
|---|
| 304 | 392 | static int __exit at91_shdwc_remove(struct platform_device *pdev) |
|---|
| 305 | 393 | { |
|---|
| 306 | 394 | struct shdwc *shdw = platform_get_drvdata(pdev); |
|---|
| 307 | 395 | |
|---|
| 308 | | - if (pm_power_off == at91_poweroff || |
|---|
| 309 | | - pm_power_off == at91_lpddr_poweroff) |
|---|
| 396 | + if (pm_power_off == at91_poweroff) |
|---|
| 310 | 397 | pm_power_off = NULL; |
|---|
| 311 | 398 | |
|---|
| 312 | 399 | /* Reset values to disable wake-up features */ |
|---|
| 313 | | - writel(0, shdw->at91_shdwc_base + AT91_SHDW_MR); |
|---|
| 314 | | - writel(0, shdw->at91_shdwc_base + AT91_SHDW_WUIR); |
|---|
| 400 | + writel(0, shdw->shdwc_base + AT91_SHDW_MR); |
|---|
| 401 | + writel(0, shdw->shdwc_base + AT91_SHDW_WUIR); |
|---|
| 315 | 402 | |
|---|
| 316 | | - clk_disable_unprepare(sclk); |
|---|
| 403 | + if (shdw->mpddrc_base) |
|---|
| 404 | + iounmap(shdw->mpddrc_base); |
|---|
| 405 | + iounmap(shdw->pmc_base); |
|---|
| 406 | + |
|---|
| 407 | + clk_disable_unprepare(shdw->sclk); |
|---|
| 317 | 408 | |
|---|
| 318 | 409 | return 0; |
|---|
| 319 | 410 | } |
|---|