Files
linux-apfs/drivers/char/isicom.c
T

1920 lines
46 KiB
C
Raw Normal View History

2005-04-16 15:20:36 -07:00
/*
* 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.
2006-01-09 20:54:21 -08:00
* This is to prevent the firmware
2005-04-16 15:20:36 -07:00
* 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
2006-01-09 20:54:21 -08:00
* when the last port on the card is
2005-04-16 15:20:36 -07:00
* closed.
*
* 10/5/00 sameer Signal mask setup command added
2006-01-09 20:54:21 -08:00
* to isicom_setup_port and
2005-04-16 15:20:36 -07:00
* isicom_shutdown_port.
*
* 24/5/00 sameer The driver is now SMP aware.
2006-01-09 20:54:21 -08:00
*
*
2005-04-16 15:20:36 -07:00
* 27/11/00 Vinayak P Risbud Fixed the Driver Crash Problem
2006-01-09 20:54:21 -08:00
*
*
2005-04-16 15:20:36 -07:00
* 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
*
2006-01-09 20:54:21 -08:00
* 11/04/01 Kevin Fixed firmware load problem with
2005-04-16 15:20:36 -07:00
* ISIHP-4X card
2006-01-09 20:54:21 -08:00
*
2005-04-16 15:20:36 -07:00
* 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
2006-01-09 20:54:21 -08:00
* info during insmod as well as module
2005-04-16 15:20:36 -07:00
* listing by lsmod.
2006-01-09 20:54:21 -08:00
*
2005-04-16 15:20:36 -07:00
* 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.
2006-01-09 20:54:21 -08:00
*
2005-04-16 15:20:36 -07:00
* 09/06/01 acme@conectiva.com.br use capable, not suser, do
* restore_flags on failure in
* isicom_send_break, verify put_user
* result
*
2006-01-09 20:54:21 -08:00
* 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
2005-04-16 15:20:36 -07:00
* Red Hat Distribution
*
* 06/01/05 Alan Cox Merged the ISI and base kernel strands
* into a single 2.6 driver
*
* ***********************************************************
*
2006-01-09 20:54:21 -08:00
* To use this driver you also need the support package. You
2005-04-16 15:20:36 -07:00
* can find this in RPM format on
* ftp://ftp.linux.org.uk/pub/linux/alan
2006-01-09 20:54:21 -08:00
*
2005-04-16 15:20:36 -07:00
* 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>
2006-01-09 20:54:25 -08:00
#include <linux/firmware.h>
2005-04-16 15:20:36 -07:00
#include <linux/kernel.h>
#include <linux/tty.h>
2006-01-09 20:54:13 -08:00
#include <linux/tty_flip.h>
2005-04-16 15:20:36 -07:00
#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>
2006-01-09 20:54:23 -08:00
#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
2006-01-09 20:54:24 -08:00
static int isicom_probe(struct pci_dev *, const struct pci_device_id *);
static void __devexit isicom_remove(struct pci_dev *);
2005-04-16 15:20:36 -07:00
static struct pci_device_id isicom_pci_tbl[] = {
2006-01-09 20:54:24 -08:00
{ 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) },
2005-04-16 15:20:36 -07:00
{ 0 }
};
MODULE_DEVICE_TABLE(pci, isicom_pci_tbl);
2006-01-09 20:54:24 -08:00
static struct pci_driver isicom_driver = {
.name = "isicom",
.id_table = isicom_pci_tbl,
.probe = isicom_probe,
.remove = __devexit_p(isicom_remove)
};
2005-04-16 15:20:36 -07:00
static int prev_card = 3; /* start servicing isi_card[0] */
static struct tty_driver *isicom_normal;
2006-12-08 02:38:55 -08:00
static DECLARE_COMPLETION(isi_timerdone);
2005-04-16 15:20:36 -07:00
static char re_schedule = 1;
static void isicom_tx(unsigned long _data);
2006-01-09 20:54:21 -08:00
static void isicom_start(struct tty_struct *tty);
2005-04-16 15:20:36 -07:00
2006-12-08 02:38:55 -08:00
static DEFINE_TIMER(tx, isicom_tx, 0, 0);
2005-04-16 15:20:36 -07:00
/* baud index mappings from linux defns to isi */
static signed char linuxb_to_isib[] = {
2007-02-10 01:44:54 -08:00
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21
2005-04-16 15:20:36 -07:00
};
struct isi_board {
unsigned long base;
2005-04-16 15:20:36 -07:00
unsigned char irq;
unsigned char port_count;
unsigned short status;
unsigned short port_status; /* each bit for each port */
2005-04-16 15:20:36 -07:00
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;
unsigned int index;
2005-04-16 15:20:36 -07:00
};
struct isi_port {
unsigned short magic;
unsigned int flags;
int count;
int blocked_open;
int close_delay;
u16 channel;
u16 status;
u16 closing_wait;
2005-04-16 15:20:36 -07:00
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.
*/
2006-01-09 20:54:21 -08:00
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);
}
2005-04-16 15:20:36 -07:00
static int lock_card(struct isi_board *card)
{
char retries;
unsigned long base = card->base;
2005-04-16 15:20:36 -07:00
for (retries = 0; retries < 100; retries++) {
spin_lock_irqsave(&card->card_lock, card->flags);
if (inw(base + 0xe) & 0x1) {
2006-01-09 20:54:21 -08:00
return 1;
2005-04-16 15:20:36 -07:00
} else {
spin_unlock_irqrestore(&card->card_lock, card->flags);
udelay(1000); /* 1ms */
}
}
printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%lx)\n",
card->base);
2006-06-30 18:23:04 +02:00
return 0; /* Failed to acquire the card! */
2005-04-16 15:20:36 -07:00
}
static int lock_card_at_interrupt(struct isi_board *card)
{
unsigned char retries;
unsigned long base = card->base;
2005-04-16 15:20:36 -07:00
for (retries = 0; retries < 200; retries++) {
spin_lock_irqsave(&card->card_lock, card->flags);
if (inw(base + 0xe) & 0x1)
2006-01-09 20:54:21 -08:00
return 1;
2005-04-16 15:20:36 -07:00
else
spin_unlock_irqrestore(&card->card_lock, card->flags);
}
/* Failing in interrupt is an acceptable event */
2006-06-30 18:23:04 +02:00
return 0; /* Failed to acquire the card! */
2005-04-16 15:20:36 -07:00
}
static void unlock_card(struct isi_board *card)
{
spin_unlock_irqrestore(&card->card_lock, card->flags);
}
/*
* ISI Card specific ops ...
*/
2006-01-09 20:54:21 -08:00
/* card->lock HAS to be held */
2006-01-09 20:54:21 -08:00
static void raise_dtr(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
unsigned long base = card->base;
u16 channel = port->channel;
2005-04-16 15:20:36 -07:00
if (WaitTillCardIsFree(base))
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
outw(0x8000 | (channel << card->shift_count) | 0x02, base);
2005-04-16 15:20:36 -07:00
outw(0x0504, base);
InterruptTheCard(base);
port->status |= ISI_DTR;
}
/* card->lock HAS to be held */
2006-01-09 20:54:21 -08:00
static inline void drop_dtr(struct isi_port *port)
{
struct isi_board *card = port->card;
unsigned long base = card->base;
u16 channel = port->channel;
2005-04-16 15:20:36 -07:00
if (WaitTillCardIsFree(base))
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
outw(0x8000 | (channel << card->shift_count) | 0x02, base);
2005-04-16 15:20:36 -07:00
outw(0x0404, base);
2006-01-09 20:54:21 -08:00
InterruptTheCard(base);
2005-04-16 15:20:36 -07:00
port->status &= ~ISI_DTR;
}
/* card->lock HAS to be held */
2006-01-09 20:54:21 -08:00
static inline void raise_rts(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
unsigned long base = card->base;
u16 channel = port->channel;
2005-04-16 15:20:36 -07:00
if (WaitTillCardIsFree(base))
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
outw(0x8000 | (channel << card->shift_count) | 0x02, base);
2005-04-16 15:20:36 -07:00
outw(0x0a04, base);
2006-01-09 20:54:21 -08:00
InterruptTheCard(base);
2005-04-16 15:20:36 -07:00
port->status |= ISI_RTS;
}
/* card->lock HAS to be held */
2006-01-09 20:54:21 -08:00
static inline void drop_rts(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
unsigned long base = card->base;
u16 channel = port->channel;
2005-04-16 15:20:36 -07:00
if (WaitTillCardIsFree(base))
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
outw(0x8000 | (channel << card->shift_count) | 0x02, base);
2005-04-16 15:20:36 -07:00
outw(0x0804, base);
2006-01-09 20:54:21 -08:00
InterruptTheCard(base);
2005-04-16 15:20:36 -07:00
port->status &= ~ISI_RTS;
}
/* card->lock MUST NOT be held */
2006-01-09 20:54:21 -08:00
static inline void raise_dtr_rts(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
unsigned long base = card->base;
u16 channel = port->channel;
2005-04-16 15:20:36 -07:00
if (!lock_card(card))
return;
2006-01-09 20:54:21 -08:00
outw(0x8000 | (channel << card->shift_count) | 0x02, base);
2005-04-16 15:20:36 -07:00
outw(0x0f04, base);
InterruptTheCard(base);
port->status |= (ISI_DTR | ISI_RTS);
unlock_card(card);
}
/* card->lock HAS to be held */
2006-01-09 20:54:21 -08:00
static void drop_dtr_rts(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
unsigned long base = card->base;
u16 channel = port->channel;
2005-04-16 15:20:36 -07:00
if (WaitTillCardIsFree(base))
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
outw(0x8000 | (channel << card->shift_count) | 0x02, base);
2005-04-16 15:20:36 -07:00
outw(0x0c04, base);
2006-01-09 20:54:21 -08:00
InterruptTheCard(base);
2005-04-16 15:20:36 -07:00
port->status &= ~(ISI_RTS | ISI_DTR);
}
/*
* ISICOM Driver specific routines ...
*
*/
2006-01-09 20:54:21 -08:00
2006-01-09 20:54:23 -08:00
static inline int __isicom_paranoia_check(struct isi_port const *port,
char *name, const char *routine)
2005-04-16 15:20:36 -07:00
{
if (!port) {
2006-01-09 20:54:23 -08:00
printk(KERN_WARNING "ISICOM: Warning: bad isicom magic for "
"dev %s in %s.\n", name, routine);
2005-04-16 15:20:36 -07:00
return 1;
}
if (port->magic != ISICOM_MAGIC) {
2006-01-09 20:54:23 -08:00
printk(KERN_WARNING "ISICOM: Warning: NULL isicom port for "
"dev %s in %s.\n", name, routine);
2005-04-16 15:20:36 -07:00
return 1;
2006-01-09 20:54:21 -08:00
}
2006-01-09 20:54:23 -08:00
2005-04-16 15:20:36 -07:00
return 0;
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/*
2006-01-09 20:54:21 -08:00
* Transmitter.
2005-04-16 15:20:36 -07:00
*
* 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;
2006-01-09 20:54:21 -08:00
struct isi_port *port;
struct tty_struct *tty;
2005-04-16 15:20:36 -07:00
/* find next active board */
card = (prev_card + 1) & 0x0003;
while(count-- > 0) {
2006-01-09 20:54:21 -08:00
if (isi_card[card].status & BOARD_ACTIVE)
2005-04-16 15:20:36 -07:00
break;
2006-01-09 20:54:21 -08:00
card = (card + 1) & 0x0003;
2005-04-16 15:20:36 -07:00
}
if (!(isi_card[card].status & BOARD_ACTIVE))
goto sched_again;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
prev_card = card;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
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) ||
2006-01-09 20:54:21 -08:00
!(port->status & ISI_TXOK))
2005-04-16 15:20:36 -07:00
unlock_card(&isi_card[card]);
continue;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
tty = port->tty;
2006-01-09 20:54:21 -08:00
if (tty == NULL) {
2005-04-16 15:20:36 -07:00
unlock_card(&isi_card[card]);
continue;
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
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]);
2006-01-09 20:54:21 -08:00
continue;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:23 -08:00
pr_dbg("txing %d bytes, port%d.\n", txcount,
port->channel + 1);
outw((port->channel << isi_card[card].shift_count) | txcount,
base);
2005-04-16 15:20:36 -07:00
residue = NO;
2006-01-09 20:54:21 -08:00
wrd = 0;
2005-04-16 15:20:36 -07:00
while (1) {
cnt = min_t(int, txcount, (SERIAL_XMIT_SIZE
- port->xmit_tail));
2005-04-16 15:20:36 -07:00
if (residue == YES) {
residue = NO;
if (cnt > 0) {
wrd |= (port->xmit_buf[port->xmit_tail]
<< 8);
port->xmit_tail = (port->xmit_tail + 1)
& (SERIAL_XMIT_SIZE - 1);
2005-04-16 15:20:36 -07:00
port->xmit_cnt--;
txcount--;
cnt--;
2006-01-09 20:54:21 -08:00
outw(wrd, base);
} else {
2005-04-16 15:20:36 -07:00
outw(wrd, base);
break;
}
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (cnt <= 0) break;
word_count = cnt >> 1;
outsw(base, port->xmit_buf+port->xmit_tail,word_count);
port->xmit_tail = (port->xmit_tail
+ (word_count << 1)) & (SERIAL_XMIT_SIZE - 1);
2005-04-16 15:20:36 -07:00
txcount -= (word_count << 1);
port->xmit_cnt -= (word_count << 1);
if (cnt & 0x0001) {
residue = YES;
wrd = port->xmit_buf[port->xmit_tail];
port->xmit_tail = (port->xmit_tail + 1)
& (SERIAL_XMIT_SIZE - 1);
2005-04-16 15:20:36 -07:00
port->xmit_cnt--;
txcount--;
}
}
InterruptTheCard(base);
if (port->xmit_cnt <= 0)
port->status &= ~ISI_TXOK;
if (port->xmit_cnt <= WAKEUP_CHARS)
tty_wakeup(tty);
2005-04-16 15:20:36 -07:00
unlock_card(&isi_card[card]);
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
2006-01-09 20:54:21 -08:00
/* schedule another tx for hopefully in about 10ms */
sched_again:
2006-01-09 20:54:23 -08:00
if (!re_schedule) {
2006-12-08 02:38:55 -08:00
complete(&isi_timerdone);
2006-01-09 20:54:23 -08:00
return;
}
2006-12-08 02:38:55 -08:00
mod_timer(&tx, jiffies + msecs_to_jiffies(10));
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
/*
2006-01-09 20:54:21 -08:00
* Main interrupt handler routine
2005-04-16 15:20:36 -07:00
*/
2006-01-09 20:54:21 -08:00
static irqreturn_t isicom_interrupt(int irq, void *dev_id)
2005-04-16 15:20:36 -07:00
{
struct isi_board *card = dev_id;
2006-01-09 20:54:21 -08:00
struct isi_port *port;
struct tty_struct *tty;
unsigned long base;
u16 header, word_count, count, channel;
2005-04-16 15:20:36 -07:00
short byte_count;
2006-01-09 20:54:13 -08:00
unsigned char *rp;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (!card || !(card->status & FIRMWARE_LOADED))
return IRQ_NONE;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
base = card->base;
/* did the card interrupt us? */
if (!(inw(base + 0x0e) & 0x02))
return IRQ_NONE;
2005-04-16 15:20:36 -07:00
spin_lock(&card->card_lock);
2006-01-09 20:54:21 -08:00
2006-12-08 02:38:51 -08:00
/*
* disable any interrupts from the PCI card and lower the
* interrupt line
*/
outw(0x8000, base+0x04);
ClearInterrupt(base);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
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) {
printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%lx): "
"%d(channel) > port_count.\n", base, channel+1);
2006-12-08 02:38:51 -08:00
outw(0x0000, base+0x04); /* enable interrupts */
2005-04-16 15:20:36 -07:00
spin_unlock(&card->card_lock);
2006-01-09 20:54:21 -08:00
return IRQ_HANDLED;
2005-04-16 15:20:36 -07:00
}
port = card->ports + channel;
if (!(port->flags & ASYNC_INITIALIZED)) {
2006-12-08 02:38:51 -08:00
outw(0x0000, base+0x04); /* enable interrupts */
2007-02-10 01:44:52 -08:00
spin_unlock(&card->card_lock);
2005-04-16 15:20:36 -07:00
return IRQ_HANDLED;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
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);
2006-12-08 02:38:51 -08:00
outw(0x0000, base+0x04); /* enable interrupts */
2005-04-16 15:20:36 -07:00
spin_unlock(&card->card_lock);
return IRQ_HANDLED;
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (header & 0x8000) { /* Status Packet */
header = inw(base);
switch(header & 0xff) {
2006-01-09 20:54:21 -08:00
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 */
pr_dbg("interrupt: DCD->low.\n"
);
2005-04-16 15:20:36 -07:00
port->status &= ~ISI_DCD;
tty_hangup(tty);
2005-04-16 15:20:36 -07:00
}
} 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);
2005-04-16 15:20:36 -07:00
}
} else {
2006-01-09 20:54:21 -08:00
if (header & ISI_DCD)
port->status |= ISI_DCD;
2005-04-16 15:20:36 -07:00
else
2006-01-09 20:54:21 -08:00
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 */
port->status |= (ISI_TXOK
| ISI_CTS);
tty_wakeup(tty);
2006-01-09 20:54:21 -08:00
}
} else if (!(header & ISI_CTS)) {
port->tty->hw_stopped = 1;
/* stop tx ing */
port->status &= ~(ISI_TXOK | ISI_CTS);
2006-01-09 20:54:21 -08:00
}
} else {
2006-01-09 20:54:21 -08:00
if (header & ISI_CTS)
port->status |= ISI_CTS;
2005-04-16 15:20:36 -07:00
else
2006-01-09 20:54:21 -08:00
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;
case 1: /* Received Break !!! */
2006-01-09 20:54:21 -08:00
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 */
2006-01-09 20:54:23 -08:00
pr_dbg("isicom_interrupt: stats!!!.\n");
2006-01-09 20:54:21 -08:00
break;
default:
2006-01-09 20:54:23 -08:00
pr_dbg("Intr: Unknown code in status packet.\n");
2006-01-09 20:54:21 -08:00
break;
}
} else { /* Data Packet */
2006-01-09 20:54:13 -08:00
count = tty_prepare_flip_string(tty, &rp, byte_count & ~1);
2006-01-09 20:54:23 -08:00
pr_dbg("Intr: Can rx %d of %d bytes.\n", count, byte_count);
2005-04-16 15:20:36 -07:00
word_count = count >> 1;
2006-01-09 20:54:13 -08:00
insw(base, rp, word_count);
2005-04-16 15:20:36 -07:00
byte_count -= (word_count << 1);
if (count & 0x0001) {
tty_insert_flip_char(tty, inw(base) & 0xff,
TTY_NORMAL);
2005-04-16 15:20:36 -07:00
byte_count -= 2;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (byte_count > 0) {
2006-01-09 20:54:23 -08:00
pr_dbg("Intr(0x%lx:%d): Flip buffer overflow! dropping "
"bytes...\n", base, channel + 1);
2005-04-16 15:20:36 -07:00
while(byte_count > 0) { /* drain out unread xtra data */
inw(base);
byte_count -= 2;
}
}
2006-01-09 20:54:13 -08:00
tty_flip_buffer_push(tty);
2005-04-16 15:20:36 -07:00
}
2006-12-08 02:38:51 -08:00
outw(0x0000, base+0x04); /* enable interrupts */
2007-02-10 01:44:52 -08:00
spin_unlock(&card->card_lock);
2005-04-16 15:20:36 -07:00
return IRQ_HANDLED;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
2006-01-09 20:54:21 -08:00
static void isicom_config_port(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
struct tty_struct *tty;
2005-04-16 15:20:36 -07:00
unsigned long baud;
unsigned long base = card->base;
u16 channel_setup, channel = port->channel,
shift_count = card->shift_count;
2005-04-16 15:20:36 -07:00
unsigned char flow_ctrl;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (!(tty = port->tty) || !tty->termios)
return;
baud = C_BAUD(tty);
if (baud & CBAUDEX) {
baud &= ~CBAUDEX;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* 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.
2006-01-09 20:54:21 -08:00
*/
2007-02-10 01:44:54 -08:00
/* 1,2,3,4 => 57.6, 115.2, 230, 460 kbps resp. */
if (baud < 1 || baud > 4)
2005-04-16 15:20:36 -07:00
port->tty->termios->c_cflag &= ~CBAUDEX;
else
baud += 15;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (baud == 15) {
2006-01-09 20:54:21 -08:00
/* the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set
2005-04-16 15:20:36 -07:00
* by the set_serial_info ioctl ... this is done by
* the 'setserial' utility.
2006-01-09 20:54:21 -08:00
*/
2005-04-16 15:20:36 -07:00
if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
2006-01-09 20:54:21 -08:00
baud++; /* 57.6 Kbps */
2005-04-16 15:20:36 -07:00
if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
2006-01-09 20:54:21 -08:00
baud +=2; /* 115 Kbps */
2007-02-10 01:44:54 -08:00
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*/
2005-04-16 15:20:36 -07:00
}
if (linuxb_to_isib[baud] == -1) {
/* hang up */
2006-01-09 20:54:21 -08:00
drop_dtr(port);
return;
}
else
2005-04-16 15:20:36 -07:00
raise_dtr(port);
2006-01-09 20:54:21 -08:00
if (WaitTillCardIsFree(base) == 0) {
2005-04-16 15:20:36 -07:00
outw(0x8000 | (channel << shift_count) |0x03, base);
outw(linuxb_to_isib[baud] << 8 | 0x03, base);
channel_setup = 0;
switch(C_CSIZE(tty)) {
2006-01-09 20:54:21 -08:00
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;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (C_CSTOPB(tty))
channel_setup |= ISICOM_2SB;
if (C_PARENB(tty)) {
channel_setup |= ISICOM_EVPAR;
if (C_PARODD(tty))
2006-01-09 20:54:21 -08:00
channel_setup |= ISICOM_ODPAR;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
outw(channel_setup, base);
2005-04-16 15:20:36 -07:00
InterruptTheCard(base);
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (C_CLOCAL(tty))
port->flags &= ~ASYNC_CHECK_CD;
else
2006-01-09 20:54:21 -08:00
port->flags |= ASYNC_CHECK_CD;
2005-04-16 15:20:36 -07:00
/* flow control settings ...*/
flow_ctrl = 0;
port->flags &= ~ASYNC_CTS_FLOW;
if (C_CRTSCTS(tty)) {
port->flags |= ASYNC_CTS_FLOW;
flow_ctrl |= ISICOM_CTSRTS;
2006-01-09 20:54:21 -08:00
}
if (I_IXON(tty))
2005-04-16 15:20:36 -07:00
flow_ctrl |= ISICOM_RESPOND_XONXOFF;
if (I_IXOFF(tty))
2006-01-09 20:54:21 -08:00
flow_ctrl |= ISICOM_INITIATE_XONXOFF;
if (WaitTillCardIsFree(base) == 0) {
2005-04-16 15:20:36 -07:00
outw(0x8000 | (channel << shift_count) |0x04, base);
outw(flow_ctrl << 8 | 0x05, base);
outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
InterruptTheCard(base);
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* rx enabled -> enable port for rx on the card */
if (C_CREAD(tty)) {
card->port_status |= (1 << channel);
outw(card->port_status, base + 0x02);
}
}
2006-01-09 20:54:21 -08:00
/* open et all */
static inline void isicom_setup_board(struct isi_board *bp)
2005-04-16 15:20:36 -07:00
{
int channel;
2006-01-09 20:54:21 -08:00
struct isi_port *port;
2005-04-16 15:20:36 -07:00
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
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;
2006-01-09 20:54:21 -08:00
for (channel = 0; channel < bp->port_count; channel++, port++)
2005-04-16 15:20:36 -07:00
drop_dtr_rts(port);
spin_unlock_irqrestore(&bp->card_lock, flags);
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
static int isicom_setup_port(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (port->flags & ASYNC_INITIALIZED) {
return 0;
}
if (!port->xmit_buf) {
unsigned long page;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (!(page = get_zeroed_page(GFP_KERNEL)))
return -ENOMEM;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (port->xmit_buf) {
free_page(page);
return -ERESTARTSYS;
}
2006-01-09 20:54:21 -08:00
port->xmit_buf = (unsigned char *) page;
}
2005-04-16 15:20:36 -07:00
spin_lock_irqsave(&card->card_lock, flags);
if (port->tty)
clear_bit(TTY_IO_ERROR, &port->tty->flags);
if (port->count == 1)
card->count++;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* discard any residual data */
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);
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
isicom_config_port(port);
port->flags |= ASYNC_INITIALIZED;
spin_unlock_irqrestore(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
return 0;
}
static int block_til_ready(struct tty_struct *tty, struct file *filp,
struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
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) {
2006-01-09 20:54:23 -08:00
pr_dbg("block_til_ready: close in progress.\n");
2005-04-16 15:20:36 -07:00
interruptible_sleep_on(&port->close_wait);
if (port->flags & ASYNC_HUP_NOTIFY)
return -EAGAIN;
else
return -ERESTARTSYS;
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* if non-blocking mode is set ... */
2006-01-09 20:54:21 -08:00
if ((filp->f_flags & O_NONBLOCK) ||
(tty->flags & (1 << TTY_IO_ERROR))) {
2006-01-09 20:54:23 -08:00
pr_dbg("block_til_ready: non-block mode.\n");
2005-04-16 15:20:36 -07:00
port->flags |= ASYNC_NORMAL_ACTIVE;
2006-01-09 20:54:21 -08:00
return 0;
}
2005-04-16 15:20:36 -07:00
if (C_CLOCAL(tty))
do_clocal = 1;
2006-01-09 20:54:21 -08:00
/* block waiting for DCD to be asserted, and while
2005-04-16 15:20:36 -07:00
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);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
while (1) {
raise_dtr_rts(port);
set_current_state(TASK_INTERRUPTIBLE);
2006-01-09 20:54:21 -08:00
if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
2005-04-16 15:20:36 -07:00
if (port->flags & ASYNC_HUP_NOTIFY)
retval = -EAGAIN;
else
retval = -ERESTARTSYS;
break;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (!(port->flags & ASYNC_CLOSING) &&
2006-01-09 20:54:21 -08:00
(do_clocal || (port->status & ISI_DCD))) {
2005-04-16 15:20:36 -07:00
break;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
2006-01-09 20:54:21 -08:00
schedule();
2005-04-16 15:20:36 -07:00
}
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;
}
2006-01-09 20:54:21 -08:00
static int isicom_open(struct tty_struct *tty, struct file *filp)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_port *port;
struct isi_board *card;
2006-12-08 02:38:57 -08:00
unsigned int board;
int error, line;
2005-04-16 15:20:36 -07:00
line = tty->index;
if (line < 0 || line > PORT_COUNT-1)
return -ENODEV;
board = BOARD(line);
card = &isi_card[board];
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (!(card->status & FIRMWARE_LOADED))
return -ENODEV;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* open on a port greater than the port count for the card !!! */
if (line > ((board * 16) + card->port_count - 1))
return -ENODEV;
2006-01-09 20:54:21 -08:00
port = &isi_ports[line];
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_open"))
return -ENODEV;
2006-01-09 20:54:21 -08:00
isicom_setup_board(card);
2005-04-16 15:20:36 -07:00
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;
2006-01-09 20:54:21 -08:00
return 0;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* close et all */
2006-01-09 20:54:21 -08:00
static inline void isicom_shutdown_board(struct isi_board *bp)
2005-04-16 15:20:36 -07:00
{
if (bp->status & BOARD_ACTIVE) {
bp->status &= ~BOARD_ACTIVE;
}
}
/* card->lock HAS to be held */
2006-01-09 20:54:21 -08:00
static void isicom_shutdown_port(struct isi_port *port)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
struct tty_struct *tty;
2005-04-16 15:20:36 -07:00
tty = port->tty;
if (!(port->flags & ASYNC_INITIALIZED))
2005-04-16 15:20:36 -07:00
return;
2005-04-16 15:20:36 -07:00
if (port->xmit_buf) {
free_page((unsigned long) port->xmit_buf);
port->xmit_buf = NULL;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
port->flags &= ~ASYNC_INITIALIZED;
/* 3rd October 2000 : Vinayak P Risbud */
port->tty = NULL;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/*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
2006-01-09 20:54:21 -08:00
appears on the remote end. Now we drop the dtr only if the
2005-04-16 15:20:36 -07:00
HUPCL(Hangup on close) flag is set for the tty*/
2006-01-09 20:54:21 -08:00
if (C_HUPCL(tty))
2005-04-16 15:20:36 -07:00
/* drop dtr on this port */
drop_dtr(port);
2006-01-09 20:54:21 -08:00
/* any other port uninits */
2005-04-16 15:20:36 -07:00
if (tty)
set_bit(TTY_IO_ERROR, &tty->flags);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (--card->count < 0) {
2006-01-09 20:54:23 -08:00
pr_dbg("isicom_shutdown_port: bad board(0x%lx) count %d.\n",
2005-04-16 15:20:36 -07:00
card->base, card->count);
2006-01-09 20:54:21 -08:00
card->count = 0;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
/* last port was closed, shutdown that boad too */
2006-01-09 20:54:21 -08:00
if (C_HUPCL(tty)) {
2005-04-16 15:20:36 -07:00
if (!card->count)
isicom_shutdown_board(card);
}
}
2006-01-09 20:54:21 -08:00
static void isicom_close(struct tty_struct *tty, struct file *filp)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-11-15 00:30:17 +01:00
struct isi_board *card;
2005-04-16 15:20:36 -07:00
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (!port)
return;
2006-11-15 00:30:17 +01:00
card = port->card;
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_close"))
return;
2006-01-09 20:54:21 -08:00
2006-01-09 20:54:23 -08:00
pr_dbg("Close start!!!.\n");
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
spin_lock_irqsave(&card->card_lock, flags);
if (tty_hung_up_p(filp)) {
spin_unlock_irqrestore(&card->card_lock, flags);
return;
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (tty->count == 1 && port->count != 1) {
printk(KERN_WARNING "ISICOM:(0x%lx) isicom_close: bad port "
"count tty->count = 1 port count = %d.\n",
2005-04-16 15:20:36 -07:00
card->base, port->count);
port->count = 1;
}
if (--port->count < 0) {
printk(KERN_WARNING "ISICOM:(0x%lx) isicom_close: bad port "
"count for channel%d = %d", card->base, port->channel,
2005-04-16 15:20:36 -07:00
port->count);
2006-01-09 20:54:21 -08:00
port->count = 0;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (port->count) {
spin_unlock_irqrestore(&card->card_lock, flags);
return;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
port->flags |= ASYNC_CLOSING;
tty->closing = 1;
spin_unlock_irqrestore(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
tty_wait_until_sent(tty, port->closing_wait);
2006-01-09 20:54:21 -08:00
/* indicate to the card that no more data can be received
2005-04-16 15:20:36 -07:00
on this port */
spin_lock_irqsave(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
if (port->flags & ASYNC_INITIALIZED) {
2005-04-16 15:20:36 -07:00
card->port_status &= ~(1 << port->channel);
outw(card->port_status, card->base + 0x02);
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
isicom_shutdown_port(port);
spin_unlock_irqrestore(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (tty->driver->flush_buffer)
tty->driver->flush_buffer(tty);
tty_ldisc_flush(tty);
spin_lock_irqsave(&card->card_lock, flags);
tty->closing = 0;
if (port->blocked_open) {
spin_unlock_irqrestore(&card->card_lock, flags);
if (port->close_delay) {
2006-01-09 20:54:23 -08:00
pr_dbg("scheduling until time out.\n");
msleep_interruptible(
jiffies_to_msecs(port->close_delay));
2005-04-16 15:20:36 -07:00
}
spin_lock_irqsave(&card->card_lock, flags);
wake_up_interruptible(&port->open_wait);
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
port->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
wake_up_interruptible(&port->close_wait);
spin_unlock_irqrestore(&card->card_lock, flags);
}
/* write et all */
2006-01-09 20:54:21 -08:00
static int isicom_write(struct tty_struct *tty, const unsigned char *buf,
int count)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
unsigned long flags;
int cnt, total = 0;
if (isicom_paranoia_check(port, tty->name, "isicom_write"))
return 0;
2006-01-09 20:54:21 -08:00
2006-06-25 05:48:48 -07:00
if (!port->xmit_buf)
2005-04-16 15:20:36 -07:00
return 0;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
spin_lock_irqsave(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
while(1) {
cnt = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt
- 1, SERIAL_XMIT_SIZE - port->xmit_head));
2006-01-09 20:54:21 -08:00
if (cnt <= 0)
2005-04-16 15:20:36 -07:00
break;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
memcpy(port->xmit_buf + port->xmit_head, buf, cnt);
port->xmit_head = (port->xmit_head + cnt) & (SERIAL_XMIT_SIZE
- 1);
2005-04-16 15:20:36 -07:00
port->xmit_cnt += cnt;
buf += cnt;
count -= cnt;
total += cnt;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped)
port->status |= ISI_TXOK;
spin_unlock_irqrestore(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
return total;
2005-04-16 15:20:36 -07:00
}
/* put_char et all */
2006-01-09 20:54:21 -08:00
static void isicom_put_char(struct tty_struct *tty, unsigned char ch)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_put_char"))
return;
2006-01-09 20:54:21 -08:00
2006-06-25 05:48:48 -07:00
if (!port->xmit_buf)
2005-04-16 15:20:36 -07:00
return;
spin_lock_irqsave(&card->card_lock, flags);
if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
spin_unlock_irqrestore(&card->card_lock, flags);
return;
}
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
port->xmit_buf[port->xmit_head++] = ch;
port->xmit_head &= (SERIAL_XMIT_SIZE - 1);
port->xmit_cnt++;
spin_unlock_irqrestore(&card->card_lock, flags);
}
/* flush_chars et all */
2006-01-09 20:54:21 -08:00
static void isicom_flush_chars(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_flush_chars"))
return;
2006-01-09 20:54:21 -08:00
if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
!port->xmit_buf)
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* this tells the transmitter to consider this port for
data output to the card ... that's the best we can do. */
2006-01-09 20:54:21 -08:00
port->status |= ISI_TXOK;
2005-04-16 15:20:36 -07:00
}
/* write_room et all */
2006-01-09 20:54:21 -08:00
static int isicom_write_room(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2005-04-16 15:20:36 -07:00
int free;
if (isicom_paranoia_check(port, tty->name, "isicom_write_room"))
return 0;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
free = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
if (free < 0)
free = 0;
return free;
}
/* chars_in_buffer et all */
2006-01-09 20:54:21 -08:00
static int isicom_chars_in_buffer(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_chars_in_buffer"))
return 0;
return port->xmit_cnt;
}
/* ioctl et all */
static inline void isicom_send_break(struct isi_port *port,
unsigned long length)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
unsigned long base = card->base;
2006-01-09 20:54:21 -08:00
if (!lock_card(card))
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
outw(0x8000 | ((port->channel) << (card->shift_count)) | 0x3, base);
outw((length & 0xff) << 8 | 0x00, base);
outw((length & 0xff00), base);
InterruptTheCard(base);
unlock_card(card);
}
static int isicom_tiocmget(struct tty_struct *tty, struct file *file)
{
struct isi_port *port = tty->driver_data;
2005-04-16 15:20:36 -07:00
/* just send the port status */
u16 status = port->status;
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
return -ENODEV;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
return ((status & ISI_RTS) ? TIOCM_RTS : 0) |
((status & ISI_DTR) ? TIOCM_DTR : 0) |
((status & ISI_DCD) ? TIOCM_CAR : 0) |
((status & ISI_DSR) ? TIOCM_DSR : 0) |
((status & ISI_CTS) ? TIOCM_CTS : 0) |
((status & ISI_RI ) ? TIOCM_RI : 0);
}
static int isicom_tiocmset(struct tty_struct *tty, struct file *file,
2006-01-09 20:54:21 -08:00
unsigned int set, unsigned int clear)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
return -ENODEV;
2006-01-09 20:54:21 -08:00
spin_lock_irqsave(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
if (set & TIOCM_RTS)
raise_rts(port);
if (set & TIOCM_DTR)
raise_dtr(port);
if (clear & TIOCM_RTS)
drop_rts(port);
if (clear & TIOCM_DTR)
drop_dtr(port);
spin_unlock_irqrestore(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
return 0;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
2006-01-09 20:54:21 -08:00
static int isicom_set_serial_info(struct isi_port *port,
struct serial_struct __user *info)
2005-04-16 15:20:36 -07:00
{
struct serial_struct newinfo;
int reconfig_port;
2006-01-09 20:54:21 -08:00
if (copy_from_user(&newinfo, info, sizeof(newinfo)))
2005-04-16 15:20:36 -07:00
return -EFAULT;
2006-01-09 20:54:21 -08:00
reconfig_port = ((port->flags & ASYNC_SPD_MASK) !=
(newinfo.flags & ASYNC_SPD_MASK));
2005-04-16 15:20:36 -07:00
if (!capable(CAP_SYS_ADMIN)) {
if ((newinfo.close_delay != port->close_delay) ||
2006-01-09 20:54:21 -08:00
(newinfo.closing_wait != port->closing_wait) ||
((newinfo.flags & ~ASYNC_USR_MASK) !=
(port->flags & ~ASYNC_USR_MASK)))
2005-04-16 15:20:36 -07:00
return -EPERM;
port->flags = ((port->flags & ~ ASYNC_USR_MASK) |
(newinfo.flags & ASYNC_USR_MASK));
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
else {
port->close_delay = newinfo.close_delay;
2006-01-09 20:54:21 -08:00
port->closing_wait = newinfo.closing_wait;
port->flags = ((port->flags & ~ASYNC_FLAGS) |
2005-04-16 15:20:36 -07:00
(newinfo.flags & ASYNC_FLAGS));
}
if (reconfig_port) {
unsigned long flags;
spin_lock_irqsave(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
isicom_config_port(port);
spin_unlock_irqrestore(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:21 -08:00
return 0;
}
2005-04-16 15:20:36 -07:00
2006-01-09 20:54:21 -08:00
static int isicom_get_serial_info(struct isi_port *port,
struct serial_struct __user *info)
2005-04-16 15:20:36 -07:00
{
struct serial_struct out_info;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
memset(&out_info, 0, sizeof(out_info));
/* out_info.type = ? */
out_info.line = port - isi_ports;
out_info.port = port->card->base;
out_info.irq = port->card->irq;
out_info.flags = port->flags;
/* out_info.baud_base = ? */
out_info.close_delay = port->close_delay;
out_info.closing_wait = port->closing_wait;
2006-01-09 20:54:21 -08:00
if (copy_to_user(info, &out_info, sizeof(out_info)))
2005-04-16 15:20:36 -07:00
return -EFAULT;
return 0;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
2006-01-09 20:54:21 -08:00
static int isicom_ioctl(struct tty_struct *tty, struct file *filp,
unsigned int cmd, unsigned long arg)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2005-04-16 15:20:36 -07:00
void __user *argp = (void __user *)arg;
int retval;
if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
return -ENODEV;
switch(cmd) {
2006-01-09 20:54:21 -08:00
case TCSBRK:
retval = tty_check_change(tty);
if (retval)
return retval;
tty_wait_until_sent(tty, 0);
if (!arg)
isicom_send_break(port, HZ/4);
return 0;
case TCSBRKP:
retval = tty_check_change(tty);
if (retval)
return retval;
tty_wait_until_sent(tty, 0);
isicom_send_break(port, arg ? arg * (HZ/10) : HZ/4);
return 0;
case TIOCGSOFTCAR:
return put_user(C_CLOCAL(tty) ? 1 : 0,
(unsigned long __user *)argp);
2006-01-09 20:54:21 -08:00
case TIOCSSOFTCAR:
if (get_user(arg, (unsigned long __user *) argp))
return -EFAULT;
tty->termios->c_cflag =
((tty->termios->c_cflag & ~CLOCAL) |
(arg ? CLOCAL : 0));
return 0;
case TIOCGSERIAL:
return isicom_get_serial_info(port, argp);
case TIOCSSERIAL:
return isicom_set_serial_info(port, argp);
default:
return -ENOIOCTLCMD;
2005-04-16 15:20:36 -07:00
}
return 0;
}
/* set_termios et all */
2006-01-09 20:54:21 -08:00
static void isicom_set_termios(struct tty_struct *tty,
2006-12-08 02:38:45 -08:00
struct ktermios *old_termios)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_set_termios"))
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (tty->termios->c_cflag == old_termios->c_cflag &&
2006-01-09 20:54:21 -08:00
tty->termios->c_iflag == old_termios->c_iflag)
2005-04-16 15:20:36 -07:00
return;
2006-01-09 20:54:21 -08:00
spin_lock_irqsave(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
isicom_config_port(port);
spin_unlock_irqrestore(&port->card->card_lock, flags);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if ((old_termios->c_cflag & CRTSCTS) &&
2006-01-09 20:54:21 -08:00
!(tty->termios->c_cflag & CRTSCTS)) {
2005-04-16 15:20:36 -07:00
tty->hw_stopped = 0;
2006-01-09 20:54:21 -08:00
isicom_start(tty);
}
2005-04-16 15:20:36 -07:00
}
/* throttle et all */
2006-01-09 20:54:21 -08:00
static void isicom_throttle(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_throttle"))
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* tell the card that this port cannot handle any more data for now */
card->port_status &= ~(1 << port->channel);
outw(card->port_status, card->base + 0x02);
}
/* unthrottle et all */
2006-01-09 20:54:21 -08:00
static void isicom_unthrottle(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_unthrottle"))
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* tell the card that this port is ready to accept more data */
card->port_status |= (1 << port->channel);
outw(card->port_status, card->base + 0x02);
}
/* stop et all */
2006-01-09 20:54:21 -08:00
static void isicom_stop(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_stop"))
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* this tells the transmitter not to consider this port for
data output to the card. */
port->status &= ~ISI_TXOK;
}
/* start et all */
2006-01-09 20:54:21 -08:00
static void isicom_start(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_start"))
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
/* this tells the transmitter to consider this port for
data output to the card. */
port->status |= ISI_TXOK;
}
2006-01-09 20:54:21 -08:00
static void isicom_hangup(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_hangup"))
return;
2006-01-09 20:54:21 -08:00
spin_lock_irqsave(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
isicom_shutdown_port(port);
spin_unlock_irqrestore(&port->card->card_lock, flags);
2005-04-16 15:20:36 -07:00
port->count = 0;
port->flags &= ~ASYNC_NORMAL_ACTIVE;
port->tty = NULL;
wake_up_interruptible(&port->open_wait);
}
/* flush_buffer et all */
2006-01-09 20:54:21 -08:00
static void isicom_flush_buffer(struct tty_struct *tty)
2005-04-16 15:20:36 -07:00
{
struct isi_port *port = tty->driver_data;
2006-01-09 20:54:21 -08:00
struct isi_board *card = port->card;
2005-04-16 15:20:36 -07:00
unsigned long flags;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
if (isicom_paranoia_check(port, tty->name, "isicom_flush_buffer"))
return;
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
spin_lock_irqsave(&card->card_lock, flags);
port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
spin_unlock_irqrestore(&card->card_lock, flags);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
tty_wakeup(tty);
}
2006-01-09 20:54:24 -08:00
/*
* Driver init and deinit functions
*/
2005-04-16 15:20:36 -07:00
2006-10-02 02:17:18 -07:00
static const struct tty_operations isicom_ops = {
2006-01-09 20:54:21 -08:00
.open = isicom_open,
.close = isicom_close,
.write = isicom_write,
.put_char = isicom_put_char,
.flush_chars = isicom_flush_chars,
.write_room = isicom_write_room,
2005-04-16 15:20:36 -07:00
.chars_in_buffer = isicom_chars_in_buffer,
2006-01-09 20:54:21 -08:00
.ioctl = isicom_ioctl,
.set_termios = isicom_set_termios,
.throttle = isicom_throttle,
.unthrottle = isicom_unthrottle,
.stop = isicom_stop,
.start = isicom_start,
.hangup = isicom_hangup,
.flush_buffer = isicom_flush_buffer,
.tiocmget = isicom_tiocmget,
.tiocmset = isicom_tiocmset,
2005-04-16 15:20:36 -07:00
};
2006-01-09 20:54:24 -08:00
static int __devinit reset_card(struct pci_dev *pdev,
const unsigned int card, unsigned int *signature)
{
struct isi_board *board = pci_get_drvdata(pdev);
unsigned long base = board->base;
2007-02-10 01:44:52 -08:00
unsigned int sig, portcount = 0;
2006-01-09 20:54:24 -08:00
int retval = 0;
dev_dbg(&pdev->dev, "ISILoad:Resetting Card%d at 0x%lx\n", card + 1,
base);
inw(base + 0x8);
2007-02-10 01:44:52 -08:00
msleep(10);
2006-01-09 20:54:24 -08:00
outw(0, base + 0x8); /* Reset */
2007-02-10 01:44:52 -08:00
msleep(1000);
2006-01-09 20:54:24 -08:00
2007-02-10 01:44:52 -08:00
sig = inw(base + 0x4) & 0xff;
2006-01-09 20:54:24 -08:00
2007-02-10 01:44:52 -08:00
if (sig != 0xa5 && sig != 0xbb && sig != 0xcc && sig != 0xdd &&
sig != 0xee) {
dev_warn(&pdev->dev, "ISILoad:Card%u reset failure (Possible "
"bad I/O Port Address 0x%lx).\n", card + 1, base);
dev_dbg(&pdev->dev, "Sig=0x%x\n", sig);
2006-12-08 02:38:51 -08:00
retval = -EIO;
goto end;
2005-04-16 15:20:36 -07:00
}
2007-02-10 01:44:52 -08:00
msleep(10);
portcount = inw(base + 0x2);
if (!inw(base + 0xe) & 0x1 || (portcount != 0 && portcount != 4 &&
portcount != 8 && portcount != 16)) {
dev_err(&pdev->dev, "ISILoad:PCI Card%d reset failure.",
card + 1);
retval = -EIO;
goto end;
}
switch (sig) {
2006-01-09 20:54:24 -08:00
case 0xa5:
case 0xbb:
case 0xdd:
2006-12-08 02:38:51 -08:00
board->port_count = (portcount == 4) ? 4 : 8;
2006-01-09 20:54:24 -08:00
board->shift_count = 12;
break;
case 0xcc:
2007-02-10 01:44:52 -08:00
case 0xee:
2006-01-09 20:54:24 -08:00
board->port_count = 16;
board->shift_count = 11;
break;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:24 -08:00
dev_info(&pdev->dev, "-Done\n");
2007-02-10 01:44:52 -08:00
*signature = sig;
2006-01-09 20:54:24 -08:00
end:
return retval;
2005-04-16 15:20:36 -07:00
}
2006-01-09 20:54:25 -08:00
static int __devinit load_firmware(struct pci_dev *pdev,
const unsigned int index, const unsigned int signature)
{
struct isi_board *board = pci_get_drvdata(pdev);
const struct firmware *fw;
unsigned long base = board->base;
unsigned int a;
u16 word_count, status;
int retval = -EIO;
char *name;
u8 *data;
struct stframe {
u16 addr;
u16 count;
u8 data[0];
} *frame;
switch (signature) {
case 0xa5:
name = "isi608.bin";
break;
case 0xbb:
name = "isi608em.bin";
break;
case 0xcc:
name = "isi616em.bin";
break;
case 0xdd:
name = "isi4608.bin";
break;
case 0xee:
name = "isi4616.bin";
break;
default:
dev_err(&pdev->dev, "Unknown signature.\n");
goto end;
}
retval = request_firmware(&fw, name, &pdev->dev);
if (retval)
goto end;
2006-09-30 23:27:50 -07:00
retval = -EIO;
2006-01-09 20:54:25 -08:00
for (frame = (struct stframe *)fw->data;
frame < (struct stframe *)(fw->data + fw->size);
2006-09-30 23:27:50 -07:00
frame = (struct stframe *)((u8 *)(frame + 1) +
frame->count)) {
2006-01-09 20:54:25 -08:00
if (WaitTillCardIsFree(base))
goto errrelfw;
outw(0xf0, base); /* start upload sequence */
outw(0x00, base);
outw(frame->addr, base); /* lsb of address */
word_count = frame->count / 2 + frame->count % 2;
outw(word_count, base);
InterruptTheCard(base);
udelay(100); /* 0x2f */
if (WaitTillCardIsFree(base))
goto errrelfw;
if ((status = inw(base + 0x4)) != 0) {
dev_warn(&pdev->dev, "Card%d rejected load header:\n"
"Address:0x%x\nCount:0x%x\nStatus:0x%x\n",
index + 1, frame->addr, frame->count, status);
goto errrelfw;
}
outsw(base, frame->data, word_count);
InterruptTheCard(base);
udelay(50); /* 0x0f */
if (WaitTillCardIsFree(base))
goto errrelfw;
if ((status = inw(base + 0x4)) != 0) {
dev_err(&pdev->dev, "Card%d got out of sync.Card "
"Status:0x%x\n", index + 1, status);
goto errrelfw;
}
}
/* XXX: should we test it by reading it back and comparing with original like
* in load firmware package? */
2006-09-30 23:27:50 -07:00
for (frame = (struct stframe *)fw->data;
frame < (struct stframe *)(fw->data + fw->size);
frame = (struct stframe *)((u8 *)(frame + 1) +
frame->count)) {
2006-01-09 20:54:25 -08:00
if (WaitTillCardIsFree(base))
goto errrelfw;
outw(0xf1, base); /* start download sequence */
outw(0x00, base);
outw(frame->addr, base); /* lsb of address */
word_count = (frame->count >> 1) + frame->count % 2;
outw(word_count + 1, base);
InterruptTheCard(base);
udelay(50); /* 0xf */
if (WaitTillCardIsFree(base))
goto errrelfw;
if ((status = inw(base + 0x4)) != 0) {
dev_warn(&pdev->dev, "Card%d rejected verify header:\n"
"Address:0x%x\nCount:0x%x\nStatus: 0x%x\n",
index + 1, frame->addr, frame->count, status);
goto errrelfw;
}
data = kmalloc(word_count * 2, GFP_KERNEL);
2006-12-08 02:38:54 -08:00
if (data == NULL) {
dev_err(&pdev->dev, "Card%d, firmware upload "
"failed, not enough memory\n", index + 1);
goto errrelfw;
}
2006-01-09 20:54:25 -08:00
inw(base);
insw(base, data, word_count);
InterruptTheCard(base);
for (a = 0; a < frame->count; a++)
if (data[a] != frame->data[a]) {
kfree(data);
dev_err(&pdev->dev, "Card%d, firmware upload "
"failed\n", index + 1);
goto errrelfw;
}
kfree(data);
udelay(50); /* 0xf */
if (WaitTillCardIsFree(base))
goto errrelfw;
if ((status = inw(base + 0x4)) != 0) {
dev_err(&pdev->dev, "Card%d verify got out of sync. "
"Card Status:0x%x\n", index + 1, status);
goto errrelfw;
}
}
2006-09-30 23:27:50 -07:00
/* xfer ctrl */
if (WaitTillCardIsFree(base))
goto errrelfw;
outw(0xf2, base);
outw(0x800, base);
outw(0x0, base);
outw(0x0, base);
InterruptTheCard(base);
outw(0x0, base + 0x4); /* for ISI4608 cards */
2006-01-09 20:54:25 -08:00
board->status |= FIRMWARE_LOADED;
retval = 0;
errrelfw:
release_firmware(fw);
end:
return retval;
}
2006-01-09 20:54:24 -08:00
/*
* Insmod can set static symbols so keep these static
*/
static unsigned int card_count;
2006-01-09 20:54:24 -08:00
static int __devinit isicom_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
2005-04-16 15:20:36 -07:00
{
2006-01-09 20:54:24 -08:00
unsigned int ioaddr, signature, index;
int retval = -EPERM;
u8 pciirq;
struct isi_board *board = NULL;
if (card_count >= BOARD_COUNT)
2006-01-09 20:54:24 -08:00
goto err;
ioaddr = pci_resource_start(pdev, 3);
/* i.e at offset 0x1c in the PCI configuration register space. */
pciirq = pdev->irq;
dev_info(&pdev->dev, "ISI PCI Card(Device ID 0x%x)\n", ent->device);
/* allot the first empty slot in the array */
for (index = 0; index < BOARD_COUNT; index++)
if (isi_card[index].base == 0) {
board = &isi_card[index];
break;
}
board->index = index;
2006-01-09 20:54:24 -08:00
board->base = ioaddr;
board->irq = pciirq;
card_count++;
2006-01-09 20:54:24 -08:00
pci_set_drvdata(pdev, board);
retval = pci_request_region(pdev, 3, ISICOM_NAME);
if (retval) {
2006-12-08 02:38:49 -08:00
dev_err(&pdev->dev, "I/O Region 0x%lx-0x%lx is busy. Card%d "
"will be disabled.\n", board->base, board->base + 15,
index + 1);
retval = -EBUSY;
goto errdec;
2006-12-08 02:38:49 -08:00
}
2006-01-09 20:54:24 -08:00
2006-12-08 02:38:49 -08:00
retval = request_irq(board->irq, isicom_interrupt,
IRQF_SHARED | IRQF_DISABLED, ISICOM_NAME, board);
if (retval < 0) {
dev_err(&pdev->dev, "Could not install handler at Irq %d. "
"Card%d will be disabled.\n", board->irq, index + 1);
2006-01-09 20:54:24 -08:00
goto errunrr;
2006-12-08 02:38:49 -08:00
}
2006-01-09 20:54:24 -08:00
retval = reset_card(pdev, index, &signature);
if (retval < 0)
goto errunri;
2006-01-09 20:54:25 -08:00
retval = load_firmware(pdev, index, signature);
if (retval < 0)
goto errunri;
for (index = 0; index < board->port_count; index++)
tty_register_device(isicom_normal, board->index * 16 + index,
&pdev->dev);
2006-01-09 20:54:24 -08:00
return 0;
errunri:
free_irq(board->irq, board);
errunrr:
pci_release_region(pdev, 3);
errdec:
2006-01-09 20:54:24 -08:00
board->base = 0;
card_count--;
err:
2006-01-09 20:54:24 -08:00
return retval;
}
static void __devexit isicom_remove(struct pci_dev *pdev)
{
struct isi_board *board = pci_get_drvdata(pdev);
unsigned int i;
for (i = 0; i < board->port_count; i++)
tty_unregister_device(isicom_normal, board->index * 16 + i);
2006-01-09 20:54:24 -08:00
free_irq(board->irq, board);
pci_release_region(pdev, 3);
board->base = 0;
card_count--;
2006-01-09 20:54:24 -08:00
}
2006-12-08 02:38:50 -08:00
static int __init isicom_init(void)
2006-01-09 20:54:24 -08:00
{
int retval, idx, channel;
2006-01-09 20:54:21 -08:00
struct isi_port *port;
2006-01-09 20:54:24 -08:00
for(idx = 0; idx < BOARD_COUNT; idx++) {
port = &isi_ports[idx * 16];
isi_card[idx].ports = port;
spin_lock_init(&isi_card[idx].card_lock);
2005-04-16 15:20:36 -07:00
for (channel = 0; channel < 16; channel++, port++) {
port->magic = ISICOM_MAGIC;
2006-01-09 20:54:24 -08:00
port->card = &isi_card[idx];
2005-04-16 15:20:36 -07:00
port->channel = channel;
2006-01-09 20:54:21 -08:00
port->close_delay = 50 * HZ/100;
port->closing_wait = 3000 * HZ/100;
port->status = 0;
init_waitqueue_head(&port->open_wait);
2005-04-16 15:20:36 -07:00
init_waitqueue_head(&port->close_wait);
/* . . . */
2006-01-09 20:54:24 -08:00
}
isi_card[idx].base = 0;
isi_card[idx].irq = 0;
2006-01-09 20:54:21 -08:00
}
2006-12-08 02:38:49 -08:00
/* tty driver structure initialization */
isicom_normal = alloc_tty_driver(PORT_COUNT);
if (!isicom_normal) {
retval = -ENOMEM;
2006-01-09 20:54:24 -08:00
goto error;
2006-12-08 02:38:49 -08:00
}
isicom_normal->owner = THIS_MODULE;
isicom_normal->name = "ttyM";
isicom_normal->major = ISICOM_NMAJOR;
isicom_normal->minor_start = 0;
isicom_normal->type = TTY_DRIVER_TYPE_SERIAL;
isicom_normal->subtype = SERIAL_TYPE_NORMAL;
isicom_normal->init_termios = tty_std_termios;
isicom_normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL |
CLOCAL;
isicom_normal->flags = TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV;
2006-12-08 02:38:49 -08:00
tty_set_operations(isicom_normal, &isicom_ops);
retval = tty_register_driver(isicom_normal);
if (retval) {
pr_dbg("Couldn't register the dialin driver\n");
goto err_puttty;
}
2005-04-16 15:20:36 -07:00
2006-01-09 20:54:24 -08:00
retval = pci_register_driver(&isicom_driver);
if (retval < 0) {
printk(KERN_ERR "ISICOM: Unable to register pci driver.\n");
2006-12-08 02:38:49 -08:00
goto err_unrtty;
2006-01-09 20:54:21 -08:00
}
2005-04-16 15:20:36 -07:00
2006-12-08 02:38:55 -08:00
mod_timer(&tx, jiffies + 1);
2006-01-09 20:54:21 -08:00
2005-04-16 15:20:36 -07:00
return 0;
2006-12-08 02:38:49 -08:00
err_unrtty:
tty_unregister_driver(isicom_normal);
err_puttty:
put_tty_driver(isicom_normal);
2006-01-09 20:54:24 -08:00
error:
return retval;
2005-04-16 15:20:36 -07:00
}
static void __exit isicom_exit(void)
{
re_schedule = 0;
2006-01-09 20:54:23 -08:00
2006-12-08 02:38:55 -08:00
wait_for_completion_timeout(&isi_timerdone, HZ);
2006-01-09 20:54:23 -08:00
2006-01-09 20:54:24 -08:00
pci_unregister_driver(&isicom_driver);
2006-12-08 02:38:49 -08:00
tty_unregister_driver(isicom_normal);
put_tty_driver(isicom_normal);
2005-04-16 15:20:36 -07:00
}
2006-12-08 02:38:50 -08:00
module_init(isicom_init);
2005-04-16 15:20:36 -07:00
module_exit(isicom_exit);
2006-01-09 20:54:23 -08:00
MODULE_AUTHOR("MultiTech");
MODULE_DESCRIPTION("Driver for the ISI series of cards by MultiTech");
MODULE_LICENSE("GPL");