linux/drivers/power/reset/at91-sama5d2_shdwc.c
<<
>>
Prefs
   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");
 458