isicom.c 45.7 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 *	Original driver code supplied by Multi-Tech
 *
 *	Changes
 *	1/9/98	alan@redhat.com		Merge to 2.0.x kernel tree
 *					Obtain and use official major/minors
 *					Loader switched to a misc device
 *					(fixed range check bug as a side effect)
 *					Printk clean up
 *	9/12/98	alan@redhat.com		Rough port to 2.1.x
 *
 *	10/6/99 sameer			Merged the ISA and PCI drivers to
 *					a new unified driver.
 *
 *	3/9/99	sameer			Added support for ISI4616 cards.
 *
 *	16/9/99	sameer			We do not force RTS low anymore.
23
 *					This is to prevent the firmware
Linus Torvalds's avatar
Linus Torvalds committed
24
25
26
27
28
29
30
31
32
33
 *					from getting confused.
 *
 *	26/10/99 sameer			Cosmetic changes:The driver now
 *					dumps the Port Count information
 *					along with I/O address and IRQ.
 *
 *	13/12/99 sameer			Fixed the problem with IRQ sharing.
 *
 *	10/5/00  sameer			Fixed isicom_shutdown_board()
 *					to not lower DTR on all the ports
34
 *					when the last port on the card is
Linus Torvalds's avatar
Linus Torvalds committed
35
36
37
 *					closed.
 *
 *	10/5/00  sameer			Signal mask setup command added
38
 *					to  isicom_setup_port and
Linus Torvalds's avatar
Linus Torvalds committed
39
40
41
 *					isicom_shutdown_port.
 *
 *	24/5/00  sameer			The driver is now SMP aware.
42
43
 *
 *
Linus Torvalds's avatar
Linus Torvalds committed
44
 *	27/11/00 Vinayak P Risbud	Fixed the Driver Crash Problem
45
46
 *
 *
Linus Torvalds's avatar
Linus Torvalds committed
47
48
49
50
51
52
 *	03/01/01  anil .s		Added support for resetting the
 *					internal modems on ISI cards.
 *
 *	08/02/01  anil .s		Upgraded the driver for kernel
 *					2.4.x
 *
53
 *	11/04/01  Kevin			Fixed firmware load problem with
Linus Torvalds's avatar
Linus Torvalds committed
54
 *					ISIHP-4X card
55
 *
Linus Torvalds's avatar
Linus Torvalds committed
56
57
58
59
60
61
62
63
64
 *	30/04/01  anil .s		Fixed the remote login through
 *					ISI port problem. Now the link
 *					does not go down before password
 *					prompt.
 *
 *	03/05/01  anil .s		Fixed the problem with IRQ sharing
 *					among ISI-PCI cards.
 *
 *	03/05/01  anil .s		Added support to display the version
65
 *					info during insmod as well as module
Linus Torvalds's avatar
Linus Torvalds committed
66
 *					listing by lsmod.
67
 *
Linus Torvalds's avatar
Linus Torvalds committed
68
69
70
71
72
73
74
75
 *	10/05/01  anil .s		Done the modifications to the source
 *					file and Install script so that the
 *					same installation can be used for
 *					2.2.x and 2.4.x kernel.
 *
 *	06/06/01  anil .s		Now we drop both dtr and rts during
 *					shutdown_port as well as raise them
 *					during isicom_config_port.
76
 *
Linus Torvalds's avatar
Linus Torvalds committed
77
78
79
80
81
 *	09/06/01 acme@conectiva.com.br	use capable, not suser, do
 *					restore_flags on failure in
 *					isicom_send_break, verify put_user
 *					result
 *
82
83
84
85
86
87
88
 *	11/02/03  ranjeeth		Added support for 230 Kbps and 460 Kbps
 *					Baud index extended to 21
 *
 *	20/03/03  ranjeeth		Made to work for Linux Advanced server.
 *					Taken care of license warning.
 *
 *	10/12/03  Ravindra		Made to work for Fedora Core 1 of
Linus Torvalds's avatar
Linus Torvalds committed
89
90
91
92
93
94
95
 *					Red Hat Distribution
 *
 *	06/01/05  Alan Cox 		Merged the ISI and base kernel strands
 *					into a single 2.6 driver
 *
 *	***********************************************************
 *
96
 *	To use this driver you also need the support package. You
Linus Torvalds's avatar
Linus Torvalds committed
97
98
 *	can find this in RPM format on
 *		ftp://ftp.linux.org.uk/pub/linux/alan
