isicom.c 44.9 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
#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>

Alan Cox's avatar
Alan Cox committed
129
130
#include <linux/uaccess.h>
#include <linux/io.h>
Linus Torvalds's avatar
Linus Torvalds committed
131
132
133
134
135
136
#include <asm/system.h>

#include <linux/pci.h>

#include <linux/isicom.h>

137
138
139
#define InterruptTheCard(base) outw(0, (base) + 0xc)
#define ClearInterrupt(base) inw((base) + 0x0a)

Jiri Slaby's avatar
Jiri Slaby committed
140
#define pr_dbg(str...) pr_debug("ISICOM: " str)
141
142
143
144
145
146
#ifdef DEBUG
#define isicom_paranoia_check(a, b, c) __isicom_paranoia_check((a), (b), (c))
#else
#define isicom_paranoia_check(a, b, c) 0
#endif

147
148
149
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
150
static struct pci_device_id isicom_pci_tbl[] = {
151
152
153
154
155
156
157
158
159
	{ 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
160
161
162
163
	{ 0 }
};
MODULE_DEVICE_TABLE(pci, isicom_pci_tbl);

164
165
166
167
168
169
170
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
171
172
173
174
static int prev_card = 3;	/*	start servicing isi_card[0]	*/
static struct tty_driver *isicom_normal;

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

177
178
static DEFINE_TIMER(tx, isicom_tx, 0, 0);

Linus Torvalds's avatar
Linus Torvalds committed
179
180
181
/*   baud index mappings from linux defns to isi */

static signed char linuxb_to_isib[] = {
182
	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21
Linus Torvalds's avatar
Linus Torvalds committed
183
184
185
};

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

struct	isi_port {
	unsigned short		magic;
	unsigned int		flags;
	int			count;
	int			blocked_open;
	int			close_delay;
205
206
207
	u16			channel;
	u16			status;
	u16			closing_wait;
Alan Cox's avatar
Alan Cox committed
208
209
	struct isi_board	*card;
	struct tty_struct 	*tty;
Linus Torvalds's avatar
Linus Torvalds committed
210
211
	wait_queue_head_t	close_wait;
	wait_queue_head_t	open_wait;
Alan Cox's avatar
Alan Cox committed
212
	unsigned char		*xmit_buf;
Linus Torvalds's avatar
Linus Torvalds committed
213
214
215
216
217
218
219
220
221
222
223
224
225
	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.
 */
226

227
static inline int WaitTillCardIsFree(unsigned long base)
228
229
230
231
232
233
234
235
236
237
238
239
240
{
	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
241
242
static int lock_card(struct isi_board *card)
{
243
	unsigned long base = card->base;
Jiri Slaby's avatar
Jiri Slaby committed
244
	unsigned int retries, a;
Linus Torvalds's avatar
Linus Torvalds committed
245

Jiri Slaby's avatar
Jiri Slaby committed
246
	for (retries = 0; retries < 10; retries++) {
Linus Torvalds's avatar
Linus Torvalds committed
247
		spin_lock_irqsave(&card->card_lock, card->flags);
Jiri Slaby's avatar
Jiri Slaby committed
248
249
250
251
		for (a = 0; a < 10; a++) {
			if (inw(base + 0xe) & 0x1)
				return 1;
			udelay(10);
Linus Torvalds's avatar
Linus Torvalds committed
252
		}
Jiri Slaby's avatar
Jiri Slaby committed
253
254
		spin_unlock_irqrestore(&card->card_lock, card->flags);
		msleep(10);
Linus Torvalds's avatar
Linus Torvalds committed
255
	}
256
257
258
	printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%lx)\n",
		card->base);

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

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

/*
 *  ISI Card specific ops ...
 */
270

271
/* card->lock HAS to be held */
272
static void raise_dtr(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
273
{
274
	struct isi_board *card = port->card;
275
276
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
277

278
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
279
280
		return;

281
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
282
283
284
285
286
	outw(0x0504, base);
	InterruptTheCard(base);
	port->status |= ISI_DTR;
}

287
/* card->lock HAS to be held */
288
289
290
static inline void drop_dtr(struct isi_port *port)
{
	struct isi_board *card = port->card;
291
292
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
293

294
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
295
296
		return;

297
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
298
	outw(0x0404, base);
299
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
300
301
302
	port->status &= ~ISI_DTR;
}

303
/* card->lock HAS to be held */
304
static inline void raise_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
305
{
306
	struct isi_board *card = port->card;
307
308
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
309

310
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
311
312
		return;

313
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
314
	outw(0x0a04, base);
315
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
316
317
	port->status |= ISI_RTS;
}
318
319

/* card->lock HAS to be held */
320
static inline void drop_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
321
{
322
	struct isi_board *card = port->card;
323
324
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
325

326
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
327
328
		return;

329
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
330
	outw(0x0804, base);
331
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
332
333
334
	port->status &= ~ISI_RTS;
}

335
/* card->lock MUST NOT be held */
336
static inline void raise_dtr_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
337
{
338
	struct isi_board *card = port->card;
339
340
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
341
342
343
344

	if (!lock_card(card))
		return;

345
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
346
347
348
349
350
351
	outw(0x0f04, base);
	InterruptTheCard(base);
	port->status |= (ISI_DTR | ISI_RTS);
	unlock_card(card);
}

352
/* card->lock HAS to be held */
353
static void drop_dtr_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
354
{
355
	struct isi_board *card = port->card;
356
357
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
358

359
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
360
361
		return;

362
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
363
	outw(0x0c04, base);
364
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
365
366
367
368
369
370
371
	port->status &= ~(ISI_RTS | ISI_DTR);
}

/*
 *	ISICOM Driver specific routines ...
 *
 */
372

373
374
static inline int __isicom_paranoia_check(struct isi_port const *port,
	char *name, const char *routine)
Linus Torvalds's avatar
Linus Torvalds committed
375
376
{
	if (!port) {
377
378
		printk(KERN_WARNING "ISICOM: Warning: bad isicom magic for "
			"dev %s in %s.\n", name, routine);
Linus Torvalds's avatar
Linus Torvalds committed
379
380
381
		return 1;
	}
	if (port->magic != ISICOM_MAGIC) {
382
383
		printk(KERN_WARNING "ISICOM: Warning: NULL isicom port for "
			"dev %s in %s.\n", name, routine);
Linus Torvalds's avatar
Linus Torvalds committed
384
		return 1;
385
	}
386

Linus Torvalds's avatar
Linus Torvalds committed
387
388
	return 0;
}
389

Linus Torvalds's avatar
Linus Torvalds committed
390
/*
391
 *	Transmitter.
Linus Torvalds's avatar
Linus Torvalds committed
392
393
394
395
396
397
398
 *
 *	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)
{
399
	unsigned long flags, base;
Jiri Slaby's avatar
Jiri Slaby committed
400
	unsigned int retries;
401
	short count = (BOARD_COUNT-1), card;
Linus Torvalds's avatar
Linus Torvalds committed
402
	short txcount, wrd, residue, word_count, cnt;
403
404
405
	struct isi_port *port;
	struct tty_struct *tty;

Linus Torvalds's avatar
Linus Torvalds committed
406
407
	/*	find next active board	*/
	card = (prev_card + 1) & 0x0003;
Alan Cox's avatar
Alan Cox committed
408
	while (count-- > 0) {
409
		if (isi_card[card].status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
410
			break;
411
		card = (card + 1) & 0x0003;
Linus Torvalds's avatar
Linus Torvalds committed
412
413
414
	}
	if (!(isi_card[card].status & BOARD_ACTIVE))
		goto sched_again;
415

Linus Torvalds's avatar
Linus Torvalds committed
416
	prev_card = card;
417

Linus Torvalds's avatar
Linus Torvalds committed
418
419
420
	count = isi_card[card].port_count;
	port = isi_card[card].ports;
	base = isi_card[card].base;
Jiri Slaby's avatar
Jiri Slaby committed
421
422
423
424
425
426
427
428
429
430

	spin_lock_irqsave(&isi_card[card].card_lock, flags);
	for (retries = 0; retries < 100; retries++) {
		if (inw(base + 0xe) & 0x1)
			break;
		udelay(2);
	}
	if (retries >= 100)
		goto unlock;

Alan Cox's avatar
Alan Cox committed
431
	for (; count > 0; count--, port++) {
Linus Torvalds's avatar
Linus Torvalds committed
432
433
		/* port not active or tx disabled to force flow control */
		if (!(port->flags & ASYNC_INITIALIZED) ||
434
				!(port->status & ISI_TXOK))
Linus Torvalds's avatar
Linus Torvalds committed
435
			continue;
436

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

Jiri Slaby's avatar
Jiri Slaby committed
439
		if (tty == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
440
			continue;
441

Linus Torvalds's avatar
Linus Torvalds committed
442
		txcount = min_t(short, TX_SIZE, port->xmit_cnt);
Jiri Slaby's avatar
Jiri Slaby committed
443
		if (txcount <= 0 || tty->stopped || tty->hw_stopped)
Linus Torvalds's avatar
Linus Torvalds committed
444
			continue;
Jiri Slaby's avatar
Jiri Slaby committed
445
446

		if (!(inw(base + 0x02) & (1 << port->channel)))
447
			continue;
Jiri Slaby's avatar
Jiri Slaby committed
448

449
450
451
452
		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
453
		residue = NO;
454
		wrd = 0;
Linus Torvalds's avatar
Linus Torvalds committed
455
		while (1) {
456
457
			cnt = min_t(int, txcount, (SERIAL_XMIT_SIZE
					- port->xmit_tail));
Linus Torvalds's avatar
Linus Torvalds committed
458
459
460
			if (residue == YES) {
				residue = NO;
				if (cnt > 0) {
461
462
463
464
					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
465
466
467
					port->xmit_cnt--;
					txcount--;
					cnt--;
468
					outw(wrd, base);
469
				} else {
Linus Torvalds's avatar
Linus Torvalds committed
470
471
472
					outw(wrd, base);
					break;
				}
473
			}
Alan Cox's avatar
Alan Cox committed
474
475
			if (cnt <= 0)
				break;
Linus Torvalds's avatar
Linus Torvalds committed
476
			word_count = cnt >> 1;
Alan Cox's avatar
Alan Cox committed
477
			outsw(base, port->xmit_buf+port->xmit_tail, word_count);
478
479
			port->xmit_tail = (port->xmit_tail
				+ (word_count << 1)) & (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
480
481
482
483
484
			txcount -= (word_count << 1);
			port->xmit_cnt -= (word_count << 1);
			if (cnt & 0x0001) {
				residue = YES;
				wrd = port->xmit_buf[port->xmit_tail];
485
486
				port->xmit_tail = (port->xmit_tail + 1)
					& (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
487
488
489
490
491
492
493
494
495
				port->xmit_cnt--;
				txcount--;
			}
		}

		InterruptTheCard(base);
		if (port->xmit_cnt <= 0)
			port->status &= ~ISI_TXOK;
		if (port->xmit_cnt <= WAKEUP_CHARS)
496
			tty_wakeup(tty);
497
	}
Linus Torvalds's avatar
Linus Torvalds committed
498

Jiri Slaby's avatar
Jiri Slaby committed
499
500
unlock:
	spin_unlock_irqrestore(&isi_card[card].card_lock, flags);
501
502
	/*	schedule another tx for hopefully in about 10ms	*/
sched_again:
503
	mod_timer(&tx, jiffies + msecs_to_jiffies(10));
504
505
}

Linus Torvalds's avatar
Linus Torvalds committed
506
/*
507
 *	Main interrupt handler routine
Linus Torvalds's avatar
Linus Torvalds committed
508
 */
509

510
static irqreturn_t isicom_interrupt(int irq, void *dev_id)
Linus Torvalds's avatar
Linus Torvalds committed
511
{
512
	struct isi_board *card = dev_id;
513
514
	struct isi_port *port;
	struct tty_struct *tty;
515
516
	unsigned long base;
	u16 header, word_count, count, channel;
Linus Torvalds's avatar
Linus Torvalds committed
517
	short byte_count;
Alan Cox's avatar
Alan Cox committed
518
	unsigned char *rp;
519

Linus Torvalds's avatar
Linus Torvalds committed
520
521
	if (!card || !(card->status & FIRMWARE_LOADED))
		return IRQ_NONE;
522

Linus Torvalds's avatar
Linus Torvalds committed
523
	base = card->base;
524
525
526
527
528

	/* did the card interrupt us? */
	if (!(inw(base + 0x0e) & 0x02))
		return IRQ_NONE;

Linus Torvalds's avatar
Linus Torvalds committed
529
	spin_lock(&card->card_lock);
530

531
532
533
534
535
536
	/*
	 * disable any interrupts from the PCI card and lower the
	 * interrupt line
	 */
	outw(0x8000, base+0x04);
	ClearInterrupt(base);
537

Linus Torvalds's avatar
Linus Torvalds committed
538
539
540
541
542
543
	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) {
544
545
		printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%lx): "
			"%d(channel) > port_count.\n", base, channel+1);
546
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
547
		spin_unlock(&card->card_lock);
548
		return IRQ_HANDLED;
Linus Torvalds's avatar
Linus Torvalds committed
549
550
551
	}
	port = card->ports + channel;
	if (!(port->flags & ASYNC_INITIALIZED)) {
552
		outw(0x0000, base+0x04); /* enable interrupts */
553
		spin_unlock(&card->card_lock);
Linus Torvalds's avatar
Linus Torvalds committed
554
		return IRQ_HANDLED;
555
556
	}

Linus Torvalds's avatar
Linus Torvalds committed
557
558
559
	tty = port->tty;
	if (tty == NULL) {
		word_count = byte_count >> 1;
Alan Cox's avatar
Alan Cox committed
560
		while (byte_count > 1) {
Linus Torvalds's avatar
Linus Torvalds committed
561
562
563
564
565
			inw(base);
			byte_count -= 2;
		}
		if (byte_count & 0x01)
			inw(base);
566
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
567
568
569
		spin_unlock(&card->card_lock);
		return IRQ_HANDLED;
	}
570

Linus Torvalds's avatar
Linus Torvalds committed
571
572
	if (header & 0x8000) {		/* Status Packet */
		header = inw(base);
Alan Cox's avatar
Alan Cox committed
573
		switch (header & 0xff) {
574
575
576
577
578
		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  */
579
580
						pr_dbg("interrupt: DCD->low.\n"
							);
581
						port->status &= ~ISI_DCD;
582
						tty_hangup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
583
					}
584
585
586
587
588
				} 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
589
				}
590
			} else {
591
592
593
594
595
596
597
598
599
600
601
				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 */
602
603
						port->status |= (ISI_TXOK
							| ISI_CTS);
604
						tty_wakeup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
605
					}
606
607
608
609
				} 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
610
				}
