isicom.c 42.8 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
#include <linux/termios.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/serial.h>
Alexey Dobriyan's avatar
Alexey Dobriyan committed
125
#include <linux/smp_lock.h>
Linus Torvalds's avatar
Linus Torvalds committed
126
127
128
129
130
131
#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
132
133
#include <linux/uaccess.h>
#include <linux/io.h>
Linus Torvalds's avatar
Linus Torvalds committed
134
135
136
137
138
139
#include <asm/system.h>

#include <linux/pci.h>

#include <linux/isicom.h>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/*
 *  ISI Card specific ops ...
 */
266

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	if (!lock_card(card))
		return;

343
344
345
346
347
348
349
350
351
352
353
	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
354
355
356
	unlock_card(card);
}

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

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

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

/*
 *	ISICOM Driver specific routines ...
 *
 */
377

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

790
791
792
/* open et all */

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

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

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

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

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

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

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

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

	return 0;
}

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

849
static struct tty_port *isicom_find_port(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
850
{
851
852
	struct isi_port *port;
	struct isi_board *card;
853
	unsigned int board;
854
	int line = tty->index;
Linus Torvalds's avatar
Linus Torvalds committed
855
856

	if (line < 0 || line > PORT_COUNT-1)
857
		return NULL;
Linus Torvalds's avatar
Linus Torvalds committed
858
859
	board = BOARD(line);
	card = &isi_card[board];
860

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

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

868
	port = &isi_ports[line];
Linus Torvalds's avatar
Linus Torvalds committed
869
	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
870
871
872
873
874
875
876
877
878
879
880
		return NULL;

	return &port->port;
}
	
static int isicom_open(struct tty_struct *tty, struct file *filp)
{
	struct isi_port *port;
	struct isi_board *card;
	struct tty_port *tport;
	int error = 0;
881

882
883
884
885
886
	tport = isicom_find_port(tty);
	if (tport == NULL)
		return -ENODEV;
	port = container_of(tport, struct isi_port, port);
	card = &isi_card[BOARD(tty->index)];
887
888
	isicom_setup_board(card);

889
	/* FIXME: locking on port.count etc */
Alan Cox's avatar
Alan Cox committed
890
	port->port.count++;
Linus Torvalds's avatar
Linus Torvalds committed
891
	tty->driver_data = port;
Alan Cox's avatar
Alan Cox committed
892
	tty_port_tty_set(&port->port, tty);
893
894
895
	/* FIXME: Locking on Initialized flag */
	if (!test_bit(ASYNCB_INITIALIZED, &tport->flags))
		error = isicom_setup_port(tty);
Alan Cox's avatar
Alan Cox committed
896
	if (error == 0)
897
		error = tty_port_block_til_ready(&port->port, tty, filp);
Alan Cox's avatar
Alan Cox committed
898
	return error;
Linus Torvalds's avatar
Linus Torvalds committed
899
}
900

Linus Torvalds's avatar
Linus Torvalds committed
901
902
/* close et all */

903
static inline void isicom_shutdown_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
904
{
Alan Cox's avatar
Alan Cox committed
905
	if (bp->status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
906
907
908
		bp->status &= ~BOARD_ACTIVE;
}

909
/* card->lock HAS to be held */
910
static void isicom_shutdown_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
911
{
912
913
914
	struct isi_board *card = port->card;
	struct tty_struct *tty;

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

Alan Cox's avatar
Alan Cox committed
917
918
	if (!(port->port.flags & ASYNC_INITIALIZED)) {
		tty_kref_put(tty);
Linus Torvalds's avatar
Linus Torvalds committed
919
		return;
Alan Cox's avatar
Alan Cox committed
920
	}
921

Alan Cox's avatar
Alan Cox committed
922
923
	tty_port_free_xmit_buf(&port->port);
	port->port.flags &= ~ASYNC_INITIALIZED;
Linus Torvalds's avatar
Linus Torvalds committed
924
	/* 3rd October 2000 : Vinayak P Risbud */
Alan Cox's avatar
Alan Cox committed
925
	tty_port_tty_set(&port->port, NULL);
926

Linus Torvalds's avatar
Linus Torvalds committed
927
928
929
	/*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
930
	appears on the remote end. Now we drop the dtr only if the
Linus Torvalds's avatar
Linus Torvalds committed
931
	HUPCL(Hangup on close) flag is set for the tty*/
932
933

	if (C_HUPCL(tty))
Linus Torvalds's avatar
Linus Torvalds committed
934
935
		/* drop dtr on this port */
		drop_dtr(port);
936
937

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

Linus Torvalds's avatar
Linus Torvalds committed
941
	if (--card->count < 0) {
942
		pr_dbg("isicom_shutdown_port: bad board(0x%lx) count %d.\n",
Linus Torvalds's avatar
Linus Torvalds committed
943
			card->base, card->count);
944
		card->count = 0;
Linus Torvalds's avatar
Linus Torvalds committed
945
	}
946

947
	/* last port was closed, shutdown that boad too */
948
	if (C_HUPCL(tty)) {
Linus Torvalds's avatar
Linus Torvalds committed
949
950
951
		if (!card->count)
			isicom_shutdown_board(card);
	}
Alan Cox's avatar
Alan Cox committed
952
	tty_kref_put(tty);
Linus Torvalds's avatar
Linus Torvalds committed
953
954
}

955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
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);
}