isicom.c 46 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 *	Original driver code supplied by Multi-Tech
 *
 *	Changes
 *	1/9/98	alan@redhat.com		Merge to 2.0.x kernel tree
 *					Obtain and use official major/minors
 *					Loader switched to a misc device
 *					(fixed range check bug as a side effect)
 *					Printk clean up
 *	9/12/98	alan@redhat.com		Rough port to 2.1.x
 *
 *	10/6/99 sameer			Merged the ISA and PCI drivers to
 *					a new unified driver.
 *
 *	3/9/99	sameer			Added support for ISI4616 cards.
 *
 *	16/9/99	sameer			We do not force RTS low anymore.
23
 *					This is to prevent the firmware
Linus Torvalds's avatar
Linus Torvalds committed
24
25
26
27
28
29
30
31
32
33
 *					from getting confused.
 *
 *	26/10/99 sameer			Cosmetic changes:The driver now
 *					dumps the Port Count information
 *					along with I/O address and IRQ.
 *
 *	13/12/99 sameer			Fixed the problem with IRQ sharing.
 *
 *	10/5/00  sameer			Fixed isicom_shutdown_board()
 *					to not lower DTR on all the ports
34
 *					when the last port on the card is
Linus Torvalds's avatar
Linus Torvalds committed
35
36
37
 *					closed.
 *
 *	10/5/00  sameer			Signal mask setup command added
38
 *					to  isicom_setup_port and
Linus Torvalds's avatar
Linus Torvalds committed
39
40
41
 *					isicom_shutdown_port.
 *
 *	24/5/00  sameer			The driver is now SMP aware.
42
43
 *
 *
Linus Torvalds's avatar
Linus Torvalds committed
44
 *	27/11/00 Vinayak P Risbud	Fixed the Driver Crash Problem
45
46
 *
 *
Linus Torvalds's avatar
Linus Torvalds committed
47
48
49
50
51
52
 *	03/01/01  anil .s		Added support for resetting the
 *					internal modems on ISI cards.
 *
 *	08/02/01  anil .s		Upgraded the driver for kernel
 *					2.4.x
 *
53
 *	11/04/01  Kevin			Fixed firmware load problem with
Linus Torvalds's avatar
Linus Torvalds committed
54
 *					ISIHP-4X card
55
 *
Linus Torvalds's avatar
Linus Torvalds committed
56
57
58
59
60
61
62
63
64
 *	30/04/01  anil .s		Fixed the remote login through
 *					ISI port problem. Now the link
 *					does not go down before password
 *					prompt.
 *
 *	03/05/01  anil .s		Fixed the problem with IRQ sharing
 *					among ISI-PCI cards.
 *
 *	03/05/01  anil .s		Added support to display the version
65
 *					info during insmod as well as module
Linus Torvalds's avatar
Linus Torvalds committed
66
 *					listing by lsmod.
67
 *
Linus Torvalds's avatar
Linus Torvalds committed
68
69
70
71
72
73
74
75
 *	10/05/01  anil .s		Done the modifications to the source
 *					file and Install script so that the
 *					same installation can be used for
 *					2.2.x and 2.4.x kernel.
 *
 *	06/06/01  anil .s		Now we drop both dtr and rts during
 *					shutdown_port as well as raise them
 *					during isicom_config_port.
76
 *
Linus Torvalds's avatar
Linus Torvalds committed
77
78
79
80
81
 *	09/06/01 acme@conectiva.com.br	use capable, not suser, do
 *					restore_flags on failure in
 *					isicom_send_break, verify put_user
 *					result
 *
82
83
84
85
86
87
88
 *	11/02/03  ranjeeth		Added support for 230 Kbps and 460 Kbps
 *					Baud index extended to 21
 *
 *	20/03/03  ranjeeth		Made to work for Linux Advanced server.
 *					Taken care of license warning.
 *
 *	10/12/03  Ravindra		Made to work for Fedora Core 1 of
Linus Torvalds's avatar
Linus Torvalds committed
89
90
91
92
93
94
95
 *					Red Hat Distribution
 *
 *	06/01/05  Alan Cox 		Merged the ISI and base kernel strands
 *					into a single 2.6 driver
 *
 *	***********************************************************
 *
96
 *	To use this driver you also need the support package. You
Linus Torvalds's avatar
Linus Torvalds committed
97
98
 *	can find this in RPM format on
 *		ftp://ftp.linux.org.uk/pub/linux/alan
99
 *
Linus Torvalds's avatar
Linus Torvalds committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
 *	You can find the original tools for this direct from Multitech
 *		ftp://ftp.multitech.com/ISI-Cards/
 *
 *	Having installed the cards the module options (/etc/modprobe.conf)
 *
 *	options isicom   io=card1,card2,card3,card4 irq=card1,card2,card3,card4
 *
 *	Omit those entries for boards you don't have installed.
 *
 *	TODO
 *		Merge testing
 *		64-bit verification
 */

