isicom.c 42.2 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_dtr_rts(struct tty_port *port, int on)
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
343
344
345
346
347
348
349
350
351
352
	if (on) {
		outw(0x8000 | (channel << card->shift_count) | 0x02, base);
		outw(0x0f04, base);
		InterruptTheCard(base);
		ip->status |= (ISI_DTR | ISI_RTS);
	} else {
		outw(0x8000 | (channel << card->shift_count) | 0x02, base);
		outw(0x0C04, base);
		InterruptTheCard(base);
		ip->status &= ~(ISI_DTR | ISI_RTS);
	}
Linus Torvalds's avatar
Linus Torvalds committed
353
354
355
	unlock_card(card);
}

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

363
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
364
365
		return;

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

/*
 *	ISICOM Driver specific routines ...
 *
 */
376

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

Linus Torvalds's avatar
Linus Torvalds committed
391
392
	return 0;
}
393

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

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

Linus Torvalds's avatar
Linus Torvalds committed
420
	prev_card = card;
421

Linus Torvalds's avatar
Linus Torvalds committed
422
423
424
	count = isi_card[card].port_count;
	port = isi_card[card].ports;
	base = isi_card[card].base;
Jiri Slaby's avatar
Jiri Slaby committed
425
426
427
428
429
430
431
432
433
434

	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
435
436
437
438
	tty = tty_port_tty_get(&port->port);
	if (tty == NULL)
		goto put_unlock;

Alan Cox's avatar
Alan Cox committed
439
	for (; count > 0; count--, port++) {
Linus Torvalds's avatar
Linus Torvalds committed
440
		/* port not active or tx disabled to force flow control */
Alan Cox's avatar
Alan Cox committed
441
		if (!(port->port.flags & ASYNC_INITIALIZED) ||
442
				!(port->status & ISI_TXOK))
Linus Torvalds's avatar
Linus Torvalds committed
443
			continue;
444

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

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

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

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

Alan Cox's avatar
Alan Cox committed
502
503
put_unlock:
	tty_kref_put(tty);
Jiri Slaby's avatar
Jiri Slaby committed
504
505
unlock:
	spin_unlock_irqrestore(&isi_card[card].card_lock, flags);
506
507
	/*	schedule another tx for hopefully in about 10ms	*/
sched_again:
508
	mod_timer(&tx, jiffies + msecs_to_jiffies(10));
509
510
}

Linus Torvalds's avatar
Linus Torvalds committed
511
/*
512
 *	Main interrupt handler routine
Linus Torvalds's avatar
Linus Torvalds committed
513
 */
514

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

Linus Torvalds's avatar
Linus Torvalds committed
525
526
	if (!card || !(card->status & FIRMWARE_LOADED))
		return IRQ_NONE;
527

Linus Torvalds's avatar
Linus Torvalds committed
528
	base = card->base;
529
530
531
532
533

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

Linus Torvalds's avatar
Linus Torvalds committed
534
	spin_lock(&card->card_lock);
535

536
537
538
539
540
541
	/*
	 * disable any interrupts from the PCI card and lower the
	 * interrupt line
	 */
	outw(0x8000, base+0x04);
	ClearInterrupt(base);
542

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

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

Linus Torvalds's avatar
Linus Torvalds committed
576
577
	if (header & 0x8000) {		/* Status Packet */
		header = inw(base);
Alan Cox's avatar
Alan Cox committed
578
		switch (header & 0xff) {
579
		case 0:	/* Change in EIA signals */
Alan Cox's avatar
Alan Cox committed
580
			if (port->port.flags & ASYNC_CHECK_CD) {
581
582
583
				if (port->status & ISI_DCD) {
					if (!(header & ISI_DCD)) {
					/* Carrier has been lost  */
584
585
						pr_dbg("interrupt: DCD->low.\n"
							);
586
						port->status &= ~ISI_DCD;
587
						tty_hangup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
588
					}
589
590
591
592
				} 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
593
					wake_up_interruptible(&port->port.open_wait);
Linus Torvalds's avatar
Linus Torvalds committed
594
				}
595
			} else {
596
597
598
599
600
601
				if (header & ISI_DCD)
					port->status |= ISI_DCD;
				else
					port->status &= ~ISI_DCD;
			}

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

635
		case 1:	/* Received Break !!! */
636
			tty_insert_flip_char(tty, 0, TTY_BREAK);
Alan Cox's avatar
Alan Cox committed
637
			if (port->port.flags & ASYNC_SAK)
638
639
640
641
642
				do_SAK(tty);
			tty_flip_buffer_push(tty);
			break;

		case 2:	/* Statistics		 */
643
			pr_dbg("isicom_interrupt: stats!!!.\n");
644
645
646
			break;

		default:
647
			pr_dbg("Intr: Unknown code in status packet.\n");
648
649
			break;
		}
