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
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
14 #include <linux/firmware.h>
16 #include <asm/mach-types.h>
17 #include <asm/hardware.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
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
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
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
45 Both methods have been tested to work. Method #2 seems safer so that is
48 We can jump into the resume function a couple of ways:
50 a. load the resume address from PSPR into pc
54 .word 0x40f00008; // PSPR
56 b. load the resume address directly into pc
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
)
77 if (!bootloader_valid
) {
78 printk(KERN_ERR
"h2200_pm: no valid bootloader; suspend cancelled\n");
82 ret
= pxa_pm_enter_orig(state
);
94 /* Return a pointer to the bootloader. */
96 get_hamcop_bootloader(void)
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)
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
);
125 schedule_delayed_work(&fw_work
, BOOTLOADER_LOAD_DELAY
);
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
);
135 bootloader_valid
= 1;
139 static int __init
h2200_pm_init(void) {
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
);
153 printk(KERN_ERR
"h2200_pm: unable to kmalloc bootloader\n");
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
160 sram
= ioremap(H2200_HAMCOP_BASE
, HAMCOP_MAP_SIZE
);
161 memcpy(bootloader
, sram
, HAMCOP_SRAM_Size
);
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) {
171 bootloader_valid
= 1;
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
);
192 schedule_delayed_work(&fw_work
, BOOTLOADER_LOAD_DELAY
);
196 bootloader_valid
= 1;
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");