blob: 96969cb960a929d9d8a3da42e9c86a1ec0ff2c52 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*======================================================================
2
3 A driver for PCMCIA serial devices
4
5 serial_cs.c 1.134 2002/05/04 05:48:53
6
7 The contents of this file are subject to the Mozilla Public
8 License Version 1.1 (the "License"); you may not use this file
9 except in compliance with the License. You may obtain a copy of
10 the License at http://www.mozilla.org/MPL/
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 Alternatively, the contents of this file may be used under the
22 terms of the GNU General Public License version 2 (the "GPL"), in which
23 case the provisions of the GPL are applicable instead of the
24 above. If you wish to allow the use of your version of this file
25 only under the terms of the GPL and not to allow others to use
26 your version of this file under the MPL, indicate your decision
27 by deleting the provisions above and replace them with the notice
28 and other provisions required by the GPL. If you do not delete
29 the provisions above, a recipient may use your version of this
30 file under either the MPL or the GPL.
31
32======================================================================*/
33
34#include <linux/module.h>
35#include <linux/moduleparam.h>
36#include <linux/kernel.h>
37#include <linux/init.h>
38#include <linux/sched.h>
39#include <linux/ptrace.h>
40#include <linux/slab.h>
41#include <linux/string.h>
42#include <linux/timer.h>
43#include <linux/serial_core.h>
44#include <linux/major.h>
45#include <asm/io.h>
46#include <asm/system.h>
47
Linus Torvalds1da177e2005-04-16 15:20:36 -070048#include <pcmcia/cs_types.h>
49#include <pcmcia/cs.h>
50#include <pcmcia/cistpl.h>
51#include <pcmcia/ciscode.h>
52#include <pcmcia/ds.h>
53#include <pcmcia/cisreg.h>
54
55#include "8250.h"
56
57#ifdef PCMCIA_DEBUG
58static int pc_debug = PCMCIA_DEBUG;
59module_param(pc_debug, int, 0644);
60#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args)
61static char *version = "serial_cs.c 1.134 2002/05/04 05:48:53 (David Hinds)";
62#else
63#define DEBUG(n, args...)
64#endif
65
66/*====================================================================*/
67
68/* Parameters that can be set with 'insmod' */
69
70/* Enable the speaker? */
71static int do_sound = 1;
72/* Skip strict UART tests? */
73static int buggy_uart;
74
75module_param(do_sound, int, 0444);
76module_param(buggy_uart, int, 0444);
77
78/*====================================================================*/
79
80/* Table of multi-port card ID's */
81
82struct multi_id {
83 u_short manfid;
84 u_short prodid;
85 int multi; /* 1 = multifunction, > 1 = # ports */
86};
87
Arjan van de Vencb3592b2005-11-28 21:04:11 +000088static const struct multi_id multi_id[] = {
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 { MANFID_OMEGA, PRODID_OMEGA_QSP_100, 4 },
90 { MANFID_QUATECH, PRODID_QUATECH_DUAL_RS232, 2 },
91 { MANFID_QUATECH, PRODID_QUATECH_DUAL_RS232_D1, 2 },
92 { MANFID_QUATECH, PRODID_QUATECH_QUAD_RS232, 4 },
93 { MANFID_SOCKET, PRODID_SOCKET_DUAL_RS232, 2 },
94 { MANFID_INTEL, PRODID_INTEL_DUAL_RS232, 2 },
95 { MANFID_NATINST, PRODID_NATINST_QUAD_RS232, 4 }
96};
97#define MULTI_COUNT (sizeof(multi_id)/sizeof(struct multi_id))
98
99struct serial_info {
100 dev_link_t link;
101 int ndev;
102 int multi;
103 int slave;
104 int manfid;
105 dev_node_t node[4];
106 int line[4];
107};
108
Yum Rayan16f31112005-05-01 08:59:14 -0700109struct serial_cfg_mem {
110 tuple_t tuple;
111 cisparse_t parse;
112 u_char buf[256];
113};
114
115
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116static void serial_config(dev_link_t * link);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117
Linus Torvalds1da177e2005-04-16 15:20:36 -0700118
Linus Torvalds1da177e2005-04-16 15:20:36 -0700119/*======================================================================
120
121 After a card is removed, serial_remove() will unregister
122 the serial device(s), and release the PCMCIA configuration.
123
124======================================================================*/
125
126static void serial_remove(dev_link_t *link)
127{
128 struct serial_info *info = link->priv;
129 int i;
130
131 link->state &= ~DEV_PRESENT;
132
133 DEBUG(0, "serial_release(0x%p)\n", link);
134
135 /*
136 * Recheck to see if the device is still configured.
137 */
138 if (info->link.state & DEV_CONFIG) {
139 for (i = 0; i < info->ndev; i++)
140 serial8250_unregister_port(info->line[i]);
141
142 info->link.dev = NULL;
143
144 if (!info->slave) {
145 pcmcia_release_configuration(info->link.handle);
146 pcmcia_release_io(info->link.handle, &info->link.io);
147 pcmcia_release_irq(info->link.handle, &info->link.irq);
148 }
149
150 info->link.state &= ~DEV_CONFIG;
151 }
152}
153
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100154static int serial_suspend(struct pcmcia_device *dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155{
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100156 dev_link_t *link = dev_to_instance(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700157 link->state |= DEV_SUSPEND;
158
159 if (link->state & DEV_CONFIG) {
160 struct serial_info *info = link->priv;
161 int i;
162
163 for (i = 0; i < info->ndev; i++)
164 serial8250_suspend_port(info->line[i]);
165
166 if (!info->slave)
167 pcmcia_release_configuration(link->handle);
168 }
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100169
170 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700171}
172
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100173static int serial_resume(struct pcmcia_device *dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174{
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100175 dev_link_t *link = dev_to_instance(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700176 link->state &= ~DEV_SUSPEND;
177
178 if (DEV_OK(link)) {
179 struct serial_info *info = link->priv;
180 int i;
181
182 if (!info->slave)
183 pcmcia_request_configuration(link->handle, &link->conf);
184
185 for (i = 0; i < info->ndev; i++)
186 serial8250_resume_port(info->line[i]);
187 }
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100188
189 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700190}
191
192/*======================================================================
193
194 serial_attach() creates an "instance" of the driver, allocating
195 local data structures for one device. The device is registered
196 with Card Services.
197
198======================================================================*/
199
Dominik Brodowskif8cfa612005-11-14 21:25:51 +0100200static int serial_probe(struct pcmcia_device *p_dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700201{
202 struct serial_info *info;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700203 dev_link_t *link;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700204
205 DEBUG(0, "serial_attach()\n");
206
207 /* Create new serial device */
208 info = kmalloc(sizeof (*info), GFP_KERNEL);
209 if (!info)
Dominik Brodowskif8cfa612005-11-14 21:25:51 +0100210 return -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700211 memset(info, 0, sizeof (*info));
212 link = &info->link;
213 link->priv = info;
214
215 link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
216 link->io.NumPorts1 = 8;
217 link->irq.Attributes = IRQ_TYPE_EXCLUSIVE;
218 link->irq.IRQInfo1 = IRQ_LEVEL_ID;
219 link->conf.Attributes = CONF_ENABLE_IRQ;
220 if (do_sound) {
221 link->conf.Attributes |= CONF_ENABLE_SPKR;
222 link->conf.Status = CCSR_AUDIO_ENA;
223 }
224 link->conf.IntType = INT_MEMORY_AND_IO;
225
Dominik Brodowskif8cfa612005-11-14 21:25:51 +0100226 link->handle = p_dev;
227 p_dev->instance = link;
228 link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
229 serial_config(link);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700230
Dominik Brodowskif8cfa612005-11-14 21:25:51 +0100231 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700232}
233
234/*======================================================================
235
236 This deletes a driver "instance". The device is de-registered
237 with Card Services. If it has been released, all local data
238 structures are freed. Otherwise, the structures will be freed
239 when the device is released.
240
241======================================================================*/
242
Dominik Brodowskicc3b4862005-11-14 21:23:14 +0100243static void serial_detach(struct pcmcia_device *p_dev)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244{
Dominik Brodowskicc3b4862005-11-14 21:23:14 +0100245 dev_link_t *link = dev_to_instance(p_dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 struct serial_info *info = link->priv;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700247
248 DEBUG(0, "serial_detach(0x%p)\n", link);
249
Linus Torvalds1da177e2005-04-16 15:20:36 -0700250 /*
251 * Ensure any outstanding scheduled tasks are completed.
252 */
253 flush_scheduled_work();
254
255 /*
256 * Ensure that the ports have been released.
257 */
258 serial_remove(link);
259
Dominik Brodowskib4635812005-11-14 21:25:35 +0100260 /* free bits */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700261 kfree(info);
262}
263
264/*====================================================================*/
265
266static int setup_serial(client_handle_t handle, struct serial_info * info,
267 kio_addr_t iobase, int irq)
268{
269 struct uart_port port;
270 int line;
271
272 memset(&port, 0, sizeof (struct uart_port));
273 port.iobase = iobase;
274 port.irq = irq;
275 port.flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_SHARE_IRQ;
276 port.uartclk = 1843200;
277 port.dev = &handle_to_dev(handle);
278 if (buggy_uart)
279 port.flags |= UPF_BUGGY_UART;
280 line = serial8250_register_port(&port);
281 if (line < 0) {
282 printk(KERN_NOTICE "serial_cs: serial8250_register_port() at "
283 "0x%04lx, irq %d failed\n", (u_long)iobase, irq);
284 return -EINVAL;
285 }
286
287 info->line[info->ndev] = line;
288 sprintf(info->node[info->ndev].dev_name, "ttyS%d", line);
289 info->node[info->ndev].major = TTY_MAJOR;
290 info->node[info->ndev].minor = 0x40 + line;
291 if (info->ndev > 0)
292 info->node[info->ndev - 1].next = &info->node[info->ndev];
293 info->ndev++;
294
295 return 0;
296}
297
298/*====================================================================*/
299
300static int
301first_tuple(client_handle_t handle, tuple_t * tuple, cisparse_t * parse)
302{
303 int i;
304 i = pcmcia_get_first_tuple(handle, tuple);
305 if (i != CS_SUCCESS)
306 return CS_NO_MORE_ITEMS;
307 i = pcmcia_get_tuple_data(handle, tuple);
308 if (i != CS_SUCCESS)
309 return i;
310 return pcmcia_parse_tuple(handle, tuple, parse);
311}
312
313static int
314next_tuple(client_handle_t handle, tuple_t * tuple, cisparse_t * parse)
315{
316 int i;
317 i = pcmcia_get_next_tuple(handle, tuple);
318 if (i != CS_SUCCESS)
319 return CS_NO_MORE_ITEMS;
320 i = pcmcia_get_tuple_data(handle, tuple);
321 if (i != CS_SUCCESS)
322 return i;
323 return pcmcia_parse_tuple(handle, tuple, parse);
324}
325
326/*====================================================================*/
327
328static int simple_config(dev_link_t *link)
329{
Arjan van de Vencb3592b2005-11-28 21:04:11 +0000330 static const kio_addr_t base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 };
331 static const int size_table[2] = { 8, 16 };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700332 client_handle_t handle = link->handle;
333 struct serial_info *info = link->priv;
Yum Rayan16f31112005-05-01 08:59:14 -0700334 struct serial_cfg_mem *cfg_mem;
335 tuple_t *tuple;
336 u_char *buf;
337 cisparse_t *parse;
338 cistpl_cftable_entry_t *cf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700339 config_info_t config;
340 int i, j, try;
341 int s;
342
Yum Rayan16f31112005-05-01 08:59:14 -0700343 cfg_mem = kmalloc(sizeof(struct serial_cfg_mem), GFP_KERNEL);
344 if (!cfg_mem)
345 return -1;
346
347 tuple = &cfg_mem->tuple;
348 parse = &cfg_mem->parse;
349 cf = &parse->cftable_entry;
350 buf = cfg_mem->buf;
351
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352 /* If the card is already configured, look up the port and irq */
353 i = pcmcia_get_configuration_info(handle, &config);
354 if ((i == CS_SUCCESS) && (config.Attributes & CONF_VALID_CLIENT)) {
355 kio_addr_t port = 0;
356 if ((config.BasePort2 != 0) && (config.NumPorts2 == 8)) {
357 port = config.BasePort2;
358 info->slave = 1;
359 } else if ((info->manfid == MANFID_OSITECH) &&
360 (config.NumPorts1 == 0x40)) {
361 port = config.BasePort1 + 0x28;
362 info->slave = 1;
363 }
Yum Rayan16f31112005-05-01 08:59:14 -0700364 if (info->slave) {
365 kfree(cfg_mem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700366 return setup_serial(handle, info, port, config.AssignedIRQ);
Yum Rayan16f31112005-05-01 08:59:14 -0700367 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700368 }
369 link->conf.Vcc = config.Vcc;
370
371 /* First pass: look for a config entry that looks normal. */
Yum Rayan16f31112005-05-01 08:59:14 -0700372 tuple->TupleData = (cisdata_t *) buf;
373 tuple->TupleOffset = 0;
374 tuple->TupleDataMax = 255;
375 tuple->Attributes = 0;
376 tuple->DesiredTuple = CISTPL_CFTABLE_ENTRY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700377 /* Two tries: without IO aliases, then with aliases */
378 for (s = 0; s < 2; s++) {
379 for (try = 0; try < 2; try++) {
Yum Rayan16f31112005-05-01 08:59:14 -0700380 i = first_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700381 while (i != CS_NO_MORE_ITEMS) {
382 if (i != CS_SUCCESS)
383 goto next_entry;
384 if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM))
385 link->conf.Vpp1 = link->conf.Vpp2 =
386 cf->vpp1.param[CISTPL_POWER_VNOM] / 10000;
387 if ((cf->io.nwin > 0) && (cf->io.win[0].len == size_table[s]) &&
388 (cf->io.win[0].base != 0)) {
389 link->conf.ConfigIndex = cf->index;
390 link->io.BasePort1 = cf->io.win[0].base;
391 link->io.IOAddrLines = (try == 0) ?
392 16 : cf->io.flags & CISTPL_IO_LINES_MASK;
393 i = pcmcia_request_io(link->handle, &link->io);
394 if (i == CS_SUCCESS)
395 goto found_port;
396 }
397next_entry:
Yum Rayan16f31112005-05-01 08:59:14 -0700398 i = next_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700399 }
400 }
401 }
402 /* Second pass: try to find an entry that isn't picky about
403 its base address, then try to grab any standard serial port
404 address, and finally try to get any free port. */
Yum Rayan16f31112005-05-01 08:59:14 -0700405 i = first_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700406 while (i != CS_NO_MORE_ITEMS) {
407 if ((i == CS_SUCCESS) && (cf->io.nwin > 0) &&
408 ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) {
409 link->conf.ConfigIndex = cf->index;
410 for (j = 0; j < 5; j++) {
411 link->io.BasePort1 = base[j];
412 link->io.IOAddrLines = base[j] ? 16 : 3;
413 i = pcmcia_request_io(link->handle, &link->io);
414 if (i == CS_SUCCESS)
415 goto found_port;
416 }
417 }
Yum Rayan16f31112005-05-01 08:59:14 -0700418 i = next_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700419 }
420
421 found_port:
422 if (i != CS_SUCCESS) {
423 printk(KERN_NOTICE
424 "serial_cs: no usable port range found, giving up\n");
425 cs_error(link->handle, RequestIO, i);
Yum Rayan16f31112005-05-01 08:59:14 -0700426 kfree(cfg_mem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700427 return -1;
428 }
429
430 i = pcmcia_request_irq(link->handle, &link->irq);
431 if (i != CS_SUCCESS) {
432 cs_error(link->handle, RequestIRQ, i);
433 link->irq.AssignedIRQ = 0;
434 }
435 if (info->multi && (info->manfid == MANFID_3COM))
436 link->conf.ConfigIndex &= ~(0x08);
437 i = pcmcia_request_configuration(link->handle, &link->conf);
438 if (i != CS_SUCCESS) {
439 cs_error(link->handle, RequestConfiguration, i);
Yum Rayan16f31112005-05-01 08:59:14 -0700440 kfree(cfg_mem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700441 return -1;
442 }
Yum Rayan16f31112005-05-01 08:59:14 -0700443 kfree(cfg_mem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700444 return setup_serial(handle, info, link->io.BasePort1, link->irq.AssignedIRQ);
445}
446
447static int multi_config(dev_link_t * link)
448{
449 client_handle_t handle = link->handle;
450 struct serial_info *info = link->priv;
Yum Rayan16f31112005-05-01 08:59:14 -0700451 struct serial_cfg_mem *cfg_mem;
452 tuple_t *tuple;
453 u_char *buf;
454 cisparse_t *parse;
455 cistpl_cftable_entry_t *cf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700456 config_info_t config;
Yum Rayan16f31112005-05-01 08:59:14 -0700457 int i, rc, base2 = 0;
458
459 cfg_mem = kmalloc(sizeof(struct serial_cfg_mem), GFP_KERNEL);
460 if (!cfg_mem)
461 return -1;
462 tuple = &cfg_mem->tuple;
463 parse = &cfg_mem->parse;
464 cf = &parse->cftable_entry;
465 buf = cfg_mem->buf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700466
467 i = pcmcia_get_configuration_info(handle, &config);
468 if (i != CS_SUCCESS) {
469 cs_error(handle, GetConfigurationInfo, i);
Yum Rayan16f31112005-05-01 08:59:14 -0700470 rc = -1;
471 goto free_cfg_mem;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700472 }
473 link->conf.Vcc = config.Vcc;
474
Yum Rayan16f31112005-05-01 08:59:14 -0700475 tuple->TupleData = (cisdata_t *) buf;
476 tuple->TupleOffset = 0;
477 tuple->TupleDataMax = 255;
478 tuple->Attributes = 0;
479 tuple->DesiredTuple = CISTPL_CFTABLE_ENTRY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700480
481 /* First, look for a generic full-sized window */
482 link->io.NumPorts1 = info->multi * 8;
Yum Rayan16f31112005-05-01 08:59:14 -0700483 i = first_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700484 while (i != CS_NO_MORE_ITEMS) {
485 /* The quad port cards have bad CIS's, so just look for a
486 window larger than 8 ports and assume it will be right */
487 if ((i == CS_SUCCESS) && (cf->io.nwin == 1) &&
488 (cf->io.win[0].len > 8)) {
489 link->conf.ConfigIndex = cf->index;
490 link->io.BasePort1 = cf->io.win[0].base;
491 link->io.IOAddrLines =
492 cf->io.flags & CISTPL_IO_LINES_MASK;
493 i = pcmcia_request_io(link->handle, &link->io);
494 base2 = link->io.BasePort1 + 8;
495 if (i == CS_SUCCESS)
496 break;
497 }
Yum Rayan16f31112005-05-01 08:59:14 -0700498 i = next_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700499 }
500
501 /* If that didn't work, look for two windows */
502 if (i != CS_SUCCESS) {
503 link->io.NumPorts1 = link->io.NumPorts2 = 8;
504 info->multi = 2;
Yum Rayan16f31112005-05-01 08:59:14 -0700505 i = first_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700506 while (i != CS_NO_MORE_ITEMS) {
507 if ((i == CS_SUCCESS) && (cf->io.nwin == 2)) {
508 link->conf.ConfigIndex = cf->index;
509 link->io.BasePort1 = cf->io.win[0].base;
510 link->io.BasePort2 = cf->io.win[1].base;
511 link->io.IOAddrLines =
512 cf->io.flags & CISTPL_IO_LINES_MASK;
513 i = pcmcia_request_io(link->handle, &link->io);
514 base2 = link->io.BasePort2;
515 if (i == CS_SUCCESS)
516 break;
517 }
Yum Rayan16f31112005-05-01 08:59:14 -0700518 i = next_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700519 }
520 }
521
522 if (i != CS_SUCCESS) {
523 cs_error(link->handle, RequestIO, i);
Yum Rayan16f31112005-05-01 08:59:14 -0700524 rc = -1;
525 goto free_cfg_mem;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700526 }
527
528 i = pcmcia_request_irq(link->handle, &link->irq);
529 if (i != CS_SUCCESS) {
530 printk(KERN_NOTICE
531 "serial_cs: no usable port range found, giving up\n");
532 cs_error(link->handle, RequestIRQ, i);
533 link->irq.AssignedIRQ = 0;
534 }
535 /* Socket Dual IO: this enables irq's for second port */
536 if (info->multi && (info->manfid == MANFID_SOCKET)) {
537 link->conf.Present |= PRESENT_EXT_STATUS;
538 link->conf.ExtStatus = ESR_REQ_ATTN_ENA;
539 }
540 i = pcmcia_request_configuration(link->handle, &link->conf);
541 if (i != CS_SUCCESS) {
542 cs_error(link->handle, RequestConfiguration, i);
Yum Rayan16f31112005-05-01 08:59:14 -0700543 rc = -1;
544 goto free_cfg_mem;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700545 }
546
547 /* The Oxford Semiconductor OXCF950 cards are in fact single-port:
548 8 registers are for the UART, the others are extra registers */
549 if (info->manfid == MANFID_OXSEMI) {
550 if (cf->index == 1 || cf->index == 3) {
551 setup_serial(handle, info, base2, link->irq.AssignedIRQ);
552 outb(12, link->io.BasePort1 + 1);
553 } else {
554 setup_serial(handle, info, link->io.BasePort1, link->irq.AssignedIRQ);
555 outb(12, base2 + 1);
556 }
Yum Rayan16f31112005-05-01 08:59:14 -0700557 rc = 0;
558 goto free_cfg_mem;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700559 }
560
561 setup_serial(handle, info, link->io.BasePort1, link->irq.AssignedIRQ);
562 /* The Nokia cards are not really multiport cards */
Yum Rayan16f31112005-05-01 08:59:14 -0700563 if (info->manfid == MANFID_NOKIA) {
564 rc = 0;
565 goto free_cfg_mem;
566 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700567 for (i = 0; i < info->multi - 1; i++)
Yum Rayan16f31112005-05-01 08:59:14 -0700568 setup_serial(handle, info, base2 + (8 * i),
569 link->irq.AssignedIRQ);
570 rc = 0;
571free_cfg_mem:
572 kfree(cfg_mem);
573 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700574}
575
576/*======================================================================
577
578 serial_config() is scheduled to run after a CARD_INSERTION event
579 is received, to configure the PCMCIA socket, and to make the
580 serial device available to the system.
581
582======================================================================*/
583
584void serial_config(dev_link_t * link)
585{
586 client_handle_t handle = link->handle;
587 struct serial_info *info = link->priv;
Yum Rayan16f31112005-05-01 08:59:14 -0700588 struct serial_cfg_mem *cfg_mem;
589 tuple_t *tuple;
590 u_char *buf;
591 cisparse_t *parse;
592 cistpl_cftable_entry_t *cf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700593 int i, last_ret, last_fn;
594
595 DEBUG(0, "serial_config(0x%p)\n", link);
596
Yum Rayan16f31112005-05-01 08:59:14 -0700597 cfg_mem = kmalloc(sizeof(struct serial_cfg_mem), GFP_KERNEL);
598 if (!cfg_mem)
599 goto failed;
600
601 tuple = &cfg_mem->tuple;
602 parse = &cfg_mem->parse;
603 cf = &parse->cftable_entry;
604 buf = cfg_mem->buf;
605
606 tuple->TupleData = (cisdata_t *) buf;
607 tuple->TupleOffset = 0;
608 tuple->TupleDataMax = 255;
609 tuple->Attributes = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700610 /* Get configuration register information */
Yum Rayan16f31112005-05-01 08:59:14 -0700611 tuple->DesiredTuple = CISTPL_CONFIG;
612 last_ret = first_tuple(handle, tuple, parse);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700613 if (last_ret != CS_SUCCESS) {
614 last_fn = ParseTuple;
615 goto cs_failed;
616 }
Yum Rayan16f31112005-05-01 08:59:14 -0700617 link->conf.ConfigBase = parse->config.base;
618 link->conf.Present = parse->config.rmask[0];
Linus Torvalds1da177e2005-04-16 15:20:36 -0700619
620 /* Configure card */
621 link->state |= DEV_CONFIG;
622
623 /* Is this a compliant multifunction card? */
Yum Rayan16f31112005-05-01 08:59:14 -0700624 tuple->DesiredTuple = CISTPL_LONGLINK_MFC;
625 tuple->Attributes = TUPLE_RETURN_COMMON | TUPLE_RETURN_LINK;
626 info->multi = (first_tuple(handle, tuple, parse) == CS_SUCCESS);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700627
628 /* Is this a multiport card? */
Yum Rayan16f31112005-05-01 08:59:14 -0700629 tuple->DesiredTuple = CISTPL_MANFID;
630 if (first_tuple(handle, tuple, parse) == CS_SUCCESS) {
Petr Vandrovecf1fc3992005-05-16 21:53:44 -0700631 info->manfid = parse->manfid.manf;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700632 for (i = 0; i < MULTI_COUNT; i++)
633 if ((info->manfid == multi_id[i].manfid) &&
Petr Vandrovecf1fc3992005-05-16 21:53:44 -0700634 (parse->manfid.card == multi_id[i].prodid))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700635 break;
636 if (i < MULTI_COUNT)
637 info->multi = multi_id[i].multi;
638 }
639
640 /* Another check for dual-serial cards: look for either serial or
641 multifunction cards that ask for appropriate IO port ranges */
Yum Rayan16f31112005-05-01 08:59:14 -0700642 tuple->DesiredTuple = CISTPL_FUNCID;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700643 if ((info->multi == 0) &&
Yum Rayan16f31112005-05-01 08:59:14 -0700644 ((first_tuple(handle, tuple, parse) != CS_SUCCESS) ||
645 (parse->funcid.func == CISTPL_FUNCID_MULTI) ||
646 (parse->funcid.func == CISTPL_FUNCID_SERIAL))) {
647 tuple->DesiredTuple = CISTPL_CFTABLE_ENTRY;
648 if (first_tuple(handle, tuple, parse) == CS_SUCCESS) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700649 if ((cf->io.nwin == 1) && (cf->io.win[0].len % 8 == 0))
650 info->multi = cf->io.win[0].len >> 3;
651 if ((cf->io.nwin == 2) && (cf->io.win[0].len == 8) &&
652 (cf->io.win[1].len == 8))
653 info->multi = 2;
654 }
655 }
656
657 if (info->multi > 1)
658 multi_config(link);
659 else
660 simple_config(link);
661
662 if (info->ndev == 0)
663 goto failed;
664
665 if (info->manfid == MANFID_IBM) {
666 conf_reg_t reg = { 0, CS_READ, 0x800, 0 };
667 last_ret = pcmcia_access_configuration_register(link->handle, &reg);
668 if (last_ret) {
669 last_fn = AccessConfigurationRegister;
670 goto cs_failed;
671 }
672 reg.Action = CS_WRITE;
673 reg.Value = reg.Value | 1;
674 last_ret = pcmcia_access_configuration_register(link->handle, &reg);
675 if (last_ret) {
676 last_fn = AccessConfigurationRegister;
677 goto cs_failed;
678 }
679 }
680
681 link->dev = &info->node[0];
682 link->state &= ~DEV_CONFIG_PENDING;
Yum Rayan16f31112005-05-01 08:59:14 -0700683 kfree(cfg_mem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700684 return;
685
686 cs_failed:
687 cs_error(link->handle, last_fn, last_ret);
688 failed:
689 serial_remove(link);
690 link->state &= ~DEV_CONFIG_PENDING;
Yum Rayan16f31112005-05-01 08:59:14 -0700691 kfree(cfg_mem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700692}
693
Dominik Brodowski325aa292005-06-27 16:28:18 -0700694static struct pcmcia_device_id serial_ids[] = {
695 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0057, 0x0021),
696 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0089, 0x110a),
697 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0104, 0x000a),
698 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0105, 0xea15),
699 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0109, 0x0501),
700 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0138, 0x110a),
701 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0140, 0x000a),
702 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0143, 0x3341),
703 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0143, 0xc0ab),
704 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x016c, 0x0081),
705 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x021b, 0x0101),
706 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x08a1, 0xc0ab),
Jun Komurof4d75102005-06-27 16:28:44 -0700707 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0105, 0x0d0a),
708 PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0105, 0x0e0a),
Dominik Brodowski325aa292005-06-27 16:28:18 -0700709 PCMCIA_PFC_DEVICE_PROD_ID123(1, "MEGAHERTZ", "CC/XJEM3288", "DATA/FAX/CELL ETHERNET MODEM", 0xf510db04, 0x04cd2988, 0x46a52d63),
710 PCMCIA_PFC_DEVICE_PROD_ID123(1, "MEGAHERTZ", "CC/XJEM3336", "DATA/FAX/CELL ETHERNET MODEM", 0xf510db04, 0x0143b773, 0x46a52d63),
711 PCMCIA_PFC_DEVICE_PROD_ID123(1, "MEGAHERTZ", "EM1144T", "PCMCIA MODEM", 0xf510db04, 0x856d66c8, 0xbd6c43ef),
712 PCMCIA_PFC_DEVICE_PROD_ID123(1, "MEGAHERTZ", "XJEM1144/CCEM1144", "PCMCIA MODEM", 0xf510db04, 0x52d21e1e, 0xbd6c43ef),
713 PCMCIA_PFC_DEVICE_PROD_ID13(1, "Xircom", "CEM28", 0x2e3ee845, 0x0ea978ea),
714 PCMCIA_PFC_DEVICE_PROD_ID13(1, "Xircom", "CEM33", 0x2e3ee845, 0x80609023),
715 PCMCIA_PFC_DEVICE_PROD_ID13(1, "Xircom", "CEM56", 0x2e3ee845, 0xa650c32a),
716 PCMCIA_PFC_DEVICE_PROD_ID13(1, "Xircom", "REM10", 0x2e3ee845, 0x76df1d29),
717 PCMCIA_PFC_DEVICE_PROD_ID13(1, "Xircom", "XEM5600", 0x2e3ee845, 0xf1403719),
Komurod277ad02005-07-28 01:07:24 -0700718 PCMCIA_PFC_DEVICE_PROD_ID12(1, "AnyCom", "Fast Ethernet + 56K COMBO", 0x578ba6e7, 0xb0ac62c4),
Dominik Brodowski325aa292005-06-27 16:28:18 -0700719 PCMCIA_PFC_DEVICE_PROD_ID12(1, "D-Link", "DME336T", 0x1a424a1c, 0xb23897ff),
720 PCMCIA_PFC_DEVICE_PROD_ID12(1, "Gateway 2000", "XJEM3336", 0xdd9989be, 0x662c394c),
721 PCMCIA_PFC_DEVICE_PROD_ID12(1, "Grey Cell", "GCS3000", 0x2a151fac, 0x48b932ae),
722 PCMCIA_PFC_DEVICE_PROD_ID12(1, "Linksys", "EtherFast 10&100 + 56K PC Card (PCMLM56)", 0x0733cc81, 0xb3765033),
723 PCMCIA_PFC_DEVICE_PROD_ID12(1, "LINKSYS", "PCMLM336", 0xf7cb0b07, 0x7a821b58),
724 PCMCIA_PFC_DEVICE_PROD_ID12(1, "MEGAHERTZ", "XJEM1144/CCEM1144", 0xf510db04, 0x52d21e1e),
Komurod277ad02005-07-28 01:07:24 -0700725 PCMCIA_PFC_DEVICE_PROD_ID12(1, "Ositech", "Trumpcard:Jack of Diamonds Modem+Ethernet", 0xc2f80cd, 0x656947b9),
726 PCMCIA_PFC_DEVICE_PROD_ID12(1, "Ositech", "Trumpcard:Jack of Hearts Modem+Ethernet", 0xc2f80cd, 0xdc9ba5ed),
Dominik Brodowski325aa292005-06-27 16:28:18 -0700727 PCMCIA_PFC_DEVICE_PROD_ID12(1, "PCMCIAs", "ComboCard", 0xdcfe12d3, 0xcd8906cc),
728 PCMCIA_PFC_DEVICE_PROD_ID12(1, "PCMCIAs", "LanModem", 0xdcfe12d3, 0xc67c648f),
729 PCMCIA_PFC_DEVICE_PROD_ID12(1, "TDK", "GlobalNetworker 3410/3412", 0x1eae9475, 0xd9a93bed),
Komurod277ad02005-07-28 01:07:24 -0700730 PCMCIA_PFC_DEVICE_PROD_ID12(1, "Xircom", "CreditCard Ethernet+Modem II", 0x2e3ee845, 0xeca401bf),
Dominik Brodowski325aa292005-06-27 16:28:18 -0700731 PCMCIA_MFC_DEVICE_MANF_CARD(0, 0x0104, 0x0070),
732 PCMCIA_MFC_DEVICE_MANF_CARD(1, 0x0101, 0x0562),
733 PCMCIA_MFC_DEVICE_MANF_CARD(1, 0x0104, 0x0070),
734 PCMCIA_MFC_DEVICE_MANF_CARD(1, 0x016c, 0x0020),
735 PCMCIA_MFC_DEVICE_PROD_ID123(1, "APEX DATA", "MULTICARD", "ETHERNET-MODEM", 0x11c2da09, 0x7289dc5d, 0xaad95e1f),
736 PCMCIA_MFC_DEVICE_PROD_ID12(1, "IBM", "Home and Away 28.8 PC Card ", 0xb569a6e5, 0x5bd4ff2c),
737 PCMCIA_MFC_DEVICE_PROD_ID12(1, "IBM", "Home and Away Credit Card Adapter", 0xb569a6e5, 0x4bdf15c3),
738 PCMCIA_MFC_DEVICE_PROD_ID12(1, "IBM", "w95 Home and Away Credit Card ", 0xb569a6e5, 0xae911c15),
739 PCMCIA_MFC_DEVICE_PROD_ID1(1, "Motorola MARQUIS", 0xf03e4e77),
740 PCMCIA_MFC_DEVICE_PROD_ID2(1, "FAX/Modem/Ethernet Combo Card ", 0x1ed59302),
741 PCMCIA_DEVICE_MANF_CARD(0x0089, 0x0301),
742 PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0039),
743 PCMCIA_DEVICE_MANF_CARD(0x0104, 0x0006),
744 PCMCIA_DEVICE_MANF_CARD(0x0105, 0x410a),
745 PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0d50),
746 PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0d51),
747 PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0d52),
748 PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0d53),
749 PCMCIA_DEVICE_MANF_CARD(0x010b, 0xd180),
750 PCMCIA_DEVICE_MANF_CARD(0x0137, 0x000e),
751 PCMCIA_DEVICE_MANF_CARD(0x0137, 0x001b),
752 PCMCIA_DEVICE_MANF_CARD(0x0137, 0x0025),
753 PCMCIA_DEVICE_MANF_CARD(0x0137, 0x0045),
754 PCMCIA_DEVICE_MANF_CARD(0x0137, 0x0052),
755 PCMCIA_DEVICE_PROD_ID134("ADV", "TECH", "COMpad-32/85", 0x67459937, 0x916d02ba, 0x8fbe92ae),
756 PCMCIA_DEVICE_PROD_ID124("GATEWAY2000", "CC3144", "PCMCIA MODEM", 0x506bccae, 0xcb3685f1, 0xbd6c43ef),
757 PCMCIA_DEVICE_PROD_ID14("MEGAHERTZ", "PCMCIA MODEM", 0xf510db04, 0xbd6c43ef),
758 PCMCIA_DEVICE_PROD_ID124("TOSHIBA", "T144PF", "PCMCIA MODEM", 0xb4585a1a, 0x7271409c, 0xbd6c43ef),
759 PCMCIA_DEVICE_PROD_ID123("FUJITSU", "FC14F ", "MBH10213", 0x6ee5a3d8, 0x30ead12b, 0xb00f05a0),
760 PCMCIA_DEVICE_PROD_ID13("MEGAHERTZ", "V.34 PCMCIA MODEM", 0xf510db04, 0xbb2cce4a),
761 PCMCIA_DEVICE_PROD_ID12("Brain Boxes", "Bluetooth PC Card", 0xee138382, 0xd4ce9b02),
762 PCMCIA_DEVICE_PROD_ID12("CIRRUS LOGIC", "FAX MODEM", 0xe625f451, 0xcecd6dfa),
763 PCMCIA_DEVICE_PROD_ID12("COMPAQ", "PCMCIA 28800 FAX/DATA MODEM", 0xa3a3062c, 0x8cbd7c76),
764 PCMCIA_DEVICE_PROD_ID12("COMPAQ", "PCMCIA 33600 FAX/DATA MODEM", 0xa3a3062c, 0x5a00ce95),
765 PCMCIA_DEVICE_PROD_ID12("Computerboards, Inc.", "PCM-COM422", 0xd0b78f51, 0x7e2d49ed),
766 PCMCIA_DEVICE_PROD_ID12("Dr. Neuhaus", "FURY CARD 14K4", 0x76942813, 0x8b96ce65),
767 PCMCIA_DEVICE_PROD_ID12("Intelligent", "ANGIA FAX/MODEM", 0xb496e65e, 0xf31602a6),
Komurod277ad02005-07-28 01:07:24 -0700768 PCMCIA_DEVICE_PROD_ID12("Intel", "MODEM 2400+", 0x816cc815, 0x412729fb),
Dominik Brodowski325aa292005-06-27 16:28:18 -0700769 PCMCIA_DEVICE_PROD_ID12("IOTech Inc ", "PCMCIA Dual RS-232 Serial Port Card", 0x3bd2d898, 0x92abc92f),
770 PCMCIA_DEVICE_PROD_ID12("MACRONIX", "FAX/MODEM", 0x668388b3, 0x3f9bdf2f),
771 PCMCIA_DEVICE_PROD_ID12("Multi-Tech", "MT1432LT", 0x5f73be51, 0x0b3e2383),
772 PCMCIA_DEVICE_PROD_ID12("Multi-Tech", "MT2834LT", 0x5f73be51, 0x4cd7c09e),
773 PCMCIA_DEVICE_PROD_ID12("OEM ", "C288MX ", 0xb572d360, 0xd2385b7a),
774 PCMCIA_DEVICE_PROD_ID12("PCMCIA ", "C336MX ", 0x99bcafe9, 0xaa25bcab),
775 PCMCIA_DEVICE_PROD_ID12("Quatech Inc", "PCMCIA Dual RS-232 Serial Port Card", 0xc4420b35, 0x92abc92f),
776 PCMCIA_PFC_DEVICE_CIS_PROD_ID12(1, "PCMCIA", "EN2218-LAN/MODEM", 0x281f1c5d, 0x570f348e, "PCMLM28.cis"),
777 PCMCIA_PFC_DEVICE_CIS_PROD_ID12(1, "PCMCIA", "UE2218-LAN/MODEM", 0x281f1c5d, 0x6fdcacee, "PCMLM28.cis"),
778 PCMCIA_PFC_DEVICE_CIS_PROD_ID12(1, "Psion Dacom", "Gold Card V34 Ethernet", 0xf5f025c2, 0x338e8155, "PCMLM28.cis"),
779 PCMCIA_PFC_DEVICE_CIS_PROD_ID12(1, "Psion Dacom", "Gold Card V34 Ethernet GSM", 0xf5f025c2, 0x4ae85d35, "PCMLM28.cis"),
780 PCMCIA_PFC_DEVICE_CIS_PROD_ID12(1, "LINKSYS", "PCMLM28", 0xf7cb0b07, 0x66881874, "PCMLM28.cis"),
781 PCMCIA_MFC_DEVICE_CIS_PROD_ID12(1, "DAYNA COMMUNICATIONS", "LAN AND MODEM MULTIFUNCTION", 0x8fdf8f89, 0xdd5ed9e8, "DP83903.cis"),
782 PCMCIA_MFC_DEVICE_CIS_PROD_ID4(1, "NSC MF LAN/Modem", 0x58fc6056, "DP83903.cis"),
783 PCMCIA_MFC_DEVICE_CIS_MANF_CARD(1, 0x0101, 0x0556, "3CCFEM556.cis"),
784 PCMCIA_MFC_DEVICE_CIS_MANF_CARD(1, 0x0175, 0x0000, "DP83903.cis"),
785 PCMCIA_MFC_DEVICE_CIS_MANF_CARD(1, 0x0101, 0x0035, "3CXEM556.cis"),
786 PCMCIA_MFC_DEVICE_CIS_MANF_CARD(1, 0x0101, 0x003d, "3CXEM556.cis"),
Dominik Brodowskia42f0dc2005-09-24 23:12:44 -0700787 PCMCIA_DEVICE_CIS_MANF_CARD(0x0192, 0x0710, "SW_7xx_SER.cis"), /* Sierra Wireless AC710/AC750 GPRS Network Adapter R1 */
Dominik Brodowski325aa292005-06-27 16:28:18 -0700788 PCMCIA_DEVICE_CIS_PROD_ID12("MultiTech", "PCMCIA 56K DataFax", 0x842047ee, 0xc2efcf03, "MT5634ZLX.cis"),
789 PCMCIA_DEVICE_CIS_PROD_ID12("ADVANTECH", "COMpad-32/85B-4", 0x96913a85, 0xcec8f102, "COMpad4.cis"),
790 PCMCIA_DEVICE_CIS_PROD_ID123("ADVANTECH", "COMpad-32/85", "1.0", 0x96913a85, 0x8fbe92ae, 0x0877b627, "COMpad2.cis"),
791 PCMCIA_DEVICE_CIS_PROD_ID2("RS-COM 2P", 0xad20b156, "RS-COM-2P.cis"),
792 /* too generic */
793 /* PCMCIA_MFC_DEVICE_MANF_CARD(0, 0x0160, 0x0002), */
794 /* PCMCIA_MFC_DEVICE_MANF_CARD(1, 0x0160, 0x0002), */
795 PCMCIA_DEVICE_FUNC_ID(2),
796 PCMCIA_DEVICE_NULL,
797};
798MODULE_DEVICE_TABLE(pcmcia, serial_ids);
799
Linus Torvalds1da177e2005-04-16 15:20:36 -0700800static struct pcmcia_driver serial_cs_driver = {
801 .owner = THIS_MODULE,
802 .drv = {
803 .name = "serial_cs",
804 },
Dominik Brodowskif8cfa612005-11-14 21:25:51 +0100805 .probe = serial_probe,
Dominik Brodowskicc3b4862005-11-14 21:23:14 +0100806 .remove = serial_detach,
Dominik Brodowski325aa292005-06-27 16:28:18 -0700807 .id_table = serial_ids,
Dominik Brodowski98e4c282005-11-14 21:21:18 +0100808 .suspend = serial_suspend,
809 .resume = serial_resume,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700810};
811
812static int __init init_serial_cs(void)
813{
814 return pcmcia_register_driver(&serial_cs_driver);
815}
816
817static void __exit exit_serial_cs(void)
818{
819 pcmcia_unregister_driver(&serial_cs_driver);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700820}
821
822module_init(init_serial_cs);
823module_exit(exit_serial_cs);
824
825MODULE_LICENSE("GPL");