Messages in this thread Patch in this message | | | From | Michael Welling <> | Subject | [PATCH 1/2] tty serial: xr17c15x driver | Date | Wed, 9 Apr 2014 23:04:30 -0500 |
| |
Signed-off-by: Michael Welling <mwelling@ieee.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
| |