#include <linux/module.h>
115
#include <linux/firmware.h>
Linus Torvalds's avatar
Linus Torvalds committed
116
117
#include <linux/kernel.h>
#include <linux/tty.h>
Alan Cox's avatar
Alan Cox committed
118
#include <linux/tty_flip.h>
Linus Torvalds's avatar
Linus Torvalds committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <linux/termios.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/serial.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/ioport.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/system.h>

#include <linux/pci.h>

#include <linux/isicom.h>

137
138
139
140
141
142
143
144
145
146
147
#define InterruptTheCard(base) outw(0, (base) + 0xc)
#define ClearInterrupt(base) inw((base) + 0x0a)

#ifdef DEBUG
#define pr_dbg(str...) printk(KERN_DEBUG "ISICOM: " str)
#define isicom_paranoia_check(a, b, c) __isicom_paranoia_check((a), (b), (c))
#else
#define pr_dbg(str...) do { } while (0)
#define isicom_paranoia_check(a, b, c) 0
#endif

148
149
150
static int isicom_probe(struct pci_dev *, const struct pci_device_id *);
static void __devexit isicom_remove(struct pci_dev *);

Linus Torvalds's avatar
Linus Torvalds committed
151
static struct pci_device_id isicom_pci_tbl[] = {
152
153
154
155
156
157
158
159
160
	{ PCI_DEVICE(VENDOR_ID, 0x2028) },
	{ PCI_DEVICE(VENDOR_ID, 0x2051) },
	{ PCI_DEVICE(VENDOR_ID, 0x2052) },
	{ PCI_DEVICE(VENDOR_ID, 0x2053) },
	{ PCI_DEVICE(VENDOR_ID, 0x2054) },
	{ PCI_DEVICE(VENDOR_ID, 0x2055) },
	{ PCI_DEVICE(VENDOR_ID, 0x2056) },
	{ PCI_DEVICE(VENDOR_ID, 0x2057) },
	{ PCI_DEVICE(VENDOR_ID, 0x2058) },
Linus Torvalds's avatar
Linus Torvalds committed
161
162
163
164
	{ 0 }
};
MODULE_DEVICE_TABLE(pci, isicom_pci_tbl);

165
166
167
168
169
170
171
static struct pci_driver isicom_driver = {
	.name		= "isicom",
	.id_table	= isicom_pci_tbl,
	.probe		= isicom_probe,
	.remove		= __devexit_p(isicom_remove)
};

Linus Torvalds's avatar
Linus Torvalds committed
172
173
174
static int prev_card = 3;	/*	start servicing isi_card[0]	*/
static struct tty_driver *isicom_normal;

175
static DECLARE_COMPLETION(isi_timerdone);
Linus Torvalds's avatar
Linus Torvalds committed
176
177
178
static char re_schedule = 1;

static void isicom_tx(unsigned long _data);
179
static void isicom_start(struct tty_struct *tty);
Linus Torvalds's avatar
Linus Torvalds committed
180

181
182
static DEFINE_TIMER(tx, isicom_tx, 0, 0);

Linus Torvalds's avatar
Linus Torvalds committed
183
184
185
/*   baud index mappings from linux defns to isi */

static signed char linuxb_to_isib[] = {
186
	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16, 17, 18, 19
Linus Torvalds's avatar
Linus Torvalds committed
187
188
189
};

struct	isi_board {
190
	unsigned long		base;
Linus Torvalds's avatar
Linus Torvalds committed
191
192
193
	unsigned char		irq;
	unsigned char		port_count;
	unsigned short		status;
194
	unsigned short		port_status; /* each bit for each port */
Linus Torvalds's avatar
Linus Torvalds committed
195
196
197
198
199
	unsigned short		shift_count;
	struct isi_port		* ports;
	signed char		count;
	spinlock_t		card_lock; /* Card wide lock 11/5/00 -sameer */
	unsigned long		flags;
200
	unsigned int		index;
Linus Torvalds's avatar
Linus Torvalds committed
201
202
203
204
205
206
207
208
};

struct	isi_port {
	unsigned short		magic;
	unsigned int		flags;
	int			count;
	int			blocked_open;
	int			close_delay;
209
210
211
	u16			channel;
	u16			status;
	u16			closing_wait;
Linus Torvalds's avatar
Linus Torvalds committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
	struct isi_board	* card;
	struct tty_struct 	* tty;
	wait_queue_head_t	close_wait;
	wait_queue_head_t	open_wait;
	struct work_struct	hangup_tq;
	struct work_struct	bh_tqueue;
	unsigned char		* xmit_buf;
	int			xmit_head;
	int			xmit_tail;
	int			xmit_cnt;
};

static struct isi_board isi_card[BOARD_COUNT];
static struct isi_port  isi_ports[PORT_COUNT];

/*
 *	Locking functions for card level locking. We need to own both
 *	the kernel lock for the card and have the card in a position that
 *	it wants to talk.
 */
232

233
234
235
236
237
238
239
240
241
242
243
244
245
246
static inline int WaitTillCardIsFree(u16 base)
{
	unsigned int count = 0;
	unsigned int a = in_atomic(); /* do we run under spinlock? */

	while (!(inw(base + 0xe) & 0x1) && count++ < 100)
		if (a)
			mdelay(1);
		else
			msleep(1);

	return !(inw(base + 0xe) & 0x1);
}