611
			} else {
612
613
				if (header & ISI_CTS)
					port->status |= ISI_CTS;
Linus Torvalds's avatar
Linus Torvalds committed
614
				else
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
					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;

630
		case 1:	/* Received Break !!! */
631
632
633
634
635
636
637
			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		 */
638
			pr_dbg("isicom_interrupt: stats!!!.\n");
639
640
641
			break;

		default:
642
			pr_dbg("Intr: Unknown code in status packet.\n");
643
644
			break;
		}
645
	} else {				/* Data   Packet */
Alan Cox's avatar
Alan Cox committed
646
647

		count = tty_prepare_flip_string(tty, &rp, byte_count & ~1);
648
		pr_dbg("Intr: Can rx %d of %d bytes.\n", count, byte_count);
Linus Torvalds's avatar
Linus Torvalds committed
649
		word_count = count >> 1;
Alan Cox's avatar
Alan Cox committed
650
		insw(base, rp, word_count);
Linus Torvalds's avatar
Linus Torvalds committed
651
652
		byte_count -= (word_count << 1);
		if (count & 0x0001) {
653
654
			tty_insert_flip_char(tty,  inw(base) & 0xff,
				TTY_NORMAL);
Linus Torvalds's avatar
Linus Torvalds committed
655
			byte_count -= 2;
656
		}
Linus Torvalds's avatar
Linus Torvalds committed
657
		if (byte_count > 0) {
658
659
			pr_dbg("Intr(0x%lx:%d): Flip buffer overflow! dropping "
				"bytes...\n", base, channel + 1);
Alan Cox's avatar
Alan Cox committed
660
661
		/* drain out unread xtra data */
		while (byte_count > 0) {
Linus Torvalds's avatar
Linus Torvalds committed
662
663
664
665
				inw(base);
				byte_count -= 2;
			}
		}
Alan Cox's avatar
Alan Cox committed
666
		tty_flip_buffer_push(tty);
Linus Torvalds's avatar
Linus Torvalds committed
667
	}