99
 *
Linus Torvalds's avatar
Linus Torvalds committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
 *	You can find the original tools for this direct from Multitech
 *		ftp://ftp.multitech.com/ISI-Cards/
 *
 *	Having installed the cards the module options (/etc/modprobe.conf)
 *
 *	options isicom   io=card1,card2,card3,card4 irq=card1,card2,card3,card4
 *
 *	Omit those entries for boards you don't have installed.
 *
 *	TODO
 *		Merge testing
 *		64-bit verification
 */

#include <linux/module.h>
115
#include <linux/firmware.h>
Linus Torvalds's avatar
Linus Torvalds committed
116
117
#include <linux/kernel.h>
#include <linux/tty.h>
Alan Cox's avatar
Alan Cox committed
118
#include <linux/tty_flip.h>
Linus Torvalds's avatar
Linus Torvalds committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <linux/termios.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/serial.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/ioport.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/system.h>

#include <linux/pci.h>

#include <linux/isicom.h>

137
138
139
140
141
142
143
144
145
146
147
#define InterruptTheCard(base) outw(0, (base) + 0xc)
#define ClearInterrupt(base) inw((base) + 0x0a)

#ifdef DEBUG
#define pr_dbg(str...) printk(KERN_DEBUG "ISICOM: " str)
#define isicom_paranoia_check(a, b, c) __isicom_paranoia_check((a), (b), (c))
#else
#define pr_dbg(str...) do { } while (0)
#define isicom_paranoia_check(a, b, c) 0
#endif

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

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

175
static DECLARE_COMPLETION(isi_timerdone);
Linus Torvalds's avatar
Linus Torvalds committed
176
177
178
static char re_schedule = 1;

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

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

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

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

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

struct	isi_port {
	unsigned short		magic;
	unsigned int		flags;
	int			count;
	int			blocked_open;
	int			close_delay;
209
210
211
	u16			channel;
	u16			status;
	u16			closing_wait;
Linus Torvalds's avatar
Linus Torvalds committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
	struct isi_board	* card;
	struct tty_struct 	* tty;
	wait_queue_head_t	close_wait;
	wait_queue_head_t	open_wait;
	unsigned char		* xmit_buf;
	int			xmit_head;
	int			xmit_tail;
	int			xmit_cnt;
};

static struct isi_board isi_card[BOARD_COUNT];
static struct isi_port  isi_ports[PORT_COUNT];

/*
 *	Locking functions for card level locking. We need to own both
 *	the kernel lock for the card and have the card in a position that
 *	it wants to talk.
 */
230

231
232
233
234
235
236
237
238
239
240
241
242
243
244
static inline int WaitTillCardIsFree(u16 base)
{
	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
245
246
247
static int lock_card(struct isi_board *card)
{
	char		retries;
248
	unsigned long base = card->base;
Linus Torvalds's avatar
Linus Torvalds committed
249
250
251
252

	for (retries = 0; retries < 100; retries++) {
		spin_lock_irqsave(&card->card_lock, card->flags);
		if (inw(base + 0xe) & 0x1) {
253
			return 1;
Linus Torvalds's avatar
Linus Torvalds committed
254
255
256
257
258
		} else {
			spin_unlock_irqrestore(&card->card_lock, card->flags);
			udelay(1000);   /* 1ms */
		}
	}
259
260
261
	printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%lx)\n",
		card->base);

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

static int lock_card_at_interrupt(struct isi_board *card)
{
	unsigned char		retries;
268
	unsigned long base = card->base;
Linus Torvalds's avatar
Linus Torvalds committed
269
270
271
272
273

	for (retries = 0; retries < 200; retries++) {
		spin_lock_irqsave(&card->card_lock, card->flags);

		if (inw(base + 0xe) & 0x1)
274
			return 1;
Linus Torvalds's avatar
Linus Torvalds committed
275
276
277
278
		else
			spin_unlock_irqrestore(&card->card_lock, card->flags);
	}
	/* Failing in interrupt is an acceptable event */
Adrian Bunk's avatar
Adrian Bunk committed
279
	return 0;	/* Failed to acquire the card! */
Linus Torvalds's avatar
Linus Torvalds committed
280
281
282
283
284
285
286
287
288
289
}

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

/*
 *  ISI Card specific ops ...
 */
290

