adv_17V35X/0040777000000000000000000000000013551571061007527 5ustar00adv_17V35X/adv17v35x.c0100777000000000000000000021731213551571025011351 0ustar00/*****************************************************************************/ /* * adv17v35x.c -- Advantech multiport serial driver. * * Copyright (C) 2018 Advantech Corporation. * * Based on Linux 2.6.37 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * Multiport Serial Driver for EXAR's PCI Family of UARTs (XR17V358/354/352) * * ChangeLog: * for : Tested on various kernel versions from 2.6.9 to 4.11.8 * date : Mar 2018 * version : 5.0.4.0 * * Check Release Notes for information on what has changed in the new version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "linux/version.h" #include "precomp.h" #define _INLINE_ inline #if LINUX_VERSION_CODE > KERNEL_VERSION(3, 8, 0) #define __devinitdata #define __devinit #define __devexit #define __devexit_p #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) struct serial_uart_config { char *name; int dfl_xmit_fifo_size; int flags; }; #endif //void tty_flip_buffer_push(struct tty_port *port); /* * Definitions for PCI support. */ #define ADVANTECH_EXAR_VER "5.0.6.0" #define ADVANTECH_EXAR_DATE "2019/10/16" #define ADVANTECH_EXAR_FILE_VER "5.0.6.0" #define ADVANTECH_VID 0x13FE /* * PCIE cards */ #define PCIE_1602 0x000E #define PCIE_1604 0x000D #define PCIE_1610 0x000C #define PCIE_1612 0x000B #define PCIE_1620 0x000A #define PCIE_1622 0x0009 /* * Beijing products */ #define PCIE_0014 0x0014 #define PCIE_0015 0x0015 #define PCIE_0016 0x0016 /* * PCM cards */ #define PCM_2602C 0x0063 #define PCM_2604C 0x0061 #define PCM_2612B 0x0062 #define PCM_2610B 0x0064 #define PCM_3612I 0x001F /* * PCI cards */ #define PCI_1602 0x0018 #define PCI_1604 0x0019 #define PCI_1610 0x001A #define PCI_1612 0x001B #define PCI_1620 0x001C #define PCI_1622 0x001D #define FL_BASE_MASK 0x0007 #define FL_BASE0 0x0000 #define FL_GET_BASE(x) (x & FL_BASE_MASK) #define NR_PORTS 256 #define XR_MAJOR 0 #define XR_MINOR 0 /* * The special register set for XR17V35x UARTs. */ #define XR_17v35x_UART_RHR 0 #define XR_17V35X_UART_DLD 2 #define XR_17V35X_UART_MSR 6 #define XR_17V35X_EXTENDED_FCTR 8 #define XR_17V35X_EXTENDED_EFR 9 #define XR_17V35X_TXFIFO_CNT 10 #define XR_17V35X_EXTENDED_TXTRG 10 #define XR_17V35X_RXFIFO_CNT 11 #define XR_17V35X_EXTENDED_RXTRG 11 #define XR_17V35X_UART_XOFF1 0x0C #define XR_17V35X_UART_XOFF2 0x0D #define XR_17V35X_UART_XON1 0x0E #define XR_17V35X_UART_XON2 0x0F #define XR_17V35X_FCTR_RTS_8DELAY 0x03 #define XR_17V35X_FCTR_TRGD 192 #define XR_17V35x_FCTR_RS485 0x20 #define XR_17V35x_MPIOLVL_7_0 0x90 #define XR_17V35x_MPIO3T_7_0 0x91 #define XR_17V35x_MPIOSEL_7_0 0x93 #define XR_17V35x_MPIOLVL_15_8 0x96 #define XR_17V35x_MPIO3T_15_8 0x97 #define XR_17V35x_MPIOSEL_15_8 0x99 // Set this parameter to 1 to enabled internal loopback #define ENABLE_INTERNAL_LOOPBACK 0 #define UART_17V35X_RX_OFFSET 0x100 #define UART_17V35X_TX_OFFSET 0x100 #define XR_17V35X_IER_RTSDTR 0x40 #define XR_17V35X_IER_CTSDSR 0x80 #define XR_17V35X_8XMODE 0x88 #define XR_17V35X_4XMODE 0x89 #define XR_17V35X_MPIOLVL_L 0x90 #define XR_17V35X_MPIOLVL_H 0x96 #define XR_17V35X_MPIOSEL_L 0x93 #define XR_17V35X_MPIOSEL_H 0x99 #define DIVISOR_CHANGED 0 #define XR_17V35X_RESET 0x8A #define PCI_NUM_BAR_RESOURCES 6 /* * serial mode, RS232 RS422 Master RS485/RS422 Slave */ #define SERIAL_MODE_UNKNOWN 0 #define SERIAL_MODE_RS232 1 #define SERIAL_MODE_RS422M 2 #define SERIAL_MODE_RS485ORRS422S 3 #define PASS_LIMIT 512 /* * macro for calcaute divide of baud */ #define TRUNC(X_Decuple) ( (X_Decuple)/10 ) #define ROUND(X_Decuple) ( ((X_Decuple)%10)>=5 ? (X_Decuple)/10+1 : (X_Decuple)/10 ) #define ADVANTECH_EXAR_MAIGC 'x' #define ADV_READ_REG _IO(ADVANTECH_EXAR_MAIGC, 1) #define ADV_WRITE_REG _IO(ADVANTECH_EXAR_MAIGC, 2) #define ADV_GET_SERIAL_MODE _IO(ADVANTECH_EXAR_MAIGC, 3) #define ADV_SET_TURN_AROUND_DELAY _IO(ADVANTECH_EXAR_MAIGC, 4) /* For SPI bus, to get Board ID */ #define REGB 0x8E #define EECK_BIT 0x10 #define EECS_BIT 0x20 #define EEDI_BIT 0x40 #define EEDO_BIT 0x80 #define GPIOA 0x12 #define SPI_READ_CMD 0x41 /* For fixed number */ #define MAX_STRING_LEN 64 #define MAX_CARD_SUPPORT 16 /* DTR/DSR flow control support */ #ifndef CDTRDSR #define CDTRDSR 004000000000 /* For DTR/DSR flow control */ #endif #if 0 #define DEBUG_INTR(fmt...) printk(fmt) #else #define DEBUG_INTR(fmt...) do { } while (0) #endif struct serial_private { struct pci_dev *dev; unsigned int nr; void __iomem *remapped_bar[PCI_NUM_BAR_RESOURCES]; struct pci_serial_quirk *quirk; int uart_index[NR_PORTS]; 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 *, const 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 advpci_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 */ enum advpci_board_num_t { adv_8port = 0, adv_4port, adv_2port, }; static struct pciserial_board advpciserial_boards[] __devinitdata = { [adv_2port] = { .flags = FL_BASE0, .num_ports = 2, .base_baud = 7812500, .uart_offset = 0x400, .reg_shift = 0, .first_offset = 0, }, [adv_4port] = { .flags = FL_BASE0, .num_ports = 4, .base_baud = 7812500, .uart_offset = 0x400, .reg_shift = 0, .first_offset = 0, }, [adv_8port] = { .flags = FL_BASE0, .num_ports = 8, .base_baud = 7812500, .uart_offset = 0x400, .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; /* * 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) #define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA #define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS struct uart_adv_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_saved_flags; unsigned char msr_saved_flags; unsigned short deviceid; unsigned short board_id; unsigned char channelnum; /* * We provide a per-port pm hook. */ void (*pm)(struct uart_port *port, unsigned int state, unsigned int old); int index; /* serial index in card, start at 0 */ int serialMode; /* RS232 or RS422 or RS485 */ int current_card_nr; int fix_number_index; }; 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 */ /* * Here we define the default xmit fifo size used for each type of UART. */ #define PORT_MAX_XR 1 #define XRPCIe_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 }, { "ADV17v35x", 256, 0 }, }; /*++++++++++++++++++++++++++++++++ function declare ++++++++++++++++++++++++++++++++*/ static int serialadv_setRS485(struct uart_port *port, int turnOn, int turnaroundTime); static void serial_set_dtrdsr ( struct uart_port *port, int turn_on); static void serial_set_ctsrts(struct uart_port *port, int turn_on); static unsigned int serialadv_calc_divisor(struct uart_port *port, unsigned int baud, unsigned int *quot, unsigned int *quot_fraction, unsigned int *devide1); /*-------------------------------- function declare --------------------------------*/ 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 { // got to be memory mapped. some hardware reading error? return -EINVAL; } return 0; } static int pci_default_setup(struct serial_private *priv, const 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[] = { { /* * PCIE cards */ .vendor = ADVANTECH_VID, .device = PCIE_1602, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_1604, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_1610, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_1612, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_1620, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_1622, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_0014, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_0015, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCIE_0016, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, /* * PCM cards */ { .vendor = ADVANTECH_VID, .device = PCM_2602C, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCM_2604C, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCM_2612B, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCM_2610B, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCM_3612I, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, /* * PCI cards */ { .vendor = ADVANTECH_VID, .device = PCI_1602, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCI_1604, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCI_1610, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCI_1612, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCI_1620, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .setup = pci_default_setup, }, { .vendor = ADVANTECH_VID, .device = PCI_1622, .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_adv_port *up, int offset) { return readb(up->port.membase + offset); } static _INLINE_ void serial_out(struct uart_adv_port *up, int offset, int value) { writeb(value, up->port.membase + offset); } static int serial_get_mode(struct uart_adv_port *up) { unsigned char mpiolvl_l = 0, mpiolvl_h = 0; unsigned char serialMode = SERIAL_MODE_UNKNOWN; unsigned char mpioData = 0; int index=0; // all MPIO set as input serial_out(up, XR_17V35X_MPIOSEL_L, 0xFF); serial_out(up, XR_17V35X_MPIOSEL_H, 0xFF); mpiolvl_l = serial_in(up, XR_17V35X_MPIOLVL_L); mpiolvl_h = serial_in(up, XR_17V35X_MPIOLVL_H); mpioData = up->index<4 ? mpiolvl_l : mpiolvl_h; index = up->index<4 ? up->index : up->index-4; if ( mpioData & (1<<(index*2)) ) { serialMode = SERIAL_MODE_RS232; } else if ( mpioData & (1<<(index*2+1)) ) { serialMode = SERIAL_MODE_RS485ORRS422S; } else { serialMode = SERIAL_MODE_RS422M; } DEBUG_INTR(KERN_INFO "serialMode = %d\n", serialMode); up->serialMode = serialMode; return serialMode; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) static void serialadv_stop_tx(struct uart_port *port, unsigned int tty_stop) #else static void serialadv_stop_tx(struct uart_port *port) #endif { struct uart_adv_port *up = (struct uart_adv_port *)port; if (up->ier & UART_IER_THRI) { up->ier &= ~UART_IER_THRI; serial_out(up, UART_IER, up->ier); } } #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) static void serialadv_start_tx(struct uart_port *port, unsigned int tty_stop) #else static void serialadv_start_tx(struct uart_port *port) #endif { struct uart_adv_port *up = (struct uart_adv_port *)port; if (!(up->ier & UART_IER_THRI)) { up->ier |= UART_IER_THRI; serial_out(up, UART_IER, up->ier); } } static void serialadv_stop_rx(struct uart_port *port) { struct uart_adv_port *up = (struct uart_adv_port *)port; up->ier &= ~UART_IER_RLSI; up->port.read_status_mask &= ~UART_LSR_DR; serial_out(up, UART_IER, up->ier); } static void serialadv_enable_ms(struct uart_port *port) { struct uart_adv_port *up = (struct uart_adv_port *)port; up->ier |= UART_IER_MSI; serial_out(up, UART_IER, up->ier); } static void receive_chars(struct uart_adv_port *up, unsigned int *status) { #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0) // prevent compile warning #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) struct tty_struct *tty = up->port.state->port.tty; #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) struct tty_struct *tty = up->port.info->port.tty; #else struct tty_struct *tty = up->port.info->tty; #endif #endif #if LINUX_VERSION_CODE > KERNEL_VERSION(3, 9, 0) struct uart_port *port = &up->port; #endif unsigned char ch[256], lsr = *status; char flag; int i, datasize_in_fifo = 0, port_index; while(datasize_in_fifo!=serial_in(up, XR_17V35X_RXFIFO_CNT)) /*Read Receive Fifo count until we get read same value twice*/ datasize_in_fifo=serial_in(up, XR_17V35X_RXFIFO_CNT); port_index = up->port.line; flag = TTY_NORMAL; if (unlikely(lsr & (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE | UART_LSR_OE))) { /* * 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; printk("handling port<%d> Parity error....\n",port_index); } else if (lsr & UART_LSR_FE) { DEBUG_INTR("handling Frame error....\n"); flag = TTY_FRAME; } } //memcpy_fromio(ch, up->port.membase + UART_17V35X_RX_OFFSET, datasize_in_fifo); for(i=0;iport.icount.rx+=datasize_in_fifo; for(i = 0; i < datasize_in_fifo; i++) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) if (uart_handle_sysrq_char(&up->port, ch[i], NULL)) #else if (uart_handle_sysrq_char(&up->port, ch[i])) #endif { continue; } uart_insert_char(&up->port, lsr, UART_LSR_OE, ch[i], flag); } spin_unlock(&up->port.lock); #if LINUX_VERSION_CODE > KERNEL_VERSION(3, 9, 0) tty_flip_buffer_push(&port->state->port); #else tty_flip_buffer_push(tty); #endif spin_lock(&up->port.lock); DEBUG_INTR(" LSR_DR..."); } static void transmit_chars(struct uart_adv_port *up) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) struct circ_buf *xmit = &up->port.info->xmit; #else struct circ_buf *xmit = &up->port.state->xmit; #endif int count, bytes_in_fifo; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) int tmp; #endif if (up->port.x_char) { serial_out(up, UART_TX, up->port.x_char); up->port.icount.tx++; up->port.x_char = 0; return; } if (uart_tx_stopped(&up->port)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) serialadv_stop_tx(&up->port, 0); #else serialadv_stop_tx(&up->port); #endif return; } if (uart_circ_empty(xmit)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) serialadv_stop_tx(&up->port, 0); #else serialadv_stop_tx(&up->port); #endif return; } bytes_in_fifo = serial_in(up, XR_17V35X_TXFIFO_CNT); // read the fifo count untill we get the same value twice while (bytes_in_fifo != serial_in(up, XR_17V35X_TXFIFO_CNT)) bytes_in_fifo = serial_in(up, XR_17V35X_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); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) if(count > 0) { //serial_out(up, UART_TX, xmit->buf[xmit->tail]); memcpy_toio(up->port.membase + UART_17V35X_TX_OFFSET, &(xmit->buf[xmit->tail]), 1); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); } #else 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_toio(up->port.membase + UART_17V35X_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_toio(up->port.membase + UART_17V35X_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); #endif if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); DEBUG_INTR("THRE..."); if (uart_circ_empty(xmit)) #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) serialadv_stop_tx(&up->port, 0); #else serialadv_stop_tx(&up->port); #endif } static unsigned int check_modem_status(struct uart_adv_port *up) { unsigned int status = serial_in(up, UART_MSR); status |= up->msr_saved_flags; up->msr_saved_flags = 0; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) if (status & UART_MSR_ANY_DELTA && up->ier & UART_IER_MSI) #else if (status & UART_MSR_ANY_DELTA && up->ier & UART_IER_MSI && up->port.state != NULL) #endif { 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); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) wake_up_interruptible(&up->port.info->delta_msr_wait); #else wake_up_interruptible(&up->port.state->port.delta_msr_wait); #endif } return status; } /* * This handles the interrupt from one port. */ static void serialadv_handle_port(struct uart_adv_port *up) { unsigned int status; unsigned long flags; spin_lock_irqsave(&up->port.lock, flags); status = serial_in(up, UART_LSR); DEBUG_INTR("status = %x...", status); if (status & (UART_LSR_DR | UART_LSR_BI)) receive_chars(up, &status); check_modem_status(up); if (status & UART_LSR_THRE) transmit_chars(up); spin_unlock_irqrestore(&up->port.lock, flags); } /* * 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. */ #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) static irqreturn_t serialadv_interrupt(int irq, void *dev_id, struct pt_regs *regs) #else static irqreturn_t serialadv_interrupt(int irq, void *dev_id) #endif { struct irq_info *i = dev_id; struct list_head *l, *end = NULL; int pass_counter = 0, handled = 0; DEBUG_INTR("serialadv_interrupt(%d)...", irq); spin_lock(&i->lock); l = i->head; do { struct uart_adv_port *up; unsigned int iir, lcr; up = list_entry(l, struct uart_adv_port, list); lcr = serial_in(up, UART_LCR); // store value of LCR serial_out(up, UART_LCR, lcr & 0x7F); // ensure LCR bit-7=0 before reading UART_IIR iir = serial_in(up, UART_IIR); if (!(iir & UART_IIR_NO_INT)) { serialadv_handle_port(up); handled = 1; end = NULL; } else if (end == NULL) end = l; serial_out(up, UART_LCR, lcr); // restore LCR l = l->next; if (l == i->head && pass_counter++ > PASS_LIMIT) { /* If we hit this, we're dead. */ printk(KERN_ERR "serialadv: too much work for irq%d\n", irq); break; } } while (l != end); spin_unlock(&i->lock); DEBUG_INTR("end.\n"); return IRQ_RETVAL(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_adv_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_adv_port *up) { struct hlist_head *h; struct hlist_node *n; struct irq_info *i; #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 14) int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0; #else int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? SA_SHIRQ : 0; #endif 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) { #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 9) i = kmalloc(sizeof(struct irq_info), GFP_KERNEL); memset(i , 0, sizeof(struct irq_info)); #else i = kzalloc(sizeof(struct irq_info), GFP_KERNEL); #endif 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_tail(&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); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) //irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED: 0; irq_flags |= up->port.irqflags; #elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 14) irq_flags = up->port.flags & UPF_SHARE_IRQ ? SA_SHIRQ : 0; #else irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0; #endif ret = request_irq(up->port.irq, serialadv_interrupt, irq_flags, "ADVserial", i); if (ret < 0) serial_do_unlink(i, up); } return ret; } static void serial_unlink_irq_chain(struct uart_adv_port *up) { struct irq_info *i = NULL; 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); } static inline int poll_timeout(int timeout) { return timeout > 6 ? (timeout / 2 - 2) : 1; } /* * 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). */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) static void serialadv_timeout(unsigned long data) #else static void serialadv_timeout(struct timer_list *t) #endif { //NOTE: Due to API changing in the Kernel //we need to handle differnent kernel version #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) struct uart_adv_port *up = (struct uart_adv_port *)data; #else struct uart_adv_port *up = from_timer(up, t, timer); #endif unsigned int iir; iir = serial_in(up, UART_IIR); if (!(iir & UART_IIR_NO_INT)) serialadv_handle_port(up); mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout)); } #define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) static unsigned int serialadv_tx_empty(struct uart_port *port) { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned long flags; unsigned int lsr; spin_lock_irqsave(&up->port.lock, flags); lsr = serial_in(up, UART_LSR); up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; spin_unlock_irqrestore(&up->port.lock, flags); return (lsr & BOTH_EMPTY) == BOTH_EMPTY ? TIOCSER_TEMT : 0; } static unsigned int serialadv_get_mctrl(struct uart_port *port) { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned int status; unsigned int ret; status = check_modem_status(up); 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 serialadv_set_mctrl(struct uart_port *port, unsigned int mctrl) { struct uart_adv_port *up = (struct uart_adv_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 | up->mcr; serial_out(up, UART_MCR, mcr); } static void serialadv_break_ctl(struct uart_port *port, int break_state) { struct uart_adv_port *up = (struct uart_adv_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 serialadv_startup(struct uart_port *port) { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned long flags; int retval; up->capabilities = uart_config[up->port.type].flags; serial_out(up, XR_17V35X_EXTENDED_EFR, UART_EFR_ECB); serial_out(up, UART_IER, 0); /* Set the RX/TX trigger levels */ /* These are some default values, the OEMs can change these values * according to their best case scenarios */ serial_out(up, XR_17V35X_EXTENDED_RXTRG, 32); serial_out(up, XR_17V35X_EXTENDED_TXTRG, 64); /* Hysteresis level of 8 */ serial_out(up, XR_17V35X_EXTENDED_FCTR, XR_17V35X_FCTR_TRGD | XR_17V35X_FCTR_RTS_8DELAY); serial_out(up, UART_LCR, 0); /* Wake up and initialize UART */ serial_out(up, XR_17V35X_EXTENDED_EFR, UART_EFR_ECB | 0x10/*Enable Shaded bits access*/); serial_out(up,XR_17V35X_UART_MSR, 0); 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_out(up, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); serial_out(up, UART_FCR, 0); /* * Clear the interrupt registers. */ (void) serial_in(up, UART_LSR); (void) serial_in(up, UART_RX); (void) serial_in(up, UART_IIR); (void) serial_in(up, UART_MSR); if(port->irq) { retval = serial_link_irq_chain(up); if(retval) return retval; } /* * Now, initialize the UART */ serial_out(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; //to enable intenal loop, uncomment the line below //up->port.mctrl |= TIOCM_LOOP; serialadv_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_RLSI | UART_IER_RDI; serial_out(up, UART_IER, up->ier); /* * And clear the interrupt registers again for luck. */ (void) serial_in(up, UART_LSR); (void) serial_in(up, UART_RX); (void) serial_in(up, UART_IIR); (void) serial_in(up, UART_MSR); return 0; } static void serialadv_shutdown(struct uart_port *port) { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned long flags; /* * Disable interrupts from this port */ up->ier = 0; serial_out(up, UART_IER, 0); spin_lock_irqsave(&up->port.lock, flags); up->port.mctrl &= ~TIOCM_OUT2; serialadv_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_in(up, UART_LCR) & ~UART_LCR_SBC); serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); serial_out(up, UART_FCR, 0); /* * Read data port to reset things, and then unlink from * the IRQ chain. */ (void) serial_in(up, UART_RX); if (port->irq) serial_unlink_irq_chain(up); } static unsigned int serialadv_get_divisor(struct uart_port *port, unsigned int baud, unsigned int *quot, unsigned int *quot_fraction, unsigned int *devide1) { serialadv_calc_divisor(port, baud, quot, quot_fraction, devide1); return 0; } static void serialadv_set_special_baudrate(struct uart_port *port, unsigned int special_baudrate) { struct uart_adv_port *up = (struct uart_adv_port *)port; signed int baud, quot, quot_fraction; unsigned char lcr_bak; unsigned int reg_read; int port_index; unsigned int devide1; /* for baud rate settings */ unsigned char oldEFR = 0; unsigned char oldMCR = 0; unsigned char valof4xreg = 0, valof8xreg= 0; port_index = up->port.line; quot_fraction = 0; printk(KERN_INFO "Enter in serialadv_set non-standard baudrate:%d\n",special_baudrate); baud = special_baudrate/*uart_get_baud_rate(port, termios, old, 0, port->uartclk/4)*/; lcr_bak = serial_in(up, UART_LCR); if((port_index > 15)||(port_index < 0)) { return; } if(port_index >= 8) port_index = port_index - 8; serialadv_get_divisor(port, baud, ", "_fraction, &devide1); // set baud rate // set sample 16 valof8xreg = serial_in(up, XR_17V35X_8XMODE); valof4xreg = serial_in(up, XR_17V35X_4XMODE); valof4xreg &= ~(1 << up->index); valof8xreg &= ~(1 << up->index); serial_out(up, XR_17V35X_8XMODE, valof8xreg); serial_out(up, XR_17V35X_4XMODE, valof4xreg); oldEFR = serial_in(up, XR_17V35X_EXTENDED_EFR); serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR|0x10); if(devide1) //Prescaler Divide by 1 { oldMCR = serial_in(up, UART_MCR); oldMCR &= ~UART_MCR_CLKSEL; // using Prescaler Divide by 1 up->port.mctrl &= ~UART_MCR_CLKSEL; serial_out (up, UART_MCR, oldMCR); } else //Prescaler Divide by 4 { oldMCR = serial_in(up, UART_MCR); oldMCR |= UART_MCR_CLKSEL; // using Prescaler Divide by 4 up->port.mctrl |= UART_MCR_CLKSEL; serial_out (up, UART_MCR, oldMCR); } serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR); serial_out(up, UART_LCR, lcr_bak | UART_LCR_DLAB);/* set DLAB */ serial_out(up, UART_DLL, quot & 0xff); /* LS of divisor */ serial_out(up, UART_DLM, quot >> 8); /* MS of divisor */ /* if RS485,we set DLD[7] */ quot_fraction = up->serialMode == SERIAL_MODE_RS485ORRS422S ? ((quot_fraction&0x0f)|0x80) : (quot_fraction&0x0f); //Fractional baud rate support { reg_read=(serial_in(up, XR_17V35X_UART_DLD)&0xF0); DEBUG_INTR(KERN_INFO "serialadv_set_special_baudrate: quot =0x%x quot_fraction=0x%x DLD_reg=0x%x\n",quot,quot_fraction,reg_read); serial_out(up, XR_17V35X_UART_DLD, quot_fraction | reg_read); reg_read=serial_in(up, XR_17V35X_UART_DLD); } serial_out(up, UART_LCR, lcr_bak); /* reset DLAB */ } static unsigned int serialadv_calc_divisor(struct uart_port *port, unsigned int baud, unsigned int *quot, unsigned int *quot_fraction, unsigned int *devide1) { // calculate the most appropriate baud, // Prescaler divide by 1 or 4 unsigned long AppropriateInt_Decuple0 = 0, AppropriateInt_Decuple1 = 0; unsigned long baudWithPrescaler0 = 0, baudWithPrescaler1 = 0; unsigned long deviation = 0; unsigned long deviationPersent0 = 0, deviationPersent1 = 0; unsigned long tempclock = 125000000; unsigned long samplingrate = 16; unsigned short tmpBaudInt0 = 0, tmpBaudInt1 = 0; unsigned short tmpBaudFraction0 = 0, tmpBaudFraction1 = 0; //calculate in prescaler Divide by 1 AppropriateInt_Decuple0 = (unsigned long) (tempclock * 10 /(samplingrate * baud) ); DEBUG_INTR(KERN_INFO"AppropriateInt_Decuple0 = %ld, tempclock = %ld, baud = %d, samplingrate = %ld\n", AppropriateInt_Decuple0, tempclock, baud, samplingrate); tmpBaudInt0 = (unsigned short)TRUNC(AppropriateInt_Decuple0); tmpBaudFraction0 = (unsigned short)ROUND ((AppropriateInt_Decuple0 -TRUNC(AppropriateInt_Decuple0) *10)*16); baudWithPrescaler0 = (tempclock * 10) /((tmpBaudInt0 * 10 + (tmpBaudFraction0 * 10/16))*samplingrate) ; deviation = baudWithPrescaler0 > baud ? (baudWithPrescaler0 - baud) : (baud -baudWithPrescaler0 ) ; deviationPersent0 = deviation * 100 / baud; DEBUG_INTR(KERN_INFO"deviationPersent0 = %ld, tmpBaudInt0 = 0x%x, tmpBaudFraction0 = 0x%x\n", deviationPersent0, tmpBaudInt0, tmpBaudFraction0); //calculate in prescaler Divide by 4 tempclock = tempclock / 4; AppropriateInt_Decuple1 = (unsigned long) (tempclock * 10 /(samplingrate * baud) ); tmpBaudInt1 = (unsigned short)TRUNC(AppropriateInt_Decuple1); tmpBaudFraction1 = (unsigned short) ROUND ((AppropriateInt_Decuple1 -TRUNC(AppropriateInt_Decuple1)*10 )*16); baudWithPrescaler1 = (tempclock * 10) / ((tmpBaudInt1 * 10 + (tmpBaudFraction1 * 10/16))*samplingrate); deviation = baudWithPrescaler1 > baud ? (baudWithPrescaler1 - baud) : (baud -baudWithPrescaler1 ) ; deviationPersent1 = deviation * 100 / baud; DEBUG_INTR(KERN_INFO"deviationPersent1 = %ld, tmpBaudInt1 = 0x%x, tmpBaudFraction1 = 0x%x\n", deviationPersent1, tmpBaudInt1, tmpBaudFraction1); /* * clk is 125000000, samplingrate is 16, quot is 16bits data, * calculate rule: quot = clk / (16*baud) * if baud<120, quot will be overrun, so we need devide clk by 4. * Though we set tmpBaudInt0, tmpBaudInt1 as unsinged short to prevent overrun, it's better to check it as 120. */ if(baud < 120) { *quot = tmpBaudInt1; *quot_fraction = tmpBaudFraction1; *devide1 = 0; } else { //find which one is less deviation if ( deviationPersent0 > deviationPersent1) { *quot = tmpBaudInt1; *quot_fraction = tmpBaudFraction1; *devide1 = 0; } else { *quot = tmpBaudInt0; *quot_fraction = tmpBaudFraction0; *devide1 = 1; } } return 0; } static void serial_set_ctsrts(struct uart_port *port, int turn_on) { unsigned char old_efr, old_mcr; struct uart_adv_port *up = (struct uart_adv_port *)port; old_efr = serial_in(up, XR_17V35X_EXTENDED_EFR); if(turn_on) { old_efr |= 0xc0; serial_out(up, XR_17V35X_EXTENDED_EFR, old_efr); old_mcr = serial_in(up, UART_MCR); old_mcr |= 0x02; serial_out(up, UART_MCR, (unsigned char) old_mcr); } else { old_efr &= 0x3f; serial_out(up, XR_17V35X_EXTENDED_EFR, old_efr); /* Force RTS# output to a HIGH (default) */ old_mcr = serial_in(up, UART_MCR); old_mcr &= 0xfd; serial_out(up, UART_MCR, (unsigned char) old_mcr); } } static void serial_set_dtrdsr(struct uart_port *port, int turn_on) { unsigned char old_efr, old_mcr; struct uart_adv_port *up = (struct uart_adv_port *)port; old_efr = serial_in(up, XR_17V35X_EXTENDED_EFR); if(turn_on) { old_efr |= 0xc0; serial_out(up, XR_17V35X_EXTENDED_EFR, old_efr); old_mcr = serial_in(up, UART_MCR); old_mcr |= 0x05; serial_out(up, UART_MCR, (unsigned char) old_mcr); } else { old_efr &= 0x3f; serial_out(up, XR_17V35X_EXTENDED_EFR, old_efr); /* Force DTR# output to a HIGH (default) */ old_mcr = serial_in(up, UART_MCR); old_mcr &= 0xfe; serial_out(up, UART_MCR, (unsigned char) old_mcr); } } static void #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23) serialadv_set_termios(struct uart_port *port, struct termios *termios, struct termios *old) #else serialadv_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) #endif { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned char cval; unsigned long flags; unsigned int baud, quot, quot_fraction; unsigned int devide1 = 0; unsigned int reg_read; /* for baud rate settings */ unsigned char oldEFR = 0; unsigned char oldMCR = 0; unsigned char valof4xreg = 0, valof8xreg= 0; int port_index; port_index = up->port.line; quot_fraction = 0; 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. * Add limited max baud rate as 921600. */ baud = uart_get_baud_rate(port, termios, old, 0, 921600); if((port_index > 15)||(port_index < 0)) { return; } if(port_index >= 8) port_index = port_index - 8; serialadv_get_divisor(port, baud, ", "_fraction, &devide1); DEBUG_INTR(KERN_INFO"serialadv_get_divisor::baud =%d, quot=0x%x, quot_fraction=0x%x, devide1=%d\n", baud, quot, quot_fraction, devide1); /* * 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_PE | 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_set_ctsrts(port, 0); serial_set_dtrdsr(port, 0); if(termios->c_cflag & CRTSCTS) { serial_set_ctsrts(port, 1); } else if(termios->c_cflag & CDTRDSR) { serial_set_dtrdsr(port, 1); } /* * Auto XON/XOFF software flow control flags */ reg_read=serial_in(up, XR_17V35X_EXTENDED_EFR); if(((termios->c_iflag) & IXOFF)&&((termios->c_iflag) & IXON)) { serial_out(up, XR_17V35X_UART_XON1,0x11); //Initializing XON1 serial_out(up, XR_17V35X_UART_XOFF1,0x13); //Initializing XOFF1 serial_out(up, XR_17V35X_EXTENDED_EFR, (reg_read & 0xF0) | 0x0A ); DEBUG_INTR(KERN_INFO "Software Flow Control Enabled\n"); } else { serial_out(up, XR_17V35X_EXTENDED_EFR, (reg_read) & 0xF0 ); DEBUG_INTR(KERN_INFO "No Software Flow Control\n"); } reg_read = serial_in(up,XR_17V35X_EXTENDED_EFR); reg_read |= 0x10; serial_out(up, XR_17V35X_EXTENDED_EFR, reg_read); if((termios->c_iflag) & IXANY) { reg_read = serial_in(up, UART_MCR); reg_read |= 0x20; serial_out(up, UART_MCR, reg_read); DEBUG_INTR(KERN_INFO "AUTO XANY Enabled\n"); } else { reg_read = serial_in(up, UART_MCR); reg_read &= ~0x20; serial_out(up, UART_MCR, reg_read); DEBUG_INTR(KERN_INFO "AUTO XANY NOT Enabled\n"); } reg_read = serial_in(up,XR_17V35X_EXTENDED_EFR); reg_read &= ~0x10; serial_out(up, XR_17V35X_EXTENDED_EFR, reg_read); //---------------------------------------------------------------------------// if(!((baud == 38400) && ((port->flags & UPF_SPD_MASK) == UPF_SPD_CUST))) { // set baud rate // set sample 16 valof8xreg = serial_in(up, XR_17V35X_8XMODE); valof4xreg = serial_in(up, XR_17V35X_4XMODE); valof4xreg &= ~(1 << up->index); valof8xreg &= ~(1 << up->index); serial_out(up, XR_17V35X_8XMODE, valof8xreg); serial_out(up, XR_17V35X_4XMODE, valof4xreg); oldEFR = serial_in(up, XR_17V35X_EXTENDED_EFR); serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR|0x10); if(devide1) //Prescaler Divide by 1 { oldMCR = serial_in(up, UART_MCR); oldMCR &= ~UART_MCR_CLKSEL; // using Prescaler Divide by 1 up->port.mctrl &= ~UART_MCR_CLKSEL; serial_out (up, UART_MCR, oldMCR); } else //Prescaler Divide by 4 { oldMCR = serial_in(up, UART_MCR); oldMCR |= UART_MCR_CLKSEL; // using Prescaler Divide by 4 up->port.mctrl |= UART_MCR_CLKSEL; serial_out (up, UART_MCR, oldMCR); } serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR); serial_out(up, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ serial_out(up, UART_DLL, (unsigned char)(quot & 0xff)); /* LS of divisor */ serial_out(up, UART_DLM, (unsigned char)(quot >> 8)); /* MS of divisor */ /* if RS485,we set DLD[7] */ quot_fraction = up->serialMode == SERIAL_MODE_RS485ORRS422S ? ((quot_fraction&0x0f)|0x80) : (quot_fraction&0x0f); //Fractional baud rate support reg_read=(serial_in(up, XR_17V35X_UART_DLD)&0xF0); DEBUG_INTR(KERN_INFO "serialxr_set_termios: quot =0x%x quot_fraction=0x%x DLD_reg=0x%x\n",quot,quot_fraction,reg_read); serial_out(up, XR_17V35X_UART_DLD, quot_fraction | reg_read); serial_out(up, UART_LCR, cval); /* reset DLAB */ up->lcr = cval; /* Save LCR */ } // set RS485 up->serialMode == SERIAL_MODE_RS485ORRS422S ? serialadv_setRS485(&up->port, 1, 0) : serialadv_setRS485(&up->port, 0, 0); serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO);/* set fcr */ /* Configuring MPIO as inputs */ if((up->deviceid == 0x354)||(up->deviceid == 0x4354)||(up->deviceid == 0x8354)) { serial_out(up, XR_17V35x_MPIOSEL_7_0,0x0FF); //0x0ff= ALL INPUTS } else if((up->deviceid == 0x358)||(up->deviceid == 0x4358)||(up->deviceid == 0x8358)) { serial_out(up, XR_17V35x_MPIOSEL_7_0,0x0FF); //0x0ff= ALL INPUTS serial_out(up, XR_17V35x_MPIOSEL_15_8,0x0FF); //0x0ff= ALL INPUTS } #if ENABLE_INTERNAL_LOOPBACK reg_read=serial_in(up, UART_MCR); serial_out(up, UART_MCR, (reg_read) | 0x10); printk(KERN_INFO "Enabling Internal Loopback\n"); #endif spin_unlock_irqrestore(&up->port.lock, flags); } /* * EXAR ioctls */ //#define FIOQSIZE 0x5460 #define EXAR_READ_REG (FIOQSIZE + 1) #define EXAR_WRITE_REG (FIOQSIZE + 2) #define EXAR_SET_MULTIDROP_MODE_NORMAL (FIOQSIZE + 3) #define EXAR_SET_MULTIDROP_MODE_AUTO (FIOQSIZE + 4) #define EXAR_SET_REMOVE_MULTIDROP_MODE (FIOQSIZE + 5) struct advioctl_rw_reg { unsigned char reg; unsigned char regvalue; }; /* * This function is used to handle Advantech 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 advioctl_rw_reg * The Ioctl functioning is pretty much self explanatory here in the code, * and the register values should be between 0 to XR_17V35X_EXTENDED_RXTRG */ static int serialadv_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg) { struct uart_adv_port *up = (struct uart_adv_port *)port; int ret = -ENOIOCTLCMD; struct advioctl_rw_reg ioctlrwarg; int iArg = 0; switch (cmd) { case ADV_READ_REG: if (copy_from_user(&ioctlrwarg, (void *)arg, sizeof(ioctlrwarg))) return -EFAULT; ioctlrwarg.regvalue = serial_in(up, ioctlrwarg.reg); if (copy_to_user((void *)arg, &ioctlrwarg, sizeof(ioctlrwarg))) return -EFAULT; DEBUG_INTR(KERN_INFO "serialadv_ioctl read reg[0x%02x]=0x%02x \n",ioctlrwarg.reg,ioctlrwarg.regvalue); ret = 0; break; case ADV_WRITE_REG: if (copy_from_user(&ioctlrwarg, (void *)arg, sizeof(ioctlrwarg))) return -EFAULT; serial_out(up, ioctlrwarg.reg, ioctlrwarg.regvalue); DEBUG_INTR(KERN_INFO "serialadv_ioctl write reg[0x%02x]=0x%02x \n",ioctlrwarg.reg,ioctlrwarg.regvalue); ret = 0; break; // serial have RS232/RS422/RS485 mode, get it case ADV_GET_SERIAL_MODE: DEBUG_INTR(KERN_INFO"enter ADV_GET_SERIAL_MODE\n"); if ( copy_to_user((void *)arg, &up->serialMode, sizeof(int)) ) return -EFAULT; ret = 0; break; case ADV_SET_TURN_AROUND_DELAY: DEBUG_INTR(KERN_INFO"enter ADV_SET_TURN_AROUND_DELAY\n"); iArg = (int) arg; DEBUG_INTR(KERN_INFO"iArg = %d\n", iArg); if( (up->serialMode!=SERIAL_MODE_RS485ORRS422S) || iArg<0 || iArg>15) return -EFAULT; serialadv_setRS485(&up->port, 1, iArg); ret = 0; break; } return ret; } static void serialadv_pm(struct uart_port *port, unsigned int state, unsigned int oldstate) { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned char oldEFR = 0; oldEFR = serial_in(up, XR_17V35X_EXTENDED_EFR); if (state) { /* sleep */ serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR | 0x10); serial_out(up, UART_IER, UART_IERX_SLEEP); serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR &(~0x10)); } else { /* wake */ serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR | 0x10); serial_out(up, UART_IER, 0); serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR &(~0x10)); } if (up->pm) up->pm(port, state, oldstate); } static void serialadv_release_port(struct uart_port *port) { } static int serialadv_request_port(struct uart_port *port) { return 0; } static void serialadv_config_port(struct uart_port *port, int flags) { struct uart_adv_port *up = (struct uart_adv_port *)port; if (flags & UART_CONFIG_TYPE) { up->port.type = XRPCIe_TYPE; if((uart_config[up->port.type].dfl_xmit_fifo_size >> sizeof(up->port.fifosize)) != 0 ) up->port.fifosize = 0xff; else up->port.fifosize = uart_config[up->port.type].dfl_xmit_fifo_size; up->capabilities = uart_config[up->port.type].flags; } } static const char * serialadv_type(struct uart_port *port) { int type = port->type; if (type >= ARRAY_SIZE(uart_config)) type = 0; return uart_config[type].name; } static int serialadv_setRS485(struct uart_port *port, int turnOn, int turnaroundTime) { struct uart_adv_port *up = (struct uart_adv_port *)port; unsigned char oldFCTR = 0; unsigned char oldDLD = 0; unsigned char oldLCR = 0; unsigned char oldEFR = 0; unsigned char valueMSR = 0; DEBUG_INTR("enter serialadv_setRS485\n"); oldFCTR = serial_in(up, XR_17V35X_EXTENDED_FCTR); if(turnOn) // set RS485 { // Auto RS485 Enable oldFCTR |= 0x20; serial_out(up, XR_17V35X_EXTENDED_FCTR, oldFCTR); // set RS-485 Polarity to 1 oldLCR = serial_in(up, UART_LCR); serial_out(up, UART_LCR, oldLCR|UART_LCR_DLAB); oldDLD = serial_in(up, XR_17V35X_UART_DLD); serial_out(up, XR_17V35X_UART_DLD, oldDLD|0x80); serial_out(up, UART_LCR, oldLCR); /* * set RS-485 turn-around delay */ oldEFR = serial_in(up, XR_17V35X_EXTENDED_EFR); oldEFR |= 0x10; serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR); // MSR write only valueMSR = turnaroundTime<<4; serial_out(up, UART_MSR, valueMSR); oldEFR &= ~(0x10); serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR); } else // unset RS485 { // Auto RS485 Disable oldFCTR &= 0xdf; serial_out(up, XR_17V35X_EXTENDED_FCTR, oldFCTR); // restore msr oldEFR = serial_in(up, XR_17V35X_EXTENDED_EFR); oldEFR |= 0x10; serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR); // MSR write only valueMSR = 0; serial_out(up, UART_MSR, valueMSR); oldEFR &= ~(0x10); serial_out(up, XR_17V35X_EXTENDED_EFR, oldEFR); } DEBUG_INTR("leave serialadv_setRS485\n"); return 0; } void serialadv_spi_delay(void) { int idex = 10; while(idex-- > 0) { ; } return; } void serialadv_spi_write_slave(struct uart_adv_port *up, unsigned char data) { int idex = 0; int send_bit = 0; unsigned char reg_value = 0; for(idex = 7; idex >= 0; idex--) { send_bit = data & (1<= 0; idex--) { serial_out(up, REGB, 0); /* CK low */ serialadv_spi_delay(); serial_out(up, REGB, EECK_BIT); /* CK high */ serialadv_spi_delay(); serial_out(up, REGB, 0); /* CK high */ serialadv_spi_delay(); reg_value = serial_in(up, REGB); /* read EEDO bit */ recv_bit = reg_value & EEDO_BIT ? 1 : 0; *p_data |= recv_bit << idex; } return 0; } void serialadv_spi_start(struct uart_adv_port *up) { /* * SPI start, set CS low */ serial_out(up, XR_17V35X_MPIOSEL_H, 0x7F); serial_out(up, XR_17V35X_MPIOLVL_H, 0x0); serialadv_spi_delay(); return; } void serialadv_spi_stop(struct uart_adv_port *up) { /* * SPI start, set CS low */ serial_out(up, XR_17V35X_MPIOSEL_H, 0x7F); serial_out(up, XR_17V35X_MPIOLVL_H, 0x80); serialadv_spi_delay(); return; } /* * Routine Description: * This routine will get card's board ID by SPI bus. * Arguments: * up - Pointer to the device structure * p_boardID - save board ID *Return value: * 0 on success, something else appropriate on failure */ int serialadv_get_boardID(struct uart_adv_port *up, short unsigned int *p_boardID) { unsigned char boardID = 0; serialadv_spi_start(up); serialadv_spi_write_slave(up, SPI_READ_CMD); serialadv_spi_write_slave(up, GPIOA); serialadv_spi_read_slave(up, &boardID); serialadv_spi_stop(up); *p_boardID = boardID; return 0; } static int serialadv_verify_port(struct uart_port *port, struct serial_struct *ser) { int ret = 0; printk(KERN_INFO "serialadv_set_special_baudrate flags:%d custom_divisor=%d\n",ser->flags,ser->custom_divisor); if(ser->custom_divisor > 0) serialadv_set_special_baudrate(port,ser->baud_base/ser->custom_divisor); return ret; } static struct uart_ops serialadv_pops = { .tx_empty = serialadv_tx_empty, .set_mctrl = serialadv_set_mctrl, .get_mctrl = serialadv_get_mctrl, .stop_tx = serialadv_stop_tx, .start_tx = serialadv_start_tx, .stop_rx = serialadv_stop_rx, .enable_ms = serialadv_enable_ms, .break_ctl = serialadv_break_ctl, .startup = serialadv_startup, .shutdown = serialadv_shutdown, .set_termios = serialadv_set_termios, .verify_port = serialadv_verify_port, .pm = serialadv_pm, .type = serialadv_type, .release_port = serialadv_release_port, .request_port = serialadv_request_port, .config_port = serialadv_config_port, .ioctl = serialadv_ioctl, }; static DEFINE_MUTEX(serial_mutex); static struct uart_adv_port serialadv_ports[NR_PORTS]; #define SERIALADV_CONSOLE NULL static struct uart_driver adv_uart_driver[MAX_CARD_SUPPORT]; static char adv_dev_name[MAX_CARD_SUPPORT][MAX_STRING_LEN]; static char adv_driver_name[MAX_CARD_SUPPORT][MAX_STRING_LEN]; unsigned int old_method_cnt = 0; static struct uart_adv_port *serialadv_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(&serialadv_ports[i].port, port)) return &serialadv_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 (serialadv_ports[i].port.type == PORT_UNKNOWN && serialadv_ports[i].port.iobase == 0) { port->line = i; return &serialadv_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 (serialadv_ports[i].port.type == PORT_UNKNOWN) return &serialadv_ports[i]; return NULL; } void init_adv_uart_struct(void) { int i; /* * We do not init "dev_name", "nr" here, these will be * initiated after getting boardID. */ for(i = 0; i < MAX_CARD_SUPPORT; i++) { adv_uart_driver[i].owner = THIS_MODULE; adv_uart_driver[i].driver_name = "ADVserial"; adv_uart_driver[i].major = XR_MAJOR; adv_uart_driver[i].minor = XR_MINOR + i; adv_uart_driver[i].cons = SERIALADV_CONSOLE; } return; } /* * serialadv_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 serialadv_register_port(struct uart_port *port, unsigned short deviceid, int index, int nr_ports) { struct uart_adv_port *uart; int i, boardID = 0, ret = -ENOSPC; char tmp_string[MAX_STRING_LEN], tmp_dev_name[MAX_STRING_LEN]; if (port->uartclk == 0) return -EINVAL; mutex_lock(&serial_mutex); uart = serialadv_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->deviceid = deviceid; uart->channelnum = index; uart->port.line = port->line; uart->index = index; spin_lock_init(&uart->port.lock); #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) init_timer(&uart->timer); uart->timer.function = serialadv_timeout; #else timer_setup(&uart->timer, serialadv_timeout, 0); #endif /* * ALPHA_KLUDGE_MCR needs to be killed. */ uart->mcr_mask = ~(0x0); //~ALPHA_KLUDGE_MCR; uart->mcr_force = 0; // ALPHA_KLUDGE_MCR; uart->port.ops = &serialadv_pops; /* * Get serial mode */ serial_get_mode(uart); /* * Get board ID */ for(i = 0; i < 10; i++) { serialadv_get_boardID(uart, &uart->board_id); if(uart->board_id != 0 && uart->board_id == boardID) break; boardID = uart->board_id; } uart->current_card_nr = nr_ports; if(uart->board_id == 0x0) { /* * Use old naming methods "ttyAP*" * WARNING: DO NOT try to modify this value "state" anywhere, * this is private value for kernel */ if(adv_uart_driver[0].state == NULL) { /* * The first member adv_uart_driver[0] is reserved for "ttyAP*" */ memset(adv_dev_name[0], 0, sizeof(adv_dev_name[0])); strncpy(adv_dev_name[0], "ttyAP", sizeof(adv_dev_name[0]) - 1); adv_uart_driver[0].dev_name = adv_dev_name[0]; adv_uart_driver[0].nr = NR_PORTS; uart_register_driver(&adv_uart_driver[0]); } /* * Add this port to first member */ uart->port.line = old_method_cnt++; uart->fix_number_index = 0; ret = uart_add_one_port(&adv_uart_driver[0], &uart->port); } else { /* * Enable fixed number. * Fill temp buffer, "ttyB" + boardID + "P" + port_num */ memset(tmp_dev_name, 0, sizeof(tmp_dev_name)); strncpy(tmp_dev_name, "ttyB", sizeof(tmp_dev_name) - 1); sprintf(tmp_string, "%02d", uart->board_id); strncat(tmp_dev_name, tmp_string, sizeof(tmp_dev_name) - strlen(tmp_dev_name)); strncat(tmp_dev_name, "P", sizeof(tmp_dev_name) - strlen(tmp_dev_name)); /* * The first member is reserved for "ttyAP*", begin to loop from 1 */ for(i = 1; i < MAX_CARD_SUPPORT; i++) { /* * Try to find filled member out first */ if(adv_uart_driver[i].state != NULL && strlen(adv_uart_driver[i].dev_name) > 0 && strlen(tmp_dev_name) > 0) { if(strcmp(adv_uart_driver[i].dev_name, tmp_dev_name) == 0) { uart->port.line = index; uart->fix_number_index = i; ret = uart_add_one_port(&adv_uart_driver[i], &uart->port); break; } } /* * Try to find& init the first uninitial member at this step */ if(adv_uart_driver[i].state == NULL) { /* * Re-write index for port number */ adv_uart_driver[i].nr = nr_ports; /* * Set device_name, "ttyB" + boardID + "P" + port_num */ memset(adv_dev_name[i], 0, sizeof(adv_dev_name[i])); strncpy(adv_dev_name[i], tmp_dev_name, ARRAY_SIZE(tmp_dev_name)); adv_uart_driver[i].dev_name = adv_dev_name[i]; memset(adv_driver_name[i], 0, sizeof(adv_driver_name[i])); sprintf(adv_driver_name[i], "ADVserialBID"); strncat(adv_driver_name[i], tmp_string, sizeof(adv_driver_name[i]) - strlen(adv_driver_name[i])); adv_uart_driver[i].driver_name = adv_driver_name[i]; uart_register_driver(&adv_uart_driver[i]); uart->port.line = index; uart->fix_number_index = i; ret = uart_add_one_port(&adv_uart_driver[i], &uart->port); break; } } } #if 0 if (ret == 0) { ret = uart->port.line; if (is_real_interrupt(uart->port.irq)) { serial_link_irq_chain(uart); } } #endif } 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 __devinit init_one_advpciserialcard(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(advpciserial_boards)) { printk(KERN_INFO "pci_init_one: invalid driver_data: %ld\n", ent->driver_data); return -EINVAL; } board = &advpciserial_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; rc = serialadv_register_port(&serial_port, dev->device, i, nr_ports); if (rc < 0) { printk(KERN_WARNING "Couldn't register serial port %s: %d\n", pci_name(dev), i); break; } printk(KERN_WARNING "init_one_advpciserialcard line:%d\n",serial_port.line); priv->uart_index[i] = serial_port.line; priv->line[i] = rc; } 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; } /* * serialadv_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 serialadv_unregister_port(int index, int line) { struct uart_adv_port *uart = &serialadv_ports[line]; mutex_lock(&serial_mutex); #if 0 if (is_real_interrupt(uart->port.irq)) serial_unlink_irq_chain(uart); #endif if(adv_uart_driver[index].state != NULL) { uart_remove_one_port(&adv_uart_driver[index], &uart->port); } uart->port.dev = NULL; mutex_unlock(&serial_mutex); } void pciserial_remove_ports(struct serial_private *priv) { int i, current_index; struct pci_serial_quirk *quirk; /* * Unregister ports one by one */ current_index =serialadv_ports[priv->uart_index[0]].fix_number_index; if((adv_uart_driver[current_index].state != NULL) && (adv_uart_driver[current_index].nr > 0)) { for(i = 0; i < serialadv_ports[priv->uart_index[0]].current_card_nr; i++) { serialadv_unregister_port(current_index, priv->uart_index[i]); } if(current_index != 0) adv_uart_driver[current_index].nr = 0; } 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 __devexit remove_one_advpciserialcard(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 advserial_pci_tbl[] = { /* * PCIE cards */ { ADVANTECH_VID, PCIE_1602, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, { ADVANTECH_VID, PCIE_1604, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, { ADVANTECH_VID, PCIE_1610, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCIE_1612, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCIE_1620, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_8port }, { ADVANTECH_VID, PCIE_1622, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_8port }, { ADVANTECH_VID, PCIE_0014, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_8port }, { ADVANTECH_VID, PCIE_0015, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCIE_0016, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, /* * PCM cards */ { ADVANTECH_VID, PCM_2602C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, { ADVANTECH_VID, PCM_2604C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, { ADVANTECH_VID, PCM_2612B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCM_2610B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCM_3612I, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, /* * PCI cards */ { ADVANTECH_VID, PCI_1602, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, { ADVANTECH_VID, PCI_1604, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_2port }, { ADVANTECH_VID, PCI_1610, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCI_1612, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_4port }, { ADVANTECH_VID, PCI_1620, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_8port }, { ADVANTECH_VID, PCI_1622, PCI_ANY_ID, PCI_ANY_ID, 0, 0, adv_8port }, { 0, } }; static struct pci_driver advserial_pci_driver = { .name = "ADVserial", .probe = init_one_advpciserialcard, .remove = __devexit_p(remove_one_advpciserialcard), .id_table = advserial_pci_tbl, }; static int __init serialadv_init(void) { int i, ret; printk("\n"); printk("===========================================================" "====\n"); printk("Advantech PCIe (17V35x) Serial Device Drivers.\n"); printk("Product V%s [%s]\nFile V%s\n", ADVANTECH_EXAR_VER, ADVANTECH_EXAR_DATE, ADVANTECH_EXAR_FILE_VER); printk("Supports: RS232/422/485 auto detection and setting\n"); printk("Devices: ICOM: PCIE-1602, PCIE-1604\n" " PCIE-1610, PCIE-1612\n" " PCIE-1620, PCIE-1622\n" " PCM-2602C, PCM-2604C\n" " PCM-2610B, PCM-2612B\n" " PCM-3612I\n" " PCI-1602, PCI-1604\n" " PCI-1610, PCI-1612\n" " PCI-1620, PCI-1622\n" ); printk("Advantech Industrial Automation Group.\n"); printk("===========================================================" "====\n"); init_adv_uart_struct(); ret = pci_register_driver(&advserial_pci_driver); if (ret < 0) { for(i = 0; i < MAX_CARD_SUPPORT; i++) { if(adv_uart_driver[i].state != NULL) { uart_unregister_driver(&adv_uart_driver[i]); } } } return ret; } static void __exit serialadv_exit(void) { int i; pci_unregister_driver(&advserial_pci_driver); for(i = 0; i < MAX_CARD_SUPPORT; i++) { if(adv_uart_driver[i].state != NULL) { uart_unregister_driver(&adv_uart_driver[i]); } } } module_init(serialadv_init); module_exit(serialadv_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Advantech PCIe specific serial driver for ADV17V35X"); MODULE_VERSION(ADVANTECH_EXAR_VER); MODULE_DEVICE_TABLE(pci, advserial_pci_tbl); adv_17V35X/Makefile0100777000000000000000000000176413515453744011206 0ustar00#makefile for adv17v35x PCIe UARTs for Linux 2.6.9 and newer KERNEL_SRC = /lib/modules/`uname -r`/build DRV_NAME := adv17v35x all: build obj-m += adv17v35x.o xrpci-objs := adv17v35x.o EXTRA_CFLAGS += -DDEBUG=1 build: $(MAKE) -C $(KERNEL_SRC) M=$(PWD) modules clean: @rm -f *.o @rm -f *.ko @rm -f *.mod.c modules.order Module.symvers Module.markers @rm -f .*.cmd @rm -rf .tmp_versions @rm -rf *~ install: @echo "Intalling the advantech serial driver..." @echo "Please wait..." $(shell ! [ -d /lib/modules/$(shell uname -r)/kernel/drivers/adv17v35x ] && mkdir /lib/modules/$(shell uname -r)/kernel/drivers/adv17v35x) $(shell [ -f adv17v35x.ko ] && cp ./adv17v35x.ko /lib/modules/$(shell uname -r)/kernel/drivers/adv17v35x/ && depmod && modprobe adv17v35x) @echo "Done" uninstall: $(shell if grep $(DRV_NAME) /proc/modules > /dev/null ; then \ rmmod $(DRV_NAME) ; fi) $(shell rm -rf /lib/modules/$(shell uname -r)/kernel/drivers/$(DRV_NAME)/ && depmod) adv_17V35X/precomp.h0100777000000000000000000000646513446320535011361 0ustar00#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) #ifndef __LINUX_MUTEX_H #define __LINUX_MUTEX_H #include #define mutex semaphore #define DEFINE_MUTEX(foo) DECLARE_MUTEX(foo) #define mutex_init(foo) init_MUTEX(foo) #define mutex_lock(foo) down(foo) #define mutex_lock_interruptible(foo) down_interruptible(foo) /* this function follows the spin_trylock() convention, so * * it is negated to the down_trylock() return values! Be careful */ #define mutex_trylock(foo) !down_trylock(foo) #define mutex_unlock(foo) custom_up(foo) /* * Note! This is subtle. We jump to wake people up only if * the semaphore was negative (== somebody was waiting on it). * The default case (no contention) will result in NO * jumps for both down() and up(). */ static inline void custom_up(struct semaphore * sem) { __asm__ __volatile__( "# atomic up operation\n\t" LOCK "incl %0\n\t" /* ++sem->count */ "jle 2f\n" "1:\n" LOCK_SECTION_START("") "2:\tcall __up_wakeup\n\t" "jmp 1b\n" LOCK_SECTION_END ".subsection 0\n" :"=m" (sem->count) :"c" (sem) :"memory"); } #endif /* __LINUX_MUTEX_H */ #endif #ifndef __ADV_PRECOMP_H #define __ADV_PRECOMP_H #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) #define SERIAL_IO_AU 4 #define SERIAL_IO_TSI 5 #define SERIAL_IO_MEM32BE 6 #define SERIAL_IO_MEM16 7 #define UPIO_AU (SERIAL_IO_AU) /* Au1x00 and RT288x type IO */ #define UPIO_TSI (SERIAL_IO_TSI) /* Tsi108/109 type IO */ #define UPIO_MEM32BE (SERIAL_IO_MEM32BE) /* 32b big endian */ #define UPIO_MEM16 (SERIAL_IO_MEM16) /* 16b little endian */ #define UART_MCR_CLKSEL 0x80 /* Divide clock by 4 (TI16C752, EFR[4]=1) */ struct uart_match { struct uart_port *port; struct uart_driver *driver; }; static inline int uart_match_port(struct uart_port *port1, struct uart_port *port2) { if (port1->iotype != port2->iotype) return 0; switch (port1->iotype) { case UPIO_PORT: return (port1->iobase == port2->iobase); case UPIO_HUB6: return (port1->iobase == port2->iobase) && (port1->hub6 == port2->hub6); case UPIO_MEM: case UPIO_MEM16: case UPIO_MEM32: case UPIO_MEM32BE: case UPIO_AU: case UPIO_TSI: return (port1->mapbase == port2->mapbase); } return 0; } static inline void uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun, unsigned int ch, unsigned int flag) { struct tty_struct *tty = port->info->tty; if ((status & port->ignore_status_mask & ~overrun) == 0) tty_insert_flip_char(tty, ch, flag); /* * Overrun is special. Since it's reported immediately, * it doesn't affect the current character. */ if (status & ~port->ignore_status_mask & overrun) tty_insert_flip_char(tty, 0, TTY_OVERRUN); } #endif #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 28) #define DIV_ROUND_CLOSEST(x, divisor)( \ { \ typeof(divisor) __divisor = divisor; \ (((x) + ((__divisor) / 2)) / (__divisor)); \ } \ ) #endif /* * older kernel as 2.6.18 not define UART_LSR_BRK_ERROR_BITS * 0x1E means BI, FE, PE, OE bits */ #ifndef UART_LSR_BRK_ERROR_BITS #define UART_LSR_BRK_ERROR_BITS 0x1E #endif #endif /* __ADV_PRECOMP_H */ adv_17V35X/readme0100777000000000000000000001722613577012207010717 0ustar00============================================================================= Advantech PCIe Multiport Device Driver Installation Guide for Linux Copyright (C) 2019, Advantech. ============================================================================= Changes log: Version 5.0.6.0 <2019-10-16> -support for CentOS7.6 Version 5.0.5.1 <2019-07-22> -fix compile error in kernel 4.15 and later version; Version 5.0.4.0 <2018-03-21> - Add XON ANY support; - Support termios2 for non-standard baud rate; - Fix RTS, DTR incorrect issue; - Support new products; Version 5.0.3.0 <2017-03-06> - Add fixed number; - Add a parameter to enable/disable internal loopback; - Add non-standard baud rates support; - Improve Software flow control and Hardware flow control behavior; - Support kernel 2.6.9; Version 5.0.2.0 <2016-08-29> - Support kernel up to 4.2.x; - Fix data error issue on Fedora 21/23; Version 5.0.1.0 <2015-08-28> - Original version ----------------------------------------------------------------------------- 1. Introduction The PCI/PCIe Multiport serial driver, supports the following multiport boards. - PCIE-1602 PCIe Multiport (2 Channels) Boards - PCIE-1604 PCIe Multiport (2 Channels) Boards - PCIE-1610 PCIe Multiport (4 Channels) Boards - PCIE-1612 PCIe Multiport (4 Channels) Boards - PCIE-1620 PCIe Multiport (8 Channels) Boards - PCIE-1622 PCIe Multiport (8 Channels) Boards - PCM-2602C PCM Multiport (2 Channels) Boards - PCM-2604C PCM Multiport (2 Channels) Boards - PCM-2610B PCM Multiport (4 Channels) Boards - PCM-2612B PCM Multiport (4 Channels) Boards - PCM-3612I PCM Multiport (4 Channels) Boards - PCI-1602 PCI Multiport (2 Channels) Boards - PCI-1604 PCI Multiport (2 Channels) Boards - PCI-1610 PCI Multiport (4 Channels) Boards - PCI-1612 PCI Multiport (4 Channels) Boards - PCI-1620 PCI Multiport (8 Channels) Boards - PCI-1622 PCI Multiport (8 Channels) Boards This driver and installation procedure have been tested on various kernel versions. All the drivers and utilities are published in form of source code under GNU General Public License in this version. Please refer to GNU General Public License announcement in each source code file for more detail. This version of driver can be installed as Loadable Module (Module driver) or built-in into kernel (Static driver). You may refer to following installation procedure for suitable one. Before you install the driver, please refer to hardware installation procedure in the User's Manual. We assume the user should be familiar with following documents. - Serial-HOWTO - Kernel-HOWTO ----------------------------------------------------------------------------- 2. System Requirement - Hardware platform: Intel x86 - Maximum of 256 ports can be installed in any PCI/PCIe board combination - This driver has been tested on various kernel versions. It is tested in follow systems: ------------------------------------------ -------------- ---------------- | Distribution | Kernel version | ------------------------------------------ ------------------------------- | Ubuntu 12.04/14.04/15.10(Desktop)/18.04 | 3.2/3.13/4.2/4.15 | ------------------------------------------ ------------------------------- | Redhat Enterprise Linux Server 7.2 | 3.10.0 | ------------------------------------------ ------------------------------- | OpenSUSE 15.0 | 4.12.14 | ------------------------------------------ ------------------------------- | Debian 9.5 | 4.9.0 | ------------------------------------------ ------------------------------- | CentOS 7.6 | 3.10.0 | ------------------------------------------ ------------------------------- It is strongly recommended that the user do a complete install of all components to ensure that this driver works correctly. ----------------------------------------------------------------------------- 3. Installation 3.1. Login as 'root' before executing the following instructions. 3.2. Uncompress the driver # tar zxvf adv_17V35X-VerX.tar.gz 3.3. Build the driver # cd adv_17V35X # make Note: To compile the source code, you must install kernel development header file package,"gcc" and "make" command. 3.4. Install the driver module # make install or #insmod ./adv17v35x.ko to see whether ttyAPn or ttyBnPn ( n = 1,2,3...) exist in /dev/ folder. 3.5. Uninstall the driver module and clean # make uninstall # make clean or #rmmod adv17v35x 3.6 Verify driver installation You may run command "dmesg" to check the latest status log reported by this driver whenever it's activated. This module registers Advantech ports as "/dev/ttyAP0", "/dev/ttyAP1", etc. Use "minicom" or your custom serial application to test the ports Attention! The name of PCIE-16XX ports are the same as Old cards which use Oxford chip, so don't use them at the same time. ----------------------------------------------------------------------------- 4. Driver files and device naming convention The driver file may be obtained via email. The first step, anyway, is to copy driver file "adv_17V35X-VerX" into specified directory. e.g. /advantech. The execute commands as below. # cd /advantech/adv_17V35X-VerX You may find the driver source code in /advantech/adv_17V35X-VerX. Driver creates traditional serial device names, "/dev/ttyAP0" onwards. Device naming when multiple boards are installed ----------------------------------------------- assuming 2 PCIE-1602 boards, 1 PCIE-1610 board and 1 PCIE-1622 board. Board Num. Port Name 1st board (1602) ttyAP0 - ttyAP1 2nd board (1602) ttyAP2 - ttyAP3 3rd board (1610) ttyAP4 - ttyAP7 4th board (1622) ttyAP8 - ttyAP15 Note. We don't know which card be found first, maybe 1602, or 1610, or 1622. This depends on the sequence that operating system scanning. ----------------------------------------------------------------------------- 5. How to use Fixed Number From the latest version (v5.0.3.0) on, we'll add the new feature callled Fixed Number. This function enables the user to alternate the PCI/PCIE card slot without changing the name of the device node generated by each port. 5.1 Using the old naming "ttyAPn" You'll find a switch called "Board ID" on the card. Adjust the switch to "ON". 5.2 Using Fixed Number "ttyBnPn" Naming: "ttyB" + Board ID + "P" + Index, such as "ttyB03P0", "ttyB15P1", etc. You'll find a switch called "Board ID" on the card. Adjust the switch to the opposite side of "ON", i.e. the non-zero value. For example, if you want to generate a device node for "ttyB03Pn", you need to set "Board ID" to 0x3. ----------------------------------------------------------------------------- 6. Troubleshooting The boot time error messages and solutions are stated as clearly as possible. If all the possible solutions fail, please contact our technical support team to get more help.