668
	outw(0x0000, base+0x04); /* enable interrupts */
669
	spin_unlock(&card->card_lock);
670

Linus Torvalds's avatar
Linus Torvalds committed
671
	return IRQ_HANDLED;
672
}
Linus Torvalds's avatar
Linus Torvalds committed
673

674
static void isicom_config_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
675
{
676
677
	struct isi_board *card = port->card;
	struct tty_struct *tty;
Linus Torvalds's avatar
Linus Torvalds committed
678
	unsigned long baud;
679
680
681
	unsigned long base = card->base;
	u16 channel_setup, channel = port->channel,
		shift_count = card->shift_count;
Linus Torvalds's avatar
Linus Torvalds committed
682
	unsigned char flow_ctrl;
683

Alan Cox's avatar
Alan Cox committed
684
685
686
	tty = port->tty;

	if (tty == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
687
		return;
Alan Cox's avatar
Alan Cox committed
688
	/* FIXME: Switch to new tty baud API */
Linus Torvalds's avatar
Linus Torvalds committed
689
690
691
	baud = C_BAUD(tty);
	if (baud & CBAUDEX) {
		baud &= ~CBAUDEX;
692

Linus Torvalds's avatar
Linus Torvalds committed
693
694
695
		/*  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.
696
697
		 */

698
699
		/* 1,2,3,4 => 57.6, 115.2, 230, 460 kbps resp. */
		if (baud < 1 || baud > 4)
Linus Torvalds's avatar
Linus Torvalds committed
700
701
702
			port->tty->termios->c_cflag &= ~CBAUDEX;
		else
			baud += 15;
703
	}
Linus Torvalds's avatar
Linus Torvalds committed
704
	if (baud == 15) {
705
706

		/*  the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set
Linus Torvalds's avatar
Linus Torvalds committed
707
708
		 *  by the set_serial_info ioctl ... this is done by
		 *  the 'setserial' utility.
709
710
		 */

Linus Torvalds's avatar
Linus Torvalds committed
711
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
712
			baud++; /*  57.6 Kbps */
Linus Torvalds's avatar
Linus Torvalds committed
713
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
Alan Cox's avatar
Alan Cox committed
714
			baud += 2; /*  115  Kbps */
715
716
717
718
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
			baud += 3; /* 230 kbps*/
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
			baud += 4; /* 460 kbps*/
Linus Torvalds's avatar
Linus Torvalds committed
719
720
721
	}
	if (linuxb_to_isib[baud] == -1) {
		/* hang up */
722
723
		drop_dtr(port);
		return;
Alan Cox's avatar
Alan Cox committed
724
	} else
Linus Torvalds's avatar
Linus Torvalds committed
725
		raise_dtr(port);
726

727
	if (WaitTillCardIsFree(base) == 0) {
Alan Cox's avatar
Alan Cox committed
728
		outw(0x8000 | (channel << shift_count) | 0x03, base);
Linus Torvalds's avatar
Linus Torvalds committed
729
730
		outw(linuxb_to_isib[baud] << 8 | 0x03, base);
		channel_setup = 0;
Alan Cox's avatar
Alan Cox committed
731
		switch (C_CSIZE(tty)) {
732
733
734
735
736
737
738
739
740
741
742
743
		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
744
		}
745

Linus Torvalds's avatar
Linus Torvalds committed
746
747
748
749
750
		if (C_CSTOPB(tty))
			channel_setup |= ISICOM_2SB;
		if (C_PARENB(tty)) {
			channel_setup |= ISICOM_EVPAR;
			if (C_PARODD(tty))
751
				channel_setup |= ISICOM_ODPAR;
Linus Torvalds's avatar
Linus Torvalds committed
752
		}
753
		outw(channel_setup, base);
Linus Torvalds's avatar
Linus Torvalds committed
754
		InterruptTheCard(base);
755
	}
Linus Torvalds's avatar
Linus Torvalds committed
756
757
758
	if (C_CLOCAL(tty))
		port->flags &= ~ASYNC_CHECK_CD;
	else
759
760
		port->flags |= ASYNC_CHECK_CD;

Linus Torvalds's avatar
Linus Torvalds committed
761
762
763
764
765
766
	/* flow control settings ...*/
	flow_ctrl = 0;
	port->flags &= ~ASYNC_CTS_FLOW;
	if (C_CRTSCTS(tty)) {
		port->flags |= ASYNC_CTS_FLOW;
		flow_ctrl |= ISICOM_CTSRTS;
767
768
	}
	if (I_IXON(tty))
Linus Torvalds's avatar
Linus Torvalds committed
769
770
		flow_ctrl |= ISICOM_RESPOND_XONXOFF;
	if (I_IXOFF(tty))
771
772
		flow_ctrl |= ISICOM_INITIATE_XONXOFF;

773
	if (WaitTillCardIsFree(base) == 0) {
Alan Cox's avatar
Alan Cox committed
774
		outw(0x8000 | (channel << shift_count) | 0x04, base);
Linus Torvalds's avatar
Linus Torvalds committed
775
776
777
778
		outw(flow_ctrl << 8 | 0x05, base);
		outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
		InterruptTheCard(base);
	}
779

Linus Torvalds's avatar
Linus Torvalds committed
780
781
782
783
784
785
786
	/*	rx enabled -> enable port for rx on the card	*/
	if (C_CREAD(tty)) {
		card->port_status |= (1 << channel);
		outw(card->port_status, base + 0x02);
	}
}

787
788
789
/* open et all */

static inline void isicom_setup_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
790
791
{
	int channel;
792
	struct isi_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
793
	unsigned long flags;
794

Linus Torvalds's avatar
Linus Torvalds committed
795
796
797
798
799
800
801
	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;
802
	for (channel = 0; channel < bp->port_count; channel++, port++)
Linus Torvalds's avatar
Linus Torvalds committed
803
		drop_dtr_rts(port);
804
	spin_unlock_irqrestore(&bp->card_lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed
805
}
806
807

static int isicom_setup_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
808
{
809
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
810
	unsigned long flags;
811

Alan Cox's avatar
Alan Cox committed
812
	if (port->flags & ASYNC_INITIALIZED)
Linus Torvalds's avatar
Linus Torvalds committed
813
814
		return 0;
	if (!port->xmit_buf) {
Alan Cox's avatar
Alan Cox committed
815
		/* Relies on BKL */
Alan Cox's avatar
Alan Cox committed
816
817
		unsigned long page  = get_zeroed_page(GFP_KERNEL);
		if (page == 0)
Linus Torvalds's avatar
Linus Torvalds committed
818
			return -ENOMEM;
Alan Cox's avatar
Alan Cox committed
819
820
821
822
		if (port->xmit_buf)
			free_page(page);
		else
			port->xmit_buf = (unsigned char *) page;
823
	}
Linus Torvalds's avatar
Linus Torvalds committed
824
825
826
827
828
829

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

Linus Torvalds's avatar
Linus Torvalds committed
831
	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
832

Linus Torvalds's avatar
Linus Torvalds committed
833
	/*	discard any residual data	*/
834
835
836
837
838
839
	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);
	}
840

Linus Torvalds's avatar
Linus Torvalds committed
841
842
843
	isicom_config_port(port);
	port->flags |= ASYNC_INITIALIZED;
	spin_unlock_irqrestore(&card->card_lock, flags);
844
845
846
847

	return 0;
}