650
	} else {				/* Data   Packet */
Alan Cox's avatar
Alan Cox committed
651
652

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

Linus Torvalds's avatar
Linus Torvalds committed
677
	return IRQ_HANDLED;
678
}
Linus Torvalds's avatar
Linus Torvalds committed
679

Alan Cox's avatar
Alan Cox committed
680
static void isicom_config_port(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
681
{
Alan Cox's avatar
Alan Cox committed
682
	struct isi_port *port = tty->driver_data;
683
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
684
	unsigned long baud;
685
686
687
	unsigned long base = card->base;
	u16 channel_setup, channel = port->channel,
		shift_count = card->shift_count;
Linus Torvalds's avatar
Linus Torvalds committed
688
	unsigned char flow_ctrl;
689

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

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

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

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

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

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

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

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

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

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

789
790
791
/* open et all */

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

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

Alan Cox's avatar
Alan Cox committed
809
static int isicom_setup_port(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
810
{
Alan Cox's avatar
Alan Cox committed
811
	struct isi_port *port = tty->driver_data;
812
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
813
	unsigned long flags;
814

Alan Cox's avatar
Alan Cox committed
815
	if (port->port.flags & ASYNC_INITIALIZED)
Linus Torvalds's avatar
Linus Torvalds committed
816
		return 0;
Alan Cox's avatar
Alan Cox committed
817
818
	if (tty_port_alloc_xmit_buf(&port->port) < 0)
		return -ENOMEM;
Linus Torvalds's avatar
Linus Torvalds committed
819
820

	spin_lock_irqsave(&card->card_lock, flags);
Alan Cox's avatar
Alan Cox committed
821
	clear_bit(TTY_IO_ERROR, &tty->flags);
Alan Cox's avatar
Alan Cox committed
822
	if (port->port.count == 1)
Linus Torvalds's avatar
Linus Torvalds committed
823
		card->count++;
824

Linus Torvalds's avatar
Linus Torvalds committed
825
	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
826

Linus Torvalds's avatar
Linus Torvalds committed
827
	/*	discard any residual data	*/
828
829
830
831
832
833
	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);
	}
834

Alan Cox's avatar
Alan Cox committed
835
	isicom_config_port(tty);
Alan Cox's avatar
Alan Cox committed
836
	port->port.flags |= ASYNC_INITIALIZED;
Linus Torvalds's avatar
Linus Torvalds committed
837
	spin_unlock_irqrestore(&card->card_lock, flags);
838
839
840
841

	return 0;
}

842
843
844
845
846
847
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;
}

