]>
Commit | Line | Data |
---|---|---|
ebf0bd36 AK |
1 | /* |
2 | * linux/drivers/i2c/chips/twl4030-power.c | |
3 | * | |
4 | * Handle TWL4030 Power initialization | |
5 | * | |
6 | * Copyright (C) 2008 Nokia Corporation | |
7 | * Copyright (C) 2006 Texas Instruments, Inc | |
8 | * | |
9 | * Written by Kalle Jokiniemi | |
10 | * Peter De Schrijver <peter.de-schrijver@nokia.com> | |
11 | * Several fixes by Amit Kucheria <amit.kucheria@verdurent.com> | |
12 | * | |
13 | * This file is subject to the terms and conditions of the GNU General | |
14 | * Public License. See the file "COPYING" in the main directory of this | |
15 | * archive for more details. | |
16 | * | |
17 | * This program is distributed in the hope that it will be useful, | |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | * GNU General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU General Public License | |
23 | * along with this program; if not, write to the Free Software | |
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
25 | */ | |
26 | ||
27 | #include <linux/module.h> | |
28 | #include <linux/pm.h> | |
b07682b6 | 29 | #include <linux/i2c/twl.h> |
ebf0bd36 | 30 | #include <linux/platform_device.h> |
b0fc1da4 | 31 | #include <linux/of.h> |
ebf0bd36 AK |
32 | |
33 | #include <asm/mach-types.h> | |
34 | ||
35 | static u8 twl4030_start_script_address = 0x2b; | |
36 | ||
32057281 TL |
37 | /* Register bits for P1, P2 and P3_SW_EVENTS */ |
38 | #define PWR_STOPON_PRWON BIT(6) | |
39 | #define PWR_STOPON_SYSEN BIT(5) | |
40 | #define PWR_ENABLE_WARMRESET BIT(4) | |
41 | #define PWR_LVL_WAKEUP BIT(3) | |
42 | #define PWR_DEVACT BIT(2) | |
43 | #define PWR_DEVSLP BIT(1) | |
44 | #define PWR_DEVOFF BIT(0) | |
45 | ||
26cc3ab9 | 46 | #define SEQ_OFFSYNC (1 << 0) |
ebf0bd36 AK |
47 | |
48 | #define PHY_TO_OFF_PM_MASTER(p) (p - 0x36) | |
49 | #define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b) | |
50 | ||
51 | /* resource - hfclk */ | |
52 | #define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6) | |
53 | ||
54 | /* PM events */ | |
55 | #define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46) | |
56 | #define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47) | |
57 | #define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48) | |
58 | #define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36) | |
59 | #define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37) | |
60 | #define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38) | |
61 | ||
ebf0bd36 AK |
62 | #define END_OF_SCRIPT 0x3f |
63 | ||
64 | #define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55) | |
65 | #define R_SEQ_ADD_S2A12 PHY_TO_OFF_PM_MASTER(0x56) | |
66 | #define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57) | |
67 | #define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58) | |
68 | #define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59) | |
69 | #define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a) | |
70 | ||
890463f0 AK |
71 | /* resource configuration registers |
72 | <RESOURCE>_DEV_GRP at address 'n+0' | |
73 | <RESOURCE>_TYPE at address 'n+1' | |
74 | <RESOURCE>_REMAP at address 'n+2' | |
75 | <RESOURCE>_DEDICATED at address 'n+3' | |
76 | */ | |
e97d1546 | 77 | #define DEV_GRP_OFFSET 0 |
ebf0bd36 | 78 | #define TYPE_OFFSET 1 |
b4ead61e AK |
79 | #define REMAP_OFFSET 2 |
80 | #define DEDICATED_OFFSET 3 | |
ebf0bd36 | 81 | |
e97d1546 | 82 | /* Bit positions in the registers */ |
890463f0 AK |
83 | |
84 | /* <RESOURCE>_DEV_GRP */ | |
e97d1546 AK |
85 | #define DEV_GRP_SHIFT 5 |
86 | #define DEV_GRP_MASK (7 << DEV_GRP_SHIFT) | |
890463f0 AK |
87 | |
88 | /* <RESOURCE>_TYPE */ | |
ebf0bd36 AK |
89 | #define TYPE_SHIFT 0 |
90 | #define TYPE_MASK (7 << TYPE_SHIFT) | |
91 | #define TYPE2_SHIFT 3 | |
92 | #define TYPE2_MASK (3 << TYPE2_SHIFT) | |
93 | ||
b4ead61e AK |
94 | /* <RESOURCE>_REMAP */ |
95 | #define SLEEP_STATE_SHIFT 0 | |
96 | #define SLEEP_STATE_MASK (0xf << SLEEP_STATE_SHIFT) | |
97 | #define OFF_STATE_SHIFT 4 | |
98 | #define OFF_STATE_MASK (0xf << OFF_STATE_SHIFT) | |
99 | ||
ebf0bd36 AK |
100 | static u8 res_config_addrs[] = { |
101 | [RES_VAUX1] = 0x17, | |
102 | [RES_VAUX2] = 0x1b, | |
103 | [RES_VAUX3] = 0x1f, | |
104 | [RES_VAUX4] = 0x23, | |
105 | [RES_VMMC1] = 0x27, | |
106 | [RES_VMMC2] = 0x2b, | |
107 | [RES_VPLL1] = 0x2f, | |
108 | [RES_VPLL2] = 0x33, | |
109 | [RES_VSIM] = 0x37, | |
110 | [RES_VDAC] = 0x3b, | |
111 | [RES_VINTANA1] = 0x3f, | |
112 | [RES_VINTANA2] = 0x43, | |
113 | [RES_VINTDIG] = 0x47, | |
114 | [RES_VIO] = 0x4b, | |
115 | [RES_VDD1] = 0x55, | |
116 | [RES_VDD2] = 0x63, | |
117 | [RES_VUSB_1V5] = 0x71, | |
118 | [RES_VUSB_1V8] = 0x74, | |
119 | [RES_VUSB_3V1] = 0x77, | |
120 | [RES_VUSBCP] = 0x7a, | |
121 | [RES_REGEN] = 0x7f, | |
122 | [RES_NRES_PWRON] = 0x82, | |
123 | [RES_CLKEN] = 0x85, | |
124 | [RES_SYSEN] = 0x88, | |
125 | [RES_HFCLKOUT] = 0x8b, | |
126 | [RES_32KCLKOUT] = 0x8e, | |
127 | [RES_RESET] = 0x91, | |
d7ac829f | 128 | [RES_MAIN_REF] = 0x94, |
ebf0bd36 AK |
129 | }; |
130 | ||
f791be49 | 131 | static int twl4030_write_script_byte(u8 address, u8 byte) |
ebf0bd36 AK |
132 | { |
133 | int err; | |
134 | ||
4850f124 | 135 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_MEMORY_ADDRESS); |
ebf0bd36 AK |
136 | if (err) |
137 | goto out; | |
4850f124 | 138 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, byte, R_MEMORY_DATA); |
ebf0bd36 AK |
139 | out: |
140 | return err; | |
141 | } | |
142 | ||
f791be49 | 143 | static int twl4030_write_script_ins(u8 address, u16 pmb_message, |
ebf0bd36 AK |
144 | u8 delay, u8 next) |
145 | { | |
146 | int err; | |
147 | ||
148 | address *= 4; | |
149 | err = twl4030_write_script_byte(address++, pmb_message >> 8); | |
150 | if (err) | |
151 | goto out; | |
152 | err = twl4030_write_script_byte(address++, pmb_message & 0xff); | |
153 | if (err) | |
154 | goto out; | |
155 | err = twl4030_write_script_byte(address++, delay); | |
156 | if (err) | |
157 | goto out; | |
158 | err = twl4030_write_script_byte(address++, next); | |
159 | out: | |
160 | return err; | |
161 | } | |
162 | ||
f791be49 | 163 | static int twl4030_write_script(u8 address, struct twl4030_ins *script, |
ebf0bd36 AK |
164 | int len) |
165 | { | |
f65e9eac | 166 | int err = -EINVAL; |
ebf0bd36 AK |
167 | |
168 | for (; len; len--, address++, script++) { | |
169 | if (len == 1) { | |
170 | err = twl4030_write_script_ins(address, | |
171 | script->pmb_message, | |
172 | script->delay, | |
173 | END_OF_SCRIPT); | |
174 | if (err) | |
175 | break; | |
176 | } else { | |
177 | err = twl4030_write_script_ins(address, | |
178 | script->pmb_message, | |
179 | script->delay, | |
180 | address + 1); | |
181 | if (err) | |
182 | break; | |
183 | } | |
184 | } | |
185 | return err; | |
186 | } | |
187 | ||
f791be49 | 188 | static int twl4030_config_wakeup3_sequence(u8 address) |
ebf0bd36 AK |
189 | { |
190 | int err; | |
191 | u8 data; | |
192 | ||
193 | /* Set SLEEP to ACTIVE SEQ address for P3 */ | |
4850f124 | 194 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_S2A3); |
ebf0bd36 AK |
195 | if (err) |
196 | goto out; | |
197 | ||
198 | /* P3 LVL_WAKEUP should be on LEVEL */ | |
4850f124 | 199 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_P3_SW_EVENTS); |
ebf0bd36 AK |
200 | if (err) |
201 | goto out; | |
32057281 | 202 | data |= PWR_LVL_WAKEUP; |
4850f124 | 203 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_P3_SW_EVENTS); |
ebf0bd36 AK |
204 | out: |
205 | if (err) | |
206 | pr_err("TWL4030 wakeup sequence for P3 config error\n"); | |
207 | return err; | |
208 | } | |
209 | ||
f791be49 | 210 | static int twl4030_config_wakeup12_sequence(u8 address) |
ebf0bd36 AK |
211 | { |
212 | int err = 0; | |
213 | u8 data; | |
214 | ||
215 | /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */ | |
4850f124 | 216 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_S2A12); |
ebf0bd36 AK |
217 | if (err) |
218 | goto out; | |
219 | ||
220 | /* P1/P2 LVL_WAKEUP should be on LEVEL */ | |
4850f124 | 221 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_P1_SW_EVENTS); |
ebf0bd36 AK |
222 | if (err) |
223 | goto out; | |
224 | ||
32057281 | 225 | data |= PWR_LVL_WAKEUP; |
4850f124 | 226 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_P1_SW_EVENTS); |
ebf0bd36 AK |
227 | if (err) |
228 | goto out; | |
229 | ||
4850f124 | 230 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, R_P2_SW_EVENTS); |
ebf0bd36 AK |
231 | if (err) |
232 | goto out; | |
233 | ||
32057281 | 234 | data |= PWR_LVL_WAKEUP; |
4850f124 | 235 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, R_P2_SW_EVENTS); |
ebf0bd36 AK |
236 | if (err) |
237 | goto out; | |
238 | ||
239 | if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) { | |
240 | /* Disabling AC charger effect on sleep-active transitions */ | |
4850f124 PU |
241 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &data, |
242 | R_CFG_P1_TRANSITION); | |
ebf0bd36 AK |
243 | if (err) |
244 | goto out; | |
245 | data &= ~(1<<1); | |
4850f124 PU |
246 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, data, |
247 | R_CFG_P1_TRANSITION); | |
ebf0bd36 AK |
248 | if (err) |
249 | goto out; | |
250 | } | |
251 | ||
252 | out: | |
253 | if (err) | |
254 | pr_err("TWL4030 wakeup sequence for P1 and P2" \ | |
255 | "config error\n"); | |
256 | return err; | |
257 | } | |
258 | ||
f791be49 | 259 | static int twl4030_config_sleep_sequence(u8 address) |
ebf0bd36 AK |
260 | { |
261 | int err; | |
262 | ||
263 | /* Set ACTIVE to SLEEP SEQ address in T2 memory*/ | |
4850f124 | 264 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_A2S); |
ebf0bd36 AK |
265 | |
266 | if (err) | |
267 | pr_err("TWL4030 sleep sequence config error\n"); | |
268 | ||
269 | return err; | |
270 | } | |
271 | ||
f791be49 | 272 | static int twl4030_config_warmreset_sequence(u8 address) |
ebf0bd36 AK |
273 | { |
274 | int err; | |
275 | u8 rd_data; | |
276 | ||
277 | /* Set WARM RESET SEQ address for P1 */ | |
4850f124 | 278 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, address, R_SEQ_ADD_WARM); |
ebf0bd36 AK |
279 | if (err) |
280 | goto out; | |
281 | ||
282 | /* P1/P2/P3 enable WARMRESET */ | |
4850f124 | 283 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &rd_data, R_P1_SW_EVENTS); |
ebf0bd36 AK |
284 | if (err) |
285 | goto out; | |
286 | ||
32057281 | 287 | rd_data |= PWR_ENABLE_WARMRESET; |
4850f124 | 288 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, rd_data, R_P1_SW_EVENTS); |
ebf0bd36 AK |
289 | if (err) |
290 | goto out; | |
291 | ||
4850f124 | 292 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &rd_data, R_P2_SW_EVENTS); |
ebf0bd36 AK |
293 | if (err) |
294 | goto out; | |
295 | ||
32057281 | 296 | rd_data |= PWR_ENABLE_WARMRESET; |
4850f124 | 297 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, rd_data, R_P2_SW_EVENTS); |
ebf0bd36 AK |
298 | if (err) |
299 | goto out; | |
300 | ||
4850f124 | 301 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &rd_data, R_P3_SW_EVENTS); |
ebf0bd36 AK |
302 | if (err) |
303 | goto out; | |
304 | ||
32057281 | 305 | rd_data |= PWR_ENABLE_WARMRESET; |
4850f124 | 306 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, rd_data, R_P3_SW_EVENTS); |
ebf0bd36 AK |
307 | out: |
308 | if (err) | |
309 | pr_err("TWL4030 warmreset seq config error\n"); | |
310 | return err; | |
311 | } | |
312 | ||
f791be49 | 313 | static int twl4030_configure_resource(struct twl4030_resconfig *rconfig) |
ebf0bd36 AK |
314 | { |
315 | int rconfig_addr; | |
316 | int err; | |
317 | u8 type; | |
318 | u8 grp; | |
b4ead61e | 319 | u8 remap; |
ebf0bd36 AK |
320 | |
321 | if (rconfig->resource > TOTAL_RESOURCES) { | |
322 | pr_err("TWL4030 Resource %d does not exist\n", | |
323 | rconfig->resource); | |
324 | return -EINVAL; | |
325 | } | |
326 | ||
327 | rconfig_addr = res_config_addrs[rconfig->resource]; | |
328 | ||
329 | /* Set resource group */ | |
4850f124 | 330 | err = twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &grp, |
fc7b92fc | 331 | rconfig_addr + DEV_GRP_OFFSET); |
ebf0bd36 AK |
332 | if (err) { |
333 | pr_err("TWL4030 Resource %d group could not be read\n", | |
334 | rconfig->resource); | |
335 | return err; | |
336 | } | |
337 | ||
56baa667 | 338 | if (rconfig->devgroup != TWL4030_RESCONFIG_UNDEF) { |
e97d1546 AK |
339 | grp &= ~DEV_GRP_MASK; |
340 | grp |= rconfig->devgroup << DEV_GRP_SHIFT; | |
4850f124 | 341 | err = twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, |
fc7b92fc | 342 | grp, rconfig_addr + DEV_GRP_OFFSET); |
ebf0bd36 AK |
343 | if (err < 0) { |
344 | pr_err("TWL4030 failed to program devgroup\n"); | |
345 | return err; | |
346 | } | |
347 | } | |
348 | ||
349 | /* Set resource types */ | |
4850f124 | 350 | err = twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &type, |
ebf0bd36 AK |
351 | rconfig_addr + TYPE_OFFSET); |
352 | if (err < 0) { | |
353 | pr_err("TWL4030 Resource %d type could not be read\n", | |
354 | rconfig->resource); | |
355 | return err; | |
356 | } | |
357 | ||
56baa667 | 358 | if (rconfig->type != TWL4030_RESCONFIG_UNDEF) { |
ebf0bd36 AK |
359 | type &= ~TYPE_MASK; |
360 | type |= rconfig->type << TYPE_SHIFT; | |
361 | } | |
362 | ||
56baa667 | 363 | if (rconfig->type2 != TWL4030_RESCONFIG_UNDEF) { |
ebf0bd36 AK |
364 | type &= ~TYPE2_MASK; |
365 | type |= rconfig->type2 << TYPE2_SHIFT; | |
366 | } | |
367 | ||
4850f124 | 368 | err = twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, |
ebf0bd36 AK |
369 | type, rconfig_addr + TYPE_OFFSET); |
370 | if (err < 0) { | |
371 | pr_err("TWL4030 failed to program resource type\n"); | |
372 | return err; | |
373 | } | |
374 | ||
b4ead61e | 375 | /* Set remap states */ |
4850f124 | 376 | err = twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &remap, |
fc7b92fc | 377 | rconfig_addr + REMAP_OFFSET); |
b4ead61e AK |
378 | if (err < 0) { |
379 | pr_err("TWL4030 Resource %d remap could not be read\n", | |
380 | rconfig->resource); | |
381 | return err; | |
382 | } | |
383 | ||
53cf9a60 | 384 | if (rconfig->remap_off != TWL4030_RESCONFIG_UNDEF) { |
b4ead61e AK |
385 | remap &= ~OFF_STATE_MASK; |
386 | remap |= rconfig->remap_off << OFF_STATE_SHIFT; | |
387 | } | |
388 | ||
53cf9a60 | 389 | if (rconfig->remap_sleep != TWL4030_RESCONFIG_UNDEF) { |
b4ead61e | 390 | remap &= ~SLEEP_STATE_MASK; |
1ea933f4 | 391 | remap |= rconfig->remap_sleep << SLEEP_STATE_SHIFT; |
b4ead61e AK |
392 | } |
393 | ||
4850f124 | 394 | err = twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, |
fc7b92fc B |
395 | remap, |
396 | rconfig_addr + REMAP_OFFSET); | |
b4ead61e AK |
397 | if (err < 0) { |
398 | pr_err("TWL4030 failed to program remap\n"); | |
399 | return err; | |
400 | } | |
401 | ||
ebf0bd36 AK |
402 | return 0; |
403 | } | |
404 | ||
f791be49 | 405 | static int load_twl4030_script(struct twl4030_script *tscript, |
ebf0bd36 AK |
406 | u8 address) |
407 | { | |
408 | int err; | |
75a74565 | 409 | static int order; |
ebf0bd36 AK |
410 | |
411 | /* Make sure the script isn't going beyond last valid address (0x3f) */ | |
412 | if ((address + tscript->size) > END_OF_SCRIPT) { | |
413 | pr_err("TWL4030 scripts too big error\n"); | |
414 | return -EINVAL; | |
415 | } | |
416 | ||
417 | err = twl4030_write_script(address, tscript->script, tscript->size); | |
418 | if (err) | |
419 | goto out; | |
420 | ||
421 | if (tscript->flags & TWL4030_WRST_SCRIPT) { | |
422 | err = twl4030_config_warmreset_sequence(address); | |
423 | if (err) | |
424 | goto out; | |
425 | } | |
426 | if (tscript->flags & TWL4030_WAKEUP12_SCRIPT) { | |
fc7d76e4 TL |
427 | /* Reset any existing sleep script to avoid hangs on reboot */ |
428 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, | |
429 | R_SEQ_ADD_A2S); | |
430 | if (err) | |
431 | goto out; | |
432 | ||
ebf0bd36 AK |
433 | err = twl4030_config_wakeup12_sequence(address); |
434 | if (err) | |
435 | goto out; | |
75a74565 | 436 | order = 1; |
ebf0bd36 AK |
437 | } |
438 | if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) { | |
439 | err = twl4030_config_wakeup3_sequence(address); | |
440 | if (err) | |
441 | goto out; | |
442 | } | |
c62dd365 | 443 | if (tscript->flags & TWL4030_SLEEP_SCRIPT) { |
1f968ff6 | 444 | if (!order) |
75a74565 AK |
445 | pr_warning("TWL4030: Bad order of scripts (sleep "\ |
446 | "script before wakeup) Leads to boot"\ | |
447 | "failure on some boards\n"); | |
ebf0bd36 | 448 | err = twl4030_config_sleep_sequence(address); |
c62dd365 | 449 | } |
ebf0bd36 AK |
450 | out: |
451 | return err; | |
452 | } | |
453 | ||
11a441ce MT |
454 | int twl4030_remove_script(u8 flags) |
455 | { | |
456 | int err = 0; | |
457 | ||
4850f124 PU |
458 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, |
459 | TWL4030_PM_MASTER_PROTECT_KEY); | |
11a441ce MT |
460 | if (err) { |
461 | pr_err("twl4030: unable to unlock PROTECT_KEY\n"); | |
462 | return err; | |
463 | } | |
464 | ||
4850f124 PU |
465 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, |
466 | TWL4030_PM_MASTER_PROTECT_KEY); | |
11a441ce MT |
467 | if (err) { |
468 | pr_err("twl4030: unable to unlock PROTECT_KEY\n"); | |
469 | return err; | |
470 | } | |
471 | ||
472 | if (flags & TWL4030_WRST_SCRIPT) { | |
4850f124 PU |
473 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, |
474 | R_SEQ_ADD_WARM); | |
11a441ce MT |
475 | if (err) |
476 | return err; | |
477 | } | |
478 | if (flags & TWL4030_WAKEUP12_SCRIPT) { | |
4850f124 PU |
479 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, |
480 | R_SEQ_ADD_S2A12); | |
eac78a21 | 481 | if (err) |
11a441ce MT |
482 | return err; |
483 | } | |
484 | if (flags & TWL4030_WAKEUP3_SCRIPT) { | |
4850f124 PU |
485 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, |
486 | R_SEQ_ADD_S2A3); | |
11a441ce MT |
487 | if (err) |
488 | return err; | |
489 | } | |
490 | if (flags & TWL4030_SLEEP_SCRIPT) { | |
4850f124 PU |
491 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, END_OF_SCRIPT, |
492 | R_SEQ_ADD_A2S); | |
11a441ce MT |
493 | if (err) |
494 | return err; | |
495 | } | |
496 | ||
4850f124 PU |
497 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, |
498 | TWL4030_PM_MASTER_PROTECT_KEY); | |
11a441ce MT |
499 | if (err) |
500 | pr_err("TWL4030 Unable to relock registers\n"); | |
501 | ||
502 | return err; | |
503 | } | |
504 | ||
fae01582 | 505 | static int twl4030_power_configure_scripts(struct twl4030_power_data *pdata) |
f58cb407 FV |
506 | { |
507 | int err; | |
508 | int i; | |
509 | u8 address = twl4030_start_script_address; | |
510 | ||
511 | for (i = 0; i < pdata->num; i++) { | |
512 | err = load_twl4030_script(pdata->scripts[i], address); | |
513 | if (err) | |
514 | return err; | |
515 | address += pdata->scripts[i]->size; | |
516 | } | |
517 | ||
518 | return 0; | |
519 | } | |
520 | ||
fae01582 | 521 | static int twl4030_power_configure_resources(struct twl4030_power_data *pdata) |
f58cb407 FV |
522 | { |
523 | struct twl4030_resconfig *resconfig = pdata->resource_config; | |
524 | int err; | |
525 | ||
526 | if (resconfig) { | |
527 | while (resconfig->resource) { | |
528 | err = twl4030_configure_resource(resconfig); | |
529 | if (err) | |
530 | return err; | |
531 | resconfig++; | |
532 | } | |
533 | } | |
534 | ||
535 | return 0; | |
536 | } | |
537 | ||
26cc3ab9 IG |
538 | /* |
539 | * In master mode, start the power off sequence. | |
540 | * After a successful execution, TWL shuts down the power to the SoC | |
541 | * and all peripherals connected to it. | |
542 | */ | |
543 | void twl4030_power_off(void) | |
544 | { | |
545 | int err; | |
546 | ||
4850f124 | 547 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, PWR_DEVOFF, |
26cc3ab9 IG |
548 | TWL4030_PM_MASTER_P1_SW_EVENTS); |
549 | if (err) | |
550 | pr_err("TWL4030 Unable to power off\n"); | |
551 | } | |
552 | ||
b0fc1da4 FV |
553 | static bool twl4030_power_use_poweroff(struct twl4030_power_data *pdata, |
554 | struct device_node *node) | |
555 | { | |
556 | if (pdata && pdata->use_poweroff) | |
557 | return true; | |
558 | ||
559 | if (of_property_read_bool(node, "ti,use_poweroff")) | |
560 | return true; | |
561 | ||
562 | return false; | |
563 | } | |
564 | ||
fae01582 | 565 | static int twl4030_power_probe(struct platform_device *pdev) |
ebf0bd36 | 566 | { |
334a41ce | 567 | struct twl4030_power_data *pdata = dev_get_platdata(&pdev->dev); |
b0fc1da4 | 568 | struct device_node *node = pdev->dev.of_node; |
ebf0bd36 | 569 | int err = 0; |
cb3cabd6 | 570 | int err2 = 0; |
f58cb407 | 571 | u8 val; |
ebf0bd36 | 572 | |
b0fc1da4 FV |
573 | if (!pdata && !node) { |
574 | dev_err(&pdev->dev, "Platform data is missing\n"); | |
575 | return -EINVAL; | |
576 | } | |
577 | ||
4850f124 PU |
578 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, |
579 | TWL4030_PM_MASTER_PROTECT_KEY); | |
e77a4c2f FV |
580 | err |= twl_i2c_write_u8(TWL_MODULE_PM_MASTER, |
581 | TWL4030_PM_MASTER_KEY_CFG2, | |
4850f124 | 582 | TWL4030_PM_MASTER_PROTECT_KEY); |
e77a4c2f FV |
583 | |
584 | if (err) { | |
585 | pr_err("TWL4030 Unable to unlock registers\n"); | |
586 | return err; | |
587 | } | |
ebf0bd36 | 588 | |
b0fc1da4 FV |
589 | if (pdata) { |
590 | /* TODO: convert to device tree */ | |
591 | err = twl4030_power_configure_scripts(pdata); | |
e77a4c2f FV |
592 | if (err) { |
593 | pr_err("TWL4030 failed to load scripts\n"); | |
cb3cabd6 | 594 | goto relock; |
e77a4c2f | 595 | } |
b0fc1da4 | 596 | err = twl4030_power_configure_resources(pdata); |
e77a4c2f FV |
597 | if (err) { |
598 | pr_err("TWL4030 failed to configure resource\n"); | |
cb3cabd6 | 599 | goto relock; |
e77a4c2f | 600 | } |
b0fc1da4 | 601 | } |
ebf0bd36 | 602 | |
26cc3ab9 | 603 | /* Board has to be wired properly to use this feature */ |
b0fc1da4 | 604 | if (twl4030_power_use_poweroff(pdata, node) && !pm_power_off) { |
26cc3ab9 | 605 | /* Default for SEQ_OFFSYNC is set, lets ensure this */ |
4850f124 | 606 | err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &val, |
26cc3ab9 IG |
607 | TWL4030_PM_MASTER_CFG_P123_TRANSITION); |
608 | if (err) { | |
609 | pr_warning("TWL4030 Unable to read registers\n"); | |
610 | ||
611 | } else if (!(val & SEQ_OFFSYNC)) { | |
612 | val |= SEQ_OFFSYNC; | |
4850f124 | 613 | err = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, val, |
26cc3ab9 IG |
614 | TWL4030_PM_MASTER_CFG_P123_TRANSITION); |
615 | if (err) { | |
616 | pr_err("TWL4030 Unable to setup SEQ_OFFSYNC\n"); | |
617 | goto relock; | |
618 | } | |
619 | } | |
620 | ||
621 | pm_power_off = twl4030_power_off; | |
622 | } | |
623 | ||
624 | relock: | |
cb3cabd6 | 625 | err2 = twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, |
4850f124 | 626 | TWL4030_PM_MASTER_PROTECT_KEY); |
cb3cabd6 | 627 | if (err2) { |
ebf0bd36 | 628 | pr_err("TWL4030 Unable to relock registers\n"); |
cb3cabd6 FV |
629 | return err2; |
630 | } | |
631 | ||
637d6895 | 632 | return err; |
637d6895 FV |
633 | } |
634 | ||
635 | static int twl4030_power_remove(struct platform_device *pdev) | |
636 | { | |
637 | return 0; | |
ebf0bd36 | 638 | } |
637d6895 | 639 | |
b0fc1da4 FV |
640 | #ifdef CONFIG_OF |
641 | static const struct of_device_id twl4030_power_of_match[] = { | |
642 | {.compatible = "ti,twl4030-power", }, | |
643 | { }, | |
644 | }; | |
645 | MODULE_DEVICE_TABLE(of, twl4030_power_of_match); | |
646 | #endif | |
647 | ||
637d6895 FV |
648 | static struct platform_driver twl4030_power_driver = { |
649 | .driver = { | |
650 | .name = "twl4030_power", | |
651 | .owner = THIS_MODULE, | |
b0fc1da4 | 652 | .of_match_table = of_match_ptr(twl4030_power_of_match), |
637d6895 FV |
653 | }, |
654 | .probe = twl4030_power_probe, | |
655 | .remove = twl4030_power_remove, | |
656 | }; | |
657 | ||
658 | module_platform_driver(twl4030_power_driver); | |
659 | ||
660 | MODULE_AUTHOR("Nokia Corporation"); | |
661 | MODULE_AUTHOR("Texas Instruments, Inc."); | |
662 | MODULE_DESCRIPTION("Power management for TWL4030"); | |
663 | MODULE_LICENSE("GPL"); | |
664 | MODULE_ALIAS("platform:twl4030_power"); |