root/drivers/power/reset/at91-poweroff.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. at91_wakeup_status
  2. at91_poweroff
  3. at91_poweroff_get_wakeup_mode
  4. at91_poweroff_dt_set_wakeup_mode
  5. at91_poweroff_probe
  6. at91_poweroff_remove

   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 
  40 enum 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 
  47 static 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 
  54 static struct shdwc {
  55         struct clk *sclk;
  56         void __iomem *shdwc_base;
  57         void __iomem *mpddrc_base;
  58 } at91_shdwc;
  59 
  60 static 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 
  79 static 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 
 104 static 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 
 121 static 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 
 152 static 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 
 202 clk_disable:
 203         clk_disable_unprepare(at91_shdwc.sclk);
 204         return ret;
 205 }
 206 
 207 static 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 
 220 static 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 };
 226 MODULE_DEVICE_TABLE(of, at91_poweroff_of_match);
 227 
 228 static 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 };
 235 module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe);
 236 
 237 MODULE_AUTHOR("Atmel Corporation");
 238 MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs");
 239 MODULE_LICENSE("GPL v2");

/* [<][>][^][v][top][bottom][index][help] */