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 void __iomem *at91_shdwc_base;
  55static struct clk *sclk;
  56static void __iomem *mpddrc_base;
  57
  58static void __init at91_wakeup_status(struct platform_device *pdev)
  59{
  60        const char *reason;
  61        u32 reg = readl(at91_shdwc_base + AT91_SHDW_SR);
  62
  63        /* Simple power-on, just bail out */
  64        if (!reg)
  65                return;
  66
  67        if (reg & AT91_SHDW_RTTWK)
  68                reason = "RTT";
  69        else if (reg & AT91_SHDW_RTCWK)
  70                reason = "RTC";
  71        else
  72                reason = "unknown";
  73
  74        dev_info(&pdev->dev, "Wake-Up source: %s\n", reason);
  75}
  76
  77static void at91_poweroff(void)
  78{
  79        writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR);
  80}
  81
  82static void at91_lpddr_poweroff(void)
  83{
  84        asm volatile(
  85                /* Align to cache lines */
  86                ".balign 32\n\t"
  87
  88                /* Ensure AT91_SHDW_CR is in the TLB by reading it */
  89                "       ldr     r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
  90
  91                /* Power down SDRAM0 */
  92                "       str     %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
  93                /* Shutdown CPU */
  94                "       str     %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
  95
  96                "       b       .\n\t"
  97                :
  98                : "r" (mpddrc_base),
  99                  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
 100                  "r" (at91_shdwc_base),
 101                  "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
 102                : "r6");
 103}
 104
 105static int at91_poweroff_get_wakeup_mode(struct device_node *np)
 106{
 107        const char *pm;
 108        unsigned int i;
 109        int err;
 110
 111        err = of_property_read_string(np, "atmel,wakeup-mode", &pm);
 112        if (err < 0)
 113                return AT91_SHDW_WKMODE0_ANYLEVEL;
 114
 115        for (i = 0; i < ARRAY_SIZE(shdwc_wakeup_modes); i++)
 116                if (!strcasecmp(pm, shdwc_wakeup_modes[i]))
 117                        return i;
 118
 119        return -ENODEV;
 120}
 121
 122static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
 123{
 124        struct device_node *np = pdev->dev.of_node;
 125        int wakeup_mode;
 126        u32 mode = 0, tmp;
 127
 128        wakeup_mode = at91_poweroff_get_wakeup_mode(np);
 129        if (wakeup_mode < 0) {
 130                dev_warn(&pdev->dev, "shdwc unknown wakeup mode\n");
 131                return;
 132        }
 133
 134        if (!of_property_read_u32(np, "atmel,wakeup-counter", &tmp)) {
 135                if (tmp > AT91_SHDW_CPTWK0_MAX) {
 136                        dev_warn(&pdev->dev,
 137                                 "shdwc wakeup counter 0x%x > 0x%x reduce it to 0x%x\n",
 138                                 tmp, AT91_SHDW_CPTWK0_MAX, AT91_SHDW_CPTWK0_MAX);
 139                        tmp = AT91_SHDW_CPTWK0_MAX;
 140                }
 141                mode |= AT91_SHDW_CPTWK0_(tmp);
 142        }
 143
 144        if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
 145                        mode |= AT91_SHDW_RTCWKEN;
 146
 147        if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
 148                        mode |= AT91_SHDW_RTTWKEN;
 149
 150        writel(wakeup_mode | mode, at91_shdwc_base + AT91_SHDW_MR);
 151}
 152
 153static int __init at91_poweroff_probe(struct platform_device *pdev)
 154{
 155        struct resource *res;
 156        struct device_node *np;
 157        u32 ddr_type;
 158        int ret;
 159
 160        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 161        at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res);
 162        if (IS_ERR(at91_shdwc_base))
 163                return PTR_ERR(at91_shdwc_base);
 164
 165        sclk = devm_clk_get(&pdev->dev, NULL);
 166        if (IS_ERR(sclk))
 167                return PTR_ERR(sclk);
 168
 169        ret = clk_prepare_enable(sclk);
 170        if (ret) {
 171                dev_err(&pdev->dev, "Could not enable slow clock\n");
 172                return ret;
 173        }
 174
 175        at91_wakeup_status(pdev);
 176
 177        if (pdev->dev.of_node)
 178                at91_poweroff_dt_set_wakeup_mode(pdev);
 179
 180        pm_power_off = at91_poweroff;
 181
 182        np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
 183        if (!np)
 184                return 0;
 185
 186        mpddrc_base = of_iomap(np, 0);
 187        of_node_put(np);
 188
 189        if (!mpddrc_base)
 190                return 0;
 191
 192        ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
 193        if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
 194            (ddr_type == AT91_DDRSDRC_MD_LPDDR3))
 195                pm_power_off = at91_lpddr_poweroff;
 196        else
 197                iounmap(mpddrc_base);
 198
 199        return 0;
 200}
 201
 202static int __exit at91_poweroff_remove(struct platform_device *pdev)
 203{
 204        if (pm_power_off == at91_poweroff ||
 205            pm_power_off == at91_lpddr_poweroff)
 206                pm_power_off = NULL;
 207
 208        clk_disable_unprepare(sclk);
 209
 210        return 0;
 211}
 212
 213static const struct of_device_id at91_ramc_of_match[] = {
 214        { .compatible = "atmel,sama5d3-ddramc", },
 215        { /* sentinel */ }
 216};
 217
 218static const struct of_device_id at91_poweroff_of_match[] = {
 219        { .compatible = "atmel,at91sam9260-shdwc", },
 220        { .compatible = "atmel,at91sam9rl-shdwc", },
 221        { .compatible = "atmel,at91sam9x5-shdwc", },
 222        { /*sentinel*/ }
 223};
 224MODULE_DEVICE_TABLE(of, at91_poweroff_of_match);
 225
 226static struct platform_driver at91_poweroff_driver = {
 227        .remove = __exit_p(at91_poweroff_remove),
 228        .driver = {
 229                .name = "at91-poweroff",
 230                .of_match_table = at91_poweroff_of_match,
 231        },
 232};
 233module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe);
 234
 235MODULE_AUTHOR("Atmel Corporation");
 236MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs");
 237MODULE_LICENSE("GPL v2");
 238