Linus Torvalds's avatar
Linus Torvalds committed
247
248
249
static int lock_card(struct isi_board *card)
{
	char		retries;
250
	unsigned long base = card->base;
Linus Torvalds's avatar
Linus Torvalds committed
251
252
253
254

	for (retries = 0; retries < 100; retries++) {
		spin_lock_irqsave(&card->card_lock, card->flags);
		if (inw(base + 0xe) & 0x1) {
255
			return 1;
Linus Torvalds's avatar
Linus Torvalds committed
256
257
258
259
260
		} else {
			spin_unlock_irqrestore(&card->card_lock, card->flags);
			udelay(1000);   /* 1ms */
		}
	}
261
262
263
	printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%lx)\n",
		card->base);

Adrian Bunk's avatar
Adrian Bunk committed
264
	return 0;	/* Failed to acquire the card! */
Linus Torvalds's avatar
Linus Torvalds committed
265
266
267
268
269
}

static int lock_card_at_interrupt(struct isi_board *card)
{
	unsigned char		retries;
270
	unsigned long base = card->base;
Linus Torvalds's avatar
Linus Torvalds committed
271
272
273
274
275

	for (retries = 0; retries < 200; retries++) {
		spin_lock_irqsave(&card->card_lock, card->flags);

		if (inw(base + 0xe) & 0x1)
276
			return 1;
Linus Torvalds's avatar
Linus Torvalds committed
277
278
279
280
		else
			spin_unlock_irqrestore(&card->card_lock, card->flags);
	}
	/* Failing in interrupt is an acceptable event */
Adrian Bunk's avatar
Adrian Bunk committed
281
	return 0;	/* Failed to acquire the card! */
Linus Torvalds's avatar
Linus Torvalds committed
282
283
284
285
286
287
288
289
290
291
}

static void unlock_card(struct isi_board *card)
{
	spin_unlock_irqrestore(&card->card_lock, card->flags);
}

/*
 *  ISI Card specific ops ...
 */
292

