From 57f0f512b273f60d52568b8c6b77e17f5636edc0 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Wed, 5 Aug 2015 17:04:01 -0300 Subject: Initial import --- drivers/staging/dgnc/Kconfig | 6 + drivers/staging/dgnc/Makefile | 6 + drivers/staging/dgnc/TODO | 10 + drivers/staging/dgnc/dgnc_cls.c | 1316 ++++++++++++++++ drivers/staging/dgnc/dgnc_cls.h | 82 + drivers/staging/dgnc/dgnc_driver.c | 729 +++++++++ drivers/staging/dgnc/dgnc_driver.h | 400 +++++ drivers/staging/dgnc/dgnc_mgmt.c | 262 +++ drivers/staging/dgnc/dgnc_mgmt.h | 25 + drivers/staging/dgnc/dgnc_neo.c | 1846 ++++++++++++++++++++++ drivers/staging/dgnc/dgnc_neo.h | 149 ++ drivers/staging/dgnc/dgnc_pci.h | 69 + drivers/staging/dgnc/dgnc_sysfs.c | 697 ++++++++ drivers/staging/dgnc/dgnc_sysfs.h | 40 + drivers/staging/dgnc/dgnc_tty.c | 3057 ++++++++++++++++++++++++++++++++++++ drivers/staging/dgnc/dgnc_tty.h | 34 + drivers/staging/dgnc/dgnc_utils.c | 18 + drivers/staging/dgnc/dgnc_utils.h | 6 + drivers/staging/dgnc/digi.h | 179 +++ 19 files changed, 8931 insertions(+) create mode 100644 drivers/staging/dgnc/Kconfig create mode 100644 drivers/staging/dgnc/Makefile create mode 100644 drivers/staging/dgnc/TODO create mode 100644 drivers/staging/dgnc/dgnc_cls.c create mode 100644 drivers/staging/dgnc/dgnc_cls.h create mode 100644 drivers/staging/dgnc/dgnc_driver.c create mode 100644 drivers/staging/dgnc/dgnc_driver.h create mode 100644 drivers/staging/dgnc/dgnc_mgmt.c create mode 100644 drivers/staging/dgnc/dgnc_mgmt.h create mode 100644 drivers/staging/dgnc/dgnc_neo.c create mode 100644 drivers/staging/dgnc/dgnc_neo.h create mode 100644 drivers/staging/dgnc/dgnc_pci.h create mode 100644 drivers/staging/dgnc/dgnc_sysfs.c create mode 100644 drivers/staging/dgnc/dgnc_sysfs.h create mode 100644 drivers/staging/dgnc/dgnc_tty.c create mode 100644 drivers/staging/dgnc/dgnc_tty.h create mode 100644 drivers/staging/dgnc/dgnc_utils.c create mode 100644 drivers/staging/dgnc/dgnc_utils.h create mode 100644 drivers/staging/dgnc/digi.h (limited to 'drivers/staging/dgnc') diff --git a/drivers/staging/dgnc/Kconfig b/drivers/staging/dgnc/Kconfig new file mode 100644 index 000000000..032c2a795 --- /dev/null +++ b/drivers/staging/dgnc/Kconfig @@ -0,0 +1,6 @@ +config DGNC + tristate "Digi Neo and Classic PCI Products" + default n + depends on TTY && PCI + ---help--- + Driver for the Digi International Neo and Classic PCI based product line. diff --git a/drivers/staging/dgnc/Makefile b/drivers/staging/dgnc/Makefile new file mode 100644 index 000000000..995c874f4 --- /dev/null +++ b/drivers/staging/dgnc/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_DGNC) += dgnc.o + +dgnc-objs := dgnc_cls.o dgnc_driver.o\ + dgnc_mgmt.o dgnc_neo.o\ + dgnc_tty.o dgnc_sysfs.o\ + dgnc_utils.o diff --git a/drivers/staging/dgnc/TODO b/drivers/staging/dgnc/TODO new file mode 100644 index 000000000..2b2c6ea03 --- /dev/null +++ b/drivers/staging/dgnc/TODO @@ -0,0 +1,10 @@ +* checkpatch fixes +* remove unecessary comments +* remove unecessary error messages. Example kzalloc() has its + own error message. Adding an extra one is useless. +* use goto statements for error handling when appropriate +* there is a lot of unecessary code in the driver. It was + originally a standalone driver. Remove uneeded code. + +Please send patches to Greg Kroah-Hartman and +Cc: Lidza Louina diff --git a/drivers/staging/dgnc/dgnc_cls.c b/drivers/staging/dgnc/dgnc_cls.c new file mode 100644 index 000000000..e3564d278 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_cls.c @@ -0,0 +1,1316 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#include +#include /* For jiffies, task states */ +#include /* For tasklet and interrupt structs/defines */ +#include /* For udelay */ +#include /* For read[bwl]/write[bwl] */ +#include /* For struct async_serial */ +#include /* For the various UART offsets */ +#include + +#include "dgnc_driver.h" /* Driver main header file */ +#include "dgnc_cls.h" +#include "dgnc_tty.h" + +static inline void cls_parse_isr(struct dgnc_board *brd, uint port); +static inline void cls_clear_break(struct channel_t *ch, int force); +static inline void cls_set_cts_flow_control(struct channel_t *ch); +static inline void cls_set_rts_flow_control(struct channel_t *ch); +static inline void cls_set_ixon_flow_control(struct channel_t *ch); +static inline void cls_set_ixoff_flow_control(struct channel_t *ch); +static inline void cls_set_no_output_flow_control(struct channel_t *ch); +static inline void cls_set_no_input_flow_control(struct channel_t *ch); +static void cls_parse_modem(struct channel_t *ch, unsigned char signals); +static void cls_tasklet(unsigned long data); +static void cls_vpd(struct dgnc_board *brd); +static void cls_uart_init(struct channel_t *ch); +static void cls_uart_off(struct channel_t *ch); +static int cls_drain(struct tty_struct *tty, uint seconds); +static void cls_param(struct tty_struct *tty); +static void cls_assert_modem_signals(struct channel_t *ch); +static void cls_flush_uart_write(struct channel_t *ch); +static void cls_flush_uart_read(struct channel_t *ch); +static void cls_disable_receiver(struct channel_t *ch); +static void cls_enable_receiver(struct channel_t *ch); +static void cls_send_break(struct channel_t *ch, int msecs); +static void cls_send_start_character(struct channel_t *ch); +static void cls_send_stop_character(struct channel_t *ch); +static void cls_copy_data_from_uart_to_queue(struct channel_t *ch); +static void cls_copy_data_from_queue_to_uart(struct channel_t *ch); +static uint cls_get_uart_bytes_left(struct channel_t *ch); +static void cls_send_immediate_char(struct channel_t *ch, unsigned char); +static irqreturn_t cls_intr(int irq, void *voidbrd); + +struct board_ops dgnc_cls_ops = { + .tasklet = cls_tasklet, + .intr = cls_intr, + .uart_init = cls_uart_init, + .uart_off = cls_uart_off, + .drain = cls_drain, + .param = cls_param, + .vpd = cls_vpd, + .assert_modem_signals = cls_assert_modem_signals, + .flush_uart_write = cls_flush_uart_write, + .flush_uart_read = cls_flush_uart_read, + .disable_receiver = cls_disable_receiver, + .enable_receiver = cls_enable_receiver, + .send_break = cls_send_break, + .send_start_character = cls_send_start_character, + .send_stop_character = cls_send_stop_character, + .copy_data_from_queue_to_uart = cls_copy_data_from_queue_to_uart, + .get_uart_bytes_left = cls_get_uart_bytes_left, + .send_immediate_char = cls_send_immediate_char +}; + +static inline void cls_set_cts_flow_control(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char ier = readb(&ch->ch_cls_uart->ier); + unsigned char isr_fcr = 0; + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn on CTS flow control, turn off IXON flow control */ + isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_CTSDSR); + isr_fcr &= ~(UART_EXAR654_EFR_IXON); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* + * Enable interrupts for CTS flow, turn off interrupts for + * received XOFF chars + */ + ier |= (UART_EXAR654_IER_CTSDSR); + ier &= ~(UART_EXAR654_IER_XOFF); + writeb(ier, &ch->ch_cls_uart->ier); + + /* Set the usual FIFO values */ + writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr); + + writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_56 | + UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR), + &ch->ch_cls_uart->isr_fcr); + + ch->ch_t_tlevel = 16; + +} + +static inline void cls_set_ixon_flow_control(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char ier = readb(&ch->ch_cls_uart->ier); + unsigned char isr_fcr = 0; + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn on IXON flow control, turn off CTS flow control */ + isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_IXON); + isr_fcr &= ~(UART_EXAR654_EFR_CTSDSR); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Now set our current start/stop chars while in enhanced mode */ + writeb(ch->ch_startc, &ch->ch_cls_uart->mcr); + writeb(0, &ch->ch_cls_uart->lsr); + writeb(ch->ch_stopc, &ch->ch_cls_uart->msr); + writeb(0, &ch->ch_cls_uart->spr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* + * Disable interrupts for CTS flow, turn on interrupts for + * received XOFF chars + */ + ier &= ~(UART_EXAR654_IER_CTSDSR); + ier |= (UART_EXAR654_IER_XOFF); + writeb(ier, &ch->ch_cls_uart->ier); + + /* Set the usual FIFO values */ + writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr); + + writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 | + UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR), + &ch->ch_cls_uart->isr_fcr); + +} + +static inline void cls_set_no_output_flow_control(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char ier = readb(&ch->ch_cls_uart->ier); + unsigned char isr_fcr = 0; + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn off IXON flow control, turn off CTS flow control */ + isr_fcr |= (UART_EXAR654_EFR_ECB); + isr_fcr &= ~(UART_EXAR654_EFR_CTSDSR | UART_EXAR654_EFR_IXON); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* + * Disable interrupts for CTS flow, turn off interrupts for + * received XOFF chars + */ + ier &= ~(UART_EXAR654_IER_CTSDSR); + ier &= ~(UART_EXAR654_IER_XOFF); + writeb(ier, &ch->ch_cls_uart->ier); + + /* Set the usual FIFO values */ + writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr); + + writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 | + UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR), + &ch->ch_cls_uart->isr_fcr); + + ch->ch_r_watermark = 0; + ch->ch_t_tlevel = 16; + ch->ch_r_tlevel = 16; + +} + +static inline void cls_set_rts_flow_control(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char ier = readb(&ch->ch_cls_uart->ier); + unsigned char isr_fcr = 0; + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn on RTS flow control, turn off IXOFF flow control */ + isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_RTSDTR); + isr_fcr &= ~(UART_EXAR654_EFR_IXOFF); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* Enable interrupts for RTS flow */ + ier |= (UART_EXAR654_IER_RTSDTR); + writeb(ier, &ch->ch_cls_uart->ier); + + /* Set the usual FIFO values */ + writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr); + + writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_56 | + UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR), + &ch->ch_cls_uart->isr_fcr); + + ch->ch_r_watermark = 4; + ch->ch_r_tlevel = 8; + +} + +static inline void cls_set_ixoff_flow_control(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char ier = readb(&ch->ch_cls_uart->ier); + unsigned char isr_fcr = 0; + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn on IXOFF flow control, turn off RTS flow control */ + isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_IXOFF); + isr_fcr &= ~(UART_EXAR654_EFR_RTSDTR); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Now set our current start/stop chars while in enhanced mode */ + writeb(ch->ch_startc, &ch->ch_cls_uart->mcr); + writeb(0, &ch->ch_cls_uart->lsr); + writeb(ch->ch_stopc, &ch->ch_cls_uart->msr); + writeb(0, &ch->ch_cls_uart->spr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* Disable interrupts for RTS flow */ + ier &= ~(UART_EXAR654_IER_RTSDTR); + writeb(ier, &ch->ch_cls_uart->ier); + + /* Set the usual FIFO values */ + writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr); + + writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 | + UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR), + &ch->ch_cls_uart->isr_fcr); + +} + +static inline void cls_set_no_input_flow_control(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char ier = readb(&ch->ch_cls_uart->ier); + unsigned char isr_fcr = 0; + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn off IXOFF flow control, turn off RTS flow control */ + isr_fcr |= (UART_EXAR654_EFR_ECB); + isr_fcr &= ~(UART_EXAR654_EFR_RTSDTR | UART_EXAR654_EFR_IXOFF); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* Disable interrupts for RTS flow */ + ier &= ~(UART_EXAR654_IER_RTSDTR); + writeb(ier, &ch->ch_cls_uart->ier); + + /* Set the usual FIFO values */ + writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr); + + writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 | + UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR), + &ch->ch_cls_uart->isr_fcr); + + ch->ch_t_tlevel = 16; + ch->ch_r_tlevel = 16; + +} + +/* + * cls_clear_break. + * Determines whether its time to shut off break condition. + * + * No locks are assumed to be held when calling this function. + * channel lock is held and released in this function. + */ +static inline void cls_clear_break(struct channel_t *ch, int force) +{ + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Bail if we aren't currently sending a break. */ + if (!ch->ch_stop_sending_break) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return; + } + + /* Turn break off, and unset some variables */ + if (ch->ch_flags & CH_BREAK_SENDING) { + if (time_after(jiffies, ch->ch_stop_sending_break) || force) { + unsigned char temp = readb(&ch->ch_cls_uart->lcr); + + writeb((temp & ~UART_LCR_SBC), &ch->ch_cls_uart->lcr); + ch->ch_flags &= ~(CH_BREAK_SENDING); + ch->ch_stop_sending_break = 0; + } + } + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + +/* Parse the ISR register for the specific port */ +static inline void cls_parse_isr(struct dgnc_board *brd, uint port) +{ + struct channel_t *ch; + unsigned char isr = 0; + unsigned long flags; + + /* + * No need to verify board pointer, it was already + * verified in the interrupt routine. + */ + + if (port >= brd->nasync) + return; + + ch = brd->channels[port]; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + /* Here we try to figure out what caused the interrupt to happen */ + while (1) { + + isr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Bail if no pending interrupt on port */ + if (isr & UART_IIR_NO_INT) + break; + + /* Receive Interrupt pending */ + if (isr & (UART_IIR_RDI | UART_IIR_RDI_TIMEOUT)) { + /* Read data from uart -> queue */ + brd->intr_rx++; + ch->ch_intr_rx++; + cls_copy_data_from_uart_to_queue(ch); + dgnc_check_queue_flow_control(ch); + } + + /* Transmit Hold register empty pending */ + if (isr & UART_IIR_THRI) { + /* Transfer data (if any) from Write Queue -> UART. */ + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + brd->intr_tx++; + ch->ch_intr_tx++; + spin_unlock_irqrestore(&ch->ch_lock, flags); + cls_copy_data_from_queue_to_uart(ch); + } + + /* CTS/RTS change of state */ + if (isr & UART_IIR_CTSRTS) { + brd->intr_modem++; + ch->ch_intr_modem++; + /* + * Don't need to do anything, the cls_parse_modem + * below will grab the updated modem signals. + */ + } + + /* Parse any modem signal changes */ + cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr)); + } +} + +/* + * cls_param() + * Send any/all changes to the line to the UART. + */ +static void cls_param(struct tty_struct *tty) +{ + unsigned char lcr = 0; + unsigned char uart_lcr = 0; + unsigned char ier = 0; + unsigned char uart_ier = 0; + uint baud = 9600; + int quot = 0; + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = (struct un_t *) tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + /* + * If baud rate is zero, flush queues, and set mval to drop DTR. + */ + if ((ch->ch_c_cflag & (CBAUD)) == 0) { + ch->ch_r_head = 0; + ch->ch_r_tail = 0; + ch->ch_e_head = 0; + ch->ch_e_tail = 0; + ch->ch_w_head = 0; + ch->ch_w_tail = 0; + + cls_flush_uart_write(ch); + cls_flush_uart_read(ch); + + /* The baudrate is B0 so all modem lines are to be dropped. */ + ch->ch_flags |= (CH_BAUD0); + ch->ch_mostat &= ~(UART_MCR_RTS | UART_MCR_DTR); + cls_assert_modem_signals(ch); + ch->ch_old_baud = 0; + return; + } else if (ch->ch_custom_speed) { + + baud = ch->ch_custom_speed; + /* Handle transition from B0 */ + if (ch->ch_flags & CH_BAUD0) { + ch->ch_flags &= ~(CH_BAUD0); + + /* + * Bring back up RTS and DTR... + * Also handle RTS or DTR toggle if set. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + } + + } else { + int iindex = 0; + int jindex = 0; + + ulong bauds[4][16] = { + { /* slowbaud */ + 0, 50, 75, 110, + 134, 150, 200, 300, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* slowbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* fastbaud */ + 0, 57600, 76800, 115200, + 131657, 153600, 230400, 460800, + 921600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* fastbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 } + }; + + /* + * Only use the TXPrint baud rate if the terminal + * unit is NOT open + */ + if (!(ch->ch_tun.un_flags & UN_ISOPEN) && + (un->un_type == DGNC_PRINT)) + baud = C_BAUD(ch->ch_pun.un_tty) & 0xff; + else + baud = C_BAUD(ch->ch_tun.un_tty) & 0xff; + + if (ch->ch_c_cflag & CBAUDEX) + iindex = 1; + + if (ch->ch_digi.digi_flags & DIGI_FAST) + iindex += 2; + + jindex = baud; + + if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) && + (jindex < 16)) { + baud = bauds[iindex][jindex]; + } else { + baud = 0; + } + + if (baud == 0) + baud = 9600; + + /* Handle transition from B0 */ + if (ch->ch_flags & CH_BAUD0) { + ch->ch_flags &= ~(CH_BAUD0); + + /* + * Bring back up RTS and DTR... + * Also handle RTS or DTR toggle if set. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + } + } + + if (ch->ch_c_cflag & PARENB) + lcr |= UART_LCR_PARITY; + + if (!(ch->ch_c_cflag & PARODD)) + lcr |= UART_LCR_EPAR; + + /* + * Not all platforms support mark/space parity, + * so this will hide behind an ifdef. + */ +#ifdef CMSPAR + if (ch->ch_c_cflag & CMSPAR) + lcr |= UART_LCR_SPAR; +#endif + + if (ch->ch_c_cflag & CSTOPB) + lcr |= UART_LCR_STOP; + + switch (ch->ch_c_cflag & CSIZE) { + case CS5: + lcr |= UART_LCR_WLEN5; + break; + case CS6: + lcr |= UART_LCR_WLEN6; + break; + case CS7: + lcr |= UART_LCR_WLEN7; + break; + case CS8: + default: + lcr |= UART_LCR_WLEN8; + break; + } + + uart_ier = readb(&ch->ch_cls_uart->ier); + ier = uart_ier; + uart_lcr = readb(&ch->ch_cls_uart->lcr); + + if (baud == 0) + baud = 9600; + + quot = ch->ch_bd->bd_dividend / baud; + + if (quot != 0 && ch->ch_old_baud != baud) { + ch->ch_old_baud = baud; + writeb(UART_LCR_DLAB, &ch->ch_cls_uart->lcr); + writeb((quot & 0xff), &ch->ch_cls_uart->txrx); + writeb((quot >> 8), &ch->ch_cls_uart->ier); + writeb(lcr, &ch->ch_cls_uart->lcr); + } + + if (uart_lcr != lcr) + writeb(lcr, &ch->ch_cls_uart->lcr); + + if (ch->ch_c_cflag & CREAD) + ier |= (UART_IER_RDI | UART_IER_RLSI); + else + ier &= ~(UART_IER_RDI | UART_IER_RLSI); + + /* + * Have the UART interrupt on modem signal changes ONLY when + * we are in hardware flow control mode, or CLOCAL/FORCEDCD is not set. + */ + if ((ch->ch_digi.digi_flags & CTSPACE) || + (ch->ch_digi.digi_flags & RTSPACE) || + (ch->ch_c_cflag & CRTSCTS) || + !(ch->ch_digi.digi_flags & DIGI_FORCEDCD) || + !(ch->ch_c_cflag & CLOCAL)) + ier |= UART_IER_MSI; + else + ier &= ~UART_IER_MSI; + + ier |= UART_IER_THRI; + + if (ier != uart_ier) + writeb(ier, &ch->ch_cls_uart->ier); + + if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) { + cls_set_cts_flow_control(ch); + } else if (ch->ch_c_iflag & IXON) { + /* + * If start/stop is set to disable, then we should + * disable flow control + */ + if ((ch->ch_startc == _POSIX_VDISABLE) || + (ch->ch_stopc == _POSIX_VDISABLE)) + cls_set_no_output_flow_control(ch); + else + cls_set_ixon_flow_control(ch); + } else { + cls_set_no_output_flow_control(ch); + } + + if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) { + cls_set_rts_flow_control(ch); + } else if (ch->ch_c_iflag & IXOFF) { + /* + * If start/stop is set to disable, then we should disable + * flow control + */ + if ((ch->ch_startc == _POSIX_VDISABLE) || + (ch->ch_stopc == _POSIX_VDISABLE)) + cls_set_no_input_flow_control(ch); + else + cls_set_ixoff_flow_control(ch); + } else { + cls_set_no_input_flow_control(ch); + } + + cls_assert_modem_signals(ch); + + /* Get current status of the modem signals now */ + cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr)); +} + +/* + * Our board poller function. + */ +static void cls_tasklet(unsigned long data) +{ + struct dgnc_board *bd = (struct dgnc_board *) data; + struct channel_t *ch; + unsigned long flags; + int i; + int state = 0; + int ports = 0; + + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + /* Cache a couple board values */ + spin_lock_irqsave(&bd->bd_lock, flags); + state = bd->state; + ports = bd->nasync; + spin_unlock_irqrestore(&bd->bd_lock, flags); + + /* + * Do NOT allow the interrupt routine to read the intr registers + * Until we release this lock. + */ + spin_lock_irqsave(&bd->bd_intr_lock, flags); + + /* + * If board is ready, parse deeper to see if there is anything to do. + */ + if ((state == BOARD_READY) && (ports > 0)) { + + /* Loop on each port */ + for (i = 0; i < ports; i++) { + ch = bd->channels[i]; + if (!ch) + continue; + + /* + * NOTE: Remember you CANNOT hold any channel + * locks when calling input. + * During input processing, its possible we + * will call ld, which might do callbacks back + * into us. + */ + dgnc_input(ch); + + /* + * Channel lock is grabbed and then released + * inside this routine. + */ + cls_copy_data_from_queue_to_uart(ch); + dgnc_wakeup_writes(ch); + + /* + * Check carrier function. + */ + dgnc_carrier(ch); + + /* + * The timing check of turning off the break is done + * inside clear_break() + */ + if (ch->ch_stop_sending_break) + cls_clear_break(ch, 0); + } + } + + spin_unlock_irqrestore(&bd->bd_intr_lock, flags); + +} + +/* + * cls_intr() + * + * Classic specific interrupt handler. + */ +static irqreturn_t cls_intr(int irq, void *voidbrd) +{ + struct dgnc_board *brd = voidbrd; + uint i = 0; + unsigned char poll_reg; + unsigned long flags; + + /* + * Check to make sure it didn't receive interrupt with a null board + * associated or a board pointer that wasn't ours. + */ + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return IRQ_NONE; + + spin_lock_irqsave(&brd->bd_intr_lock, flags); + + brd->intr_count++; + + /* + * Check the board's global interrupt offset to see if we + * we actually do have an interrupt pending for us. + */ + poll_reg = readb(brd->re_map_membase + UART_CLASSIC_POLL_ADDR_OFFSET); + + /* If 0, no interrupts pending */ + if (!poll_reg) { + spin_unlock_irqrestore(&brd->bd_intr_lock, flags); + return IRQ_NONE; + } + + /* Parse each port to find out what caused the interrupt */ + for (i = 0; i < brd->nasync; i++) + cls_parse_isr(brd, i); + + /* + * Schedule tasklet to more in-depth servicing at a better time. + */ + tasklet_schedule(&brd->helper_tasklet); + + spin_unlock_irqrestore(&brd->bd_intr_lock, flags); + + return IRQ_HANDLED; +} + +static void cls_disable_receiver(struct channel_t *ch) +{ + unsigned char tmp = readb(&ch->ch_cls_uart->ier); + + tmp &= ~(UART_IER_RDI); + writeb(tmp, &ch->ch_cls_uart->ier); +} + +static void cls_enable_receiver(struct channel_t *ch) +{ + unsigned char tmp = readb(&ch->ch_cls_uart->ier); + + tmp |= (UART_IER_RDI); + writeb(tmp, &ch->ch_cls_uart->ier); +} + +static void cls_copy_data_from_uart_to_queue(struct channel_t *ch) +{ + int qleft = 0; + unsigned char linestatus = 0; + unsigned char error_mask = 0; + ushort head; + ushort tail; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* cache head and tail of queue */ + head = ch->ch_r_head; + tail = ch->ch_r_tail; + + /* Store how much space we have left in the queue */ + qleft = (tail - head - 1); + if (qleft < 0) + qleft += RQUEUEMASK + 1; + + /* + * Create a mask to determine whether we should + * insert the character (if any) into our queue. + */ + if (ch->ch_c_iflag & IGNBRK) + error_mask |= UART_LSR_BI; + + while (1) { + linestatus = readb(&ch->ch_cls_uart->lsr); + + if (!(linestatus & (UART_LSR_DR))) + break; + + /* + * Discard character if we are ignoring the error mask. + */ + if (linestatus & error_mask) { + unsigned char discard; + + linestatus = 0; + discard = readb(&ch->ch_cls_uart->txrx); + continue; + } + + /* + * If our queue is full, we have no choice but to drop some + * data. The assumption is that HWFLOW or SWFLOW should have + * stopped things way way before we got to this point. + * + * I decided that I wanted to ditch the oldest data first, + * I hope thats okay with everyone? Yes? Good. + */ + while (qleft < 1) { + tail = (tail + 1) & RQUEUEMASK; + ch->ch_r_tail = tail; + ch->ch_err_overrun++; + qleft++; + } + + ch->ch_equeue[head] = linestatus & (UART_LSR_BI | UART_LSR_PE + | UART_LSR_FE); + ch->ch_rqueue[head] = readb(&ch->ch_cls_uart->txrx); + + qleft--; + + if (ch->ch_equeue[head] & UART_LSR_PE) + ch->ch_err_parity++; + if (ch->ch_equeue[head] & UART_LSR_BI) + ch->ch_err_break++; + if (ch->ch_equeue[head] & UART_LSR_FE) + ch->ch_err_frame++; + + /* Add to, and flip head if needed */ + head = (head + 1) & RQUEUEMASK; + ch->ch_rxcount++; + } + + /* + * Write new final heads to channel structure. + */ + ch->ch_r_head = head & RQUEUEMASK; + ch->ch_e_head = head & EQUEUEMASK; + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + +/* + * This function basically goes to sleep for secs, or until + * it gets signalled that the port has fully drained. + */ +static int cls_drain(struct tty_struct *tty, uint seconds) +{ + unsigned long flags; + struct channel_t *ch; + struct un_t *un; + + if (!tty || tty->magic != TTY_MAGIC) + return -ENXIO; + + un = (struct un_t *) tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -ENXIO; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + spin_lock_irqsave(&ch->ch_lock, flags); + un->un_flags |= UN_EMPTY; + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * NOTE: Do something with time passed in. + */ + + /* If ret is non-zero, user ctrl-c'ed us */ + + return wait_event_interruptible(un->un_flags_wait, + ((un->un_flags & UN_EMPTY) == 0)); +} + +/* Channel lock MUST be held before calling this function! */ +static void cls_flush_uart_write(struct channel_t *ch) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_XMIT), + &ch->ch_cls_uart->isr_fcr); + udelay(10); + + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); +} + +/* Channel lock MUST be held before calling this function! */ +static void cls_flush_uart_read(struct channel_t *ch) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + /* + * For complete POSIX compatibility, we should be purging the + * read FIFO in the UART here. + * + * However, clearing the read FIFO (UART_FCR_CLEAR_RCVR) also + * incorrectly flushes write data as well as just basically trashing the + * FIFO. + * + * Presumably, this is a bug in this UART. + */ + + udelay(10); +} + +static void cls_copy_data_from_queue_to_uart(struct channel_t *ch) +{ + ushort head; + ushort tail; + int n; + int qlen; + uint len_written = 0; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* No data to write to the UART */ + if (ch->ch_w_tail == ch->ch_w_head) + goto exit_unlock; + + /* If port is "stopped", don't send any data to the UART */ + if ((ch->ch_flags & CH_FORCED_STOP) || + (ch->ch_flags & CH_BREAK_SENDING)) + goto exit_unlock; + + if (!(ch->ch_flags & (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM))) + goto exit_unlock; + + n = 32; + + /* cache head and tail of queue */ + head = ch->ch_w_head & WQUEUEMASK; + tail = ch->ch_w_tail & WQUEUEMASK; + qlen = (head - tail) & WQUEUEMASK; + + /* Find minimum of the FIFO space, versus queue length */ + n = min(n, qlen); + + while (n > 0) { + + /* + * If RTS Toggle mode is on, turn on RTS now if not already set, + * and make sure we get an event when the data transfer has + * completed. + */ + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) { + if (!(ch->ch_mostat & UART_MCR_RTS)) { + ch->ch_mostat |= (UART_MCR_RTS); + cls_assert_modem_signals(ch); + } + ch->ch_tun.un_flags |= (UN_EMPTY); + } + + /* + * If DTR Toggle mode is on, turn on DTR now if not already set, + * and make sure we get an event when the data transfer has + * completed. + */ + if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) { + if (!(ch->ch_mostat & UART_MCR_DTR)) { + ch->ch_mostat |= (UART_MCR_DTR); + cls_assert_modem_signals(ch); + } + ch->ch_tun.un_flags |= (UN_EMPTY); + } + writeb(ch->ch_wqueue[ch->ch_w_tail], &ch->ch_cls_uart->txrx); + ch->ch_w_tail++; + ch->ch_w_tail &= WQUEUEMASK; + ch->ch_txcount++; + len_written++; + n--; + } + + if (len_written > 0) + ch->ch_flags &= ~(CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + +exit_unlock: + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + +static void cls_parse_modem(struct channel_t *ch, unsigned char signals) +{ + unsigned char msignals = signals; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + /* + * Do altpin switching. Altpin switches DCD and DSR. + * This prolly breaks DSRPACE, so we should be more clever here. + */ + spin_lock_irqsave(&ch->ch_lock, flags); + if (ch->ch_digi.digi_flags & DIGI_ALTPIN) { + unsigned char mswap = signals; + + if (mswap & UART_MSR_DDCD) { + msignals &= ~UART_MSR_DDCD; + msignals |= UART_MSR_DDSR; + } + if (mswap & UART_MSR_DDSR) { + msignals &= ~UART_MSR_DDSR; + msignals |= UART_MSR_DDCD; + } + if (mswap & UART_MSR_DCD) { + msignals &= ~UART_MSR_DCD; + msignals |= UART_MSR_DSR; + } + if (mswap & UART_MSR_DSR) { + msignals &= ~UART_MSR_DSR; + msignals |= UART_MSR_DCD; + } + } + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * Scrub off lower bits. They signify delta's, which I don't + * care about + */ + signals &= 0xf0; + + spin_lock_irqsave(&ch->ch_lock, flags); + if (msignals & UART_MSR_DCD) + ch->ch_mistat |= UART_MSR_DCD; + else + ch->ch_mistat &= ~UART_MSR_DCD; + + if (msignals & UART_MSR_DSR) + ch->ch_mistat |= UART_MSR_DSR; + else + ch->ch_mistat &= ~UART_MSR_DSR; + + if (msignals & UART_MSR_RI) + ch->ch_mistat |= UART_MSR_RI; + else + ch->ch_mistat &= ~UART_MSR_RI; + + if (msignals & UART_MSR_CTS) + ch->ch_mistat |= UART_MSR_CTS; + else + ch->ch_mistat &= ~UART_MSR_CTS; + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + +/* Make the UART raise any of the output signals we want up */ +static void cls_assert_modem_signals(struct channel_t *ch) +{ + unsigned char out; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + out = ch->ch_mostat; + + if (ch->ch_flags & CH_LOOPBACK) + out |= UART_MCR_LOOP; + + writeb(out, &ch->ch_cls_uart->mcr); + + /* Give time for the UART to actually drop the signals */ + udelay(10); +} + +static void cls_send_start_character(struct channel_t *ch) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + if (ch->ch_startc != _POSIX_VDISABLE) { + ch->ch_xon_sends++; + writeb(ch->ch_startc, &ch->ch_cls_uart->txrx); + } +} + +static void cls_send_stop_character(struct channel_t *ch) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + if (ch->ch_stopc != _POSIX_VDISABLE) { + ch->ch_xoff_sends++; + writeb(ch->ch_stopc, &ch->ch_cls_uart->txrx); + } +} + +/* Inits UART */ +static void cls_uart_init(struct channel_t *ch) +{ + unsigned char lcrb = readb(&ch->ch_cls_uart->lcr); + unsigned char isr_fcr = 0; + + writeb(0, &ch->ch_cls_uart->ier); + + /* + * The Enhanced Register Set may only be accessed when + * the Line Control Register is set to 0xBFh. + */ + writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr); + + isr_fcr = readb(&ch->ch_cls_uart->isr_fcr); + + /* Turn on Enhanced/Extended controls */ + isr_fcr |= (UART_EXAR654_EFR_ECB); + + writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr); + + /* Write old LCR value back out, which turns enhanced access off */ + writeb(lcrb, &ch->ch_cls_uart->lcr); + + /* Clear out UART and FIFO */ + readb(&ch->ch_cls_uart->txrx); + + writeb((UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT), + &ch->ch_cls_uart->isr_fcr); + udelay(10); + + ch->ch_flags |= (CH_FIFO_ENABLED | CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + + readb(&ch->ch_cls_uart->lsr); + readb(&ch->ch_cls_uart->msr); +} + +/* + * Turns off UART. + */ +static void cls_uart_off(struct channel_t *ch) +{ + writeb(0, &ch->ch_cls_uart->ier); +} + +/* + * cls_get_uarts_bytes_left. + * Returns 0 is nothing left in the FIFO, returns 1 otherwise. + * + * The channel lock MUST be held by the calling function. + */ +static uint cls_get_uart_bytes_left(struct channel_t *ch) +{ + unsigned char left = 0; + unsigned char lsr = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + lsr = readb(&ch->ch_cls_uart->lsr); + + /* Determine whether the Transmitter is empty or not */ + if (!(lsr & UART_LSR_TEMT)) { + if (ch->ch_flags & CH_TX_FIFO_EMPTY) + tasklet_schedule(&ch->ch_bd->helper_tasklet); + left = 1; + } else { + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + left = 0; + } + + return left; +} + +/* + * cls_send_break. + * Starts sending a break thru the UART. + * + * The channel lock MUST be held by the calling function. + */ +static void cls_send_break(struct channel_t *ch, int msecs) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + /* + * If we receive a time of 0, this means turn off the break. + */ + if (msecs == 0) { + /* Turn break off, and unset some variables */ + if (ch->ch_flags & CH_BREAK_SENDING) { + unsigned char temp = readb(&ch->ch_cls_uart->lcr); + + writeb((temp & ~UART_LCR_SBC), &ch->ch_cls_uart->lcr); + ch->ch_flags &= ~(CH_BREAK_SENDING); + ch->ch_stop_sending_break = 0; + } + return; + } + + /* + * Set the time we should stop sending the break. + * If we are already sending a break, toss away the existing + * time to stop, and use this new value instead. + */ + ch->ch_stop_sending_break = jiffies + dgnc_jiffies_from_ms(msecs); + + /* Tell the UART to start sending the break */ + if (!(ch->ch_flags & CH_BREAK_SENDING)) { + unsigned char temp = readb(&ch->ch_cls_uart->lcr); + + writeb((temp | UART_LCR_SBC), &ch->ch_cls_uart->lcr); + ch->ch_flags |= (CH_BREAK_SENDING); + } +} + +/* + * cls_send_immediate_char. + * Sends a specific character as soon as possible to the UART, + * jumping over any bytes that might be in the write queue. + * + * The channel lock MUST be held by the calling function. + */ +static void cls_send_immediate_char(struct channel_t *ch, unsigned char c) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + writeb(c, &ch->ch_cls_uart->txrx); +} + +static void cls_vpd(struct dgnc_board *brd) +{ + ulong vpdbase; /* Start of io base of the card */ + u8 __iomem *re_map_vpdbase;/* Remapped memory of the card */ + int i = 0; + + vpdbase = pci_resource_start(brd->pdev, 3); + + /* No VPD */ + if (!vpdbase) + return; + + re_map_vpdbase = ioremap(vpdbase, 0x400); + + if (!re_map_vpdbase) + return; + + /* Store the VPD into our buffer */ + for (i = 0; i < 0x40; i++) { + brd->vpd[i] = readb(re_map_vpdbase + i); + pr_info("%x ", brd->vpd[i]); + } + pr_info("\n"); + + if (re_map_vpdbase) + iounmap(re_map_vpdbase); +} diff --git a/drivers/staging/dgnc/dgnc_cls.h b/drivers/staging/dgnc/dgnc_cls.h new file mode 100644 index 000000000..2597e36d3 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_cls.h @@ -0,0 +1,82 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DGNC_CLS_H +#define __DGNC_CLS_H + +/************************************************************************ + * Per channel/port Classic UART structure * + ************************************************************************ + * Base Structure Entries Usage Meanings to Host * + * * + * W = read write R = read only * + * U = Unused. * + ************************************************************************/ + +/* + * txrx : WR RHR/THR - Holding reg + * ier : WR IER - Interrupt Enable Reg + * isr_fcr : WR ISR/FCR - Interrupt Status Reg/Fifo Control Reg + * lcr : WR LCR - Line Control Reg + * mcr : WR MCR - Modem Control Reg + * lsr : WR LSR - Line Status Reg + * msr : WR MSG - Modem Status Reg + * spr : WR SPR - Scratch pad Reg + */ +struct cls_uart_struct { + u8 txrx; + u8 ier; + u8 isr_fcr; + u8 lcr; + u8 mcr; + u8 lsr; + u8 msr; + u8 spr; +}; + +/* Where to read the interrupt register (8bits) */ +#define UART_CLASSIC_POLL_ADDR_OFFSET 0x40 + +#define UART_EXAR654_ENHANCED_REGISTER_SET 0xBF + +#define UART_16654_FCR_TXTRIGGER_16 0x10 +#define UART_16654_FCR_RXTRIGGER_16 0x40 +#define UART_16654_FCR_RXTRIGGER_56 0x80 + +/* Received CTS/RTS change of state */ +#define UART_IIR_CTSRTS 0x20 + +/* Receiver data TIMEOUT */ +#define UART_IIR_RDI_TIMEOUT 0x0C + +/* + * These are the EXTENDED definitions for the Exar 654's Interrupt + * Enable Register. + */ +#define UART_EXAR654_EFR_ECB 0x10 /* Enhanced control bit */ +#define UART_EXAR654_EFR_IXON 0x2 /* Receiver compares Xon1/Xoff1 */ +#define UART_EXAR654_EFR_IXOFF 0x8 /* Transmit Xon1/Xoff1 */ +#define UART_EXAR654_EFR_RTSDTR 0x40 /* Auto RTS/DTR Flow Control Enable */ +#define UART_EXAR654_EFR_CTSDSR 0x80 /* Auto CTS/DSR Flow COntrol Enable */ +#define UART_EXAR654_IER_XOFF 0x20 /* Xoff Interrupt Enable */ +#define UART_EXAR654_IER_RTSDTR 0x40 /* Output Interrupt Enable */ +#define UART_EXAR654_IER_CTSDSR 0x80 /* Input Interrupt Enable */ + +/* + * Our Global Variables + */ +extern struct board_ops dgnc_cls_ops; + +#endif diff --git a/drivers/staging/dgnc/dgnc_driver.c b/drivers/staging/dgnc/dgnc_driver.c new file mode 100644 index 000000000..805dc617e --- /dev/null +++ b/drivers/staging/dgnc/dgnc_driver.c @@ -0,0 +1,729 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include "dgnc_driver.h" +#include "dgnc_pci.h" +#include "dgnc_mgmt.h" +#include "dgnc_tty.h" +#include "dgnc_cls.h" +#include "dgnc_neo.h" +#include "dgnc_sysfs.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Digi International, http://www.digi.com"); +MODULE_DESCRIPTION("Driver for the Digi International Neo and Classic PCI based product line"); +MODULE_SUPPORTED_DEVICE("dgnc"); + +/************************************************************************** + * + * protos for this file + * + */ +static int dgnc_start(void); +static int dgnc_finalize_board_init(struct dgnc_board *brd); +static void dgnc_init_globals(void); +static int dgnc_found_board(struct pci_dev *pdev, int id); +static void dgnc_cleanup_board(struct dgnc_board *brd); +static void dgnc_poll_handler(ulong dummy); +static int dgnc_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent); +static void dgnc_do_remap(struct dgnc_board *brd); + +/* + * File operations permitted on Control/Management major. + */ +static const struct file_operations dgnc_BoardFops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dgnc_mgmt_ioctl, + .open = dgnc_mgmt_open, + .release = dgnc_mgmt_close +}; + + +/* + * Globals + */ +uint dgnc_NumBoards; +struct dgnc_board *dgnc_Board[MAXBOARDS]; +DEFINE_SPINLOCK(dgnc_global_lock); +uint dgnc_Major; +int dgnc_poll_tick = 20; /* Poll interval - 20 ms */ + +/* + * Static vars. + */ +static struct class *dgnc_class; + +/* + * Poller stuff + */ +static DEFINE_SPINLOCK(dgnc_poll_lock); /* Poll scheduling lock */ +static ulong dgnc_poll_time; /* Time of next poll */ +static uint dgnc_poll_stop; /* Used to tell poller to stop */ +static struct timer_list dgnc_poll_timer; + + +static const struct pci_device_id dgnc_pci_tbl[] = { + {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_4_DID), .driver_data = 0}, + {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_4_422_DID), .driver_data = 1}, + {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_8_DID), .driver_data = 2}, + {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_8_422_DID), .driver_data = 3}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, dgnc_pci_tbl); + +struct board_id { + unsigned char *name; + uint maxports; + unsigned int is_pci_express; +}; + +static struct board_id dgnc_Ids[] = { + { PCI_DEVICE_CLASSIC_4_PCI_NAME, 4, 0 }, + { PCI_DEVICE_CLASSIC_4_422_PCI_NAME, 4, 0 }, + { PCI_DEVICE_CLASSIC_8_PCI_NAME, 8, 0 }, + { PCI_DEVICE_CLASSIC_8_422_PCI_NAME, 8, 0 }, + { PCI_DEVICE_NEO_4_PCI_NAME, 4, 0 }, + { PCI_DEVICE_NEO_8_PCI_NAME, 8, 0 }, + { PCI_DEVICE_NEO_2DB9_PCI_NAME, 2, 0 }, + { PCI_DEVICE_NEO_2DB9PRI_PCI_NAME, 2, 0 }, + { PCI_DEVICE_NEO_2RJ45_PCI_NAME, 2, 0 }, + { PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME, 2, 0 }, + { PCI_DEVICE_NEO_1_422_PCI_NAME, 1, 0 }, + { PCI_DEVICE_NEO_1_422_485_PCI_NAME, 1, 0 }, + { PCI_DEVICE_NEO_2_422_485_PCI_NAME, 2, 0 }, + { PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME, 8, 1 }, + { PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME, 4, 1 }, + { PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME, 4, 1 }, + { PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME, 8, 1 }, + { NULL, 0, 0 } +}; + +static struct pci_driver dgnc_driver = { + .name = "dgnc", + .probe = dgnc_init_one, + .id_table = dgnc_pci_tbl, +}; + +/************************************************************************ + * + * Driver load/unload functions + * + ************************************************************************/ + +/* + * dgnc_cleanup_module() + * + * Module unload. This is where it all ends. + */ +static void dgnc_cleanup_module(void) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&dgnc_poll_lock, flags); + dgnc_poll_stop = 1; + spin_unlock_irqrestore(&dgnc_poll_lock, flags); + + /* Turn off poller right away. */ + del_timer_sync(&dgnc_poll_timer); + + dgnc_remove_driver_sysfiles(&dgnc_driver); + + device_destroy(dgnc_class, MKDEV(dgnc_Major, 0)); + class_destroy(dgnc_class); + unregister_chrdev(dgnc_Major, "dgnc"); + + for (i = 0; i < dgnc_NumBoards; ++i) { + dgnc_remove_ports_sysfiles(dgnc_Board[i]); + dgnc_tty_uninit(dgnc_Board[i]); + dgnc_cleanup_board(dgnc_Board[i]); + } + + dgnc_tty_post_uninit(); + + if (dgnc_NumBoards) + pci_unregister_driver(&dgnc_driver); +} + +/* + * init_module() + * + * Module load. This is where it all starts. + */ +static int __init dgnc_init_module(void) +{ + int rc = 0; + + /* + * Initialize global stuff + */ + rc = dgnc_start(); + + if (rc < 0) + return rc; + + /* + * Find and configure all the cards + */ + rc = pci_register_driver(&dgnc_driver); + + /* + * If something went wrong in the scan, bail out of driver. + */ + if (rc < 0) { + /* Only unregister if it was actually registered. */ + if (dgnc_NumBoards) + pci_unregister_driver(&dgnc_driver); + else + pr_warn("WARNING: dgnc driver load failed. No Digi Neo or Classic boards found.\n"); + + dgnc_cleanup_module(); + } else { + dgnc_create_driver_sysfiles(&dgnc_driver); + } + + return rc; +} + +module_init(dgnc_init_module); +module_exit(dgnc_cleanup_module); + +/* + * Start of driver. + */ +static int dgnc_start(void) +{ + int rc = 0; + unsigned long flags; + struct device *dev; + + /* make sure that the globals are init'd before we do anything else */ + dgnc_init_globals(); + + /* + * Register our base character device into the kernel. + * This allows the download daemon to connect to the downld device + * before any of the boards are init'ed. + * + * Register management/dpa devices + */ + rc = register_chrdev(0, "dgnc", &dgnc_BoardFops); + if (rc < 0) { + pr_err(DRVSTR ": Can't register dgnc driver device (%d)\n", rc); + return rc; + } + dgnc_Major = rc; + + dgnc_class = class_create(THIS_MODULE, "dgnc_mgmt"); + if (IS_ERR(dgnc_class)) { + rc = PTR_ERR(dgnc_class); + pr_err(DRVSTR ": Can't create dgnc_mgmt class (%d)\n", rc); + goto failed_class; + } + + dev = device_create(dgnc_class, NULL, + MKDEV(dgnc_Major, 0), + NULL, "dgnc_mgmt"); + if (IS_ERR(dev)) { + rc = PTR_ERR(dev); + pr_err(DRVSTR ": Can't create device (%d)\n", rc); + goto failed_device; + } + + /* + * Init any global tty stuff. + */ + rc = dgnc_tty_preinit(); + + if (rc < 0) { + pr_err(DRVSTR ": tty preinit - not enough memory (%d)\n", rc); + goto failed_tty; + } + + /* Start the poller */ + spin_lock_irqsave(&dgnc_poll_lock, flags); + setup_timer(&dgnc_poll_timer, dgnc_poll_handler, 0); + dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick); + dgnc_poll_timer.expires = dgnc_poll_time; + spin_unlock_irqrestore(&dgnc_poll_lock, flags); + + add_timer(&dgnc_poll_timer); + + return 0; + +failed_tty: + device_destroy(dgnc_class, MKDEV(dgnc_Major, 0)); +failed_device: + class_destroy(dgnc_class); +failed_class: + unregister_chrdev(dgnc_Major, "dgnc"); + return rc; +} + +/* returns count (>= 0), or negative on error */ +static int dgnc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc; + + /* wake up and enable device */ + rc = pci_enable_device(pdev); + + if (rc < 0) { + rc = -EIO; + } else { + rc = dgnc_found_board(pdev, ent->driver_data); + if (rc == 0) + dgnc_NumBoards++; + } + return rc; +} + +/* + * dgnc_cleanup_board() + * + * Free all the memory associated with a board + */ +static void dgnc_cleanup_board(struct dgnc_board *brd) +{ + int i = 0; + + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return; + + switch (brd->device) { + case PCI_DEVICE_CLASSIC_4_DID: + case PCI_DEVICE_CLASSIC_8_DID: + case PCI_DEVICE_CLASSIC_4_422_DID: + case PCI_DEVICE_CLASSIC_8_422_DID: + + /* Tell card not to interrupt anymore. */ + outb(0, brd->iobase + 0x4c); + break; + + default: + break; + } + + if (brd->irq) + free_irq(brd->irq, brd); + + tasklet_kill(&brd->helper_tasklet); + + if (brd->re_map_membase) { + iounmap(brd->re_map_membase); + brd->re_map_membase = NULL; + } + + if (brd->msgbuf_head) { + unsigned long flags; + + spin_lock_irqsave(&dgnc_global_lock, flags); + brd->msgbuf = NULL; + dev_dbg(&brd->pdev->dev, "%s\n", brd->msgbuf_head); + kfree(brd->msgbuf_head); + brd->msgbuf_head = NULL; + spin_unlock_irqrestore(&dgnc_global_lock, flags); + } + + /* Free all allocated channels structs */ + for (i = 0; i < MAXPORTS ; i++) { + if (brd->channels[i]) { + kfree(brd->channels[i]->ch_rqueue); + kfree(brd->channels[i]->ch_equeue); + kfree(brd->channels[i]->ch_wqueue); + kfree(brd->channels[i]); + brd->channels[i] = NULL; + } + } + + kfree(brd->flipbuf); + + dgnc_Board[brd->boardnum] = NULL; + + kfree(brd); +} + + +/* + * dgnc_found_board() + * + * A board has been found, init it. + */ +static int dgnc_found_board(struct pci_dev *pdev, int id) +{ + struct dgnc_board *brd; + unsigned int pci_irq; + int i = 0; + int rc = 0; + unsigned long flags; + + /* get the board structure and prep it */ + dgnc_Board[dgnc_NumBoards] = kzalloc(sizeof(*brd), GFP_KERNEL); + brd = dgnc_Board[dgnc_NumBoards]; + + if (!brd) + return -ENOMEM; + + /* make a temporary message buffer for the boot messages */ + brd->msgbuf_head = kcalloc(8192, sizeof(u8), GFP_KERNEL); + brd->msgbuf = brd->msgbuf_head; + + if (!brd->msgbuf) { + kfree(brd); + return -ENOMEM; + } + + /* store the info for the board we've found */ + brd->magic = DGNC_BOARD_MAGIC; + brd->boardnum = dgnc_NumBoards; + brd->vendor = dgnc_pci_tbl[id].vendor; + brd->device = dgnc_pci_tbl[id].device; + brd->pdev = pdev; + brd->pci_bus = pdev->bus->number; + brd->pci_slot = PCI_SLOT(pdev->devfn); + brd->name = dgnc_Ids[id].name; + brd->maxports = dgnc_Ids[id].maxports; + if (dgnc_Ids[i].is_pci_express) + brd->bd_flags |= BD_IS_PCI_EXPRESS; + brd->dpastatus = BD_NOFEP; + init_waitqueue_head(&brd->state_wait); + + spin_lock_init(&brd->bd_lock); + spin_lock_init(&brd->bd_intr_lock); + + brd->state = BOARD_FOUND; + + for (i = 0; i < MAXPORTS; i++) + brd->channels[i] = NULL; + + /* store which card & revision we have */ + pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor); + pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice); + pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev); + + pci_irq = pdev->irq; + brd->irq = pci_irq; + + + switch (brd->device) { + + case PCI_DEVICE_CLASSIC_4_DID: + case PCI_DEVICE_CLASSIC_8_DID: + case PCI_DEVICE_CLASSIC_4_422_DID: + case PCI_DEVICE_CLASSIC_8_422_DID: + + brd->dpatype = T_CLASSIC | T_PCIBUS; + + /* + * For PCI ClassicBoards + * PCI Local Address (i.e. "resource" number) space + * 0 PLX Memory Mapped Config + * 1 PLX I/O Mapped Config + * 2 I/O Mapped UARTs and Status + * 3 Memory Mapped VPD + * 4 Memory Mapped UARTs and Status + */ + + + /* get the PCI Base Address Registers */ + brd->membase = pci_resource_start(pdev, 4); + + if (!brd->membase) { + dev_err(&brd->pdev->dev, + "Card has no PCI IO resources, failing.\n"); + return -ENODEV; + } + + brd->membase_end = pci_resource_end(pdev, 4); + + if (brd->membase & 1) + brd->membase &= ~3; + else + brd->membase &= ~15; + + brd->iobase = pci_resource_start(pdev, 1); + brd->iobase_end = pci_resource_end(pdev, 1); + brd->iobase = ((unsigned int) (brd->iobase)) & 0xFFFE; + + /* Assign the board_ops struct */ + brd->bd_ops = &dgnc_cls_ops; + + brd->bd_uart_offset = 0x8; + brd->bd_dividend = 921600; + + dgnc_do_remap(brd); + + /* Get and store the board VPD, if it exists */ + brd->bd_ops->vpd(brd); + + /* + * Enable Local Interrupt 1 (0x1), + * Local Interrupt 1 Polarity Active high (0x2), + * Enable PCI interrupt (0x40) + */ + outb(0x43, brd->iobase + 0x4c); + + break; + + + case PCI_DEVICE_NEO_4_DID: + case PCI_DEVICE_NEO_8_DID: + case PCI_DEVICE_NEO_2DB9_DID: + case PCI_DEVICE_NEO_2DB9PRI_DID: + case PCI_DEVICE_NEO_2RJ45_DID: + case PCI_DEVICE_NEO_2RJ45PRI_DID: + case PCI_DEVICE_NEO_1_422_DID: + case PCI_DEVICE_NEO_1_422_485_DID: + case PCI_DEVICE_NEO_2_422_485_DID: + case PCI_DEVICE_NEO_EXPRESS_8_DID: + case PCI_DEVICE_NEO_EXPRESS_4_DID: + case PCI_DEVICE_NEO_EXPRESS_4RJ45_DID: + case PCI_DEVICE_NEO_EXPRESS_8RJ45_DID: + + /* + * This chip is set up 100% when we get to it. + * No need to enable global interrupts or anything. + */ + if (brd->bd_flags & BD_IS_PCI_EXPRESS) + brd->dpatype = T_NEO_EXPRESS | T_PCIBUS; + else + brd->dpatype = T_NEO | T_PCIBUS; + + /* get the PCI Base Address Registers */ + brd->membase = pci_resource_start(pdev, 0); + brd->membase_end = pci_resource_end(pdev, 0); + + if (brd->membase & 1) + brd->membase &= ~3; + else + brd->membase &= ~15; + + /* Assign the board_ops struct */ + brd->bd_ops = &dgnc_neo_ops; + + brd->bd_uart_offset = 0x200; + brd->bd_dividend = 921600; + + dgnc_do_remap(brd); + + if (brd->re_map_membase) { + + /* Read and store the dvid after remapping */ + brd->dvid = readb(brd->re_map_membase + 0x8D); + + /* Get and store the board VPD, if it exists */ + brd->bd_ops->vpd(brd); + } + break; + + default: + dev_err(&brd->pdev->dev, + "Didn't find any compatible Neo/Classic PCI boards.\n"); + return -ENXIO; + + } + + /* + * Do tty device initialization. + */ + + rc = dgnc_tty_register(brd); + if (rc < 0) { + pr_err(DRVSTR ": Can't register tty devices (%d)\n", rc); + goto failed; + } + + rc = dgnc_finalize_board_init(brd); + if (rc < 0) { + pr_err(DRVSTR ": Can't finalize board init (%d)\n", rc); + goto failed; + } + + rc = dgnc_tty_init(brd); + if (rc < 0) { + pr_err(DRVSTR ": Can't init tty devices (%d)\n", rc); + goto failed; + } + + brd->state = BOARD_READY; + brd->dpastatus = BD_RUNNING; + + dgnc_create_ports_sysfiles(brd); + + /* init our poll helper tasklet */ + tasklet_init(&brd->helper_tasklet, + brd->bd_ops->tasklet, + (unsigned long) brd); + + spin_lock_irqsave(&dgnc_global_lock, flags); + brd->msgbuf = NULL; + dev_dbg(&brd->pdev->dev, "%s\n", brd->msgbuf_head); + kfree(brd->msgbuf_head); + brd->msgbuf_head = NULL; + spin_unlock_irqrestore(&dgnc_global_lock, flags); + + /* + * allocate flip buffer for board. + * + * Okay to malloc with GFP_KERNEL, we are not at interrupt + * context, and there are no locks held. + */ + brd->flipbuf = kzalloc(MYFLIPLEN, GFP_KERNEL); + + wake_up_interruptible(&brd->state_wait); + + return 0; + +failed: + dgnc_tty_uninit(brd); + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + + return -ENXIO; + +} + + +static int dgnc_finalize_board_init(struct dgnc_board *brd) +{ + int rc = 0; + + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return -ENODEV; + + if (brd->irq) { + rc = request_irq(brd->irq, brd->bd_ops->intr, + IRQF_SHARED, "DGNC", brd); + + if (rc) { + dev_err(&brd->pdev->dev, + "Failed to hook IRQ %d\n", brd->irq); + brd->state = BOARD_FAILED; + brd->dpastatus = BD_NOFEP; + rc = -ENODEV; + } + } + return rc; +} + +/* + * Remap PCI memory. + */ +static void dgnc_do_remap(struct dgnc_board *brd) +{ + + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return; + + brd->re_map_membase = ioremap(brd->membase, 0x1000); +} + + +/***************************************************************************** +* +* Function: +* +* dgnc_poll_handler +* +* Author: +* +* Scott H Kilau +* +* Parameters: +* +* dummy -- ignored +* +* Return Values: +* +* none +* +* Description: +* +* As each timer expires, it determines (a) whether the "transmit" +* waiter needs to be woken up, and (b) whether the poller needs to +* be rescheduled. +* +******************************************************************************/ + +static void dgnc_poll_handler(ulong dummy) +{ + struct dgnc_board *brd; + unsigned long flags; + int i; + unsigned long new_time; + + /* Go thru each board, kicking off a tasklet for each if needed */ + for (i = 0; i < dgnc_NumBoards; i++) { + brd = dgnc_Board[i]; + + spin_lock_irqsave(&brd->bd_lock, flags); + + /* If board is in a failed state don't schedule a tasklet */ + if (brd->state == BOARD_FAILED) { + spin_unlock_irqrestore(&brd->bd_lock, flags); + continue; + } + + /* Schedule a poll helper task */ + tasklet_schedule(&brd->helper_tasklet); + + spin_unlock_irqrestore(&brd->bd_lock, flags); + } + + /* + * Schedule ourself back at the nominal wakeup interval. + */ + spin_lock_irqsave(&dgnc_poll_lock, flags); + dgnc_poll_time += dgnc_jiffies_from_ms(dgnc_poll_tick); + + new_time = dgnc_poll_time - jiffies; + + if ((ulong) new_time >= 2 * dgnc_poll_tick) + dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick); + + setup_timer(&dgnc_poll_timer, dgnc_poll_handler, 0); + dgnc_poll_timer.expires = dgnc_poll_time; + spin_unlock_irqrestore(&dgnc_poll_lock, flags); + + if (!dgnc_poll_stop) + add_timer(&dgnc_poll_timer); +} + +/* + * dgnc_init_globals() + * + * This is where we initialize the globals from the static insmod + * configuration variables. These are declared near the head of + * this file. + */ +static void dgnc_init_globals(void) +{ + int i = 0; + + dgnc_NumBoards = 0; + + for (i = 0; i < MAXBOARDS; i++) + dgnc_Board[i] = NULL; + + init_timer(&dgnc_poll_timer); +} + diff --git a/drivers/staging/dgnc/dgnc_driver.h b/drivers/staging/dgnc/dgnc_driver.h new file mode 100644 index 000000000..f77fed57b --- /dev/null +++ b/drivers/staging/dgnc/dgnc_driver.h @@ -0,0 +1,400 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + ************************************************************************* + * + * Driver includes + * + *************************************************************************/ + +#ifndef __DGNC_DRIVER_H +#define __DGNC_DRIVER_H + +#include /* To pick up the varions Linux types */ +#include /* To pick up the various tty structs/defines */ +#include /* For irqreturn_t type */ + +#include "digi.h" /* Digi specific ioctl header */ +#include "dgnc_sysfs.h" /* Support for SYSFS */ + +/************************************************************************* + * + * Driver defines + * + *************************************************************************/ + +/* Driver identification and error statments */ +#define PROCSTR "dgnc" /* /proc entries */ +#define DEVSTR "/dev/dg/dgnc" /* /dev entries */ +#define DRVSTR "dgnc" /* Driver name string */ +#define DG_PART "40002369_F" /* RPM part number */ + +#define TRC_TO_CONSOLE 1 + +/* Number of boards we support at once. */ +#define MAXBOARDS 20 +#define MAXPORTS 8 +#define MAXTTYNAMELEN 200 + +/* Our 3 magic numbers for our board, channel and unit structs */ +#define DGNC_BOARD_MAGIC 0x5c6df104 +#define DGNC_CHANNEL_MAGIC 0x6c6df104 +#define DGNC_UNIT_MAGIC 0x7c6df104 + +/* Serial port types */ +#define DGNC_SERIAL 0 +#define DGNC_PRINT 1 + +#define SERIAL_TYPE_NORMAL 1 + +#define PORT_NUM(dev) ((dev) & 0x7f) +#define IS_PRINT(dev) (((dev) & 0xff) >= 0x80) + +/* MAX number of stop characters we will send when our read queue is getting full */ +#define MAX_STOPS_SENT 5 + +/* 4 extra for alignment play space */ +#define WRITEBUFLEN ((4096) + 4) +#define MYFLIPLEN N_TTY_BUF_SIZE + +#define dgnc_jiffies_from_ms(a) (((a) * HZ) / 1000) + +/* + * Define a local default termios struct. All ports will be created + * with this termios initially. This is the same structure that is defined + * as the default in tty_io.c with the same settings overriden as in serial.c + * + * In short, this should match the internal serial ports' defaults. + */ +#define DEFAULT_IFLAGS (ICRNL | IXON) +#define DEFAULT_OFLAGS (OPOST | ONLCR) +#define DEFAULT_CFLAGS (B9600 | CS8 | CREAD | HUPCL | CLOCAL) +#define DEFAULT_LFLAGS (ISIG | ICANON | ECHO | ECHOE | ECHOK | \ + ECHOCTL | ECHOKE | IEXTEN) + +#ifndef _POSIX_VDISABLE +#define _POSIX_VDISABLE '\0' +#endif + + +/* + * All the possible states the driver can be while being loaded. + */ +enum { + DRIVER_INITIALIZED = 0, + DRIVER_READY +}; + +/* + * All the possible states the board can be while booting up. + */ +enum { + BOARD_FAILED = 0, + BOARD_FOUND, + BOARD_READY +}; + + +/************************************************************************* + * + * Structures and closely related defines. + * + *************************************************************************/ + +struct dgnc_board; +struct channel_t; + +/************************************************************************ + * Per board operations structure * + ************************************************************************/ +struct board_ops { + void (*tasklet)(unsigned long data); + irqreturn_t (*intr)(int irq, void *voidbrd); + void (*uart_init)(struct channel_t *ch); + void (*uart_off)(struct channel_t *ch); + int (*drain)(struct tty_struct *tty, uint seconds); + void (*param)(struct tty_struct *tty); + void (*vpd)(struct dgnc_board *brd); + void (*assert_modem_signals)(struct channel_t *ch); + void (*flush_uart_write)(struct channel_t *ch); + void (*flush_uart_read)(struct channel_t *ch); + void (*disable_receiver)(struct channel_t *ch); + void (*enable_receiver)(struct channel_t *ch); + void (*send_break)(struct channel_t *ch, int); + void (*send_start_character)(struct channel_t *ch); + void (*send_stop_character)(struct channel_t *ch); + void (*copy_data_from_queue_to_uart)(struct channel_t *ch); + uint (*get_uart_bytes_left)(struct channel_t *ch); + void (*send_immediate_char)(struct channel_t *ch, unsigned char); +}; + +/************************************************************************ + * Device flag definitions for bd_flags. + ************************************************************************/ +#define BD_IS_PCI_EXPRESS 0x0001 /* Is a PCI Express board */ + + +/* + * Per-board information + */ +struct dgnc_board { + int magic; /* Board Magic number. */ + int boardnum; /* Board number: 0-32 */ + + int type; /* Type of board */ + char *name; /* Product Name */ + struct pci_dev *pdev; /* Pointer to the pci_dev struct */ + unsigned long bd_flags; /* Board flags */ + u16 vendor; /* PCI vendor ID */ + u16 device; /* PCI device ID */ + u16 subvendor; /* PCI subsystem vendor ID */ + u16 subdevice; /* PCI subsystem device ID */ + unsigned char rev; /* PCI revision ID */ + uint pci_bus; /* PCI bus value */ + uint pci_slot; /* PCI slot value */ + uint maxports; /* MAX ports this board can handle */ + unsigned char dvid; /* Board specific device id */ + unsigned char vpd[128]; /* VPD of board, if found */ + unsigned char serial_num[20]; /* Serial number of board, if found in VPD */ + + spinlock_t bd_lock; /* Used to protect board */ + + spinlock_t bd_intr_lock; /* Used to protect the poller tasklet and + * the interrupt routine from each other. + */ + + uint state; /* State of card. */ + wait_queue_head_t state_wait; /* Place to sleep on for state change */ + + struct tasklet_struct helper_tasklet; /* Poll helper tasklet */ + + uint nasync; /* Number of ports on card */ + + uint irq; /* Interrupt request number */ + ulong intr_count; /* Count of interrupts */ + ulong intr_modem; /* Count of interrupts */ + ulong intr_tx; /* Count of interrupts */ + ulong intr_rx; /* Count of interrupts */ + + ulong membase; /* Start of base memory of the card */ + ulong membase_end; /* End of base memory of the card */ + + u8 __iomem *re_map_membase;/* Remapped memory of the card */ + + ulong iobase; /* Start of io base of the card */ + ulong iobase_end; /* End of io base of the card */ + + uint bd_uart_offset; /* Space between each UART */ + + struct channel_t *channels[MAXPORTS]; /* array of pointers to our channels. */ + + struct tty_driver SerialDriver; + char SerialName[200]; + struct tty_driver PrintDriver; + char PrintName[200]; + + bool dgnc_Major_Serial_Registered; + bool dgnc_Major_TransparentPrint_Registered; + + uint dgnc_Serial_Major; + uint dgnc_TransparentPrint_Major; + + uint TtyRefCnt; + + char *flipbuf; /* Our flip buffer, alloced if board is found */ + + u16 dpatype; /* The board "type", as defined by DPA */ + u16 dpastatus; /* The board "status", as defined by DPA */ + + /* + * Mgmt data. + */ + char *msgbuf_head; + char *msgbuf; + + uint bd_dividend; /* Board/UARTs specific dividend */ + + struct board_ops *bd_ops; + + /* /proc/ entries */ + struct proc_dir_entry *proc_entry_pointer; + struct dgnc_proc_entry *dgnc_board_table; + +}; + + +/************************************************************************ + * Unit flag definitions for un_flags. + ************************************************************************/ +#define UN_ISOPEN 0x0001 /* Device is open */ +#define UN_CLOSING 0x0002 /* Line is being closed */ +#define UN_IMM 0x0004 /* Service immediately */ +#define UN_BUSY 0x0008 /* Some work this channel */ +#define UN_BREAKI 0x0010 /* Input break received */ +#define UN_PWAIT 0x0020 /* Printer waiting for terminal */ +#define UN_TIME 0x0040 /* Waiting on time */ +#define UN_EMPTY 0x0080 /* Waiting output queue empty */ +#define UN_LOW 0x0100 /* Waiting output low water mark*/ +#define UN_EXCL_OPEN 0x0200 /* Open for exclusive use */ +#define UN_WOPEN 0x0400 /* Device waiting for open */ +#define UN_WIOCTL 0x0800 /* Device waiting for open */ +#define UN_HANGUP 0x8000 /* Carrier lost */ + +struct device; + +/************************************************************************ + * Structure for terminal or printer unit. + ************************************************************************/ +struct un_t { + int magic; /* Unit Magic Number. */ + struct channel_t *un_ch; + ulong un_time; + uint un_type; + uint un_open_count; /* Counter of opens to port */ + struct tty_struct *un_tty;/* Pointer to unit tty structure */ + uint un_flags; /* Unit flags */ + wait_queue_head_t un_flags_wait; /* Place to sleep to wait on unit */ + uint un_dev; /* Minor device number */ + struct device *un_sysfs; +}; + + +/************************************************************************ + * Device flag definitions for ch_flags. + ************************************************************************/ +#define CH_PRON 0x0001 /* Printer on string */ +#define CH_STOP 0x0002 /* Output is stopped */ +#define CH_STOPI 0x0004 /* Input is stopped */ +#define CH_CD 0x0008 /* Carrier is present */ +#define CH_FCAR 0x0010 /* Carrier forced on */ +#define CH_HANGUP 0x0020 /* Hangup received */ + +#define CH_RECEIVER_OFF 0x0040 /* Receiver is off */ +#define CH_OPENING 0x0080 /* Port in fragile open state */ +#define CH_CLOSING 0x0100 /* Port in fragile close state */ +#define CH_FIFO_ENABLED 0x0200 /* Port has FIFOs enabled */ +#define CH_TX_FIFO_EMPTY 0x0400 /* TX Fifo is completely empty */ +#define CH_TX_FIFO_LWM 0x0800 /* TX Fifo is below Low Water */ +#define CH_BREAK_SENDING 0x1000 /* Break is being sent */ +#define CH_LOOPBACK 0x2000 /* Channel is in lookback mode */ +#define CH_FLIPBUF_IN_USE 0x4000 /* Channel's flipbuf is in use */ +#define CH_BAUD0 0x08000 /* Used for checking B0 transitions */ +#define CH_FORCED_STOP 0x20000 /* Output is forcibly stopped */ +#define CH_FORCED_STOPI 0x40000 /* Input is forcibly stopped */ + + +/* Our Read/Error/Write queue sizes */ +#define RQUEUEMASK 0x1FFF /* 8 K - 1 */ +#define EQUEUEMASK 0x1FFF /* 8 K - 1 */ +#define WQUEUEMASK 0x0FFF /* 4 K - 1 */ +#define RQUEUESIZE (RQUEUEMASK + 1) +#define EQUEUESIZE RQUEUESIZE +#define WQUEUESIZE (WQUEUEMASK + 1) + + +/************************************************************************ + * Channel information structure. + ************************************************************************/ +struct channel_t { + int magic; /* Channel Magic Number */ + struct dgnc_board *ch_bd; /* Board structure pointer */ + struct digi_t ch_digi; /* Transparent Print structure */ + struct un_t ch_tun; /* Terminal unit info */ + struct un_t ch_pun; /* Printer unit info */ + + spinlock_t ch_lock; /* provide for serialization */ + wait_queue_head_t ch_flags_wait; + + uint ch_portnum; /* Port number, 0 offset. */ + uint ch_open_count; /* open count */ + uint ch_flags; /* Channel flags */ + + ulong ch_close_delay; /* How long we should drop RTS/DTR for */ + + ulong ch_cpstime; /* Time for CPS calculations */ + + tcflag_t ch_c_iflag; /* channel iflags */ + tcflag_t ch_c_cflag; /* channel cflags */ + tcflag_t ch_c_oflag; /* channel oflags */ + tcflag_t ch_c_lflag; /* channel lflags */ + unsigned char ch_stopc; /* Stop character */ + unsigned char ch_startc; /* Start character */ + + uint ch_old_baud; /* Cache of the current baud */ + uint ch_custom_speed;/* Custom baud, if set */ + + uint ch_wopen; /* Waiting for open process cnt */ + + unsigned char ch_mostat; /* FEP output modem status */ + unsigned char ch_mistat; /* FEP input modem status */ + + struct neo_uart_struct __iomem *ch_neo_uart; /* Pointer to the "mapped" UART struct */ + struct cls_uart_struct __iomem *ch_cls_uart; /* Pointer to the "mapped" UART struct */ + + unsigned char ch_cached_lsr; /* Cached value of the LSR register */ + + unsigned char *ch_rqueue; /* Our read queue buffer - malloc'ed */ + ushort ch_r_head; /* Head location of the read queue */ + ushort ch_r_tail; /* Tail location of the read queue */ + + unsigned char *ch_equeue; /* Our error queue buffer - malloc'ed */ + ushort ch_e_head; /* Head location of the error queue */ + ushort ch_e_tail; /* Tail location of the error queue */ + + unsigned char *ch_wqueue; /* Our write queue buffer - malloc'ed */ + ushort ch_w_head; /* Head location of the write queue */ + ushort ch_w_tail; /* Tail location of the write queue */ + + ulong ch_rxcount; /* total of data received so far */ + ulong ch_txcount; /* total of data transmitted so far */ + + unsigned char ch_r_tlevel; /* Receive Trigger level */ + unsigned char ch_t_tlevel; /* Transmit Trigger level */ + + unsigned char ch_r_watermark; /* Receive Watermark */ + + ulong ch_stop_sending_break; /* Time we should STOP sending a break */ + + uint ch_stops_sent; /* How many times I have sent a stop character + * to try to stop the other guy sending. + */ + ulong ch_err_parity; /* Count of parity errors on channel */ + ulong ch_err_frame; /* Count of framing errors on channel */ + ulong ch_err_break; /* Count of breaks on channel */ + ulong ch_err_overrun; /* Count of overruns on channel */ + + ulong ch_xon_sends; /* Count of xons transmitted */ + ulong ch_xoff_sends; /* Count of xoffs transmitted */ + + ulong ch_intr_modem; /* Count of interrupts */ + ulong ch_intr_tx; /* Count of interrupts */ + ulong ch_intr_rx; /* Count of interrupts */ + + + /* /proc// entries */ + struct proc_dir_entry *proc_entry_pointer; + struct dgnc_proc_entry *dgnc_channel_table; + +}; + +/* + * Our Global Variables. + */ +extern uint dgnc_Major; /* Our driver/mgmt major */ +extern int dgnc_poll_tick; /* Poll interval - 20 ms */ +extern spinlock_t dgnc_global_lock; /* Driver global spinlock */ +extern uint dgnc_NumBoards; /* Total number of boards */ +extern struct dgnc_board *dgnc_Board[MAXBOARDS]; /* Array of board structs */ + +#endif diff --git a/drivers/staging/dgnc/dgnc_mgmt.c b/drivers/staging/dgnc/dgnc_mgmt.c new file mode 100644 index 000000000..b13318a82 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_mgmt.c @@ -0,0 +1,262 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +/************************************************************************ + * + * This file implements the mgmt functionality for the + * Neo and ClassicBoard based product lines. + * + ************************************************************************ + */ +#include +#include +#include /* For jiffies, task states */ +#include /* For tasklet and interrupt structs/defines */ +#include +#include +#include /* For copy_from_user/copy_to_user */ + +#include "dgnc_driver.h" +#include "dgnc_pci.h" +#include "dgnc_mgmt.h" + + +/* Our "in use" variables, to enforce 1 open only */ +static int dgnc_mgmt_in_use[MAXMGMTDEVICES]; + + +/* + * dgnc_mgmt_open() + * + * Open the mgmt/downld/dpa device + */ +int dgnc_mgmt_open(struct inode *inode, struct file *file) +{ + unsigned long flags; + unsigned int minor = iminor(inode); + + spin_lock_irqsave(&dgnc_global_lock, flags); + + /* mgmt device */ + if (minor < MAXMGMTDEVICES) { + /* Only allow 1 open at a time on mgmt device */ + if (dgnc_mgmt_in_use[minor]) { + spin_unlock_irqrestore(&dgnc_global_lock, flags); + return -EBUSY; + } + dgnc_mgmt_in_use[minor]++; + } else { + spin_unlock_irqrestore(&dgnc_global_lock, flags); + return -ENXIO; + } + + spin_unlock_irqrestore(&dgnc_global_lock, flags); + + return 0; +} + + +/* + * dgnc_mgmt_close() + * + * Open the mgmt/dpa device + */ +int dgnc_mgmt_close(struct inode *inode, struct file *file) +{ + unsigned long flags; + unsigned int minor = iminor(inode); + + spin_lock_irqsave(&dgnc_global_lock, flags); + + /* mgmt device */ + if (minor < MAXMGMTDEVICES) { + if (dgnc_mgmt_in_use[minor]) + dgnc_mgmt_in_use[minor] = 0; + } + spin_unlock_irqrestore(&dgnc_global_lock, flags); + + return 0; +} + + +/* + * dgnc_mgmt_ioctl() + * + * ioctl the mgmt/dpa device + */ + +long dgnc_mgmt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + void __user *uarg = (void __user *) arg; + + switch (cmd) { + + case DIGI_GETDD: + { + /* + * This returns the total number of boards + * in the system, as well as driver version + * and has space for a reserved entry + */ + struct digi_dinfo ddi; + + spin_lock_irqsave(&dgnc_global_lock, flags); + + ddi.dinfo_nboards = dgnc_NumBoards; + sprintf(ddi.dinfo_version, "%s", DG_PART); + + spin_unlock_irqrestore(&dgnc_global_lock, flags); + + if (copy_to_user(uarg, &ddi, sizeof(ddi))) + return -EFAULT; + + break; + } + + case DIGI_GETBD: + { + int brd; + + struct digi_info di; + + if (copy_from_user(&brd, uarg, sizeof(int))) + return -EFAULT; + + if (brd < 0 || brd >= dgnc_NumBoards) + return -ENODEV; + + memset(&di, 0, sizeof(di)); + + di.info_bdnum = brd; + + spin_lock_irqsave(&dgnc_Board[brd]->bd_lock, flags); + + di.info_bdtype = dgnc_Board[brd]->dpatype; + di.info_bdstate = dgnc_Board[brd]->dpastatus; + di.info_ioport = 0; + di.info_physaddr = (ulong) dgnc_Board[brd]->membase; + di.info_physsize = (ulong) dgnc_Board[brd]->membase - dgnc_Board[brd]->membase_end; + if (dgnc_Board[brd]->state != BOARD_FAILED) + di.info_nports = dgnc_Board[brd]->nasync; + else + di.info_nports = 0; + + spin_unlock_irqrestore(&dgnc_Board[brd]->bd_lock, flags); + + if (copy_to_user(uarg, &di, sizeof(di))) + return -EFAULT; + + break; + } + + case DIGI_GET_NI_INFO: + { + struct channel_t *ch; + struct ni_info ni; + unsigned char mstat = 0; + uint board = 0; + uint channel = 0; + + if (copy_from_user(&ni, uarg, sizeof(ni))) + return -EFAULT; + + board = ni.board; + channel = ni.channel; + + /* Verify boundaries on board */ + if (board >= dgnc_NumBoards) + return -ENODEV; + + /* Verify boundaries on channel */ + if (channel >= dgnc_Board[board]->nasync) + return -ENODEV; + + ch = dgnc_Board[board]->channels[channel]; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENODEV; + + memset(&ni, 0, sizeof(ni)); + ni.board = board; + ni.channel = channel; + + spin_lock_irqsave(&ch->ch_lock, flags); + + mstat = (ch->ch_mostat | ch->ch_mistat); + + if (mstat & UART_MCR_DTR) { + ni.mstat |= TIOCM_DTR; + ni.dtr = TIOCM_DTR; + } + if (mstat & UART_MCR_RTS) { + ni.mstat |= TIOCM_RTS; + ni.rts = TIOCM_RTS; + } + if (mstat & UART_MSR_CTS) { + ni.mstat |= TIOCM_CTS; + ni.cts = TIOCM_CTS; + } + if (mstat & UART_MSR_RI) { + ni.mstat |= TIOCM_RI; + ni.ri = TIOCM_RI; + } + if (mstat & UART_MSR_DCD) { + ni.mstat |= TIOCM_CD; + ni.dcd = TIOCM_CD; + } + if (mstat & UART_MSR_DSR) + ni.mstat |= TIOCM_DSR; + + ni.iflag = ch->ch_c_iflag; + ni.oflag = ch->ch_c_oflag; + ni.cflag = ch->ch_c_cflag; + ni.lflag = ch->ch_c_lflag; + + if (ch->ch_digi.digi_flags & CTSPACE || + ch->ch_c_cflag & CRTSCTS) + ni.hflow = 1; + else + ni.hflow = 0; + + if ((ch->ch_flags & CH_STOPI) || + (ch->ch_flags & CH_FORCED_STOPI)) + ni.recv_stopped = 1; + else + ni.recv_stopped = 0; + + if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP)) + ni.xmit_stopped = 1; + else + ni.xmit_stopped = 0; + + ni.curtx = ch->ch_txcount; + ni.currx = ch->ch_rxcount; + + ni.baud = ch->ch_old_baud; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(uarg, &ni, sizeof(ni))) + return -EFAULT; + + break; + } + + + } + + return 0; +} diff --git a/drivers/staging/dgnc/dgnc_mgmt.h b/drivers/staging/dgnc/dgnc_mgmt.h new file mode 100644 index 000000000..708abe959 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_mgmt.h @@ -0,0 +1,25 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DGNC_MGMT_H +#define __DGNC_MGMT_H + +#define MAXMGMTDEVICES 8 + +int dgnc_mgmt_open(struct inode *inode, struct file *file); +int dgnc_mgmt_close(struct inode *inode, struct file *file); +long dgnc_mgmt_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +#endif + diff --git a/drivers/staging/dgnc/dgnc_neo.c b/drivers/staging/dgnc/dgnc_neo.c new file mode 100644 index 000000000..f5a4d3651 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_neo.c @@ -0,0 +1,1846 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + + +#include +#include /* For jiffies, task states */ +#include /* For tasklet and interrupt structs/defines */ +#include /* For udelay */ +#include /* For read[bwl]/write[bwl] */ +#include /* For struct async_serial */ +#include /* For the various UART offsets */ + +#include "dgnc_driver.h" /* Driver main header file */ +#include "dgnc_neo.h" /* Our header file */ +#include "dgnc_tty.h" + +static inline void neo_parse_lsr(struct dgnc_board *brd, uint port); +static inline void neo_parse_isr(struct dgnc_board *brd, uint port); +static void neo_copy_data_from_uart_to_queue(struct channel_t *ch); +static inline void neo_clear_break(struct channel_t *ch, int force); +static inline void neo_set_cts_flow_control(struct channel_t *ch); +static inline void neo_set_rts_flow_control(struct channel_t *ch); +static inline void neo_set_ixon_flow_control(struct channel_t *ch); +static inline void neo_set_ixoff_flow_control(struct channel_t *ch); +static inline void neo_set_no_output_flow_control(struct channel_t *ch); +static inline void neo_set_no_input_flow_control(struct channel_t *ch); +static inline void neo_set_new_start_stop_chars(struct channel_t *ch); +static void neo_parse_modem(struct channel_t *ch, unsigned char signals); +static void neo_tasklet(unsigned long data); +static void neo_vpd(struct dgnc_board *brd); +static void neo_uart_init(struct channel_t *ch); +static void neo_uart_off(struct channel_t *ch); +static int neo_drain(struct tty_struct *tty, uint seconds); +static void neo_param(struct tty_struct *tty); +static void neo_assert_modem_signals(struct channel_t *ch); +static void neo_flush_uart_write(struct channel_t *ch); +static void neo_flush_uart_read(struct channel_t *ch); +static void neo_disable_receiver(struct channel_t *ch); +static void neo_enable_receiver(struct channel_t *ch); +static void neo_send_break(struct channel_t *ch, int msecs); +static void neo_send_start_character(struct channel_t *ch); +static void neo_send_stop_character(struct channel_t *ch); +static void neo_copy_data_from_queue_to_uart(struct channel_t *ch); +static uint neo_get_uart_bytes_left(struct channel_t *ch); +static void neo_send_immediate_char(struct channel_t *ch, unsigned char c); +static irqreturn_t neo_intr(int irq, void *voidbrd); + + +struct board_ops dgnc_neo_ops = { + .tasklet = neo_tasklet, + .intr = neo_intr, + .uart_init = neo_uart_init, + .uart_off = neo_uart_off, + .drain = neo_drain, + .param = neo_param, + .vpd = neo_vpd, + .assert_modem_signals = neo_assert_modem_signals, + .flush_uart_write = neo_flush_uart_write, + .flush_uart_read = neo_flush_uart_read, + .disable_receiver = neo_disable_receiver, + .enable_receiver = neo_enable_receiver, + .send_break = neo_send_break, + .send_start_character = neo_send_start_character, + .send_stop_character = neo_send_stop_character, + .copy_data_from_queue_to_uart = neo_copy_data_from_queue_to_uart, + .get_uart_bytes_left = neo_get_uart_bytes_left, + .send_immediate_char = neo_send_immediate_char +}; + +static uint dgnc_offset_table[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; + + +/* + * This function allows calls to ensure that all outstanding + * PCI writes have been completed, by doing a PCI read against + * a non-destructive, read-only location on the Neo card. + * + * In this case, we are reading the DVID (Read-only Device Identification) + * value of the Neo card. + */ +static inline void neo_pci_posting_flush(struct dgnc_board *bd) +{ + readb(bd->re_map_membase + 0x8D); +} + +static inline void neo_set_cts_flow_control(struct channel_t *ch) +{ + unsigned char ier = readb(&ch->ch_neo_uart->ier); + unsigned char efr = readb(&ch->ch_neo_uart->efr); + + + /* Turn on auto CTS flow control */ +#if 1 + ier |= UART_17158_IER_CTSDSR; +#else + ier &= ~(UART_17158_IER_CTSDSR); +#endif + + efr |= (UART_17158_EFR_ECB | UART_17158_EFR_CTSDSR); + + /* Turn off auto Xon flow control */ + efr &= ~UART_17158_EFR_IXON; + + /* Why? Becuz Exar's spec says we have to zero it out before setting it */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Turn on UART enhanced bits */ + writeb(efr, &ch->ch_neo_uart->efr); + + /* Turn on table D, with 8 char hi/low watermarks */ + writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_4DELAY), &ch->ch_neo_uart->fctr); + + /* Feed the UART our trigger levels */ + writeb(8, &ch->ch_neo_uart->tfifo); + ch->ch_t_tlevel = 8; + + writeb(ier, &ch->ch_neo_uart->ier); + + neo_pci_posting_flush(ch->ch_bd); +} + + +static inline void neo_set_rts_flow_control(struct channel_t *ch) +{ + unsigned char ier = readb(&ch->ch_neo_uart->ier); + unsigned char efr = readb(&ch->ch_neo_uart->efr); + + /* Turn on auto RTS flow control */ +#if 1 + ier |= UART_17158_IER_RTSDTR; +#else + ier &= ~(UART_17158_IER_RTSDTR); +#endif + efr |= (UART_17158_EFR_ECB | UART_17158_EFR_RTSDTR); + + /* Turn off auto Xoff flow control */ + ier &= ~UART_17158_IER_XOFF; + efr &= ~UART_17158_EFR_IXOFF; + + /* Why? Becuz Exar's spec says we have to zero it out before setting it */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Turn on UART enhanced bits */ + writeb(efr, &ch->ch_neo_uart->efr); + + writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_4DELAY), &ch->ch_neo_uart->fctr); + ch->ch_r_watermark = 4; + + writeb(32, &ch->ch_neo_uart->rfifo); + ch->ch_r_tlevel = 32; + + writeb(ier, &ch->ch_neo_uart->ier); + + /* + * From the Neo UART spec sheet: + * The auto RTS/DTR function must be started by asserting + * RTS/DTR# output pin (MCR bit-0 or 1 to logic 1 after + * it is enabled. + */ + ch->ch_mostat |= UART_MCR_RTS; + + neo_pci_posting_flush(ch->ch_bd); +} + + +static inline void neo_set_ixon_flow_control(struct channel_t *ch) +{ + unsigned char ier = readb(&ch->ch_neo_uart->ier); + unsigned char efr = readb(&ch->ch_neo_uart->efr); + + /* Turn off auto CTS flow control */ + ier &= ~UART_17158_IER_CTSDSR; + efr &= ~UART_17158_EFR_CTSDSR; + + /* Turn on auto Xon flow control */ + efr |= (UART_17158_EFR_ECB | UART_17158_EFR_IXON); + + /* Why? Becuz Exar's spec says we have to zero it out before setting it */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Turn on UART enhanced bits */ + writeb(efr, &ch->ch_neo_uart->efr); + + writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr); + ch->ch_r_watermark = 4; + + writeb(32, &ch->ch_neo_uart->rfifo); + ch->ch_r_tlevel = 32; + + /* Tell UART what start/stop chars it should be looking for */ + writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1); + writeb(0, &ch->ch_neo_uart->xonchar2); + + writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1); + writeb(0, &ch->ch_neo_uart->xoffchar2); + + writeb(ier, &ch->ch_neo_uart->ier); + + neo_pci_posting_flush(ch->ch_bd); +} + + +static inline void neo_set_ixoff_flow_control(struct channel_t *ch) +{ + unsigned char ier = readb(&ch->ch_neo_uart->ier); + unsigned char efr = readb(&ch->ch_neo_uart->efr); + + /* Turn off auto RTS flow control */ + ier &= ~UART_17158_IER_RTSDTR; + efr &= ~UART_17158_EFR_RTSDTR; + + /* Turn on auto Xoff flow control */ + ier |= UART_17158_IER_XOFF; + efr |= (UART_17158_EFR_ECB | UART_17158_EFR_IXOFF); + + /* Why? Becuz Exar's spec says we have to zero it out before setting it */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Turn on UART enhanced bits */ + writeb(efr, &ch->ch_neo_uart->efr); + + /* Turn on table D, with 8 char hi/low watermarks */ + writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr); + + writeb(8, &ch->ch_neo_uart->tfifo); + ch->ch_t_tlevel = 8; + + /* Tell UART what start/stop chars it should be looking for */ + writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1); + writeb(0, &ch->ch_neo_uart->xonchar2); + + writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1); + writeb(0, &ch->ch_neo_uart->xoffchar2); + + writeb(ier, &ch->ch_neo_uart->ier); + + neo_pci_posting_flush(ch->ch_bd); +} + + +static inline void neo_set_no_input_flow_control(struct channel_t *ch) +{ + unsigned char ier = readb(&ch->ch_neo_uart->ier); + unsigned char efr = readb(&ch->ch_neo_uart->efr); + + /* Turn off auto RTS flow control */ + ier &= ~UART_17158_IER_RTSDTR; + efr &= ~UART_17158_EFR_RTSDTR; + + /* Turn off auto Xoff flow control */ + ier &= ~UART_17158_IER_XOFF; + if (ch->ch_c_iflag & IXON) + efr &= ~(UART_17158_EFR_IXOFF); + else + efr &= ~(UART_17158_EFR_ECB | UART_17158_EFR_IXOFF); + + + /* Why? Becuz Exar's spec says we have to zero it out before setting it */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Turn on UART enhanced bits */ + writeb(efr, &ch->ch_neo_uart->efr); + + /* Turn on table D, with 8 char hi/low watermarks */ + writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr); + + ch->ch_r_watermark = 0; + + writeb(16, &ch->ch_neo_uart->tfifo); + ch->ch_t_tlevel = 16; + + writeb(16, &ch->ch_neo_uart->rfifo); + ch->ch_r_tlevel = 16; + + writeb(ier, &ch->ch_neo_uart->ier); + + neo_pci_posting_flush(ch->ch_bd); +} + + +static inline void neo_set_no_output_flow_control(struct channel_t *ch) +{ + unsigned char ier = readb(&ch->ch_neo_uart->ier); + unsigned char efr = readb(&ch->ch_neo_uart->efr); + + /* Turn off auto CTS flow control */ + ier &= ~UART_17158_IER_CTSDSR; + efr &= ~UART_17158_EFR_CTSDSR; + + /* Turn off auto Xon flow control */ + if (ch->ch_c_iflag & IXOFF) + efr &= ~UART_17158_EFR_IXON; + else + efr &= ~(UART_17158_EFR_ECB | UART_17158_EFR_IXON); + + /* Why? Becuz Exar's spec says we have to zero it out before setting it */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Turn on UART enhanced bits */ + writeb(efr, &ch->ch_neo_uart->efr); + + /* Turn on table D, with 8 char hi/low watermarks */ + writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr); + + ch->ch_r_watermark = 0; + + writeb(16, &ch->ch_neo_uart->tfifo); + ch->ch_t_tlevel = 16; + + writeb(16, &ch->ch_neo_uart->rfifo); + ch->ch_r_tlevel = 16; + + writeb(ier, &ch->ch_neo_uart->ier); + + neo_pci_posting_flush(ch->ch_bd); +} + + +/* change UARTs start/stop chars */ +static inline void neo_set_new_start_stop_chars(struct channel_t *ch) +{ + + /* if hardware flow control is set, then skip this whole thing */ + if (ch->ch_digi.digi_flags & (CTSPACE | RTSPACE) || ch->ch_c_cflag & CRTSCTS) + return; + + /* Tell UART what start/stop chars it should be looking for */ + writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1); + writeb(0, &ch->ch_neo_uart->xonchar2); + + writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1); + writeb(0, &ch->ch_neo_uart->xoffchar2); + + neo_pci_posting_flush(ch->ch_bd); +} + + +/* + * No locks are assumed to be held when calling this function. + */ +static inline void neo_clear_break(struct channel_t *ch, int force) +{ + unsigned long flags; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Bail if we aren't currently sending a break. */ + if (!ch->ch_stop_sending_break) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return; + } + + /* Turn break off, and unset some variables */ + if (ch->ch_flags & CH_BREAK_SENDING) { + if (time_after_eq(jiffies, ch->ch_stop_sending_break) + || force) { + unsigned char temp = readb(&ch->ch_neo_uart->lcr); + + writeb((temp & ~UART_LCR_SBC), &ch->ch_neo_uart->lcr); + neo_pci_posting_flush(ch->ch_bd); + ch->ch_flags &= ~(CH_BREAK_SENDING); + ch->ch_stop_sending_break = 0; + } + } + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +/* + * Parse the ISR register. + */ +static inline void neo_parse_isr(struct dgnc_board *brd, uint port) +{ + struct channel_t *ch; + unsigned char isr; + unsigned char cause; + unsigned long flags; + + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return; + + if (port >= brd->maxports) + return; + + ch = brd->channels[port]; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + /* Here we try to figure out what caused the interrupt to happen */ + while (1) { + + isr = readb(&ch->ch_neo_uart->isr_fcr); + + /* Bail if no pending interrupt */ + if (isr & UART_IIR_NO_INT) + break; + + /* + * Yank off the upper 2 bits, which just show that the FIFO's are enabled. + */ + isr &= ~(UART_17158_IIR_FIFO_ENABLED); + + if (isr & (UART_17158_IIR_RDI_TIMEOUT | UART_IIR_RDI)) { + /* Read data from uart -> queue */ + brd->intr_rx++; + ch->ch_intr_rx++; + neo_copy_data_from_uart_to_queue(ch); + + /* Call our tty layer to enforce queue flow control if needed. */ + spin_lock_irqsave(&ch->ch_lock, flags); + dgnc_check_queue_flow_control(ch); + spin_unlock_irqrestore(&ch->ch_lock, flags); + } + + if (isr & UART_IIR_THRI) { + brd->intr_tx++; + ch->ch_intr_tx++; + /* Transfer data (if any) from Write Queue -> UART. */ + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + spin_unlock_irqrestore(&ch->ch_lock, flags); + neo_copy_data_from_queue_to_uart(ch); + } + + if (isr & UART_17158_IIR_XONXOFF) { + cause = readb(&ch->ch_neo_uart->xoffchar1); + + /* + * Since the UART detected either an XON or + * XOFF match, we need to figure out which + * one it was, so we can suspend or resume data flow. + */ + if (cause == UART_17158_XON_DETECT) { + /* Is output stopped right now, if so, resume it */ + if (brd->channels[port]->ch_flags & CH_STOP) { + spin_lock_irqsave(&ch->ch_lock, + flags); + ch->ch_flags &= ~(CH_STOP); + spin_unlock_irqrestore(&ch->ch_lock, + flags); + } + } else if (cause == UART_17158_XOFF_DETECT) { + if (!(brd->channels[port]->ch_flags & CH_STOP)) { + spin_lock_irqsave(&ch->ch_lock, + flags); + ch->ch_flags |= CH_STOP; + spin_unlock_irqrestore(&ch->ch_lock, + flags); + } + } + } + + if (isr & UART_17158_IIR_HWFLOW_STATE_CHANGE) { + /* + * If we get here, this means the hardware is doing auto flow control. + * Check to see whether RTS/DTR or CTS/DSR caused this interrupt. + */ + brd->intr_modem++; + ch->ch_intr_modem++; + cause = readb(&ch->ch_neo_uart->mcr); + /* Which pin is doing auto flow? RTS or DTR? */ + if ((cause & 0x4) == 0) { + if (cause & UART_MCR_RTS) { + spin_lock_irqsave(&ch->ch_lock, + flags); + ch->ch_mostat |= UART_MCR_RTS; + spin_unlock_irqrestore(&ch->ch_lock, + flags); + } else { + spin_lock_irqsave(&ch->ch_lock, + flags); + ch->ch_mostat &= ~(UART_MCR_RTS); + spin_unlock_irqrestore(&ch->ch_lock, + flags); + } + } else { + if (cause & UART_MCR_DTR) { + spin_lock_irqsave(&ch->ch_lock, + flags); + ch->ch_mostat |= UART_MCR_DTR; + spin_unlock_irqrestore(&ch->ch_lock, + flags); + } else { + spin_lock_irqsave(&ch->ch_lock, + flags); + ch->ch_mostat &= ~(UART_MCR_DTR); + spin_unlock_irqrestore(&ch->ch_lock, + flags); + } + } + } + + /* Parse any modem signal changes */ + neo_parse_modem(ch, readb(&ch->ch_neo_uart->msr)); + } +} + + +static inline void neo_parse_lsr(struct dgnc_board *brd, uint port) +{ + struct channel_t *ch; + int linestatus; + unsigned long flags; + + /* + * Check to make sure it didn't receive interrupt with a null board + * associated or a board pointer that wasn't ours. + */ + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return; + + if (port >= brd->maxports) + return; + + ch = brd->channels[port]; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + linestatus = readb(&ch->ch_neo_uart->lsr); + + ch->ch_cached_lsr |= linestatus; + + if (ch->ch_cached_lsr & UART_LSR_DR) { + brd->intr_rx++; + ch->ch_intr_rx++; + /* Read data from uart -> queue */ + neo_copy_data_from_uart_to_queue(ch); + spin_lock_irqsave(&ch->ch_lock, flags); + dgnc_check_queue_flow_control(ch); + spin_unlock_irqrestore(&ch->ch_lock, flags); + } + + /* + * The next 3 tests should *NOT* happen, as the above test + * should encapsulate all 3... At least, thats what Exar says. + */ + + if (linestatus & UART_LSR_PE) + ch->ch_err_parity++; + + if (linestatus & UART_LSR_FE) + ch->ch_err_frame++; + + if (linestatus & UART_LSR_BI) + ch->ch_err_break++; + + if (linestatus & UART_LSR_OE) { + /* + * Rx Oruns. Exar says that an orun will NOT corrupt + * the FIFO. It will just replace the holding register + * with this new data byte. So basically just ignore this. + * Probably we should eventually have an orun stat in our driver... + */ + ch->ch_err_overrun++; + } + + if (linestatus & UART_LSR_THRE) { + brd->intr_tx++; + ch->ch_intr_tx++; + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* Transfer data (if any) from Write Queue -> UART. */ + neo_copy_data_from_queue_to_uart(ch); + } else if (linestatus & UART_17158_TX_AND_FIFO_CLR) { + brd->intr_tx++; + ch->ch_intr_tx++; + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* Transfer data (if any) from Write Queue -> UART. */ + neo_copy_data_from_queue_to_uart(ch); + } +} + + +/* + * neo_param() + * Send any/all changes to the line to the UART. + */ +static void neo_param(struct tty_struct *tty) +{ + unsigned char lcr = 0; + unsigned char uart_lcr = 0; + unsigned char ier = 0; + unsigned char uart_ier = 0; + uint baud = 9600; + int quot = 0; + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = (struct un_t *) tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + /* + * If baud rate is zero, flush queues, and set mval to drop DTR. + */ + if ((ch->ch_c_cflag & (CBAUD)) == 0) { + ch->ch_r_head = 0; + ch->ch_r_tail = 0; + ch->ch_e_head = 0; + ch->ch_e_tail = 0; + ch->ch_w_head = 0; + ch->ch_w_tail = 0; + + neo_flush_uart_write(ch); + neo_flush_uart_read(ch); + + /* The baudrate is B0 so all modem lines are to be dropped. */ + ch->ch_flags |= (CH_BAUD0); + ch->ch_mostat &= ~(UART_MCR_RTS | UART_MCR_DTR); + neo_assert_modem_signals(ch); + ch->ch_old_baud = 0; + return; + + } else if (ch->ch_custom_speed) { + + baud = ch->ch_custom_speed; + /* Handle transition from B0 */ + if (ch->ch_flags & CH_BAUD0) { + ch->ch_flags &= ~(CH_BAUD0); + + /* + * Bring back up RTS and DTR... + * Also handle RTS or DTR toggle if set. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + } + } else { + int iindex = 0; + int jindex = 0; + + ulong bauds[4][16] = { + { /* slowbaud */ + 0, 50, 75, 110, + 134, 150, 200, 300, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* slowbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* fastbaud */ + 0, 57600, 76800, 115200, + 131657, 153600, 230400, 460800, + 921600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }, + { /* fastbaud & CBAUDEX */ + 0, 57600, 115200, 230400, + 460800, 150, 200, 921600, + 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 } + }; + + /* Only use the TXPrint baud rate if the terminal unit is NOT open */ + if (!(ch->ch_tun.un_flags & UN_ISOPEN) && (un->un_type == DGNC_PRINT)) + baud = C_BAUD(ch->ch_pun.un_tty) & 0xff; + else + baud = C_BAUD(ch->ch_tun.un_tty) & 0xff; + + if (ch->ch_c_cflag & CBAUDEX) + iindex = 1; + + if (ch->ch_digi.digi_flags & DIGI_FAST) + iindex += 2; + + jindex = baud; + + if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) && (jindex < 16)) + baud = bauds[iindex][jindex]; + else + baud = 0; + + if (baud == 0) + baud = 9600; + + /* Handle transition from B0 */ + if (ch->ch_flags & CH_BAUD0) { + ch->ch_flags &= ~(CH_BAUD0); + + /* + * Bring back up RTS and DTR... + * Also handle RTS or DTR toggle if set. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + } + } + + if (ch->ch_c_cflag & PARENB) + lcr |= UART_LCR_PARITY; + + if (!(ch->ch_c_cflag & PARODD)) + lcr |= UART_LCR_EPAR; + + /* + * Not all platforms support mark/space parity, + * so this will hide behind an ifdef. + */ +#ifdef CMSPAR + if (ch->ch_c_cflag & CMSPAR) + lcr |= UART_LCR_SPAR; +#endif + + if (ch->ch_c_cflag & CSTOPB) + lcr |= UART_LCR_STOP; + + switch (ch->ch_c_cflag & CSIZE) { + case CS5: + lcr |= UART_LCR_WLEN5; + break; + case CS6: + lcr |= UART_LCR_WLEN6; + break; + case CS7: + lcr |= UART_LCR_WLEN7; + break; + case CS8: + default: + lcr |= UART_LCR_WLEN8; + break; + } + + uart_ier = readb(&ch->ch_neo_uart->ier); + ier = uart_ier; + + uart_lcr = readb(&ch->ch_neo_uart->lcr); + + if (baud == 0) + baud = 9600; + + quot = ch->ch_bd->bd_dividend / baud; + + if (quot != 0 && ch->ch_old_baud != baud) { + ch->ch_old_baud = baud; + writeb(UART_LCR_DLAB, &ch->ch_neo_uart->lcr); + writeb((quot & 0xff), &ch->ch_neo_uart->txrx); + writeb((quot >> 8), &ch->ch_neo_uart->ier); + writeb(lcr, &ch->ch_neo_uart->lcr); + } + + if (uart_lcr != lcr) + writeb(lcr, &ch->ch_neo_uart->lcr); + + if (ch->ch_c_cflag & CREAD) + ier |= (UART_IER_RDI | UART_IER_RLSI); + else + ier &= ~(UART_IER_RDI | UART_IER_RLSI); + + /* + * Have the UART interrupt on modem signal changes ONLY when + * we are in hardware flow control mode, or CLOCAL/FORCEDCD is not set. + */ + if ((ch->ch_digi.digi_flags & CTSPACE) || + (ch->ch_digi.digi_flags & RTSPACE) || + (ch->ch_c_cflag & CRTSCTS) || + !(ch->ch_digi.digi_flags & DIGI_FORCEDCD) || + !(ch->ch_c_cflag & CLOCAL)) + ier |= UART_IER_MSI; + else + ier &= ~UART_IER_MSI; + + ier |= UART_IER_THRI; + + if (ier != uart_ier) + writeb(ier, &ch->ch_neo_uart->ier); + + /* Set new start/stop chars */ + neo_set_new_start_stop_chars(ch); + + if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) { + neo_set_cts_flow_control(ch); + } else if (ch->ch_c_iflag & IXON) { + /* If start/stop is set to disable, then we should disable flow control */ + if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE)) + neo_set_no_output_flow_control(ch); + else + neo_set_ixon_flow_control(ch); + } else { + neo_set_no_output_flow_control(ch); + } + + if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) { + neo_set_rts_flow_control(ch); + } else if (ch->ch_c_iflag & IXOFF) { + /* If start/stop is set to disable, then we should disable flow control */ + if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE)) + neo_set_no_input_flow_control(ch); + else + neo_set_ixoff_flow_control(ch); + } else { + neo_set_no_input_flow_control(ch); + } + + /* + * Adjust the RX FIFO Trigger level if baud is less than 9600. + * Not exactly elegant, but this is needed because of the Exar chip's + * delay on firing off the RX FIFO interrupt on slower baud rates. + */ + if (baud < 9600) { + writeb(1, &ch->ch_neo_uart->rfifo); + ch->ch_r_tlevel = 1; + } + + neo_assert_modem_signals(ch); + + /* Get current status of the modem signals now */ + neo_parse_modem(ch, readb(&ch->ch_neo_uart->msr)); +} + + +/* + * Our board poller function. + */ +static void neo_tasklet(unsigned long data) +{ + struct dgnc_board *bd = (struct dgnc_board *) data; + struct channel_t *ch; + unsigned long flags; + int i; + int state = 0; + int ports = 0; + + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + /* Cache a couple board values */ + spin_lock_irqsave(&bd->bd_lock, flags); + state = bd->state; + ports = bd->nasync; + spin_unlock_irqrestore(&bd->bd_lock, flags); + + /* + * Do NOT allow the interrupt routine to read the intr registers + * Until we release this lock. + */ + spin_lock_irqsave(&bd->bd_intr_lock, flags); + + /* + * If board is ready, parse deeper to see if there is anything to do. + */ + if ((state == BOARD_READY) && (ports > 0)) { + /* Loop on each port */ + for (i = 0; i < ports; i++) { + ch = bd->channels[i]; + + /* Just being careful... */ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + continue; + + /* + * NOTE: Remember you CANNOT hold any channel + * locks when calling the input routine. + * + * During input processing, its possible we + * will call the Linux ld, which might in turn, + * do a callback right back into us, resulting + * in us trying to grab the channel lock twice! + */ + dgnc_input(ch); + + /* + * Channel lock is grabbed and then released + * inside both of these routines, but neither + * call anything else that could call back into us. + */ + neo_copy_data_from_queue_to_uart(ch); + dgnc_wakeup_writes(ch); + + /* + * Call carrier carrier function, in case something + * has changed. + */ + dgnc_carrier(ch); + + /* + * Check to see if we need to turn off a sending break. + * The timing check is done inside clear_break() + */ + if (ch->ch_stop_sending_break) + neo_clear_break(ch, 0); + } + } + + /* Allow interrupt routine to access the interrupt register again */ + spin_unlock_irqrestore(&bd->bd_intr_lock, flags); + +} + + +/* + * dgnc_neo_intr() + * + * Neo specific interrupt handler. + */ +static irqreturn_t neo_intr(int irq, void *voidbrd) +{ + struct dgnc_board *brd = voidbrd; + struct channel_t *ch; + int port = 0; + int type = 0; + int current_port; + u32 tmp; + u32 uart_poll; + unsigned long flags; + unsigned long flags2; + + /* + * Check to make sure it didn't receive interrupt with a null board + * associated or a board pointer that wasn't ours. + */ + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return IRQ_NONE; + + brd->intr_count++; + + /* Lock out the slow poller from running on this board. */ + spin_lock_irqsave(&brd->bd_intr_lock, flags); + + /* + * Read in "extended" IRQ information from the 32bit Neo register. + * Bits 0-7: What port triggered the interrupt. + * Bits 8-31: Each 3bits indicate what type of interrupt occurred. + */ + uart_poll = readl(brd->re_map_membase + UART_17158_POLL_ADDR_OFFSET); + + /* + * If 0, no interrupts pending. + * This can happen if the IRQ is shared among a couple Neo/Classic boards. + */ + if (!uart_poll) { + spin_unlock_irqrestore(&brd->bd_intr_lock, flags); + return IRQ_NONE; + } + + /* At this point, we have at least SOMETHING to service, dig further... */ + + current_port = 0; + + /* Loop on each port */ + while ((uart_poll & 0xff) != 0) { + + tmp = uart_poll; + + /* Check current port to see if it has interrupt pending */ + if ((tmp & dgnc_offset_table[current_port]) != 0) { + port = current_port; + type = tmp >> (8 + (port * 3)); + type &= 0x7; + } else { + current_port++; + continue; + } + + /* Remove this port + type from uart_poll */ + uart_poll &= ~(dgnc_offset_table[port]); + + if (!type) { + /* If no type, just ignore it, and move onto next port */ + continue; + } + + /* Switch on type of interrupt we have */ + switch (type) { + + case UART_17158_RXRDY_TIMEOUT: + /* + * RXRDY Time-out is cleared by reading data in the + * RX FIFO until it falls below the trigger level. + */ + + /* Verify the port is in range. */ + if (port >= brd->nasync) + continue; + + ch = brd->channels[port]; + neo_copy_data_from_uart_to_queue(ch); + + /* Call our tty layer to enforce queue flow control if needed. */ + spin_lock_irqsave(&ch->ch_lock, flags2); + dgnc_check_queue_flow_control(ch); + spin_unlock_irqrestore(&ch->ch_lock, flags2); + + continue; + + case UART_17158_RX_LINE_STATUS: + /* + * RXRDY and RX LINE Status (logic OR of LSR[4:1]) + */ + neo_parse_lsr(brd, port); + continue; + + case UART_17158_TXRDY: + /* + * TXRDY interrupt clears after reading ISR register for the UART channel. + */ + + /* + * Yes, this is odd... + * Why would I check EVERY possibility of type of + * interrupt, when we know its TXRDY??? + * Becuz for some reason, even tho we got triggered for TXRDY, + * it seems to be occasionally wrong. Instead of TX, which + * it should be, I was getting things like RXDY too. Weird. + */ + neo_parse_isr(brd, port); + continue; + + case UART_17158_MSR: + /* + * MSR or flow control was seen. + */ + neo_parse_isr(brd, port); + continue; + + default: + /* + * The UART triggered us with a bogus interrupt type. + * It appears the Exar chip, when REALLY bogged down, will throw + * these once and awhile. + * Its harmless, just ignore it and move on. + */ + continue; + } + } + + /* + * Schedule tasklet to more in-depth servicing at a better time. + */ + tasklet_schedule(&brd->helper_tasklet); + + spin_unlock_irqrestore(&brd->bd_intr_lock, flags); + + return IRQ_HANDLED; +} + + +/* + * Neo specific way of turning off the receiver. + * Used as a way to enforce queue flow control when in + * hardware flow control mode. + */ +static void neo_disable_receiver(struct channel_t *ch) +{ + unsigned char tmp = readb(&ch->ch_neo_uart->ier); + + tmp &= ~(UART_IER_RDI); + writeb(tmp, &ch->ch_neo_uart->ier); + neo_pci_posting_flush(ch->ch_bd); +} + + +/* + * Neo specific way of turning on the receiver. + * Used as a way to un-enforce queue flow control when in + * hardware flow control mode. + */ +static void neo_enable_receiver(struct channel_t *ch) +{ + unsigned char tmp = readb(&ch->ch_neo_uart->ier); + + tmp |= (UART_IER_RDI); + writeb(tmp, &ch->ch_neo_uart->ier); + neo_pci_posting_flush(ch->ch_bd); +} + + +static void neo_copy_data_from_uart_to_queue(struct channel_t *ch) +{ + int qleft = 0; + unsigned char linestatus = 0; + unsigned char error_mask = 0; + int n = 0; + int total = 0; + ushort head; + ushort tail; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* cache head and tail of queue */ + head = ch->ch_r_head & RQUEUEMASK; + tail = ch->ch_r_tail & RQUEUEMASK; + + /* Get our cached LSR */ + linestatus = ch->ch_cached_lsr; + ch->ch_cached_lsr = 0; + + /* Store how much space we have left in the queue */ + qleft = tail - head - 1; + if (qleft < 0) + qleft += RQUEUEMASK + 1; + + /* + * If the UART is not in FIFO mode, force the FIFO copy to + * NOT be run, by setting total to 0. + * + * On the other hand, if the UART IS in FIFO mode, then ask + * the UART to give us an approximation of data it has RX'ed. + */ + if (!(ch->ch_flags & CH_FIFO_ENABLED)) + total = 0; + else { + total = readb(&ch->ch_neo_uart->rfifo); + + /* + * EXAR chip bug - RX FIFO COUNT - Fudge factor. + * + * This resolves a problem/bug with the Exar chip that sometimes + * returns a bogus value in the rfifo register. + * The count can be any where from 0-3 bytes "off". + * Bizarre, but true. + */ + if ((ch->ch_bd->dvid & 0xf0) >= UART_XR17E158_DVID) + total -= 1; + else + total -= 3; + } + + + /* + * Finally, bound the copy to make sure we don't overflow + * our own queue... + * The byte by byte copy loop below this loop this will + * deal with the queue overflow possibility. + */ + total = min(total, qleft); + + while (total > 0) { + + /* + * Grab the linestatus register, we need to check + * to see if there are any errors in the FIFO. + */ + linestatus = readb(&ch->ch_neo_uart->lsr); + + /* + * Break out if there is a FIFO error somewhere. + * This will allow us to go byte by byte down below, + * finding the exact location of the error. + */ + if (linestatus & UART_17158_RX_FIFO_DATA_ERROR) + break; + + /* Make sure we don't go over the end of our queue */ + n = min(((uint) total), (RQUEUESIZE - (uint) head)); + + /* + * Cut down n even further if needed, this is to fix + * a problem with memcpy_fromio() with the Neo on the + * IBM pSeries platform. + * 15 bytes max appears to be the magic number. + */ + n = min_t(uint, n, 12); + + /* + * Since we are grabbing the linestatus register, which + * will reset some bits after our read, we need to ensure + * we don't miss our TX FIFO emptys. + */ + if (linestatus & (UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR)) + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + + linestatus = 0; + + /* Copy data from uart to the queue */ + memcpy_fromio(ch->ch_rqueue + head, &ch->ch_neo_uart->txrxburst, n); + + /* + * Since RX_FIFO_DATA_ERROR was 0, we are guarenteed + * that all the data currently in the FIFO is free of + * breaks and parity/frame/orun errors. + */ + memset(ch->ch_equeue + head, 0, n); + + /* Add to and flip head if needed */ + head = (head + n) & RQUEUEMASK; + total -= n; + qleft -= n; + ch->ch_rxcount += n; + } + + /* + * Create a mask to determine whether we should + * insert the character (if any) into our queue. + */ + if (ch->ch_c_iflag & IGNBRK) + error_mask |= UART_LSR_BI; + + /* + * Now cleanup any leftover bytes still in the UART. + * Also deal with any possible queue overflow here as well. + */ + while (1) { + + /* + * Its possible we have a linestatus from the loop above + * this, so we "OR" on any extra bits. + */ + linestatus |= readb(&ch->ch_neo_uart->lsr); + + /* + * If the chip tells us there is no more data pending to + * be read, we can then leave. + * But before we do, cache the linestatus, just in case. + */ + if (!(linestatus & UART_LSR_DR)) { + ch->ch_cached_lsr = linestatus; + break; + } + + /* No need to store this bit */ + linestatus &= ~UART_LSR_DR; + + /* + * Since we are grabbing the linestatus register, which + * will reset some bits after our read, we need to ensure + * we don't miss our TX FIFO emptys. + */ + if (linestatus & (UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR)) { + linestatus &= ~(UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR); + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + } + + /* + * Discard character if we are ignoring the error mask. + */ + if (linestatus & error_mask) { + unsigned char discard; + + linestatus = 0; + memcpy_fromio(&discard, &ch->ch_neo_uart->txrxburst, 1); + continue; + } + + /* + * If our queue is full, we have no choice but to drop some data. + * The assumption is that HWFLOW or SWFLOW should have stopped + * things way way before we got to this point. + * + * I decided that I wanted to ditch the oldest data first, + * I hope thats okay with everyone? Yes? Good. + */ + while (qleft < 1) { + tail = (tail + 1) & RQUEUEMASK; + ch->ch_r_tail = tail; + ch->ch_err_overrun++; + qleft++; + } + + memcpy_fromio(ch->ch_rqueue + head, &ch->ch_neo_uart->txrxburst, 1); + ch->ch_equeue[head] = (unsigned char) linestatus; + + /* Ditch any remaining linestatus value. */ + linestatus = 0; + + /* Add to and flip head if needed */ + head = (head + 1) & RQUEUEMASK; + + qleft--; + ch->ch_rxcount++; + } + + /* + * Write new final heads to channel structure. + */ + ch->ch_r_head = head & RQUEUEMASK; + ch->ch_e_head = head & EQUEUEMASK; + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +/* + * This function basically goes to sleep for secs, or until + * it gets signalled that the port has fully drained. + */ +static int neo_drain(struct tty_struct *tty, uint seconds) +{ + unsigned long flags; + struct channel_t *ch; + struct un_t *un; + int rc = 0; + + if (!tty || tty->magic != TTY_MAGIC) + return -ENXIO; + + un = (struct un_t *) tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -ENXIO; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + spin_lock_irqsave(&ch->ch_lock, flags); + un->un_flags |= UN_EMPTY; + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * Go to sleep waiting for the tty layer to wake me back up when + * the empty flag goes away. + * + * NOTE: TODO: Do something with time passed in. + */ + rc = wait_event_interruptible(un->un_flags_wait, ((un->un_flags & UN_EMPTY) == 0)); + + /* If ret is non-zero, user ctrl-c'ed us */ + return rc; +} + + +/* + * Flush the WRITE FIFO on the Neo. + * + * NOTE: Channel lock MUST be held before calling this function! + */ +static void neo_flush_uart_write(struct channel_t *ch) +{ + unsigned char tmp = 0; + int i = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_XMIT), &ch->ch_neo_uart->isr_fcr); + neo_pci_posting_flush(ch->ch_bd); + + for (i = 0; i < 10; i++) { + + /* Check to see if the UART feels it completely flushed the FIFO. */ + tmp = readb(&ch->ch_neo_uart->isr_fcr); + if (tmp & 4) + udelay(10); + else + break; + } + + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); +} + + +/* + * Flush the READ FIFO on the Neo. + * + * NOTE: Channel lock MUST be held before calling this function! + */ +static void neo_flush_uart_read(struct channel_t *ch) +{ + unsigned char tmp = 0; + int i = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR), &ch->ch_neo_uart->isr_fcr); + neo_pci_posting_flush(ch->ch_bd); + + for (i = 0; i < 10; i++) { + + /* Check to see if the UART feels it completely flushed the FIFO. */ + tmp = readb(&ch->ch_neo_uart->isr_fcr); + if (tmp & 2) + udelay(10); + else + break; + } +} + + +static void neo_copy_data_from_queue_to_uart(struct channel_t *ch) +{ + ushort head; + ushort tail; + int n; + int s; + int qlen; + uint len_written = 0; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* No data to write to the UART */ + if (ch->ch_w_tail == ch->ch_w_head) + goto exit_unlock; + + /* If port is "stopped", don't send any data to the UART */ + if ((ch->ch_flags & CH_FORCED_STOP) || + (ch->ch_flags & CH_BREAK_SENDING)) + goto exit_unlock; + + /* + * If FIFOs are disabled. Send data directly to txrx register + */ + if (!(ch->ch_flags & CH_FIFO_ENABLED)) { + unsigned char lsrbits = readb(&ch->ch_neo_uart->lsr); + + /* Cache the LSR bits for later parsing */ + ch->ch_cached_lsr |= lsrbits; + if (ch->ch_cached_lsr & UART_LSR_THRE) { + ch->ch_cached_lsr &= ~(UART_LSR_THRE); + + /* + * If RTS Toggle mode is on, turn on RTS now if not already set, + * and make sure we get an event when the data transfer has completed. + */ + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) { + if (!(ch->ch_mostat & UART_MCR_RTS)) { + ch->ch_mostat |= (UART_MCR_RTS); + neo_assert_modem_signals(ch); + } + ch->ch_tun.un_flags |= (UN_EMPTY); + } + /* + * If DTR Toggle mode is on, turn on DTR now if not already set, + * and make sure we get an event when the data transfer has completed. + */ + if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) { + if (!(ch->ch_mostat & UART_MCR_DTR)) { + ch->ch_mostat |= (UART_MCR_DTR); + neo_assert_modem_signals(ch); + } + ch->ch_tun.un_flags |= (UN_EMPTY); + } + + writeb(ch->ch_wqueue[ch->ch_w_tail], &ch->ch_neo_uart->txrx); + ch->ch_w_tail++; + ch->ch_w_tail &= WQUEUEMASK; + ch->ch_txcount++; + } + + goto exit_unlock; + } + + /* + * We have to do it this way, because of the EXAR TXFIFO count bug. + */ + if ((ch->ch_bd->dvid & 0xf0) < UART_XR17E158_DVID) { + if (!(ch->ch_flags & (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM))) + goto exit_unlock; + + len_written = 0; + + n = readb(&ch->ch_neo_uart->tfifo); + + if ((unsigned int) n > ch->ch_t_tlevel) + goto exit_unlock; + + n = UART_17158_TX_FIFOSIZE - ch->ch_t_tlevel; + } else { + n = UART_17158_TX_FIFOSIZE - readb(&ch->ch_neo_uart->tfifo); + } + + /* cache head and tail of queue */ + head = ch->ch_w_head & WQUEUEMASK; + tail = ch->ch_w_tail & WQUEUEMASK; + qlen = (head - tail) & WQUEUEMASK; + + /* Find minimum of the FIFO space, versus queue length */ + n = min(n, qlen); + + while (n > 0) { + + s = ((head >= tail) ? head : WQUEUESIZE) - tail; + s = min(s, n); + + if (s <= 0) + break; + + /* + * If RTS Toggle mode is on, turn on RTS now if not already set, + * and make sure we get an event when the data transfer has completed. + */ + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) { + if (!(ch->ch_mostat & UART_MCR_RTS)) { + ch->ch_mostat |= (UART_MCR_RTS); + neo_assert_modem_signals(ch); + } + ch->ch_tun.un_flags |= (UN_EMPTY); + } + + /* + * If DTR Toggle mode is on, turn on DTR now if not already set, + * and make sure we get an event when the data transfer has completed. + */ + if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) { + if (!(ch->ch_mostat & UART_MCR_DTR)) { + ch->ch_mostat |= (UART_MCR_DTR); + neo_assert_modem_signals(ch); + } + ch->ch_tun.un_flags |= (UN_EMPTY); + } + + memcpy_toio(&ch->ch_neo_uart->txrxburst, ch->ch_wqueue + tail, s); + + /* Add and flip queue if needed */ + tail = (tail + s) & WQUEUEMASK; + n -= s; + ch->ch_txcount += s; + len_written += s; + } + + /* Update the final tail */ + ch->ch_w_tail = tail & WQUEUEMASK; + + if (len_written > 0) { + neo_pci_posting_flush(ch->ch_bd); + ch->ch_flags &= ~(CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + } + +exit_unlock: + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void neo_parse_modem(struct channel_t *ch, unsigned char signals) +{ + unsigned char msignals = signals; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + /* + * Do altpin switching. Altpin switches DCD and DSR. + * This prolly breaks DSRPACE, so we should be more clever here. + */ + if (ch->ch_digi.digi_flags & DIGI_ALTPIN) { + unsigned char mswap = msignals; + + if (mswap & UART_MSR_DDCD) { + msignals &= ~UART_MSR_DDCD; + msignals |= UART_MSR_DDSR; + } + if (mswap & UART_MSR_DDSR) { + msignals &= ~UART_MSR_DDSR; + msignals |= UART_MSR_DDCD; + } + if (mswap & UART_MSR_DCD) { + msignals &= ~UART_MSR_DCD; + msignals |= UART_MSR_DSR; + } + if (mswap & UART_MSR_DSR) { + msignals &= ~UART_MSR_DSR; + msignals |= UART_MSR_DCD; + } + } + + /* Scrub off lower bits. They signify delta's, which I don't care about */ + msignals &= 0xf0; + + if (msignals & UART_MSR_DCD) + ch->ch_mistat |= UART_MSR_DCD; + else + ch->ch_mistat &= ~UART_MSR_DCD; + + if (msignals & UART_MSR_DSR) + ch->ch_mistat |= UART_MSR_DSR; + else + ch->ch_mistat &= ~UART_MSR_DSR; + + if (msignals & UART_MSR_RI) + ch->ch_mistat |= UART_MSR_RI; + else + ch->ch_mistat &= ~UART_MSR_RI; + + if (msignals & UART_MSR_CTS) + ch->ch_mistat |= UART_MSR_CTS; + else + ch->ch_mistat &= ~UART_MSR_CTS; +} + + +/* Make the UART raise any of the output signals we want up */ +static void neo_assert_modem_signals(struct channel_t *ch) +{ + unsigned char out; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + out = ch->ch_mostat; + + if (ch->ch_flags & CH_LOOPBACK) + out |= UART_MCR_LOOP; + + writeb(out, &ch->ch_neo_uart->mcr); + neo_pci_posting_flush(ch->ch_bd); + + /* Give time for the UART to actually raise/drop the signals */ + udelay(10); +} + + +static void neo_send_start_character(struct channel_t *ch) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + if (ch->ch_startc != _POSIX_VDISABLE) { + ch->ch_xon_sends++; + writeb(ch->ch_startc, &ch->ch_neo_uart->txrx); + neo_pci_posting_flush(ch->ch_bd); + udelay(10); + } +} + + +static void neo_send_stop_character(struct channel_t *ch) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + if (ch->ch_stopc != _POSIX_VDISABLE) { + ch->ch_xoff_sends++; + writeb(ch->ch_stopc, &ch->ch_neo_uart->txrx); + neo_pci_posting_flush(ch->ch_bd); + udelay(10); + } +} + + +/* + * neo_uart_init + */ +static void neo_uart_init(struct channel_t *ch) +{ + + writeb(0, &ch->ch_neo_uart->ier); + writeb(0, &ch->ch_neo_uart->efr); + writeb(UART_EFR_ECB, &ch->ch_neo_uart->efr); + + + /* Clear out UART and FIFO */ + readb(&ch->ch_neo_uart->txrx); + writeb((UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT), &ch->ch_neo_uart->isr_fcr); + readb(&ch->ch_neo_uart->lsr); + readb(&ch->ch_neo_uart->msr); + + ch->ch_flags |= CH_FIFO_ENABLED; + + /* Assert any signals we want up */ + writeb(ch->ch_mostat, &ch->ch_neo_uart->mcr); + neo_pci_posting_flush(ch->ch_bd); +} + + +/* + * Make the UART completely turn off. + */ +static void neo_uart_off(struct channel_t *ch) +{ + /* Turn off UART enhanced bits */ + writeb(0, &ch->ch_neo_uart->efr); + + /* Stop all interrupts from occurring. */ + writeb(0, &ch->ch_neo_uart->ier); + neo_pci_posting_flush(ch->ch_bd); +} + + +static uint neo_get_uart_bytes_left(struct channel_t *ch) +{ + unsigned char left = 0; + unsigned char lsr = readb(&ch->ch_neo_uart->lsr); + + /* We must cache the LSR as some of the bits get reset once read... */ + ch->ch_cached_lsr |= lsr; + + /* Determine whether the Transmitter is empty or not */ + if (!(lsr & UART_LSR_TEMT)) { + if (ch->ch_flags & CH_TX_FIFO_EMPTY) + tasklet_schedule(&ch->ch_bd->helper_tasklet); + left = 1; + } else { + ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM); + left = 0; + } + + return left; +} + + +/* Channel lock MUST be held by the calling function! */ +static void neo_send_break(struct channel_t *ch, int msecs) +{ + /* + * If we receive a time of 0, this means turn off the break. + */ + if (msecs == 0) { + if (ch->ch_flags & CH_BREAK_SENDING) { + unsigned char temp = readb(&ch->ch_neo_uart->lcr); + + writeb((temp & ~UART_LCR_SBC), &ch->ch_neo_uart->lcr); + neo_pci_posting_flush(ch->ch_bd); + ch->ch_flags &= ~(CH_BREAK_SENDING); + ch->ch_stop_sending_break = 0; + } + return; + } + + /* + * Set the time we should stop sending the break. + * If we are already sending a break, toss away the existing + * time to stop, and use this new value instead. + */ + ch->ch_stop_sending_break = jiffies + dgnc_jiffies_from_ms(msecs); + + /* Tell the UART to start sending the break */ + if (!(ch->ch_flags & CH_BREAK_SENDING)) { + unsigned char temp = readb(&ch->ch_neo_uart->lcr); + + writeb((temp | UART_LCR_SBC), &ch->ch_neo_uart->lcr); + neo_pci_posting_flush(ch->ch_bd); + ch->ch_flags |= (CH_BREAK_SENDING); + } +} + + +/* + * neo_send_immediate_char. + * + * Sends a specific character as soon as possible to the UART, + * jumping over any bytes that might be in the write queue. + * + * The channel lock MUST be held by the calling function. + */ +static void neo_send_immediate_char(struct channel_t *ch, unsigned char c) +{ + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + writeb(c, &ch->ch_neo_uart->txrx); + neo_pci_posting_flush(ch->ch_bd); +} + + +static unsigned int neo_read_eeprom(unsigned char __iomem *base, unsigned int address) +{ + unsigned int enable; + unsigned int bits; + unsigned int databit; + unsigned int val; + + /* enable chip select */ + writeb(NEO_EECS, base + NEO_EEREG); + /* READ */ + enable = (address | 0x180); + + for (bits = 9; bits--; ) { + databit = (enable & (1 << bits)) ? NEO_EEDI : 0; + /* Set read address */ + writeb(databit | NEO_EECS, base + NEO_EEREG); + writeb(databit | NEO_EECS | NEO_EECK, base + NEO_EEREG); + } + + val = 0; + + for (bits = 17; bits--; ) { + /* clock to EEPROM */ + writeb(NEO_EECS, base + NEO_EEREG); + writeb(NEO_EECS | NEO_EECK, base + NEO_EEREG); + val <<= 1; + /* read EEPROM */ + if (readb(base + NEO_EEREG) & NEO_EEDO) + val |= 1; + } + + /* clock falling edge */ + writeb(NEO_EECS, base + NEO_EEREG); + + /* drop chip select */ + writeb(0x00, base + NEO_EEREG); + + return val; +} + + +static void neo_vpd(struct dgnc_board *brd) +{ + unsigned int i = 0; + unsigned int a; + + if (!brd || brd->magic != DGNC_BOARD_MAGIC) + return; + + if (!brd->re_map_membase) + return; + + /* Store the VPD into our buffer */ + for (i = 0; i < NEO_VPD_IMAGESIZE; i++) { + a = neo_read_eeprom(brd->re_map_membase, i); + brd->vpd[i*2] = a & 0xff; + brd->vpd[(i*2)+1] = (a >> 8) & 0xff; + } + + if (((brd->vpd[0x08] != 0x82) /* long resource name tag */ + && (brd->vpd[0x10] != 0x82)) /* long resource name tag (PCI-66 files)*/ + || (brd->vpd[0x7F] != 0x78)) { /* small resource end tag */ + + memset(brd->vpd, '\0', NEO_VPD_IMAGESIZE); + } else { + /* Search for the serial number */ + for (i = 0; i < NEO_VPD_IMAGEBYTES - 3; i++) + if (brd->vpd[i] == 'S' && brd->vpd[i + 1] == 'N') + strncpy(brd->serial_num, &(brd->vpd[i + 3]), 9); + } +} diff --git a/drivers/staging/dgnc/dgnc_neo.h b/drivers/staging/dgnc/dgnc_neo.h new file mode 100644 index 000000000..c528df5a0 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_neo.h @@ -0,0 +1,149 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DGNC_NEO_H +#define __DGNC_NEO_H + +#include "dgnc_driver.h" + +/************************************************************************ + * Per channel/port NEO UART structure * + ************************************************************************ + * Base Structure Entries Usage Meanings to Host * + * * + * W = read write R = read only * + * U = Unused. * + ************************************************************************/ + +struct neo_uart_struct { + u8 txrx; /* WR RHR/THR - Holding Reg */ + u8 ier; /* WR IER - Interrupt Enable Reg */ + u8 isr_fcr; /* WR ISR/FCR - Interrupt Status Reg/Fifo Control Reg */ + u8 lcr; /* WR LCR - Line Control Reg */ + u8 mcr; /* WR MCR - Modem Control Reg */ + u8 lsr; /* WR LSR - Line Status Reg */ + u8 msr; /* WR MSR - Modem Status Reg */ + u8 spr; /* WR SPR - Scratch Pad Reg */ + u8 fctr; /* WR FCTR - Feature Control Reg */ + u8 efr; /* WR EFR - Enhanced Function Reg */ + u8 tfifo; /* WR TXCNT/TXTRG - Transmit FIFO Reg */ + u8 rfifo; /* WR RXCNT/RXTRG - Receive FIFO Reg */ + u8 xoffchar1; /* WR XOFF 1 - XOff Character 1 Reg */ + u8 xoffchar2; /* WR XOFF 2 - XOff Character 2 Reg */ + u8 xonchar1; /* WR XON 1 - Xon Character 1 Reg */ + u8 xonchar2; /* WR XON 2 - XOn Character 2 Reg */ + + u8 reserved1[0x2ff - 0x200]; /* U Reserved by Exar */ + u8 txrxburst[64]; /* RW 64 bytes of RX/TX FIFO Data */ + u8 reserved2[0x37f - 0x340]; /* U Reserved by Exar */ + u8 rxburst_with_errors[64]; /* R 64 bytes of RX FIFO Data + LSR */ +}; + +/* Where to read the extended interrupt register (32bits instead of 8bits) */ +#define UART_17158_POLL_ADDR_OFFSET 0x80 + +/* These are the current dvid's of the Neo boards */ +#define UART_XR17C158_DVID 0x20 +#define UART_XR17D158_DVID 0x20 +#define UART_XR17E158_DVID 0x40 + +#define NEO_EECK 0x10 /* Clock */ +#define NEO_EECS 0x20 /* Chip Select */ +#define NEO_EEDI 0x40 /* Data In is an Output Pin */ +#define NEO_EEDO 0x80 /* Data Out is an Input Pin */ +#define NEO_EEREG 0x8E /* offset to EEPROM control reg */ + + +#define NEO_VPD_IMAGESIZE 0x40 /* size of image to read from EEPROM in words */ +#define NEO_VPD_IMAGEBYTES (NEO_VPD_IMAGESIZE * 2) + +/* + * These are the redefinitions for the FCTR on the XR17C158, since + * Exar made them different than their earlier design. (XR16C854) + */ + +/* These are only applicable when table D is selected */ +#define UART_17158_FCTR_RTS_NODELAY 0x00 +#define UART_17158_FCTR_RTS_4DELAY 0x01 +#define UART_17158_FCTR_RTS_6DELAY 0x02 +#define UART_17158_FCTR_RTS_8DELAY 0x03 +#define UART_17158_FCTR_RTS_12DELAY 0x12 +#define UART_17158_FCTR_RTS_16DELAY 0x05 +#define UART_17158_FCTR_RTS_20DELAY 0x13 +#define UART_17158_FCTR_RTS_24DELAY 0x06 +#define UART_17158_FCTR_RTS_28DELAY 0x14 +#define UART_17158_FCTR_RTS_32DELAY 0x07 +#define UART_17158_FCTR_RTS_36DELAY 0x16 +#define UART_17158_FCTR_RTS_40DELAY 0x08 +#define UART_17158_FCTR_RTS_44DELAY 0x09 +#define UART_17158_FCTR_RTS_48DELAY 0x10 +#define UART_17158_FCTR_RTS_52DELAY 0x11 + +#define UART_17158_FCTR_RTS_IRDA 0x10 +#define UART_17158_FCTR_RS485 0x20 +#define UART_17158_FCTR_TRGA 0x00 +#define UART_17158_FCTR_TRGB 0x40 +#define UART_17158_FCTR_TRGC 0x80 +#define UART_17158_FCTR_TRGD 0xC0 + +/* 17158 trigger table selects.. */ +#define UART_17158_FCTR_BIT6 0x40 +#define UART_17158_FCTR_BIT7 0x80 + +/* 17158 TX/RX memmapped buffer offsets */ +#define UART_17158_RX_FIFOSIZE 64 +#define UART_17158_TX_FIFOSIZE 64 + +/* 17158 Extended IIR's */ +#define UART_17158_IIR_RDI_TIMEOUT 0x0C /* Receiver data TIMEOUT */ +#define UART_17158_IIR_XONXOFF 0x10 /* Received an XON/XOFF char */ +#define UART_17158_IIR_HWFLOW_STATE_CHANGE 0x20 /* CTS/DSR or RTS/DTR state change */ +#define UART_17158_IIR_FIFO_ENABLED 0xC0 /* 16550 FIFOs are Enabled */ + +/* + * These are the extended interrupts that get sent + * back to us from the UART's 32bit interrupt register + */ +#define UART_17158_RX_LINE_STATUS 0x1 /* RX Ready */ +#define UART_17158_RXRDY_TIMEOUT 0x2 /* RX Ready Timeout */ +#define UART_17158_TXRDY 0x3 /* TX Ready */ +#define UART_17158_MSR 0x4 /* Modem State Change */ +#define UART_17158_TX_AND_FIFO_CLR 0x40 /* Transmitter Holding Reg Empty */ +#define UART_17158_RX_FIFO_DATA_ERROR 0x80 /* UART detected an RX FIFO Data error */ + +/* + * These are the EXTENDED definitions for the 17C158's Interrupt + * Enable Register. + */ +#define UART_17158_EFR_ECB 0x10 /* Enhanced control bit */ +#define UART_17158_EFR_IXON 0x2 /* Receiver compares Xon1/Xoff1 */ +#define UART_17158_EFR_IXOFF 0x8 /* Transmit Xon1/Xoff1 */ +#define UART_17158_EFR_RTSDTR 0x40 /* Auto RTS/DTR Flow Control Enable */ +#define UART_17158_EFR_CTSDSR 0x80 /* Auto CTS/DSR Flow COntrol Enable */ + +#define UART_17158_XOFF_DETECT 0x1 /* Indicates whether chip saw an incoming XOFF char */ +#define UART_17158_XON_DETECT 0x2 /* Indicates whether chip saw an incoming XON char */ + +#define UART_17158_IER_RSVD1 0x10 /* Reserved by Exar */ +#define UART_17158_IER_XOFF 0x20 /* Xoff Interrupt Enable */ +#define UART_17158_IER_RTSDTR 0x40 /* Output Interrupt Enable */ +#define UART_17158_IER_CTSDSR 0x80 /* Input Interrupt Enable */ + +/* + * Our Global Variables + */ +extern struct board_ops dgnc_neo_ops; + +#endif diff --git a/drivers/staging/dgnc/dgnc_pci.h b/drivers/staging/dgnc/dgnc_pci.h new file mode 100644 index 000000000..617d40d1e --- /dev/null +++ b/drivers/staging/dgnc/dgnc_pci.h @@ -0,0 +1,69 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DGNC_PCI_H +#define __DGNC_PCI_H + +#define PCIMAX 32 /* maximum number of PCI boards */ + +#define DIGI_VID 0x114F + +#define PCI_DEVICE_CLASSIC_4_DID 0x0028 +#define PCI_DEVICE_CLASSIC_8_DID 0x0029 +#define PCI_DEVICE_CLASSIC_4_422_DID 0x00D0 +#define PCI_DEVICE_CLASSIC_8_422_DID 0x00D1 +#define PCI_DEVICE_NEO_4_DID 0x00B0 +#define PCI_DEVICE_NEO_8_DID 0x00B1 +#define PCI_DEVICE_NEO_2DB9_DID 0x00C8 +#define PCI_DEVICE_NEO_2DB9PRI_DID 0x00C9 +#define PCI_DEVICE_NEO_2RJ45_DID 0x00CA +#define PCI_DEVICE_NEO_2RJ45PRI_DID 0x00CB +#define PCI_DEVICE_NEO_1_422_DID 0x00CC +#define PCI_DEVICE_NEO_1_422_485_DID 0x00CD +#define PCI_DEVICE_NEO_2_422_485_DID 0x00CE +#define PCI_DEVICE_NEO_EXPRESS_8_DID 0x00F0 +#define PCI_DEVICE_NEO_EXPRESS_4_DID 0x00F1 +#define PCI_DEVICE_NEO_EXPRESS_4RJ45_DID 0x00F2 +#define PCI_DEVICE_NEO_EXPRESS_8RJ45_DID 0x00F3 +#define PCI_DEVICE_NEO_EXPRESS_4_IBM_DID 0x00F4 + +#define PCI_DEVICE_CLASSIC_4_PCI_NAME "ClassicBoard 4 PCI" +#define PCI_DEVICE_CLASSIC_8_PCI_NAME "ClassicBoard 8 PCI" +#define PCI_DEVICE_CLASSIC_4_422_PCI_NAME "ClassicBoard 4 422 PCI" +#define PCI_DEVICE_CLASSIC_8_422_PCI_NAME "ClassicBoard 8 422 PCI" +#define PCI_DEVICE_NEO_4_PCI_NAME "Neo 4 PCI" +#define PCI_DEVICE_NEO_8_PCI_NAME "Neo 8 PCI" +#define PCI_DEVICE_NEO_2DB9_PCI_NAME "Neo 2 - DB9 Universal PCI" +#define PCI_DEVICE_NEO_2DB9PRI_PCI_NAME "Neo 2 - DB9 Universal PCI - Powered Ring Indicator" +#define PCI_DEVICE_NEO_2RJ45_PCI_NAME "Neo 2 - RJ45 Universal PCI" +#define PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME "Neo 2 - RJ45 Universal PCI - Powered Ring Indicator" +#define PCI_DEVICE_NEO_1_422_PCI_NAME "Neo 1 422 PCI" +#define PCI_DEVICE_NEO_1_422_485_PCI_NAME "Neo 1 422/485 PCI" +#define PCI_DEVICE_NEO_2_422_485_PCI_NAME "Neo 2 422/485 PCI" + +#define PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME "Neo 8 PCI Express" +#define PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME "Neo 4 PCI Express" +#define PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME "Neo 4 PCI Express RJ45" +#define PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME "Neo 8 PCI Express RJ45" +#define PCI_DEVICE_NEO_EXPRESS_4_IBM_PCI_NAME "Neo 4 PCI Express IBM" + + +/* Size of Memory and I/O for PCI (4 K) */ +#define PCI_RAM_SIZE 0x1000 + +/* Size of Memory (2MB) */ +#define PCI_MEM_SIZE 0x1000 + +#endif diff --git a/drivers/staging/dgnc/dgnc_sysfs.c b/drivers/staging/dgnc/dgnc_sysfs.c new file mode 100644 index 000000000..65551d190 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_sysfs.c @@ -0,0 +1,697 @@ +/* + * Copyright 2004 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dgnc_driver.h" +#include "dgnc_mgmt.h" + + +static ssize_t dgnc_driver_version_show(struct device_driver *ddp, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", DG_PART); +} +static DRIVER_ATTR(version, S_IRUSR, dgnc_driver_version_show, NULL); + + +static ssize_t dgnc_driver_boards_show(struct device_driver *ddp, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", dgnc_NumBoards); +} +static DRIVER_ATTR(boards, S_IRUSR, dgnc_driver_boards_show, NULL); + + +static ssize_t dgnc_driver_maxboards_show(struct device_driver *ddp, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", MAXBOARDS); +} +static DRIVER_ATTR(maxboards, S_IRUSR, dgnc_driver_maxboards_show, NULL); + + +static ssize_t dgnc_driver_pollrate_show(struct device_driver *ddp, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%dms\n", dgnc_poll_tick); +} + +static ssize_t dgnc_driver_pollrate_store(struct device_driver *ddp, const char *buf, size_t count) +{ + int ret; + + ret = sscanf(buf, "%d\n", &dgnc_poll_tick); + if (ret != 1) + return -EINVAL; + return count; +} +static DRIVER_ATTR(pollrate, (S_IRUSR | S_IWUSR), dgnc_driver_pollrate_show, dgnc_driver_pollrate_store); + + +void dgnc_create_driver_sysfiles(struct pci_driver *dgnc_driver) +{ + int rc = 0; + struct device_driver *driverfs = &dgnc_driver->driver; + + rc |= driver_create_file(driverfs, &driver_attr_version); + rc |= driver_create_file(driverfs, &driver_attr_boards); + rc |= driver_create_file(driverfs, &driver_attr_maxboards); + rc |= driver_create_file(driverfs, &driver_attr_pollrate); + if (rc) + pr_err("DGNC: sysfs driver_create_file failed!\n"); +} + + +void dgnc_remove_driver_sysfiles(struct pci_driver *dgnc_driver) +{ + struct device_driver *driverfs = &dgnc_driver->driver; + + driver_remove_file(driverfs, &driver_attr_version); + driver_remove_file(driverfs, &driver_attr_boards); + driver_remove_file(driverfs, &driver_attr_maxboards); + driver_remove_file(driverfs, &driver_attr_pollrate); +} + + +#define DGNC_VERIFY_BOARD(p, bd) \ + do { \ + if (!p) \ + return 0; \ + \ + bd = dev_get_drvdata(p); \ + if (!bd || bd->magic != DGNC_BOARD_MAGIC) \ + return 0; \ + if (bd->state != BOARD_READY) \ + return 0; \ + } while (0) + + + +static ssize_t dgnc_vpd_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + count += sprintf(buf + count, "\n 0 1 2 3 4 5 6 7 8 9 A B C D E F"); + for (i = 0; i < 0x40 * 2; i++) { + if (!(i % 16)) + count += sprintf(buf + count, "\n%04X ", i * 2); + count += sprintf(buf + count, "%02X ", bd->vpd[i]); + } + count += sprintf(buf + count, "\n"); + + return count; +} +static DEVICE_ATTR(vpd, S_IRUSR, dgnc_vpd_show, NULL); + +static ssize_t dgnc_serial_number_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + + DGNC_VERIFY_BOARD(p, bd); + + if (bd->serial_num[0] == '\0') + count += sprintf(buf + count, "\n"); + else + count += sprintf(buf + count, "%s\n", bd->serial_num); + + return count; +} +static DEVICE_ATTR(serial_number, S_IRUSR, dgnc_serial_number_show, NULL); + + +static ssize_t dgnc_ports_state_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, + "%d %s\n", bd->channels[i]->ch_portnum, + bd->channels[i]->ch_open_count ? "Open" : "Closed"); + } + return count; +} +static DEVICE_ATTR(ports_state, S_IRUSR, dgnc_ports_state_show, NULL); + + +static ssize_t dgnc_ports_baud_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, + "%d %d\n", bd->channels[i]->ch_portnum, bd->channels[i]->ch_old_baud); + } + return count; +} +static DEVICE_ATTR(ports_baud, S_IRUSR, dgnc_ports_baud_show, NULL); + + +static ssize_t dgnc_ports_msignals_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + if (bd->channels[i]->ch_open_count) { + count += snprintf(buf + count, PAGE_SIZE - count, + "%d %s %s %s %s %s %s\n", bd->channels[i]->ch_portnum, + (bd->channels[i]->ch_mostat & UART_MCR_RTS) ? "RTS" : "", + (bd->channels[i]->ch_mistat & UART_MSR_CTS) ? "CTS" : "", + (bd->channels[i]->ch_mostat & UART_MCR_DTR) ? "DTR" : "", + (bd->channels[i]->ch_mistat & UART_MSR_DSR) ? "DSR" : "", + (bd->channels[i]->ch_mistat & UART_MSR_DCD) ? "DCD" : "", + (bd->channels[i]->ch_mistat & UART_MSR_RI) ? "RI" : ""); + } else { + count += snprintf(buf + count, PAGE_SIZE - count, + "%d\n", bd->channels[i]->ch_portnum); + } + } + return count; +} +static DEVICE_ATTR(ports_msignals, S_IRUSR, dgnc_ports_msignals_show, NULL); + + +static ssize_t dgnc_ports_iflag_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_iflag); + } + return count; +} +static DEVICE_ATTR(ports_iflag, S_IRUSR, dgnc_ports_iflag_show, NULL); + + +static ssize_t dgnc_ports_cflag_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_cflag); + } + return count; +} +static DEVICE_ATTR(ports_cflag, S_IRUSR, dgnc_ports_cflag_show, NULL); + + +static ssize_t dgnc_ports_oflag_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_oflag); + } + return count; +} +static DEVICE_ATTR(ports_oflag, S_IRUSR, dgnc_ports_oflag_show, NULL); + + +static ssize_t dgnc_ports_lflag_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_lflag); + } + return count; +} +static DEVICE_ATTR(ports_lflag, S_IRUSR, dgnc_ports_lflag_show, NULL); + + +static ssize_t dgnc_ports_digi_flag_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_digi.digi_flags); + } + return count; +} +static DEVICE_ATTR(ports_digi_flag, S_IRUSR, dgnc_ports_digi_flag_show, NULL); + + +static ssize_t dgnc_ports_rxcount_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_rxcount); + } + return count; +} +static DEVICE_ATTR(ports_rxcount, S_IRUSR, dgnc_ports_rxcount_show, NULL); + + +static ssize_t dgnc_ports_txcount_show(struct device *p, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + int count = 0; + int i = 0; + + DGNC_VERIFY_BOARD(p, bd); + + for (i = 0; i < bd->nasync; i++) { + count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n", + bd->channels[i]->ch_portnum, bd->channels[i]->ch_txcount); + } + return count; +} +static DEVICE_ATTR(ports_txcount, S_IRUSR, dgnc_ports_txcount_show, NULL); + + +/* this function creates the sys files that will export each signal status + * to sysfs each value will be put in a separate filename + */ +void dgnc_create_ports_sysfiles(struct dgnc_board *bd) +{ + int rc = 0; + + dev_set_drvdata(&bd->pdev->dev, bd); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_state); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_baud); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_msignals); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_iflag); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_cflag); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_oflag); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_lflag); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_rxcount); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_txcount); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_vpd); + rc |= device_create_file(&(bd->pdev->dev), &dev_attr_serial_number); + if (rc) + dev_err(&bd->pdev->dev, "dgnc: sysfs device_create_file failed!\n"); +} + + +/* removes all the sys files created for that port */ +void dgnc_remove_ports_sysfiles(struct dgnc_board *bd) +{ + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_state); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_baud); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_msignals); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_iflag); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_cflag); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_oflag); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_lflag); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_rxcount); + device_remove_file(&(bd->pdev->dev), &dev_attr_ports_txcount); + device_remove_file(&(bd->pdev->dev), &dev_attr_vpd); + device_remove_file(&(bd->pdev->dev), &dev_attr_serial_number); +} + + +static ssize_t dgnc_tty_state_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%s", un->un_open_count ? "Open" : "Closed"); +} +static DEVICE_ATTR(state, S_IRUSR, dgnc_tty_state_show, NULL); + + +static ssize_t dgnc_tty_baud_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_old_baud); +} +static DEVICE_ATTR(baud, S_IRUSR, dgnc_tty_baud_show, NULL); + + +static ssize_t dgnc_tty_msignals_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + if (ch->ch_open_count) { + return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n", + (ch->ch_mostat & UART_MCR_RTS) ? "RTS" : "", + (ch->ch_mistat & UART_MSR_CTS) ? "CTS" : "", + (ch->ch_mostat & UART_MCR_DTR) ? "DTR" : "", + (ch->ch_mistat & UART_MSR_DSR) ? "DSR" : "", + (ch->ch_mistat & UART_MSR_DCD) ? "DCD" : "", + (ch->ch_mistat & UART_MSR_RI) ? "RI" : ""); + } + return 0; +} +static DEVICE_ATTR(msignals, S_IRUSR, dgnc_tty_msignals_show, NULL); + + +static ssize_t dgnc_tty_iflag_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_iflag); +} +static DEVICE_ATTR(iflag, S_IRUSR, dgnc_tty_iflag_show, NULL); + + +static ssize_t dgnc_tty_cflag_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_cflag); +} +static DEVICE_ATTR(cflag, S_IRUSR, dgnc_tty_cflag_show, NULL); + + +static ssize_t dgnc_tty_oflag_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_oflag); +} +static DEVICE_ATTR(oflag, S_IRUSR, dgnc_tty_oflag_show, NULL); + + +static ssize_t dgnc_tty_lflag_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_lflag); +} +static DEVICE_ATTR(lflag, S_IRUSR, dgnc_tty_lflag_show, NULL); + + +static ssize_t dgnc_tty_digi_flag_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags); +} +static DEVICE_ATTR(digi_flag, S_IRUSR, dgnc_tty_digi_flag_show, NULL); + + +static ssize_t dgnc_tty_rxcount_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_rxcount); +} +static DEVICE_ATTR(rxcount, S_IRUSR, dgnc_tty_rxcount_show, NULL); + + +static ssize_t dgnc_tty_txcount_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_txcount); +} +static DEVICE_ATTR(txcount, S_IRUSR, dgnc_tty_txcount_show, NULL); + + +static ssize_t dgnc_tty_name_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + + if (!d) + return 0; + un = dev_get_drvdata(d); + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return 0; + if (bd->state != BOARD_READY) + return 0; + + return snprintf(buf, PAGE_SIZE, "%sn%d%c\n", + (un->un_type == DGNC_PRINT) ? "pr" : "tty", + bd->boardnum + 1, 'a' + ch->ch_portnum); +} +static DEVICE_ATTR(custom_name, S_IRUSR, dgnc_tty_name_show, NULL); + + +static struct attribute *dgnc_sysfs_tty_entries[] = { + &dev_attr_state.attr, + &dev_attr_baud.attr, + &dev_attr_msignals.attr, + &dev_attr_iflag.attr, + &dev_attr_cflag.attr, + &dev_attr_oflag.attr, + &dev_attr_lflag.attr, + &dev_attr_digi_flag.attr, + &dev_attr_rxcount.attr, + &dev_attr_txcount.attr, + &dev_attr_custom_name.attr, + NULL +}; + + +static struct attribute_group dgnc_tty_attribute_group = { + .name = NULL, + .attrs = dgnc_sysfs_tty_entries, +}; + + +void dgnc_create_tty_sysfs(struct un_t *un, struct device *c) +{ + int ret; + + ret = sysfs_create_group(&c->kobj, &dgnc_tty_attribute_group); + if (ret) { + dev_err(c, "dgnc: failed to create sysfs tty device attributes.\n"); + sysfs_remove_group(&c->kobj, &dgnc_tty_attribute_group); + return; + } + + dev_set_drvdata(c, un); + +} + + +void dgnc_remove_tty_sysfs(struct device *c) +{ + sysfs_remove_group(&c->kobj, &dgnc_tty_attribute_group); +} + diff --git a/drivers/staging/dgnc/dgnc_sysfs.h b/drivers/staging/dgnc/dgnc_sysfs.h new file mode 100644 index 000000000..be0f90a67 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_sysfs.h @@ -0,0 +1,40 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DGNC_SYSFS_H +#define __DGNC_SYSFS_H + +#include +#include "dgnc_driver.h" + +struct dgnc_board; +struct channel_t; +struct un_t; +struct pci_driver; +struct class_device; + +extern void dgnc_create_ports_sysfiles(struct dgnc_board *bd); +extern void dgnc_remove_ports_sysfiles(struct dgnc_board *bd); + +extern void dgnc_create_driver_sysfiles(struct pci_driver *); +extern void dgnc_remove_driver_sysfiles(struct pci_driver *); + +extern int dgnc_tty_class_init(void); +extern int dgnc_tty_class_destroy(void); + +extern void dgnc_create_tty_sysfs(struct un_t *un, struct device *c); +extern void dgnc_remove_tty_sysfs(struct device *c); + +#endif diff --git a/drivers/staging/dgnc/dgnc_tty.c b/drivers/staging/dgnc/dgnc_tty.c new file mode 100644 index 000000000..ce4187f60 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_tty.c @@ -0,0 +1,3057 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +/************************************************************************ + * + * This file implements the tty driver functionality for the + * Neo and ClassicBoard PCI based product lines. + * + ************************************************************************ + * + */ + +#include +#include /* For jiffies, task states */ +#include /* For tasklet and interrupt structs/defines */ +#include +#include +#include +#include +#include +#include +#include +#include /* For udelay */ +#include /* For copy_from_user/copy_to_user */ +#include +#include "dgnc_driver.h" +#include "dgnc_tty.h" +#include "dgnc_neo.h" +#include "dgnc_cls.h" +#include "dgnc_sysfs.h" +#include "dgnc_utils.h" + +#define init_MUTEX(sem) sema_init(sem, 1) +#define DECLARE_MUTEX(name) \ + struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1) + +/* + * internal variables + */ +static struct dgnc_board *dgnc_BoardsByMajor[256]; +static unsigned char *dgnc_TmpWriteBuf; +static DECLARE_MUTEX(dgnc_TmpWriteSem); + +/* + * Default transparent print information. + */ +static struct digi_t dgnc_digi_init = { + .digi_flags = DIGI_COOK, /* Flags */ + .digi_maxcps = 100, /* Max CPS */ + .digi_maxchar = 50, /* Max chars in print queue */ + .digi_bufsize = 100, /* Printer buffer size */ + .digi_onlen = 4, /* size of printer on string */ + .digi_offlen = 4, /* size of printer off string */ + .digi_onstr = "\033[5i", /* ANSI printer on string ] */ + .digi_offstr = "\033[4i", /* ANSI printer off string ] */ + .digi_term = "ansi" /* default terminal type */ +}; + + +/* + * Define a local default termios struct. All ports will be created + * with this termios initially. + * + * This defines a raw port at 9600 baud, 8 data bits, no parity, + * 1 stop bit. + */ +static struct ktermios DgncDefaultTermios = { + .c_iflag = (DEFAULT_IFLAGS), /* iflags */ + .c_oflag = (DEFAULT_OFLAGS), /* oflags */ + .c_cflag = (DEFAULT_CFLAGS), /* cflags */ + .c_lflag = (DEFAULT_LFLAGS), /* lflags */ + .c_cc = INIT_C_CC, + .c_line = 0, +}; + + +/* Our function prototypes */ +static int dgnc_tty_open(struct tty_struct *tty, struct file *file); +static void dgnc_tty_close(struct tty_struct *tty, struct file *file); +static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch); +static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg); +static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo); +static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info); +static int dgnc_tty_write_room(struct tty_struct *tty); +static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c); +static int dgnc_tty_chars_in_buffer(struct tty_struct *tty); +static void dgnc_tty_start(struct tty_struct *tty); +static void dgnc_tty_stop(struct tty_struct *tty); +static void dgnc_tty_throttle(struct tty_struct *tty); +static void dgnc_tty_unthrottle(struct tty_struct *tty); +static void dgnc_tty_flush_chars(struct tty_struct *tty); +static void dgnc_tty_flush_buffer(struct tty_struct *tty); +static void dgnc_tty_hangup(struct tty_struct *tty); +static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value); +static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value); +static int dgnc_tty_tiocmget(struct tty_struct *tty); +static int dgnc_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); +static int dgnc_tty_send_break(struct tty_struct *tty, int msec); +static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout); +static int dgnc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count); +static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios); +static void dgnc_tty_send_xchar(struct tty_struct *tty, char ch); + + +static const struct tty_operations dgnc_tty_ops = { + .open = dgnc_tty_open, + .close = dgnc_tty_close, + .write = dgnc_tty_write, + .write_room = dgnc_tty_write_room, + .flush_buffer = dgnc_tty_flush_buffer, + .chars_in_buffer = dgnc_tty_chars_in_buffer, + .flush_chars = dgnc_tty_flush_chars, + .ioctl = dgnc_tty_ioctl, + .set_termios = dgnc_tty_set_termios, + .stop = dgnc_tty_stop, + .start = dgnc_tty_start, + .throttle = dgnc_tty_throttle, + .unthrottle = dgnc_tty_unthrottle, + .hangup = dgnc_tty_hangup, + .put_char = dgnc_tty_put_char, + .tiocmget = dgnc_tty_tiocmget, + .tiocmset = dgnc_tty_tiocmset, + .break_ctl = dgnc_tty_send_break, + .wait_until_sent = dgnc_tty_wait_until_sent, + .send_xchar = dgnc_tty_send_xchar +}; + +/************************************************************************ + * + * TTY Initialization/Cleanup Functions + * + ************************************************************************/ + +/* + * dgnc_tty_preinit() + * + * Initialize any global tty related data before we download any boards. + */ +int dgnc_tty_preinit(void) +{ + /* + * Allocate a buffer for doing the copy from user space to + * kernel space in dgnc_write(). We only use one buffer and + * control access to it with a semaphore. If we are paging, we + * are already in trouble so one buffer won't hurt much anyway. + * + * We are okay to sleep in the malloc, as this routine + * is only called during module load, (not in interrupt context), + * and with no locks held. + */ + dgnc_TmpWriteBuf = kmalloc(WRITEBUFLEN, GFP_KERNEL); + + if (!dgnc_TmpWriteBuf) + return -ENOMEM; + + return 0; +} + + +/* + * dgnc_tty_register() + * + * Init the tty subsystem for this board. + */ +int dgnc_tty_register(struct dgnc_board *brd) +{ + int rc = 0; + + brd->SerialDriver.magic = TTY_DRIVER_MAGIC; + + snprintf(brd->SerialName, MAXTTYNAMELEN, "tty_dgnc_%d_", brd->boardnum); + + brd->SerialDriver.name = brd->SerialName; + brd->SerialDriver.name_base = 0; + brd->SerialDriver.major = 0; + brd->SerialDriver.minor_start = 0; + brd->SerialDriver.num = brd->maxports; + brd->SerialDriver.type = TTY_DRIVER_TYPE_SERIAL; + brd->SerialDriver.subtype = SERIAL_TYPE_NORMAL; + brd->SerialDriver.init_termios = DgncDefaultTermios; + brd->SerialDriver.driver_name = DRVSTR; + brd->SerialDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK); + + /* + * The kernel wants space to store pointers to + * tty_struct's and termios's. + */ + brd->SerialDriver.ttys = kcalloc(brd->maxports, sizeof(*brd->SerialDriver.ttys), GFP_KERNEL); + if (!brd->SerialDriver.ttys) + return -ENOMEM; + + kref_init(&brd->SerialDriver.kref); + brd->SerialDriver.termios = kcalloc(brd->maxports, sizeof(*brd->SerialDriver.termios), GFP_KERNEL); + if (!brd->SerialDriver.termios) + return -ENOMEM; + + /* + * Entry points for driver. Called by the kernel from + * tty_io.c and n_tty.c. + */ + tty_set_operations(&brd->SerialDriver, &dgnc_tty_ops); + + if (!brd->dgnc_Major_Serial_Registered) { + /* Register tty devices */ + rc = tty_register_driver(&brd->SerialDriver); + if (rc < 0) { + dev_dbg(&brd->pdev->dev, + "Can't register tty device (%d)\n", rc); + return rc; + } + brd->dgnc_Major_Serial_Registered = true; + } + + /* + * If we're doing transparent print, we have to do all of the above + * again, separately so we don't get the LD confused about what major + * we are when we get into the dgnc_tty_open() routine. + */ + brd->PrintDriver.magic = TTY_DRIVER_MAGIC; + snprintf(brd->PrintName, MAXTTYNAMELEN, "pr_dgnc_%d_", brd->boardnum); + + brd->PrintDriver.name = brd->PrintName; + brd->PrintDriver.name_base = 0; + brd->PrintDriver.major = brd->SerialDriver.major; + brd->PrintDriver.minor_start = 0x80; + brd->PrintDriver.num = brd->maxports; + brd->PrintDriver.type = TTY_DRIVER_TYPE_SERIAL; + brd->PrintDriver.subtype = SERIAL_TYPE_NORMAL; + brd->PrintDriver.init_termios = DgncDefaultTermios; + brd->PrintDriver.driver_name = DRVSTR; + brd->PrintDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK); + + /* + * The kernel wants space to store pointers to + * tty_struct's and termios's. Must be separated from + * the Serial Driver so we don't get confused + */ + brd->PrintDriver.ttys = kcalloc(brd->maxports, sizeof(*brd->PrintDriver.ttys), GFP_KERNEL); + if (!brd->PrintDriver.ttys) + return -ENOMEM; + kref_init(&brd->PrintDriver.kref); + brd->PrintDriver.termios = kcalloc(brd->maxports, sizeof(*brd->PrintDriver.termios), GFP_KERNEL); + if (!brd->PrintDriver.termios) + return -ENOMEM; + + /* + * Entry points for driver. Called by the kernel from + * tty_io.c and n_tty.c. + */ + tty_set_operations(&brd->PrintDriver, &dgnc_tty_ops); + + if (!brd->dgnc_Major_TransparentPrint_Registered) { + /* Register Transparent Print devices */ + rc = tty_register_driver(&brd->PrintDriver); + if (rc < 0) { + dev_dbg(&brd->pdev->dev, + "Can't register Transparent Print device(%d)\n", + rc); + return rc; + } + brd->dgnc_Major_TransparentPrint_Registered = true; + } + + dgnc_BoardsByMajor[brd->SerialDriver.major] = brd; + brd->dgnc_Serial_Major = brd->SerialDriver.major; + brd->dgnc_TransparentPrint_Major = brd->PrintDriver.major; + + return rc; +} + + +/* + * dgnc_tty_init() + * + * Init the tty subsystem. Called once per board after board has been + * downloaded and init'ed. + */ +int dgnc_tty_init(struct dgnc_board *brd) +{ + int i; + void __iomem *vaddr; + struct channel_t *ch; + + if (!brd) + return -ENXIO; + + /* + * Initialize board structure elements. + */ + + vaddr = brd->re_map_membase; + + brd->nasync = brd->maxports; + + /* + * Allocate channel memory that might not have been allocated + * when the driver was first loaded. + */ + for (i = 0; i < brd->nasync; i++) { + if (!brd->channels[i]) { + + /* + * Okay to malloc with GFP_KERNEL, we are not at + * interrupt context, and there are no locks held. + */ + brd->channels[i] = kzalloc(sizeof(*brd->channels[i]), GFP_KERNEL); + } + } + + ch = brd->channels[0]; + vaddr = brd->re_map_membase; + + /* Set up channel variables */ + for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) { + + if (!brd->channels[i]) + continue; + + spin_lock_init(&ch->ch_lock); + + /* Store all our magic numbers */ + ch->magic = DGNC_CHANNEL_MAGIC; + ch->ch_tun.magic = DGNC_UNIT_MAGIC; + ch->ch_tun.un_ch = ch; + ch->ch_tun.un_type = DGNC_SERIAL; + ch->ch_tun.un_dev = i; + + ch->ch_pun.magic = DGNC_UNIT_MAGIC; + ch->ch_pun.un_ch = ch; + ch->ch_pun.un_type = DGNC_PRINT; + ch->ch_pun.un_dev = i + 128; + + if (brd->bd_uart_offset == 0x200) + ch->ch_neo_uart = vaddr + (brd->bd_uart_offset * i); + else + ch->ch_cls_uart = vaddr + (brd->bd_uart_offset * i); + + ch->ch_bd = brd; + ch->ch_portnum = i; + ch->ch_digi = dgnc_digi_init; + + /* .25 second delay */ + ch->ch_close_delay = 250; + + init_waitqueue_head(&ch->ch_flags_wait); + init_waitqueue_head(&ch->ch_tun.un_flags_wait); + init_waitqueue_head(&ch->ch_pun.un_flags_wait); + + { + struct device *classp; + + classp = tty_register_device(&brd->SerialDriver, i, + &(ch->ch_bd->pdev->dev)); + ch->ch_tun.un_sysfs = classp; + dgnc_create_tty_sysfs(&ch->ch_tun, classp); + + classp = tty_register_device(&brd->PrintDriver, i, + &(ch->ch_bd->pdev->dev)); + ch->ch_pun.un_sysfs = classp; + dgnc_create_tty_sysfs(&ch->ch_pun, classp); + } + + } + + return 0; +} + + +/* + * dgnc_tty_post_uninit() + * + * UnInitialize any global tty related data. + */ +void dgnc_tty_post_uninit(void) +{ + kfree(dgnc_TmpWriteBuf); + dgnc_TmpWriteBuf = NULL; +} + + +/* + * dgnc_tty_uninit() + * + * Uninitialize the TTY portion of this driver. Free all memory and + * resources. + */ +void dgnc_tty_uninit(struct dgnc_board *brd) +{ + int i = 0; + + if (brd->dgnc_Major_Serial_Registered) { + dgnc_BoardsByMajor[brd->SerialDriver.major] = NULL; + brd->dgnc_Serial_Major = 0; + for (i = 0; i < brd->nasync; i++) { + dgnc_remove_tty_sysfs(brd->channels[i]->ch_tun.un_sysfs); + tty_unregister_device(&brd->SerialDriver, i); + } + tty_unregister_driver(&brd->SerialDriver); + brd->dgnc_Major_Serial_Registered = false; + } + + if (brd->dgnc_Major_TransparentPrint_Registered) { + dgnc_BoardsByMajor[brd->PrintDriver.major] = NULL; + brd->dgnc_TransparentPrint_Major = 0; + for (i = 0; i < brd->nasync; i++) { + dgnc_remove_tty_sysfs(brd->channels[i]->ch_pun.un_sysfs); + tty_unregister_device(&brd->PrintDriver, i); + } + tty_unregister_driver(&brd->PrintDriver); + brd->dgnc_Major_TransparentPrint_Registered = false; + } + + kfree(brd->SerialDriver.ttys); + brd->SerialDriver.ttys = NULL; + kfree(brd->PrintDriver.ttys); + brd->PrintDriver.ttys = NULL; +} + + +#define TMPBUFLEN (1024) + +/*======================================================================= + * + * dgnc_wmove - Write data to transmit queue. + * + * ch - Pointer to channel structure. + * buf - Poiter to characters to be moved. + * n - Number of characters to move. + * + *=======================================================================*/ +static void dgnc_wmove(struct channel_t *ch, char *buf, uint n) +{ + int remain; + uint head; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + head = ch->ch_w_head & WQUEUEMASK; + + /* + * If the write wraps over the top of the circular buffer, + * move the portion up to the wrap point, and reset the + * pointers to the bottom. + */ + remain = WQUEUESIZE - head; + + if (n >= remain) { + n -= remain; + memcpy(ch->ch_wqueue + head, buf, remain); + head = 0; + buf += remain; + } + + if (n > 0) { + /* + * Move rest of data. + */ + remain = n; + memcpy(ch->ch_wqueue + head, buf, remain); + head += remain; + } + + head &= WQUEUEMASK; + ch->ch_w_head = head; +} + + + + +/*======================================================================= + * + * dgnc_input - Process received data. + * + * ch - Pointer to channel structure. + * + *=======================================================================*/ +void dgnc_input(struct channel_t *ch) +{ + struct dgnc_board *bd; + struct tty_struct *tp; + struct tty_ldisc *ld = NULL; + uint rmask; + ushort head; + ushort tail; + int data_len; + unsigned long flags; + int flip_len; + int len = 0; + int n = 0; + int s = 0; + int i = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + tp = ch->ch_tun.un_tty; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Figure the number of characters in the buffer. + * Exit immediately if none. + */ + rmask = RQUEUEMASK; + head = ch->ch_r_head & rmask; + tail = ch->ch_r_tail & rmask; + data_len = (head - tail) & rmask; + + if (data_len == 0) + goto exit_unlock; + + /* + * If the device is not open, or CREAD is off, + * flush input data and return immediately. + */ + if (!tp || (tp->magic != TTY_MAGIC) || !(ch->ch_tun.un_flags & UN_ISOPEN) || + !(tp->termios.c_cflag & CREAD) || (ch->ch_tun.un_flags & UN_CLOSING)) { + + ch->ch_r_head = tail; + + /* Force queue flow control to be released, if needed */ + dgnc_check_queue_flow_control(ch); + + goto exit_unlock; + } + + /* + * If we are throttled, simply don't read any data. + */ + if (ch->ch_flags & CH_FORCED_STOPI) + goto exit_unlock; + + flip_len = TTY_FLIPBUF_SIZE; + + /* Chop down the length, if needed */ + len = min(data_len, flip_len); + len = min(len, (N_TTY_BUF_SIZE - 1)); + + ld = tty_ldisc_ref(tp); + +#ifdef TTY_DONT_FLIP + /* + * If the DONT_FLIP flag is on, don't flush our buffer, and act + * like the ld doesn't have any space to put the data right now. + */ + if (test_bit(TTY_DONT_FLIP, &tp->flags)) + len = 0; +#endif + + /* + * If we were unable to get a reference to the ld, + * don't flush our buffer, and act like the ld doesn't + * have any space to put the data right now. + */ + if (!ld) { + len = 0; + } else { + /* + * If ld doesn't have a pointer to a receive_buf function, + * flush the data, then act like the ld doesn't have any + * space to put the data right now. + */ + if (!ld->ops->receive_buf) { + ch->ch_r_head = ch->ch_r_tail; + len = 0; + } + } + + if (len <= 0) + goto exit_unlock; + + /* + * The tty layer in the kernel has changed in 2.6.16+. + * + * The flip buffers in the tty structure are no longer exposed, + * and probably will be going away eventually. + * + * If we are completely raw, we don't need to go through a lot + * of the tty layers that exist. + * In this case, we take the shortest and fastest route we + * can to relay the data to the user. + * + * On the other hand, if we are not raw, we need to go through + * the new 2.6.16+ tty layer, which has its API more well defined. + */ + len = tty_buffer_request_room(tp->port, len); + n = len; + + /* + * n now contains the most amount of data we can copy, + * bounded either by how much the Linux tty layer can handle, + * or the amount of data the card actually has pending... + */ + while (n) { + s = ((head >= tail) ? head : RQUEUESIZE) - tail; + s = min(s, n); + + if (s <= 0) + break; + + /* + * If conditions are such that ld needs to see all + * UART errors, we will have to walk each character + * and error byte and send them to the buffer one at + * a time. + */ + if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) { + for (i = 0; i < s; i++) { + if (*(ch->ch_equeue + tail + i) & UART_LSR_BI) + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_BREAK); + else if (*(ch->ch_equeue + tail + i) & UART_LSR_PE) + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_PARITY); + else if (*(ch->ch_equeue + tail + i) & UART_LSR_FE) + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_FRAME); + else + tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_NORMAL); + } + } else { + tty_insert_flip_string(tp->port, ch->ch_rqueue + tail, s); + } + + tail += s; + n -= s; + /* Flip queue if needed */ + tail &= rmask; + } + + ch->ch_r_tail = tail & rmask; + ch->ch_e_tail = tail & rmask; + dgnc_check_queue_flow_control(ch); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* Tell the tty layer its okay to "eat" the data now */ + tty_flip_buffer_push(tp->port); + + if (ld) + tty_ldisc_deref(ld); + return; + +exit_unlock: + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (ld) + tty_ldisc_deref(ld); +} + + +/************************************************************************ + * Determines when CARRIER changes state and takes appropriate + * action. + ************************************************************************/ +void dgnc_carrier(struct channel_t *ch) +{ + struct dgnc_board *bd; + + int virt_carrier = 0; + int phys_carrier = 0; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + if (ch->ch_mistat & UART_MSR_DCD) + phys_carrier = 1; + + if (ch->ch_digi.digi_flags & DIGI_FORCEDCD) + virt_carrier = 1; + + if (ch->ch_c_cflag & CLOCAL) + virt_carrier = 1; + + /* + * Test for a VIRTUAL carrier transition to HIGH. + */ + if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) { + + /* + * When carrier rises, wake any threads waiting + * for carrier in the open routine. + */ + + if (waitqueue_active(&(ch->ch_flags_wait))) + wake_up_interruptible(&ch->ch_flags_wait); + } + + /* + * Test for a PHYSICAL carrier transition to HIGH. + */ + if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) { + + /* + * When carrier rises, wake any threads waiting + * for carrier in the open routine. + */ + + if (waitqueue_active(&(ch->ch_flags_wait))) + wake_up_interruptible(&ch->ch_flags_wait); + } + + /* + * Test for a PHYSICAL transition to low, so long as we aren't + * currently ignoring physical transitions (which is what "virtual + * carrier" indicates). + * + * The transition of the virtual carrier to low really doesn't + * matter... it really only means "ignore carrier state", not + * "make pretend that carrier is there". + */ + if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0) && + (phys_carrier == 0)) { + + /* + * When carrier drops: + * + * Drop carrier on all open units. + * + * Flush queues, waking up any task waiting in the + * line discipline. + * + * Send a hangup to the control terminal. + * + * Enable all select calls. + */ + if (waitqueue_active(&(ch->ch_flags_wait))) + wake_up_interruptible(&ch->ch_flags_wait); + + if (ch->ch_tun.un_open_count > 0) + tty_hangup(ch->ch_tun.un_tty); + + if (ch->ch_pun.un_open_count > 0) + tty_hangup(ch->ch_pun.un_tty); + } + + /* + * Make sure that our cached values reflect the current reality. + */ + if (virt_carrier == 1) + ch->ch_flags |= CH_FCAR; + else + ch->ch_flags &= ~CH_FCAR; + + if (phys_carrier == 1) + ch->ch_flags |= CH_CD; + else + ch->ch_flags &= ~CH_CD; +} + +/* + * Assign the custom baud rate to the channel structure + */ +static void dgnc_set_custom_speed(struct channel_t *ch, uint newrate) +{ + int testdiv; + int testrate_high; + int testrate_low; + int deltahigh; + int deltalow; + + if (newrate <= 0) { + ch->ch_custom_speed = 0; + return; + } + + /* + * Since the divisor is stored in a 16-bit integer, we make sure + * we don't allow any rates smaller than a 16-bit integer would allow. + * And of course, rates above the dividend won't fly. + */ + if (newrate && newrate < ((ch->ch_bd->bd_dividend / 0xFFFF) + 1)) + newrate = ((ch->ch_bd->bd_dividend / 0xFFFF) + 1); + + if (newrate && newrate > ch->ch_bd->bd_dividend) + newrate = ch->ch_bd->bd_dividend; + + if (newrate > 0) { + testdiv = ch->ch_bd->bd_dividend / newrate; + + /* + * If we try to figure out what rate the board would use + * with the test divisor, it will be either equal or higher + * than the requested baud rate. If we then determine the + * rate with a divisor one higher, we will get the next lower + * supported rate below the requested. + */ + testrate_high = ch->ch_bd->bd_dividend / testdiv; + testrate_low = ch->ch_bd->bd_dividend / (testdiv + 1); + + /* + * If the rate for the requested divisor is correct, just + * use it and be done. + */ + if (testrate_high != newrate) { + /* + * Otherwise, pick the rate that is closer (i.e. whichever rate + * has a smaller delta). + */ + deltahigh = testrate_high - newrate; + deltalow = newrate - testrate_low; + + if (deltahigh < deltalow) + newrate = testrate_high; + else + newrate = testrate_low; + } + } + + ch->ch_custom_speed = newrate; +} + + +void dgnc_check_queue_flow_control(struct channel_t *ch) +{ + int qleft = 0; + + /* Store how much space we have left in the queue */ + qleft = ch->ch_r_tail - ch->ch_r_head - 1; + if (qleft < 0) + qleft += RQUEUEMASK + 1; + + /* + * Check to see if we should enforce flow control on our queue because + * the ld (or user) isn't reading data out of our queue fast enuf. + * + * NOTE: This is done based on what the current flow control of the + * port is set for. + * + * 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt. + * This will cause the UART's FIFO to back up, and force + * the RTS signal to be dropped. + * 2) SWFLOW (IXOFF) - Keep trying to send a stop character to + * the other side, in hopes it will stop sending data to us. + * 3) NONE - Nothing we can do. We will simply drop any extra data + * that gets sent into us when the queue fills up. + */ + if (qleft < 256) { + /* HWFLOW */ + if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) { + if (!(ch->ch_flags & CH_RECEIVER_OFF)) { + ch->ch_bd->bd_ops->disable_receiver(ch); + ch->ch_flags |= (CH_RECEIVER_OFF); + } + } + /* SWFLOW */ + else if (ch->ch_c_iflag & IXOFF) { + if (ch->ch_stops_sent <= MAX_STOPS_SENT) { + ch->ch_bd->bd_ops->send_stop_character(ch); + ch->ch_stops_sent++; + } + } + } + + /* + * Check to see if we should unenforce flow control because + * ld (or user) finally read enuf data out of our queue. + * + * NOTE: This is done based on what the current flow control of the + * port is set for. + * + * 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt. + * This will cause the UART's FIFO to raise RTS back up, + * which will allow the other side to start sending data again. + * 2) SWFLOW (IXOFF) - Send a start character to + * the other side, so it will start sending data to us again. + * 3) NONE - Do nothing. Since we didn't do anything to turn off the + * other side, we don't need to do anything now. + */ + if (qleft > (RQUEUESIZE / 2)) { + /* HWFLOW */ + if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) { + if (ch->ch_flags & CH_RECEIVER_OFF) { + ch->ch_bd->bd_ops->enable_receiver(ch); + ch->ch_flags &= ~(CH_RECEIVER_OFF); + } + } + /* SWFLOW */ + else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) { + ch->ch_stops_sent = 0; + ch->ch_bd->bd_ops->send_start_character(ch); + } + /* No FLOW */ + else { + /* Nothing needed. */ + } + } +} + + +void dgnc_wakeup_writes(struct channel_t *ch) +{ + int qlen = 0; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * If channel now has space, wake up anyone waiting on the condition. + */ + qlen = ch->ch_w_head - ch->ch_w_tail; + if (qlen < 0) + qlen += WQUEUESIZE; + + if (qlen >= (WQUEUESIZE - 256)) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return; + } + + if (ch->ch_tun.un_flags & UN_ISOPEN) { + if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + ch->ch_tun.un_tty->ldisc->ops->write_wakeup) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty); + spin_lock_irqsave(&ch->ch_lock, flags); + } + + wake_up_interruptible(&ch->ch_tun.un_tty->write_wait); + + /* + * If unit is set to wait until empty, check to make sure + * the queue AND FIFO are both empty. + */ + if (ch->ch_tun.un_flags & UN_EMPTY) { + if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) { + ch->ch_tun.un_flags &= ~(UN_EMPTY); + + /* + * If RTS Toggle mode is on, whenever + * the queue and UART is empty, keep RTS low. + */ + if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) { + ch->ch_mostat &= ~(UART_MCR_RTS); + ch->ch_bd->bd_ops->assert_modem_signals(ch); + } + + /* + * If DTR Toggle mode is on, whenever + * the queue and UART is empty, keep DTR low. + */ + if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) { + ch->ch_mostat &= ~(UART_MCR_DTR); + ch->ch_bd->bd_ops->assert_modem_signals(ch); + } + } + } + + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + + if (ch->ch_pun.un_flags & UN_ISOPEN) { + if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + ch->ch_pun.un_tty->ldisc->ops->write_wakeup) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty); + spin_lock_irqsave(&ch->ch_lock, flags); + } + + wake_up_interruptible(&ch->ch_pun.un_tty->write_wait); + + /* + * If unit is set to wait until empty, check to make sure + * the queue AND FIFO are both empty. + */ + if (ch->ch_pun.un_flags & UN_EMPTY) { + if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) + ch->ch_pun.un_flags &= ~(UN_EMPTY); + } + + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + + +/************************************************************************ + * + * TTY Entry points and helper functions + * + ************************************************************************/ + +/* + * dgnc_tty_open() + * + */ +static int dgnc_tty_open(struct tty_struct *tty, struct file *file) +{ + struct dgnc_board *brd; + struct channel_t *ch; + struct un_t *un; + uint major = 0; + uint minor = 0; + int rc = 0; + unsigned long flags; + + rc = 0; + + major = MAJOR(tty_devnum(tty)); + minor = MINOR(tty_devnum(tty)); + + if (major > 255) + return -ENXIO; + + /* Get board pointer from our array of majors we have allocated */ + brd = dgnc_BoardsByMajor[major]; + if (!brd) + return -ENXIO; + + /* + * If board is not yet up to a state of READY, go to + * sleep waiting for it to happen or they cancel the open. + */ + rc = wait_event_interruptible(brd->state_wait, + (brd->state & BOARD_READY)); + + if (rc) + return rc; + + spin_lock_irqsave(&brd->bd_lock, flags); + + /* If opened device is greater than our number of ports, bail. */ + if (PORT_NUM(minor) >= brd->nasync) { + spin_unlock_irqrestore(&brd->bd_lock, flags); + return -ENXIO; + } + + ch = brd->channels[PORT_NUM(minor)]; + if (!ch) { + spin_unlock_irqrestore(&brd->bd_lock, flags); + return -ENXIO; + } + + /* Drop board lock */ + spin_unlock_irqrestore(&brd->bd_lock, flags); + + /* Grab channel lock */ + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Figure out our type */ + if (!IS_PRINT(minor)) { + un = &brd->channels[PORT_NUM(minor)]->ch_tun; + un->un_type = DGNC_SERIAL; + } else if (IS_PRINT(minor)) { + un = &brd->channels[PORT_NUM(minor)]->ch_pun; + un->un_type = DGNC_PRINT; + } else { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return -ENXIO; + } + + /* + * If the port is still in a previous open, and in a state + * where we simply cannot safely keep going, wait until the + * state clears. + */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + + rc = wait_event_interruptible(ch->ch_flags_wait, ((ch->ch_flags & CH_OPENING) == 0)); + + /* If ret is non-zero, user ctrl-c'ed us */ + if (rc) + return -EINTR; + + /* + * If either unit is in the middle of the fragile part of close, + * we just cannot touch the channel safely. + * Go to sleep, knowing that when the channel can be + * touched safely, the close routine will signal the + * ch_flags_wait to wake us back up. + */ + rc = wait_event_interruptible(ch->ch_flags_wait, + (((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING) == 0)); + + /* If ret is non-zero, user ctrl-c'ed us */ + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + + /* Store our unit into driver_data, so we always have it available. */ + tty->driver_data = un; + + + /* + * Initialize tty's + */ + if (!(un->un_flags & UN_ISOPEN)) { + /* Store important variables. */ + un->un_tty = tty; + + /* Maybe do something here to the TTY struct as well? */ + } + + + /* + * Allocate channel buffers for read/write/error. + * Set flag, so we don't get trounced on. + */ + ch->ch_flags |= (CH_OPENING); + + /* Drop locks, as malloc with GFP_KERNEL can sleep */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (!ch->ch_rqueue) + ch->ch_rqueue = kzalloc(RQUEUESIZE, GFP_KERNEL); + if (!ch->ch_equeue) + ch->ch_equeue = kzalloc(EQUEUESIZE, GFP_KERNEL); + if (!ch->ch_wqueue) + ch->ch_wqueue = kzalloc(WQUEUESIZE, GFP_KERNEL); + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~(CH_OPENING); + wake_up_interruptible(&ch->ch_flags_wait); + + /* + * Initialize if neither terminal or printer is open. + */ + if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) { + + /* + * Flush input queues. + */ + ch->ch_r_head = 0; + ch->ch_r_tail = 0; + ch->ch_e_head = 0; + ch->ch_e_tail = 0; + ch->ch_w_head = 0; + ch->ch_w_tail = 0; + + brd->bd_ops->flush_uart_write(ch); + brd->bd_ops->flush_uart_read(ch); + + ch->ch_flags = 0; + ch->ch_cached_lsr = 0; + ch->ch_stop_sending_break = 0; + ch->ch_stops_sent = 0; + + ch->ch_c_cflag = tty->termios.c_cflag; + ch->ch_c_iflag = tty->termios.c_iflag; + ch->ch_c_oflag = tty->termios.c_oflag; + ch->ch_c_lflag = tty->termios.c_lflag; + ch->ch_startc = tty->termios.c_cc[VSTART]; + ch->ch_stopc = tty->termios.c_cc[VSTOP]; + + /* + * Bring up RTS and DTR... + * Also handle RTS or DTR toggle if set. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + + /* Tell UART to init itself */ + brd->bd_ops->uart_init(ch); + } + + /* + * Run param in case we changed anything + */ + brd->bd_ops->param(tty); + + dgnc_carrier(ch); + + /* + * follow protocol for opening port + */ + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + rc = dgnc_block_til_ready(tty, file, ch); + + /* No going back now, increment our unit and channel counters */ + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_open_count++; + un->un_open_count++; + un->un_flags |= (UN_ISOPEN); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return rc; +} + + +/* + * dgnc_block_til_ready() + * + * Wait for DCD, if needed. + */ +static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch) +{ + int retval = 0; + struct un_t *un = NULL; + unsigned long flags; + uint old_flags = 0; + int sleep_on_un_flags = 0; + + if (!tty || tty->magic != TTY_MAGIC || !file || !ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -ENXIO; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_wopen++; + + /* Loop forever */ + while (1) { + + sleep_on_un_flags = 0; + + /* + * If board has failed somehow during our sleep, bail with error. + */ + if (ch->ch_bd->state == BOARD_FAILED) { + retval = -ENXIO; + break; + } + + /* If tty was hung up, break out of loop and set error. */ + if (tty_hung_up_p(file)) { + retval = -EAGAIN; + break; + } + + /* + * If either unit is in the middle of the fragile part of close, + * we just cannot touch the channel safely. + * Go back to sleep, knowing that when the channel can be + * touched safely, the close routine will signal the + * ch_wait_flags to wake us back up. + */ + if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING)) { + + /* + * Our conditions to leave cleanly and happily: + * 1) NONBLOCKING on the tty is set. + * 2) CLOCAL is set. + * 3) DCD (fake or real) is active. + */ + + if (file->f_flags & O_NONBLOCK) + break; + + if (tty->flags & (1 << TTY_IO_ERROR)) { + retval = -EIO; + break; + } + + if (ch->ch_flags & CH_CD) + break; + + if (ch->ch_flags & CH_FCAR) + break; + } else { + sleep_on_un_flags = 1; + } + + /* + * If there is a signal pending, the user probably + * interrupted (ctrl-c) us. + * Leave loop with error set. + */ + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + + /* + * Store the flags before we let go of channel lock + */ + if (sleep_on_un_flags) + old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags; + else + old_flags = ch->ch_flags; + + /* + * Let go of channel lock before calling schedule. + * Our poller will get any FEP events and wake us up when DCD + * eventually goes active. + */ + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * Wait for something in the flags to change from the current value. + */ + if (sleep_on_un_flags) + retval = wait_event_interruptible(un->un_flags_wait, + (old_flags != (ch->ch_tun.un_flags | ch->ch_pun.un_flags))); + else + retval = wait_event_interruptible(ch->ch_flags_wait, + (old_flags != ch->ch_flags)); + + /* + * We got woken up for some reason. + * Before looping around, grab our channel lock. + */ + spin_lock_irqsave(&ch->ch_lock, flags); + } + + ch->ch_wopen--; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (retval) + return retval; + + return 0; +} + + +/* + * dgnc_tty_hangup() + * + * Hangup the port. Like a close, but don't wait for output to drain. + */ +static void dgnc_tty_hangup(struct tty_struct *tty) +{ + struct un_t *un; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + /* flush the transmit queues */ + dgnc_tty_flush_buffer(tty); + +} + + +/* + * dgnc_tty_close() + * + */ +static void dgnc_tty_close(struct tty_struct *tty, struct file *file) +{ + struct ktermios *ts; + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + int rc = 0; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + ts = &tty->termios; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Determine if this is the last close or not - and if we agree about + * which type of close it is with the Line Discipline + */ + if ((tty->count == 1) && (un->un_open_count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. un_open_count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + dev_dbg(tty->dev, + "tty->count is 1, un open count is %d\n", + un->un_open_count); + un->un_open_count = 1; + } + + if (un->un_open_count) + un->un_open_count--; + else + dev_dbg(tty->dev, + "bad serial port open count of %d\n", + un->un_open_count); + + ch->ch_open_count--; + + if (ch->ch_open_count && un->un_open_count) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return; + } + + /* OK, its the last close on the unit */ + un->un_flags |= UN_CLOSING; + + tty->closing = 1; + + + /* + * Only officially close channel if count is 0 and + * DIGI_PRINTER bit is not set. + */ + if ((ch->ch_open_count == 0) && !(ch->ch_digi.digi_flags & DIGI_PRINTER)) { + + ch->ch_flags &= ~(CH_STOPI | CH_FORCED_STOPI); + + /* + * turn off print device when closing print device. + */ + if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_offstr, + (int) ch->ch_digi.digi_offlen); + ch->ch_flags &= ~CH_PRON; + } + + spin_unlock_irqrestore(&ch->ch_lock, flags); + /* wait for output to drain */ + /* This will also return if we take an interrupt */ + + rc = bd->bd_ops->drain(tty, 0); + + dgnc_tty_flush_buffer(tty); + tty_ldisc_flush(tty); + + spin_lock_irqsave(&ch->ch_lock, flags); + + tty->closing = 0; + + /* + * If we have HUPCL set, lower DTR and RTS + */ + if (ch->ch_c_cflag & HUPCL) { + + /* Drop RTS/DTR */ + ch->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS); + bd->bd_ops->assert_modem_signals(ch); + + /* + * Go to sleep to ensure RTS/DTR + * have been dropped for modems to see it. + */ + if (ch->ch_close_delay) { + spin_unlock_irqrestore(&ch->ch_lock, + flags); + dgnc_ms_sleep(ch->ch_close_delay); + spin_lock_irqsave(&ch->ch_lock, flags); + } + } + + ch->ch_old_baud = 0; + + /* Turn off UART interrupts for this port */ + ch->ch_bd->bd_ops->uart_off(ch); + } else { + /* + * turn off print device when closing print device. + */ + if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_offstr, + (int) ch->ch_digi.digi_offlen); + ch->ch_flags &= ~CH_PRON; + } + } + + un->un_tty = NULL; + un->un_flags &= ~(UN_ISOPEN | UN_CLOSING); + + wake_up_interruptible(&ch->ch_flags_wait); + wake_up_interruptible(&un->un_flags_wait); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +/* + * dgnc_tty_chars_in_buffer() + * + * Return number of characters that have not been transmitted yet. + * + * This routine is used by the line discipline to determine if there + * is data waiting to be transmitted/drained/flushed or not. + */ +static int dgnc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + ushort thead; + ushort ttail; + uint tmask; + uint chars = 0; + unsigned long flags; + + if (tty == NULL) + return 0; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + spin_lock_irqsave(&ch->ch_lock, flags); + + tmask = WQUEUEMASK; + thead = ch->ch_w_head & tmask; + ttail = ch->ch_w_tail & tmask; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (ttail == thead) { + chars = 0; + } else { + if (thead >= ttail) + chars = thead - ttail; + else + chars = thead - ttail + WQUEUESIZE; + } + + return chars; +} + + +/* + * dgnc_maxcps_room + * + * Reduces bytes_available to the max number of characters + * that can be sent currently given the maxcps value, and + * returns the new bytes_available. This only affects printer + * output. + */ +static int dgnc_maxcps_room(struct tty_struct *tty, int bytes_available) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + + if (!tty) + return bytes_available; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return bytes_available; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return bytes_available; + + /* + * If its not the Transparent print device, return + * the full data amount. + */ + if (un->un_type != DGNC_PRINT) + return bytes_available; + + if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) { + int cps_limit = 0; + unsigned long current_time = jiffies; + unsigned long buffer_time = current_time + + (HZ * ch->ch_digi.digi_bufsize) / ch->ch_digi.digi_maxcps; + + if (ch->ch_cpstime < current_time) { + /* buffer is empty */ + ch->ch_cpstime = current_time; /* reset ch_cpstime */ + cps_limit = ch->ch_digi.digi_bufsize; + } else if (ch->ch_cpstime < buffer_time) { + /* still room in the buffer */ + cps_limit = ((buffer_time - ch->ch_cpstime) * ch->ch_digi.digi_maxcps) / HZ; + } else { + /* no room in the buffer */ + cps_limit = 0; + } + + bytes_available = min(cps_limit, bytes_available); + } + + return bytes_available; +} + + +/* + * dgnc_tty_write_room() + * + * Return space available in Tx buffer + */ +static int dgnc_tty_write_room(struct tty_struct *tty) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + ushort head; + ushort tail; + ushort tmask; + int ret = 0; + unsigned long flags; + + if (tty == NULL || dgnc_TmpWriteBuf == NULL) + return 0; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + spin_lock_irqsave(&ch->ch_lock, flags); + + tmask = WQUEUEMASK; + head = (ch->ch_w_head) & tmask; + tail = (ch->ch_w_tail) & tmask; + + ret = tail - head - 1; + if (ret < 0) + ret += WQUEUESIZE; + + /* Limit printer to maxcps */ + ret = dgnc_maxcps_room(tty, ret); + + /* + * If we are printer device, leave space for + * possibly both the on and off strings. + */ + if (un->un_type == DGNC_PRINT) { + if (!(ch->ch_flags & CH_PRON)) + ret -= ch->ch_digi.digi_onlen; + ret -= ch->ch_digi.digi_offlen; + } else { + if (ch->ch_flags & CH_PRON) + ret -= ch->ch_digi.digi_offlen; + } + + if (ret < 0) + ret = 0; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return ret; +} + + +/* + * dgnc_tty_put_char() + * + * Put a character into ch->ch_buf + * + * - used by the line discipline for OPOST processing + */ +static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c) +{ + /* + * Simply call tty_write. + */ + dgnc_tty_write(tty, &c, 1); + return 1; +} + + +/* + * dgnc_tty_write() + * + * Take data from the user or kernel and send it out to the FEP. + * In here exists all the Transparent Print magic as well. + */ +static int dgnc_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct channel_t *ch = NULL; + struct un_t *un = NULL; + int bufcount = 0, n = 0; + int orig_count = 0; + unsigned long flags; + ushort head; + ushort tail; + ushort tmask; + uint remain; + int from_user = 0; + + if (tty == NULL || dgnc_TmpWriteBuf == NULL) + return 0; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return 0; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return 0; + + if (!count) + return 0; + + /* + * Store original amount of characters passed in. + * This helps to figure out if we should ask the FEP + * to send us an event when it has more space available. + */ + orig_count = count; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Get our space available for the channel from the board */ + tmask = WQUEUEMASK; + head = (ch->ch_w_head) & tmask; + tail = (ch->ch_w_tail) & tmask; + + bufcount = tail - head - 1; + if (bufcount < 0) + bufcount += WQUEUESIZE; + + /* + * Limit printer output to maxcps overall, with bursts allowed + * up to bufsize characters. + */ + bufcount = dgnc_maxcps_room(tty, bufcount); + + /* + * Take minimum of what the user wants to send, and the + * space available in the FEP buffer. + */ + count = min(count, bufcount); + + /* + * Bail if no space left. + */ + if (count <= 0) + goto exit_retry; + + /* + * Output the printer ON string, if we are in terminal mode, but + * need to be in printer mode. + */ + if ((un->un_type == DGNC_PRINT) && !(ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_onstr, + (int) ch->ch_digi.digi_onlen); + head = (ch->ch_w_head) & tmask; + ch->ch_flags |= CH_PRON; + } + + /* + * On the other hand, output the printer OFF string, if we are + * currently in printer mode, but need to output to the terminal. + */ + if ((un->un_type != DGNC_PRINT) && (ch->ch_flags & CH_PRON)) { + dgnc_wmove(ch, ch->ch_digi.digi_offstr, + (int) ch->ch_digi.digi_offlen); + head = (ch->ch_w_head) & tmask; + ch->ch_flags &= ~CH_PRON; + } + + /* + * If there is nothing left to copy, or I can't handle any more data, leave. + */ + if (count <= 0) + goto exit_retry; + + if (from_user) { + + count = min(count, WRITEBUFLEN); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * If data is coming from user space, copy it into a temporary + * buffer so we don't get swapped out while doing the copy to + * the board. + */ + /* we're allowed to block if it's from_user */ + if (down_interruptible(&dgnc_TmpWriteSem)) + return -EINTR; + + /* + * copy_from_user() returns the number + * of bytes that could *NOT* be copied. + */ + count -= copy_from_user(dgnc_TmpWriteBuf, (const unsigned char __user *) buf, count); + + if (!count) { + up(&dgnc_TmpWriteSem); + return -EFAULT; + } + + spin_lock_irqsave(&ch->ch_lock, flags); + + buf = dgnc_TmpWriteBuf; + + } + + n = count; + + /* + * If the write wraps over the top of the circular buffer, + * move the portion up to the wrap point, and reset the + * pointers to the bottom. + */ + remain = WQUEUESIZE - head; + + if (n >= remain) { + n -= remain; + memcpy(ch->ch_wqueue + head, buf, remain); + head = 0; + buf += remain; + } + + if (n > 0) { + /* + * Move rest of data. + */ + remain = n; + memcpy(ch->ch_wqueue + head, buf, remain); + head += remain; + } + + if (count) { + head &= tmask; + ch->ch_w_head = head; + } + + /* Update printer buffer empty time. */ + if ((un->un_type == DGNC_PRINT) && (ch->ch_digi.digi_maxcps > 0) + && (ch->ch_digi.digi_bufsize > 0)) { + ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps; + } + + if (from_user) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + up(&dgnc_TmpWriteSem); + } else { + spin_unlock_irqrestore(&ch->ch_lock, flags); + } + + if (count) { + /* + * Channel lock is grabbed and then released + * inside this routine. + */ + ch->ch_bd->bd_ops->copy_data_from_queue_to_uart(ch); + } + + return count; + +exit_retry: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; +} + + +/* + * Return modem signals to ld. + */ + +static int dgnc_tty_tiocmget(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + int result = -EIO; + unsigned char mstat = 0; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return result; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return result; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return result; + + spin_lock_irqsave(&ch->ch_lock, flags); + + mstat = (ch->ch_mostat | ch->ch_mistat); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + result = 0; + + if (mstat & UART_MCR_DTR) + result |= TIOCM_DTR; + if (mstat & UART_MCR_RTS) + result |= TIOCM_RTS; + if (mstat & UART_MSR_CTS) + result |= TIOCM_CTS; + if (mstat & UART_MSR_DSR) + result |= TIOCM_DSR; + if (mstat & UART_MSR_RI) + result |= TIOCM_RI; + if (mstat & UART_MSR_DCD) + result |= TIOCM_CD; + + return result; +} + + +/* + * dgnc_tty_tiocmset() + * + * Set modem signals, called by ld. + */ + +static int dgnc_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int ret = -EIO; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return ret; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return ret; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return ret; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return ret; + + spin_lock_irqsave(&ch->ch_lock, flags); + + if (set & TIOCM_RTS) + ch->ch_mostat |= UART_MCR_RTS; + + if (set & TIOCM_DTR) + ch->ch_mostat |= UART_MCR_DTR; + + if (clear & TIOCM_RTS) + ch->ch_mostat &= ~(UART_MCR_RTS); + + if (clear & TIOCM_DTR) + ch->ch_mostat &= ~(UART_MCR_DTR); + + ch->ch_bd->bd_ops->assert_modem_signals(ch); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; +} + + +/* + * dgnc_tty_send_break() + * + * Send a Break, called by ld. + */ +static int dgnc_tty_send_break(struct tty_struct *tty, int msec) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int ret = -EIO; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return ret; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return ret; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return ret; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return ret; + + switch (msec) { + case -1: + msec = 0xFFFF; + break; + case 0: + msec = 0; + break; + default: + break; + } + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->send_break(ch, msec); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + +} + + +/* + * dgnc_tty_wait_until_sent() + * + * wait until data has been transmitted, called by ld. + */ +static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int rc; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + rc = bd->bd_ops->drain(tty, 0); +} + + +/* + * dgnc_send_xchar() + * + * send a high priority character, called by ld. + */ +static void dgnc_tty_send_xchar(struct tty_struct *tty, char c) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + dev_dbg(tty->dev, "dgnc_tty_send_xchar start\n"); + + spin_lock_irqsave(&ch->ch_lock, flags); + bd->bd_ops->send_immediate_char(ch, c); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + dev_dbg(tty->dev, "dgnc_tty_send_xchar finish\n"); +} + + + + +/* + * Return modem signals to ld. + */ +static inline int dgnc_get_mstat(struct channel_t *ch) +{ + unsigned char mstat; + int result = -EIO; + unsigned long flags; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + spin_lock_irqsave(&ch->ch_lock, flags); + + mstat = (ch->ch_mostat | ch->ch_mistat); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + result = 0; + + if (mstat & UART_MCR_DTR) + result |= TIOCM_DTR; + if (mstat & UART_MCR_RTS) + result |= TIOCM_RTS; + if (mstat & UART_MSR_CTS) + result |= TIOCM_CTS; + if (mstat & UART_MSR_DSR) + result |= TIOCM_DSR; + if (mstat & UART_MSR_RI) + result |= TIOCM_RI; + if (mstat & UART_MSR_DCD) + result |= TIOCM_CD; + + return result; +} + + + +/* + * Return modem signals to ld. + */ +static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value) +{ + int result; + + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENXIO; + + result = dgnc_get_mstat(ch); + + if (result < 0) + return -ENXIO; + + return put_user(result, value); +} + + +/* + * dgnc_set_modem_info() + * + * Set modem signals, called by ld. + */ +static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int ret = -ENXIO; + unsigned int arg = 0; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return ret; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return ret; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return ret; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return ret; + + ret = get_user(arg, value); + if (ret) + return ret; + + switch (command) { + case TIOCMBIS: + if (arg & TIOCM_RTS) + ch->ch_mostat |= UART_MCR_RTS; + + if (arg & TIOCM_DTR) + ch->ch_mostat |= UART_MCR_DTR; + + break; + + case TIOCMBIC: + if (arg & TIOCM_RTS) + ch->ch_mostat &= ~(UART_MCR_RTS); + + if (arg & TIOCM_DTR) + ch->ch_mostat &= ~(UART_MCR_DTR); + + break; + + case TIOCMSET: + + if (arg & TIOCM_RTS) + ch->ch_mostat |= UART_MCR_RTS; + else + ch->ch_mostat &= ~(UART_MCR_RTS); + + if (arg & TIOCM_DTR) + ch->ch_mostat |= UART_MCR_DTR; + else + ch->ch_mostat &= ~(UART_MCR_DTR); + + break; + + default: + return -EINVAL; + } + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->assert_modem_signals(ch); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; +} + + +/* + * dgnc_tty_digigeta() + * + * Ioctl to get the information for ditty. + * + * + * + */ +static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo) +{ + struct channel_t *ch; + struct un_t *un; + struct digi_t tmp; + unsigned long flags; + + if (!retinfo) + return -EFAULT; + + if (!tty || tty->magic != TTY_MAGIC) + return -EFAULT; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -EFAULT; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + + spin_lock_irqsave(&ch->ch_lock, flags); + memcpy(&tmp, &ch->ch_digi, sizeof(tmp)); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + + return 0; +} + + +/* + * dgnc_tty_digiseta() + * + * Ioctl to set the information for ditty. + * + * + * + */ +static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + struct digi_t new_digi; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return -EFAULT; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -EFAULT; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -EFAULT; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return -EFAULT; + + if (copy_from_user(&new_digi, new_info, sizeof(new_digi))) + return -EFAULT; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Handle transistions to and from RTS Toggle. + */ + if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && (new_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat &= ~(UART_MCR_RTS); + if ((ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && !(new_digi.digi_flags & DIGI_RTS_TOGGLE)) + ch->ch_mostat |= (UART_MCR_RTS); + + /* + * Handle transistions to and from DTR Toggle. + */ + if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && (new_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat &= ~(UART_MCR_DTR); + if ((ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && !(new_digi.digi_flags & DIGI_DTR_TOGGLE)) + ch->ch_mostat |= (UART_MCR_DTR); + + memcpy(&ch->ch_digi, &new_digi, sizeof(new_digi)); + + if (ch->ch_digi.digi_maxcps < 1) + ch->ch_digi.digi_maxcps = 1; + + if (ch->ch_digi.digi_maxcps > 10000) + ch->ch_digi.digi_maxcps = 10000; + + if (ch->ch_digi.digi_bufsize < 10) + ch->ch_digi.digi_bufsize = 10; + + if (ch->ch_digi.digi_maxchar < 1) + ch->ch_digi.digi_maxchar = 1; + + if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize) + ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize; + + if (ch->ch_digi.digi_onlen > DIGI_PLEN) + ch->ch_digi.digi_onlen = DIGI_PLEN; + + if (ch->ch_digi.digi_offlen > DIGI_PLEN) + ch->ch_digi.digi_offlen = DIGI_PLEN; + + ch->ch_bd->bd_ops->param(tty); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; +} + + +/* + * dgnc_set_termios() + */ +static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_c_cflag = tty->termios.c_cflag; + ch->ch_c_iflag = tty->termios.c_iflag; + ch->ch_c_oflag = tty->termios.c_oflag; + ch->ch_c_lflag = tty->termios.c_lflag; + ch->ch_startc = tty->termios.c_cc[VSTART]; + ch->ch_stopc = tty->termios.c_cc[VSTOP]; + + ch->ch_bd->bd_ops->param(tty); + dgnc_carrier(ch); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_throttle(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags |= (CH_FORCED_STOPI); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_unthrottle(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~(CH_FORCED_STOPI); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_start(struct tty_struct *tty) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~(CH_FORCED_STOP); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +static void dgnc_tty_stop(struct tty_struct *tty) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags |= (CH_FORCED_STOP); + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + +/* + * dgnc_tty_flush_chars() + * + * Flush the cook buffer + * + * Note to self, and any other poor souls who venture here: + * + * flush in this case DOES NOT mean dispose of the data. + * instead, it means "stop buffering and send it if you + * haven't already." Just guess how I figured that out... SRW 2-Jun-98 + * + * It is also always called in interrupt context - JAR 8-Sept-99 + */ +static void dgnc_tty_flush_chars(struct tty_struct *tty) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Do something maybe here */ + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + + +/* + * dgnc_tty_flush_buffer() + * + * Flush Tx buffer (make in == out) + */ +static void dgnc_tty_flush_buffer(struct tty_struct *tty) +{ + struct channel_t *ch; + struct un_t *un; + unsigned long flags; + + if (!tty || tty->magic != TTY_MAGIC) + return; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_flags &= ~CH_STOP; + + /* Flush our write queue */ + ch->ch_w_head = ch->ch_w_tail; + + /* Flush UARTs transmit FIFO */ + ch->ch_bd->bd_ops->flush_uart_write(ch); + + if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + + spin_unlock_irqrestore(&ch->ch_lock, flags); +} + + + +/***************************************************************************** + * + * The IOCTL function and all of its helpers + * + *****************************************************************************/ + +/* + * dgnc_tty_ioctl() + * + * The usual assortment of ioctl's + */ +static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct dgnc_board *bd; + struct channel_t *ch; + struct un_t *un; + int rc; + unsigned long flags; + void __user *uarg = (void __user *) arg; + + if (!tty || tty->magic != TTY_MAGIC) + return -ENODEV; + + un = tty->driver_data; + if (!un || un->magic != DGNC_UNIT_MAGIC) + return -ENODEV; + + ch = un->un_ch; + if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) + return -ENODEV; + + bd = ch->ch_bd; + if (!bd || bd->magic != DGNC_BOARD_MAGIC) + return -ENODEV; + + spin_lock_irqsave(&ch->ch_lock, flags); + + if (un->un_open_count <= 0) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return -EIO; + } + + switch (cmd) { + + /* Here are all the standard ioctl's that we MUST implement */ + + case TCSBRK: + /* + * TCSBRK is SVID version: non-zero arg --> no break + * this behaviour is exploited by tcdrain(). + * + * According to POSIX.1 spec (7.2.2.1.2) breaks should be + * between 0.25 and 0.5 seconds so we'll ask for something + * in the middle: 0.375 seconds. + */ + rc = tty_check_change(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (rc) + return rc; + + rc = ch->ch_bd->bd_ops->drain(tty, 0); + + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP)) + ch->ch_bd->bd_ops->send_break(ch, 250); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + + case TCSBRKP: + /* support for POSIX tcsendbreak() + * According to POSIX.1 spec (7.2.2.1.2) breaks should be + * between 0.25 and 0.5 seconds so we'll ask for something + * in the middle: 0.375 seconds. + */ + rc = tty_check_change(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (rc) + return rc; + + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->send_break(ch, 250); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + case TIOCSBRK: + rc = tty_check_change(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + if (rc) + return rc; + + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + + ch->ch_bd->bd_ops->send_break(ch, 250); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + case TIOCCBRK: + /* Do Nothing */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + + case TIOCGSOFTCAR: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg); + return rc; + + case TIOCSSOFTCAR: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(arg, (unsigned long __user *) arg); + if (rc) + return rc; + + spin_lock_irqsave(&ch->ch_lock, flags); + tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0)); + ch->ch_bd->bd_ops->param(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return 0; + + case TIOCMGET: + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_get_modem_info(ch, uarg); + + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_set_modem_info(tty, cmd, uarg); + + /* + * Here are any additional ioctl's that we want to implement + */ + + case TCFLSH: + /* + * The linux tty driver doesn't have a flush + * input routine for the driver, assuming all backed + * up data is in the line disc. buffers. However, + * we all know that's not the case. Here, we + * act on the ioctl, but then lie and say we didn't + * so the line discipline will process the flush + * also. + */ + rc = tty_check_change(tty); + if (rc) { + spin_unlock_irqrestore(&ch->ch_lock, flags); + return rc; + } + + if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) { + ch->ch_r_head = ch->ch_r_tail; + ch->ch_bd->bd_ops->flush_uart_read(ch); + /* Force queue flow control to be released, if needed */ + dgnc_check_queue_flow_control(ch); + } + + if ((arg == TCOFLUSH) || (arg == TCIOFLUSH)) { + if (!(un->un_type == DGNC_PRINT)) { + ch->ch_w_head = ch->ch_w_tail; + ch->ch_bd->bd_ops->flush_uart_write(ch); + + if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_tun.un_flags_wait); + } + + if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) { + ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY); + wake_up_interruptible(&ch->ch_pun.un_flags_wait); + } + + } + } + + /* pretend we didn't recognize this IOCTL */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + return -ENOIOCTLCMD; + case TCSETSF: + case TCSETSW: + /* + * The linux tty driver doesn't have a flush + * input routine for the driver, assuming all backed + * up data is in the line disc. buffers. However, + * we all know that's not the case. Here, we + * act on the ioctl, but then lie and say we didn't + * so the line discipline will process the flush + * also. + */ + if (cmd == TCSETSF) { + /* flush rx */ + ch->ch_flags &= ~CH_STOP; + ch->ch_r_head = ch->ch_r_tail; + ch->ch_bd->bd_ops->flush_uart_read(ch); + /* Force queue flow control to be released, if needed */ + dgnc_check_queue_flow_control(ch); + } + + /* now wait for all the output to drain */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + /* pretend we didn't recognize this */ + return -ENOIOCTLCMD; + + case TCSETAW: + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = ch->ch_bd->bd_ops->drain(tty, 0); + if (rc) + return -EINTR; + + /* pretend we didn't recognize this */ + return -ENOIOCTLCMD; + + case TCXONC: + spin_unlock_irqrestore(&ch->ch_lock, flags); + /* Make the ld do it */ + return -ENOIOCTLCMD; + + case DIGI_GETA: + /* get information for ditty */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_tty_digigeta(tty, uarg); + + case DIGI_SETAW: + case DIGI_SETAF: + + /* set information for ditty */ + if (cmd == (DIGI_SETAW)) { + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = ch->ch_bd->bd_ops->drain(tty, 0); + + if (rc) + return -EINTR; + + spin_lock_irqsave(&ch->ch_lock, flags); + } else { + tty_ldisc_flush(tty); + } + /* fall thru */ + + case DIGI_SETA: + spin_unlock_irqrestore(&ch->ch_lock, flags); + return dgnc_tty_digiseta(tty, uarg); + + case DIGI_LOOPBACK: + { + uint loopback = 0; + /* Let go of locks when accessing user space, could sleep */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(loopback, (unsigned int __user *) arg); + if (rc) + return rc; + spin_lock_irqsave(&ch->ch_lock, flags); + + /* Enable/disable internal loopback for this port */ + if (loopback) + ch->ch_flags |= CH_LOOPBACK; + else + ch->ch_flags &= ~(CH_LOOPBACK); + + ch->ch_bd->bd_ops->param(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + } + + case DIGI_GETCUSTOMBAUD: + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = put_user(ch->ch_custom_speed, (unsigned int __user *) arg); + return rc; + + case DIGI_SETCUSTOMBAUD: + { + int new_rate; + /* Let go of locks when accessing user space, could sleep */ + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(new_rate, (int __user *) arg); + if (rc) + return rc; + spin_lock_irqsave(&ch->ch_lock, flags); + dgnc_set_custom_speed(ch, new_rate); + ch->ch_bd->bd_ops->param(tty); + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + } + + /* + * This ioctl allows insertion of a character into the front + * of any pending data to be transmitted. + * + * This ioctl is to satify the "Send Character Immediate" + * call that the RealPort protocol spec requires. + */ + case DIGI_REALPORT_SENDIMMEDIATE: + { + unsigned char c; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = get_user(c, (unsigned char __user *) arg); + if (rc) + return rc; + spin_lock_irqsave(&ch->ch_lock, flags); + ch->ch_bd->bd_ops->send_immediate_char(ch, c); + spin_unlock_irqrestore(&ch->ch_lock, flags); + return 0; + } + + /* + * This ioctl returns all the current counts for the port. + * + * This ioctl is to satify the "Line Error Counters" + * call that the RealPort protocol spec requires. + */ + case DIGI_REALPORT_GETCOUNTERS: + { + struct digi_getcounter buf; + + buf.norun = ch->ch_err_overrun; + buf.noflow = 0; /* The driver doesn't keep this stat */ + buf.nframe = ch->ch_err_frame; + buf.nparity = ch->ch_err_parity; + buf.nbreak = ch->ch_err_break; + buf.rbytes = ch->ch_rxcount; + buf.tbytes = ch->ch_txcount; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(uarg, &buf, sizeof(buf))) + return -EFAULT; + + return 0; + } + + /* + * This ioctl returns all current events. + * + * This ioctl is to satify the "Event Reporting" + * call that the RealPort protocol spec requires. + */ + case DIGI_REALPORT_GETEVENTS: + { + unsigned int events = 0; + + /* NOTE: MORE EVENTS NEEDS TO BE ADDED HERE */ + if (ch->ch_flags & CH_BREAK_SENDING) + events |= EV_TXB; + if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP)) + events |= (EV_OPU | EV_OPS); + + if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI)) + events |= (EV_IPU | EV_IPS); + + spin_unlock_irqrestore(&ch->ch_lock, flags); + rc = put_user(events, (unsigned int __user *) arg); + return rc; + } + + /* + * This ioctl returns TOUT and TIN counters based + * upon the values passed in by the RealPort Server. + * It also passes back whether the UART Transmitter is + * empty as well. + */ + case DIGI_REALPORT_GETBUFFERS: + { + struct digi_getbuffer buf; + int tdist; + int count; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + /* + * Get data from user first. + */ + if (copy_from_user(&buf, uarg, sizeof(buf))) + return -EFAULT; + + spin_lock_irqsave(&ch->ch_lock, flags); + + /* + * Figure out how much data is in our RX and TX queues. + */ + buf.rxbuf = (ch->ch_r_head - ch->ch_r_tail) & RQUEUEMASK; + buf.txbuf = (ch->ch_w_head - ch->ch_w_tail) & WQUEUEMASK; + + /* + * Is the UART empty? Add that value to whats in our TX queue. + */ + count = buf.txbuf + ch->ch_bd->bd_ops->get_uart_bytes_left(ch); + + /* + * Figure out how much data the RealPort Server believes should + * be in our TX queue. + */ + tdist = (buf.tIn - buf.tOut) & 0xffff; + + /* + * If we have more data than the RealPort Server believes we + * should have, reduce our count to its amount. + * + * This count difference CAN happen because the Linux LD can + * insert more characters into our queue for OPOST processing + * that the RealPort Server doesn't know about. + */ + if (buf.txbuf > tdist) + buf.txbuf = tdist; + + /* + * Report whether our queue and UART TX are completely empty. + */ + if (count) + buf.txdone = 0; + else + buf.txdone = 1; + + spin_unlock_irqrestore(&ch->ch_lock, flags); + + if (copy_to_user(uarg, &buf, sizeof(buf))) + return -EFAULT; + + return 0; + } + default: + spin_unlock_irqrestore(&ch->ch_lock, flags); + + return -ENOIOCTLCMD; + } +} diff --git a/drivers/staging/dgnc/dgnc_tty.h b/drivers/staging/dgnc/dgnc_tty.h new file mode 100644 index 000000000..21d3369b8 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_tty.h @@ -0,0 +1,34 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DGNC_TTY_H +#define __DGNC_TTY_H + +#include "dgnc_driver.h" + +int dgnc_tty_register(struct dgnc_board *brd); + +int dgnc_tty_preinit(void); +int dgnc_tty_init(struct dgnc_board *); + +void dgnc_tty_post_uninit(void); +void dgnc_tty_uninit(struct dgnc_board *); + +void dgnc_input(struct channel_t *ch); +void dgnc_carrier(struct channel_t *ch); +void dgnc_wakeup_writes(struct channel_t *ch); +void dgnc_check_queue_flow_control(struct channel_t *ch); + +#endif diff --git a/drivers/staging/dgnc/dgnc_utils.c b/drivers/staging/dgnc/dgnc_utils.c new file mode 100644 index 000000000..f76de8290 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_utils.c @@ -0,0 +1,18 @@ +#include +#include +#include "dgnc_utils.h" +#include "digi.h" + +/* + * dgnc_ms_sleep() + * + * Put the driver to sleep for x ms's + * + * Returns 0 if timed out, !0 (showing signal) if interrupted by a signal. + */ +int dgnc_ms_sleep(ulong ms) +{ + __set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout((ms * HZ) / 1000); + return signal_pending(current); +} diff --git a/drivers/staging/dgnc/dgnc_utils.h b/drivers/staging/dgnc/dgnc_utils.h new file mode 100644 index 000000000..1164c3a09 --- /dev/null +++ b/drivers/staging/dgnc/dgnc_utils.h @@ -0,0 +1,6 @@ +#ifndef __DGNC_UTILS_H +#define __DGNC_UTILS_H + +int dgnc_ms_sleep(ulong ms); + +#endif diff --git a/drivers/staging/dgnc/digi.h b/drivers/staging/dgnc/digi.h new file mode 100644 index 000000000..d637a7802 --- /dev/null +++ b/drivers/staging/dgnc/digi.h @@ -0,0 +1,179 @@ +/* + * Copyright 2003 Digi International (www.digi.com) + * Scott H Kilau + * + * 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, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ + +#ifndef __DIGI_H +#define __DIGI_H + +#ifndef TIOCM_LE +#define TIOCM_LE 0x01 /* line enable */ +#define TIOCM_DTR 0x02 /* data terminal ready */ +#define TIOCM_RTS 0x04 /* request to send */ +#define TIOCM_ST 0x08 /* secondary transmit */ +#define TIOCM_SR 0x10 /* secondary receive */ +#define TIOCM_CTS 0x20 /* clear to send */ +#define TIOCM_CAR 0x40 /* carrier detect */ +#define TIOCM_RNG 0x80 /* ring indicator */ +#define TIOCM_DSR 0x100 /* data set ready */ +#define TIOCM_RI TIOCM_RNG /* ring (alternate) */ +#define TIOCM_CD TIOCM_CAR /* carrier detect (alt) */ +#endif + +#if !defined(TIOCMSET) +#define TIOCMSET (('d'<<8) | 252) /* set modem ctrl state */ +#define TIOCMGET (('d'<<8) | 253) /* set modem ctrl state */ +#endif + +#if !defined(TIOCMBIC) +#define TIOCMBIC (('d'<<8) | 254) /* set modem ctrl state */ +#define TIOCMBIS (('d'<<8) | 255) /* set modem ctrl state */ +#endif + +#define DIGI_GETA (('e'<<8) | 94) /* Read params */ +#define DIGI_SETA (('e'<<8) | 95) /* Set params */ +#define DIGI_SETAW (('e'<<8) | 96) /* Drain & set params */ +#define DIGI_SETAF (('e'<<8) | 97) /* Drain, flush & set params */ +#define DIGI_GET_NI_INFO (('d'<<8) | 250) /* Non-intelligent state info */ +#define DIGI_LOOPBACK (('d'<<8) | 252) /* + * Enable/disable UART + * internal loopback + */ +#define DIGI_FAST 0x0002 /* Fast baud rates */ +#define RTSPACE 0x0004 /* RTS input flow control */ +#define CTSPACE 0x0008 /* CTS output flow control */ +#define DIGI_COOK 0x0080 /* Cooked processing done in FEP */ +#define DIGI_FORCEDCD 0x0100 /* Force carrier */ +#define DIGI_ALTPIN 0x0200 /* Alternate RJ-45 pin config */ +#define DIGI_PRINTER 0x0800 /* Hold port open for flow cntrl*/ +#define DIGI_DTR_TOGGLE 0x2000 /* Support DTR Toggle */ +#define DIGI_RTS_TOGGLE 0x8000 /* Support RTS Toggle */ +#define DIGI_PLEN 28 /* String length */ +#define DIGI_TSIZ 10 /* Terminal string len */ + +/************************************************************************ + * Structure used with ioctl commands for DIGI parameters. + ************************************************************************/ +struct digi_t { + unsigned short digi_flags; /* Flags (see above) */ + unsigned short digi_maxcps; /* Max printer CPS */ + unsigned short digi_maxchar; /* Max chars in print queue */ + unsigned short digi_bufsize; /* Buffer size */ + unsigned char digi_onlen; /* Length of ON string */ + unsigned char digi_offlen; /* Length of OFF string */ + char digi_onstr[DIGI_PLEN]; /* Printer on string */ + char digi_offstr[DIGI_PLEN]; /* Printer off string */ + char digi_term[DIGI_TSIZ]; /* terminal string */ +}; + +/************************************************************************ + * Structure to get driver status information + ************************************************************************/ +struct digi_dinfo { + unsigned int dinfo_nboards; /* # boards configured */ + char dinfo_reserved[12]; /* for future expansion */ + char dinfo_version[16]; /* driver version */ +}; + +#define DIGI_GETDD (('d'<<8) | 248) /* get driver info */ + +/************************************************************************ + * Structure used with ioctl commands for per-board information + * + * physsize and memsize differ when board has "windowed" memory + ************************************************************************/ +struct digi_info { + unsigned int info_bdnum; /* Board number (0 based) */ + unsigned int info_ioport; /* io port address */ + unsigned int info_physaddr; /* memory address */ + unsigned int info_physsize; /* Size of host mem window */ + unsigned int info_memsize; /* Amount of dual-port mem */ + /* on board */ + unsigned short info_bdtype; /* Board type */ + unsigned short info_nports; /* number of ports */ + char info_bdstate; /* board state */ + char info_reserved[7]; /* for future expansion */ +}; + +#define DIGI_GETBD (('d'<<8) | 249) /* get board info */ + +struct digi_getbuffer /* Struct for holding buffer use counts */ +{ + unsigned long tIn; + unsigned long tOut; + unsigned long rxbuf; + unsigned long txbuf; + unsigned long txdone; +}; + +struct digi_getcounter { + unsigned long norun; /* number of UART overrun errors */ + unsigned long noflow; /* number of buffer overflow errors */ + unsigned long nframe; /* number of framing errors */ + unsigned long nparity; /* number of parity errors */ + unsigned long nbreak; /* number of breaks received */ + unsigned long rbytes; /* number of received bytes */ + unsigned long tbytes; /* number of bytes transmitted fully */ +}; + +/* Board State Definitions */ +#define BD_RUNNING 0x0 +#define BD_NOFEP 0x5 + +#define DIGI_SETCUSTOMBAUD _IOW('e', 106, int) /* Set integer baud rate */ +#define DIGI_GETCUSTOMBAUD _IOR('e', 107, int) /* Get integer baud rate */ + +#define DIGI_REALPORT_GETBUFFERS (('e'<<8) | 108) +#define DIGI_REALPORT_SENDIMMEDIATE (('e'<<8) | 109) +#define DIGI_REALPORT_GETCOUNTERS (('e'<<8) | 110) +#define DIGI_REALPORT_GETEVENTS (('e'<<8) | 111) + +#define EV_OPU 0x0001 /* ! nonintelligent + * DPA translation + */ +struct ni_info { + int board; + int channel; + int dtr; + int rts; + int cts; + int dsr; + int ri; + int dcd; + int curtx; + int currx; + unsigned short iflag; + unsigned short oflag; + unsigned short cflag; + unsigned short lflag; + unsigned int mstat; + unsigned char hflow; + unsigned char xmit_stopped; + unsigned char recv_stopped; + unsigned int baud; +}; + +#define T_CLASSIC 0002 +#define T_PCIBUS 0400 +#define T_NEO_EXPRESS 0001 +#define T_NEO 0000 + +#define TTY_FLIPBUF_SIZE 512 +#endif /* DIGI_H */ -- cgit v1.2.3-54-g00ecf