diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/serial/8250_pci.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/serial/8250_pci.c')
-rw-r--r-- | drivers/serial/8250_pci.c | 2303 |
1 files changed, 2303 insertions, 0 deletions
diff --git a/drivers/serial/8250_pci.c b/drivers/serial/8250_pci.c new file mode 100644 index 00000000000..f8d90d0ecfe --- /dev/null +++ b/drivers/serial/8250_pci.c @@ -0,0 +1,2303 @@ +/* + * linux/drivers/char/8250_pci.c + * + * Probe module for 8250/16550-type PCI serial ports. + * + * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * + * 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. + * + * $Id: 8250_pci.c,v 1.28 2002/11/02 11:14:18 rmk Exp $ + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <linux/serial_core.h> +#include <linux/8250_pci.h> +#include <linux/bitops.h> + +#include <asm/byteorder.h> +#include <asm/io.h> + +#include "8250.h" + +#undef SERIAL_DEBUG_PCI + +/* + * Definitions for PCI support. + */ +#define FL_BASE_MASK 0x0007 +#define FL_BASE0 0x0000 +#define FL_BASE1 0x0001 +#define FL_BASE2 0x0002 +#define FL_BASE3 0x0003 +#define FL_BASE4 0x0004 +#define FL_GET_BASE(x) (x & FL_BASE_MASK) + +/* Use successive BARs (PCI base address registers), + else use offset into some specified BAR */ +#define FL_BASE_BARS 0x0008 + +/* do not assign an irq */ +#define FL_NOIRQ 0x0080 + +/* Use the Base address register size to cap number of ports */ +#define FL_REGION_SZ_CAP 0x0100 + +struct pci_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 pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx); + void (*exit)(struct pci_dev *dev); +}; + +#define PCI_NUM_BAR_RESOURCES 6 + +struct serial_private { + unsigned int nr; + void __iomem *remapped_bar[PCI_NUM_BAR_RESOURCES]; + struct pci_serial_quirk *quirk; + int line[0]; +}; + +static void moan_device(const char *str, struct pci_dev *dev) +{ + printk(KERN_WARNING "%s: %s\n" + KERN_WARNING "Please send the output of lspci -vv, this\n" + KERN_WARNING "message (0x%04x,0x%04x,0x%04x,0x%04x), the\n" + KERN_WARNING "manufacturer and name of serial board or\n" + KERN_WARNING "modem board to rmk+serial@arm.linux.org.uk.\n", + pci_name(dev), str, dev->vendor, dev->device, + dev->subsystem_vendor, dev->subsystem_device); +} + +static int +setup_port(struct pci_dev *dev, struct uart_port *port, + int bar, int offset, int regshift) +{ + struct serial_private *priv = pci_get_drvdata(dev); + unsigned long base, len; + + if (bar >= PCI_NUM_BAR_RESOURCES) + return -EINVAL; + + if (pci_resource_flags(dev, bar) & IORESOURCE_MEM) { + base = pci_resource_start(dev, bar); + 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->mapbase = base + offset; + port->membase = priv->remapped_bar[bar] + offset; + port->regshift = regshift; + } else { + base = pci_resource_start(dev, bar) + offset; + port->iotype = UPIO_PORT; + port->iobase = base; + } + return 0; +} + +/* + * AFAVLAB uses a different mixture of BARs and offsets + * Not that ugly ;) -- HW + */ +static int +afavlab_setup(struct pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx) +{ + unsigned int bar, offset = board->first_offset; + + bar = FL_GET_BASE(board->flags); + if (idx < 4) + bar += idx; + else { + bar = 4; + offset += (idx - 4) * board->uart_offset; + } + + return setup_port(dev, port, bar, offset, board->reg_shift); +} + +/* + * HP's Remote Management Console. The Diva chip came in several + * different versions. N-class, L2000 and A500 have two Diva chips, each + * with 3 UARTs (the third UART on the second chip is unused). Superdome + * and Keystone have one Diva chip with 3 UARTs. Some later machines have + * one Diva chip, but it has been expanded to 5 UARTs. + */ +static int __devinit pci_hp_diva_init(struct pci_dev *dev) +{ + int rc = 0; + + switch (dev->subsystem_device) { + case PCI_DEVICE_ID_HP_DIVA_TOSCA1: + case PCI_DEVICE_ID_HP_DIVA_HALFDOME: + case PCI_DEVICE_ID_HP_DIVA_KEYSTONE: + case PCI_DEVICE_ID_HP_DIVA_EVEREST: + rc = 3; + break; + case PCI_DEVICE_ID_HP_DIVA_TOSCA2: + rc = 2; + break; + case PCI_DEVICE_ID_HP_DIVA_MAESTRO: + rc = 4; + break; + case PCI_DEVICE_ID_HP_DIVA_POWERBAR: + rc = 1; + break; + } + + return rc; +} + +/* + * HP's Diva chip puts the 4th/5th serial port further out, and + * some serial ports are supposed to be hidden on certain models. + */ +static int +pci_hp_diva_setup(struct pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx) +{ + unsigned int offset = board->first_offset; + unsigned int bar = FL_GET_BASE(board->flags); + + switch (dev->subsystem_device) { + case PCI_DEVICE_ID_HP_DIVA_MAESTRO: + if (idx == 3) + idx++; + break; + case PCI_DEVICE_ID_HP_DIVA_EVEREST: + if (idx > 0) + idx++; + if (idx > 2) + idx++; + break; + } + if (idx > 2) + offset = 0x18; + + offset += idx * board->uart_offset; + + return setup_port(dev, port, bar, offset, board->reg_shift); +} + +/* + * Added for EKF Intel i960 serial boards + */ +static int __devinit pci_inteli960ni_init(struct pci_dev *dev) +{ + unsigned long oldval; + + if (!(dev->subsystem_device & 0x1000)) + return -ENODEV; + + /* is firmware started? */ + pci_read_config_dword(dev, 0x44, (void*) &oldval); + if (oldval == 0x00001000L) { /* RESET value */ + printk(KERN_DEBUG "Local i960 firmware missing"); + return -ENODEV; + } + return 0; +} + +/* + * Some PCI serial cards using the PLX 9050 PCI interface chip require + * that the card interrupt be explicitly enabled or disabled. This + * seems to be mainly needed on card using the PLX which also use I/O + * mapped memory. + */ +static int __devinit pci_plx9050_init(struct pci_dev *dev) +{ + u8 irq_config; + void __iomem *p; + + if ((pci_resource_flags(dev, 0) & IORESOURCE_MEM) == 0) { + moan_device("no memory in bar 0", dev); + return 0; + } + + irq_config = 0x41; + if (dev->vendor == PCI_VENDOR_ID_PANACOM) + irq_config = 0x43; + if ((dev->vendor == PCI_VENDOR_ID_PLX) && + (dev->device == PCI_DEVICE_ID_PLX_ROMULUS)) { + /* + * As the megawolf cards have the int pins active + * high, and have 2 UART chips, both ints must be + * enabled on the 9050. Also, the UARTS are set in + * 16450 mode by default, so we have to enable the + * 16C950 'enhanced' mode so that we can use the + * deep FIFOs + */ + irq_config = 0x5b; + } + + /* + * enable/disable interrupts + */ + p = ioremap(pci_resource_start(dev, 0), 0x80); + if (p == NULL) + return -ENOMEM; + writel(irq_config, p + 0x4c); + + /* + * Read the register back to ensure that it took effect. + */ + readl(p + 0x4c); + iounmap(p); + + return 0; +} + +static void __devexit pci_plx9050_exit(struct pci_dev *dev) +{ + u8 __iomem *p; + + if ((pci_resource_flags(dev, 0) & IORESOURCE_MEM) == 0) + return; + + /* + * disable interrupts + */ + p = ioremap(pci_resource_start(dev, 0), 0x80); + if (p != NULL) { + writel(0, p + 0x4c); + + /* + * Read the register back to ensure that it took effect. + */ + readl(p + 0x4c); + iounmap(p); + } +} + +/* SBS Technologies Inc. PMC-OCTPRO and P-OCTAL cards */ +static int +sbs_setup(struct pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx) +{ + unsigned int bar, offset = board->first_offset; + + bar = 0; + + if (idx < 4) { + /* first four channels map to 0, 0x100, 0x200, 0x300 */ + offset += idx * board->uart_offset; + } else if (idx < 8) { + /* last four channels map to 0x1000, 0x1100, 0x1200, 0x1300 */ + offset += idx * board->uart_offset + 0xC00; + } else /* we have only 8 ports on PMC-OCTALPRO */ + return 1; + + return setup_port(dev, port, bar, offset, board->reg_shift); +} + +/* +* This does initialization for PMC OCTALPRO cards: +* maps the device memory, resets the UARTs (needed, bc +* if the module is removed and inserted again, the card +* is in the sleep mode) and enables global interrupt. +*/ + +/* global control register offset for SBS PMC-OctalPro */ +#define OCT_REG_CR_OFF 0x500 + +static int __devinit sbs_init(struct pci_dev *dev) +{ + u8 __iomem *p; + + p = ioremap(pci_resource_start(dev, 0),pci_resource_len(dev,0)); + + if (p == NULL) + return -ENOMEM; + /* Set bit-4 Control Register (UART RESET) in to reset the uarts */ + writeb(0x10,p + OCT_REG_CR_OFF); + udelay(50); + writeb(0x0,p + OCT_REG_CR_OFF); + + /* Set bit-2 (INTENABLE) of Control Register */ + writeb(0x4, p + OCT_REG_CR_OFF); + iounmap(p); + + return 0; +} + +/* + * Disables the global interrupt of PMC-OctalPro + */ + +static void __devexit sbs_exit(struct pci_dev *dev) +{ + u8 __iomem *p; + + p = ioremap(pci_resource_start(dev, 0),pci_resource_len(dev,0)); + if (p != NULL) { + writeb(0, p + OCT_REG_CR_OFF); + } + iounmap(p); +} + +/* + * SIIG serial cards have an PCI interface chip which also controls + * the UART clocking frequency. Each UART can be clocked independently + * (except cards equiped with 4 UARTs) and initial clocking settings + * are stored in the EEPROM chip. It can cause problems because this + * version of serial driver doesn't support differently clocked UART's + * on single PCI card. To prevent this, initialization functions set + * high frequency clocking for all UART's on given card. It is safe (I + * hope) because it doesn't touch EEPROM settings to prevent conflicts + * with other OSes (like M$ DOS). + * + * SIIG support added by Andrey Panin <pazke@donpac.ru>, 10/1999 + * + * There is two family of SIIG serial cards with different PCI + * interface chip and different configuration methods: + * - 10x cards have control registers in IO and/or memory space; + * - 20x cards have control registers in standard PCI configuration space. + * + * Note: some SIIG cards are probed by the parport_serial object. + */ + +#define PCI_DEVICE_ID_SIIG_1S_10x (PCI_DEVICE_ID_SIIG_1S_10x_550 & 0xfffc) +#define PCI_DEVICE_ID_SIIG_2S_10x (PCI_DEVICE_ID_SIIG_2S_10x_550 & 0xfff8) + +static int pci_siig10x_init(struct pci_dev *dev) +{ + u16 data; + void __iomem *p; + + switch (dev->device & 0xfff8) { + case PCI_DEVICE_ID_SIIG_1S_10x: /* 1S */ + data = 0xffdf; + break; + case PCI_DEVICE_ID_SIIG_2S_10x: /* 2S, 2S1P */ + data = 0xf7ff; + break; + default: /* 1S1P, 4S */ + data = 0xfffb; + break; + } + + p = ioremap(pci_resource_start(dev, 0), 0x80); + if (p == NULL) + return -ENOMEM; + + writew(readw(p + 0x28) & data, p + 0x28); + readw(p + 0x28); + iounmap(p); + return 0; +} + +#define PCI_DEVICE_ID_SIIG_2S_20x (PCI_DEVICE_ID_SIIG_2S_20x_550 & 0xfffc) +#define PCI_DEVICE_ID_SIIG_2S1P_20x (PCI_DEVICE_ID_SIIG_2S1P_20x_550 & 0xfffc) + +static int pci_siig20x_init(struct pci_dev *dev) +{ + u8 data; + + /* Change clock frequency for the first UART. */ + pci_read_config_byte(dev, 0x6f, &data); + pci_write_config_byte(dev, 0x6f, data & 0xef); + + /* If this card has 2 UART, we have to do the same with second UART. */ + if (((dev->device & 0xfffc) == PCI_DEVICE_ID_SIIG_2S_20x) || + ((dev->device & 0xfffc) == PCI_DEVICE_ID_SIIG_2S1P_20x)) { + pci_read_config_byte(dev, 0x73, &data); + pci_write_config_byte(dev, 0x73, data & 0xef); + } + return 0; +} + +int pci_siig10x_fn(struct pci_dev *dev, int enable) +{ + int ret = 0; + if (enable) + ret = pci_siig10x_init(dev); + return ret; +} + +int pci_siig20x_fn(struct pci_dev *dev, int enable) +{ + int ret = 0; + if (enable) + ret = pci_siig20x_init(dev); + return ret; +} + +EXPORT_SYMBOL(pci_siig10x_fn); +EXPORT_SYMBOL(pci_siig20x_fn); + +/* + * Timedia has an explosion of boards, and to avoid the PCI table from + * growing *huge*, we use this function to collapse some 70 entries + * in the PCI table into one, for sanity's and compactness's sake. + */ +static unsigned short timedia_single_port[] = { + 0x4025, 0x4027, 0x4028, 0x5025, 0x5027, 0 +}; + +static unsigned short timedia_dual_port[] = { + 0x0002, 0x4036, 0x4037, 0x4038, 0x4078, 0x4079, 0x4085, + 0x4088, 0x4089, 0x5037, 0x5078, 0x5079, 0x5085, 0x6079, + 0x7079, 0x8079, 0x8137, 0x8138, 0x8237, 0x8238, 0x9079, + 0x9137, 0x9138, 0x9237, 0x9238, 0xA079, 0xB079, 0xC079, + 0xD079, 0 +}; + +static unsigned short timedia_quad_port[] = { + 0x4055, 0x4056, 0x4095, 0x4096, 0x5056, 0x8156, 0x8157, + 0x8256, 0x8257, 0x9056, 0x9156, 0x9157, 0x9158, 0x9159, + 0x9256, 0x9257, 0xA056, 0xA157, 0xA158, 0xA159, 0xB056, + 0xB157, 0 +}; + +static unsigned short timedia_eight_port[] = { + 0x4065, 0x4066, 0x5065, 0x5066, 0x8166, 0x9066, 0x9166, + 0x9167, 0x9168, 0xA066, 0xA167, 0xA168, 0 +}; + +static struct timedia_struct { + int num; + unsigned short *ids; +} timedia_data[] = { + { 1, timedia_single_port }, + { 2, timedia_dual_port }, + { 4, timedia_quad_port }, + { 8, timedia_eight_port }, + { 0, NULL } +}; + +static int __devinit pci_timedia_init(struct pci_dev *dev) +{ + unsigned short *ids; + int i, j; + + for (i = 0; timedia_data[i].num; i++) { + ids = timedia_data[i].ids; + for (j = 0; ids[j]; j++) + if (dev->subsystem_device == ids[j]) + return timedia_data[i].num; + } + return 0; +} + +/* + * Timedia/SUNIX uses a mixture of BARs and offsets + * Ugh, this is ugly as all hell --- TYT + */ +static int +pci_timedia_setup(struct pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx) +{ + unsigned int bar = 0, offset = board->first_offset; + + switch (idx) { + case 0: + bar = 0; + break; + case 1: + offset = board->uart_offset; + bar = 0; + break; + case 2: + bar = 1; + break; + case 3: + offset = board->uart_offset; + bar = 1; + case 4: /* BAR 2 */ + case 5: /* BAR 3 */ + case 6: /* BAR 4 */ + case 7: /* BAR 5 */ + bar = idx - 2; + } + + return setup_port(dev, port, bar, offset, board->reg_shift); +} + +/* + * Some Titan cards are also a little weird + */ +static int +titan_400l_800l_setup(struct pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx) +{ + unsigned int bar, offset = board->first_offset; + + switch (idx) { + case 0: + bar = 1; + break; + case 1: + bar = 2; + break; + default: + bar = 4; + offset = (idx - 2) * board->uart_offset; + } + + return setup_port(dev, port, bar, offset, board->reg_shift); +} + +static int __devinit pci_xircom_init(struct pci_dev *dev) +{ + msleep(100); + return 0; +} + +static int __devinit pci_netmos_init(struct pci_dev *dev) +{ + /* subdevice 0x00PS means <P> parallel, <S> serial */ + unsigned int num_serial = dev->subsystem_device & 0xf; + + if (num_serial == 0) + return -ENODEV; + return num_serial; +} + +static int +pci_default_setup(struct pci_dev *dev, struct pci_board *board, + struct uart_port *port, int idx) +{ + unsigned int bar, offset = board->first_offset, maxnr; + + bar = FL_GET_BASE(board->flags); + if (board->flags & FL_BASE_BARS) + bar += idx; + else + offset += idx * board->uart_offset; + + maxnr = (pci_resource_len(dev, bar) - board->first_offset) / + (8 << board->reg_shift); + + if (board->flags & FL_REGION_SZ_CAP && idx >= maxnr) + return 1; + + return setup_port(dev, port, bar, offset, board->reg_shift); +} + +/* This should be in linux/pci_ids.h */ +#define PCI_VENDOR_ID_SBSMODULARIO 0x124B +#define PCI_SUBVENDOR_ID_SBSMODULARIO 0x124B +#define PCI_DEVICE_ID_OCTPRO 0x0001 +#define PCI_SUBDEVICE_ID_OCTPRO232 0x0108 +#define PCI_SUBDEVICE_ID_OCTPRO422 0x0208 +#define PCI_SUBDEVICE_ID_POCTAL232 0x0308 +#define PCI_SUBDEVICE_ID_POCTAL422 0x0408 + +/* + * 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[] = { + /* + * AFAVLAB cards. + * It is not clear whether this applies to all products. + */ + { + .vendor = PCI_VENDOR_ID_AFAVLAB, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .setup = afavlab_setup, + }, + /* + * HP Diva + */ + { + .vendor = PCI_VENDOR_ID_HP, + .device = PCI_DEVICE_ID_HP_DIVA, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_hp_diva_init, + .setup = pci_hp_diva_setup, + }, + /* + * Intel + */ + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_80960_RP, + .subvendor = 0xe4bf, + .subdevice = PCI_ANY_ID, + .init = pci_inteli960ni_init, + .setup = pci_default_setup, + }, + /* + * Panacom + */ + { + .vendor = PCI_VENDOR_ID_PANACOM, + .device = PCI_DEVICE_ID_PANACOM_QUADMODEM, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_plx9050_init, + .setup = pci_default_setup, + .exit = __devexit_p(pci_plx9050_exit), + }, + { + .vendor = PCI_VENDOR_ID_PANACOM, + .device = PCI_DEVICE_ID_PANACOM_DUALMODEM, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_plx9050_init, + .setup = pci_default_setup, + .exit = __devexit_p(pci_plx9050_exit), + }, + /* + * PLX + */ + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9050, + .subvendor = PCI_SUBVENDOR_ID_KEYSPAN, + .subdevice = PCI_SUBDEVICE_ID_KEYSPAN_SX2, + .init = pci_plx9050_init, + .setup = pci_default_setup, + .exit = __devexit_p(pci_plx9050_exit), + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_ROMULUS, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = PCI_DEVICE_ID_PLX_ROMULUS, + .init = pci_plx9050_init, + .setup = pci_default_setup, + .exit = __devexit_p(pci_plx9050_exit), + }, + /* + * SBS Technologies, Inc., PMC-OCTALPRO 232 + */ + { + .vendor = PCI_VENDOR_ID_SBSMODULARIO, + .device = PCI_DEVICE_ID_OCTPRO, + .subvendor = PCI_SUBVENDOR_ID_SBSMODULARIO, + .subdevice = PCI_SUBDEVICE_ID_OCTPRO232, + .init = sbs_init, + .setup = sbs_setup, + .exit = __devexit_p(sbs_exit), + }, + /* + * SBS Technologies, Inc., PMC-OCTALPRO 422 + */ + { + .vendor = PCI_VENDOR_ID_SBSMODULARIO, + .device = PCI_DEVICE_ID_OCTPRO, + .subvendor = PCI_SUBVENDOR_ID_SBSMODULARIO, + .subdevice = PCI_SUBDEVICE_ID_OCTPRO422, + .init = sbs_init, + .setup = sbs_setup, + .exit = __devexit_p(sbs_exit), + }, + /* + * SBS Technologies, Inc., P-Octal 232 + */ + { + .vendor = PCI_VENDOR_ID_SBSMODULARIO, + .device = PCI_DEVICE_ID_OCTPRO, + .subvendor = PCI_SUBVENDOR_ID_SBSMODULARIO, + .subdevice = PCI_SUBDEVICE_ID_POCTAL232, + .init = sbs_init, + .setup = sbs_setup, + .exit = __devexit_p(sbs_exit), + }, + /* + * SBS Technologies, Inc., P-Octal 422 + */ + { + .vendor = PCI_VENDOR_ID_SBSMODULARIO, + .device = PCI_DEVICE_ID_OCTPRO, + .subvendor = PCI_SUBVENDOR_ID_SBSMODULARIO, + .subdevice = PCI_SUBDEVICE_ID_POCTAL422, + .init = sbs_init, + .setup = sbs_setup, + .exit = __devexit_p(sbs_exit), + }, + + /* + * SIIG cards. + * It is not clear whether these could be collapsed. + */ + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_1S_10x_550, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_1S_10x_650, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_1S_10x_850, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_2S_10x_550, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_2S_10x_650, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_2S_10x_850, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_4S_10x_550, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_4S_10x_650, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_4S_10x_850, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig10x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_1S_20x_550, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_1S_20x_650, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_1S_20x_850, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_2S_20x_550, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_2S_20x_650, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_2S_20x_850, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_4S_20x_550, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_4S_20x_650, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + { + .vendor = PCI_VENDOR_ID_SIIG, + .device = PCI_DEVICE_ID_SIIG_4S_20x_850, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_siig20x_init, + .setup = pci_default_setup, + }, + /* + * Titan cards + */ + { + .vendor = PCI_VENDOR_ID_TITAN, + .device = PCI_DEVICE_ID_TITAN_400L, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .setup = titan_400l_800l_setup, + }, + { + .vendor = PCI_VENDOR_ID_TITAN, + .device = PCI_DEVICE_ID_TITAN_800L, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .setup = titan_400l_800l_setup, + }, + /* + * Timedia cards + */ + { + .vendor = PCI_VENDOR_ID_TIMEDIA, + .device = PCI_DEVICE_ID_TIMEDIA_1889, + .subvendor = PCI_VENDOR_ID_TIMEDIA, + .subdevice = PCI_ANY_ID, + .init = pci_timedia_init, + .setup = pci_timedia_setup, + }, + { + .vendor = PCI_VENDOR_ID_TIMEDIA, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .setup = pci_timedia_setup, + }, + /* + * Xircom cards + */ + { + .vendor = PCI_VENDOR_ID_XIRCOM, + .device = PCI_DEVICE_ID_XIRCOM_X3201_MDM, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_xircom_init, + .setup = pci_default_setup, + }, + /* + * Netmos cards + */ + { + .vendor = PCI_VENDOR_ID_NETMOS, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .init = pci_netmos_init, + .setup = pci_default_setup, + }, + /* + * Default "match everything" terminator entry + */ + { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .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) && + quirk_id_matches(quirk->subvendor, dev->subsystem_vendor) && + quirk_id_matches(quirk->subdevice, dev->subsystem_device)) + break; + return quirk; +} + +static _INLINE_ int +get_pci_irq(struct pci_dev *dev, struct pci_board *board, int idx) +{ + if (board->flags & FL_NOIRQ) + return 0; + else + return dev->irq; +} + +/* + * This is the configuration table for all of the PCI serial boards + * which we support. It is directly indexed by the pci_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 pci_board_num_t { + pbn_default = 0, + + pbn_b0_1_115200, + pbn_b0_2_115200, + pbn_b0_4_115200, + pbn_b0_5_115200, + + pbn_b0_1_921600, + pbn_b0_2_921600, + pbn_b0_4_921600, + + pbn_b0_bt_1_115200, + pbn_b0_bt_2_115200, + pbn_b0_bt_8_115200, + + pbn_b0_bt_1_460800, + pbn_b0_bt_2_460800, + pbn_b0_bt_4_460800, + + pbn_b0_bt_1_921600, + pbn_b0_bt_2_921600, + pbn_b0_bt_4_921600, + pbn_b0_bt_8_921600, + + pbn_b1_1_115200, + pbn_b1_2_115200, + pbn_b1_4_115200, + pbn_b1_8_115200, + + pbn_b1_1_921600, + pbn_b1_2_921600, + pbn_b1_4_921600, + pbn_b1_8_921600, + + pbn_b1_bt_2_921600, + + pbn_b1_1_1382400, + pbn_b1_2_1382400, + pbn_b1_4_1382400, + pbn_b1_8_1382400, + + pbn_b2_1_115200, + pbn_b2_8_115200, + + pbn_b2_1_460800, + pbn_b2_4_460800, + pbn_b2_8_460800, + pbn_b2_16_460800, + + pbn_b2_1_921600, + pbn_b2_4_921600, + pbn_b2_8_921600, + + pbn_b2_bt_1_115200, + pbn_b2_bt_2_115200, + pbn_b2_bt_4_115200, + + pbn_b2_bt_2_921600, + pbn_b2_bt_4_921600, + + pbn_b3_4_115200, + pbn_b3_8_115200, + + /* + * Board-specific versions. + */ + pbn_panacom, + pbn_panacom2, + pbn_panacom4, + pbn_plx_romulus, + pbn_oxsemi, + pbn_intel_i960, + pbn_sgi_ioc3, + pbn_nec_nile4, + pbn_computone_4, + pbn_computone_6, + pbn_computone_8, + pbn_sbsxrsio, + pbn_exar_XR17C152, + pbn_exar_XR17C154, + pbn_exar_XR17C158, +}; + +/* + * uart_offset - the space between channels + * reg_shift - describes how the UART registers are mapped + * to PCI memory by the card. + * For example IER register on SBS, Inc. PMC-OctPro is located at + * offset 0x10 from the UART base, while UART_IER is defined as 1 + * in include/linux/serial_reg.h, + * see first lines of serial_in() and serial_out() in 8250.c +*/ + +static struct pci_board pci_boards[] __devinitdata = { + [pbn_default] = { + .flags = FL_BASE0, + .num_ports = 1, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b0_1_115200] = { + .flags = FL_BASE0, + .num_ports = 1, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b0_2_115200] = { + .flags = FL_BASE0, + .num_ports = 2, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b0_4_115200] = { + .flags = FL_BASE0, + .num_ports = 4, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b0_5_115200] = { + .flags = FL_BASE0, + .num_ports = 5, + .base_baud = 115200, + .uart_offset = 8, + }, + + [pbn_b0_1_921600] = { + .flags = FL_BASE0, + .num_ports = 1, + .base_baud = 921600, + .uart_offset = 8, + }, + [pbn_b0_2_921600] = { + .flags = FL_BASE0, + .num_ports = 2, + .base_baud = 921600, + .uart_offset = 8, + }, + [pbn_b0_4_921600] = { + .flags = FL_BASE0, + .num_ports = 4, + .base_baud = 921600, + .uart_offset = 8, + }, + + [pbn_b0_bt_1_115200] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 1, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b0_bt_2_115200] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 2, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b0_bt_8_115200] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 8, + .base_baud = 115200, + .uart_offset = 8, + }, + + [pbn_b0_bt_1_460800] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 1, + .base_baud = 460800, + .uart_offset = 8, + }, + [pbn_b0_bt_2_460800] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 2, + .base_baud = 460800, + .uart_offset = 8, + }, + [pbn_b0_bt_4_460800] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 4, + .base_baud = 460800, + .uart_offset = 8, + }, + + [pbn_b0_bt_1_921600] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 1, + .base_baud = 921600, + .uart_offset = 8, + }, + [pbn_b0_bt_2_921600] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 2, + .base_baud = 921600, + .uart_offset = 8, + }, + [pbn_b0_bt_4_921600] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 4, + .base_baud = 921600, + .uart_offset = 8, + }, + [pbn_b0_bt_8_921600] = { + .flags = FL_BASE0|FL_BASE_BARS, + .num_ports = 8, + .base_baud = 921600, + .uart_offset = 8, + }, + + [pbn_b1_1_115200] = { + .flags = FL_BASE1, + .num_ports = 1, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b1_2_115200] = { + .flags = FL_BASE1, + .num_ports = 2, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b1_4_115200] = { + .flags = FL_BASE1, + .num_ports = 4, + .base_baud = 115200, + .uart_offset = 8, + }, + [pbn_b1_8_115200] = { + .flags = FL_BASE1, + .num_ports = 8, + .base_baud = 115200, + .uart_offset = 8, + }, + + [pbn_b1_1_921600] = { + .flags = FL_BASE1, + .num_ports = 1, + .base_baud = 921600, + .uart_offset = 8, |