diff options
Diffstat (limited to 'drivers/isdn/hardware/avm/avm_cs.c')
-rw-r--r-- | drivers/isdn/hardware/avm/avm_cs.c | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/drivers/isdn/hardware/avm/avm_cs.c b/drivers/isdn/hardware/avm/avm_cs.c new file mode 100644 index 000000000000..dc00c85e3e35 --- /dev/null +++ b/drivers/isdn/hardware/avm/avm_cs.c | |||
@@ -0,0 +1,510 @@ | |||
1 | /* $Id: avm_cs.c,v 1.4.6.3 2001/09/23 22:24:33 kai Exp $ | ||
2 | * | ||
3 | * A PCMCIA client driver for AVM B1/M1/M2 | ||
4 | * | ||
5 | * Copyright 1999 by Carsten Paeth <calle@calle.de> | ||
6 | * | ||
7 | * This software may be used and distributed according to the terms | ||
8 | * of the GNU General Public License, incorporated herein by reference. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/sched.h> | ||
16 | #include <linux/ptrace.h> | ||
17 | #include <linux/slab.h> | ||
18 | #include <linux/string.h> | ||
19 | #include <linux/tty.h> | ||
20 | #include <linux/serial.h> | ||
21 | #include <linux/major.h> | ||
22 | #include <asm/io.h> | ||
23 | #include <asm/system.h> | ||
24 | |||
25 | #include <pcmcia/version.h> | ||
26 | #include <pcmcia/cs_types.h> | ||
27 | #include <pcmcia/cs.h> | ||
28 | #include <pcmcia/cistpl.h> | ||
29 | #include <pcmcia/ciscode.h> | ||
30 | #include <pcmcia/ds.h> | ||
31 | #include <pcmcia/cisreg.h> | ||
32 | |||
33 | #include <linux/skbuff.h> | ||
34 | #include <linux/capi.h> | ||
35 | #include <linux/b1lli.h> | ||
36 | #include <linux/b1pcmcia.h> | ||
37 | |||
38 | /*====================================================================*/ | ||
39 | |||
40 | MODULE_DESCRIPTION("CAPI4Linux: PCMCIA client driver for AVM B1/M1/M2"); | ||
41 | MODULE_AUTHOR("Carsten Paeth"); | ||
42 | MODULE_LICENSE("GPL"); | ||
43 | |||
44 | /*====================================================================*/ | ||
45 | |||
46 | /* | ||
47 | The event() function is this driver's Card Services event handler. | ||
48 | It will be called by Card Services when an appropriate card status | ||
49 | event is received. The config() and release() entry points are | ||
50 | used to configure or release a socket, in response to card insertion | ||
51 | and ejection events. They are invoked from the skeleton event | ||
52 | handler. | ||
53 | */ | ||
54 | |||
55 | static void avmcs_config(dev_link_t *link); | ||
56 | static void avmcs_release(dev_link_t *link); | ||
57 | static int avmcs_event(event_t event, int priority, | ||
58 | event_callback_args_t *args); | ||
59 | |||
60 | /* | ||
61 | The attach() and detach() entry points are used to create and destroy | ||
62 | "instances" of the driver, where each instance represents everything | ||
63 | needed to manage one actual PCMCIA card. | ||
64 | */ | ||
65 | |||
66 | static dev_link_t *avmcs_attach(void); | ||
67 | static void avmcs_detach(dev_link_t *); | ||
68 | |||
69 | /* | ||
70 | The dev_info variable is the "key" that is used to match up this | ||
71 | device driver with appropriate cards, through the card configuration | ||
72 | database. | ||
73 | */ | ||
74 | |||
75 | static dev_info_t dev_info = "avm_cs"; | ||
76 | |||
77 | /* | ||
78 | A linked list of "instances" of the skeleton device. Each actual | ||
79 | PCMCIA card corresponds to one device instance, and is described | ||
80 | by one dev_link_t structure (defined in ds.h). | ||
81 | |||
82 | You may not want to use a linked list for this -- for example, the | ||
83 | memory card driver uses an array of dev_link_t pointers, where minor | ||
84 | device numbers are used to derive the corresponding array index. | ||
85 | */ | ||
86 | |||
87 | static dev_link_t *dev_list = NULL; | ||
88 | |||
89 | /* | ||
90 | A dev_link_t structure has fields for most things that are needed | ||
91 | to keep track of a socket, but there will usually be some device | ||
92 | specific information that also needs to be kept track of. The | ||
93 | 'priv' pointer in a dev_link_t structure can be used to point to | ||
94 | a device-specific private data structure, like this. | ||
95 | |||
96 | A driver needs to provide a dev_node_t structure for each device | ||
97 | on a card. In some cases, there is only one device per card (for | ||
98 | example, ethernet cards, modems). In other cases, there may be | ||
99 | many actual or logical devices (SCSI adapters, memory cards with | ||
100 | multiple partitions). The dev_node_t structures need to be kept | ||
101 | in a linked list starting at the 'dev' field of a dev_link_t | ||
102 | structure. We allocate them in the card's private data structure, | ||
103 | because they generally can't be allocated dynamically. | ||
104 | */ | ||
105 | |||
106 | typedef struct local_info_t { | ||
107 | dev_node_t node; | ||
108 | } local_info_t; | ||
109 | |||
110 | /*====================================================================== | ||
111 | |||
112 | avmcs_attach() creates an "instance" of the driver, allocating | ||
113 | local data structures for one device. The device is registered | ||
114 | with Card Services. | ||
115 | |||
116 | The dev_link structure is initialized, but we don't actually | ||
117 | configure the card at this point -- we wait until we receive a | ||
118 | card insertion event. | ||
119 | |||
120 | ======================================================================*/ | ||
121 | |||
122 | static dev_link_t *avmcs_attach(void) | ||
123 | { | ||
124 | client_reg_t client_reg; | ||
125 | dev_link_t *link; | ||
126 | local_info_t *local; | ||
127 | int ret; | ||
128 | |||
129 | /* Initialize the dev_link_t structure */ | ||
130 | link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); | ||
131 | if (!link) | ||
132 | goto err; | ||
133 | memset(link, 0, sizeof(struct dev_link_t)); | ||
134 | |||
135 | /* The io structure describes IO port mapping */ | ||
136 | link->io.NumPorts1 = 16; | ||
137 | link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; | ||
138 | link->io.NumPorts2 = 0; | ||
139 | |||
140 | /* Interrupt setup */ | ||
141 | link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; | ||
142 | link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; | ||
143 | |||
144 | link->irq.IRQInfo1 = IRQ_LEVEL_ID; | ||
145 | |||
146 | /* General socket configuration */ | ||
147 | link->conf.Attributes = CONF_ENABLE_IRQ; | ||
148 | link->conf.Vcc = 50; | ||
149 | link->conf.IntType = INT_MEMORY_AND_IO; | ||
150 | link->conf.ConfigIndex = 1; | ||
151 | link->conf.Present = PRESENT_OPTION; | ||
152 | |||
153 | /* Allocate space for private device-specific data */ | ||
154 | local = kmalloc(sizeof(local_info_t), GFP_KERNEL); | ||
155 | if (!local) | ||
156 | goto err_kfree; | ||
157 | memset(local, 0, sizeof(local_info_t)); | ||
158 | link->priv = local; | ||
159 | |||
160 | /* Register with Card Services */ | ||
161 | link->next = dev_list; | ||
162 | dev_list = link; | ||
163 | client_reg.dev_info = &dev_info; | ||
164 | client_reg.EventMask = | ||
165 | CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | | ||
166 | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | | ||
167 | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; | ||
168 | client_reg.event_handler = &avmcs_event; | ||
169 | client_reg.Version = 0x0210; | ||
170 | client_reg.event_callback_args.client_data = link; | ||
171 | ret = pcmcia_register_client(&link->handle, &client_reg); | ||
172 | if (ret != 0) { | ||
173 | cs_error(link->handle, RegisterClient, ret); | ||
174 | avmcs_detach(link); | ||
175 | goto err; | ||
176 | } | ||
177 | return link; | ||
178 | |||
179 | err_kfree: | ||
180 | kfree(link); | ||
181 | err: | ||
182 | return NULL; | ||
183 | } /* avmcs_attach */ | ||
184 | |||
185 | /*====================================================================== | ||
186 | |||
187 | This deletes a driver "instance". The device is de-registered | ||
188 | with Card Services. If it has been released, all local data | ||
189 | structures are freed. Otherwise, the structures will be freed | ||
190 | when the device is released. | ||
191 | |||
192 | ======================================================================*/ | ||
193 | |||
194 | static void avmcs_detach(dev_link_t *link) | ||
195 | { | ||
196 | dev_link_t **linkp; | ||
197 | |||
198 | /* Locate device structure */ | ||
199 | for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) | ||
200 | if (*linkp == link) break; | ||
201 | if (*linkp == NULL) | ||
202 | return; | ||
203 | |||
204 | /* | ||
205 | If the device is currently configured and active, we won't | ||
206 | actually delete it yet. Instead, it is marked so that when | ||
207 | the release() function is called, that will trigger a proper | ||
208 | detach(). | ||
209 | */ | ||
210 | if (link->state & DEV_CONFIG) { | ||
211 | link->state |= DEV_STALE_LINK; | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | /* Break the link with Card Services */ | ||
216 | if (link->handle) | ||
217 | pcmcia_deregister_client(link->handle); | ||
218 | |||
219 | /* Unlink device structure, free pieces */ | ||
220 | *linkp = link->next; | ||
221 | if (link->priv) { | ||
222 | kfree(link->priv); | ||
223 | } | ||
224 | kfree(link); | ||
225 | |||
226 | } /* avmcs_detach */ | ||
227 | |||
228 | /*====================================================================== | ||
229 | |||
230 | avmcs_config() is scheduled to run after a CARD_INSERTION event | ||
231 | is received, to configure the PCMCIA socket, and to make the | ||
232 | ethernet device available to the system. | ||
233 | |||
234 | ======================================================================*/ | ||
235 | |||
236 | static int get_tuple(client_handle_t handle, tuple_t *tuple, | ||
237 | cisparse_t *parse) | ||
238 | { | ||
239 | int i = pcmcia_get_tuple_data(handle, tuple); | ||
240 | if (i != CS_SUCCESS) return i; | ||
241 | return pcmcia_parse_tuple(handle, tuple, parse); | ||
242 | } | ||
243 | |||
244 | static int first_tuple(client_handle_t handle, tuple_t *tuple, | ||
245 | cisparse_t *parse) | ||
246 | { | ||
247 | int i = pcmcia_get_first_tuple(handle, tuple); | ||
248 | if (i != CS_SUCCESS) return i; | ||
249 | return get_tuple(handle, tuple, parse); | ||
250 | } | ||
251 | |||
252 | static int next_tuple(client_handle_t handle, tuple_t *tuple, | ||
253 | cisparse_t *parse) | ||
254 | { | ||
255 | int i = pcmcia_get_next_tuple(handle, tuple); | ||
256 | if (i != CS_SUCCESS) return i; | ||
257 | return get_tuple(handle, tuple, parse); | ||
258 | } | ||
259 | |||
260 | static void avmcs_config(dev_link_t *link) | ||
261 | { | ||
262 | client_handle_t handle; | ||
263 | tuple_t tuple; | ||
264 | cisparse_t parse; | ||
265 | cistpl_cftable_entry_t *cf = &parse.cftable_entry; | ||
266 | local_info_t *dev; | ||
267 | int i; | ||
268 | u_char buf[64]; | ||
269 | char devname[128]; | ||
270 | int cardtype; | ||
271 | int (*addcard)(unsigned int port, unsigned irq); | ||
272 | |||
273 | handle = link->handle; | ||
274 | dev = link->priv; | ||
275 | |||
276 | /* | ||
277 | This reads the card's CONFIG tuple to find its configuration | ||
278 | registers. | ||
279 | */ | ||
280 | do { | ||
281 | tuple.DesiredTuple = CISTPL_CONFIG; | ||
282 | i = pcmcia_get_first_tuple(handle, &tuple); | ||
283 | if (i != CS_SUCCESS) break; | ||
284 | tuple.TupleData = buf; | ||
285 | tuple.TupleDataMax = 64; | ||
286 | tuple.TupleOffset = 0; | ||
287 | i = pcmcia_get_tuple_data(handle, &tuple); | ||
288 | if (i != CS_SUCCESS) break; | ||
289 | i = pcmcia_parse_tuple(handle, &tuple, &parse); | ||
290 | if (i != CS_SUCCESS) break; | ||
291 | link->conf.ConfigBase = parse.config.base; | ||
292 | } while (0); | ||
293 | if (i != CS_SUCCESS) { | ||
294 | cs_error(link->handle, ParseTuple, i); | ||
295 | link->state &= ~DEV_CONFIG_PENDING; | ||
296 | return; | ||
297 | } | ||
298 | |||
299 | /* Configure card */ | ||
300 | link->state |= DEV_CONFIG; | ||
301 | |||
302 | do { | ||
303 | |||
304 | tuple.Attributes = 0; | ||
305 | tuple.TupleData = buf; | ||
306 | tuple.TupleDataMax = 254; | ||
307 | tuple.TupleOffset = 0; | ||
308 | tuple.DesiredTuple = CISTPL_VERS_1; | ||
309 | |||
310 | devname[0] = 0; | ||
311 | if( !first_tuple(handle, &tuple, &parse) && parse.version_1.ns > 1 ) { | ||
312 | strlcpy(devname,parse.version_1.str + parse.version_1.ofs[1], | ||
313 | sizeof(devname)); | ||
314 | } | ||
315 | /* | ||
316 | * find IO port | ||
317 | */ | ||
318 | tuple.TupleData = (cisdata_t *)buf; | ||
319 | tuple.TupleOffset = 0; tuple.TupleDataMax = 255; | ||
320 | tuple.Attributes = 0; | ||
321 | tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; | ||
322 | i = first_tuple(handle, &tuple, &parse); | ||
323 | while (i == CS_SUCCESS) { | ||
324 | if (cf->io.nwin > 0) { | ||
325 | link->conf.ConfigIndex = cf->index; | ||
326 | link->io.BasePort1 = cf->io.win[0].base; | ||
327 | link->io.NumPorts1 = cf->io.win[0].len; | ||
328 | link->io.NumPorts2 = 0; | ||
329 | printk(KERN_INFO "avm_cs: testing i/o %#x-%#x\n", | ||
330 | link->io.BasePort1, | ||
331 | link->io.BasePort1+link->io.NumPorts1-1); | ||
332 | i = pcmcia_request_io(link->handle, &link->io); | ||
333 | if (i == CS_SUCCESS) goto found_port; | ||
334 | } | ||
335 | i = next_tuple(handle, &tuple, &parse); | ||
336 | } | ||
337 | |||
338 | found_port: | ||
339 | if (i != CS_SUCCESS) { | ||
340 | cs_error(link->handle, RequestIO, i); | ||
341 | break; | ||
342 | } | ||
343 | |||
344 | /* | ||
345 | * allocate an interrupt line | ||
346 | */ | ||
347 | i = pcmcia_request_irq(link->handle, &link->irq); | ||
348 | if (i != CS_SUCCESS) { | ||
349 | cs_error(link->handle, RequestIRQ, i); | ||
350 | pcmcia_release_io(link->handle, &link->io); | ||
351 | break; | ||
352 | } | ||
353 | |||
354 | /* | ||
355 | * configure the PCMCIA socket | ||
356 | */ | ||
357 | i = pcmcia_request_configuration(link->handle, &link->conf); | ||
358 | if (i != CS_SUCCESS) { | ||
359 | cs_error(link->handle, RequestConfiguration, i); | ||
360 | pcmcia_release_io(link->handle, &link->io); | ||
361 | pcmcia_release_irq(link->handle, &link->irq); | ||
362 | break; | ||
363 | } | ||
364 | |||
365 | } while (0); | ||
366 | |||
367 | /* At this point, the dev_node_t structure(s) should be | ||
368 | initialized and arranged in a linked list at link->dev. */ | ||
369 | |||
370 | if (devname[0]) { | ||
371 | char *s = strrchr(devname, ' '); | ||
372 | if (!s) | ||
373 | s = devname; | ||
374 | else s++; | ||
375 | strcpy(dev->node.dev_name, s); | ||
376 | if (strcmp("M1", s) == 0) { | ||
377 | cardtype = AVM_CARDTYPE_M1; | ||
378 | } else if (strcmp("M2", s) == 0) { | ||
379 | cardtype = AVM_CARDTYPE_M2; | ||
380 | } else { | ||
381 | cardtype = AVM_CARDTYPE_B1; | ||
382 | } | ||
383 | } else { | ||
384 | strcpy(dev->node.dev_name, "b1"); | ||
385 | cardtype = AVM_CARDTYPE_B1; | ||
386 | } | ||
387 | |||
388 | dev->node.major = 64; | ||
389 | dev->node.minor = 0; | ||
390 | link->dev = &dev->node; | ||
391 | |||
392 | link->state &= ~DEV_CONFIG_PENDING; | ||
393 | /* If any step failed, release any partially configured state */ | ||
394 | if (i != 0) { | ||
395 | avmcs_release(link); | ||
396 | return; | ||
397 | } | ||
398 | |||
399 | |||
400 | switch (cardtype) { | ||
401 | case AVM_CARDTYPE_M1: addcard = b1pcmcia_addcard_m1; break; | ||
402 | case AVM_CARDTYPE_M2: addcard = b1pcmcia_addcard_m2; break; | ||
403 | default: | ||
404 | case AVM_CARDTYPE_B1: addcard = b1pcmcia_addcard_b1; break; | ||
405 | } | ||
406 | if ((i = (*addcard)(link->io.BasePort1, link->irq.AssignedIRQ)) < 0) { | ||
407 | printk(KERN_ERR "avm_cs: failed to add AVM-%s-Controller at i/o %#x, irq %d\n", | ||
408 | dev->node.dev_name, link->io.BasePort1, link->irq.AssignedIRQ); | ||
409 | avmcs_release(link); | ||
410 | return; | ||
411 | } | ||
412 | dev->node.minor = i; | ||
413 | |||
414 | } /* avmcs_config */ | ||
415 | |||
416 | /*====================================================================== | ||
417 | |||
418 | After a card is removed, avmcs_release() will unregister the net | ||
419 | device, and release the PCMCIA configuration. If the device is | ||
420 | still open, this will be postponed until it is closed. | ||
421 | |||
422 | ======================================================================*/ | ||
423 | |||
424 | static void avmcs_release(dev_link_t *link) | ||
425 | { | ||
426 | b1pcmcia_delcard(link->io.BasePort1, link->irq.AssignedIRQ); | ||
427 | |||
428 | /* Unlink the device chain */ | ||
429 | link->dev = NULL; | ||
430 | |||
431 | /* Don't bother checking to see if these succeed or not */ | ||
432 | pcmcia_release_configuration(link->handle); | ||
433 | pcmcia_release_io(link->handle, &link->io); | ||
434 | pcmcia_release_irq(link->handle, &link->irq); | ||
435 | link->state &= ~DEV_CONFIG; | ||
436 | |||
437 | if (link->state & DEV_STALE_LINK) | ||
438 | avmcs_detach(link); | ||
439 | |||
440 | } /* avmcs_release */ | ||
441 | |||
442 | /*====================================================================== | ||
443 | |||
444 | The card status event handler. Mostly, this schedules other | ||
445 | stuff to run after an event is received. A CARD_REMOVAL event | ||
446 | also sets some flags to discourage the net drivers from trying | ||
447 | to talk to the card any more. | ||
448 | |||
449 | When a CARD_REMOVAL event is received, we immediately set a flag | ||
450 | to block future accesses to this device. All the functions that | ||
451 | actually access the device should check this flag to make sure | ||
452 | the card is still present. | ||
453 | |||
454 | ======================================================================*/ | ||
455 | |||
456 | static int avmcs_event(event_t event, int priority, | ||
457 | event_callback_args_t *args) | ||
458 | { | ||
459 | dev_link_t *link = args->client_data; | ||
460 | |||
461 | switch (event) { | ||
462 | case CS_EVENT_CARD_REMOVAL: | ||
463 | link->state &= ~DEV_PRESENT; | ||
464 | if (link->state & DEV_CONFIG) | ||
465 | avmcs_release(link); | ||
466 | break; | ||
467 | case CS_EVENT_CARD_INSERTION: | ||
468 | link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; | ||
469 | avmcs_config(link); | ||
470 | break; | ||
471 | case CS_EVENT_PM_SUSPEND: | ||
472 | link->state |= DEV_SUSPEND; | ||
473 | /* Fall through... */ | ||
474 | case CS_EVENT_RESET_PHYSICAL: | ||
475 | if (link->state & DEV_CONFIG) | ||
476 | pcmcia_release_configuration(link->handle); | ||
477 | break; | ||
478 | case CS_EVENT_PM_RESUME: | ||
479 | link->state &= ~DEV_SUSPEND; | ||
480 | /* Fall through... */ | ||
481 | case CS_EVENT_CARD_RESET: | ||
482 | if (link->state & DEV_CONFIG) | ||
483 | pcmcia_request_configuration(link->handle, &link->conf); | ||
484 | break; | ||
485 | } | ||
486 | return 0; | ||
487 | } /* avmcs_event */ | ||
488 | |||
489 | static struct pcmcia_driver avmcs_driver = { | ||
490 | .owner = THIS_MODULE, | ||
491 | .drv = { | ||
492 | .name = "avm_cs", | ||
493 | }, | ||
494 | .attach = avmcs_attach, | ||
495 | .detach = avmcs_detach, | ||
496 | }; | ||
497 | |||
498 | static int __init avmcs_init(void) | ||
499 | { | ||
500 | return pcmcia_register_driver(&avmcs_driver); | ||
501 | } | ||
502 | |||
503 | static void __exit avmcs_exit(void) | ||
504 | { | ||
505 | pcmcia_unregister_driver(&avmcs_driver); | ||
506 | BUG_ON(dev_list != NULL); | ||
507 | } | ||
508 | |||
509 | module_init(avmcs_init); | ||
510 | module_exit(avmcs_exit); | ||