Commit | Line | Data |
---|---|---|
ae499f0f MR |
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/io.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/printk.h> | |
18 | ||
19 | #define AT91_SHDW_CR 0x00 /* Shut Down Control Register */ | |
20 | #define AT91_SHDW_SHDW BIT(0) /* Shut Down command */ | |
21 | #define AT91_SHDW_KEY (0xa5 << 24) /* KEY Password */ | |
22 | ||
23 | #define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */ | |
24 | #define AT91_SHDW_WKMODE0 GENMASK(2, 0) /* Wake-up 0 Mode Selection */ | |
25 | #define AT91_SHDW_CPTWK0_MAX 0xf /* Maximum Counter On Wake Up 0 */ | |
26 | #define AT91_SHDW_CPTWK0 (AT91_SHDW_CPTWK0_MAX << 4) /* Counter On Wake Up 0 */ | |
27 | #define AT91_SHDW_CPTWK0_(x) ((x) << 4) | |
28 | #define AT91_SHDW_RTTWKEN BIT(16) /* Real Time Timer Wake-up Enable */ | |
29 | #define AT91_SHDW_RTCWKEN BIT(17) /* Real Time Clock Wake-up Enable */ | |
30 | ||
31 | #define AT91_SHDW_SR 0x08 /* Shut Down Status Register */ | |
32 | #define AT91_SHDW_WAKEUP0 BIT(0) /* Wake-up 0 Status */ | |
33 | #define AT91_SHDW_RTTWK BIT(16) /* Real-time Timer Wake-up */ | |
34 | #define AT91_SHDW_RTCWK BIT(17) /* Real-time Clock Wake-up [SAM9RL] */ | |
35 | ||
36 | enum wakeup_type { | |
37 | AT91_SHDW_WKMODE0_NONE = 0, | |
38 | AT91_SHDW_WKMODE0_HIGH = 1, | |
39 | AT91_SHDW_WKMODE0_LOW = 2, | |
40 | AT91_SHDW_WKMODE0_ANYLEVEL = 3, | |
41 | }; | |
42 | ||
43 | static const char *shdwc_wakeup_modes[] = { | |
44 | [AT91_SHDW_WKMODE0_NONE] = "none", | |
45 | [AT91_SHDW_WKMODE0_HIGH] = "high", | |
46 | [AT91_SHDW_WKMODE0_LOW] = "low", | |
47 | [AT91_SHDW_WKMODE0_ANYLEVEL] = "any", | |
48 | }; | |
49 | ||
50 | static void __iomem *at91_shdwc_base; | |
51 | ||
52 | static void __init at91_wakeup_status(void) | |
53 | { | |
405a72c5 | 54 | u32 reg = readl(at91_shdwc_base + AT91_SHDW_SR); |
ae499f0f MR |
55 | char *reason = "unknown"; |
56 | ||
57 | /* Simple power-on, just bail out */ | |
58 | if (!reg) | |
59 | return; | |
60 | ||
61 | if (reg & AT91_SHDW_RTTWK) | |
62 | reason = "RTT"; | |
63 | else if (reg & AT91_SHDW_RTCWK) | |
64 | reason = "RTC"; | |
65 | ||
66 | pr_info("AT91: Wake-Up source: %s\n", reason); | |
67 | } | |
68 | ||
69 | static void at91_poweroff(void) | |
70 | { | |
71 | writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR); | |
72 | } | |
73 | ||
a538cf04 | 74 | static int at91_poweroff_get_wakeup_mode(struct device_node *np) |
ae499f0f MR |
75 | { |
76 | const char *pm; | |
a538cf04 GR |
77 | unsigned int i; |
78 | int err; | |
ae499f0f MR |
79 | |
80 | err = of_property_read_string(np, "atmel,wakeup-mode", &pm); | |
81 | if (err < 0) | |
82 | return AT91_SHDW_WKMODE0_ANYLEVEL; | |
83 | ||
84 | for (i = 0; i < ARRAY_SIZE(shdwc_wakeup_modes); i++) | |
85 | if (!strcasecmp(pm, shdwc_wakeup_modes[i])) | |
86 | return i; | |
87 | ||
88 | return -ENODEV; | |
89 | } | |
90 | ||
91 | static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev) | |
92 | { | |
93 | struct device_node *np = pdev->dev.of_node; | |
a538cf04 | 94 | int wakeup_mode; |
ae499f0f MR |
95 | u32 mode = 0, tmp; |
96 | ||
97 | wakeup_mode = at91_poweroff_get_wakeup_mode(np); | |
98 | if (wakeup_mode < 0) { | |
99 | dev_warn(&pdev->dev, "shdwc unknown wakeup mode\n"); | |
100 | return; | |
101 | } | |
102 | ||
103 | if (!of_property_read_u32(np, "atmel,wakeup-counter", &tmp)) { | |
104 | if (tmp > AT91_SHDW_CPTWK0_MAX) { | |
105 | dev_warn(&pdev->dev, | |
106 | "shdwc wakeup counter 0x%x > 0x%x reduce it to 0x%x\n", | |
107 | tmp, AT91_SHDW_CPTWK0_MAX, AT91_SHDW_CPTWK0_MAX); | |
108 | tmp = AT91_SHDW_CPTWK0_MAX; | |
109 | } | |
110 | mode |= AT91_SHDW_CPTWK0_(tmp); | |
111 | } | |
112 | ||
113 | if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) | |
114 | mode |= AT91_SHDW_RTCWKEN; | |
115 | ||
116 | if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) | |
117 | mode |= AT91_SHDW_RTTWKEN; | |
118 | ||
119 | writel(wakeup_mode | mode, at91_shdwc_base + AT91_SHDW_MR); | |
120 | } | |
121 | ||
122 | static int at91_poweroff_probe(struct platform_device *pdev) | |
123 | { | |
124 | struct resource *res; | |
125 | ||
126 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
127 | at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res); | |
128 | if (IS_ERR(at91_shdwc_base)) { | |
129 | dev_err(&pdev->dev, "Could not map reset controller address\n"); | |
130 | return PTR_ERR(at91_shdwc_base); | |
131 | } | |
132 | ||
133 | at91_wakeup_status(); | |
134 | ||
135 | if (pdev->dev.of_node) | |
136 | at91_poweroff_dt_set_wakeup_mode(pdev); | |
137 | ||
138 | pm_power_off = at91_poweroff; | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
8fb08855 | 143 | static const struct of_device_id at91_poweroff_of_match[] = { |
ae499f0f MR |
144 | { .compatible = "atmel,at91sam9260-shdwc", }, |
145 | { .compatible = "atmel,at91sam9rl-shdwc", }, | |
146 | { .compatible = "atmel,at91sam9x5-shdwc", }, | |
147 | { /*sentinel*/ } | |
148 | }; | |
149 | ||
150 | static struct platform_driver at91_poweroff_driver = { | |
151 | .probe = at91_poweroff_probe, | |
152 | .driver = { | |
153 | .name = "at91-poweroff", | |
154 | .of_match_table = at91_poweroff_of_match, | |
155 | }, | |
156 | }; | |
157 | module_platform_driver(at91_poweroff_driver); |