isicom.c 45.4 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
/*
 *	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
10
11
 *	1/9/98	alan@lxorguk.ukuu.org.uk
 *					Merge to 2.0.x kernel tree
Linus Torvalds's avatar
Linus Torvalds committed
12
13
14
15
 *					Obtain and use official major/minors
 *					Loader switched to a misc device
 *					(fixed range check bug as a side effect)
 *					Printk clean up
16
17
 *	9/12/98	alan@lxorguk.ukuu.org.uk
 *					Rough port to 2.1.x
Linus Torvalds's avatar
Linus Torvalds committed
18
19
20
21
22
23
24
 *
 *	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.
25
 *					This is to prevent the firmware
Linus Torvalds's avatar
Linus Torvalds committed
26
27
28
29
30
31
32
33
34
35
 *					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
36
 *					when the last port on the card is
Linus Torvalds's avatar
Linus Torvalds committed
37
38
39
 *					closed.
 *
 *	10/5/00  sameer			Signal mask setup command added
40
 *					to  isicom_setup_port and
Linus Torvalds's avatar
Linus Torvalds committed
41
42
43
 *					isicom_shutdown_port.
 *
 *	24/5/00  sameer			The driver is now SMP aware.
44
45
 *
 *
Linus Torvalds's avatar
Linus Torvalds committed
46
 *	27/11/00 Vinayak P Risbud	Fixed the Driver Crash Problem
47
48
 *
 *
Linus Torvalds's avatar
Linus Torvalds committed
49
50
51
52
53
54
 *	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
 *
55
 *	11/04/01  Kevin			Fixed firmware load problem with
Linus Torvalds's avatar
Linus Torvalds committed
56
 *					ISIHP-4X card
57
 *
Linus Torvalds's avatar
Linus Torvalds committed
58
59
60
61
62
63
64
65
66
 *	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
67
 *					info during insmod as well as module
Linus Torvalds's avatar
Linus Torvalds committed
68
 *					listing by lsmod.
69
 *
Linus Torvalds's avatar
Linus Torvalds committed
70
71
72
73
74
75
76
77
 *	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.
78
 *
Linus Torvalds's avatar
Linus Torvalds committed
79
80
81
82
83
 *	09/06/01 acme@conectiva.com.br	use capable, not suser, do
 *					restore_flags on failure in
 *					isicom_send_break, verify put_user
 *					result
 *
84
85
86
87
88
89
90
 *	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
91
92
93
94
95
96
97
 *					Red Hat Distribution
 *
 *	06/01/05  Alan Cox 		Merged the ISI and base kernel strands
 *					into a single 2.6 driver
 *
 *	***********************************************************
 *
98
 *	To use this driver you also need the support package. You
Linus Torvalds's avatar
Linus Torvalds committed
99
100
 *	can find this in RPM format on
 *		ftp://ftp.linux.org.uk/pub/linux/alan
101
 *
Linus Torvalds's avatar
Linus Torvalds committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
 *	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>
117
#include <linux/firmware.h>
Linus Torvalds's avatar
Linus Torvalds committed
118
119
#include <linux/kernel.h>
#include <linux/tty.h>
Alan Cox's avatar
Alan Cox committed
120
#include <linux/tty_flip.h>
Linus Torvalds's avatar
Linus Torvalds committed
121
122
123
124
125
126
127
128
129
130
#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
131
132
#include <linux/uaccess.h>
#include <linux/io.h>
Linus Torvalds's avatar
Linus Torvalds committed
133
134
135
136
137
138
#include <asm/system.h>

#include <linux/pci.h>

#include <linux/isicom.h>

139
140
141
#define InterruptTheCard(base) outw(0, (base) + 0xc)
#define ClearInterrupt(base) inw((base) + 0x0a)

Jiri Slaby's avatar
Jiri Slaby committed
142
#define pr_dbg(str...) pr_debug("ISICOM: " str)
143
144
145
146
147
148
#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

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

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

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

179
180
static DEFINE_TIMER(tx, isicom_tx, 0, 0);

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

static signed char linuxb_to_isib[] = {
184
	-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
185
186
187
};

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

struct	isi_port {
	unsigned short		magic;
Alan Cox's avatar
Alan Cox committed
203
	struct tty_port		port;
204
205
	u16			channel;
	u16			status;
Alan Cox's avatar
Alan Cox committed
206
207
	struct isi_board	*card;
	unsigned char		*xmit_buf;
Linus Torvalds's avatar
Linus Torvalds committed
208
209
210
211
212
213
214
215
216
217
218
219
220
	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.
 */
