lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Wed,  9 Apr 2014 23:04:30 -0500
From:	Michael Welling <mwelling@...e.org>
To:	linux-kernel@...r.kernel.org, gregkh@...uxfoundation.org,
	jslaby@...e.cz
Cc:	Michael Welling <mwelling@...e.org>
Subject: [PATCH 1/2] tty serial: xr17c15x driver

Signed-off-by: Michael Welling <mwelling@...e.org>
---
 drivers/tty/serial/Kconfig    |    9 +-
 drivers/tty/serial/Makefile   |    1 +
 drivers/tty/serial/xr17c15x.c | 1540 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1549 insertions(+), 1 deletion(-)
 create mode 100644 drivers/tty/serial/xr17c15x.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 2e6d8dd..4f61582 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -223,7 +223,7 @@ config SERIAL_SAMSUNG_UARTS
 	help
 	  Select the number of available UART ports for the Samsung S3C
 	  serial driver
-	
+
 config SERIAL_SAMSUNG_DEBUG
 	bool "Samsung SoC serial debug"
 	depends on SERIAL_SAMSUNG && DEBUG_LL
@@ -1507,6 +1507,13 @@ config SERIAL_ST_ASC_CONSOLE
 	depends on SERIAL_ST_ASC=y
 	select SERIAL_CORE_CONSOLE
 
+config SERIAL_XR17C15X
+	tristate "Exar xr17c15x support"
+	depends on PCI
+	select SERIAL_CORE
+	help
+	  This driver supports the Exar xr17c15x series serial ports.
+
 endmenu
 
 endif # TTY
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 3680854..8921041 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -87,3 +87,4 @@ obj-$(CONFIG_SERIAL_EFM32_UART) += efm32-uart.o
 obj-$(CONFIG_SERIAL_ARC)	+= arc_uart.o
 obj-$(CONFIG_SERIAL_RP2)	+= rp2.o
 obj-$(CONFIG_SERIAL_FSL_LPUART)	+= fsl_lpuart.o