291
/* card->lock HAS to be held */
292
static void raise_dtr(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
293
{
294
	struct isi_board *card = port->card;
295
296
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
297

298
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
299
300
		return;

301
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
302
303
304
305
306
	outw(0x0504, base);
	InterruptTheCard(base);
	port->status |= ISI_DTR;
}

307
/* card->lock HAS to be held */
308
309
310
static inline void drop_dtr(struct isi_port *port)
{
	struct isi_board *card = port->card;
311
312
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
313

314
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
315
316
		return;

317
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
318
	outw(0x0404, base);
319
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
320
321
322
	port->status &= ~ISI_DTR;
}

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

330
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
331
332
		return;

333
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
334
	outw(0x0a04, base);
335
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
336
337
	port->status |= ISI_RTS;
}
338
339

/* card->lock HAS to be held */
340
static inline void drop_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
341
{
342
	struct isi_board *card = port->card;
343
344
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
345

346
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
347
348
		return;

349
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
350
	outw(0x0804, base);
351
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
352
353
354
	port->status &= ~ISI_RTS;
}

355
/* card->lock MUST NOT be held */
356
static inline void raise_dtr_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
357
{
358
	struct isi_board *card = port->card;
359
360
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
361
362
363
364

	if (!lock_card(card))
		return;

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

372
/* card->lock HAS to be held */
373
static void drop_dtr_rts(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
374
{
375
	struct isi_board *card = port->card;
376
377
	unsigned long base = card->base;
	u16 channel = port->channel;
Linus Torvalds's avatar
Linus Torvalds committed
378

379
	if (WaitTillCardIsFree(base))
Linus Torvalds's avatar
Linus Torvalds committed
380
381
		return;

382
	outw(0x8000 | (channel << card->shift_count) | 0x02, base);
Linus Torvalds's avatar
Linus Torvalds committed
383
	outw(0x0c04, base);
384
	InterruptTheCard(base);
Linus Torvalds's avatar
Linus Torvalds committed
385
386
387
388
389
390
391
	port->status &= ~(ISI_RTS | ISI_DTR);
}

/*
 *	ISICOM Driver specific routines ...
 *
 */
392

393
394
static inline int __isicom_paranoia_check(struct isi_port const *port,
	char *name, const char *routine)
Linus Torvalds's avatar
Linus Torvalds committed
395
396
{
	if (!port) {
397
398
		printk(KERN_WARNING "ISICOM: Warning: bad isicom magic for "
			"dev %s in %s.\n", name, routine);
Linus Torvalds's avatar
Linus Torvalds committed
399
400
401
		return 1;
	}
	if (port->magic != ISICOM_MAGIC) {
402
403
		printk(KERN_WARNING "ISICOM: Warning: NULL isicom port for "
			"dev %s in %s.\n", name, routine);
Linus Torvalds's avatar
Linus Torvalds committed
404
		return 1;
405
	}
406

Linus Torvalds's avatar
Linus Torvalds committed
407
408
	return 0;
}
409

Linus Torvalds's avatar
Linus Torvalds committed
410
/*
411
 *	Transmitter.
Linus Torvalds's avatar
Linus Torvalds committed
412
413
414
415
416
417
418
419
420
 *
 *	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)
{
	short count = (BOARD_COUNT-1), card, base;
	short txcount, wrd, residue, word_count, cnt;
421
422
423
	struct isi_port *port;
	struct tty_struct *tty;

Linus Torvalds's avatar
Linus Torvalds committed
424
425
426
	/*	find next active board	*/
	card = (prev_card + 1) & 0x0003;
	while(count-- > 0) {
427
		if (isi_card[card].status & BOARD_ACTIVE)
Linus Torvalds's avatar
Linus Torvalds committed
428
			break;
429
		card = (card + 1) & 0x0003;
Linus Torvalds's avatar
Linus Torvalds committed
430
431
432
	}
	if (!(isi_card[card].status & BOARD_ACTIVE))
		goto sched_again;
433

Linus Torvalds's avatar
Linus Torvalds committed
434
	prev_card = card;
435

Linus Torvalds's avatar
Linus Torvalds committed
436
437
438
439
440
441
442
443
	count = isi_card[card].port_count;
	port = isi_card[card].ports;
	base = isi_card[card].base;
	for (;count > 0;count--, port++) {
		if (!lock_card_at_interrupt(&isi_card[card]))
			continue;
		/* port not active or tx disabled to force flow control */
		if (!(port->flags & ASYNC_INITIALIZED) ||
444
				!(port->status & ISI_TXOK))
Linus Torvalds's avatar
Linus Torvalds committed
445
446
			unlock_card(&isi_card[card]);
			continue;
447

Linus Torvalds's avatar
Linus Torvalds committed
448
		tty = port->tty;
449
450
451


		if (tty == NULL) {
Linus Torvalds's avatar
Linus Torvalds committed
452
453
454
			unlock_card(&isi_card[card]);
			continue;
		}
455

Linus Torvalds's avatar
Linus Torvalds committed
456
457
458
459
460
461
462
		txcount = min_t(short, TX_SIZE, port->xmit_cnt);
		if (txcount <= 0 || tty->stopped || tty->hw_stopped) {
			unlock_card(&isi_card[card]);
			continue;
		}
		if (!(inw(base + 0x02) & (1 << port->channel))) {
			unlock_card(&isi_card[card]);
463
			continue;
Linus Torvalds's avatar
Linus Torvalds committed
464
		}
465
466
467
468
		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
469
		residue = NO;
470
		wrd = 0;
Linus Torvalds's avatar
Linus Torvalds committed
471
		while (1) {
472
473
			cnt = min_t(int, txcount, (SERIAL_XMIT_SIZE
					- port->xmit_tail));
Linus Torvalds's avatar
Linus Torvalds committed
474
475
476
			if (residue == YES) {
				residue = NO;
				if (cnt > 0) {
477
478
479
480
					wrd |= (port->xmit_buf[port->xmit_tail]
									<< 8);
					port->xmit_tail = (port->xmit_tail + 1)
						& (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
481
482
483
					port->xmit_cnt--;
					txcount--;
					cnt--;
484
					outw(wrd, base);
485
				} else {
Linus Torvalds's avatar
Linus Torvalds committed
486
487
488
					outw(wrd, base);
					break;
				}
489
			}
Linus Torvalds's avatar
Linus Torvalds committed
490
491
			if (cnt <= 0) break;
			word_count = cnt >> 1;
492
493
494
			outsw(base, port->xmit_buf+port->xmit_tail,word_count);
			port->xmit_tail = (port->xmit_tail
				+ (word_count << 1)) & (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
495
496
497
498
499
			txcount -= (word_count << 1);
			port->xmit_cnt -= (word_count << 1);
			if (cnt & 0x0001) {
				residue = YES;
				wrd = port->xmit_buf[port->xmit_tail];
500
501
				port->xmit_tail = (port->xmit_tail + 1)
					& (SERIAL_XMIT_SIZE - 1);
Linus Torvalds's avatar
Linus Torvalds committed
502
503
504
505
506
507
508
509
510
				port->xmit_cnt--;
				txcount--;
			}
		}

		InterruptTheCard(base);
		if (port->xmit_cnt <= 0)
			port->status &= ~ISI_TXOK;
		if (port->xmit_cnt <= WAKEUP_CHARS)
511
			tty_wakeup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
512
		unlock_card(&isi_card[card]);
513
	}
Linus Torvalds's avatar
Linus Torvalds committed
514

515
516
	/*	schedule another tx for hopefully in about 10ms	*/
sched_again:
517
	if (!re_schedule) {
518
		complete(&isi_timerdone);
519
520
521
 		return;
	}

522
	mod_timer(&tx, jiffies + msecs_to_jiffies(10));
523
524
}

Linus Torvalds's avatar
Linus Torvalds committed
525
/*
526
 *	Main interrupt handler routine
Linus Torvalds's avatar
Linus Torvalds committed
527
 */
528

529
static irqreturn_t isicom_interrupt(int irq, void *dev_id)
Linus Torvalds's avatar
Linus Torvalds committed
530
{
531
	struct isi_board *card = dev_id;
532
533
	struct isi_port *port;
	struct tty_struct *tty;
534
535
	unsigned long base;
	u16 header, word_count, count, channel;
Linus Torvalds's avatar
Linus Torvalds committed
536
	short byte_count;
Alan Cox's avatar
Alan Cox committed
537
	unsigned char *rp;
538

Linus Torvalds's avatar
Linus Torvalds committed
539
540
	if (!card || !(card->status & FIRMWARE_LOADED))
		return IRQ_NONE;
541

Linus Torvalds's avatar
Linus Torvalds committed
542
	base = card->base;
543
544
545
546
547

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

Linus Torvalds's avatar
Linus Torvalds committed
548
	spin_lock(&card->card_lock);
549

550
551
552
553
554
555
	/*
	 * disable any interrupts from the PCI card and lower the
	 * interrupt line
	 */
	outw(0x8000, base+0x04);
	ClearInterrupt(base);
556

Linus Torvalds's avatar
Linus Torvalds committed
557
558
559
560
561
562
	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) {
563
564
		printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%lx): "
			"%d(channel) > port_count.\n", base, channel+1);
565
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
566
		spin_unlock(&card->card_lock);
567
		return IRQ_HANDLED;
Linus Torvalds's avatar
Linus Torvalds committed
568
569
570
	}
	port = card->ports + channel;
	if (!(port->flags & ASYNC_INITIALIZED)) {
571
		outw(0x0000, base+0x04); /* enable interrupts */
572
		spin_unlock(&card->card_lock);
Linus Torvalds's avatar
Linus Torvalds committed
573
		return IRQ_HANDLED;
574
575
	}

Linus Torvalds's avatar
Linus Torvalds committed
576
577
578
579
580
581
582
583
584
	tty = port->tty;
	if (tty == NULL) {
		word_count = byte_count >> 1;
		while(byte_count > 1) {
			inw(base);
			byte_count -= 2;
		}
		if (byte_count & 0x01)
			inw(base);
585
		outw(0x0000, base+0x04); /* enable interrupts */
Linus Torvalds's avatar
Linus Torvalds committed
586
587
588
		spin_unlock(&card->card_lock);
		return IRQ_HANDLED;
	}
589

Linus Torvalds's avatar
Linus Torvalds committed
590
591
592
	if (header & 0x8000) {		/* Status Packet */
		header = inw(base);
		switch(header & 0xff) {
593
594
595
596
597
		case 0:	/* Change in EIA signals */
			if (port->flags & ASYNC_CHECK_CD) {
				if (port->status & ISI_DCD) {
					if (!(header & ISI_DCD)) {
					/* Carrier has been lost  */
598
599
						pr_dbg("interrupt: DCD->low.\n"
							);
600
						port->status &= ~ISI_DCD;
601
						tty_hangup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
602
					}
603
604
605
606
607
				} else if (header & ISI_DCD) {
				/* Carrier has been detected */
					pr_dbg("interrupt: DCD->high.\n");
					port->status |= ISI_DCD;
					wake_up_interruptible(&port->open_wait);
Linus Torvalds's avatar
Linus Torvalds committed
608
				}
609
			} else {
610
611
612
613
614
615
616
617
618
619
620
				if (header & ISI_DCD)
					port->status |= ISI_DCD;
				else
					port->status &= ~ISI_DCD;
			}

			if (port->flags & ASYNC_CTS_FLOW) {
				if (port->tty->hw_stopped) {
					if (header & ISI_CTS) {
						port->tty->hw_stopped = 0;
						/* start tx ing */
621
622
						port->status |= (ISI_TXOK
							| ISI_CTS);
623
						tty_wakeup(tty);
Linus Torvalds's avatar
Linus Torvalds committed
624
					}
625
626
627
628
				} else if (!(header & ISI_CTS)) {
					port->tty->hw_stopped = 1;
					/* stop tx ing */
					port->status &= ~(ISI_TXOK | ISI_CTS);
Linus Torvalds's avatar
Linus Torvalds committed
629
				}
630
			} else {
631
632
				if (header & ISI_CTS)
					port->status |= ISI_CTS;
Linus Torvalds's avatar
Linus Torvalds committed
633
				else
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
					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;

649
		case 1:	/* Received Break !!! */
650
651
652
653
654
655
656
			tty_insert_flip_char(tty, 0, TTY_BREAK);
			if (port->flags & ASYNC_SAK)
				do_SAK(tty);
			tty_flip_buffer_push(tty);
			break;

		case 2:	/* Statistics		 */
657
			pr_dbg("isicom_interrupt: stats!!!.\n");
658
659
660
			break;

		default:
661
			pr_dbg("Intr: Unknown code in status packet.\n");
662
663
			break;
		}
664
	} else {				/* Data   Packet */
Alan Cox's avatar
Alan Cox committed
665
666

		count = tty_prepare_flip_string(tty, &rp, byte_count & ~1);
667
		pr_dbg("Intr: Can rx %d of %d bytes.\n", count, byte_count);
Linus Torvalds's avatar
Linus Torvalds committed
668
		word_count = count >> 1;
Alan Cox's avatar
Alan Cox committed
669
		insw(base, rp, word_count);
Linus Torvalds's avatar
Linus Torvalds committed
670
671
		byte_count -= (word_count << 1);
		if (count & 0x0001) {
672
673
			tty_insert_flip_char(tty,  inw(base) & 0xff,
				TTY_NORMAL);
Linus Torvalds's avatar
Linus Torvalds committed
674
			byte_count -= 2;
675
		}
Linus Torvalds's avatar
Linus Torvalds committed
676
		if (byte_count > 0) {
677
678
			pr_dbg("Intr(0x%lx:%d): Flip buffer overflow! dropping "
				"bytes...\n", base, channel + 1);
Linus Torvalds's avatar
Linus Torvalds committed
679
680
681
682
683
			while(byte_count > 0) { /* drain out unread xtra data */
				inw(base);
				byte_count -= 2;
			}
		}
Alan Cox's avatar
Alan Cox committed
684
		tty_flip_buffer_push(tty);
Linus Torvalds's avatar
Linus Torvalds committed
685
	}
686
	outw(0x0000, base+0x04); /* enable interrupts */
687
	spin_unlock(&card->card_lock);
688

Linus Torvalds's avatar
Linus Torvalds committed
689
	return IRQ_HANDLED;
690
}
Linus Torvalds's avatar
Linus Torvalds committed
691

692
static void isicom_config_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
693
{
694
695
	struct isi_board *card = port->card;
	struct tty_struct *tty;
Linus Torvalds's avatar
Linus Torvalds committed
696
	unsigned long baud;
697
698
699
	unsigned long base = card->base;
	u16 channel_setup, channel = port->channel,
		shift_count = card->shift_count;
Linus Torvalds's avatar
Linus Torvalds committed
700
	unsigned char flow_ctrl;
701

Linus Torvalds's avatar
Linus Torvalds committed
702
703
704
705
706
	if (!(tty = port->tty) || !tty->termios)
		return;
	baud = C_BAUD(tty);
	if (baud & CBAUDEX) {
		baud &= ~CBAUDEX;
707

Linus Torvalds's avatar
Linus Torvalds committed
708
709
710
		/*  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.
711
712
		 */

713
714
		/* 1,2,3,4 => 57.6, 115.2, 230, 460 kbps resp. */
		if (baud < 1 || baud > 4)
Linus Torvalds's avatar
Linus Torvalds committed
715
716
717
			port->tty->termios->c_cflag &= ~CBAUDEX;
		else
			baud += 15;
718
	}
Linus Torvalds's avatar
Linus Torvalds committed
719
	if (baud == 15) {
720
721

		/*  the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set
Linus Torvalds's avatar
Linus Torvalds committed
722
723
		 *  by the set_serial_info ioctl ... this is done by
		 *  the 'setserial' utility.
724
725
		 */

Linus Torvalds's avatar
Linus Torvalds committed
726
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
727
			baud++; /*  57.6 Kbps */
Linus Torvalds's avatar
Linus Torvalds committed
728
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
729
			baud +=2; /*  115  Kbps */
730
731
732
733
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
			baud += 3; /* 230 kbps*/
		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
			baud += 4; /* 460 kbps*/
Linus Torvalds's avatar
Linus Torvalds committed
734
735
736
	}
	if (linuxb_to_isib[baud] == -1) {
		/* hang up */
737
738
739
740
		drop_dtr(port);
		return;
	}
	else
Linus Torvalds's avatar
Linus Torvalds committed
741
		raise_dtr(port);
742

743
	if (WaitTillCardIsFree(base) == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
744
745
746
747
		outw(0x8000 | (channel << shift_count) |0x03, base);
		outw(linuxb_to_isib[baud] << 8 | 0x03, base);
		channel_setup = 0;
		switch(C_CSIZE(tty)) {
748
749
750
751
752
753
754
755
756
757
758
759
		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
760
		}
761

Linus Torvalds's avatar
Linus Torvalds committed
762
763
764
765
766
		if (C_CSTOPB(tty))
			channel_setup |= ISICOM_2SB;
		if (C_PARENB(tty)) {
			channel_setup |= ISICOM_EVPAR;
			if (C_PARODD(tty))
767
				channel_setup |= ISICOM_ODPAR;
Linus Torvalds's avatar
Linus Torvalds committed
768
		}
769
		outw(channel_setup, base);
Linus Torvalds's avatar
Linus Torvalds committed
770
		InterruptTheCard(base);
771
	}
Linus Torvalds's avatar
Linus Torvalds committed
772
773
774
	if (C_CLOCAL(tty))
		port->flags &= ~ASYNC_CHECK_CD;
	else
775
776
		port->flags |= ASYNC_CHECK_CD;

Linus Torvalds's avatar
Linus Torvalds committed
777
778
779
780
781
782
	/* flow control settings ...*/
	flow_ctrl = 0;
	port->flags &= ~ASYNC_CTS_FLOW;
	if (C_CRTSCTS(tty)) {
		port->flags |= ASYNC_CTS_FLOW;
		flow_ctrl |= ISICOM_CTSRTS;
783
784
	}
	if (I_IXON(tty))
Linus Torvalds's avatar
Linus Torvalds committed
785
786
		flow_ctrl |= ISICOM_RESPOND_XONXOFF;
	if (I_IXOFF(tty))
787
788
		flow_ctrl |= ISICOM_INITIATE_XONXOFF;

789
	if (WaitTillCardIsFree(base) == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
790
791
792
793
794
		outw(0x8000 | (channel << shift_count) |0x04, base);
		outw(flow_ctrl << 8 | 0x05, base);
		outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
		InterruptTheCard(base);
	}
795

Linus Torvalds's avatar
Linus Torvalds committed
796
797
798
799
800
801
802
	/*	rx enabled -> enable port for rx on the card	*/
	if (C_CREAD(tty)) {
		card->port_status |= (1 << channel);
		outw(card->port_status, base + 0x02);
	}
}

803
804
805
/* open et all */

static inline void isicom_setup_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
806
807
{
	int channel;
808
	struct isi_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
809
	unsigned long flags;
810

Linus Torvalds's avatar
Linus Torvalds committed
811
812
813
814
815
816
817
	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;
818
	for (channel = 0; channel < bp->port_count; channel++, port++)
Linus Torvalds's avatar
Linus Torvalds committed
819
		drop_dtr_rts(port);
820
	spin_unlock_irqrestore(&bp->card_lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed
821
}
822
823

static int isicom_setup_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
824
{
825
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
826
	unsigned long flags;
827

Linus Torvalds's avatar
Linus Torvalds committed
828
829
830
831
832
	if (port->flags & ASYNC_INITIALIZED) {
		return 0;
	}
	if (!port->xmit_buf) {
		unsigned long page;
833

Linus Torvalds's avatar
Linus Torvalds committed
834
835
		if (!(page = get_zeroed_page(GFP_KERNEL)))
			return -ENOMEM;
836

Linus Torvalds's avatar
Linus Torvalds committed
837
838
839
840
		if (port->xmit_buf) {
			free_page(page);
			return -ERESTARTSYS;
		}
841
842
		port->xmit_buf = (unsigned char *) page;
	}
Linus Torvalds's avatar
Linus Torvalds committed
843
844
845
846
847
848

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

Linus Torvalds's avatar
Linus Torvalds committed
850
	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
851

Linus Torvalds's avatar
Linus Torvalds committed
852
	/*	discard any residual data	*/
853
854
855
856
857
858
	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);
	}
859

Linus Torvalds's avatar
Linus Torvalds committed
860
861
862
	isicom_config_port(port);
	port->flags |= ASYNC_INITIALIZED;
	spin_unlock_irqrestore(&card->card_lock, flags);
863
864
865
866

	return 0;
}

