hh.org updates
[hh.org.git] / arch / arm / mach-pxa / h2200 / h2200_pm.c
blobfcae9615de9a308e85d7d96370e26f5191610e0b
1 /*
2 * h2200 power management support for the HTC bootloader
4 * Use consistent with the GNU GPL is permitted, provided that this
5 * copyright notice is preserved in its entirety in all copies and
6 * derived works.
8 */
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/pm.h>
14 #include <linux/firmware.h>
16 #include <asm/mach-types.h>
17 #include <asm/hardware.h>
18 #include <asm/io.h>
19 #include <asm/arch/pxa-regs.h>
21 #include <asm/arch/h2200-asic.h>
22 #include <asm/hardware/ipaq-hamcop.h>
25 /* On resume, the PXA begins executing the code at 0x00000000, which is in
26 HAMCOP's SRAM. But there is a conflict: the samcop_sdi driver uses HAMCOP's
27 SRAM for DMA, which results in the bootloader being overwritten. So we
28 must save the bootloader before it gets overwritten, and restore it at
29 suspend.
31 The problem with using the HTC bootloader is that it does its
32 initialization and then jumps to 0xa0040000, which on Linux
33 is somewhere in kernel code. There are a couple of solutions to this
34 problem:
36 1. Save/restore enough bytes at 0xa0040000 to insert code to jump to the
37 resume function. This is still a little risky depending on what's
38 at 0xa0040000.
40 2. Store a position-independent code fragment into SRAM that jumps to the
41 resume function. This is safer since we don't have to worry about
42 stomping on the kernel; and we have to copy the bootloader into place
43 anyway.
45 Both methods have been tested to work. Method #2 seems safer so that is
46 what we're using.
48 We can jump into the resume function a couple of ways:
50 a. load the resume address from PSPR into pc
52 ldr r0, [pc, #0]
53 ldr pc, [r0]
54 .word 0x40f00008; // PSPR
56 b. load the resume address directly into pc
58 ldr pc, [pc, #-4]
59 .word resume_addr
62 static u8 *bootloader;
63 static int bootloader_valid = 0;
65 #define BOOTLOADER_LOAD_DELAY (HZ * 30)
66 static struct work_struct fw_work;
68 extern struct pm_ops pxa_pm_ops;
70 static int (*pxa_pm_enter_orig)(suspend_state_t state);
73 static int h2200_pxa_pm_enter(suspend_state_t state)
75 int ret;
77 if (!bootloader_valid) {
78 printk(KERN_ERR "h2200_pm: no valid bootloader; suspend cancelled\n");
79 return 1;
82 ret = pxa_pm_enter_orig(state);
84 MSC0 = 0x246c7ffc;
85 (void)MSC0;
86 MSC1 = 0x7ff07ff0;
87 (void)MSC1;
88 MSC2 = 0x7ff07ff0;
89 (void)MSC2;
91 return ret;
94 /* Return a pointer to the bootloader. */
95 u8 *
96 get_hamcop_bootloader(void)
98 return bootloader;
100 EXPORT_SYMBOL(get_hamcop_bootloader);
102 /* Patch the bootloader so it resumes to the address in PSPR
103 * instead of 0xa0040000. */
104 static void h2200_patch_fw(void)
106 int i;
107 u32 *code = (u32 *)bootloader;
109 i = 0x75c / sizeof(u32);
110 code[i++] = 0xe59f0000; /* ldr r0, [pc, #0] */
111 code[i++] = 0xe590f000; /* ldr pc, [r0] */
112 code[i++] = 0x40f00008; /* PSPR */
115 #if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
116 static void h2200_load_bootloader(void *data)
118 const struct firmware *fw;
120 if (request_firmware(&fw, "h2200_bootloader.bin", &h2200_hamcop.dev)) {
121 printk(KERN_ERR "h2200_pm: request_firmware failed\n");
122 #if defined(CONFIG_FW_LOADER_MODULE)
123 schedule_work(&fw_work);
124 #else
125 schedule_delayed_work(&fw_work, BOOTLOADER_LOAD_DELAY);
126 #endif
127 return;
130 printk("h2200_pm: bootloader loaded\n");
131 memcpy(bootloader, fw->data, fw->size < HAMCOP_SRAM_Size ?
132 fw->size : HAMCOP_SRAM_Size);
133 release_firmware(fw);
134 h2200_patch_fw();
135 bootloader_valid = 1;
137 #endif
139 static int __init h2200_pm_init(void) {
141 u8 *sram;
143 if (machine_is_h2200()) {
145 printk("Initialising wince bootloader suspend workaround\n");
147 pxa_pm_enter_orig = pxa_pm_ops.enter;
148 pxa_pm_ops.enter = h2200_pxa_pm_enter;
150 bootloader_valid = 0;
151 bootloader = kmalloc(HAMCOP_SRAM_Size, GFP_KERNEL);
152 if (!bootloader) {
153 printk(KERN_ERR "h2200_pm: unable to kmalloc bootloader\n");
154 return -ENOMEM;
157 /* Copy what's already in SRAM for now, as it's good in
158 * many cases (e.g. where LAB hasn't already written
159 * over it.) */
160 sram = ioremap(H2200_HAMCOP_BASE, HAMCOP_MAP_SIZE);
161 memcpy(bootloader, sram, HAMCOP_SRAM_Size);
162 iounmap(sram);
164 /* If the bootloader is good, there's no need to load
165 * another. Check for the HTC bootloader by looking for
166 * 'b 0x80' at +0x0, and for 'ECEC' at +0x40. */
167 if (((u32 *)bootloader)[0] == 0xea00001e &&
168 ((u32 *)bootloader)[0x10] == 0x43454345) {
170 h2200_patch_fw();
171 bootloader_valid = 1;
172 } else
173 printk(KERN_ERR "h2200_pm: bootloader may be invalid; "
174 "resume may not work\n");
176 #if defined(CONFIG_FW_LOADER) || defined(CONFIG_FW_LOADER_MODULE)
177 if (!bootloader_valid) {
179 /* There doesn't seem to be a valid bootloader in
180 * SRAM, so try to load one from userland. Note that
181 * we schedule the work for a little later when compiled
182 * into the kernel (rather than as a module), to give
183 * other modules a chance to initialize; without this
184 * delay, we were getting an error,
185 * "Badness in kref_get". */
186 printk("h2200_pm: requesting bootloader\n");
188 INIT_WORK(&fw_work, h2200_load_bootloader, NULL);
189 #if defined(CONFIG_FW_LOADER_MODULE)
190 schedule_work(&fw_work);
191 #else
192 schedule_delayed_work(&fw_work, BOOTLOADER_LOAD_DELAY);
193 #endif
195 #else
196 bootloader_valid = 1;
197 #endif
200 return 0;
203 static void __exit h2200_pm_exit(void)
205 pxa_pm_ops.enter = pxa_pm_enter_orig;
208 module_init(h2200_pm_init);
209 module_exit(h2200_pm_exit);
211 MODULE_AUTHOR("Matt Reimer <mreimer@vpop.net>");
212 MODULE_DESCRIPTION("HP iPAQ h2200 power management support for HTC bootloader");
213 MODULE_LICENSE("Dual BSD/GPL");