diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/pcmcia/hd64465_ss.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/pcmcia/hd64465_ss.c')
-rw-r--r-- | drivers/pcmcia/hd64465_ss.c | 968 |
1 files changed, 968 insertions, 0 deletions
diff --git a/drivers/pcmcia/hd64465_ss.c b/drivers/pcmcia/hd64465_ss.c new file mode 100644 index 000000000000..5e6c4ba7d995 --- /dev/null +++ b/drivers/pcmcia/hd64465_ss.c | |||
@@ -0,0 +1,968 @@ | |||
1 | /* | ||
2 | * $Id: hd64465_ss.c,v 1.7 2003/07/06 14:42:50 lethal Exp $ | ||
3 | * | ||
4 | * Device driver for the PCMCIA controller module of the | ||
5 | * Hitachi HD64465 handheld companion chip. | ||
6 | * | ||
7 | * Note that the HD64465 provides a very thin PCMCIA host bridge | ||
8 | * layer, requiring a lot of the work of supporting cards to be | ||
9 | * performed by the processor. For example: mapping of card | ||
10 | * interrupts to processor IRQs is done by IRQ demuxing software; | ||
11 | * IO and memory mappings are fixed; setting voltages according | ||
12 | * to card Voltage Select pins etc is done in software. | ||
13 | * | ||
14 | * Note also that this driver uses only the simple, fixed, | ||
15 | * 16MB, 16-bit wide mappings to PCMCIA spaces defined by the | ||
16 | * HD64465. Larger mappings, smaller mappings, or mappings of | ||
17 | * different width to the same socket, are all possible only by | ||
18 | * involving the SH7750's MMU, which is considered unnecessary here. | ||
19 | * The downside is that it may be possible for some drivers to | ||
20 | * break because they need or expect 8-bit mappings. | ||
21 | * | ||
22 | * This driver currently supports only the following configuration: | ||
23 | * SH7750 CPU, HD64465, TPS2206 voltage control chip. | ||
24 | * | ||
25 | * by Greg Banks <gbanks@pocketpenguins.com> | ||
26 | * (c) 2000 PocketPenguins Inc | ||
27 | */ | ||
28 | |||
29 | #include <linux/types.h> | ||
30 | #include <linux/module.h> | ||
31 | #include <linux/init.h> | ||
32 | #include <linux/string.h> | ||
33 | #include <linux/kernel.h> | ||
34 | #include <linux/ioport.h> | ||
35 | #include <linux/mm.h> | ||
36 | #include <linux/vmalloc.h> | ||
37 | #include <asm/errno.h> | ||
38 | #include <linux/irq.h> | ||
39 | #include <linux/interrupt.h> | ||
40 | #include <linux/device.h> | ||
41 | |||
42 | #include <asm/io.h> | ||
43 | #include <asm/hd64465/hd64465.h> | ||
44 | #include <asm/hd64465/io.h> | ||
45 | |||
46 | #include <pcmcia/version.h> | ||
47 | #include <pcmcia/cs_types.h> | ||
48 | #include <pcmcia/cs.h> | ||
49 | #include <pcmcia/cistpl.h> | ||
50 | #include <pcmcia/ds.h> | ||
51 | #include <pcmcia/ss.h> | ||
52 | #include <pcmcia/bulkmem.h> | ||
53 | #include "cs_internal.h" | ||
54 | |||
55 | #define MODNAME "hd64465_ss" | ||
56 | |||
57 | /* #define HD64465_DEBUG 1 */ | ||
58 | |||
59 | #if HD64465_DEBUG | ||
60 | #define DPRINTK(args...) printk(MODNAME ": " args) | ||
61 | #else | ||
62 | #define DPRINTK(args...) | ||
63 | #endif | ||
64 | |||
65 | extern int hd64465_io_debug; | ||
66 | extern void * p3_ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags); | ||
67 | extern void p3_iounmap(void *addr); | ||
68 | |||
69 | /*============================================================*/ | ||
70 | |||
71 | #define HS_IO_MAP_SIZE (64*1024) | ||
72 | |||
73 | typedef struct hs_socket_t | ||
74 | { | ||
75 | unsigned int number; | ||
76 | u_int irq; | ||
77 | u_long mem_base; | ||
78 | void *io_base; | ||
79 | u_long mem_length; | ||
80 | u_int ctrl_base; | ||
81 | socket_state_t state; | ||
82 | pccard_io_map io_maps[MAX_IO_WIN]; | ||
83 | pccard_mem_map mem_maps[MAX_WIN]; | ||
84 | struct pcmcia_socket socket; | ||
85 | } hs_socket_t; | ||
86 | |||
87 | |||
88 | |||
89 | #define HS_MAX_SOCKETS 2 | ||
90 | static hs_socket_t hs_sockets[HS_MAX_SOCKETS]; | ||
91 | |||
92 | #define hs_in(sp, r) inb((sp)->ctrl_base + (r)) | ||
93 | #define hs_out(sp, v, r) outb(v, (sp)->ctrl_base + (r)) | ||
94 | |||
95 | |||
96 | /* translate a boolean value to a bit in a register */ | ||
97 | #define bool_to_regbit(sp, r, bi, bo) \ | ||
98 | do { \ | ||
99 | unsigned short v = hs_in(sp, r); \ | ||
100 | if (bo) \ | ||
101 | v |= (bi); \ | ||
102 | else \ | ||
103 | v &= ~(bi); \ | ||
104 | hs_out(sp, v, r); \ | ||
105 | } while(0) | ||
106 | |||
107 | /* register offsets from HD64465_REG_PCC[01]ISR */ | ||
108 | #define ISR 0x0 | ||
109 | #define GCR 0x2 | ||
110 | #define CSCR 0x4 | ||
111 | #define CSCIER 0x6 | ||
112 | #define SCR 0x8 | ||
113 | |||
114 | |||
115 | /* Mask and values for CSCIER register */ | ||
116 | #define IER_MASK 0x80 | ||
117 | #define IER_ON 0x3f /* interrupts on */ | ||
118 | #define IER_OFF 0x00 /* interrupts off */ | ||
119 | |||
120 | /*============================================================*/ | ||
121 | |||
122 | #if HD64465_DEBUG > 10 | ||
123 | |||
124 | static void cis_hex_dump(const unsigned char *x, int len) | ||
125 | { | ||
126 | int i; | ||
127 | |||
128 | for (i=0 ; i<len ; i++) | ||
129 | { | ||
130 | if (!(i & 0xf)) | ||
131 | printk("\n%08x", (unsigned)(x + i)); | ||
132 | printk(" %02x", *(volatile unsigned short*)x); | ||
133 | x += 2; | ||
134 | } | ||
135 | printk("\n"); | ||
136 | } | ||
137 | |||
138 | #endif | ||
139 | /*============================================================*/ | ||
140 | |||
141 | /* | ||
142 | * This code helps create the illusion that the IREQ line from | ||
143 | * the PC card is mapped to one of the CPU's IRQ lines by the | ||
144 | * host bridge hardware (which is how every host bridge *except* | ||
145 | * the HD64465 works). In particular, it supports enabling | ||
146 | * and disabling the IREQ line by code which knows nothing | ||
147 | * about the host bridge (e.g. device drivers, IDE code) using | ||
148 | * the request_irq(), free_irq(), probe_irq_on() and probe_irq_off() | ||
149 | * functions. Also, it supports sharing the mapped IRQ with | ||
150 | * real hardware IRQs from the -IRL0-3 lines. | ||
151 | */ | ||
152 | |||
153 | #define HS_NUM_MAPPED_IRQS 16 /* Limitation of the PCMCIA code */ | ||
154 | static struct | ||
155 | { | ||
156 | /* index is mapped irq number */ | ||
157 | hs_socket_t *sock; | ||
158 | hw_irq_controller *old_handler; | ||
159 | } hs_mapped_irq[HS_NUM_MAPPED_IRQS]; | ||
160 | |||
161 | static void hs_socket_enable_ireq(hs_socket_t *sp) | ||
162 | { | ||
163 | unsigned short cscier; | ||
164 | |||
165 | DPRINTK("hs_socket_enable_ireq(sock=%d)\n", sp->number); | ||
166 | |||
167 | cscier = hs_in(sp, CSCIER); | ||
168 | cscier &= ~HD64465_PCCCSCIER_PIREQE_MASK; | ||
169 | cscier |= HD64465_PCCCSCIER_PIREQE_LEVEL; | ||
170 | hs_out(sp, cscier, CSCIER); | ||
171 | } | ||
172 | |||
173 | static void hs_socket_disable_ireq(hs_socket_t *sp) | ||
174 | { | ||
175 | unsigned short cscier; | ||
176 | |||
177 | DPRINTK("hs_socket_disable_ireq(sock=%d)\n", sp->number); | ||
178 | |||
179 | cscier = hs_in(sp, CSCIER); | ||
180 | cscier &= ~HD64465_PCCCSCIER_PIREQE_MASK; | ||
181 | hs_out(sp, cscier, CSCIER); | ||
182 | } | ||
183 | |||
184 | static unsigned int hs_startup_irq(unsigned int irq) | ||
185 | { | ||
186 | hs_socket_enable_ireq(hs_mapped_irq[irq].sock); | ||
187 | hs_mapped_irq[irq].old_handler->startup(irq); | ||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static void hs_shutdown_irq(unsigned int irq) | ||
192 | { | ||
193 | hs_socket_disable_ireq(hs_mapped_irq[irq].sock); | ||
194 | hs_mapped_irq[irq].old_handler->shutdown(irq); | ||
195 | } | ||
196 | |||
197 | static void hs_enable_irq(unsigned int irq) | ||
198 | { | ||
199 | hs_socket_enable_ireq(hs_mapped_irq[irq].sock); | ||
200 | hs_mapped_irq[irq].old_handler->enable(irq); | ||
201 | } | ||
202 | |||
203 | static void hs_disable_irq(unsigned int irq) | ||
204 | { | ||
205 | hs_socket_disable_ireq(hs_mapped_irq[irq].sock); | ||
206 | hs_mapped_irq[irq].old_handler->disable(irq); | ||
207 | } | ||
208 | |||
209 | extern struct hw_interrupt_type no_irq_type; | ||
210 | |||
211 | static void hs_mask_and_ack_irq(unsigned int irq) | ||
212 | { | ||
213 | hs_socket_disable_ireq(hs_mapped_irq[irq].sock); | ||
214 | /* ack_none() spuriously complains about an unexpected IRQ */ | ||
215 | if (hs_mapped_irq[irq].old_handler != &no_irq_type) | ||
216 | hs_mapped_irq[irq].old_handler->ack(irq); | ||
217 | } | ||
218 | |||
219 | static void hs_end_irq(unsigned int irq) | ||
220 | { | ||
221 | hs_socket_enable_ireq(hs_mapped_irq[irq].sock); | ||
222 | hs_mapped_irq[irq].old_handler->end(irq); | ||
223 | } | ||
224 | |||
225 | |||
226 | static struct hw_interrupt_type hd64465_ss_irq_type = { | ||
227 | .typename = "PCMCIA-IRQ", | ||
228 | .startup = hs_startup_irq, | ||
229 | .shutdown = hs_shutdown_irq, | ||
230 | .enable = hs_enable_irq, | ||
231 | .disable = hs_disable_irq, | ||
232 | .ack = hs_mask_and_ack_irq, | ||
233 | .end = hs_end_irq | ||
234 | }; | ||
235 | |||
236 | /* | ||
237 | * This function should only ever be called with interrupts disabled. | ||
238 | */ | ||
239 | static void hs_map_irq(hs_socket_t *sp, unsigned int irq) | ||
240 | { | ||
241 | DPRINTK("hs_map_irq(sock=%d irq=%d)\n", sp->number, irq); | ||
242 | |||
243 | if (irq >= HS_NUM_MAPPED_IRQS) | ||
244 | return; | ||
245 | |||
246 | hs_mapped_irq[irq].sock = sp; | ||
247 | /* insert ourselves as the irq controller */ | ||
248 | hs_mapped_irq[irq].old_handler = irq_desc[irq].handler; | ||
249 | irq_desc[irq].handler = &hd64465_ss_irq_type; | ||
250 | } | ||
251 | |||
252 | |||
253 | /* | ||
254 | * This function should only ever be called with interrupts disabled. | ||
255 | */ | ||
256 | static void hs_unmap_irq(hs_socket_t *sp, unsigned int irq) | ||
257 | { | ||
258 | DPRINTK("hs_unmap_irq(sock=%d irq=%d)\n", sp->number, irq); | ||
259 | |||
260 | if (irq >= HS_NUM_MAPPED_IRQS) | ||
261 | return; | ||
262 | |||
263 | /* restore the original irq controller */ | ||
264 | irq_desc[irq].handler = hs_mapped_irq[irq].old_handler; | ||
265 | } | ||
266 | |||
267 | /*============================================================*/ | ||
268 | |||
269 | |||
270 | /* | ||
271 | * Set Vpp and Vcc (in tenths of a Volt). Does not | ||
272 | * support the hi-Z state. | ||
273 | * | ||
274 | * Note, this assumes the board uses a TPS2206 chip to control | ||
275 | * the Vcc and Vpp voltages to the hs_sockets. If your board | ||
276 | * uses the MIC2563 (also supported by the HD64465) then you | ||
277 | * will have to modify this function. | ||
278 | */ | ||
279 | /* 0V 3.3V 5.5V */ | ||
280 | static const u_char hs_tps2206_avcc[3] = { 0x00, 0x04, 0x08 }; | ||
281 | static const u_char hs_tps2206_bvcc[3] = { 0x00, 0x80, 0x40 }; | ||
282 | |||
283 | static int hs_set_voltages(hs_socket_t *sp, int Vcc, int Vpp) | ||
284 | { | ||
285 | u_int psr; | ||
286 | u_int vcci = 0; | ||
287 | u_int sock = sp->number; | ||
288 | |||
289 | DPRINTK("hs_set_voltage(%d, %d, %d)\n", sock, Vcc, Vpp); | ||
290 | |||
291 | switch (Vcc) | ||
292 | { | ||
293 | case 0: vcci = 0; break; | ||
294 | case 33: vcci = 1; break; | ||
295 | case 50: vcci = 2; break; | ||
296 | default: return 0; | ||
297 | } | ||
298 | |||
299 | /* Note: Vpp = 120 not supported -- Greg Banks */ | ||
300 | if (Vpp != 0 && Vpp != Vcc) | ||
301 | return 0; | ||
302 | |||
303 | /* The PSR register holds 8 of the 9 bits which control | ||
304 | * the TPS2206 via its serial interface. | ||
305 | */ | ||
306 | psr = inw(HD64465_REG_PCCPSR); | ||
307 | switch (sock) | ||
308 | { | ||
309 | case 0: | ||
310 | psr &= 0x0f; | ||
311 | psr |= hs_tps2206_avcc[vcci]; | ||
312 | psr |= (Vpp == 0 ? 0x00 : 0x02); | ||
313 | break; | ||
314 | case 1: | ||
315 | psr &= 0xf0; | ||
316 | psr |= hs_tps2206_bvcc[vcci]; | ||
317 | psr |= (Vpp == 0 ? 0x00 : 0x20); | ||
318 | break; | ||
319 | }; | ||
320 | outw(psr, HD64465_REG_PCCPSR); | ||
321 | |||
322 | return 1; | ||
323 | } | ||
324 | |||
325 | |||
326 | /*============================================================*/ | ||
327 | |||
328 | /* | ||
329 | * Drive the RESET line to the card. | ||
330 | */ | ||
331 | static void hs_reset_socket(hs_socket_t *sp, int on) | ||
332 | { | ||
333 | unsigned short v; | ||
334 | |||
335 | v = hs_in(sp, GCR); | ||
336 | if (on) | ||
337 | v |= HD64465_PCCGCR_PCCR; | ||
338 | else | ||
339 | v &= ~HD64465_PCCGCR_PCCR; | ||
340 | hs_out(sp, v, GCR); | ||
341 | } | ||
342 | |||
343 | /*============================================================*/ | ||
344 | |||
345 | static int hs_init(struct pcmcia_socket *s) | ||
346 | { | ||
347 | hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); | ||
348 | |||
349 | DPRINTK("hs_init(%d)\n", sp->number); | ||
350 | |||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | /*============================================================*/ | ||
355 | |||
356 | |||
357 | static int hs_get_status(struct pcmcia_socket *s, u_int *value) | ||
358 | { | ||
359 | hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); | ||
360 | unsigned int isr; | ||
361 | u_int status = 0; | ||
362 | |||
363 | |||
364 | isr = hs_in(sp, ISR); | ||
365 | |||
366 | /* Card is seated and powered when *both* CD pins are low */ | ||
367 | if ((isr & HD64465_PCCISR_PCD_MASK) == 0) | ||
368 | { | ||
369 | status |= SS_DETECT; /* card present */ | ||
370 | |||
371 | switch (isr & HD64465_PCCISR_PBVD_MASK) | ||
372 | { | ||
373 | case HD64465_PCCISR_PBVD_BATGOOD: | ||
374 | break; | ||
375 | case HD64465_PCCISR_PBVD_BATWARN: | ||
376 | status |= SS_BATWARN; | ||
377 | break; | ||
378 | default: | ||
379 | status |= SS_BATDEAD; | ||
380 | break; | ||
381 | } | ||
382 | |||
383 | if (isr & HD64465_PCCISR_PREADY) | ||
384 | status |= SS_READY; | ||
385 | |||
386 | if (isr & HD64465_PCCISR_PMWP) | ||
387 | status |= SS_WRPROT; | ||
388 | |||
389 | /* Voltage Select pins interpreted as per Table 4-5 of the std. | ||
390 | * Assuming we have the TPS2206, the socket is a "Low Voltage | ||
391 | * key, 3.3V and 5V available, no X.XV available". | ||
392 | */ | ||
393 | switch (isr & (HD64465_PCCISR_PVS2|HD64465_PCCISR_PVS1)) | ||
394 | { | ||
395 | case HD64465_PCCISR_PVS1: | ||
396 | printk(KERN_NOTICE MODNAME ": cannot handle X.XV card, ignored\n"); | ||
397 | status = 0; | ||
398 | break; | ||
399 | case 0: | ||
400 | case HD64465_PCCISR_PVS2: | ||
401 | /* 3.3V */ | ||
402 | status |= SS_3VCARD; | ||
403 | break; | ||
404 | case HD64465_PCCISR_PVS2|HD64465_PCCISR_PVS1: | ||
405 | /* 5V */ | ||
406 | break; | ||
407 | } | ||
408 | |||
409 | /* TODO: SS_POWERON */ | ||
410 | /* TODO: SS_STSCHG */ | ||
411 | } | ||
412 | |||
413 | DPRINTK("hs_get_status(%d) = %x\n", sock, status); | ||
414 | |||
415 | *value = status; | ||
416 | return 0; | ||
417 | } | ||
418 | |||
419 | /*============================================================*/ | ||
420 | |||
421 | static int hs_get_socket(struct pcmcia_socket *s, socket_state_t *state) | ||
422 | { | ||
423 | hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); | ||
424 | |||
425 | DPRINTK("hs_get_socket(%d)\n", sock); | ||
426 | |||
427 | *state = sp->state; | ||
428 | return 0; | ||
429 | } | ||
430 | |||
431 | /*============================================================*/ | ||
432 | |||
433 | static int hs_set_socket(struct pcmcia_socket *s, socket_state_t *state) | ||
434 | { | ||
435 | hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); | ||
436 | u_long flags; | ||
437 | u_int changed; | ||
438 | unsigned short cscier; | ||
439 | |||
440 | DPRINTK("hs_set_socket(sock=%d, flags=%x, csc_mask=%x, Vcc=%d, Vpp=%d, io_irq=%d)\n", | ||
441 | sock, state->flags, state->csc_mask, state->Vcc, state->Vpp, state->io_irq); | ||
442 | |||
443 | local_irq_save(flags); /* Don't want interrupts happening here */ | ||
444 | |||
445 | if (state->Vpp != sp->state.Vpp || | ||
446 | state->Vcc != sp->state.Vcc) { | ||
447 | if (!hs_set_voltages(sp, state->Vcc, state->Vpp)) { | ||
448 | local_irq_restore(flags); | ||
449 | return -EINVAL; | ||
450 | } | ||
451 | } | ||
452 | |||
453 | /* hd64465_io_debug = 1; */ | ||
454 | /* | ||
455 | * Handle changes in the Card Status Change mask, | ||
456 | * by propagating to the CSCR register | ||
457 | */ | ||
458 | changed = sp->state.csc_mask ^ state->csc_mask; | ||
459 | cscier = hs_in(sp, CSCIER); | ||
460 | |||
461 | if (changed & SS_DETECT) { | ||
462 | if (state->csc_mask & SS_DETECT) | ||
463 | cscier |= HD64465_PCCCSCIER_PCDE; | ||
464 | else | ||
465 | cscier &= ~HD64465_PCCCSCIER_PCDE; | ||
466 | } | ||
467 | |||
468 | if (changed & SS_READY) { | ||
469 | if (state->csc_mask & SS_READY) | ||
470 | cscier |= HD64465_PCCCSCIER_PRE; | ||
471 | else | ||
472 | cscier &= ~HD64465_PCCCSCIER_PRE; | ||
473 | } | ||
474 | |||
475 | if (changed & SS_BATDEAD) { | ||
476 | if (state->csc_mask & SS_BATDEAD) | ||
477 | cscier |= HD64465_PCCCSCIER_PBDE; | ||
478 | else | ||
479 | cscier &= ~HD64465_PCCCSCIER_PBDE; | ||
480 | } | ||
481 | |||
482 | if (changed & SS_BATWARN) { | ||
483 | if (state->csc_mask & SS_BATWARN) | ||
484 | cscier |= HD64465_PCCCSCIER_PBWE; | ||
485 | else | ||
486 | cscier &= ~HD64465_PCCCSCIER_PBWE; | ||
487 | } | ||
488 | |||
489 | if (changed & SS_STSCHG) { | ||
490 | if (state->csc_mask & SS_STSCHG) | ||
491 | cscier |= HD64465_PCCCSCIER_PSCE; | ||
492 | else | ||
493 | cscier &= ~HD64465_PCCCSCIER_PSCE; | ||
494 | } | ||
495 | |||
496 | hs_out(sp, cscier, CSCIER); | ||
497 | |||
498 | if (sp->state.io_irq && !state->io_irq) | ||
499 | hs_unmap_irq(sp, sp->state.io_irq); | ||
500 | else if (!sp->state.io_irq && state->io_irq) | ||
501 | hs_map_irq(sp, state->io_irq); | ||
502 | |||
503 | |||
504 | /* | ||
505 | * Handle changes in the flags field, | ||
506 | * by propagating to config registers. | ||
507 | */ | ||
508 | changed = sp->state.flags ^ state->flags; | ||
509 | |||
510 | if (changed & SS_IOCARD) { | ||
511 | DPRINTK("card type: %s\n", | ||
512 | (state->flags & SS_IOCARD ? "i/o" : "memory" )); | ||
513 | bool_to_regbit(sp, GCR, HD64465_PCCGCR_PCCT, | ||
514 | state->flags & SS_IOCARD); | ||
515 | } | ||
516 | |||
517 | if (changed & SS_RESET) { | ||
518 | DPRINTK("%s reset card\n", | ||
519 | (state->flags & SS_RESET ? "start" : "stop")); | ||
520 | bool_to_regbit(sp, GCR, HD64465_PCCGCR_PCCR, | ||
521 | state->flags & SS_RESET); | ||
522 | } | ||
523 | |||
524 | if (changed & SS_OUTPUT_ENA) { | ||
525 | DPRINTK("%sabling card output\n", | ||
526 | (state->flags & SS_OUTPUT_ENA ? "en" : "dis")); | ||
527 | bool_to_regbit(sp, GCR, HD64465_PCCGCR_PDRV, | ||
528 | state->flags & SS_OUTPUT_ENA); | ||
529 | } | ||
530 | |||
531 | /* TODO: SS_SPKR_ENA */ | ||
532 | |||
533 | /* hd64465_io_debug = 0; */ | ||
534 | sp->state = *state; | ||
535 | |||
536 | local_irq_restore(flags); | ||
537 | |||
538 | #if HD64465_DEBUG > 10 | ||
539 | if (state->flags & SS_OUTPUT_ENA) | ||
540 | cis_hex_dump((const unsigned char*)sp->mem_base, 0x100); | ||
541 | #endif | ||
542 | return 0; | ||
543 | } | ||
544 | |||
545 | /*============================================================*/ | ||
546 | |||
547 | static int hs_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) | ||
548 | { | ||
549 | hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); | ||
550 | int map = io->map; | ||
551 | int sock = sp->number; | ||
552 | struct pccard_io_map *sio; | ||
553 | pgprot_t prot; | ||
554 | |||
555 | DPRINTK("hs_set_io_map(sock=%d, map=%d, flags=0x%x, speed=%dns, start=%#lx, stop=%#lx)\n", | ||
556 | sock, map, io->flags, io->speed, io->start, io->stop); | ||
557 | if (map >= MAX_IO_WIN) | ||
558 | return -EINVAL; | ||
559 | sio = &sp->io_maps[map]; | ||
560 | |||
561 | /* check for null changes */ | ||
562 | if (io->flags == sio->flags && | ||
563 | io->start == sio->start && | ||
564 | io->stop == sio->stop) | ||
565 | return 0; | ||
566 | |||
567 | if (io->flags & MAP_AUTOSZ) | ||
568 | prot = PAGE_KERNEL_PCC(sock, _PAGE_PCC_IODYN); | ||
569 | else if (io->flags & MAP_16BIT) | ||
570 | prot = PAGE_KERNEL_PCC(sock, _PAGE_PCC_IO16); | ||
571 | else | ||
572 | prot = PAGE_KERNEL_PCC(sock, _PAGE_PCC_IO8); | ||
573 | |||
574 | /* TODO: handle MAP_USE_WAIT */ | ||
575 | if (io->flags & MAP_USE_WAIT) | ||
576 | printk(KERN_INFO MODNAME ": MAP_USE_WAIT unimplemented\n"); | ||
577 | /* TODO: handle MAP_PREFETCH */ | ||
578 | if (io->flags & MAP_PREFETCH) | ||
579 | printk(KERN_INFO MODNAME ": MAP_PREFETCH unimplemented\n"); | ||
580 | /* TODO: handle MAP_WRPROT */ | ||
581 | if (io->flags & MAP_WRPROT) | ||
582 | printk(KERN_INFO MODNAME ": MAP_WRPROT unimplemented\n"); | ||
583 | /* TODO: handle MAP_0WS */ | ||
584 | if (io->flags & MAP_0WS) | ||
585 | printk(KERN_INFO MODNAME ": MAP_0WS unimplemented\n"); | ||
586 | |||
587 | if (io->flags & MAP_ACTIVE) { | ||
588 | unsigned long pstart, psize, paddrbase; | ||
589 | |||
590 | paddrbase = virt_to_phys((void*)(sp->mem_base + 2 * HD64465_PCC_WINDOW)); | ||
591 | pstart = io->start & PAGE_MASK; | ||
592 | psize = ((io->stop + PAGE_SIZE) & PAGE_MASK) - pstart; | ||
593 | |||
594 | /* | ||
595 | * Change PTEs in only that portion of the mapping requested | ||
596 | * by the caller. This means that most of the time, most of | ||
597 | * the PTEs in the io_vma will be unmapped and only the bottom | ||
598 | * page will be mapped. But the code allows for weird cards | ||
599 | * that might want IO ports > 4K. | ||
600 | */ | ||
601 | sp->io_base = p3_ioremap(paddrbase + pstart, psize, pgprot_val(prot)); | ||
602 | |||
603 | /* | ||
604 | * Change the mapping used by inb() outb() etc | ||
605 | */ | ||
606 | hd64465_port_map(io->start, | ||
607 | io->stop - io->start + 1, | ||
608 | (unsigned long)sp->io_base + io->start, 0); | ||
609 | } else { | ||
610 | hd64465_port_unmap(sio->start, sio->stop - sio->start + 1); | ||
611 | p3_iounmap(sp->io_base); | ||
612 | } | ||
613 | |||
614 | *sio = *io; | ||
615 | return 0; | ||
616 | } | ||
617 | |||
618 | /*============================================================*/ | ||
619 | |||
620 | static int hs_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *mem) | ||
621 | { | ||
622 | hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); | ||
623 | struct pccard_mem_map *smem; | ||
624 | int map = mem->map; | ||
625 | unsigned long paddr; | ||
626 | |||
627 | #if 0 | ||
628 | DPRINTK("hs_set_mem_map(sock=%d, map=%d, flags=0x%x, card_start=0x%08x)\n", | ||
629 | sock, map, mem->flags, mem->card_start); | ||
630 | #endif | ||
631 | |||
632 | if (map >= MAX_WIN) | ||
633 | return -EINVAL; | ||
634 | smem = &sp->mem_maps[map]; | ||
635 | |||
636 | paddr = sp->mem_base; /* base of Attribute mapping */ | ||
637 | if (!(mem->flags & MAP_ATTRIB)) | ||
638 | paddr += HD64465_PCC_WINDOW; /* base of Common mapping */ | ||
639 | paddr += mem->card_start; | ||
640 | |||
641 | /* Because we specified SS_CAP_STATIC_MAP, we are obliged | ||
642 | * at this time to report the system address corresponding | ||
643 | * to the card address requested. This is how Socket Services | ||
644 | * queries our fixed mapping. I wish this fact had been | ||
645 | * documented - Greg Banks. | ||
646 | */ | ||
647 | mem->static_start = paddr; | ||
648 | |||
649 | *smem = *mem; | ||
650 | |||
651 | return 0; | ||
652 | } | ||
653 | |||
654 | /* TODO: do we need to use the MMU to access Common memory ??? */ | ||
655 | |||
656 | /*============================================================*/ | ||
657 | |||
658 | /* | ||
659 | * This function is registered with the HD64465 glue code to do a | ||
660 | * secondary demux step on the PCMCIA interrupts. It handles | ||
661 | * mapping the IREQ request from the card to a standard Linux | ||
662 | * IRQ, as requested by SocketServices. | ||
663 | */ | ||
664 | static int hs_irq_demux(int irq, void *dev) | ||
665 | { | ||
666 | hs_socket_t *sp = (hs_socket_t *)dev; | ||
667 | u_int cscr; | ||
668 | |||
669 | DPRINTK("hs_irq_demux(irq=%d)\n", irq); | ||
670 | |||
671 | if (sp->state.io_irq && | ||
672 | (cscr = hs_in(sp, CSCR)) & HD64465_PCCCSCR_PIREQ) { | ||
673 | cscr &= ~HD64465_PCCCSCR_PIREQ; | ||
674 | hs_out(sp, cscr, CSCR); | ||
675 | return sp->state.io_irq; | ||
676 | } | ||
677 | |||
678 | return irq; | ||
679 | } | ||
680 | |||
681 | /*============================================================*/ | ||
682 | |||
683 | /* | ||
684 | * Interrupt handling routine. | ||
685 | */ | ||
686 | |||
687 | static irqreturn_t hs_interrupt(int irq, void *dev, struct pt_regs *regs) | ||
688 | { | ||
689 | hs_socket_t *sp = (hs_socket_t *)dev; | ||
690 | u_int events = 0; | ||
691 | u_int cscr; | ||
692 | |||
693 | |||
694 | cscr = hs_in(sp, CSCR); | ||
695 | |||
696 | DPRINTK("hs_interrupt, cscr=%04x\n", cscr); | ||
697 | |||
698 | /* check for bus-related changes to be reported to Socket Services */ | ||
699 | if (cscr & HD64465_PCCCSCR_PCDC) { | ||
700 | /* double-check for a 16-bit card, as we don't support CardBus */ | ||
701 | if ((hs_in(sp, ISR) & HD64465_PCCISR_PCD_MASK) != 0) { | ||
702 | printk(KERN_NOTICE MODNAME | ||
703 | ": socket %d, card not a supported card type or not inserted correctly\n", | ||
704 | sp->number); | ||
705 | /* Don't do the rest unless a card is present */ | ||
706 | cscr &= ~(HD64465_PCCCSCR_PCDC| | ||
707 | HD64465_PCCCSCR_PRC| | ||
708 | HD64465_PCCCSCR_PBW| | ||
709 | HD64465_PCCCSCR_PBD| | ||
710 | HD64465_PCCCSCR_PSC); | ||
711 | } else { | ||
712 | cscr &= ~HD64465_PCCCSCR_PCDC; | ||
713 | events |= SS_DETECT; /* card insertion or removal */ | ||
714 | } | ||
715 | } | ||
716 | if (cscr & HD64465_PCCCSCR_PRC) { | ||
717 | cscr &= ~HD64465_PCCCSCR_PRC; | ||
718 | events |= SS_READY; /* ready signal changed */ | ||
719 | } | ||
720 | if (cscr & HD64465_PCCCSCR_PBW) { | ||
721 | cscr &= ~HD64465_PCCCSCR_PSC; | ||
722 | events |= SS_BATWARN; /* battery warning */ | ||
723 | } | ||
724 | if (cscr & HD64465_PCCCSCR_PBD) { | ||
725 | cscr &= ~HD64465_PCCCSCR_PSC; | ||
726 | events |= SS_BATDEAD; /* battery dead */ | ||
727 | } | ||
728 | if (cscr & HD64465_PCCCSCR_PSC) { | ||
729 | cscr &= ~HD64465_PCCCSCR_PSC; | ||
730 | events |= SS_STSCHG; /* STSCHG (status changed) signal */ | ||
731 | } | ||
732 | |||
733 | if (cscr & HD64465_PCCCSCR_PIREQ) { | ||
734 | cscr &= ~HD64465_PCCCSCR_PIREQ; | ||
735 | |||
736 | /* This should have been dealt with during irq demux */ | ||
737 | printk(KERN_NOTICE MODNAME ": unexpected IREQ from card\n"); | ||
738 | } | ||
739 | |||
740 | hs_out(sp, cscr, CSCR); | ||
741 | |||
742 | if (events) | ||
743 | pcmcia_parse_events(&sp->socket, events); | ||
744 | |||
745 | return IRQ_HANDLED; | ||
746 | } | ||
747 | |||
748 | /*============================================================*/ | ||
749 | |||
750 | static struct pccard_operations hs_operations = { | ||
751 | .init = hs_init, | ||
752 | .get_status = hs_get_status, | ||
753 | .get_socket = hs_get_socket, | ||
754 | .set_socket = hs_set_socket, | ||
755 | .set_io_map = hs_set_io_map, | ||
756 | .set_mem_map = hs_set_mem_map, | ||
757 | }; | ||
758 | |||
759 | static int hs_init_socket(hs_socket_t *sp, int irq, unsigned long mem_base, | ||
760 | unsigned int ctrl_base) | ||
761 | { | ||
762 | unsigned short v; | ||
763 | int i, err; | ||
764 | |||
765 | memset(sp, 0, sizeof(*sp)); | ||
766 | sp->irq = irq; | ||
767 | sp->mem_base = mem_base; | ||
768 | sp->mem_length = 4*HD64465_PCC_WINDOW; /* 16MB */ | ||
769 | sp->ctrl_base = ctrl_base; | ||
770 | |||
771 | for (i=0 ; i<MAX_IO_WIN ; i++) | ||
772 | sp->io_maps[i].map = i; | ||
773 | for (i=0 ; i<MAX_WIN ; i++) | ||
774 | sp->mem_maps[i].map = i; | ||
775 | |||
776 | hd64465_register_irq_demux(sp->irq, hs_irq_demux, sp); | ||
777 | |||
778 | if ((err = request_irq(sp->irq, hs_interrupt, SA_INTERRUPT, MODNAME, sp)) < 0) | ||
779 | return err; | ||
780 | if (request_mem_region(sp->mem_base, sp->mem_length, MODNAME) == 0) { | ||
781 | sp->mem_base = 0; | ||
782 | return -ENOMEM; | ||
783 | } | ||
784 | |||
785 | |||
786 | /* According to section 3.2 of the PCMCIA standard, low-voltage | ||
787 | * capable cards must implement cold insertion, i.e. Vpp and | ||
788 | * Vcc set to 0 before card is inserted. | ||
789 | */ | ||
790 | /*hs_set_voltages(sp, 0, 0);*/ | ||
791 | |||
792 | /* hi-Z the outputs to the card and set 16MB map mode */ | ||
793 | v = hs_in(sp, GCR); | ||
794 | v &= ~HD64465_PCCGCR_PCCT; /* memory-only card */ | ||
795 | hs_out(sp, v, GCR); | ||
796 | |||
797 | v = hs_in(sp, GCR); | ||
798 | v |= HD64465_PCCGCR_PDRV; /* enable outputs to card */ | ||
799 | hs_out(sp, v, GCR); | ||
800 | |||
801 | v = hs_in(sp, GCR); | ||
802 | v |= HD64465_PCCGCR_PMMOD; /* 16MB mapping mode */ | ||
803 | hs_out(sp, v, GCR); | ||
804 | |||
805 | v = hs_in(sp, GCR); | ||
806 | /* lowest 16MB of Common */ | ||
807 | v &= ~(HD64465_PCCGCR_PPA25|HD64465_PCCGCR_PPA24); | ||
808 | hs_out(sp, v, GCR); | ||
809 | |||
810 | hs_reset_socket(sp, 1); | ||
811 | |||
812 | printk(KERN_INFO "HD64465 PCMCIA bridge socket %d at 0x%08lx irq %d\n", | ||
813 | i, sp->mem_base, sp->irq); | ||
814 | |||
815 | return 0; | ||
816 | } | ||
817 | |||
818 | static void hs_exit_socket(hs_socket_t *sp) | ||
819 | { | ||
820 | unsigned short cscier, gcr; | ||
821 | unsigned long flags; | ||
822 | |||
823 | local_irq_save(flags); | ||
824 | |||
825 | /* turn off interrupts in hardware */ | ||
826 | cscier = hs_in(sp, CSCIER); | ||
827 | cscier = (cscier & IER_MASK) | IER_OFF; | ||
828 | hs_out(sp, cscier, CSCIER); | ||
829 | |||
830 | /* hi-Z the outputs to the card */ | ||
831 | gcr = hs_in(sp, GCR); | ||
832 | gcr &= HD64465_PCCGCR_PDRV; | ||
833 | hs_out(sp, gcr, GCR); | ||
834 | |||
835 | /* power the card down */ | ||
836 | hs_set_voltages(sp, 0, 0); | ||
837 | |||
838 | if (sp->mem_base != 0) | ||
839 | release_mem_region(sp->mem_base, sp->mem_length); | ||
840 | if (sp->irq != 0) { | ||
841 | free_irq(sp->irq, hs_interrupt); | ||
842 | hd64465_unregister_irq_demux(sp->irq); | ||
843 | } | ||
844 | |||
845 | local_irq_restore(flags); | ||
846 | } | ||
847 | |||
848 | static int hd64465_suspend(struct device *dev, u32 state, u32 level) | ||
849 | { | ||
850 | int ret = 0; | ||
851 | if (level == SUSPEND_SAVE_STATE) | ||
852 | ret = pcmcia_socket_dev_suspend(dev, state); | ||
853 | return ret; | ||
854 | } | ||
855 | |||
856 | static int hd64465_resume(struct device *dev, u32 level) | ||
857 | { | ||
858 | int ret = 0; | ||
859 | if (level == RESUME_RESTORE_STATE) | ||
860 | ret = pcmcia_socket_dev_resume(dev); | ||
861 | return ret; | ||
862 | } | ||
863 | |||
864 | static struct device_driver hd64465_driver = { | ||
865 | .name = "hd64465-pcmcia", | ||
866 | .bus = &platform_bus_type, | ||
867 | .suspend = hd64465_suspend, | ||
868 | .resume = hd64465_resume, | ||
869 | }; | ||
870 | |||
871 | static struct platform_device hd64465_device = { | ||
872 | .name = "hd64465-pcmcia", | ||
873 | .id = 0, | ||
874 | }; | ||
875 | |||
876 | static int __init init_hs(void) | ||
877 | { | ||
878 | int i; | ||
879 | unsigned short v; | ||
880 | |||
881 | /* hd64465_io_debug = 1; */ | ||
882 | if (driver_register(&hd64465_driver)) | ||
883 | return -EINVAL; | ||
884 | |||
885 | /* Wake both sockets out of STANDBY mode */ | ||
886 | /* TODO: wait 15ms */ | ||
887 | v = inw(HD64465_REG_SMSCR); | ||
888 | v &= ~(HD64465_SMSCR_PC0ST|HD64465_SMSCR_PC1ST); | ||
889 | outw(v, HD64465_REG_SMSCR); | ||
890 | |||
891 | /* keep power controller out of shutdown mode */ | ||
892 | v = inb(HD64465_REG_PCC0SCR); | ||
893 | v |= HD64465_PCCSCR_SHDN; | ||
894 | outb(v, HD64465_REG_PCC0SCR); | ||
895 | |||
896 | /* use serial (TPS2206) power controller */ | ||
897 | v = inb(HD64465_REG_PCC0CSCR); | ||
898 | v |= HD64465_PCCCSCR_PSWSEL; | ||
899 | outb(v, HD64465_REG_PCC0CSCR); | ||
900 | |||
901 | /* | ||
902 | * Setup hs_sockets[] structures and request system resources. | ||
903 | * TODO: on memory allocation failure, power down the socket | ||
904 | * before quitting. | ||
905 | */ | ||
906 | for (i=0; i<HS_MAX_SOCKETS; i++) { | ||
907 | hs_set_voltages(&hs_sockets[i], 0, 0); | ||
908 | |||
909 | hs_sockets[i].socket.features |= SS_CAP_PCCARD | SS_CAP_STATIC_MAP; /* mappings are fixed in host memory */ | ||
910 | hs_sockets[i].socket.resource_ops = &pccard_static_ops; | ||
911 | hs_sockets[i].socket.irq_mask = 0xffde;/*0xffff*/ /* IRQs mapped in s/w so can do any, really */ | ||
912 | hs_sockets[i].socket.map_size = HD64465_PCC_WINDOW; /* 16MB fixed window size */ | ||
913 | |||
914 | hs_sockets[i].socket.owner = THIS_MODULE; | ||
915 | hs_sockets[i].socket.ss_entry = &hs_operations; | ||
916 | } | ||
917 | |||
918 | i = hs_init_socket(&hs_sockets[0], | ||
919 | HD64465_IRQ_PCMCIA0, | ||
920 | HD64465_PCC0_BASE, | ||
921 | HD64465_REG_PCC0ISR); | ||
922 | if (i < 0) { | ||
923 | unregister_driver(&hd64465_driver); | ||
924 | return i; | ||
925 | } | ||
926 | i = hs_init_socket(&hs_sockets[1], | ||
927 | HD64465_IRQ_PCMCIA1, | ||
928 | HD64465_PCC1_BASE, | ||
929 | HD64465_REG_PCC1ISR); | ||
930 | if (i < 0) { | ||
931 | unregister_driver(&hd64465_driver); | ||
932 | return i; | ||
933 | } | ||
934 | |||
935 | /* hd64465_io_debug = 0; */ | ||
936 | |||
937 | platform_device_register(&hd64465_device); | ||
938 | |||
939 | for (i=0; i<HS_MAX_SOCKETS; i++) { | ||
940 | unsigned int ret; | ||
941 | hs_sockets[i].socket.dev.dev = &hd64465_device.dev; | ||
942 | hs_sockets[i].number = i; | ||
943 | ret = pcmcia_register_socket(&hs_sockets[i].socket); | ||
944 | if (ret && i) | ||
945 | pcmcia_unregister_socket(&hs_sockets[0].socket); | ||
946 | } | ||
947 | |||
948 | return 0; | ||
949 | } | ||
950 | |||
951 | static void __exit exit_hs(void) | ||
952 | { | ||
953 | int i; | ||
954 | |||
955 | for (i=0 ; i<HS_MAX_SOCKETS ; i++) { | ||
956 | pcmcia_unregister_socket(&hs_sockets[i].socket); | ||
957 | hs_exit_socket(&hs_sockets[i]); | ||
958 | } | ||
959 | |||
960 | platform_device_unregister(&hd64465_device); | ||
961 | unregister_driver(&hd64465_driver); | ||
962 | } | ||
963 | |||
964 | module_init(init_hs); | ||
965 | module_exit(exit_hs); | ||
966 | |||
967 | /*============================================================*/ | ||
968 | /*END*/ | ||