848
849
static int block_til_ready(struct tty_struct *tty, struct file *filp,
	struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
850
{
851
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
852
853
854
855
856
857
858
	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) {
859
		pr_dbg("block_til_ready: close in progress.\n");
Linus Torvalds's avatar
Linus Torvalds committed
860
861
862
863
864
865
		interruptible_sleep_on(&port->close_wait);
		if (port->flags & ASYNC_HUP_NOTIFY)
			return -EAGAIN;
		else
			return -ERESTARTSYS;
	}
866

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

869
870
	if ((filp->f_flags & O_NONBLOCK) ||
			(tty->flags & (1 << TTY_IO_ERROR))) {
871
		pr_dbg("block_til_ready: non-block mode.\n");
Linus Torvalds's avatar
Linus Torvalds committed
872
		port->flags |= ASYNC_NORMAL_ACTIVE;
873
874
875
		return 0;
	}

Linus Torvalds's avatar
Linus Torvalds committed
876
877
	if (C_CLOCAL(tty))
		do_clocal = 1;
878
879

	/* block waiting for DCD to be asserted, and while
Linus Torvalds's avatar
Linus Torvalds committed
880
881
882
883
884
885
886
887
888
						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);
889

Linus Torvalds's avatar
Linus Torvalds committed
890
891
892
893
	while (1) {
		raise_dtr_rts(port);

		set_current_state(TASK_INTERRUPTIBLE);
894
		if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
Linus Torvalds's avatar
Linus Torvalds committed
895
896
897
898
899
			if (port->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;
			break;
900
		}
Linus Torvalds's avatar
Linus Torvalds committed
901
		if (!(port->flags & ASYNC_CLOSING) &&
902
				(do_clocal || (port->status & ISI_DCD))) {
Linus Torvalds's avatar
Linus Torvalds committed
903
			break;
904
		}
Linus Torvalds's avatar
Linus Torvalds committed
905
906
907
908
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			break;
		}
909
		schedule();
Linus Torvalds's avatar
Linus Torvalds committed
910
911
912
913
914
915
916
917
918
919
920
921
922
	}
	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;
}
923
924

static int isicom_open(struct tty_struct *tty, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
925
{
926
927
	struct isi_port *port;
	struct isi_board *card;
928
929
	unsigned int board;
	int error, line;
Linus Torvalds's avatar
Linus Torvalds committed
930
931
932
933
934
935

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

Linus Torvalds's avatar
Linus Torvalds committed
937
938
	if (!(card->status & FIRMWARE_LOADED))
		return -ENODEV;
939

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

944
	port = &isi_ports[line];
Linus Torvalds's avatar
Linus Torvalds committed
945
946
	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
		return -ENODEV;
947
948
949

	isicom_setup_board(card);

Linus Torvalds's avatar
Linus Torvalds committed
950
951
952
	port->count++;
	tty->driver_data = port;
	port->tty = tty;
Alan Cox's avatar
Alan Cox committed
953
954
955
956
	error = isicom_setup_port(port);
	if (error == 0)
		error = block_til_ready(tty, filp, port);
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
957
}
958

Linus Torvalds's avatar
Linus Torvalds committed
959
960
/* close et all */

961
static inline void isicom_shutdown_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
962
{
Alan Cox's avatar
Alan Cox committed
963
	if (bp->status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
964
965
966
		bp->status &= ~BOARD_ACTIVE;
}

967
/* card->lock HAS to be held */
968
static void isicom_shutdown_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
969
{
970
971
972
	struct isi_board *card = port->card;
	struct tty_struct *tty;

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

975
	if (!(port->flags & ASYNC_INITIALIZED))
Linus Torvalds's avatar
Linus Torvalds committed
976
		return;
977

Linus Torvalds's avatar
Linus Torvalds committed
978
979
980
	if (port->xmit_buf) {
		free_page((unsigned long) port->xmit_buf);
		port->xmit_buf = NULL;
981
	}
Linus Torvalds's avatar
Linus Torvalds committed
982
983
984
	port->flags &= ~ASYNC_INITIALIZED;
	/* 3rd October 2000 : Vinayak P Risbud */
	port->tty = NULL;
985

Linus Torvalds's avatar
Linus Torvalds committed
986
987
988
	/*Fix done by Anil .S on 30-04-2001
	remote login through isi port has dtr toggle problem
	due to which the carrier drops before the password prompt
989
	appears on the remote end. Now we drop the dtr only if the
Linus Torvalds's avatar
Linus Torvalds committed
990
	HUPCL(Hangup on close) flag is set for the tty*/
991
992

	if (C_HUPCL(tty))
Linus Torvalds's avatar
Linus Torvalds committed
993
994
		/* drop dtr on this port */
		drop_dtr(port);
995
996

	/* any other port uninits  */
Linus Torvalds's avatar
Linus Torvalds committed
997
998
	if (tty)
		set_bit(TTY_IO_ERROR, &tty->flags);
999

Linus Torvalds's avatar
Linus Torvalds committed
1000
	if (--card->count < 0) {