293
/* card->lock HAS to be held */
294
static void raise_dtr(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
295
{
296
	struct isi_board *card = port->card;
297
298
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
299

300
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
301
302
		return;

303
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
304
305
306
307
308
	outw(0x0504, base);
	InterruptTheCard(base);
	port->status |= ISI_DTR;
}

309
/* card->lock HAS to be held */
310
311
312
static inline void drop_dtr(struct isi_port *port)
{
	struct isi_board *card = port->card;
313
314
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
315

316
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
317
318
		return;

319
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
320
	outw(0x0404, base);
321
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
322
323
324
	port->status &= ~ISI_DTR;
}

325
/* card->lock HAS to be held */
326
static inline void raise_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
327
{
328
	struct isi_board *card = port->card;
329
330
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
331

332
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
333
334
		return;

335
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
336
	outw(0x0a04, base);
337
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
338
339
	port->status |= ISI_RTS;
}
340
341

/* card->lock HAS to be held */
342
static inline void drop_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
343
{
344
	struct isi_board *card = port->card;
345
346
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
347

348
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
349
350
		return;

351
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
352
	outw(0x0804, base);
353
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
354
355
356
	port->status &= ~ISI_RTS;
}

357
/* card->lock MUST NOT be held */
358
static inline void raise_dtr_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
359
{
360
	struct isi_board *card = port->card;
361
362
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
363
364
365
366

	if (!lock_card(card))
		return;

367
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
368
369
370
371
372
373
	outw(0x0f04, base);
	InterruptTheCard(base);
	port->status |= (ISI_DTR | ISI_RTS);
	unlock_card(card);
}

374
/* card->lock HAS to be held */
375
static void drop_dtr_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
376
{
377
	struct isi_board *card = port->card;
378
379
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
380

381
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
382
383
		return;

384
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
385
	outw(0x0c04, base);
386
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
387
388
389
390
391
392
393
	port->status &= ~(ISI_RTS | ISI_DTR);
}

/*
 *	ISICOM Driver specific routines ...
 *
 */
394

395
396
static inline int __isicom_paranoia_check(struct isi_port const *port,
	char *name, const char *routine)
Linus Torvalds's avatar
Linus Torvalds committed
397
398
{
	if (!port) {
399
400
		printk(KERN_WARNING "ISICOM: Warning: bad isicom magic for "
			"dev %s in %s.\n", name, routine);
Linus Torvalds's avatar
Linus Torvalds committed
401
402
403
		return 1;
	}
	if (port->magic != ISICOM_MAGIC) {
404
405
		printk(KERN_WARNING "ISICOM: Warning: NULL isicom port for "
			"dev %s in %s.\n", name, routine);
Linus Torvalds's avatar
Linus Torvalds committed
406
		return 1;
407
	}
408

Linus Torvalds's avatar
Linus Torvalds committed
409
410
	return 0;
}
411

Linus Torvalds's avatar
Linus Torvalds committed
412
/*
413
 *	Transmitter.
Linus Torvalds's avatar
Linus Torvalds committed
414
415
416
417
418
419
420
421
422
 *
 *	We shovel data into the card buffers on a regular basis. The card
 *	will do the rest of the work for us.
 */

static void isicom_tx(unsigned long _data)
{
	short count = (BOARD_COUNT-1), card, base;
	short txcount, wrd, residue, word_count, cnt;
423
424
425
	struct isi_port *port;
	struct tty_struct *tty;

Linus Torvalds's avatar
Linus Torvalds committed
426
427
428
	/*	find next active board	*/
	card = (prev_card + 1) & 0x0003;
	while(count-- > 0) {
429
		if (isi_card[card].status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
430
			break;
431
		card = (card + 1) & 0x0003;
Linus Torvalds's avatar
Linus Torvalds committed
432
433
434
	}
	if (!(isi_card[card].status & BOARD_ACTIVE))
		goto sched_again;
435

Linus Torvalds's avatar
Linus Torvalds committed
436
	prev_card = card;
437

Linus Torvalds's avatar
Linus Torvalds committed
438
439
440
441
442
443
444
445
	count = isi_card[card].port_count;
	port = isi_card[card].ports;
	base = isi_card[card].base;
	for (;count > 0;count--, port++) {
		if (!lock_card_at_interrupt(&isi_card[card]))
			continue;
		/* port not active or tx disabled to force flow control */
		if (!(port->flags & ASYNC_INITIALIZED) ||
446
				!(port->status & ISI_TXOK))
Linus Torvalds's avatar
Linus Torvalds committed
447
448
			unlock_card(&isi_card[card]);
			continue;
449

Linus Torvalds's avatar
Linus Torvalds committed
450
		tty = port->tty;
451
452
453


		if (tty == NULL) {
Linus Torvalds's avatar
Linus Torvalds committed
454
455
456
			unlock_card(&isi_card[card]);
			continue;
		}
457

Linus Torvalds's avatar
Linus Torvalds committed
458
459
460
461
462
463
464
		txcount = min_t(short, TX_SIZE, port->xmit_cnt);
		if (txcount <= 0 || tty->stopped || tty->hw_stopped) {
			unlock_card(&isi_card[card]);
			continue;
		}
		if (!(inw(base + 0x02) & (1 << port->channel))) {
			unlock_card(&isi_card[card]);
465
			continue;
Linus Torvalds's avatar
Linus Torvalds committed
466
		}
467
468
469
470
		pr_dbg("txing %d bytes, port%d.\n", txcount,
			port->channel + 1);
		outw((port->channel << isi_card[card].shift_count) | txcount,
			base);
Linus Torvalds's avatar
Linus Torvalds committed
471
		residue = NO;
472
		wrd = 0;
Linus Torvalds's avatar
Linus Torvalds committed
473
		while (1) {
474
475
			cnt = min_t(int, txcount, (SERIAL_XMIT_SIZE
					- port->xmit_tail));
Linus Torvalds's avatar
Linus Torvalds committed
476
477
478
			if (residue == YES) {
				residue = NO;
				if (cnt > 0) {
479
480
481
482
					wrd |= (port->xmit_buf[port->xmit_tail]
									<< 8);
					port->xmit_tail = (port->xmit_tail + 1)
						& (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
483
484
485
					port->xmit_cnt--;
					txcount--;
					cnt--;
486
					outw(wrd, base);
487
				} else {
Linus Torvalds's avatar
Linus Torvalds committed
488
489
490
					outw(wrd, base);
					break;
				}
491
			}
Linus Torvalds's avatar
Linus Torvalds committed
492
493
			if (cnt <= 0) break;
			word_count = cnt >> 1;
494
495
496
			outsw(base, port->xmit_buf+port->xmit_tail,word_count);
			port->xmit_tail = (port->xmit_tail
				+ (word_count << 1)) & (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
497
498
499
500
501
			txcount -= (word_count << 1);
			port->xmit_cnt -= (word_count << 1);
			if (cnt & 0x0001) {
				residue = YES;
				wrd = port->xmit_buf[port->xmit_tail];
502
503
				port->xmit_tail = (port->xmit_tail + 1)
					& (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
504
505
506
507
508
509
510
511
512
513
514
				port->xmit_cnt--;
				txcount--;
			}
		}

		InterruptTheCard(base);
		if (port->xmit_cnt <= 0)
			port->status &= ~ISI_TXOK;
		if (port->xmit_cnt <= WAKEUP_CHARS)
			schedule_work(&port->bh_tqueue);
		unlock_card(&isi_card[card]);
515
	}
Linus Torvalds's avatar
Linus Torvalds committed
516

517
518
	/*	schedule another tx for hopefully in about 10ms	*/
sched_again:
519
	if (!re_schedule) {
520
		complete(&isi_timerdone);
521
522
523
 		return;
	}

524
	mod_timer(&tx, jiffies + msecs_to_jiffies(10));
525
526
}

Linus Torvalds's avatar
Linus Torvalds committed
527
528
/* 	Interrupt handlers 	*/

529

David Howells's avatar
David Howells committed
530
static void isicom_bottomhalf(struct work_struct *work)
Linus Torvalds's avatar
Linus Torvalds committed
531
{
David Howells's avatar
David Howells committed
532
	struct isi_port *port = container_of(work, struct isi_port, bh_tqueue);
533
534
	struct tty_struct *tty = port->tty;

Linus Torvalds's avatar
Linus Torvalds committed
535
536
537
	if (!tty)
		return;

538
	tty_wakeup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
539
	wake_up_interruptible(&tty->write_wait);
540
541
}

Linus Torvalds's avatar
Linus Torvalds committed
542
/*
543
 *	Main interrupt handler routine
Linus Torvalds's avatar
Linus Torvalds committed
544
 */
545

546
static irqreturn_t isicom_interrupt(int irq, void *dev_id)
Linus Torvalds's avatar
Linus Torvalds committed
547
{
548
	struct isi_board *card = dev_id;
549
550
	struct isi_port *port;
	struct tty_struct *tty;
551
552
	unsigned long base;
	u16 header, word_count, count, channel;
Linus Torvalds's avatar
Linus Torvalds committed
553
	short byte_count;
Alan Cox's avatar
Alan Cox committed
554
	unsigned char *rp;
555

Linus Torvalds's avatar
Linus Torvalds committed
556
557
	if (!card || !(card->status & FIRMWARE_LOADED))
		return IRQ_NONE;
558

Linus Torvalds's avatar
Linus Torvalds committed
559
560
	base = card->base;
	spin_lock(&card->card_lock);
561

562
563
564
565
566
567
	/*
	 * disable any interrupts from the PCI card and lower the
	 * interrupt line
	 */
	outw(0x8000, base+0x04);
	ClearInterrupt(base);
568

Linus Torvalds's avatar
Linus Torvalds committed
569
570
571
572
573
574
	inw(base);		/* get the dummy word out */
	header = inw(base);
	channel = (header & 0x7800) >> card->shift_count;
	byte_count = header & 0xff;

	if (channel + 1 > card->port_count) {
575
576
		printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%lx): "
			"%d(channel) > port_count.\n", base, channel+1);
577
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
578
		spin_unlock(&card->card_lock);
579
		return IRQ_HANDLED;
Linus Torvalds's avatar
Linus Torvalds committed
580
581
582
	}
	port = card->ports + channel;
	if (!(port->flags & ASYNC_INITIALIZED)) {
583
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
584
		return IRQ_HANDLED;
585
586
	}

Linus Torvalds's avatar
Linus Torvalds committed
587
588
589
590
591
592
593
594
595
	tty = port->tty;
	if (tty == NULL) {
		word_count = byte_count >> 1;
		while(byte_count > 1) {
			inw(base);
			byte_count -= 2;
		}
		if (byte_count & 0x01)
			inw(base);
596
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
597
598
599
		spin_unlock(&card->card_lock);
		return IRQ_HANDLED;
	}
600

Linus Torvalds's avatar
Linus Torvalds committed
601
602
603
	if (header & 0x8000) {		/* Status Packet */
		header = inw(base);
		switch(header & 0xff) {
604
605
606
607
608
		case 0:	/* Change in EIA signals */
			if (port->flags & ASYNC_CHECK_CD) {
				if (port->status & ISI_DCD) {
					if (!(header & ISI_DCD)) {
					/* Carrier has been lost  */
609
610
						pr_dbg("interrupt: DCD->low.\n"
							);
611
612
						port->status &= ~ISI_DCD;
						schedule_work(&port->hangup_tq);
Linus Torvalds's avatar
Linus Torvalds committed
613
					}
614
615
616
617
618
				} else if (header & ISI_DCD) {
				/* Carrier has been detected */
					pr_dbg("interrupt: DCD->high.\n");
					port->status |= ISI_DCD;
					wake_up_interruptible(&port->open_wait);
Linus Torvalds's avatar
Linus Torvalds committed
619
				}
620
			} else {
621
622
623
624
625
626
627
628
629
630
631
				if (header & ISI_DCD)
					port->status |= ISI_DCD;
				else
					port->status &= ~ISI_DCD;
			}

			if (port->flags & ASYNC_CTS_FLOW) {
				if (port->tty->hw_stopped) {
					if (header & ISI_CTS) {
						port->tty->hw_stopped = 0;
						/* start tx ing */
632
633
						port->status |= (ISI_TXOK
							| ISI_CTS);
634
						schedule_work(&port->bh_tqueue);
Linus Torvalds's avatar
Linus Torvalds committed
635
					}
636
637
638
639
				} else if (!(header & ISI_CTS)) {
					port->tty->hw_stopped = 1;
					/* stop tx ing */
					port->status &= ~(ISI_TXOK | ISI_CTS);
Linus Torvalds's avatar
Linus Torvalds committed
640
				}
641
			} else {
642
643
				if (header & ISI_CTS)
					port->status |= ISI_CTS;
Linus Torvalds's avatar
Linus Torvalds committed
644
				else
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
					port->status &= ~ISI_CTS;
			}

			if (header & ISI_DSR)
				port->status |= ISI_DSR;
			else
				port->status &= ~ISI_DSR;

			if (header & ISI_RI)
				port->status |= ISI_RI;
			else
				port->status &= ~ISI_RI;

			break;

660
		case 1:	/* Received Break !!! */
661
662
663
664
665
666
667
			tty_insert_flip_char(tty, 0, TTY_BREAK);
			if (port->flags & ASYNC_SAK)
				do_SAK(tty);
			tty_flip_buffer_push(tty);
			break;

		case 2:	/* Statistics		 */
668
			pr_dbg("isicom_interrupt: stats!!!.\n");
669
670
671
			break;

		default:
672
			pr_dbg("Intr: Unknown code in status packet.\n");
673
674
			break;
		}
675
	} else {				/* Data   Packet */
Alan Cox's avatar
Alan Cox committed
676
677

		count = tty_prepare_flip_string(tty, &rp, byte_count & ~1);
678
		pr_dbg("Intr: Can rx %d of %d bytes.\n", count, byte_count);
Linus Torvalds's avatar
Linus Torvalds committed
679
		word_count = count >> 1;
Alan Cox's avatar
Alan Cox committed
680
		insw(base, rp, word_count);
Linus Torvalds's avatar
Linus Torvalds committed
681
682
		byte_count -= (word_count << 1);
		if (count & 0x0001) {
683
684
			tty_insert_flip_char(tty,  inw(base) & 0xff,
				TTY_NORMAL);
Linus Torvalds's avatar
Linus Torvalds committed
685
			byte_count -= 2;
686
		}
Linus Torvalds's avatar
Linus Torvalds committed
687
		if (byte_count > 0) {
688
689
			pr_dbg("Intr(0x%lx:%d): Flip buffer overflow! dropping "
				"bytes...\n", base, channel + 1);
Linus Torvalds's avatar
Linus Torvalds committed
690
691
692
693
694
			while(byte_count > 0) { /* drain out unread xtra data */
				inw(base);
				byte_count -= 2;
			}
		}
Alan Cox's avatar
Alan Cox committed
695
		tty_flip_buffer_push(tty);
Linus Torvalds's avatar
Linus Torvalds committed
696
	}
697
	outw(0x0000, base+0x04); /* enable interrupts */
698

Linus Torvalds's avatar
Linus Torvalds committed
699
	return IRQ_HANDLED;
700
}
Linus Torvalds's avatar
Linus Torvalds committed
701

702
static void isicom_config_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
703
{
704
705
	struct isi_board *card = port->card;
	struct tty_struct *tty;
Linus Torvalds's avatar
Linus Torvalds committed
706
	unsigned long baud;
707
708
709
	unsigned long base = card->base;
	u16 channel_setup, channel = port->channel,
		shift_count = card->shift_count;
Linus Torvalds's avatar
Linus Torvalds committed
710
	unsigned char flow_ctrl;
711

Linus Torvalds's avatar
Linus Torvalds committed
712
713
714
715
716
	if (!(tty = port->tty) || !tty->termios)
		return;
	baud = C_BAUD(tty);
	if (baud & CBAUDEX) {
		baud &= ~CBAUDEX;
717

Linus Torvalds's avatar
Linus Torvalds committed
718
719
720
		/*  if CBAUDEX bit is on and the baud is set to either 50 or 75
		 *  then the card is programmed for 57.6Kbps or 115Kbps
		 *  respectively.
721
722
		 */

Linus Torvalds's avatar
Linus Torvalds committed
723
724
725
726
		if (baud < 1 || baud > 2)
			port->tty->termios->c_cflag &= ~CBAUDEX;
		else
			baud += 15;
727
	}
Linus Torvalds's avatar
Linus Torvalds committed
728
	if (baud == 15) {
729
730

		/*  the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set
Linus Torvalds's avatar
Linus Torvalds committed
731
732
		 *  by the set_serial_info ioctl ... this is done by
		 *  the 'setserial' utility.
733
734
		 */

Linus Torvalds's avatar
Linus Torvalds committed
735
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
736
			baud++; /*  57.6 Kbps */
Linus Torvalds's avatar
Linus Torvalds committed
737
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
738
			baud +=2; /*  115  Kbps */
Linus Torvalds's avatar
Linus Torvalds committed
739
740
741
	}
	if (linuxb_to_isib[baud] == -1) {
		/* hang up */
742
743
744
745
		drop_dtr(port);
		return;
	}
	else
Linus Torvalds's avatar
Linus Torvalds committed
746
		raise_dtr(port);
747

748
	if (WaitTillCardIsFree(base) == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
749
750
751
752
		outw(0x8000 | (channel << shift_count) |0x03, base);
		outw(linuxb_to_isib[baud] << 8 | 0x03, base);
		channel_setup = 0;
		switch(C_CSIZE(tty)) {
753
754
755
756
757
758
759
760
761
762
763
764
		case CS5:
			channel_setup |= ISICOM_CS5;
			break;
		case CS6:
			channel_setup |= ISICOM_CS6;
			break;
		case CS7:
			channel_setup |= ISICOM_CS7;
			break;
		case CS8:
			channel_setup |= ISICOM_CS8;
			break;
Linus Torvalds's avatar
Linus Torvalds committed
765
		}
766

Linus Torvalds's avatar
Linus Torvalds committed
767
768
769
770
771
		if (C_CSTOPB(tty))
			channel_setup |= ISICOM_2SB;
		if (C_PARENB(tty)) {
			channel_setup |= ISICOM_EVPAR;
			if (C_PARODD(tty))
772
				channel_setup |= ISICOM_ODPAR;
Linus Torvalds's avatar
Linus Torvalds committed
773
		}
774
		outw(channel_setup, base);
Linus Torvalds's avatar
Linus Torvalds committed
775
		InterruptTheCard(base);
776
	}
Linus Torvalds's avatar
Linus Torvalds committed
777
778
779
	if (C_CLOCAL(tty))
		port->flags &= ~ASYNC_CHECK_CD;
	else
780
781
		port->flags |= ASYNC_CHECK_CD;

Linus Torvalds's avatar
Linus Torvalds committed
782
783
784
785
786
787
	/* flow control settings ...*/
	flow_ctrl = 0;
	port->flags &= ~ASYNC_CTS_FLOW;
	if (C_CRTSCTS(tty)) {
		port->flags |= ASYNC_CTS_FLOW;
		flow_ctrl |= ISICOM_CTSRTS;
788
789
	}
	if (I_IXON(tty))
Linus Torvalds's avatar
Linus Torvalds committed
790
791
		flow_ctrl |= ISICOM_RESPOND_XONXOFF;
	if (I_IXOFF(tty))
792
793
		flow_ctrl |= ISICOM_INITIATE_XONXOFF;

794
	if (WaitTillCardIsFree(base) == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
795
796
797
798
799
		outw(0x8000 | (channel << shift_count) |0x04, base);
		outw(flow_ctrl << 8 | 0x05, base);
		outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
		InterruptTheCard(base);
	}
800

Linus Torvalds's avatar
Linus Torvalds committed
801
802
803
804
805
806
807
	/*	rx enabled -> enable port for rx on the card	*/
	if (C_CREAD(tty)) {
		card->port_status |= (1 << channel);
		outw(card->port_status, base + 0x02);
	}
}

808
809
810
/* open et all */

static inline void isicom_setup_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
811
812
{
	int channel;
813
	struct isi_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
814
	unsigned long flags;
815

Linus Torvalds's avatar
Linus Torvalds committed
816
817
818
819
820
821
822
	spin_lock_irqsave(&bp->card_lock, flags);
	if (bp->status & BOARD_ACTIVE) {
		spin_unlock_irqrestore(&bp->card_lock, flags);
		return;
	}
	port = bp->ports;
	bp->status |= BOARD_ACTIVE;
823
	for (channel = 0; channel < bp->port_count; channel++, port++)
Linus Torvalds's avatar
Linus Torvalds committed
824
		drop_dtr_rts(port);
825
	spin_unlock_irqrestore(&bp->card_lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed
826
}
827
828

static int isicom_setup_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
829
{
830
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
831
	unsigned long flags;
832

Linus Torvalds's avatar
Linus Torvalds committed
833
834
835
836
837
	if (port->flags & ASYNC_INITIALIZED) {
		return 0;
	}
	if (!port->xmit_buf) {
		unsigned long page;
838

Linus Torvalds's avatar
Linus Torvalds committed
839
840
		if (!(page = get_zeroed_page(GFP_KERNEL)))
			return -ENOMEM;
841

Linus Torvalds's avatar
Linus Torvalds committed
842
843
844
845
		if (port->xmit_buf) {
			free_page(page);
			return -ERESTARTSYS;
		}
846
847
		port->xmit_buf = (unsigned char *) page;
	}
Linus Torvalds's avatar
Linus Torvalds committed
848
849
850
851
852
853

	spin_lock_irqsave(&card->card_lock, flags);
	if (port->tty)
		clear_bit(TTY_IO_ERROR, &port->tty->flags);
	if (port->count == 1)
		card->count++;
854

Linus Torvalds's avatar
Linus Torvalds committed
855
	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
856

Linus Torvalds's avatar
Linus Torvalds committed
857
	/*	discard any residual data	*/
858
859
860
861
862
863
	if (WaitTillCardIsFree(card->base) == 0) {
		outw(0x8000 | (port->channel << card->shift_count) | 0x02,
				card->base);
		outw(((ISICOM_KILLTX | ISICOM_KILLRX) << 8) | 0x06, card->base);
		InterruptTheCard(card->base);
	}
864

Linus Torvalds's avatar
Linus Torvalds committed
865
866
867
	isicom_config_port(port);
	port->flags |= ASYNC_INITIALIZED;
	spin_unlock_irqrestore(&card->card_lock, flags);
868
869
870
871

	return 0;
}

872
873
static int block_til_ready(struct tty_struct *tty, struct file *filp,
	struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
874
{
875
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
876
877
878
879
880
881
882
	int do_clocal = 0, retval;
	unsigned long flags;
	DECLARE_WAITQUEUE(wait, current);

	/* block if port is in the process of being closed */

	if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
883
		pr_dbg("block_til_ready: close in progress.\n");
Linus Torvalds's avatar
Linus Torvalds committed
884
885
886
887
888
889
		interruptible_sleep_on(&port->close_wait);
		if (port->flags & ASYNC_HUP_NOTIFY)
			return -EAGAIN;
		else
			return -ERESTARTSYS;
	}
890

Linus Torvalds's avatar
Linus Torvalds committed
891
	/* if non-blocking mode is set ... */
892

893
894
	if ((filp->f_flags & O_NONBLOCK) ||
			(tty->flags & (1 << TTY_IO_ERROR))) {
895
		pr_dbg("block_til_ready: non-block mode.\n");
Linus Torvalds's avatar
Linus Torvalds committed
896
		port->flags |= ASYNC_NORMAL_ACTIVE;
897
898
899
		return 0;
	}

Linus Torvalds's avatar
Linus Torvalds committed
900
901
	if (C_CLOCAL(tty))
		do_clocal = 1;
902
903

	/* block waiting for DCD to be asserted, and while
Linus Torvalds's avatar
Linus Torvalds committed
904
905
906
907
908
909
910
911
912
						callout dev is busy */
	retval = 0;
	add_wait_queue(&port->open_wait, &wait);

	spin_lock_irqsave(&card->card_lock, flags);
	if (!tty_hung_up_p(filp))
		port->count--;
	port->blocked_open++;
	spin_unlock_irqrestore(&card->card_lock, flags);
913

Linus Torvalds's avatar
Linus Torvalds committed
914
915
916
917
	while (1) {
		raise_dtr_rts(port);

		set_current_state(TASK_INTERRUPTIBLE);
918
		if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
Linus Torvalds's avatar
Linus Torvalds committed
919
920
921
922
923
			if (port->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;
			break;
924
		}
Linus Torvalds's avatar
Linus Torvalds committed
925
		if (!(port->flags & ASYNC_CLOSING) &&
926
				(do_clocal || (port->status & ISI_DCD))) {
Linus Torvalds's avatar
Linus Torvalds committed
927
			break;
928
		}
Linus Torvalds's avatar
Linus Torvalds committed
929
930
931
932
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			break;
		}
933
		schedule();
Linus Torvalds's avatar
Linus Torvalds committed
934
935
936
937
938
939
940
941
942
943
944
945
946
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&port->open_wait, &wait);
	spin_lock_irqsave(&card->card_lock, flags);
	if (!tty_hung_up_p(filp))
		port->count++;
	port->blocked_open--;
	spin_unlock_irqrestore(&card->card_lock, flags);
	if (retval)
		return retval;
	port->flags |= ASYNC_NORMAL_ACTIVE;
	return 0;
}
947
948

static int isicom_open(struct tty_struct *tty, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
949
{
950
951
	struct isi_port *port;
	struct isi_board *card;
952
953
	unsigned int board;
	int error, line;
Linus Torvalds's avatar
Linus Torvalds committed
954
955
956
957
958
959

	line = tty->index;
	if (line < 0 || line > PORT_COUNT-1)
		return -ENODEV;
	board = BOARD(line);
	card = &isi_card[board];
960

Linus Torvalds's avatar
Linus Torvalds committed
961
962
	if (!(card->status & FIRMWARE_LOADED))
		return -ENODEV;
963

Linus Torvalds's avatar
Linus Torvalds committed
964
965
966
967
	/*  open on a port greater than the port count for the card !!! */
	if (line > ((board * 16) + card->port_count - 1))
		return -ENODEV;

968
	port = &isi_ports[line];
Linus Torvalds's avatar
Linus Torvalds committed
969
970
	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
		return -ENODEV;
971
972
973

	isicom_setup_board(card);

Linus Torvalds's avatar
Linus Torvalds committed
974
975
976
977
978
979
980
981
	port->count++;
	tty->driver_data = port;
	port->tty = tty;
	if ((error = isicom_setup_port(port))!=0)
		return error;
	if ((error = block_til_ready(tty, filp, port))!=0)
		return error;

982
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
983
}
984

Linus Torvalds's avatar
Linus Torvalds committed
985
986
/* close et all */

987
static inline void isicom_shutdown_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
988
989
990
991
992
993
{
	if (bp->status & BOARD_ACTIVE) {
		bp->status &= ~BOARD_ACTIVE;
	}
}

994
/* card->lock HAS to be held */
995
static void isicom_shutdown_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
996
{
997
998
999
	struct isi_board *card = port->card;
	struct tty_struct *tty;

Linus Torvalds's avatar
Linus Torvalds committed
1000
	tty = port->tty;