isicom.c 45.1 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
#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
192
193
194
195
	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;
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;
Linus Torvalds's avatar
Linus Torvalds committed
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
	struct isi_board	* card;
	struct tty_struct 	* tty;
	wait_queue_head_t	close_wait;
	wait_queue_head_t	open_wait;
	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.
 */
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
408
	/*	find next active board	*/
	card = (prev_card + 1) & 0x0003;
	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;

Linus Torvalds's avatar
Linus Torvalds committed
431
432
433
	for (;count > 0;count--, port++) {
		/* 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
			}
Linus Torvalds's avatar
Linus Torvalds committed
474
475
			if (cnt <= 0) break;
			word_count = cnt >> 1;
476
477
478
			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
479
480
481
482
483
			txcount -= (word_count << 1);
			port->xmit_cnt -= (word_count << 1);
			if (cnt & 0x0001) {
				residue = YES;
				wrd = port->xmit_buf[port->xmit_tail];
484
485
				port->xmit_tail = (port->xmit_tail + 1)
					& (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
486
487
488
489
490
491
492
493
494
				port->xmit_cnt--;
				txcount--;
			}
		}

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

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

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

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

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

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

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

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

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

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

Linus Torvalds's avatar
Linus Torvalds committed
556
557
558
559
560
561
562
563
564
	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);
565
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
566
567
568
		spin_unlock(&card->card_lock);
		return IRQ_HANDLED;
	}
569

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

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

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

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

Linus Torvalds's avatar
Linus Torvalds committed
669
	return IRQ_HANDLED;
670
}
Linus Torvalds's avatar
Linus Torvalds committed
671

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

Linus Torvalds's avatar
Linus Torvalds committed
682
683
684
685
686
	if (!(tty = port->tty) || !tty->termios)
		return;
	baud = C_BAUD(tty);
	if (baud & CBAUDEX) {
		baud &= ~CBAUDEX;
687

Linus Torvalds's avatar
Linus Torvalds committed
688
689
690
		/*  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.
691
692
		 */

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

		/*  the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set
Linus Torvalds's avatar
Linus Torvalds committed
702
703
		 *  by the set_serial_info ioctl ... this is done by
		 *  the 'setserial' utility.
704
705
		 */

Linus Torvalds's avatar
Linus Torvalds committed
706
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
707
			baud++; /*  57.6 Kbps */
Linus Torvalds's avatar
Linus Torvalds committed
708
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
709
			baud +=2; /*  115  Kbps */
710
711
712
713
		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
714
715
716
	}
	if (linuxb_to_isib[baud] == -1) {
		/* hang up */
717
718
719
720
		drop_dtr(port);
		return;
	}
	else
Linus Torvalds's avatar
Linus Torvalds committed
721
		raise_dtr(port);
722

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

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

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

769
	if (WaitTillCardIsFree(base) == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
770
771
772
773
774
		outw(0x8000 | (channel << shift_count) |0x04, base);
		outw(flow_ctrl << 8 | 0x05, base);
		outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
		InterruptTheCard(base);
	}
775

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

783
784
785
/* open et all */

static inline void isicom_setup_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
786
787
{
	int channel;
788
	struct isi_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
789
	unsigned long flags;
790

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

static int isicom_setup_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
804
{
805
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
806
	unsigned long flags;
807

Linus Torvalds's avatar
Linus Torvalds committed
808
809
810
811
812
	if (port->flags & ASYNC_INITIALIZED) {
		return 0;
	}
	if (!port->xmit_buf) {
		unsigned long page;
813

Linus Torvalds's avatar
Linus Torvalds committed
814
815
		if (!(page = get_zeroed_page(GFP_KERNEL)))
			return -ENOMEM;
816

Linus Torvalds's avatar
Linus Torvalds committed
817
818
819
820
		if (port->xmit_buf) {
			free_page(page);
			return -ERESTARTSYS;
		}
821
822
		port->xmit_buf = (unsigned char *) page;
	}
Linus Torvalds's avatar
Linus Torvalds committed
823
824
825
826
827
828

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

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

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

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

	return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

	isicom_setup_board(card);

Linus Torvalds's avatar
Linus Torvalds committed
949
950
951
952
953
954
955
956
	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;

957
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
958
}
959

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

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

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

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

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

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

Linus Torvalds's avatar
Linus Torvalds committed
988
989
990
	/*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
991
	appears on the remote end. Now we drop the dtr only if the
Linus Torvalds's avatar
Linus Torvalds committed
992
	HUPCL(Hangup on close) flag is set for the tty*/
993
994

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

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