linux/drivers/power/reset/at91-poweroff.c
<<
>>
Prefs
   1/*
   2 * Atmel AT91 SAM9 SoCs reset code
   3 *
   4 * Copyright (C) 2007 Atmel Corporation.
   5 * Copyright (C) 2011 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
   6 * Copyright (C) 2014 Free Electrons
   7 *
   8 * This file is licensed under the terms of the GNU General Public
   9 * License version 2.  This program is licensed "as is" without any
  10 * warranty of any kind, whether express or implied.
  11 */
  12
  13#include <linux/clk.h>
  14#include <linux/io.h>
  15#include <linux/module.h>
  16#include <linux/of.h>
  17#include <linux/of_address.h>
  18#include <linux/platform_device.h>
  19#include <linux/printk.h>
  20
  21#include <soc/at91/at91sam9_ddrsdr.h>
  22
  23#define AT91_SHDW_CR    0x00            /* Shut Down Control Register */
  24#define AT91_SHDW_SHDW          BIT(0)                  /* Shut Down command */
  25#define AT91_SHDW_KEY           (0xa5 << 24)            /* KEY Password */
  26
  27#define AT91_SHDW_MR    0x04            /* Shut Down Mode Register */
  28#define AT91_SHDW_WKMODE0       GENMASK(2, 0)           /* Wake-up 0 Mode Selection */
  29#define AT91_SHDW_CPTWK0_MAX    0xf                     /* Maximum Counter On Wake Up 0 */
  30#define AT91_SHDW_CPTWK0        (AT91_SHDW_CPTWK0_MAX << 4) /* Counter On Wake Up 0 */
  31#define AT91_SHDW_CPTWK0_(x)    ((x) << 4)
  32#define AT91_SHDW_RTTWKEN       BIT(16)                 /* Real Time Timer Wake-up Enable */
  33#define AT91_SHDW_RTCWKEN       BIT(17)                 /* Real Time Clock Wake-up Enable */
  34
  35#define AT91_SHDW_SR    0x08            /* Shut Down Status Register */
  36#define AT91_SHDW_WAKEUP0       BIT(0)                  /* Wake-up 0 Status */
  37#define AT91_SHDW_RTTWK         BIT(16)                 /* Real-time Timer Wake-up */
  38#define AT91_SHDW_RTCWK         BIT(17)                 /* Real-time Clock Wake-up [SAM9RL] */
  39
  40enum wakeup_type {
  41        AT91_SHDW_WKMODE0_NONE          = 0,
  42        AT91_SHDW_WKMODE0_HIGH          = 1,
  43        AT91_SHDW_WKMODE0_LOW           = 2,
  44        AT91_SHDW_WKMODE0_ANYLEVEL      = 3,
  45};
  46
  47static const char *shdwc_wakeup_modes[] = {
  48        [AT91_SHDW_WKMODE0_NONE]        = "none",
  49        [AT91_SHDW_WKMODE0_HIGH]        = "high",
  50        [AT91_SHDW_WKMODE0_LOW]         = "low",
  51        [AT91_SHDW_WKMODE0_ANYLEVEL]    = "any",
  52};
  53
  54static struct shdwc {
  55        struct clk *sclk;
  56        void __iomem *shdwc_base;
  57        void __iomem *mpddrc_base;
  58} at91_shdwc;
  59
  60static void __init at91_wakeup_status(struct platform_device *pdev)
  61{
  62        const char *reason;
  63        u32 reg = readl(at91_shdwc.shdwc_base + AT91_SHDW_SR);
  64
  65        /* Simple power-on, just bail out */
  66        if (!reg)
  67                return;
  68
  69        if (reg & AT91_SHDW_RTTWK)
  70                reason = "RTT";
  71        else if (reg & AT91_SHDW_RTCWK)
  72                reason = "RTC";
  73        else
  74                reason = "unknown";
  75
  76        dev_info(&pdev->dev, "Wake-Up source: %s\n", reason);
  77}
  78
  79static void at91_poweroff(void)
  80{
  81        asm volatile(
  82                /* Align to cache lines */
  83                ".balign 32\n\t"
  84
  85                /* Ensure AT91_SHDW_CR is in the TLB by reading it */
  86                "       ldr     r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
  87
  88                /* Power down SDRAM0 */
  89                "       tst     %0, #0\n\t"
  90                "       beq     1f\n\t"
  91                "       str     %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
  92                /* Shutdown CPU */
  93                "1:     str     %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
  94
  95                "       b       .\n\t"
  96                :
  97                : "r" (at91_shdwc.mpddrc_base),
  98                  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
  99                  "r" (at91_shdwc.shdwc_base),
 100                  "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
 101                : "r6");
 102}
 103
 104static int at91_poweroff_get_wakeup_mode(struct device_node *np)
 105{
 106        const char *pm;
 107        unsigned int i;
 108        int err;
 109
 110        err = of_property_read_string(np, "atmel,wakeup-mode", &pm);
 111        if (err < 0)
 112                return AT91_SHDW_WKMODE0_ANYLEVEL;
 113
 114        for (i = 0; i < ARRAY_SIZE(shdwc_wakeup_modes); i++)
 115                if (!strcasecmp(pm, shdwc_wakeup_modes[i]))
 116                        return i;
 117
 118        return -ENODEV;
 119}
 120
 121static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
 122{
 123        struct device_node *np = pdev->dev.of_node;
 124        int wakeup_mode;
 125        u32 mode = 0, tmp;
 126
 127        wakeup_mode = at91_poweroff_get_wakeup_mode(np);
 128        if (wakeup_mode < 0) {
 129                dev_warn(&pdev->dev, "shdwc unknown wakeup mode\n");
 130                return;
 131        }
 132
 133        if (!of_property_read_u32(np, "atmel,wakeup-counter", &tmp)) {
 134                if (tmp > AT91_SHDW_CPTWK0_MAX) {
 135                        dev_warn(&pdev->dev,
 136                                 "shdwc wakeup counter 0x%x > 0x%x reduce it to 0x%x\n",
 137                                 tmp, AT91_SHDW_CPTWK0_MAX, AT91_SHDW_CPTWK0_MAX);
 138                        tmp = AT91_SHDW_CPTWK0_MAX;
 139                }
 140                mode |= AT91_SHDW_CPTWK0_(tmp);
 141        }
 142
 143        if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
 144                        mode |= AT91_SHDW_RTCWKEN;
 145
 146        if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
 147                        mode |= AT91_SHDW_RTTWKEN;
 148
 149        writel(wakeup_mode | mode, at91_shdwc.shdwc_base + AT91_SHDW_MR);
 150}
 151
 152static int __init at91_poweroff_probe(struct platform_device *pdev)
 153{
 154        struct resource *res;
 155        struct device_node *np;
 156        u32 ddr_type;
 157        int ret;
 158
 159        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 160        at91_shdwc.shdwc_base = devm_ioremap_resource(&pdev->dev, res);
 161        if (IS_ERR(at91_shdwc.shdwc_base))
 162                return PTR_ERR(at91_shdwc.shdwc_base);
 163
 164        at91_shdwc.sclk = devm_clk_get(&pdev->dev, NULL);
 165        if (IS_ERR(at91_shdwc.sclk))
 166                return PTR_ERR(at91_shdwc.sclk);
 167
 168        ret = clk_prepare_enable(at91_shdwc.sclk);
 169        if (ret) {
 170                dev_err(&pdev->dev, "Could not enable slow clock\n");
 171                return ret;
 172        }
 173
 174        at91_wakeup_status(pdev);
 175
 176        if (pdev->dev.of_node)
 177                at91_poweroff_dt_set_wakeup_mode(pdev);
 178
 179        np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
 180        if (np) {
 181                at91_shdwc.mpddrc_base = of_iomap(np, 0);
 182                of_node_put(np);
 183
 184                if (!at91_shdwc.mpddrc_base) {
 185                        ret = -ENOMEM;
 186                        goto clk_disable;
 187                }
 188
 189                ddr_type = readl(at91_shdwc.mpddrc_base + AT91_DDRSDRC_MDR) &
 190                                 AT91_DDRSDRC_MD;
 191                if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 &&
 192                    ddr_type != AT91_DDRSDRC_MD_LPDDR3) {
 193                        iounmap(at91_shdwc.mpddrc_base);
 194                        at91_shdwc.mpddrc_base = NULL;
 195                }
 196        }
 197
 198        pm_power_off = at91_poweroff;
 199
 200        return 0;
 201
 202clk_disable:
 203        clk_disable_unprepare(at91_shdwc.sclk);
 204        return ret;
 205}
 206
 207static int __exit at91_poweroff_remove(struct platform_device *pdev)
 208{
 209        if (pm_power_off == at91_poweroff)
 210                pm_power_off = NULL;
 211
 212        if (at91_shdwc.mpddrc_base)
 213                iounmap(at91_shdwc.mpddrc_base);
 214
 215        clk_disable_unprepare(at91_shdwc.sclk);
 216
 217        return 0;
 218}
 219
 220static const struct of_device_id at91_poweroff_of_match[] = {
 221        { .compatible = "atmel,at91sam9260-shdwc", },
 222        { .compatible = "atmel,at91sam9rl-shdwc", },
 223        { .compatible = "atmel,at91sam9x5-shdwc", },
 224        { /*sentinel*/ }
 225};
 226MODULE_DEVICE_TABLE(of, at91_poweroff_of_match);
 227
 228static struct platform_driver at91_poweroff_driver = {
 229        .remove = __exit_p(at91_poweroff_remove),
 230        .driver = {
 231                .name = "at91-poweroff",
 232                .of_match_table = at91_poweroff_of_match,
 233        },
 234};
 235module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe);
 236
 237MODULE_AUTHOR("Atmel Corporation");
 238MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs");
 239MODULE_LICENSE("GPL v2");
 240