867
868
static int block_til_ready(struct tty_struct *tty, struct file *filp,
	struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
869
{
870
	struct isi_board *card = port->card;
Linus Torvalds's avatar
Linus Torvalds committed
871
872
873
874
875
876
877
	int do_clocal = 0, retval;
	unsigned long flags;
	DECLARE_WAITQUEUE(wait, current);

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

	if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
878
		pr_dbg("block_til_ready: close in progress.\n");
Linus Torvalds's avatar
Linus Torvalds committed
879
880
881
882
883
884
		interruptible_sleep_on(&port->close_wait);
		if (port->flags & ASYNC_HUP_NOTIFY)
			return -EAGAIN;
		else
			return -ERESTARTSYS;
	}
885

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

888
889
	if ((filp->f_flags & O_NONBLOCK) ||
			(tty->flags & (1 << TTY_IO_ERROR))) {
890
		pr_dbg("block_til_ready: non-block mode.\n");
Linus Torvalds's avatar
Linus Torvalds committed
891
		port->flags |= ASYNC_NORMAL_ACTIVE;
892
893
894
		return 0;
	}

Linus Torvalds's avatar
Linus Torvalds committed
895
896
	if (C_CLOCAL(tty))
		do_clocal = 1;
897
898

	/* block waiting for DCD to be asserted, and while
Linus Torvalds's avatar
Linus Torvalds committed
899
900
901
902
903
904
905
906
907
						callout dev is busy */
	retval = 0;
	add_wait_queue(&port->open_wait, &wait);

	spin_lock_irqsave(&card->card_lock, flags);
	if (!tty_hung_up_p(filp))
		port->count--;
	port->blocked_open++;
	spin_unlock_irqrestore(&card->card_lock, flags);
908

Linus Torvalds's avatar
Linus Torvalds committed
909
910
911
912
	while (1) {
		raise_dtr_rts(port);

		set_current_state(TASK_INTERRUPTIBLE);
913
		if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
Linus Torvalds's avatar
Linus Torvalds committed
914
915
916
917
918
			if (port->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;
			break;
919
		}
Linus Torvalds's avatar
Linus Torvalds committed
920
		if (!(port->flags & ASYNC_CLOSING) &&
921
				(do_clocal || (port->status & ISI_DCD))) {
Linus Torvalds's avatar
Linus Torvalds committed
922
			break;
923
		}
Linus Torvalds's avatar
Linus Torvalds committed
924
925
926
927
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			break;
		}
928
		schedule();
Linus Torvalds's avatar
Linus Torvalds committed
929
930
931
932
933
934
935
936
937
938
939
940
941
	}
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&port->open_wait, &wait);
	spin_lock_irqsave(&card->card_lock, flags);
	if (!tty_hung_up_p(filp))
		port->count++;
	port->blocked_open--;
	spin_unlock_irqrestore(&card->card_lock, flags);
	if (retval)
		return retval;
	port->flags |= ASYNC_NORMAL_ACTIVE;
	return 0;
}
942
943

