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/bluetooth/dtl1_cs.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/bluetooth/dtl1_cs.c')
-rw-r--r-- | drivers/bluetooth/dtl1_cs.c | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c new file mode 100644 index 000000000000..fe954e5d9a1d --- /dev/null +++ b/drivers/bluetooth/dtl1_cs.c | |||
@@ -0,0 +1,832 @@ | |||
1 | /* | ||
2 | * | ||
3 | * A driver for Nokia Connectivity Card DTL-1 devices | ||
4 | * | ||
5 | * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> | ||
6 | * | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation; | ||
11 | * | ||
12 | * Software distributed under the License is distributed on an "AS | ||
13 | * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or | ||
14 | * implied. See the License for the specific language governing | ||
15 | * rights and limitations under the License. | ||
16 | * | ||
17 | * The initial developer of the original code is David A. Hinds | ||
18 | * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds | ||
19 | * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #include <linux/config.h> | ||
24 | #include <linux/module.h> | ||
25 | |||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/init.h> | ||
28 | #include <linux/slab.h> | ||
29 | #include <linux/types.h> | ||
30 | #include <linux/sched.h> | ||
31 | #include <linux/delay.h> | ||
32 | #include <linux/errno.h> | ||
33 | #include <linux/ptrace.h> | ||
34 | #include <linux/ioport.h> | ||
35 | #include <linux/spinlock.h> | ||
36 | #include <linux/moduleparam.h> | ||
37 | |||
38 | #include <linux/skbuff.h> | ||
39 | #include <linux/string.h> | ||
40 | #include <linux/serial.h> | ||
41 | #include <linux/serial_reg.h> | ||
42 | #include <linux/bitops.h> | ||
43 | #include <asm/system.h> | ||
44 | #include <asm/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/ciscode.h> | ||
51 | #include <pcmcia/ds.h> | ||
52 | #include <pcmcia/cisreg.h> | ||
53 | |||
54 | #include <net/bluetooth/bluetooth.h> | ||
55 | #include <net/bluetooth/hci_core.h> | ||
56 | |||
57 | |||
58 | |||
59 | /* ======================== Module parameters ======================== */ | ||
60 | |||
61 | |||
62 | MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); | ||
63 | MODULE_DESCRIPTION("Bluetooth driver for Nokia Connectivity Card DTL-1"); | ||
64 | MODULE_LICENSE("GPL"); | ||
65 | |||
66 | |||
67 | |||
68 | /* ======================== Local structures ======================== */ | ||
69 | |||
70 | |||
71 | typedef struct dtl1_info_t { | ||
72 | dev_link_t link; | ||
73 | dev_node_t node; | ||
74 | |||
75 | struct hci_dev *hdev; | ||
76 | |||
77 | spinlock_t lock; /* For serializing operations */ | ||
78 | |||
79 | unsigned long flowmask; /* HCI flow mask */ | ||
80 | int ri_latch; | ||
81 | |||
82 | struct sk_buff_head txq; | ||
83 | unsigned long tx_state; | ||
84 | |||
85 | unsigned long rx_state; | ||
86 | unsigned long rx_count; | ||
87 | struct sk_buff *rx_skb; | ||
88 | } dtl1_info_t; | ||
89 | |||
90 | |||
91 | static void dtl1_config(dev_link_t *link); | ||
92 | static void dtl1_release(dev_link_t *link); | ||
93 | static int dtl1_event(event_t event, int priority, event_callback_args_t *args); | ||
94 | |||
95 | static dev_info_t dev_info = "dtl1_cs"; | ||
96 | |||
97 | static dev_link_t *dtl1_attach(void); | ||
98 | static void dtl1_detach(dev_link_t *); | ||
99 | |||
100 | static dev_link_t *dev_list = NULL; | ||
101 | |||
102 | |||
103 | /* Transmit states */ | ||
104 | #define XMIT_SENDING 1 | ||
105 | #define XMIT_WAKEUP 2 | ||
106 | #define XMIT_WAITING 8 | ||
107 | |||
108 | /* Receiver States */ | ||
109 | #define RECV_WAIT_NSH 0 | ||
110 | #define RECV_WAIT_DATA 1 | ||
111 | |||
112 | |||
113 | typedef struct { | ||
114 | u8 type; | ||
115 | u8 zero; | ||
116 | u16 len; | ||
117 | } __attribute__ ((packed)) nsh_t; /* Nokia Specific Header */ | ||
118 | |||
119 | #define NSHL 4 /* Nokia Specific Header Length */ | ||
120 | |||
121 | |||
122 | |||
123 | /* ======================== Interrupt handling ======================== */ | ||
124 | |||
125 | |||
126 | static int dtl1_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) | ||
127 | { | ||
128 | int actual = 0; | ||
129 | |||
130 | /* Tx FIFO should be empty */ | ||
131 | if (!(inb(iobase + UART_LSR) & UART_LSR_THRE)) | ||
132 | return 0; | ||
133 | |||
134 | /* Fill FIFO with current frame */ | ||
135 | while ((fifo_size-- > 0) && (actual < len)) { | ||
136 | /* Transmit next byte */ | ||
137 | outb(buf[actual], iobase + UART_TX); | ||
138 | actual++; | ||
139 | } | ||
140 | |||
141 | return actual; | ||
142 | } | ||
143 | |||
144 | |||
145 | static void dtl1_write_wakeup(dtl1_info_t *info) | ||
146 | { | ||
147 | if (!info) { | ||
148 | BT_ERR("Unknown device"); | ||
149 | return; | ||
150 | } | ||
151 | |||
152 | if (test_bit(XMIT_WAITING, &(info->tx_state))) { | ||
153 | set_bit(XMIT_WAKEUP, &(info->tx_state)); | ||
154 | return; | ||
155 | } | ||
156 | |||
157 | if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { | ||
158 | set_bit(XMIT_WAKEUP, &(info->tx_state)); | ||
159 | return; | ||
160 | } | ||
161 | |||
162 | do { | ||
163 | register unsigned int iobase = info->link.io.BasePort1; | ||
164 | register struct sk_buff *skb; | ||
165 | register int len; | ||
166 | |||
167 | clear_bit(XMIT_WAKEUP, &(info->tx_state)); | ||
168 | |||
169 | if (!(info->link.state & DEV_PRESENT)) | ||
170 | return; | ||
171 | |||
172 | if (!(skb = skb_dequeue(&(info->txq)))) | ||
173 | break; | ||
174 | |||
175 | /* Send frame */ | ||
176 | len = dtl1_write(iobase, 32, skb->data, skb->len); | ||
177 | |||
178 | if (len == skb->len) { | ||
179 | set_bit(XMIT_WAITING, &(info->tx_state)); | ||
180 | kfree_skb(skb); | ||
181 | } else { | ||
182 | skb_pull(skb, len); | ||
183 | skb_queue_head(&(info->txq), skb); | ||
184 | } | ||
185 | |||
186 | info->hdev->stat.byte_tx += len; | ||
187 | |||
188 | } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); | ||
189 | |||
190 | clear_bit(XMIT_SENDING, &(info->tx_state)); | ||
191 | } | ||
192 | |||
193 | |||
194 | static void dtl1_control(dtl1_info_t *info, struct sk_buff *skb) | ||
195 | { | ||
196 | u8 flowmask = *(u8 *)skb->data; | ||
197 | int i; | ||
198 | |||
199 | printk(KERN_INFO "Bluetooth: Nokia control data ="); | ||
200 | for (i = 0; i < skb->len; i++) { | ||
201 | printk(" %02x", skb->data[i]); | ||
202 | } | ||
203 | printk("\n"); | ||
204 | |||
205 | /* transition to active state */ | ||
206 | if (((info->flowmask & 0x07) == 0) && ((flowmask & 0x07) != 0)) { | ||
207 | clear_bit(XMIT_WAITING, &(info->tx_state)); | ||
208 | dtl1_write_wakeup(info); | ||
209 | } | ||
210 | |||
211 | info->flowmask = flowmask; | ||
212 | |||
213 | kfree_skb(skb); | ||
214 | } | ||
215 | |||
216 | |||
217 | static void dtl1_receive(dtl1_info_t *info) | ||
218 | { | ||
219 | unsigned int iobase; | ||
220 | nsh_t *nsh; | ||
221 | int boguscount = 0; | ||
222 | |||
223 | if (!info) { | ||
224 | BT_ERR("Unknown device"); | ||
225 | return; | ||
226 | } | ||
227 | |||
228 | iobase = info->link.io.BasePort1; | ||
229 | |||
230 | do { | ||
231 | info->hdev->stat.byte_rx++; | ||
232 | |||
233 | /* Allocate packet */ | ||
234 | if (info->rx_skb == NULL) | ||
235 | if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { | ||
236 | BT_ERR("Can't allocate mem for new packet"); | ||
237 | info->rx_state = RECV_WAIT_NSH; | ||
238 | info->rx_count = NSHL; | ||
239 | return; | ||
240 | } | ||
241 | |||
242 | *skb_put(info->rx_skb, 1) = inb(iobase + UART_RX); | ||
243 | nsh = (nsh_t *)info->rx_skb->data; | ||
244 | |||
245 | info->rx_count--; | ||
246 | |||
247 | if (info->rx_count == 0) { | ||
248 | |||
249 | switch (info->rx_state) { | ||
250 | case RECV_WAIT_NSH: | ||
251 | info->rx_state = RECV_WAIT_DATA; | ||
252 | info->rx_count = nsh->len + (nsh->len & 0x0001); | ||
253 | break; | ||
254 | case RECV_WAIT_DATA: | ||
255 | info->rx_skb->pkt_type = nsh->type; | ||
256 | |||
257 | /* remove PAD byte if it exists */ | ||
258 | if (nsh->len & 0x0001) { | ||
259 | info->rx_skb->tail--; | ||
260 | info->rx_skb->len--; | ||
261 | } | ||
262 | |||
263 | /* remove NSH */ | ||
264 | skb_pull(info->rx_skb, NSHL); | ||
265 | |||
266 | switch (info->rx_skb->pkt_type) { | ||
267 | case 0x80: | ||
268 | /* control data for the Nokia Card */ | ||
269 | dtl1_control(info, info->rx_skb); | ||
270 | break; | ||
271 | case 0x82: | ||
272 | case 0x83: | ||
273 | case 0x84: | ||
274 | /* send frame to the HCI layer */ | ||
275 | info->rx_skb->dev = (void *) info->hdev; | ||
276 | info->rx_skb->pkt_type &= 0x0f; | ||
277 | hci_recv_frame(info->rx_skb); | ||
278 | break; | ||
279 | default: | ||
280 | /* unknown packet */ | ||
281 | BT_ERR("Unknown HCI packet with type 0x%02x received", info->rx_skb->pkt_type); | ||
282 | kfree_skb(info->rx_skb); | ||
283 | break; | ||
284 | } | ||
285 | |||
286 | info->rx_state = RECV_WAIT_NSH; | ||
287 | info->rx_count = NSHL; | ||
288 | info->rx_skb = NULL; | ||
289 | break; | ||
290 | } | ||
291 | |||
292 | } | ||
293 | |||
294 | /* Make sure we don't stay here too long */ | ||
295 | if (boguscount++ > 32) | ||
296 | break; | ||
297 | |||
298 | } while (inb(iobase + UART_LSR) & UART_LSR_DR); | ||
299 | } | ||
300 | |||
301 | |||
302 | static irqreturn_t dtl1_interrupt(int irq, void *dev_inst, struct pt_regs *regs) | ||
303 | { | ||
304 | dtl1_info_t *info = dev_inst; | ||
305 | unsigned int iobase; | ||
306 | unsigned char msr; | ||
307 | int boguscount = 0; | ||
308 | int iir, lsr; | ||
309 | |||
310 | if (!info || !info->hdev) { | ||
311 | BT_ERR("Call of irq %d for unknown device", irq); | ||
312 | return IRQ_NONE; | ||
313 | } | ||
314 | |||
315 | iobase = info->link.io.BasePort1; | ||
316 | |||
317 | spin_lock(&(info->lock)); | ||
318 | |||
319 | iir = inb(iobase + UART_IIR) & UART_IIR_ID; | ||
320 | while (iir) { | ||
321 | |||
322 | /* Clear interrupt */ | ||
323 | lsr = inb(iobase + UART_LSR); | ||
324 | |||
325 | switch (iir) { | ||
326 | case UART_IIR_RLSI: | ||
327 | BT_ERR("RLSI"); | ||
328 | break; | ||
329 | case UART_IIR_RDI: | ||
330 | /* Receive interrupt */ | ||
331 | dtl1_receive(info); | ||
332 | break; | ||
333 | case UART_IIR_THRI: | ||
334 | if (lsr & UART_LSR_THRE) { | ||
335 | /* Transmitter ready for data */ | ||
336 | dtl1_write_wakeup(info); | ||
337 | } | ||
338 | break; | ||
339 | default: | ||
340 | BT_ERR("Unhandled IIR=%#x", iir); | ||
341 | break; | ||
342 | } | ||
343 | |||
344 | /* Make sure we don't stay here too long */ | ||
345 | if (boguscount++ > 100) | ||
346 | break; | ||
347 | |||
348 | iir = inb(iobase + UART_IIR) & UART_IIR_ID; | ||
349 | |||
350 | } | ||
351 | |||
352 | msr = inb(iobase + UART_MSR); | ||
353 | |||
354 | if (info->ri_latch ^ (msr & UART_MSR_RI)) { | ||
355 | info->ri_latch = msr & UART_MSR_RI; | ||
356 | clear_bit(XMIT_WAITING, &(info->tx_state)); | ||
357 | dtl1_write_wakeup(info); | ||
358 | } | ||
359 | |||
360 | spin_unlock(&(info->lock)); | ||
361 | |||
362 | return IRQ_HANDLED; | ||
363 | } | ||
364 | |||
365 | |||
366 | |||
367 | /* ======================== HCI interface ======================== */ | ||
368 | |||
369 | |||
370 | static int dtl1_hci_open(struct hci_dev *hdev) | ||
371 | { | ||
372 | set_bit(HCI_RUNNING, &(hdev->flags)); | ||
373 | |||
374 | return 0; | ||
375 | } | ||
376 | |||
377 | |||
378 | static int dtl1_hci_flush(struct hci_dev *hdev) | ||
379 | { | ||
380 | dtl1_info_t *info = (dtl1_info_t *)(hdev->driver_data); | ||
381 | |||
382 | /* Drop TX queue */ | ||
383 | skb_queue_purge(&(info->txq)); | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | |||
389 | static int dtl1_hci_close(struct hci_dev *hdev) | ||
390 | { | ||
391 | if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) | ||
392 | return 0; | ||
393 | |||
394 | dtl1_hci_flush(hdev); | ||
395 | |||
396 | return 0; | ||
397 | } | ||
398 | |||
399 | |||
400 | static int dtl1_hci_send_frame(struct sk_buff *skb) | ||
401 | { | ||
402 | dtl1_info_t *info; | ||
403 | struct hci_dev *hdev = (struct hci_dev *)(skb->dev); | ||
404 | struct sk_buff *s; | ||
405 | nsh_t nsh; | ||
406 | |||
407 | if (!hdev) { | ||
408 | BT_ERR("Frame for unknown HCI device (hdev=NULL)"); | ||
409 | return -ENODEV; | ||
410 | } | ||
411 | |||
412 | info = (dtl1_info_t *)(hdev->driver_data); | ||
413 | |||
414 | switch (skb->pkt_type) { | ||
415 | case HCI_COMMAND_PKT: | ||
416 | hdev->stat.cmd_tx++; | ||
417 | nsh.type = 0x81; | ||
418 | break; | ||
419 | case HCI_ACLDATA_PKT: | ||
420 | hdev->stat.acl_tx++; | ||
421 | nsh.type = 0x82; | ||
422 | break; | ||
423 | case HCI_SCODATA_PKT: | ||
424 | hdev->stat.sco_tx++; | ||
425 | nsh.type = 0x83; | ||
426 | break; | ||
427 | }; | ||
428 | |||
429 | nsh.zero = 0; | ||
430 | nsh.len = skb->len; | ||
431 | |||
432 | s = bt_skb_alloc(NSHL + skb->len + 1, GFP_ATOMIC); | ||
433 | skb_reserve(s, NSHL); | ||
434 | memcpy(skb_put(s, skb->len), skb->data, skb->len); | ||
435 | if (skb->len & 0x0001) | ||
436 | *skb_put(s, 1) = 0; /* PAD */ | ||
437 | |||
438 | /* Prepend skb with Nokia frame header and queue */ | ||
439 | memcpy(skb_push(s, NSHL), &nsh, NSHL); | ||
440 | skb_queue_tail(&(info->txq), s); | ||
441 | |||
442 | dtl1_write_wakeup(info); | ||
443 | |||
444 | kfree_skb(skb); | ||
445 | |||
446 | return 0; | ||
447 | } | ||
448 | |||
449 | |||
450 | static void dtl1_hci_destruct(struct hci_dev *hdev) | ||
451 | { | ||
452 | } | ||
453 | |||
454 | |||
455 | static int dtl1_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) | ||
456 | { | ||
457 | return -ENOIOCTLCMD; | ||
458 | } | ||
459 | |||
460 | |||
461 | |||
462 | /* ======================== Card services HCI interaction ======================== */ | ||
463 | |||
464 | |||
465 | static int dtl1_open(dtl1_info_t *info) | ||
466 | { | ||
467 | unsigned long flags; | ||
468 | unsigned int iobase = info->link.io.BasePort1; | ||
469 | struct hci_dev *hdev; | ||
470 | |||
471 | spin_lock_init(&(info->lock)); | ||
472 | |||
473 | skb_queue_head_init(&(info->txq)); | ||
474 | |||
475 | info->rx_state = RECV_WAIT_NSH; | ||
476 | info->rx_count = NSHL; | ||
477 | info->rx_skb = NULL; | ||
478 | |||
479 | set_bit(XMIT_WAITING, &(info->tx_state)); | ||
480 | |||
481 | /* Initialize HCI device */ | ||
482 | hdev = hci_alloc_dev(); | ||
483 | if (!hdev) { | ||
484 | BT_ERR("Can't allocate HCI device"); | ||
485 | return -ENOMEM; | ||
486 | } | ||
487 | |||
488 | info->hdev = hdev; | ||
489 | |||
490 | hdev->type = HCI_PCCARD; | ||
491 | hdev->driver_data = info; | ||
492 | |||
493 | hdev->open = dtl1_hci_open; | ||
494 | hdev->close = dtl1_hci_close; | ||
495 | hdev->flush = dtl1_hci_flush; | ||
496 | hdev->send = dtl1_hci_send_frame; | ||
497 | hdev->destruct = dtl1_hci_destruct; | ||
498 | hdev->ioctl = dtl1_hci_ioctl; | ||
499 | |||
500 | hdev->owner = THIS_MODULE; | ||
501 | |||
502 | spin_lock_irqsave(&(info->lock), flags); | ||
503 | |||
504 | /* Reset UART */ | ||
505 | outb(0, iobase + UART_MCR); | ||
506 | |||
507 | /* Turn off interrupts */ | ||
508 | outb(0, iobase + UART_IER); | ||
509 | |||
510 | /* Initialize UART */ | ||
511 | outb(UART_LCR_WLEN8, iobase + UART_LCR); /* Reset DLAB */ | ||
512 | outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase + UART_MCR); | ||
513 | |||
514 | info->ri_latch = inb(info->link.io.BasePort1 + UART_MSR) & UART_MSR_RI; | ||
515 | |||
516 | /* Turn on interrupts */ | ||
517 | outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); | ||
518 | |||
519 | spin_unlock_irqrestore(&(info->lock), flags); | ||
520 | |||
521 | /* Timeout before it is safe to send the first HCI packet */ | ||
522 | msleep(2000); | ||
523 | |||
524 | /* Register HCI device */ | ||
525 | if (hci_register_dev(hdev) < 0) { | ||
526 | BT_ERR("Can't register HCI device"); | ||
527 | info->hdev = NULL; | ||
528 | hci_free_dev(hdev); | ||
529 | return -ENODEV; | ||
530 | } | ||
531 | |||
532 | return 0; | ||
533 | } | ||
534 | |||
535 | |||
536 | static int dtl1_close(dtl1_info_t *info) | ||
537 | { | ||
538 | unsigned long flags; | ||
539 | unsigned int iobase = info->link.io.BasePort1; | ||
540 | struct hci_dev *hdev = info->hdev; | ||
541 | |||
542 | if (!hdev) | ||
543 | return -ENODEV; | ||
544 | |||
545 | dtl1_hci_close(hdev); | ||
546 | |||
547 | spin_lock_irqsave(&(info->lock), flags); | ||
548 | |||
549 | /* Reset UART */ | ||
550 | outb(0, iobase + UART_MCR); | ||
551 | |||
552 | /* Turn off interrupts */ | ||
553 | outb(0, iobase + UART_IER); | ||
554 | |||
555 | spin_unlock_irqrestore(&(info->lock), flags); | ||
556 | |||
557 | if (hci_unregister_dev(hdev) < 0) | ||
558 | BT_ERR("Can't unregister HCI device %s", hdev->name); | ||
559 | |||
560 | hci_free_dev(hdev); | ||
561 | |||
562 | return 0; | ||
563 | } | ||
564 | |||
565 | static dev_link_t *dtl1_attach(void) | ||
566 | { | ||
567 | dtl1_info_t *info; | ||
568 | client_reg_t client_reg; | ||
569 | dev_link_t *link; | ||
570 | int ret; | ||
571 | |||
572 | /* Create new info device */ | ||
573 | info = kmalloc(sizeof(*info), GFP_KERNEL); | ||
574 | if (!info) | ||
575 | return NULL; | ||
576 | memset(info, 0, sizeof(*info)); | ||
577 | |||
578 | link = &info->link; | ||
579 | link->priv = info; | ||
580 | |||
581 | link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; | ||
582 | link->io.NumPorts1 = 8; | ||
583 | link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; | ||
584 | link->irq.IRQInfo1 = IRQ_LEVEL_ID; | ||
585 | |||
586 | link->irq.Handler = dtl1_interrupt; | ||
587 | link->irq.Instance = info; | ||
588 | |||
589 | link->conf.Attributes = CONF_ENABLE_IRQ; | ||
590 | link->conf.Vcc = 50; | ||
591 | link->conf.IntType = INT_MEMORY_AND_IO; | ||
592 | |||
593 | /* Register with Card Services */ | ||
594 | link->next = dev_list; | ||
595 | dev_list = link; | ||
596 | client_reg.dev_info = &dev_info; | ||
597 | client_reg.EventMask = | ||
598 | CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | | ||
599 | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | | ||
600 | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; | ||
601 | client_reg.event_handler = &dtl1_event; | ||
602 | client_reg.Version = 0x0210; | ||
603 | client_reg.event_callback_args.client_data = link; | ||
604 | |||
605 | ret = pcmcia_register_client(&link->handle, &client_reg); | ||
606 | if (ret != CS_SUCCESS) { | ||
607 | cs_error(link->handle, RegisterClient, ret); | ||
608 | dtl1_detach(link); | ||
609 | return NULL; | ||
610 | } | ||
611 | |||
612 | return link; | ||
613 | } | ||
614 | |||
615 | |||
616 | static void dtl1_detach(dev_link_t *link) | ||
617 | { | ||
618 | dtl1_info_t *info = link->priv; | ||
619 | dev_link_t **linkp; | ||
620 | int ret; | ||
621 | |||
622 | /* Locate device structure */ | ||
623 | for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) | ||
624 | if (*linkp == link) | ||
625 | break; | ||
626 | |||
627 | if (*linkp == NULL) | ||
628 | return; | ||
629 | |||
630 | if (link->state & DEV_CONFIG) | ||
631 | dtl1_release(link); | ||
632 | |||
633 | if (link->handle) { | ||
634 | ret = pcmcia_deregister_client(link->handle); | ||
635 | if (ret != CS_SUCCESS) | ||
636 | cs_error(link->handle, DeregisterClient, ret); | ||
637 | } | ||
638 | |||
639 | /* Unlink device structure, free bits */ | ||
640 | *linkp = link->next; | ||
641 | |||
642 | kfree(info); | ||
643 | } | ||
644 | |||
645 | static int get_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) | ||
646 | { | ||
647 | int i; | ||
648 | |||
649 | i = pcmcia_get_tuple_data(handle, tuple); | ||
650 | if (i != CS_SUCCESS) | ||
651 | return i; | ||
652 | |||
653 | return pcmcia_parse_tuple(handle, tuple, parse); | ||
654 | } | ||
655 | |||
656 | static int first_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) | ||
657 | { | ||
658 | if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) | ||
659 | return CS_NO_MORE_ITEMS; | ||
660 | return get_tuple(handle, tuple, parse); | ||
661 | } | ||
662 | |||
663 | static int next_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) | ||
664 | { | ||
665 | if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) | ||
666 | return CS_NO_MORE_ITEMS; | ||
667 | return get_tuple(handle, tuple, parse); | ||
668 | } | ||
669 | |||
670 | static void dtl1_config(dev_link_t *link) | ||
671 | { | ||
672 | client_handle_t handle = link->handle; | ||
673 | dtl1_info_t *info = link->priv; | ||
674 | tuple_t tuple; | ||
675 | u_short buf[256]; | ||
676 | cisparse_t parse; | ||
677 | cistpl_cftable_entry_t *cf = &parse.cftable_entry; | ||
678 | config_info_t config; | ||
679 | int i, last_ret, last_fn; | ||
680 | |||
681 | tuple.TupleData = (cisdata_t *)buf; | ||
682 | tuple.TupleOffset = 0; | ||
683 | tuple.TupleDataMax = 255; | ||
684 | tuple.Attributes = 0; | ||
685 | |||
686 | /* Get configuration register information */ | ||
687 | tuple.DesiredTuple = CISTPL_CONFIG; | ||
688 | last_ret = first_tuple(handle, &tuple, &parse); | ||
689 | if (last_ret != CS_SUCCESS) { | ||
690 | last_fn = ParseTuple; | ||
691 | goto cs_failed; | ||
692 | } | ||
693 | link->conf.ConfigBase = parse.config.base; | ||
694 | link->conf.Present = parse.config.rmask[0]; | ||
695 | |||
696 | /* Configure card */ | ||
697 | link->state |= DEV_CONFIG; | ||
698 | i = pcmcia_get_configuration_info(handle, &config); | ||
699 | link->conf.Vcc = config.Vcc; | ||
700 | |||
701 | tuple.TupleData = (cisdata_t *)buf; | ||
702 | tuple.TupleOffset = 0; | ||
703 | tuple.TupleDataMax = 255; | ||
704 | tuple.Attributes = 0; | ||
705 | tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; | ||
706 | |||
707 | /* Look for a generic full-sized window */ | ||
708 | link->io.NumPorts1 = 8; | ||
709 | i = first_tuple(handle, &tuple, &parse); | ||
710 | while (i != CS_NO_MORE_ITEMS) { | ||
711 | if ((i == CS_SUCCESS) && (cf->io.nwin == 1) && (cf->io.win[0].len > 8)) { | ||
712 | link->conf.ConfigIndex = cf->index; | ||
713 | link->io.BasePort1 = cf->io.win[0].base; | ||
714 | link->io.NumPorts1 = cf->io.win[0].len; /*yo */ | ||
715 | link->io.IOAddrLines = cf->io.flags & CISTPL_IO_LINES_MASK; | ||
716 | i = pcmcia_request_io(link->handle, &link->io); | ||
717 | if (i == CS_SUCCESS) | ||
718 | break; | ||
719 | } | ||
720 | i = next_tuple(handle, &tuple, &parse); | ||
721 | } | ||
722 | |||
723 | if (i != CS_SUCCESS) { | ||
724 | cs_error(link->handle, RequestIO, i); | ||
725 | goto failed; | ||
726 | } | ||
727 | |||
728 | i = pcmcia_request_irq(link->handle, &link->irq); | ||
729 | if (i != CS_SUCCESS) { | ||
730 | cs_error(link->handle, RequestIRQ, i); | ||
731 | link->irq.AssignedIRQ = 0; | ||
732 | } | ||
733 | |||
734 | i = pcmcia_request_configuration(link->handle, &link->conf); | ||
735 | if (i != CS_SUCCESS) { | ||
736 | cs_error(link->handle, RequestConfiguration, i); | ||
737 | goto failed; | ||
738 | } | ||
739 | |||
740 | if (dtl1_open(info) != 0) | ||
741 | goto failed; | ||
742 | |||
743 | strcpy(info->node.dev_name, info->hdev->name); | ||
744 | link->dev = &info->node; | ||
745 | link->state &= ~DEV_CONFIG_PENDING; | ||
746 | |||
747 | return; | ||
748 | |||
749 | cs_failed: | ||
750 | cs_error(link->handle, last_fn, last_ret); | ||
751 | |||
752 | failed: | ||
753 | dtl1_release(link); | ||
754 | } | ||
755 | |||
756 | |||
757 | static void dtl1_release(dev_link_t *link) | ||
758 | { | ||
759 | dtl1_info_t *info = link->priv; | ||
760 | |||
761 | if (link->state & DEV_PRESENT) | ||
762 | dtl1_close(info); | ||
763 | |||
764 | link->dev = NULL; | ||
765 | |||
766 | pcmcia_release_configuration(link->handle); | ||
767 | pcmcia_release_io(link->handle, &link->io); | ||
768 | pcmcia_release_irq(link->handle, &link->irq); | ||
769 | |||
770 | link->state &= ~DEV_CONFIG; | ||
771 | } | ||
772 | |||
773 | |||
774 | static int dtl1_event(event_t event, int priority, event_callback_args_t *args) | ||
775 | { | ||
776 | dev_link_t *link = args->client_data; | ||
777 | dtl1_info_t *info = link->priv; | ||
778 | |||
779 | switch (event) { | ||
780 | case CS_EVENT_CARD_REMOVAL: | ||
781 | link->state &= ~DEV_PRESENT; | ||
782 | if (link->state & DEV_CONFIG) { | ||
783 | dtl1_close(info); | ||
784 | dtl1_release(link); | ||
785 | } | ||
786 | break; | ||
787 | case CS_EVENT_CARD_INSERTION: | ||
788 | link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; | ||
789 | dtl1_config(link); | ||
790 | break; | ||
791 | case CS_EVENT_PM_SUSPEND: | ||
792 | link->state |= DEV_SUSPEND; | ||
793 | /* Fall through... */ | ||
794 | case CS_EVENT_RESET_PHYSICAL: | ||
795 | if (link->state & DEV_CONFIG) | ||
796 | pcmcia_release_configuration(link->handle); | ||
797 | break; | ||
798 | case CS_EVENT_PM_RESUME: | ||
799 | link->state &= ~DEV_SUSPEND; | ||
800 | /* Fall through... */ | ||
801 | case CS_EVENT_CARD_RESET: | ||
802 | if (DEV_OK(link)) | ||
803 | pcmcia_request_configuration(link->handle, &link->conf); | ||
804 | break; | ||
805 | } | ||
806 | |||
807 | return 0; | ||
808 | } | ||
809 | |||
810 | static struct pcmcia_driver dtl1_driver = { | ||
811 | .owner = THIS_MODULE, | ||
812 | .drv = { | ||
813 | .name = "dtl1_cs", | ||
814 | }, | ||
815 | .attach = dtl1_attach, | ||
816 | .detach = dtl1_detach, | ||
817 | }; | ||
818 | |||
819 | static int __init init_dtl1_cs(void) | ||
820 | { | ||
821 | return pcmcia_register_driver(&dtl1_driver); | ||
822 | } | ||
823 | |||
824 | |||
825 | static void __exit exit_dtl1_cs(void) | ||
826 | { | ||
827 | pcmcia_unregister_driver(&dtl1_driver); | ||
828 | BUG_ON(dev_list != NULL); | ||
829 | } | ||
830 | |||
831 | module_init(init_dtl1_cs); | ||
832 | module_exit(exit_dtl1_cs); | ||