+obj-$(CONFIG_SERIAL_XR17C15X)	+= xr17c15x.o
diff --git a/drivers/tty/serial/xr17c15x.c b/drivers/tty/serial/xr17c15x.c
new file mode 100644
index 0000000..f8963e1
--- /dev/null
+++ b/drivers/tty/serial/xr17c15x.c
@@ -0,0 +1,1540 @@
+/*
+ * xr1715x.c  -- EXAR multiport serial driver.
+ *
+ * Updated and tested with the 3.10.0 kernel
+ *
+ * Copyright (C) 2010 Exar Corporation.
+ *
+ * Based on Linux 2.6.31 Kernel's  drivers/serial/8250.c and /8250_pci.c
+ *
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/sysrq.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include <linux/serial_reg.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/tty_flip.h>
+
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <asm/irq.h>
+#include <linux/bitops.h>
+#include <asm/byteorder.h>
+#include <linux/serial.h>
+#include <linux/uaccess.h>
+
+#define _INLINE_ inline
+
+/*
+ * Definitions for PCI support.
+ */
+#define FL_BASE_MASK		0x0007
+#define FL_BASE0		0x0000
+#define FL_GET_BASE(x)		(x & FL_BASE_MASK)
+
+#define NR_PORTS	128	/* we can support more too */
+
+#define XR_MAJOR       30
+#define XR_MINOR       0
+
+/*
+ * The special register set for XR17c15x UARTs.
+ */
+
+#define XR_17C15X_EXTENDED_FCTR		8
+#define XR_17C15X_EXTENDED_EFR		9
+#define XR_17C15X_TXFIFO_CNT		10
+#define XR_17C15X_RXFIFO_CNT		11
+#define XR_17C15X_EXTENDED_RXTRG	11
+
+#define XR_17C15X_FCTR_RTS_8DELAY	0x03
+#define XR_17C15X_FCTR_TRGD		192
+
+/* 17V15X TX/RX memory mapped buffer offsets */
+
+#define UART_17C15X_RX_OFFSET		0x100
+#define UART_17C15X_TX_OFFSET		0x100
+
+/*
+ * These are the EXTENDED definitions for the 17C15X's Interrupt
+ * Enable Register
+ */
+#define	XR_17C15X_IER_RTSDTR	0x40
+#define XR_17C15X_IER_CTSDSR	0x80
+
+#define PCI_NUM_BAR_RESOURCES	6
+
+#define ALPHA_KLUDGE_MCR	0
+
+struct serial_private {
+	struct pci_dev *dev;
+	unsigned int nr;
+	void *remapped_bar[PCI_NUM_BAR_RESOURCES];
+	struct pci_serial_quirk *quirk;
+	int line[0];
+};
+
+struct pciserial_board {
+	unsigned int flags;
+	unsigned int num_ports;
+	unsigned int base_baud;
+	unsigned int uart_offset;
+	unsigned int reg_shift;
+	unsigned int first_offset;
+};
+
+/*
+ * init function returns:
+ *  > 0 - number of ports
+ *  = 0 - use board->num_ports
+ *  < 0 - error
+ */
+struct pci_serial_quirk {
+	u32 vendor;
+	u32 device;
+	u32 subvendor;
+	u32 subdevice;
+	int (*init)(struct pci_dev *dev);
+	int (*setup)(struct serial_private *, struct pciserial_board *,
+		     struct uart_port *, int);
+	void (*exit)(struct pci_dev *dev);
+};
+
+/*
+ * This is the configuration table for all of the PCI serial boards
+ * which we support.  It is directly indexed by the xrpci_board_num_t enum
+ * value, which is encoded in the pci_device_id PCI probe table's
+ * driver_data member.
+ *
+ * The makeup of these names are:
+ *  pbn_bn{_bt}_n_baud
+ *
+ *  bn   = PCI BAR number
+ *  bt   = Index using PCI BARs
+ *  n    = number of serial ports
+ *  baud = baud rate
+ *
+ * Please note: in theory if n = 1, _bt infix should make no difference.
+ * ie, pbn_b0_1_115200 is the same as pbn_b0_bt_1_115200
+ */
+enum xrpci_board_num_t {
+	xr_8port = 0,
+	xr_4port,
+	xr_2port
+};
+
+static struct pciserial_board xrpciserial_boards[] = {
+	[xr_8port] = {
+		      .flags = FL_BASE0,
+		      .num_ports = 8,
+		      .base_baud = 921600,
+		      .uart_offset = 0x200,
+		      .reg_shift = 0,
+		      .first_offset = 0,
+		      },
+
+	[xr_4port] = {
+		      .flags = FL_BASE0,
+		      .num_ports = 4,
+		      .base_baud = 921600,
+		      .uart_offset = 0x200,
+		      .reg_shift = 0,
+		      .first_offset = 0,
+		      },
+	[xr_2port] = {
+		      .flags = FL_BASE0,
+		      .num_ports = 2,
+		      .base_baud = 921600,
+		      .uart_offset = 0x200,
+		      .reg_shift = 0,
+		      .first_offset = 0,
+		      },
+};
+
+/*
+ * Configuration:
+ *   share_irqs - whether we pass SA_SHIRQ to request_irq().  This option
+ *                is unsafe when used on edge-triggered interrupts.
+ */
+#define SERIALEXAR_SHARE_IRQS 1
+unsigned int share_irqs = SERIALEXAR_SHARE_IRQS;
+
+/*
+ * Debugging.
+ */
+#if 0
+#define DEBUG_AUTOCONF(fmt...)	printk(fmt)
+#else
+#define DEBUG_AUTOCONF(fmt...)	do { } while (0)
+#endif
+
+#if 0
+#define DEBUG_INTR(fmt...)	printk(fmt)
+#else
+#define DEBUG_INTR(fmt...)	do { } while (0)
+#endif
+
+#define PASS_LIMIT	256
+
+/*
+ * We default to IRQ0 for the "no irq" hack.   Some
+ * machine types want others as well - they're free
+ * to redefine this in their header file.
+ */
+#define is_real_interrupt(irq)	((irq) != 0)
+
+struct uart_xr_port {
+	struct uart_port port;
+	struct timer_list timer;	/* "no irq" timer */
+	struct list_head list;	/* ports on this IRQ */
+	unsigned short capabilities;	/* port capabilities */
+	unsigned short bugs;	/* port bugs */
+	unsigned int tx_loadsz;	/* transmit fifo load size */
+	unsigned char acr;
+	unsigned char ier;
+	unsigned char lcr;
+	unsigned char mcr;
+	unsigned char mcr_mask;	/* mask of user bits */
+	unsigned char mcr_force;	/* mask of forced bits */
+	unsigned char lsr_break_flag;
+
+	/*
+	 * We provide a per-port pm hook.
+	 */
+	void (*pm)(struct uart_port *port,
+		   unsigned int state, unsigned int old);
+};
+
+/*
+struct irq_info {
+	spinlock_t lock;
+	struct list_head *head;
+};
+
+static struct irq_info irq_lists[NR_IRQS];
+*/
+
+struct irq_info {
+	struct			hlist_node node;
+	int			irq;
+	spinlock_t		lock;	/* Protects list not the hash */
+	struct list_head	*head;
+};
+
+#define NR_IRQ_HASH		32	/* Can be adjusted later */
+static struct hlist_head irq_lists[NR_IRQ_HASH];
+static DEFINE_MUTEX(hash_mutex);	/* Used to walk the hash */
+
+
+
+
+
+struct serial_uart_config {
+	char	*name;
+	int	dfl_xmit_fifo_size;
+	int	flags;
+};
+
+/*
+ * Here we define the default xmit fifo size used for each type of UART.
+ */
+#define PORT_MAX_XR 1
+#define XRPCI_TYPE 1		/* the second entry that is [1] in the array */
+static const struct serial_uart_config uart_config[PORT_MAX_XR + 1] = {
+	{"Unknown", 1, 0},
+	{"XR17c15x", 64, 0},
+};
+
+static int
+setup_port(struct serial_private *priv, struct uart_port *port,
+	   int bar, int offset, int regshift)
+{
+	struct pci_dev *dev = priv->dev;
+	unsigned long base, len;
+
+	if (bar >= PCI_NUM_BAR_RESOURCES)
+		return -EINVAL;
+
+	base = pci_resource_start(dev, bar);
+
+	if (pci_resource_flags(dev, bar) & IORESOURCE_MEM) {
+		len = pci_resource_len(dev, bar);
+		if (!priv->remapped_bar[bar])
+			priv->remapped_bar[bar] = ioremap(base, len);
+		if (!priv->remapped_bar[bar])
+			return -ENOMEM;
+
+		port->iotype = UPIO_MEM;
+		port->iobase = 0;
+		port->mapbase = base + offset;
+		port->membase = priv->remapped_bar[bar] + offset;
+		port->regshift = regshift;
+	} else {
+		/* Exar's got to be memory mapped.
+		 * Some hardware reading error? */
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int
+pci_default_setup(struct serial_private *priv, struct pciserial_board *board,
+		  struct uart_port *port, int idx)
+{
+	unsigned int bar, offset = board->first_offset;
+
+	bar = FL_GET_BASE(board->flags);
+	offset += idx * board->uart_offset;
+
+	return setup_port(priv, port, bar, offset, board->reg_shift);
+}
+
+/*
+ * Master list of serial port init/setup/exit quirks.
+ * This does not describe the general nature of the port.
+ * (ie, baud base, number and location of ports, etc)
+ *
+ * This list is ordered alphabetically by vendor then device.
+ * Specific entries must come before more generic entries.
+ */
+static struct pci_serial_quirk pci_serial_quirks[] = {
+	{
+	 .vendor = 0x13a8,
+	 .device = 0x158,
+	 .subvendor = PCI_ANY_ID,
+	 .subdevice = PCI_ANY_ID,
+	 .setup = pci_default_setup,
+	 },
+
+	{
+	 .vendor = 0x13a8,
+	 .device = 0x154,
+	 .subvendor = PCI_ANY_ID,
+	 .subdevice = PCI_ANY_ID,
+	 .setup = pci_default_setup,
+	 },
+
+	{
+	 .vendor = 0x13a8,
+	 .device = 0x152,
+	 .subvendor = PCI_ANY_ID,
+	 .subdevice = PCI_ANY_ID,
+	 .setup = pci_default_setup,
+	 }
+};
+
+static inline int quirk_id_matches(u32 quirk_id, u32 dev_id)
+{
+	return quirk_id == PCI_ANY_ID || quirk_id == dev_id;
+}
+
+static struct pci_serial_quirk *find_quirk(struct pci_dev *dev)
+{
+	struct pci_serial_quirk *quirk;
+
+	for (quirk = pci_serial_quirks;; quirk++)
+		if (quirk_id_matches(quirk->vendor, dev->vendor) &&
+		    quirk_id_matches(quirk->device, dev->device))
+			break;
+	return quirk;
+}
+
+static _INLINE_ unsigned int serial_in(struct uart_xr_port *up, int offset)
+{
+	offset <<= up->port.regshift;
+
+	switch (up->port.iotype) {
+	case SERIAL_IO_HUB6:
+		outb(up->port.hub6 - 1 + offset, up->port.iobase);
+		return inb(up->port.iobase + 1);
+
+	case SERIAL_IO_MEM:
+		return readb(up->port.membase + offset);
+
+	default:
+		return inb(up->port.iobase + offset);
+	}
+}
+
+static _INLINE_ void serial_out(struct uart_xr_port *up, int offset, int value)
+{
+	offset <<= up->port.regshift;
+
+	switch (up->port.iotype) {
+	case SERIAL_IO_HUB6:
+		outb(up->port.hub6 - 1 + offset, up->port.iobase);
+		outb(value, up->port.iobase + 1);
+		break;
+
+	case SERIAL_IO_MEM:
+		writeb(value, up->port.membase + offset);
+		break;
+
+	default:
+		outb(value, up->port.iobase + offset);
+	}
+}
+
+/*
+ * We used to support using pause I/O for certain machines.  We
+ * haven't supported this for a while, but just in case it's badly
+ * needed for certain old 386 machines, I've left these #define's
+ * in....
+ */
+#define serial_inp(up, offset)		serial_in(up, offset)
+#define serial_outp(up, offset, value)	serial_out(up, offset, value)
+
+static void serialxr_stop_tx(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+
+	if (up->ier & UART_IER_THRI) {
+		up->ier &= ~UART_IER_THRI;
+		serial_out(up, UART_IER, up->ier);
+	}
+}
+
+static void serialxr_start_tx(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+
+	if (!(up->ier & UART_IER_THRI)) {
+		up->ier |= UART_IER_THRI;
+		serial_out(up, UART_IER, up->ier);
+	}
+}
+
+static void serialxr_stop_rx(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+
+	up->ier &= ~UART_IER_RLSI;
+	up->port.read_status_mask &= ~UART_LSR_DR;
+	serial_out(up, UART_IER, up->ier);
+}
+
+static void serialxr_enable_ms(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+
+	up->ier |= UART_IER_MSI;
+	serial_out(up, UART_IER, up->ier);
+}
+
+static _INLINE_ void receive_chars(struct uart_xr_port *up, int *status)
+{
+	struct tty_port *tty = &up->port.state->port;
+	unsigned char ch, lsr = *status;
+	int max_count = 256;
+	char flag;
+
+	do {
+		ch = serial_inp(up, UART_RX);
+		flag = TTY_NORMAL;
+		up->port.icount.rx++;
+
+		if (unlikely(lsr & (UART_LSR_BI | UART_LSR_PE |
+				    UART_LSR_FE | UART_LSR_OE))) {
+			/*
+			 * For statistics only
+			 */
+			if (lsr & UART_LSR_BI) {
+				lsr &= ~(UART_LSR_FE | UART_LSR_PE);
+				up->port.icount.brk++;
+				/*
+				 * We do the SysRQ and SAK checking
+				 * here because otherwise the break
+				 * may get masked by ignore_status_mask
+				 * or read_status_mask.
+				 */
+				if (uart_handle_break(&up->port))
+					goto ignore_char;
+			} else if (lsr & UART_LSR_PE)
+				up->port.icount.parity++;
+			else if (lsr & UART_LSR_FE)
+				up->port.icount.frame++;
+			if (lsr & UART_LSR_OE)
+				up->port.icount.overrun++;
+
+			/*
+			 * Mask off conditions which should be ignored.
+			 */
+			lsr &= up->port.read_status_mask;
+
+			if (lsr & UART_LSR_BI) {
+				DEBUG_INTR("handling break....");
+				flag = TTY_BREAK;
+			} else if (lsr & UART_LSR_PE)
+				flag = TTY_PARITY;
+			else if (lsr & UART_LSR_FE)
+				flag = TTY_FRAME;
+		}
+		if (uart_handle_sysrq_char(&up->port, ch))
+			goto ignore_char;
+
+		uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);
+
+ignore_char:
+		lsr = serial_inp(up, UART_LSR);
+	} while ((lsr & UART_LSR_DR) && (max_count-- > 0));
+	spin_unlock(&up->port.lock);
+	tty_flip_buffer_push(tty);
+	spin_lock(&up->port.lock);
+	*status = lsr;
+}
+
+static _INLINE_ void transmit_chars(struct uart_xr_port *up)
+{
+	struct circ_buf *xmit = &up->port.state->xmit;
+	int count, bytes_in_fifo;
+	int tmp;
+
+	if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {
+		serialxr_stop_tx(&up->port);
+		return;
+	}
+
+	bytes_in_fifo = serial_inp(up, XR_17C15X_TXFIFO_CNT);
+	/* read the fifo count untill we get the same value twice */
+	while (bytes_in_fifo != serial_inp(up, XR_17C15X_TXFIFO_CNT))
+		bytes_in_fifo = serial_inp(up, XR_17C15X_TXFIFO_CNT);
+
+	/* how much buffer is availabe now to write? */
+	count = up->port.fifosize - bytes_in_fifo;
+	if (uart_circ_chars_pending(xmit) < count)
+		count = uart_circ_chars_pending(xmit);
+
+	do {
+		/* if the count is more than (tail to end of the buffer),
+		 * transmit only the rest here. tail+tmp&(UART_XMIT_SIZE-1)
+		 * will reset the tail to the starting of the circular buffer
+		 */
+		if (((xmit->tail+count) & (UART_XMIT_SIZE-1)) < xmit->tail) {
+			tmp = UART_XMIT_SIZE - xmit->tail;
+			memcpy(up->port.membase + UART_17C15X_TX_OFFSET,
+			       &(xmit->buf[xmit->tail]), tmp);
+			xmit->tail += tmp;
+			xmit->tail &= (UART_XMIT_SIZE - 1);
+			up->port.icount.tx += tmp;
+
+			count -= tmp;
+		} else {
+			memcpy(up->port.membase + UART_17C15X_TX_OFFSET,
+			       &(xmit->buf[xmit->tail]), count);
+			xmit->tail += count;
+			xmit->tail &= UART_XMIT_SIZE - 1;
+
+			up->port.icount.tx += count;
+			count = 0;
+		}
+
+	} while (count > 0);
+
+	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+		uart_write_wakeup(&up->port);
+
+	DEBUG_INTR("THRE...");
+
+	if (uart_circ_empty(xmit))
+		serialxr_stop_tx(&up->port);
+}
+
+static _INLINE_ void check_modem_status(struct uart_xr_port *up)
+{
+	int status;
+
+	status = serial_in(up, UART_MSR);
+
+	if ((status & UART_MSR_ANY_DELTA) == 0)
+		return;
+
+	if (status & UART_MSR_TERI)
+		up->port.icount.rng++;
+	if (status & UART_MSR_DDSR)
+		up->port.icount.dsr++;
+	if (status & UART_MSR_DDCD)
+		uart_handle_dcd_change(&up->port, status & UART_MSR_DCD);
+	if (status & UART_MSR_DCTS)
+		uart_handle_cts_change(&up->port, status & UART_MSR_CTS);
+
+	wake_up_interruptible(&up->port.state->port.delta_msr_wait);
+}
+
+/*
+ * This handles the interrupt from one port.
+ */
+static inline void serialxr_handle_port(struct uart_xr_port *up)
+{
+	unsigned int status = serial_inp(up, UART_LSR);
+
+	DEBUG_INTR("status = %x...", status);
+
+	if (status & UART_LSR_DR)
+		receive_chars(up, &status);
+	check_modem_status(up);
+	if (status & UART_LSR_THRE)
+		transmit_chars(up);
+}
+
+/*
+ * This is the serial driver's interrupt routine.
+ *
+ * Arjan thinks the old way was overly complex, so it got simplified.
+ * Alan disagrees, saying that need the complexity to handle the weird
+ * nature of ISA shared interrupts.  (This is a special exception.)
+ *
+ * In order to handle ISA shared interrupts properly, we need to check
+ * that all ports have been serviced, and therefore the ISA interrupt
+ * line has been de-asserted.
+ *
+ * This means we need to loop through all ports. checking that they
+ * don't have an interrupt pending.
+ */
+static irqreturn_t serialxr_interrupt(int irq, void *dev_id)
+{
+	struct irq_info *i = dev_id;
+	struct list_head *l, *end = NULL;
+	int pass_counter = 0;
+
+	DEBUG_INTR("serialxr_interrupt(%d)...", irq);
+
+	spin_lock(&i->lock);
+
+	l = i->head;
+	do {
+		struct uart_xr_port *up;
+		unsigned int iir;
+
+		up = list_entry(l, struct uart_xr_port, list);
+
+		iir = serial_in(up, UART_IIR);
+		if (!(iir & UART_IIR_NO_INT)) {
+			spin_lock(&up->port.lock);
+			serialxr_handle_port(up);
+			spin_unlock(&up->port.lock);
+
+			end = NULL;
+		} else if (end == NULL)
+			end = l;
+
+		l = l->next;
+
+		if (l == i->head && pass_counter++ > PASS_LIMIT) {
+			/* If we hit this, we're dead. */
+			pr_err("serialxr: too much work for irq%d\n", irq);
+			break;
+		}
+	} while (l != end);
+
+	spin_unlock(&i->lock);
+
+	DEBUG_INTR("end.\n");
+	/* FIXME! Was it really ours? */
+	return IRQ_HANDLED;
+}
+
+/*
+ * To support ISA shared interrupts, we need to have one interrupt
+ * handler that ensures that the IRQ line has been deasserted
+ * before returning.  Failing to do this will result in the IRQ
+ * line being stuck active, and, since ISA irqs are edge triggered,
+ * no more IRQs will be seen.
+ */
+static void serial_do_unlink(struct irq_info *i, struct uart_xr_port *up)
+{
+	spin_lock_irq(&i->lock);
+
+	if (!list_empty(i->head)) {
+		if (i->head == &up->list)
+			i->head = i->head->next;
+		list_del(&up->list);
+	} else {
+		BUG_ON(i->head != &up->list);
+		i->head = NULL;
+	}
+
+	spin_unlock_irq(&i->lock);
+	/* List empty so throw away the hash node */
+	if (i->head == NULL) {
+		hlist_del(&i->node);
+		kfree(i);
+	}
+}
+
+static int serial_link_irq_chain(struct uart_xr_port *up)
+{
+	struct hlist_head *h;
+	struct hlist_node *n;
+	struct irq_info *i;
+	int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;
+
+	mutex_lock(&hash_mutex);
+
+	h = &irq_lists[up->port.irq % NR_IRQ_HASH];
+
+	hlist_for_each(n, h) {
+		i = hlist_entry(n, struct irq_info, node);
+		if (i->irq == up->port.irq)
+			break;
+	}
+
+	if (n == NULL) {
+		i = kzalloc(sizeof(struct irq_info), GFP_KERNEL);
+		if (i == NULL) {
+			mutex_unlock(&hash_mutex);
+			return -ENOMEM;
+		}
+		spin_lock_init(&i->lock);
+		i->irq = up->port.irq;
+		hlist_add_head(&i->node, h);
+	}
+	mutex_unlock(&hash_mutex);
+
+	spin_lock_irq(&i->lock);
+
+	if (i->head) {
+		list_add(&up->list, i->head);
+		spin_unlock_irq(&i->lock);
+
+		ret = 0;
+	} else {
+		INIT_LIST_HEAD(&up->list);
+		i->head = &up->list;
+		spin_unlock_irq(&i->lock);
+		irq_flags |= up->port.irqflags;
+		ret = request_irq(up->port.irq, serialxr_interrupt,
+				  irq_flags, "xrserial", i);
+		if (ret < 0)
+			serial_do_unlink(i, up);
+	}
+
+	return ret;
+}
+
+static void serial_unlink_irq_chain(struct uart_xr_port *up)
+{
+	struct irq_info *i;
+	struct hlist_node *n;
+	struct hlist_head *h;
+
+	mutex_lock(&hash_mutex);
+
+	h = &irq_lists[up->port.irq % NR_IRQ_HASH];
+
+	hlist_for_each(n, h) {
+		i = hlist_entry(n, struct irq_info, node);
+		if (i->irq == up->port.irq)
+			break;
+	}
+
+	BUG_ON(n == NULL);
+	BUG_ON(i->head == NULL);
+
+	if (list_empty(i->head))
+		free_irq(up->port.irq, i);
+
+	serial_do_unlink(i, up);
+	mutex_unlock(&hash_mutex);
+}
+
+/*
+ * This function is used to handle ports that do not have an
+ * interrupt.  This doesn't work very well for 16450's, but gives
+ * barely passable results for a 16550A.  (Although at the expense
+ * of much CPU overhead).
+ */
+static void serialxr_timeout(unsigned long data)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)data;
+	unsigned int timeout;
+	unsigned int iir;
+
+	iir = serial_in(up, UART_IIR);
+	if (!(iir & UART_IIR_NO_INT)) {
+		spin_lock(&up->port.lock);
+		serialxr_handle_port(up);
+		spin_unlock(&up->port.lock);
+	}
+
+	timeout = up->port.timeout;
+	timeout = timeout > 6 ? (timeout / 2 - 2) : 1;
+	mod_timer(&up->timer, jiffies + timeout);
+}
+
+static unsigned int serialxr_tx_empty(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned long flags;
+	unsigned int ret;
+
+	spin_lock_irqsave(&up->port.lock, flags);
+	ret = serial_in(up, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0;
+	spin_unlock_irqrestore(&up->port.lock, flags);
+
+	return ret;
+}
+
+static unsigned int serialxr_get_mctrl(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned char status;
+	unsigned int ret;
+
+	status = serial_in(up, UART_MSR);
+
+	ret = 0;
+	if (status & UART_MSR_DCD)
+		ret |= TIOCM_CAR;
+	if (status & UART_MSR_RI)
+		ret |= TIOCM_RNG;
+	if (status & UART_MSR_DSR)
+		ret |= TIOCM_DSR;
+	if (status & UART_MSR_CTS)
+		ret |= TIOCM_CTS;
+	return ret;
+}
+
+static void serialxr_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned char mcr = 0;
+
+	if (mctrl & TIOCM_RTS)
+		mcr |= UART_MCR_RTS;
+	if (mctrl & TIOCM_DTR)
+		mcr |= UART_MCR_DTR;
+	if (mctrl & TIOCM_OUT1)
+		mcr |= UART_MCR_OUT1;
+	if (mctrl & TIOCM_OUT2)
+		mcr |= UART_MCR_OUT2;
+	if (mctrl & TIOCM_LOOP)
+		mcr |= UART_MCR_LOOP;
+
+	mcr = (mcr & up->mcr_mask) | up->mcr_force;
+
+	serial_out(up, UART_MCR, mcr);
+}
+
+static void serialxr_break_ctl(struct uart_port *port, int break_state)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned long flags;
+
+	spin_lock_irqsave(&up->port.lock, flags);
+	if (break_state == -1)
+		up->lcr |= UART_LCR_SBC;
+	else
+		up->lcr &= ~UART_LCR_SBC;
+	serial_out(up, UART_LCR, up->lcr);
+	spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static int serialxr_startup(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned long flags;
+	int retval;
+
+	up->capabilities = uart_config[up->port.type].flags;
+
+	serial_out(up, XR_17C15X_EXTENDED_EFR, UART_EFR_ECB);
+	serial_out(up, UART_IER, 0);
+
+	/*
+	 * Set the RX trigger level for 32 bytes,
+	 * with a Hysteresis level of 8
+	 * These are some default values, the OEMs can change these values
+	 * according to their best case scenarios
+	 */
+
+	serial_out(up, XR_17C15X_EXTENDED_RXTRG, 32);
+	serial_out(up, XR_17C15X_EXTENDED_FCTR,
+		   XR_17C15X_FCTR_TRGD | XR_17C15X_FCTR_RTS_8DELAY);
+
+	serial_outp(up, UART_LCR, 0);
+
+	/* Wake up and initialize UART */
+	serial_out(up, XR_17C15X_EXTENDED_EFR, UART_EFR_ECB);
+	serial_out(up, UART_IER, 0);
+	serial_out(up, UART_LCR, 0);
+
+	/*
+	 * Clear the FIFO buffers and disable them.
+	 * (they will be reeanbled in set_termios())
+	 */
+	serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO |
+		    UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	serial_outp(up, UART_FCR, 0);
+
+	/*
+	 * Clear the interrupt registers.
+	 */
+	(void)serial_inp(up, UART_LSR);
+	(void)serial_inp(up, UART_RX);
+	(void)serial_inp(up, UART_IIR);
+	(void)serial_inp(up, UART_MSR);
+
+	/*
+	 * If the "interrupt" for this port doesn't correspond with any
+	 * hardware interrupt, we use a timer-based system.  The original
+	 * driver used to do this with IRQ0.
+	 */
+	if (!is_real_interrupt(up->port.irq)) {
+		unsigned int timeout = up->port.timeout;
+
+		timeout = timeout > 6 ? (timeout / 2 - 2) : 1;
+
+		up->timer.data = (unsigned long)up;
+		mod_timer(&up->timer, jiffies + timeout);
+	} else {
+		retval = serial_link_irq_chain(up);
+		if (retval)
+			return retval;
+	}
+
+	/*
+	 * Now, initialize the UART
+	 */
+	serial_outp(up, UART_LCR, UART_LCR_WLEN8);
+
+	spin_lock_irqsave(&up->port.lock, flags);
+
+	/*
+	 * Most PC uarts need OUT2 raised to enable interrupts.
+	 */
+	if (is_real_interrupt(up->port.irq))
+		up->port.mctrl |= TIOCM_OUT2;
+
+	serialxr_set_mctrl(&up->port, up->port.mctrl);
+	spin_unlock_irqrestore(&up->port.lock, flags);
+
+	/*
+	 * Finally, enable interrupts.  Note: Modem status interrupts
+	 * are set via set_termios(), which will be occurring imminently
+	 * anyway, so we don't enable them here.
+	 */
+	up->ier = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
+	/* extra for XR17c15x */
+	up->ier |= XR_17C15X_IER_RTSDTR | XR_17C15X_IER_CTSDSR;
+	serial_outp(up, UART_IER, up->ier);
+
+	/*
+	 * And clear the interrupt registers again for luck.
+	 */
+	(void)serial_inp(up, UART_LSR);
+	(void)serial_inp(up, UART_RX);
+	(void)serial_inp(up, UART_IIR);
+	(void)serial_inp(up, UART_MSR);
+
+	return 0;
+}
+
+static void serialxr_shutdown(struct uart_port *port)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned long flags;
+
+	/*
+	 * Disable interrupts from this port
+	 */
+	up->ier = 0;
+	serial_outp(up, UART_IER, 0);
+
+	spin_lock_irqsave(&up->port.lock, flags);
+
+	up->port.mctrl &= ~TIOCM_OUT2;
+
+	serialxr_set_mctrl(&up->port, up->port.mctrl);
+	spin_unlock_irqrestore(&up->port.lock, flags);
+
+	/*
+	 * Disable break condition and FIFOs
+	 */
+	serial_out(up, UART_LCR, serial_inp(up, UART_LCR) & ~UART_LCR_SBC);
+	serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO |
+		    UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	serial_outp(up, UART_FCR, 0);
+
+	/*
+	 * Read data port to reset things, and then unlink from
+	 * the IRQ chain.
+	 */
+	(void)serial_in(up, UART_RX);
+
+	if (!is_real_interrupt(up->port.irq))
+		del_timer_sync(&up->timer);
+	else
+		serial_unlink_irq_chain(up);
+}
+
+static unsigned int serialxr_get_divisor(struct uart_port *port,
+					 unsigned int baud)
+{
+	unsigned int quot;
+
+	quot = uart_get_divisor(port, baud);
+
+	return quot;
+}
+
+static void
+serialxr_set_termios(struct uart_port *port, struct ktermios *termios,
+		     struct ktermios *old)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	unsigned char cval;
+	unsigned long flags;
+	unsigned int baud, quot;
+
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		cval = 0x00;
+		break;
+	case CS6:
+		cval = 0x01;
+		break;
+	case CS7:
+		cval = 0x02;
+		break;
+	default:
+	case CS8:
+		cval = 0x03;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		cval |= 0x04;
+	if (termios->c_cflag & PARENB)
+		cval |= UART_LCR_PARITY;
+	if (!(termios->c_cflag & PARODD))
+		cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (termios->c_cflag & CMSPAR)
+		cval |= UART_LCR_SPAR;
+#endif
+
+	/*
+	 * Ask the core to calculate the divisor for us.
+	 */
+	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
+	quot = serialxr_get_divisor(port, baud);
+
+	/*
+	 * Ok, we're now changing the port state.  Do it with
+	 * interrupts disabled.
+	 */
+	spin_lock_irqsave(&up->port.lock, flags);
+
+	/*
+	 * Update the per-port timeout.
+	 */
+	uart_update_timeout(port, termios->c_cflag, baud);
+
+	up->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (termios->c_iflag & INPCK)
+		up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (termios->c_iflag & (BRKINT | PARMRK))
+		up->port.read_status_mask |= UART_LSR_BI;
+
+	/*
+	 * Characteres to ignore
+	 */
+	up->port.ignore_status_mask = 0;
+	if (termios->c_iflag & IGNPAR)
+		up->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if (termios->c_iflag & IGNBRK) {
+		up->port.ignore_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too (for real raw support).
+		 */
+		if (termios->c_iflag & IGNPAR)
+			up->port.ignore_status_mask |= UART_LSR_OE;
+	}
+
+	/*
+	 * ignore all characters if CREAD is not set
+	 */
+	if ((termios->c_cflag & CREAD) == 0)
+		up->port.ignore_status_mask |= UART_LSR_DR;
+
+	/*
+	 * CTS flow control flag and modem status interrupts
+	 */
+	up->ier &= ~UART_IER_MSI;
+	if (UART_ENABLE_MS(&up->port, termios->c_cflag))
+		up->ier |= UART_IER_MSI;
+
+	serial_out(up, UART_IER, up->ier);
+
+	serial_outp(up, XR_17C15X_EXTENDED_EFR,
+		    termios->c_cflag & CRTSCTS ? UART_EFR_CTS : 0);
+
+	serial_outp(up, UART_LCR, cval | UART_LCR_DLAB);	/* set DLAB */
+
+	serial_outp(up, UART_DLL, quot & 0xff);	/* LS of divisor */
+	serial_outp(up, UART_DLM, quot >> 8);	/* MS of divisor */
+	serial_outp(up, UART_LCR, cval);	/* reset DLAB */
+	up->lcr = cval;		/* Save LCR */
+
+	serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO);	/* set fcr */
+
+	spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+/*
+ *      EXAR ioctls
+ */
+/* #define       FIOQSIZE                0x5460 */
+#define		EXAR_READ_REG		(FIOQSIZE + 1)
+#define		EXAR_WRITE_REG		(FIOQSIZE + 2)
+
+struct xrioctl_rw_reg {
+	unsigned char reg;
+	unsigned char regvalue;
+};
+/*
+ * This function is used to handle Exar Device specific ioctl calls
+ * The user level application should have defined the above ioctl
+ * commands with the above values to access these ioctls and the
+ * input parameters for these ioctls should be struct xrioctl_rw_reg
+ * The Ioctl functioning is pretty much self explanatory here in the code,
+ * and the register values should be between 0 to XR_17C15X_EXTENDED_RXTRG
+ */
+
+static int
+serialxr_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	int ret = -ENOIOCTLCMD;
+	struct xrioctl_rw_reg ioctlrwarg;
+
+	switch (cmd) {
+	case EXAR_READ_REG:
+		if (copy_from_user
+		    (&ioctlrwarg, (void *)arg, sizeof(ioctlrwarg)))
+			return -EFAULT;
+		ioctlrwarg.regvalue = serial_inp(up, ioctlrwarg.reg);
+		if (copy_to_user((void *)arg, &ioctlrwarg, sizeof(ioctlrwarg)))
+			return -EFAULT;
+		ret = 0;
+		break;
+
+	case EXAR_WRITE_REG:
+		if (copy_from_user
+		    (&ioctlrwarg, (void *)arg, sizeof(ioctlrwarg)))
+			return -EFAULT;
+		serial_outp(up, ioctlrwarg.reg, ioctlrwarg.regvalue);
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+static void
+serialxr_pm(struct uart_port *port, unsigned int state, unsigned int oldstate)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+	if (state) {
+		/* sleep */
+		serial_outp(up, XR_17C15X_EXTENDED_EFR, UART_EFR_ECB);
+		serial_outp(up, UART_IER, UART_IERX_SLEEP);
+		serial_outp(up, XR_17C15X_EXTENDED_EFR, 0);
+
+		if (up->pm)
+			up->pm(port, state, oldstate);
+	} else {
+		/* wake */
+
+		/* Wake up UART */
+		serial_outp(up, XR_17C15X_EXTENDED_EFR, UART_EFR_ECB);
+		serial_outp(up, UART_IER, 0);
+		serial_outp(up, XR_17C15X_EXTENDED_EFR, 0);
+
+		if (up->pm)
+			up->pm(port, state, oldstate);
+	}
+}
+
+static void serialxr_release_port(struct uart_port *port)
+{
+}
+
+static int serialxr_request_port(struct uart_port *port)
+{
+	return 0;
+}
+
+static void serialxr_config_port(struct uart_port *port, int flags)
+{
+	struct uart_xr_port *up = (struct uart_xr_port *)port;
+
+	if (flags & UART_CONFIG_TYPE) {
+		up->port.type = XRPCI_TYPE;
+		up->port.fifosize =
+		    uart_config[up->port.type].dfl_xmit_fifo_size;
+		up->capabilities = uart_config[up->port.type].flags;
+	}
+}
+
+static const char *serialxr_type(struct uart_port *port)
+{
+	int type = port->type;
+
+	if (type >= ARRAY_SIZE(uart_config))
+		type = 0;
+	return uart_config[type].name;
+}
+
+static struct uart_ops serialxr_pops = {
+	.tx_empty = serialxr_tx_empty,
+	.set_mctrl = serialxr_set_mctrl,
+	.get_mctrl = serialxr_get_mctrl,
+	.stop_tx = serialxr_stop_tx,
+	.start_tx = serialxr_start_tx,
+	.stop_rx = serialxr_stop_rx,
+	.enable_ms = serialxr_enable_ms,
+	.break_ctl = serialxr_break_ctl,
+	.startup = serialxr_startup,
+	.shutdown = serialxr_shutdown,
+	.set_termios = serialxr_set_termios,
+	.pm = serialxr_pm,
+	.type = serialxr_type,
+	.release_port = serialxr_release_port,
+	.request_port = serialxr_request_port,
+	.config_port = serialxr_config_port,
+	.ioctl = serialxr_ioctl,
+};
+
+static DEFINE_MUTEX(serial_mutex);
+
+static struct uart_xr_port serialxr_ports[NR_PORTS];
+
+#define SERIALXR_CONSOLE	NULL
+
+static struct uart_driver xr_uart_driver = {
+	.owner = THIS_MODULE,
+	.driver_name = "xrserial",
+	.dev_name = "ttyS",
+	.major = XR_MAJOR,
+	.minor = XR_MINOR,
+	.nr = NR_PORTS,
+	.cons = SERIALXR_CONSOLE,
+};
+
+static struct uart_xr_port *serialxr_find_match_or_unused(struct uart_port
+							  *port)
+{
+	int i;
+
+	/*
+	 * First, find a port entry which matches.
+	 */
+	for (i = 0; i < NR_PORTS; i++)
+		if (uart_match_port(&serialxr_ports[i].port, port))
+			return &serialxr_ports[i];
+
+	/*
+	 * We didn't find a matching entry, so look for the first
+	 * free entry.  We look for one which hasn't been previously
+	 * used (indicated by zero iobase).
+	 */
+	for (i = 0; i < NR_PORTS; i++)
+		if (serialxr_ports[i].port.type == PORT_UNKNOWN &&
+		    serialxr_ports[i].port.iobase == 0) {
+			port->line = i;
+			return &serialxr_ports[i];
+		}
+
+	/*
+	 * That also failed.  Last resort is to find any entry which
+	 * doesn't have a real port associated with it.
+	 */
+	for (i = 0; i < NR_PORTS; i++)
+		if (serialxr_ports[i].port.type == PORT_UNKNOWN)
+			return &serialxr_ports[i];
+
+	return NULL;
+}
+
+/*
+ *	serialxr_register_port - register a serial port
+ *	@port: serial port template
+ *
+ *	Configure the serial port specified by the request. If the
+ *	port exists and is in use, it is hung up and unregistered
+ *	first.
+ *
+ *	The port is then probed and if necessary the IRQ is autodetected
+ *	If this fails an error is returned.
+ *
+ *	On success the port is ready to use and the line number is returned.
+ */
+int serialxr_register_port(struct uart_port *port)
+{
+	struct uart_xr_port *uart;
+	int ret = -ENOSPC;
+
+	if (port->uartclk == 0)
+		return -EINVAL;
+
+	mutex_lock(&serial_mutex);
+	uart = serialxr_find_match_or_unused(port);
+	if (uart) {
+		uart->port.iobase = port->iobase;
+		uart->port.membase = port->membase;
+		uart->port.irq = port->irq;
+		uart->port.uartclk = port->uartclk;
+		uart->port.fifosize = port->fifosize;
+		uart->port.regshift = port->regshift;
+		uart->port.iotype = port->iotype;
+		uart->port.flags = port->flags | UPF_BOOT_AUTOCONF;
+		uart->port.mapbase = port->mapbase;
+		if (port->dev)
+			uart->port.dev = port->dev;
+
+		uart->port.line = port->line;
+		spin_lock_init(&uart->port.lock);
+
+		init_timer(&uart->timer);
+		uart->timer.function = serialxr_timeout;
+
+		/*
+		 * ALPHA_KLUDGE_MCR needs to be killed.
+		 */
+		uart->mcr_mask = ~ALPHA_KLUDGE_MCR;
+		uart->mcr_force = ALPHA_KLUDGE_MCR;
+
+		uart->port.ops = &serialxr_pops;
+
+		ret = uart_add_one_port(&xr_uart_driver, &uart->port);
+		if (ret == 0)
+			ret = uart->port.line;
+	}
+	mutex_unlock(&serial_mutex);
+
+	return ret;
+}
+
+/*
+ * Probe one serial board.  Unfortunately, there is no rhyme nor reason
+ * to the arrangement of serial ports on a PCI card.
+ */
+static int
+init_one_xrpciserialcard(struct pci_dev *dev, const struct pci_device_id *ent)
+{
+	struct serial_private *priv;
+	struct pciserial_board *board;
+	struct pci_serial_quirk *quirk;
+	struct uart_port serial_port;
+	int rc, nr_ports, i;
+
+	if (ent->driver_data >= ARRAY_SIZE(xrpciserial_boards)) {
+		pr_info("pci_init_one: invalid driver_data: %ld\n",
+		       ent->driver_data);
+		return -EINVAL;
+	}
+
+	board = &xrpciserial_boards[ent->driver_data];
+
+	rc = pci_enable_device(dev);
+	if (rc)
+		return rc;
+
+	nr_ports = board->num_ports;
+
+	/*
+	 * Find an init and setup quirks.
+	 */
+	quirk = find_quirk(dev);
+
+	/*
+	 * Run the new-style initialization function.
+	 * The initialization function returns:
+	 *  <0  - error
+	 *   0  - use board->num_ports
+	 *  >0  - number of ports
+	 */
+	if (quirk->init) {
+		rc = quirk->init(dev);
+		if (rc < 0)
+			goto disable;
+		if (rc)
+			nr_ports = rc;
+	}
+
+	priv = kmalloc(sizeof(struct serial_private) +
+		       sizeof(unsigned int) * nr_ports, GFP_KERNEL);
+	if (!priv) {
+		rc = -ENOMEM;
+		goto deinit;
+	}
+
+	memset(priv, 0, sizeof(struct serial_private) +
+	       sizeof(unsigned int) * nr_ports);
+
+	priv->dev = dev;
+	priv->quirk = quirk;
+
+	memset(&serial_port, 0, sizeof(struct uart_port));
+	serial_port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ;
+	serial_port.uartclk = board->base_baud * 16;
+	serial_port.irq = dev->irq;
+	serial_port.dev = &dev->dev;
+	for (i = 0; i < nr_ports; i++) {
+		if (quirk->setup(priv, board, &serial_port, i))
+			break;
+
+		priv->line[i] = serialxr_register_port(&serial_port);
+		if (priv->line[i] < 0) {
+			pr_warn("Couldn't register serial port %s: %d\n",
+			       pci_name(dev), priv->line[i]);
+			break;
+		}
+	}
+
+	priv->nr = i;
+
+	if (!IS_ERR(priv)) {
+		pci_set_drvdata(dev, priv);
+		return 0;
+	}
+
+deinit:
+	if (quirk->exit)
+		quirk->exit(dev);
+disable:
+	pci_disable_device(dev);
+	return rc;
+}
+
+/*
+ *	serialxr_unregister_port - remove a serial port at runtime
+ *	@line: serial line number
+ *
+ *	Remove one serial port.  This may not be called from interrupt
+ *	context.  We hand the port back to the our control.
+ */
+void serialxr_unregister_port(int line)
+{
+	struct uart_xr_port *uart = &serialxr_ports[line];
+
+	mutex_lock(&serial_mutex);
+	uart_remove_one_port(&xr_uart_driver, &uart->port);
+	uart->port.dev = NULL;
+	mutex_unlock(&serial_mutex);
+}
+
+void pciserial_remove_ports(struct serial_private *priv)
+{
+	struct pci_serial_quirk *quirk;
+	int i;
+
+	for (i = 0; i < priv->nr; i++)
+		serialxr_unregister_port(priv->line[i]);
+
+	for (i = 0; i < PCI_NUM_BAR_RESOURCES; i++) {
+		if (priv->remapped_bar[i])
+			iounmap(priv->remapped_bar[i]);
+		priv->remapped_bar[i] = NULL;
+	}
+
+	/*
+	 * Find the exit quirks.
+	 */
+	quirk = find_quirk(priv->dev);
+	if (quirk->exit)
+		quirk->exit(priv->dev);
+
+	kfree(priv);
+}
+
+static void remove_one_xrpciserialcard(struct pci_dev *dev)
+{
+	struct serial_private *priv = pci_get_drvdata(dev);
+
+	pci_set_drvdata(dev, NULL);
+
+	pciserial_remove_ports(priv);
+
+	pci_disable_device(dev);
+}
+
+static struct pci_device_id xrserial_pci_tbl[] = {
+	{0x13a8, 0x158,
+	 PCI_ANY_ID, PCI_ANY_ID,
+	 0, 0, xr_8port},
+	{0x13a8, 0x154,
+	 PCI_ANY_ID, PCI_ANY_ID,
+	 0, 0, xr_4port},
+	{0x13a8, 0x152,
+	 PCI_ANY_ID, PCI_ANY_ID,
+	 0, 0, xr_2port},
+	{0,}
+};
+
+static struct pci_driver xrserial_pci_driver = {
+	.name = "xrserial",
+	.probe = init_one_xrpciserialcard,
+	.remove = remove_one_xrpciserialcard,
+	.id_table = xrserial_pci_tbl,
+};
+
+static int __init serialxr_init(void)
+{
+	int ret;
+
+	pr_info("Exar PCI 17c15x serial driver Revision: 4.8\n");
+
+	ret = uart_register_driver(&xr_uart_driver);
+	if (ret)
+		return ret;
+
+	ret = pci_register_driver(&xrserial_pci_driver);
+
+	if (ret < 0)
+		uart_unregister_driver(&xr_uart_driver);
+
+	return ret;
+}
+
+static void __exit serialxr_exit(void)
+{
+	pci_unregister_driver(&xrserial_pci_driver);
+	uart_unregister_driver(&xr_uart_driver);
+}
+
+module_init(serialxr_init);
+module_exit(serialxr_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Exar PCI specific serial driver for XR17c15x- Rev: 4.8");
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