221

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

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

Adrian Bunk's avatar
Adrian Bunk committed
254
	return 0;	/* Failed to acquire the card! */
Linus Torvalds's avatar
Linus Torvalds committed
255
256
257
258
259
260
261
262
263
264
}

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

/*
 *  ISI Card specific ops ...
 */
265

266
/* card->lock HAS to be held */
267
static void raise_dtr(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
268
{
269
	struct isi_board *card = port->card;
270
271
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
272

273
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
274
275
		return;

276
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
277
278
279
280
281
	outw(0x0504, base);
	InterruptTheCard(base);
	port->status |= ISI_DTR;
}

282
/* card->lock HAS to be held */
283
284
285
static inline void drop_dtr(struct isi_port *port)
{
	struct isi_board *card = port->card;
286
287
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
288

289
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
290
291
		return;

292
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
293
	outw(0x0404, base);
294
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
295
296
297
	port->status &= ~ISI_DTR;
}

298
/* card->lock HAS to be held */
299
static inline void raise_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
300
{
301
	struct isi_board *card = port->card;
302
303
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
304

305
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
306
307
		return;

308
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
309
	outw(0x0a04, base);
310
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
311
312
	port->status |= ISI_RTS;
}
313
314

/* card->lock HAS to be held */
315
static inline void drop_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
316
{
317
	struct isi_board *card = port->card;
318
319
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
320

321
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
322
323
		return;

324
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
325
	outw(0x0804, base);
326
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
327
328
329
	port->status &= ~ISI_RTS;
}

330
/* card->lock MUST NOT be held */
331
332

static void isicom_raise_dtr_rts(struct tty_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
333
{
334
335
	struct isi_port *ip = container_of(port, struct isi_port, port);
	struct isi_board *card = ip->card;
336
	unsigned long base = card->base;
337
	u16 channel = ip->channel;
Linus Torvalds's avatar
Linus Torvalds committed
338
339
340
341

	if (!lock_card(card))
		return;

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

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

356
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
357
358
		return;

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

/*
 *	ISICOM Driver specific routines ...
 *
 */
369

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

Linus Torvalds's avatar
Linus Torvalds committed
384
385
	return 0;
}
386

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

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

Linus Torvalds's avatar
Linus Torvalds committed
413
	prev_card = card;
414

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

	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
428
429
430
431
	tty = tty_port_tty_get(&port->port);
	if (tty == NULL)
		goto put_unlock;

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

Linus Torvalds's avatar
Linus Torvalds committed
438
		txcount = min_t(short, TX_SIZE, port->xmit_cnt);
Jiri Slaby's avatar
Jiri Slaby committed
439
		if (txcount <= 0 || tty->stopped || tty->hw_stopped)
Linus Torvalds's avatar
Linus Torvalds committed
440
			continue;
Jiri Slaby's avatar
Jiri Slaby committed
441
442

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

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

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

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

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

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

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

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

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

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

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

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

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

Linus Torvalds's avatar
Linus Torvalds committed
569
570
	if (header & 0x8000) {		/* Status Packet */
		header = inw(base);
Alan Cox's avatar
Alan Cox committed
571
		switch (header & 0xff) {
572
		case 0:	/* Change in EIA signals */
Alan Cox's avatar
Alan Cox committed
573
			if (port->port.flags & ASYNC_CHECK_CD) {
574
575
576
				if (port->status & ISI_DCD) {
					if (!(header & ISI_DCD)) {
					/* Carrier has been lost  */
577
578
						pr_dbg("interrupt: DCD->low.\n"
							);
579
						port->status &= ~ISI_DCD;
580
						tty_hangup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
581
					}
582
583
584
585
				} else if (header & ISI_DCD) {
				/* Carrier has been detected */
					pr_dbg("interrupt: DCD->high.\n");
					port->status |= ISI_DCD;
Alan Cox's avatar
Alan Cox committed
586
					wake_up_interruptible(&port->port.open_wait);
Linus Torvalds's avatar
Linus Torvalds committed
587
				}
588
			} else {
589
590
591
592
593
594
				if (header & ISI_DCD)
					port->status |= ISI_DCD;
				else
					port->status &= ~ISI_DCD;
			}

Alan Cox's avatar
Alan Cox committed
595
			if (port->port.flags & ASYNC_CTS_FLOW) {
Alan Cox's avatar
Alan Cox committed
596
				if (tty->hw_stopped) {
597
					if (header & ISI_CTS) {
Alan Cox's avatar
Alan Cox committed
598
						port->port.tty->hw_stopped = 0;
599
						/* start tx ing */
600
601
						port->status |= (ISI_TXOK
							| ISI_CTS);
602
						tty_wakeup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
603
					}
604
				} else if (!(header & ISI_CTS)) {
Alan Cox's avatar
Alan Cox committed
605
					tty->hw_stopped = 1;
606
607
					/* stop tx ing */
					port->status &= ~(ISI_TXOK | ISI_CTS);
Linus Torvalds's avatar
Linus Torvalds committed
608
				}
609
			} else {
610
611
				if (header & ISI_CTS)
					port->status |= ISI_CTS;
Linus Torvalds's avatar
Linus Torvalds committed
612
				else
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
					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;

628
		case 1:	/* Received Break !!! */
629
			tty_insert_flip_char(tty, 0, TTY_BREAK);
Alan Cox's avatar
Alan Cox committed
630
			if (port->port.flags & ASYNC_SAK)
631
632
633
634
635
				do_SAK(tty);
			tty_flip_buffer_push(tty);
			break;

		case 2:	/* Statistics		 */
636
			pr_dbg("isicom_interrupt: stats!!!.\n");
637
638
639
			break;

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

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

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

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

Alan Cox's avatar
Alan Cox committed
683
	/* FIXME: Switch to new tty baud API */
Linus Torvalds's avatar
Linus Torvalds committed
684
685
686
	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)
Alan Cox's avatar
Alan Cox committed
695
			tty->termios->c_cflag &= ~CBAUDEX;
Linus Torvalds's avatar
Linus Torvalds committed
696
697
		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
		 */

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

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

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

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

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

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

782
783
784
/* open et all */

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

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

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

Alan Cox's avatar
Alan Cox committed
808
	if (port->port.flags & ASYNC_INITIALIZED)
Linus Torvalds's avatar
Linus Torvalds committed
809
		return 0;
Alan Cox's avatar
Alan Cox committed
810
811
	if (tty_port_alloc_xmit_buf(&port->port) < 0)
		return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
812
813

	spin_lock_irqsave(&card->card_lock, flags);
Alan Cox's avatar
Alan Cox committed
814
	clear_bit(TTY_IO_ERROR, &tty->flags);
Alan Cox's avatar
Alan Cox committed
815
	if (port->port.count == 1)
Linus Torvalds's avatar
Linus Torvalds committed
816
		card->count++;
817

Linus Torvalds's avatar
Linus Torvalds committed
818
	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
819

Linus Torvalds's avatar
Linus Torvalds committed
820
	/*	discard any residual data	*/
821
822
823
824
825
826
	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);
	}
827

Alan Cox's avatar
Alan Cox committed
828
	isicom_config_port(tty);
Alan Cox's avatar
Alan Cox committed
829
	port->port.flags |= ASYNC_INITIALIZED;
Linus Torvalds's avatar
Linus Torvalds committed
830
	spin_unlock_irqrestore(&card->card_lock, flags);
831
832
833
834

	return 0;
}

835
836
837
838
839
840
static int isicom_carrier_raised(struct tty_port *port)
{
	struct isi_port *ip = container_of(port, struct isi_port, port);
	return (ip->status & ISI_DCD)?1 : 0;
}

841
static int block_til_ready(struct tty_struct *tty, struct file *filp,
842
	struct isi_port *ip)
Linus Torvalds's avatar
Linus Torvalds committed
843
{
844
845
	struct isi_board *card = ip->card;
	struct tty_port *port = &ip->port;
Linus Torvalds's avatar
Linus Torvalds committed
846
847
848
	int do_clocal = 0, retval;
	unsigned long flags;
	DECLARE_WAITQUEUE(wait, current);
849
	int cd;
Linus Torvalds's avatar
Linus Torvalds committed
850
851
852

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

853
	if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
854
		pr_dbg("block_til_ready: close in progress.\n");
855
856
		interruptible_sleep_on(&port->close_wait);
		if (port->flags & ASYNC_HUP_NOTIFY)
Linus Torvalds's avatar
Linus Torvalds committed
857
858
859
860
			return -EAGAIN;
		else
			return -ERESTARTSYS;
	}
861

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

864
865
	if ((filp->f_flags & O_NONBLOCK) ||
			(tty->flags & (1 << TTY_IO_ERROR))) {
866
		pr_dbg("block_til_ready: non-block mode.\n");
867
		port->flags |= ASYNC_NORMAL_ACTIVE;
868
869
870
		return 0;
	}

Linus Torvalds's avatar
Linus Torvalds committed
871
872
	if (C_CLOCAL(tty))
		do_clocal = 1;
873
874

	/* block waiting for DCD to be asserted, and while
Linus Torvalds's avatar
Linus Torvalds committed
875
876
						callout dev is busy */
	retval = 0;
877
	add_wait_queue(&port->open_wait, &wait);
Linus Torvalds's avatar
Linus Torvalds committed
878
879
880

	spin_lock_irqsave(&card->card_lock, flags);
	if (!tty_hung_up_p(filp))
881
882
		port->count--;
	port->blocked_open++;
Linus Torvalds's avatar
Linus Torvalds committed
883
	spin_unlock_irqrestore(&card->card_lock, flags);
884

Linus Torvalds's avatar
Linus Torvalds committed
885
	while (1) {
886
		tty_port_raise_dtr_rts(port);
Linus Torvalds's avatar
Linus Torvalds committed
887
888

		set_current_state(TASK_INTERRUPTIBLE);
889
890
		if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
			if (port->flags & ASYNC_HUP_NOTIFY)
Linus Torvalds's avatar
Linus Torvalds committed
891
892
893
894
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;
			break;
895
		}
896
897
898
		cd = tty_port_carrier_raised(port);
		if (!(port->flags & ASYNC_CLOSING) &&
				(do_clocal || cd))
Linus Torvalds's avatar
Linus Torvalds committed
899
900
901
902
903
			break;
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			break;
		}
