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(addr: 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, propname: "atmel,wakeup-mode" , out_string: &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(s1: pm, s2: 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, propname: "atmel,wakeup-counter" , out_value: &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, propname: "atmel,wakeup-rtc-timer" )) |
144 | mode |= AT91_SHDW_RTCWKEN; |
145 | |
146 | if (of_property_read_bool(np, propname: "atmel,wakeup-rtt-timer" )) |
147 | mode |= AT91_SHDW_RTTWKEN; |
148 | |
149 | writel(val: wakeup_mode | mode, addr: at91_shdwc.shdwc_base + AT91_SHDW_MR); |
150 | } |
151 | |
152 | static int __init at91_poweroff_probe(struct platform_device *pdev) |
153 | { |
154 | struct device_node *np; |
155 | u32 ddr_type; |
156 | int ret; |
157 | |
158 | at91_shdwc.shdwc_base = devm_platform_ioremap_resource(pdev, index: 0); |
159 | if (IS_ERR(ptr: at91_shdwc.shdwc_base)) |
160 | return PTR_ERR(ptr: at91_shdwc.shdwc_base); |
161 | |
162 | at91_shdwc.sclk = devm_clk_get(dev: &pdev->dev, NULL); |
163 | if (IS_ERR(ptr: at91_shdwc.sclk)) |
164 | return PTR_ERR(ptr: at91_shdwc.sclk); |
165 | |
166 | ret = clk_prepare_enable(clk: at91_shdwc.sclk); |
167 | if (ret) { |
168 | dev_err(&pdev->dev, "Could not enable slow clock\n" ); |
169 | return ret; |
170 | } |
171 | |
172 | at91_wakeup_status(pdev); |
173 | |
174 | if (pdev->dev.of_node) |
175 | at91_poweroff_dt_set_wakeup_mode(pdev); |
176 | |
177 | np = of_find_compatible_node(NULL, NULL, compat: "atmel,sama5d3-ddramc" ); |
178 | if (np) { |
179 | at91_shdwc.mpddrc_base = of_iomap(node: np, index: 0); |
180 | of_node_put(node: np); |
181 | |
182 | if (!at91_shdwc.mpddrc_base) { |
183 | ret = -ENOMEM; |
184 | goto clk_disable; |
185 | } |
186 | |
187 | ddr_type = readl(addr: at91_shdwc.mpddrc_base + AT91_DDRSDRC_MDR) & |
188 | AT91_DDRSDRC_MD; |
189 | if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 && |
190 | ddr_type != AT91_DDRSDRC_MD_LPDDR3) { |
191 | iounmap(addr: at91_shdwc.mpddrc_base); |
192 | at91_shdwc.mpddrc_base = NULL; |
193 | } |
194 | } |
195 | |
196 | pm_power_off = at91_poweroff; |
197 | |
198 | return 0; |
199 | |
200 | clk_disable: |
201 | clk_disable_unprepare(clk: at91_shdwc.sclk); |
202 | return ret; |
203 | } |
204 | |
205 | static int __exit at91_poweroff_remove(struct platform_device *pdev) |
206 | { |
207 | if (pm_power_off == at91_poweroff) |
208 | pm_power_off = NULL; |
209 | |
210 | if (at91_shdwc.mpddrc_base) |
211 | iounmap(addr: at91_shdwc.mpddrc_base); |
212 | |
213 | clk_disable_unprepare(clk: at91_shdwc.sclk); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static 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 | }; |
224 | MODULE_DEVICE_TABLE(of, at91_poweroff_of_match); |
225 | |
226 | static 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 | }; |
233 | module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe); |
234 | |
235 | MODULE_AUTHOR("Atmel Corporation" ); |
236 | MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs" ); |
237 | MODULE_LICENSE("GPL v2" ); |
238 | |