static int isicom_open(struct tty_struct *tty, struct file *filp)
Linus Torvalds's avatar
Linus Torvalds committed
944
{
945
946
	struct isi_port *port;
	struct isi_board *card;
947
948
	unsigned int board;
	int error, line;
Linus Torvalds's avatar
Linus Torvalds committed
949
950
951
952
953
954

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

Linus Torvalds's avatar
Linus Torvalds committed
956
957
	if (!(card->status & FIRMWARE_LOADED))
		return -ENODEV;
958

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

963
	port = &isi_ports[line];
Linus Torvalds's avatar
Linus Torvalds committed
964
965
	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
		return -ENODEV;
966
967
968

	isicom_setup_board(card);

Linus Torvalds's avatar
Linus Torvalds committed
969
970
971
972
973
974
975
976
	port->count++;
	tty->driver_data = port;
	port->tty = tty;
	if ((error = isicom_setup_port(port))!=0)
		return error;
	if ((error = block_til_ready(tty, filp, port))!=0)
		return error;

977
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
978
}
979

Linus Torvalds's avatar
Linus Torvalds committed
980
981
/* close et all */

982
static inline void isicom_shutdown_board(struct isi_board *bp)
Linus Torvalds's avatar
Linus Torvalds committed
983
984
985
986
987
988
{
	if (bp->status & BOARD_ACTIVE) {
		bp->status &= ~BOARD_ACTIVE;
	}
}

989
/* card->lock HAS to be held */
990
static void isicom_shutdown_port(struct isi_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
991
{
992
993
994
	struct isi_board *card = port->card;
	struct tty_struct *tty;

Linus Torvalds's avatar
Linus Torvalds committed
995
996
	tty = port->tty;

997
	if (!(port->flags & ASYNC_INITIALIZED))
Linus Torvalds's avatar
Linus Torvalds committed
998
		return;
999

Linus Torvalds's avatar
Linus Torvalds committed
1000
	if (port->xmit_buf) {
For faster browsing, not all history is shown. View entire blame