904
		schedule();
Linus Torvalds's avatar
Linus Torvalds committed
905
906
	}
	set_current_state(TASK_RUNNING);
907
	remove_wait_queue(&port->open_wait, &wait);
Linus Torvalds's avatar
Linus Torvalds committed
908
909
	spin_lock_irqsave(&card->card_lock, flags);
	if (!tty_hung_up_p(filp))
910
911
		port->count++;
	port->blocked_open--;
Linus Torvalds's avatar
Linus Torvalds committed
912
913
914
	spin_unlock_irqrestore(&card->card_lock, flags);
	if (retval)
		return retval;
915
	port->flags |= ASYNC_NORMAL_ACTIVE;
Linus Torvalds's avatar
Linus Torvalds committed
916
917
	return 0;
}
918
919

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

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

Linus Torvalds's avatar
Linus Torvalds committed
932
933
	if (!(card->status & FIRMWARE_LOADED))
		return -ENODEV;
934

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

939
	port = &isi_ports[line];
Linus Torvalds's avatar
Linus Torvalds committed
940
941
	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
		return -ENODEV;
942
943
944

	isicom_setup_board(card);

Alan Cox's avatar
Alan Cox committed
945
	port->port.count++;
Linus Torvalds's avatar
Linus Torvalds committed
946
	tty->driver_data = port;
Alan Cox's avatar
Alan Cox committed
947
948
	tty_port_tty_set(&port->port, tty);
	error = isicom_setup_port(tty);
Alan Cox's avatar
Alan Cox committed
949
950
951
	if (error == 0)
		error = block_til_ready(tty, filp, port);
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
952
}
953

Linus Torvalds's avatar
Linus Torvalds committed
954
955
/* close et all */

956
static inline void isicom_shutdown_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
957
{
Alan Cox's avatar
Alan Cox committed
958
	if (bp->status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
959
960
961
		bp->status &= ~BOARD_ACTIVE;
}

962
/* card->lock HAS to be held */
963
static void isicom_shutdown_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
964
{
965
966
967
	struct isi_board *card = port->card;
	struct tty_struct *tty;

Alan Cox's avatar
Alan Cox committed
968
	tty = tty_port_tty_get(&port->port);
Linus Torvalds's avatar
Linus Torvalds committed
969

Alan Cox's avatar
Alan Cox committed
970
971
	if (!(port->port.flags & ASYNC_INITIALIZED)) {