848
static int isicom_open(struct tty_struct *tty, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
849
{
850
851
	struct isi_port *port;
	struct isi_board *card;
852
853
	unsigned int board;
	int error, line;
Linus Torvalds's avatar
Linus Torvalds committed
854
855
856
857
858
859

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

Linus Torvalds's avatar
Linus Torvalds committed
861
862
	if (!(card->status & FIRMWARE_LOADED))
		return -ENODEV;
863

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

868
	port = &isi_ports[line];
Linus Torvalds's avatar
Linus Torvalds committed
869
870
	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
		return -ENODEV;
871
872
873

	isicom_setup_board(card);

874
	/* FIXME: locking on port.count etc */
Alan Cox's avatar
Alan Cox committed
875
	port->port.count++;
Linus Torvalds's avatar
Linus Torvalds committed
876
	tty->driver_data = port;
Alan Cox's avatar
Alan Cox committed
877
878
	tty_port_tty_set(&port->port, tty);
	error = isicom_setup_port(tty);
Alan Cox's avatar
Alan Cox committed
879
	if (error == 0)
880
		error = tty_port_block_til_ready(&port->port, tty, filp);
Alan Cox's avatar
Alan Cox committed
881
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
882
}
883

Linus Torvalds's avatar
Linus Torvalds committed
884
885
/* close et all */

886
static inline void isicom_shutdown_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
887
{
Alan Cox's avatar
Alan Cox committed
888
	if (bp->status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
889
890
891
		bp->status &= ~BOARD_ACTIVE;
}

892
/* card->lock HAS to be held */
893
static void isicom_shutdown_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
894
{
895
896
897
	struct isi_board *card = port->card;
	struct tty_struct *tty;

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

Alan Cox's avatar
Alan Cox committed
900
901
	if (!(port->port.flags & ASYNC_INITIALIZED)) {
		tty_kref_put(tty);
Linus Torvalds's avatar
Linus Torvalds committed
902
		return;
Alan Cox's avatar
Alan Cox committed
903
	}
904

Alan Cox's avatar
Alan Cox committed
905
906
	tty_port_free_xmit_buf(&port->port);
	port->port.flags &= ~ASYNC_INITIALIZED;
Linus Torvalds's avatar
Linus Torvalds committed
907
	/* 3rd October 2000 : Vinayak P Risbud */
Alan Cox's avatar
Alan Cox committed
908
	tty_port_tty_set(&port->port, NULL);
909

Linus Torvalds's avatar
Linus Torvalds committed
910
911
912
	/*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
913
	appears on the remote end. Now we drop the dtr only if the
Linus Torvalds's avatar
Linus Torvalds committed
914
	HUPCL(Hangup on close) flag is set for the tty*/
915
916

	if (C_HUPCL(tty))
Linus Torvalds's avatar
Linus Torvalds committed
917
918
		/* drop dtr on this port */
		drop_dtr(port);
919
920

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

Linus Torvalds's avatar
Linus Torvalds committed
924
	if (--card->count < 0) {
925
		pr_dbg("isicom_shutdown_port: bad board(0x%lx) count %d.\n",
Linus Torvalds's avatar
Linus Torvalds committed
926
			card->base, card->count);
927
		card->count = 0;
Linus Torvalds's avatar
Linus Torvalds committed
928
	}
929

930
	/* last port was closed, shutdown that boad too */
931
	if (C_HUPCL(tty)) {
Linus Torvalds's avatar
Linus Torvalds committed
932
933
934
		if (!card->count)
			isicom_shutdown_board(card);
	}
Alan Cox's avatar
Alan Cox committed
935
	tty_kref_put(tty);
Linus Torvalds's avatar
Linus Torvalds committed
936
937
}

938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
static void isicom_flush_buffer(struct tty_struct *tty)
{
	struct isi_port *port = tty->driver_data;
	struct isi_board *card = port->card;
	unsigned long flags;

	if (isicom_paranoia_check(port, tty->name, "isicom_flush_buffer"))
		return;

	spin_lock_irqsave(&card->card_lock, flags);
	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
	spin_unlock_irqrestore(&card->card_lock, flags);

	tty_wakeup(tty);
}

954
static void isicom_close(struct tty_struct *tty, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
955
{
956
957
	struct isi_port *ip = tty->driver_data;
	struct tty_port *port = &ip->port;
958
	struct isi_board *card;
Linus Torvalds's avatar
Linus Torvalds committed
959
	unsigned long flags;
960

961
	BUG_ON(!ip);
962

963
964
	card = ip->card;
	if (isicom_paranoia_check(ip, tty->name, "isicom_close"))
Linus Torvalds's avatar
Linus Torvalds committed
965
		return;
966
967

	/* indicate to the card that no more data can be received
Linus Torvalds's avatar
Linus Torvalds committed
968
969
	   on this port */
	spin_lock_irqsave(&card->card_lock, flags);
970