blob: 0658fc5482225089d07abda0d4c43f582d1e0890 [file] [log] [blame]
/*
* linux/drivers/char/synclink.c
*
* $Id: synclink.c,v 4.38 2005/11/07 16:30:34 paulkf Exp $
*
* Device driver for Microgate SyncLink ISA and PCI
* high speed multiprotocol serial adapters.
*
* written by Paul Fulghum for Microgate Corporation
* paulkf@microgate.com
*
* Microgate and SyncLink are trademarks of Microgate Corporation
*
* Derived from serial.c written by Theodore Ts'o and Linus Torvalds
*
* Original release 01/11/99
*
* This code is released under the GNU General Public License (GPL)
*
* This driver is primarily intended for use in synchronous
* HDLC mode. Asynchronous mode is also provided.
*
* When operating in synchronous mode, each call to mgsl_write()
* contains exactly one complete HDLC frame. Calling mgsl_put_char
* will start assembling an HDLC frame that will not be sent until
* mgsl_flush_chars or mgsl_write is called.
*
* Synchronous receive data is reported as complete frames. To accomplish
* this, the TTY flip buffer is bypassed (too small to hold largest
* frame and may fragment frames) and the line discipline
* receive entry point is called directly.
*
* This driver has been tested with a slightly modified ppp.c driver
* for synchronous PPP.
*
* 2000/02/16
* Added interface for syncppp.c driver (an alternate synchronous PPP
* implementation that also supports Cisco HDLC). Each device instance
* registers as a tty device AND a network device (if dosyncppp option
* is set for the device). The functionality is determined by which
* device interface is opened.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if defined(__i386__)
# define BREAKPOINT() asm(" int $3");
#else
# define BREAKPOINT() { }
#endif
#define MAX_ISA_DEVICES 10
#define MAX_PCI_DEVICES 10
#define MAX_TOTAL_DEVICES 20
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/synclink.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/dma.h>
#include <linux/bitops.h>
#include <asm/types.h>
#include <linux/termios.h>
#include <linux/workqueue.h>
#include <linux/hdlc.h>
#include <linux/dma-mapping.h>
#if defined(CONFIG_HDLC) || (defined(CONFIG_HDLC_MODULE) && defined(CONFIG_SYNCLINK_MODULE))
#define SYNCLINK_GENERIC_HDLC 1
#else
#define SYNCLINK_GENERIC_HDLC 0
#endif
#define GET_USER(error,value,addr) error = get_user(value,addr)
#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
#define PUT_USER(error,value,addr) error = put_user(value,addr)
#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
#include <asm/uaccess.h>
#define RCLRVALUE 0xffff
static MGSL_PARAMS default_params = {
MGSL_MODE_HDLC, /* unsigned long mode */
0, /* unsigned char loopback; */
HDLC_FLAG_UNDERRUN_ABORT15, /* unsigned short flags; */
HDLC_ENCODING_NRZI_SPACE, /* unsigned char encoding; */
0, /* unsigned long clock_speed; */
0xff, /* unsigned char addr_filter; */
HDLC_CRC_16_CCITT, /* unsigned short crc_type; */
HDLC_PREAMBLE_LENGTH_8BITS, /* unsigned char preamble_length; */
HDLC_PREAMBLE_PATTERN_NONE, /* unsigned char preamble; */
9600, /* unsigned long data_rate; */
8, /* unsigned char data_bits; */
1, /* unsigned char stop_bits; */
ASYNC_PARITY_NONE /* unsigned char parity; */
};
#define SHARED_MEM_ADDRESS_SIZE 0x40000
#define BUFFERLISTSIZE 4096
#define DMABUFFERSIZE 4096
#define MAXRXFRAMES 7
typedef struct _DMABUFFERENTRY
{
u32 phys_addr; /* 32-bit flat physical address of data buffer */
volatile u16 count; /* buffer size/data count */
volatile u16 status; /* Control/status field */
volatile u16 rcc; /* character count field */
u16 reserved; /* padding required by 16C32 */
u32 link; /* 32-bit flat link to next buffer entry */
char *virt_addr; /* virtual address of data buffer */
u32 phys_entry; /* physical address of this buffer entry */
dma_addr_t dma_addr;
} DMABUFFERENTRY, *DMAPBUFFERENTRY;
/* The queue of BH actions to be performed */
#define BH_RECEIVE 1
#define BH_TRANSMIT 2
#define BH_STATUS 4
#define IO_PIN_SHUTDOWN_LIMIT 100
struct _input_signal_events {
int ri_up;
int ri_down;
int dsr_up;
int dsr_down;
int dcd_up;
int dcd_down;
int cts_up;
int cts_down;
};
/* transmit holding buffer definitions*/
#define MAX_TX_HOLDING_BUFFERS 5
struct tx_holding_buffer {
int buffer_size;
unsigned char * buffer;
};
/*
* Device instance data structure
*/
struct mgsl_struct {
int magic;
struct tty_port port;
int line;
int hw_version;
struct mgsl_icount icount;
int timeout;
int x_char; /* xon/xoff character */
u16 read_status_mask;
u16 ignore_status_mask;
unsigned char *xmit_buf;
int xmit_head;
int xmit_tail;
int xmit_cnt;
wait_queue_head_t status_event_wait_q;
wait_queue_head_t event_wait_q;
struct timer_list tx_timer; /* HDLC transmit timeout timer */
struct mgsl_struct *next_device; /* device list link */
spinlock_t irq_spinlock; /* spinlock for synchronizing with ISR */
struct work_struct task; /* task structure for scheduling bh */
u32 EventMask; /* event trigger mask */
u32 RecordedEvents; /* pending events */
u32 max_frame_size; /* as set by device config */
u32 pending_bh;
bool bh_running; /* Protection from multiple */
int isr_overflow;
bool bh_requested;
int dcd_chkcount; /* check counts to prevent */
int cts_chkcount; /* too many IRQs if a signal */
int dsr_chkcount; /* is floating */
int ri_chkcount;
char *buffer_list; /* virtual address of Rx & Tx buffer lists */
u32 buffer_list_phys;
dma_addr_t buffer_list_dma_addr;
unsigned int rx_buffer_count; /* count of total allocated Rx buffers */
DMABUFFERENTRY *rx_buffer_list; /* list of receive buffer entries */
unsigned int current_rx_buffer;
int num_tx_dma_buffers; /* number of tx dma frames required */
int tx_dma_buffers_used;
unsigned int tx_buffer_count; /* count of total allocated Tx buffers */
DMABUFFERENTRY *tx_buffer_list; /* list of transmit buffer entries */
int start_tx_dma_buffer; /* tx dma buffer to start tx dma operation */
int current_tx_buffer; /* next tx dma buffer to be loaded */
unsigned char *intermediate_rxbuffer;
int num_tx_holding_buffers; /* number of tx holding buffer allocated */
int get_tx_holding_index; /* next tx holding buffer for adapter to load */
int put_tx_holding_index; /* next tx holding buffer to store user request */
int tx_holding_count; /* number of tx holding buffers waiting */
struct tx_holding_buffer tx_holding_buffers[MAX_TX_HOLDING_BUFFERS];
bool rx_enabled;
bool rx_overflow;
bool rx_rcc_underrun;
bool tx_enabled;
bool tx_active;
u32 idle_mode;
u16 cmr_value;
u16 tcsr_value;
char device_name[25]; /* device instance name */
unsigned int bus_type; /* expansion bus type (ISA,EISA,PCI) */
unsigned char bus; /* expansion bus number (zero based) */
unsigned char function; /* PCI device number */
unsigned int io_base; /* base I/O address of adapter */
unsigned int io_addr_size; /* size of the I/O address range */
bool io_addr_requested; /* true if I/O address requested */
unsigned int irq_level; /* interrupt level */
unsigned long irq_flags;
bool irq_requested; /* true if IRQ requested */
unsigned int dma_level; /* DMA channel */
bool dma_requested; /* true if dma channel requested */
u16 mbre_bit;
u16 loopback_bits;
u16 usc_idle_mode;
MGSL_PARAMS params; /* communications parameters */
unsigned char serial_signals; /* current serial signal states */
bool irq_occurred; /* for diagnostics use */
unsigned int init_error; /* Initialization startup error (DIAGS) */
int fDiagnosticsmode; /* Driver in Diagnostic mode? (DIAGS) */
u32 last_mem_alloc;
unsigned char* memory_base; /* shared memory address (PCI only) */
u32 phys_memory_base;
bool shared_mem_requested;
unsigned char* lcr_base; /* local config registers (PCI only) */
u32 phys_lcr_base;
u32 lcr_offset;
bool lcr_mem_requested;
u32 misc_ctrl_value;
char flag_buf[MAX_ASYNC_BUFFER_SIZE];
char char_buf[MAX_ASYNC_BUFFER_SIZE];
bool drop_rts_on_tx_done;
bool loopmode_insert_requested;
bool loopmode_send_done_requested;
struct _input_signal_events input_signal_events;
/* generic HDLC device parts */
int netcount;
spinlock_t netlock;
#if SYNCLINK_GENERIC_HDLC
struct net_device *netdev;
#endif
};
#define MGSL_MAGIC 0x5401
/*
* The size of the serial xmit buffer is 1 page, or 4096 bytes
*/
#ifndef SERIAL_XMIT_SIZE
#define SERIAL_XMIT_SIZE 4096
#endif
/*
* These macros define the offsets used in calculating the
* I/O address of the specified USC registers.
*/
#define DCPIN 2 /* Bit 1 of I/O address */
#define SDPIN 4 /* Bit 2 of I/O address */
#define DCAR 0 /* DMA command/address register */
#define CCAR SDPIN /* channel command/address register */
#define DATAREG DCPIN + SDPIN /* serial data register */
#define MSBONLY 0x41
#define LSBONLY 0x40
/*
* These macros define the register address (ordinal number)
* used for writing address/value pairs to the USC.
*/
#define CMR 0x02 /* Channel mode Register */
#define CCSR 0x04 /* Channel Command/status Register */
#define CCR 0x06 /* Channel Control Register */
#define PSR 0x08 /* Port status Register */
#define PCR 0x0a /* Port Control Register */
#define TMDR 0x0c /* Test mode Data Register */
#define TMCR 0x0e /* Test mode Control Register */
#define CMCR 0x10 /* Clock mode Control Register */
#define HCR 0x12 /* Hardware Configuration Register */
#define IVR 0x14 /* Interrupt Vector Register */
#define IOCR 0x16 /* Input/Output Control Register */
#define ICR 0x18 /* Interrupt Control Register */
#define DCCR 0x1a /* Daisy Chain Control Register */
#define MISR 0x1c /* Misc Interrupt status Register */
#define SICR 0x1e /* status Interrupt Control Register */
#define RDR 0x20 /* Receive Data Register */
#define RMR 0x22 /* Receive mode Register */
#define RCSR 0x24 /* Receive Command/status Register */
#define RICR 0x26 /* Receive Interrupt Control Register */
#define RSR 0x28 /* Receive Sync Register */
#define RCLR 0x2a /* Receive count Limit Register */
#define RCCR 0x2c /* Receive Character count Register */
#define TC0R 0x2e /* Time Constant 0 Register */
#define TDR 0x30 /* Transmit Data Register */
#define TMR 0x32 /* Transmit mode Register */
#define TCSR 0x34 /* Transmit Command/status Register */
#define TICR 0x36 /* Transmit Interrupt Control Register */
#define TSR 0x38 /* Transmit Sync Register */
#define TCLR 0x3a /* Transmit count Limit Register */
#define TCCR 0x3c /* Transmit Character count Register */
#define TC1R 0x3e /* Time Constant 1 Register */
/*
* MACRO DEFINITIONS FOR DMA REGISTERS
*/
#define DCR 0x06 /* DMA Control Register (shared) */
#define DACR 0x08 /* DMA Array count Register (shared) */
#define BDCR 0x12 /* Burst/Dwell Control Register (shared) */
#define DIVR 0x14 /* DMA Interrupt Vector Register (shared) */
#define DICR 0x18 /* DMA Interrupt Control Register (shared) */
#define CDIR 0x1a /* Clear DMA Interrupt Register (shared) */
#define SDIR 0x1c /* Set DMA Interrupt Register (shared) */
#define TDMR 0x02 /* Transmit DMA mode Register */
#define TDIAR 0x1e /* Transmit DMA Interrupt Arm Register */
#define TBCR 0x2a /* Transmit Byte count Register */
#define TARL 0x2c /* Transmit Address Register (low) */
#define TARU 0x2e /* Transmit Address Register (high) */
#define NTBCR 0x3a /* Next Transmit Byte count Register */
#define NTARL 0x3c /* Next Transmit Address Register (low) */
#define NTARU 0x3e /* Next Transmit Address Register (high) */
#define RDMR 0x82 /* Receive DMA mode Register (non-shared) */
#define RDIAR 0x9e /* Receive DMA Interrupt Arm Register */
#define RBCR 0xaa /* Receive Byte count Register */
#define RARL 0xac /* Receive Address Register (low) */
#define RARU 0xae /* Receive Address Register (high) */
#define NRBCR 0xba /* Next Receive Byte count Register */
#define NRARL 0xbc /* Next Receive Address Register (low) */
#define NRARU 0xbe /* Next Receive Address Register (high) */
/*
* MACRO DEFINITIONS FOR MODEM STATUS BITS
*/
#define MODEMSTATUS_DTR 0x80
#define MODEMSTATUS_DSR 0x40
#define MODEMSTATUS_RTS 0x20
#define MODEMSTATUS_CTS 0x10
#define MODEMSTATUS_RI 0x04
#define MODEMSTATUS_DCD 0x01
/*
* Channel Command/Address Register (CCAR) Command Codes
*/
#define RTCmd_Null 0x0000
#define RTCmd_ResetHighestIus 0x1000
#define RTCmd_TriggerChannelLoadDma 0x2000
#define RTCmd_TriggerRxDma 0x2800
#define RTCmd_TriggerTxDma 0x3000
#define RTCmd_TriggerRxAndTxDma 0x3800
#define RTCmd_PurgeRxFifo 0x4800
#define RTCmd_PurgeTxFifo 0x5000
#define RTCmd_PurgeRxAndTxFifo 0x5800
#define RTCmd_LoadRcc 0x6800
#define RTCmd_LoadTcc 0x7000
#define RTCmd_LoadRccAndTcc 0x7800
#define RTCmd_LoadTC0 0x8800
#define RTCmd_LoadTC1 0x9000
#define RTCmd_LoadTC0AndTC1 0x9800
#define RTCmd_SerialDataLSBFirst 0xa000
#define RTCmd_SerialDataMSBFirst 0xa800
#define RTCmd_SelectBigEndian 0xb000
#define RTCmd_SelectLittleEndian 0xb800
/*
* DMA Command/Address Register (DCAR) Command Codes
*/
#define DmaCmd_Null 0x0000
#define DmaCmd_ResetTxChannel 0x1000
#define DmaCmd_ResetRxChannel 0x1200
#define DmaCmd_StartTxChannel 0x2000
#define DmaCmd_StartRxChannel 0x2200
#define DmaCmd_ContinueTxChannel 0x3000
#define DmaCmd_ContinueRxChannel 0x3200
#define DmaCmd_PauseTxChannel 0x4000
#define DmaCmd_PauseRxChannel 0x4200
#define DmaCmd_AbortTxChannel 0x5000
#define DmaCmd_AbortRxChannel 0x5200
#define DmaCmd_InitTxChannel 0x7000
#define DmaCmd_InitRxChannel 0x7200
#define DmaCmd_ResetHighestDmaIus 0x8000
#define DmaCmd_ResetAllChannels 0x9000
#define DmaCmd_StartAllChannels 0xa000
#define DmaCmd_ContinueAllChannels 0xb000
#define DmaCmd_PauseAllChannels 0xc000
#define DmaCmd_AbortAllChannels 0xd000
#define DmaCmd_InitAllChannels 0xf000
#define TCmd_Null 0x0000
#define TCmd_ClearTxCRC 0x2000
#define TCmd_SelectTicrTtsaData 0x4000
#define TCmd_SelectTicrTxFifostatus 0x5000
#define TCmd_SelectTicrIntLevel 0x6000
#define TCmd_SelectTicrdma_level 0x7000
#define TCmd_SendFrame 0x8000
#define TCmd_SendAbort 0x9000
#define TCmd_EnableDleInsertion 0xc000
#define TCmd_DisableDleInsertion 0xd000
#define TCmd_ClearEofEom 0xe000
#define TCmd_SetEofEom 0xf000
#define RCmd_Null 0x0000
#define RCmd_ClearRxCRC 0x2000
#define RCmd_EnterHuntmode 0x3000
#define RCmd_SelectRicrRtsaData 0x4000
#define RCmd_SelectRicrRxFifostatus 0x5000
#define RCmd_SelectRicrIntLevel 0x6000
#define RCmd_SelectRicrdma_level 0x7000
/*
* Bits for enabling and disabling IRQs in Interrupt Control Register (ICR)
*/
#define RECEIVE_STATUS BIT5
#define RECEIVE_DATA BIT4
#define TRANSMIT_STATUS BIT3
#define TRANSMIT_DATA BIT2
#define IO_PIN BIT1
#define MISC BIT0
/*
* Receive status Bits in Receive Command/status Register RCSR
*/
#define RXSTATUS_SHORT_FRAME BIT8
#define RXSTATUS_CODE_VIOLATION BIT8
#define RXSTATUS_EXITED_HUNT BIT7
#define RXSTATUS_IDLE_RECEIVED BIT6
#define RXSTATUS_BREAK_RECEIVED BIT5
#define RXSTATUS_ABORT_RECEIVED BIT5
#define RXSTATUS_RXBOUND BIT4
#define RXSTATUS_CRC_ERROR BIT3
#define RXSTATUS_FRAMING_ERROR BIT3
#define RXSTATUS_ABORT BIT2
#define RXSTATUS_PARITY_ERROR BIT2
#define RXSTATUS_OVERRUN BIT1
#define RXSTATUS_DATA_AVAILABLE BIT0
#define RXSTATUS_ALL 0x01f6
#define usc_UnlatchRxstatusBits(a,b) usc_OutReg( (a), RCSR, (u16)((b) & RXSTATUS_ALL) )
/*
* Values for setting transmit idle mode in
* Transmit Control/status Register (TCSR)
*/
#define IDLEMODE_FLAGS 0x0000
#define IDLEMODE_ALT_ONE_ZERO 0x0100
#define IDLEMODE_ZERO 0x0200
#define IDLEMODE_ONE 0x0300
#define IDLEMODE_ALT_MARK_SPACE 0x0500
#define IDLEMODE_SPACE 0x0600
#define IDLEMODE_MARK 0x0700
#define IDLEMODE_MASK 0x0700
/*
* IUSC revision identifiers
*/
#define IUSC_SL1660 0x4d44
#define IUSC_PRE_SL1660 0x4553
/*
* Transmit status Bits in Transmit Command/status Register (TCSR)
*/
#define TCSR_PRESERVE 0x0F00
#define TCSR_UNDERWAIT BIT11
#define TXSTATUS_PREAMBLE_SENT BIT7
#define TXSTATUS_IDLE_SENT BIT6
#define TXSTATUS_ABORT_SENT BIT5
#define TXSTATUS_EOF_SENT BIT4
#define TXSTATUS_EOM_SENT BIT4
#define TXSTATUS_CRC_SENT BIT3
#define TXSTATUS_ALL_SENT BIT2
#define TXSTATUS_UNDERRUN BIT1
#define TXSTATUS_FIFO_EMPTY BIT0
#define TXSTATUS_ALL 0x00fa
#define usc_UnlatchTxstatusBits(a,b) usc_OutReg( (a), TCSR, (u16)((a)->tcsr_value + ((b) & 0x00FF)) )
#define MISCSTATUS_RXC_LATCHED BIT15
#define MISCSTATUS_RXC BIT14
#define MISCSTATUS_TXC_LATCHED BIT13
#define MISCSTATUS_TXC BIT12
#define MISCSTATUS_RI_LATCHED BIT11
#define MISCSTATUS_RI BIT10
#define MISCSTATUS_DSR_LATCHED BIT9
#define MISCSTATUS_DSR BIT8
#define MISCSTATUS_DCD_LATCHED BIT7
#define MISCSTATUS_DCD BIT6
#define MISCSTATUS_CTS_LATCHED BIT5
#define MISCSTATUS_CTS BIT4
#define MISCSTATUS_RCC_UNDERRUN BIT3
#define MISCSTATUS_DPLL_NO_SYNC BIT2
#define MISCSTATUS_BRG1_ZERO BIT1
#define MISCSTATUS_BRG0_ZERO BIT0
#define usc_UnlatchIostatusBits(a,b) usc_OutReg((a),MISR,(u16)((b) & 0xaaa0))
#define usc_UnlatchMiscstatusBits(a,b) usc_OutReg((a),MISR,(u16)((b) & 0x000f))
#define SICR_RXC_ACTIVE BIT15
#define SICR_RXC_INACTIVE BIT14
#define SICR_RXC (BIT15+BIT14)
#define SICR_TXC_ACTIVE BIT13
#define SICR_TXC_INACTIVE BIT12
#define SICR_TXC (BIT13+BIT12)
#define SICR_RI_ACTIVE BIT11
#define SICR_RI_INACTIVE BIT10
#define SICR_RI (BIT11+BIT10)
#define SICR_DSR_ACTIVE BIT9
#define SICR_DSR_INACTIVE BIT8
#define SICR_DSR (BIT9+BIT8)
#define SICR_DCD_ACTIVE BIT7
#define SICR_DCD_INACTIVE BIT6
#define SICR_DCD (BIT7+BIT6)
#define SICR_CTS_ACTIVE BIT5
#define SICR_CTS_INACTIVE BIT4
#define SICR_CTS (BIT5+BIT4)
#define SICR_RCC_UNDERFLOW BIT3
#define SICR_DPLL_NO_SYNC BIT2
#define SICR_BRG1_ZERO BIT1
#define SICR_BRG0_ZERO BIT0
void usc_DisableMasterIrqBit( struct mgsl_struct *info );
void usc_EnableMasterIrqBit( struct mgsl_struct *info );
void usc_EnableInterrupts( struct mgsl_struct *info, u16 IrqMask );
void usc_DisableInterrupts( struct mgsl_struct *info, u16 IrqMask );
void usc_ClearIrqPendingBits( struct mgsl_struct *info, u16 IrqMask );
#define usc_EnableInterrupts( a, b ) \
usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0xff00) + 0xc0 + (b)) )
#define usc_DisableInterrupts( a, b ) \
usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0xff00) + 0x80 + (b)) )
#define usc_EnableMasterIrqBit(a) \
usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0x0f00) + 0xb000) )
#define usc_DisableMasterIrqBit(a) \
usc_OutReg( (a), ICR, (u16)(usc_InReg((a),ICR) & 0x7f00) )
#define usc_ClearIrqPendingBits( a, b ) usc_OutReg( (a), DCCR, 0x40 + (b) )
/*
* Transmit status Bits in Transmit Control status Register (TCSR)
* and Transmit Interrupt Control Register (TICR) (except BIT2, BIT0)
*/
#define TXSTATUS_PREAMBLE_SENT BIT7
#define TXSTATUS_IDLE_SENT BIT6
#define TXSTATUS_ABORT_SENT BIT5
#define TXSTATUS_EOF BIT4
#define TXSTATUS_CRC_SENT BIT3
#define TXSTATUS_ALL_SENT BIT2
#define TXSTATUS_UNDERRUN BIT1
#define TXSTATUS_FIFO_EMPTY BIT0
#define DICR_MASTER BIT15
#define DICR_TRANSMIT BIT0
#define DICR_RECEIVE BIT1
#define usc_EnableDmaInterrupts(a,b) \
usc_OutDmaReg( (a), DICR, (u16)(usc_InDmaReg((a),DICR) | (b)) )
#define usc_DisableDmaInterrupts(a,b) \
usc_OutDmaReg( (a), DICR, (u16)(usc_InDmaReg((a),DICR) & ~(b)) )
#define usc_EnableStatusIrqs(a,b) \
usc_OutReg( (a), SICR, (u16)(usc_InReg((a),SICR) | (b)) )
#define usc_DisablestatusIrqs(a,b) \
usc_OutReg( (a), SICR, (u16)(usc_InReg((a),SICR) & ~(b)) )
/* Transmit status Bits in Transmit Control status Register (TCSR) */
/* and Transmit Interrupt Control Register (TICR) (except BIT2, BIT0) */
#define DISABLE_UNCONDITIONAL 0
#define DISABLE_END_OF_FRAME 1
#define ENABLE_UNCONDITIONAL 2
#define ENABLE_AUTO_CTS 3
#define ENABLE_AUTO_DCD 3
#define usc_EnableTransmitter(a,b) \
usc_OutReg( (a), TMR, (u16)((usc_InReg((a),TMR) & 0xfffc) | (b)) )
#define usc_EnableReceiver(a,b) \
usc_OutReg( (a), RMR, (u16)((usc_InReg((a),RMR) & 0xfffc) | (b)) )
static u16 usc_InDmaReg( struct mgsl_struct *info, u16 Port );
static void usc_OutDmaReg( struct mgsl_struct *info, u16 Port, u16 Value );
static void usc_DmaCmd( struct mgsl_struct *info, u16 Cmd );
static u16 usc_InReg( struct mgsl_struct *info, u16 Port );
static void usc_OutReg( struct mgsl_struct *info, u16 Port, u16 Value );
static void usc_RTCmd( struct mgsl_struct *info, u16 Cmd );
void usc_RCmd( struct mgsl_struct *info, u16 Cmd );
void usc_TCmd( struct mgsl_struct *info, u16 Cmd );
#define usc_TCmd(a,b) usc_OutReg((a), TCSR, (u16)((a)->tcsr_value + (b)))
#define usc_RCmd(a,b) usc_OutReg((a), RCSR, (b))
#define usc_SetTransmitSyncChars(a,s0,s1) usc_OutReg((a), TSR, (u16)(((u16)s0<<8)|(u16)s1))
static void usc_process_rxoverrun_sync( struct mgsl_struct *info );
static void usc_start_receiver( struct mgsl_struct *info );
static void usc_stop_receiver( struct mgsl_struct *info );
static void usc_start_transmitter( struct mgsl_struct *info );
static void usc_stop_transmitter( struct mgsl_struct *info );
static void usc_set_txidle( struct mgsl_struct *info );
static void usc_load_txfifo( struct mgsl_struct *info );
static void usc_enable_aux_clock( struct mgsl_struct *info, u32 DataRate );
static void usc_enable_loopback( struct mgsl_struct *info, int enable );
static void usc_get_serial_signals( struct mgsl_struct *info );
static void usc_set_serial_signals( struct mgsl_struct *info );
static void usc_reset( struct mgsl_struct *info );
static void usc_set_sync_mode( struct mgsl_struct *info );
static void usc_set_sdlc_mode( struct mgsl_struct *info );
static void usc_set_async_mode( struct mgsl_struct *info );
static void usc_enable_async_clock( struct mgsl_struct *info, u32 DataRate );
static void usc_loopback_frame( struct mgsl_struct *info );
static void mgsl_tx_timeout(unsigned long context);
static void usc_loopmode_cancel_transmit( struct mgsl_struct * info );
static void usc_loopmode_insert_request( struct mgsl_struct * info );
static int usc_loopmode_active( struct mgsl_struct * info);
static void usc_loopmode_send_done( struct mgsl_struct * info );
static int mgsl_ioctl_common(struct mgsl_struct *info, unsigned int cmd, unsigned long arg);
#if SYNCLINK_GENERIC_HDLC
#define dev_to_port(D) (dev_to_hdlc(D)->priv)
static void hdlcdev_tx_done(struct mgsl_struct *info);
static void hdlcdev_rx(struct mgsl_struct *info, char *buf, int size);
static int hdlcdev_init(struct mgsl_struct *info);
static void hdlcdev_exit(struct mgsl_struct *info);
#endif
/*
* Defines a BUS descriptor value for the PCI adapter
* local bus address ranges.
*/
#define BUS_DESCRIPTOR( WrHold, WrDly, RdDly, Nwdd, Nwad, Nxda, Nrdd, Nrad ) \
(0x00400020 + \
((WrHold) << 30) + \
((WrDly) << 28) + \
((RdDly) << 26) + \
((Nwdd) << 20) + \
((Nwad) << 15) + \
((Nxda) << 13) + \
((Nrdd) << 11) + \
((Nrad) << 6) )
static void mgsl_trace_block(struct mgsl_struct *info,const char* data, int count, int xmit);
/*
* Adapter diagnostic routines
*/
static bool mgsl_register_test( struct mgsl_struct *info );
static bool mgsl_irq_test( struct mgsl_struct *info );
static bool mgsl_dma_test( struct mgsl_struct *info );
static bool mgsl_memory_test( struct mgsl_struct *info );
static int mgsl_adapter_test( struct mgsl_struct *info );
/*
* device and resource management routines
*/
static int mgsl_claim_resources(struct mgsl_struct *info);
static void mgsl_release_resources(struct mgsl_struct *info);
static void mgsl_add_device(struct mgsl_struct *info);
static struct mgsl_struct* mgsl_allocate_device(void);
/*
* DMA buffer manupulation functions.
*/
static void mgsl_free_rx_frame_buffers( struct mgsl_struct *info, unsigned int StartIndex, unsigned int EndIndex );
static bool mgsl_get_rx_frame( struct mgsl_struct *info );
static bool mgsl_get_raw_rx_frame( struct mgsl_struct *info );
static void mgsl_reset_rx_dma_buffers( struct mgsl_struct *info );
static void mgsl_reset_tx_dma_buffers( struct mgsl_struct *info );
static int num_free_tx_dma_buffers(struct mgsl_struct *info);
static void mgsl_load_tx_dma_buffer( struct mgsl_struct *info, const char *Buffer, unsigned int BufferSize);
static void mgsl_load_pci_memory(char* TargetPtr, const char* SourcePtr, unsigned short count);
/*
* DMA and Shared Memory buffer allocation and formatting
*/
static int mgsl_allocate_dma_buffers(struct mgsl_struct *info);
static void mgsl_free_dma_buffers(struct mgsl_struct *info);
static int mgsl_alloc_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList,int Buffercount);
static void mgsl_free_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList,int Buffercount);
static int mgsl_alloc_buffer_list_memory(struct mgsl_struct *info);
static void mgsl_free_buffer_list_memory(struct mgsl_struct *info);
static int mgsl_alloc_intermediate_rxbuffer_memory(struct mgsl_struct *info);
static void mgsl_free_intermediate_rxbuffer_memory(struct mgsl_struct *info);
static int mgsl_alloc_intermediate_txbuffer_memory(struct mgsl_struct *info);
static void mgsl_free_intermediate_txbuffer_memory(struct mgsl_struct *info);
static bool load_next_tx_holding_buffer(struct mgsl_struct *info);
static int save_tx_buffer_request(struct mgsl_struct *info,const char *Buffer, unsigned int BufferSize);
/*
* Bottom half interrupt handlers
*/
static void mgsl_bh_handler(struct work_struct *work);
static void mgsl_bh_receive(struct mgsl_struct *info);
static void mgsl_bh_transmit(struct mgsl_struct *info);
static void mgsl_bh_status(struct mgsl_struct *info);
/*
* Interrupt handler routines and dispatch table.
*/
static void mgsl_isr_null( struct mgsl_struct *info );
static void mgsl_isr_transmit_data( struct mgsl_struct *info );
static void mgsl_isr_receive_data( struct mgsl_struct *info );
static void mgsl_isr_receive_status( struct mgsl_struct *info );
static void mgsl_isr_transmit_status( struct mgsl_struct *info );
static void mgsl_isr_io_pin( struct mgsl_struct *info );
static void mgsl_isr_misc( struct mgsl_struct *info );
static void mgsl_isr_receive_dma( struct mgsl_struct *info );
static void mgsl_isr_transmit_dma( struct mgsl_struct *info );
typedef void (*isr_dispatch_func)(struct mgsl_struct *);
static isr_dispatch_func UscIsrTable[7] =
{
mgsl_isr_null,
mgsl_isr_misc,
mgsl_isr_io_pin,
mgsl_isr_transmit_data,
mgsl_isr_transmit_status,
mgsl_isr_receive_data,
mgsl_isr_receive_status
};
/*
* ioctl call handlers
*/
static int tiocmget(struct tty_struct *tty, struct file *file);
static int tiocmset(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear);
static int mgsl_get_stats(struct mgsl_struct * info, struct mgsl_icount
__user *user_icount);
static int mgsl_get_params(struct mgsl_struct * info, MGSL_PARAMS __user *user_params);
static int mgsl_set_params(struct mgsl_struct * info, MGSL_PARAMS __user *new_params);
static int mgsl_get_txidle(struct mgsl_struct * info, int __user *idle_mode);
static int mgsl_set_txidle(struct mgsl_struct * info, int idle_mode);
static int mgsl_txenable(struct mgsl_struct * info, int enable);
static int mgsl_txabort(struct mgsl_struct * info);
static int mgsl_rxenable(struct mgsl_struct * info, int enable);
static int mgsl_wait_event(struct mgsl_struct * info, int __user *mask);
static int mgsl_loopmode_send_done( struct mgsl_struct * info );
/* set non-zero on successful registration with PCI subsystem */
static bool pci_registered;
/*
* Global linked list of SyncLink devices
*/
static struct mgsl_struct *mgsl_device_list;
static int mgsl_device_count;
/*
* Set this param to non-zero to load eax with the
* .text section address and breakpoint on module load.
* This is useful for use with gdb and add-symbol-file command.
*/
static int break_on_load;
/*
* Driver major number, defaults to zero to get auto
* assigned major number. May be forced as module parameter.
*/
static int ttymajor;
/*
* Array of user specified options for ISA adapters.
*/
static int io[MAX_ISA_DEVICES];
static int irq[MAX_ISA_DEVICES];
static int dma[MAX_ISA_DEVICES];
static int debug_level;
static int maxframe[MAX_TOTAL_DEVICES];
static int txdmabufs[MAX_TOTAL_DEVICES];
static int txholdbufs[MAX_TOTAL_DEVICES];
module_param(break_on_load, bool, 0);
module_param(ttymajor, int, 0);
module_param_array(io, int, NULL, 0);
module_param_array(irq, int, NULL, 0);
module_param_array(dma, int, NULL, 0);
module_param(debug_level, int, 0);
module_param_array(maxframe, int, NULL, 0);
module_param_array(txdmabufs, int, NULL, 0);
module_param_array(txholdbufs, int, NULL, 0);
static char *driver_name = "SyncLink serial driver";
static char *driver_version = "$Revision: 4.38 $";
static int synclink_init_one (struct pci_dev *dev,
const struct pci_device_id *ent);
static void synclink_remove_one (struct pci_dev *dev);
static struct pci_device_id synclink_pci_tbl[] = {
{ PCI_VENDOR_ID_MICROGATE, PCI_DEVICE_ID_MICROGATE_USC, PCI_ANY_ID, PCI_ANY_ID, },
{ PCI_VENDOR_ID_MICROGATE, 0x0210, PCI_ANY_ID, PCI_ANY_ID, },
{ 0, }, /* terminate list */
};
MODULE_DEVICE_TABLE(pci, synclink_pci_tbl);
MODULE_LICENSE("GPL");
static struct pci_driver synclink_pci_driver = {
.name = "synclink",
.id_table = synclink_pci_tbl,
.probe = synclink_init_one,
.remove = __devexit_p(synclink_remove_one),
};
static struct tty_driver *serial_driver;
/* number of characters left in xmit buffer before we ask for more */
#define WAKEUP_CHARS 256
static void mgsl_change_params(struct mgsl_struct *info);
static void mgsl_wait_until_sent(struct tty_struct *tty, int timeout);
/*
* 1st function defined in .text section. Calling this function in
* init_module() followed by a breakpoint allows a remote debugger
* (gdb) to get the .text address for the add-symbol-file command.
* This allows remote debugging of dynamically loadable modules.
*/
static void* mgsl_get_text_ptr(void)
{
return mgsl_get_text_ptr;
}
static inline int mgsl_paranoia_check(struct mgsl_struct *info,
char *name, const char *routine)
{
#ifdef MGSL_PARANOIA_CHECK
static const char *badmagic =
"Warning: bad magic number for mgsl struct (%s) in %s\n";
static const char *badinfo =
"Warning: null mgsl_struct for (%s) in %s\n";
if (!info) {
printk(badinfo, name, routine);
return 1;
}
if (info->magic != MGSL_MAGIC) {
printk(badmagic, name, routine);
return 1;
}
#else
if (!info)
return 1;
#endif
return 0;
}
/**
* line discipline callback wrappers
*
* The wrappers maintain line discipline references
* while calling into the line discipline.
*
* ldisc_receive_buf - pass receive data to line discipline
*/
static void ldisc_receive_buf(struct tty_struct *tty,
const __u8 *data, char *flags, int count)
{
struct tty_ldisc *ld;
if (!tty)
return;
ld = tty_ldisc_ref(tty);
if (ld) {
if (ld->ops->receive_buf)
ld->ops->receive_buf(tty, data, flags, count);
tty_ldisc_deref(ld);
}
}
/* mgsl_stop() throttle (stop) transmitter
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static void mgsl_stop(struct tty_struct *tty)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if (mgsl_paranoia_check(info, tty->name, "mgsl_stop"))
return;
if ( debug_level >= DEBUG_LEVEL_INFO )
printk("mgsl_stop(%s)\n",info->device_name);
spin_lock_irqsave(&info->irq_spinlock,flags);
if (info->tx_enabled)
usc_stop_transmitter(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
} /* end of mgsl_stop() */
/* mgsl_start() release (start) transmitter
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static void mgsl_start(struct tty_struct *tty)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if (mgsl_paranoia_check(info, tty->name, "mgsl_start"))
return;
if ( debug_level >= DEBUG_LEVEL_INFO )
printk("mgsl_start(%s)\n",info->device_name);
spin_lock_irqsave(&info->irq_spinlock,flags);
if (!info->tx_enabled)
usc_start_transmitter(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
} /* end of mgsl_start() */
/*
* Bottom half work queue access functions
*/
/* mgsl_bh_action() Return next bottom half action to perform.
* Return Value: BH action code or 0 if nothing to do.
*/
static int mgsl_bh_action(struct mgsl_struct *info)
{
unsigned long flags;
int rc = 0;
spin_lock_irqsave(&info->irq_spinlock,flags);
if (info->pending_bh & BH_RECEIVE) {
info->pending_bh &= ~BH_RECEIVE;
rc = BH_RECEIVE;
} else if (info->pending_bh & BH_TRANSMIT) {
info->pending_bh &= ~BH_TRANSMIT;
rc = BH_TRANSMIT;
} else if (info->pending_bh & BH_STATUS) {
info->pending_bh &= ~BH_STATUS;
rc = BH_STATUS;
}
if (!rc) {
/* Mark BH routine as complete */
info->bh_running = false;
info->bh_requested = false;
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
return rc;
}
/*
* Perform bottom half processing of work items queued by ISR.
*/
static void mgsl_bh_handler(struct work_struct *work)
{
struct mgsl_struct *info =
container_of(work, struct mgsl_struct, task);
int action;
if (!info)
return;
if ( debug_level >= DEBUG_LEVEL_BH )
printk( "%s(%d):mgsl_bh_handler(%s) entry\n",
__FILE__,__LINE__,info->device_name);
info->bh_running = true;
while((action = mgsl_bh_action(info)) != 0) {
/* Process work item */
if ( debug_level >= DEBUG_LEVEL_BH )
printk( "%s(%d):mgsl_bh_handler() work item action=%d\n",
__FILE__,__LINE__,action);
switch (action) {
case BH_RECEIVE:
mgsl_bh_receive(info);
break;
case BH_TRANSMIT:
mgsl_bh_transmit(info);
break;
case BH_STATUS:
mgsl_bh_status(info);
break;
default:
/* unknown work item ID */
printk("Unknown work item ID=%08X!\n", action);
break;
}
}
if ( debug_level >= DEBUG_LEVEL_BH )
printk( "%s(%d):mgsl_bh_handler(%s) exit\n",
__FILE__,__LINE__,info->device_name);
}
static void mgsl_bh_receive(struct mgsl_struct *info)
{
bool (*get_rx_frame)(struct mgsl_struct *info) =
(info->params.mode == MGSL_MODE_HDLC ? mgsl_get_rx_frame : mgsl_get_raw_rx_frame);
if ( debug_level >= DEBUG_LEVEL_BH )
printk( "%s(%d):mgsl_bh_receive(%s)\n",
__FILE__,__LINE__,info->device_name);
do
{
if (info->rx_rcc_underrun) {
unsigned long flags;
spin_lock_irqsave(&info->irq_spinlock,flags);
usc_start_receiver(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
return;
}
} while(get_rx_frame(info));
}
static void mgsl_bh_transmit(struct mgsl_struct *info)
{
struct tty_struct *tty = info->port.tty;
unsigned long flags;
if ( debug_level >= DEBUG_LEVEL_BH )
printk( "%s(%d):mgsl_bh_transmit() entry on %s\n",
__FILE__,__LINE__,info->device_name);
if (tty)
tty_wakeup(tty);
/* if transmitter idle and loopmode_send_done_requested
* then start echoing RxD to TxD
*/
spin_lock_irqsave(&info->irq_spinlock,flags);
if ( !info->tx_active && info->loopmode_send_done_requested )
usc_loopmode_send_done( info );
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
static void mgsl_bh_status(struct mgsl_struct *info)
{
if ( debug_level >= DEBUG_LEVEL_BH )
printk( "%s(%d):mgsl_bh_status() entry on %s\n",
__FILE__,__LINE__,info->device_name);
info->ri_chkcount = 0;
info->dsr_chkcount = 0;
info->dcd_chkcount = 0;
info->cts_chkcount = 0;
}
/* mgsl_isr_receive_status()
*
* Service a receive status interrupt. The type of status
* interrupt is indicated by the state of the RCSR.
* This is only used for HDLC mode.
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_receive_status( struct mgsl_struct *info )
{
u16 status = usc_InReg( info, RCSR );
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_receive_status status=%04X\n",
__FILE__,__LINE__,status);
if ( (status & RXSTATUS_ABORT_RECEIVED) &&
info->loopmode_insert_requested &&
usc_loopmode_active(info) )
{
++info->icount.rxabort;
info->loopmode_insert_requested = false;
/* clear CMR:13 to start echoing RxD to TxD */
info->cmr_value &= ~BIT13;
usc_OutReg(info, CMR, info->cmr_value);
/* disable received abort irq (no longer required) */
usc_OutReg(info, RICR,
(usc_InReg(info, RICR) & ~RXSTATUS_ABORT_RECEIVED));
}
if (status & (RXSTATUS_EXITED_HUNT + RXSTATUS_IDLE_RECEIVED)) {
if (status & RXSTATUS_EXITED_HUNT)
info->icount.exithunt++;
if (status & RXSTATUS_IDLE_RECEIVED)
info->icount.rxidle++;
wake_up_interruptible(&info->event_wait_q);
}
if (status & RXSTATUS_OVERRUN){
info->icount.rxover++;
usc_process_rxoverrun_sync( info );
}
usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
usc_UnlatchRxstatusBits( info, status );
} /* end of mgsl_isr_receive_status() */
/* mgsl_isr_transmit_status()
*
* Service a transmit status interrupt
* HDLC mode :end of transmit frame
* Async mode:all data is sent
* transmit status is indicated by bits in the TCSR.
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_transmit_status( struct mgsl_struct *info )
{
u16 status = usc_InReg( info, TCSR );
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_transmit_status status=%04X\n",
__FILE__,__LINE__,status);
usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
usc_UnlatchTxstatusBits( info, status );
if ( status & (TXSTATUS_UNDERRUN | TXSTATUS_ABORT_SENT) )
{
/* finished sending HDLC abort. This may leave */
/* the TxFifo with data from the aborted frame */
/* so purge the TxFifo. Also shutdown the DMA */
/* channel in case there is data remaining in */
/* the DMA buffer */
usc_DmaCmd( info, DmaCmd_ResetTxChannel );
usc_RTCmd( info, RTCmd_PurgeTxFifo );
}
if ( status & TXSTATUS_EOF_SENT )
info->icount.txok++;
else if ( status & TXSTATUS_UNDERRUN )
info->icount.txunder++;
else if ( status & TXSTATUS_ABORT_SENT )
info->icount.txabort++;
else
info->icount.txunder++;
info->tx_active = false;
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
del_timer(&info->tx_timer);
if ( info->drop_rts_on_tx_done ) {
usc_get_serial_signals( info );
if ( info->serial_signals & SerialSignal_RTS ) {
info->serial_signals &= ~SerialSignal_RTS;
usc_set_serial_signals( info );
}
info->drop_rts_on_tx_done = false;
}
#if SYNCLINK_GENERIC_HDLC
if (info->netcount)
hdlcdev_tx_done(info);
else
#endif
{
if (info->port.tty->stopped || info->port.tty->hw_stopped) {
usc_stop_transmitter(info);
return;
}
info->pending_bh |= BH_TRANSMIT;
}
} /* end of mgsl_isr_transmit_status() */
/* mgsl_isr_io_pin()
*
* Service an Input/Output pin interrupt. The type of
* interrupt is indicated by bits in the MISR
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_io_pin( struct mgsl_struct *info )
{
struct mgsl_icount *icount;
u16 status = usc_InReg( info, MISR );
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_io_pin status=%04X\n",
__FILE__,__LINE__,status);
usc_ClearIrqPendingBits( info, IO_PIN );
usc_UnlatchIostatusBits( info, status );
if (status & (MISCSTATUS_CTS_LATCHED | MISCSTATUS_DCD_LATCHED |
MISCSTATUS_DSR_LATCHED | MISCSTATUS_RI_LATCHED) ) {
icount = &info->icount;
/* update input line counters */
if (status & MISCSTATUS_RI_LATCHED) {
if ((info->ri_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
usc_DisablestatusIrqs(info,SICR_RI);
icount->rng++;
if ( status & MISCSTATUS_RI )
info->input_signal_events.ri_up++;
else
info->input_signal_events.ri_down++;
}
if (status & MISCSTATUS_DSR_LATCHED) {
if ((info->dsr_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
usc_DisablestatusIrqs(info,SICR_DSR);
icount->dsr++;
if ( status & MISCSTATUS_DSR )
info->input_signal_events.dsr_up++;
else
info->input_signal_events.dsr_down++;
}
if (status & MISCSTATUS_DCD_LATCHED) {
if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
usc_DisablestatusIrqs(info,SICR_DCD);
icount->dcd++;
if (status & MISCSTATUS_DCD) {
info->input_signal_events.dcd_up++;
} else
info->input_signal_events.dcd_down++;
#if SYNCLINK_GENERIC_HDLC
if (info->netcount) {
if (status & MISCSTATUS_DCD)
netif_carrier_on(info->netdev);
else
netif_carrier_off(info->netdev);
}
#endif
}
if (status & MISCSTATUS_CTS_LATCHED)
{
if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
usc_DisablestatusIrqs(info,SICR_CTS);
icount->cts++;
if ( status & MISCSTATUS_CTS )
info->input_signal_events.cts_up++;
else
info->input_signal_events.cts_down++;
}
wake_up_interruptible(&info->status_event_wait_q);
wake_up_interruptible(&info->event_wait_q);
if ( (info->port.flags & ASYNC_CHECK_CD) &&
(status & MISCSTATUS_DCD_LATCHED) ) {
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s CD now %s...", info->device_name,
(status & MISCSTATUS_DCD) ? "on" : "off");
if (status & MISCSTATUS_DCD)
wake_up_interruptible(&info->port.open_wait);
else {
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("doing serial hangup...");
if (info->port.tty)
tty_hangup(info->port.tty);
}
}
if ( (info->port.flags & ASYNC_CTS_FLOW) &&
(status & MISCSTATUS_CTS_LATCHED) ) {
if (info->port.tty->hw_stopped) {
if (status & MISCSTATUS_CTS) {
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("CTS tx start...");
if (info->port.tty)
info->port.tty->hw_stopped = 0;
usc_start_transmitter(info);
info->pending_bh |= BH_TRANSMIT;
return;
}
} else {
if (!(status & MISCSTATUS_CTS)) {
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("CTS tx stop...");
if (info->port.tty)
info->port.tty->hw_stopped = 1;
usc_stop_transmitter(info);
}
}
}
}
info->pending_bh |= BH_STATUS;
/* for diagnostics set IRQ flag */
if ( status & MISCSTATUS_TXC_LATCHED ){
usc_OutReg( info, SICR,
(unsigned short)(usc_InReg(info,SICR) & ~(SICR_TXC_ACTIVE+SICR_TXC_INACTIVE)) );
usc_UnlatchIostatusBits( info, MISCSTATUS_TXC_LATCHED );
info->irq_occurred = true;
}
} /* end of mgsl_isr_io_pin() */
/* mgsl_isr_transmit_data()
*
* Service a transmit data interrupt (async mode only).
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_transmit_data( struct mgsl_struct *info )
{
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_transmit_data xmit_cnt=%d\n",
__FILE__,__LINE__,info->xmit_cnt);
usc_ClearIrqPendingBits( info, TRANSMIT_DATA );
if (info->port.tty->stopped || info->port.tty->hw_stopped) {
usc_stop_transmitter(info);
return;
}
if ( info->xmit_cnt )
usc_load_txfifo( info );
else
info->tx_active = false;
if (info->xmit_cnt < WAKEUP_CHARS)
info->pending_bh |= BH_TRANSMIT;
} /* end of mgsl_isr_transmit_data() */
/* mgsl_isr_receive_data()
*
* Service a receive data interrupt. This occurs
* when operating in asynchronous interrupt transfer mode.
* The receive data FIFO is flushed to the receive data buffers.
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_receive_data( struct mgsl_struct *info )
{
int Fifocount;
u16 status;
int work = 0;
unsigned char DataByte;
struct tty_struct *tty = info->port.tty;
struct mgsl_icount *icount = &info->icount;
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_receive_data\n",
__FILE__,__LINE__);
usc_ClearIrqPendingBits( info, RECEIVE_DATA );
/* select FIFO status for RICR readback */
usc_RCmd( info, RCmd_SelectRicrRxFifostatus );
/* clear the Wordstatus bit so that status readback */
/* only reflects the status of this byte */
usc_OutReg( info, RICR+LSBONLY, (u16)(usc_InReg(info, RICR+LSBONLY) & ~BIT3 ));
/* flush the receive FIFO */
while( (Fifocount = (usc_InReg(info,RICR) >> 8)) ) {
int flag;
/* read one byte from RxFIFO */
outw( (inw(info->io_base + CCAR) & 0x0780) | (RDR+LSBONLY),
info->io_base + CCAR );
DataByte = inb( info->io_base + CCAR );
/* get the status of the received byte */
status = usc_InReg(info, RCSR);
if ( status & (RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR +
RXSTATUS_OVERRUN + RXSTATUS_BREAK_RECEIVED) )
usc_UnlatchRxstatusBits(info,RXSTATUS_ALL);
icount->rx++;
flag = 0;
if ( status & (RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR +
RXSTATUS_OVERRUN + RXSTATUS_BREAK_RECEIVED) ) {
printk("rxerr=%04X\n",status);
/* update error statistics */
if ( status & RXSTATUS_BREAK_RECEIVED ) {
status &= ~(RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR);
icount->brk++;
} else if (status & RXSTATUS_PARITY_ERROR)
icount->parity++;
else if (status & RXSTATUS_FRAMING_ERROR)
icount->frame++;
else if (status & RXSTATUS_OVERRUN) {
/* must issue purge fifo cmd before */
/* 16C32 accepts more receive chars */
usc_RTCmd(info,RTCmd_PurgeRxFifo);
icount->overrun++;
}
/* discard char if tty control flags say so */
if (status & info->ignore_status_mask)
continue;
status &= info->read_status_mask;
if (status & RXSTATUS_BREAK_RECEIVED) {
flag = TTY_BREAK;
if (info->port.flags & ASYNC_SAK)
do_SAK(tty);
} else if (status & RXSTATUS_PARITY_ERROR)
flag = TTY_PARITY;
else if (status & RXSTATUS_FRAMING_ERROR)
flag = TTY_FRAME;
} /* end of if (error) */
tty_insert_flip_char(tty, DataByte, flag);
if (status & RXSTATUS_OVERRUN) {
/* Overrun is special, since it's
* reported immediately, and doesn't
* affect the current character
*/
work += tty_insert_flip_char(tty, 0, TTY_OVERRUN);
}
}
if ( debug_level >= DEBUG_LEVEL_ISR ) {
printk("%s(%d):rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
__FILE__,__LINE__,icount->rx,icount->brk,
icount->parity,icount->frame,icount->overrun);
}
if(work)
tty_flip_buffer_push(tty);
}
/* mgsl_isr_misc()
*
* Service a miscellaneous interrupt source.
*
* Arguments: info pointer to device extension (instance data)
* Return Value: None
*/
static void mgsl_isr_misc( struct mgsl_struct *info )
{
u16 status = usc_InReg( info, MISR );
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_misc status=%04X\n",
__FILE__,__LINE__,status);
if ((status & MISCSTATUS_RCC_UNDERRUN) &&
(info->params.mode == MGSL_MODE_HDLC)) {
/* turn off receiver and rx DMA */
usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
usc_DmaCmd(info, DmaCmd_ResetRxChannel);
usc_UnlatchRxstatusBits(info, RXSTATUS_ALL);
usc_ClearIrqPendingBits(info, RECEIVE_DATA + RECEIVE_STATUS);
usc_DisableInterrupts(info, RECEIVE_DATA + RECEIVE_STATUS);
/* schedule BH handler to restart receiver */
info->pending_bh |= BH_RECEIVE;
info->rx_rcc_underrun = true;
}
usc_ClearIrqPendingBits( info, MISC );
usc_UnlatchMiscstatusBits( info, status );
} /* end of mgsl_isr_misc() */
/* mgsl_isr_null()
*
* Services undefined interrupt vectors from the
* USC. (hence this function SHOULD never be called)
*
* Arguments: info pointer to device extension (instance data)
* Return Value: None
*/
static void mgsl_isr_null( struct mgsl_struct *info )
{
} /* end of mgsl_isr_null() */
/* mgsl_isr_receive_dma()
*
* Service a receive DMA channel interrupt.
* For this driver there are two sources of receive DMA interrupts
* as identified in the Receive DMA mode Register (RDMR):
*
* BIT3 EOA/EOL End of List, all receive buffers in receive
* buffer list have been filled (no more free buffers
* available). The DMA controller has shut down.
*
* BIT2 EOB End of Buffer. This interrupt occurs when a receive
* DMA buffer is terminated in response to completion
* of a good frame or a frame with errors. The status
* of the frame is stored in the buffer entry in the
* list of receive buffer entries.
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_receive_dma( struct mgsl_struct *info )
{
u16 status;
/* clear interrupt pending and IUS bit for Rx DMA IRQ */
usc_OutDmaReg( info, CDIR, BIT9+BIT1 );
/* Read the receive DMA status to identify interrupt type. */
/* This also clears the status bits. */
status = usc_InDmaReg( info, RDMR );
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_receive_dma(%s) status=%04X\n",
__FILE__,__LINE__,info->device_name,status);
info->pending_bh |= BH_RECEIVE;
if ( status & BIT3 ) {
info->rx_overflow = true;
info->icount.buf_overrun++;
}
} /* end of mgsl_isr_receive_dma() */
/* mgsl_isr_transmit_dma()
*
* This function services a transmit DMA channel interrupt.
*
* For this driver there is one source of transmit DMA interrupts
* as identified in the Transmit DMA Mode Register (TDMR):
*
* BIT2 EOB End of Buffer. This interrupt occurs when a
* transmit DMA buffer has been emptied.
*
* The driver maintains enough transmit DMA buffers to hold at least
* one max frame size transmit frame. When operating in a buffered
* transmit mode, there may be enough transmit DMA buffers to hold at
* least two or more max frame size frames. On an EOB condition,
* determine if there are any queued transmit buffers and copy into
* transmit DMA buffers if we have room.
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void mgsl_isr_transmit_dma( struct mgsl_struct *info )
{
u16 status;
/* clear interrupt pending and IUS bit for Tx DMA IRQ */
usc_OutDmaReg(info, CDIR, BIT8+BIT0 );
/* Read the transmit DMA status to identify interrupt type. */
/* This also clears the status bits. */
status = usc_InDmaReg( info, TDMR );
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):mgsl_isr_transmit_dma(%s) status=%04X\n",
__FILE__,__LINE__,info->device_name,status);
if ( status & BIT2 ) {
--info->tx_dma_buffers_used;
/* if there are transmit frames queued,
* try to load the next one
*/
if ( load_next_tx_holding_buffer(info) ) {
/* if call returns non-zero value, we have
* at least one free tx holding buffer
*/
info->pending_bh |= BH_TRANSMIT;
}
}
} /* end of mgsl_isr_transmit_dma() */
/* mgsl_interrupt()
*
* Interrupt service routine entry point.
*
* Arguments:
*
* irq interrupt number that caused interrupt
* dev_id device ID supplied during interrupt registration
*
* Return Value: None
*/
static irqreturn_t mgsl_interrupt(int dummy, void *dev_id)
{
struct mgsl_struct *info = dev_id;
u16 UscVector;
u16 DmaVector;
if ( debug_level >= DEBUG_LEVEL_ISR )
printk(KERN_DEBUG "%s(%d):mgsl_interrupt(%d)entry.\n",
__FILE__, __LINE__, info->irq_level);
spin_lock(&info->irq_spinlock);
for(;;) {
/* Read the interrupt vectors from hardware. */
UscVector = usc_InReg(info, IVR) >> 9;
DmaVector = usc_InDmaReg(info, DIVR);
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):%s UscVector=%08X DmaVector=%08X\n",
__FILE__,__LINE__,info->device_name,UscVector,DmaVector);
if ( !UscVector && !DmaVector )
break;
/* Dispatch interrupt vector */
if ( UscVector )
(*UscIsrTable[UscVector])(info);
else if ( (DmaVector&(BIT10|BIT9)) == BIT10)
mgsl_isr_transmit_dma(info);
else
mgsl_isr_receive_dma(info);
if ( info->isr_overflow ) {
printk(KERN_ERR "%s(%d):%s isr overflow irq=%d\n",
__FILE__, __LINE__, info->device_name, info->irq_level);
usc_DisableMasterIrqBit(info);
usc_DisableDmaInterrupts(info,DICR_MASTER);
break;
}
}
/* Request bottom half processing if there's something
* for it to do and the bh is not already running
*/
if ( info->pending_bh && !info->bh_running && !info->bh_requested ) {
if ( debug_level >= DEBUG_LEVEL_ISR )
printk("%s(%d):%s queueing bh task.\n",
__FILE__,__LINE__,info->device_name);
schedule_work(&info->task);
info->bh_requested = true;
}
spin_unlock(&info->irq_spinlock);
if ( debug_level >= DEBUG_LEVEL_ISR )
printk(KERN_DEBUG "%s(%d):mgsl_interrupt(%d)exit.\n",
__FILE__, __LINE__, info->irq_level);
return IRQ_HANDLED;
} /* end of mgsl_interrupt() */
/* startup()
*
* Initialize and start device.
*
* Arguments: info pointer to device instance data
* Return Value: 0 if success, otherwise error code
*/
static int startup(struct mgsl_struct * info)
{
int retval = 0;
if ( debug_level >= DEBUG_LEVEL_INFO )
printk("%s(%d):mgsl_startup(%s)\n",__FILE__,__LINE__,info->device_name);
if (info->port.flags & ASYNC_INITIALIZED)
return 0;
if (!info->xmit_buf) {
/* allocate a page of memory for a transmit buffer */
info->xmit_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL);
if (!info->xmit_buf) {
printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
__FILE__,__LINE__,info->device_name);
return -ENOMEM;
}
}
info->pending_bh = 0;
memset(&info->icount, 0, sizeof(info->icount));
setup_timer(&info->tx_timer, mgsl_tx_timeout, (unsigned long)info);
/* Allocate and claim adapter resources */
retval = mgsl_claim_resources(info);
/* perform existence check and diagnostics */
if ( !retval )
retval = mgsl_adapter_test(info);
if ( retval ) {
if (capable(CAP_SYS_ADMIN) && info->port.tty)
set_bit(TTY_IO_ERROR, &info->port.tty->flags);
mgsl_release_resources(info);
return retval;
}
/* program hardware for current parameters */
mgsl_change_params(info);
if (info->port.tty)
clear_bit(TTY_IO_ERROR, &info->port.tty->flags);
info->port.flags |= ASYNC_INITIALIZED;
return 0;
} /* end of startup() */
/* shutdown()
*
* Called by mgsl_close() and mgsl_hangup() to shutdown hardware
*
* Arguments: info pointer to device instance data
* Return Value: None
*/
static void shutdown(struct mgsl_struct * info)
{
unsigned long flags;
if (!(info->port.flags & ASYNC_INITIALIZED))
return;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_shutdown(%s)\n",
__FILE__,__LINE__, info->device_name );
/* clear status wait queue because status changes */
/* can't happen after shutting down the hardware */
wake_up_interruptible(&info->status_event_wait_q);
wake_up_interruptible(&info->event_wait_q);
del_timer_sync(&info->tx_timer);
if (info->xmit_buf) {
free_page((unsigned long) info->xmit_buf);
info->xmit_buf = NULL;
}
spin_lock_irqsave(&info->irq_spinlock,flags);
usc_DisableMasterIrqBit(info);
usc_stop_receiver(info);
usc_stop_transmitter(info);
usc_DisableInterrupts(info,RECEIVE_DATA + RECEIVE_STATUS +
TRANSMIT_DATA + TRANSMIT_STATUS + IO_PIN + MISC );
usc_DisableDmaInterrupts(info,DICR_MASTER + DICR_TRANSMIT + DICR_RECEIVE);
/* Disable DMAEN (Port 7, Bit 14) */
/* This disconnects the DMA request signal from the ISA bus */
/* on the ISA adapter. This has no effect for the PCI adapter */
usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT15) | BIT14));
/* Disable INTEN (Port 6, Bit12) */
/* This disconnects the IRQ request signal to the ISA bus */
/* on the ISA adapter. This has no effect for the PCI adapter */
usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) | BIT12));
if (!info->port.tty || info->port.tty->termios->c_cflag & HUPCL) {
info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
usc_set_serial_signals(info);
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
mgsl_release_resources(info);
if (info->port.tty)
set_bit(TTY_IO_ERROR, &info->port.tty->flags);
info->port.flags &= ~ASYNC_INITIALIZED;
} /* end of shutdown() */
static void mgsl_program_hw(struct mgsl_struct *info)
{
unsigned long flags;
spin_lock_irqsave(&info->irq_spinlock,flags);
usc_stop_receiver(info);
usc_stop_transmitter(info);
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
if (info->params.mode == MGSL_MODE_HDLC ||
info->params.mode == MGSL_MODE_RAW ||
info->netcount)
usc_set_sync_mode(info);
else
usc_set_async_mode(info);
usc_set_serial_signals(info);
info->dcd_chkcount = 0;
info->cts_chkcount = 0;
info->ri_chkcount = 0;
info->dsr_chkcount = 0;
usc_EnableStatusIrqs(info,SICR_CTS+SICR_DSR+SICR_DCD+SICR_RI);
usc_EnableInterrupts(info, IO_PIN);
usc_get_serial_signals(info);
if (info->netcount || info->port.tty->termios->c_cflag & CREAD)
usc_start_receiver(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
/* Reconfigure adapter based on new parameters
*/
static void mgsl_change_params(struct mgsl_struct *info)
{
unsigned cflag;
int bits_per_char;
if (!info->port.tty || !info->port.tty->termios)
return;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_change_params(%s)\n",
__FILE__,__LINE__, info->device_name );
cflag = info->port.tty->termios->c_cflag;
/* if B0 rate (hangup) specified then negate DTR and RTS */
/* otherwise assert DTR and RTS */
if (cflag & CBAUD)
info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
else
info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
/* byte size and parity */
switch (cflag & CSIZE) {
case CS5: info->params.data_bits = 5; break;
case CS6: info->params.data_bits = 6; break;
case CS7: info->params.data_bits = 7; break;
case CS8: info->params.data_bits = 8; break;
/* Never happens, but GCC is too dumb to figure it out */
default: info->params.data_bits = 7; break;
}
if (cflag & CSTOPB)
info->params.stop_bits = 2;
else
info->params.stop_bits = 1;
info->params.parity = ASYNC_PARITY_NONE;
if (cflag & PARENB) {
if (cflag & PARODD)
info->params.parity = ASYNC_PARITY_ODD;
else
info->params.parity = ASYNC_PARITY_EVEN;
#ifdef CMSPAR
if (cflag & CMSPAR)
info->params.parity = ASYNC_PARITY_SPACE;
#endif
}
/* calculate number of jiffies to transmit a full
* FIFO (32 bytes) at specified data rate
*/
bits_per_char = info->params.data_bits +
info->params.stop_bits + 1;
/* if port data rate is set to 460800 or less then
* allow tty settings to override, otherwise keep the
* current data rate.
*/
if (info->params.data_rate <= 460800)
info->params.data_rate = tty_get_baud_rate(info->port.tty);
if ( info->params.data_rate ) {
info->timeout = (32*HZ*bits_per_char) /
info->params.data_rate;
}
info->timeout += HZ/50; /* Add .02 seconds of slop */
if (cflag & CRTSCTS)
info->port.flags |= ASYNC_CTS_FLOW;
else
info->port.flags &= ~ASYNC_CTS_FLOW;
if (cflag & CLOCAL)
info->port.flags &= ~ASYNC_CHECK_CD;
else
info->port.flags |= ASYNC_CHECK_CD;
/* process tty input control flags */
info->read_status_mask = RXSTATUS_OVERRUN;
if (I_INPCK(info->port.tty))
info->read_status_mask |= RXSTATUS_PARITY_ERROR | RXSTATUS_FRAMING_ERROR;
if (I_BRKINT(info->port.tty) || I_PARMRK(info->port.tty))
info->read_status_mask |= RXSTATUS_BREAK_RECEIVED;
if (I_IGNPAR(info->port.tty))
info->ignore_status_mask |= RXSTATUS_PARITY_ERROR | RXSTATUS_FRAMING_ERROR;
if (I_IGNBRK(info->port.tty)) {
info->ignore_status_mask |= RXSTATUS_BREAK_RECEIVED;
/* If ignoring parity and break indicators, ignore
* overruns too. (For real raw support).
*/
if (I_IGNPAR(info->port.tty))
info->ignore_status_mask |= RXSTATUS_OVERRUN;
}
mgsl_program_hw(info);
} /* end of mgsl_change_params() */
/* mgsl_put_char()
*
* Add a character to the transmit buffer.
*
* Arguments: tty pointer to tty information structure
* ch character to add to transmit buffer
*
* Return Value: None
*/
static int mgsl_put_char(struct tty_struct *tty, unsigned char ch)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
int ret = 0;
if (debug_level >= DEBUG_LEVEL_INFO) {
printk(KERN_DEBUG "%s(%d):mgsl_put_char(%d) on %s\n",
__FILE__, __LINE__, ch, info->device_name);
}
if (mgsl_paranoia_check(info, tty->name, "mgsl_put_char"))
return 0;
if (!info->xmit_buf)
return 0;
spin_lock_irqsave(&info->irq_spinlock, flags);
if ((info->params.mode == MGSL_MODE_ASYNC ) || !info->tx_active) {
if (info->xmit_cnt < SERIAL_XMIT_SIZE - 1) {
info->xmit_buf[info->xmit_head++] = ch;
info->xmit_head &= SERIAL_XMIT_SIZE-1;
info->xmit_cnt++;
ret = 1;
}
}
spin_unlock_irqrestore(&info->irq_spinlock, flags);
return ret;
} /* end of mgsl_put_char() */
/* mgsl_flush_chars()
*
* Enable transmitter so remaining characters in the
* transmit buffer are sent.
*
* Arguments: tty pointer to tty information structure
* Return Value: None
*/
static void mgsl_flush_chars(struct tty_struct *tty)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_flush_chars() entry on %s xmit_cnt=%d\n",
__FILE__,__LINE__,info->device_name,info->xmit_cnt);
if (mgsl_paranoia_check(info, tty->name, "mgsl_flush_chars"))
return;
if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
!info->xmit_buf)
return;
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_flush_chars() entry on %s starting transmitter\n",
__FILE__,__LINE__,info->device_name );
spin_lock_irqsave(&info->irq_spinlock,flags);
if (!info->tx_active) {
if ( (info->params.mode == MGSL_MODE_HDLC ||
info->params.mode == MGSL_MODE_RAW) && info->xmit_cnt ) {
/* operating in synchronous (frame oriented) mode */
/* copy data from circular xmit_buf to */
/* transmit DMA buffer. */
mgsl_load_tx_dma_buffer(info,
info->xmit_buf,info->xmit_cnt);
}
usc_start_transmitter(info);
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
} /* end of mgsl_flush_chars() */
/* mgsl_write()
*
* Send a block of data
*
* Arguments:
*
* tty pointer to tty information structure
* buf pointer to buffer containing send data
* count size of send data in bytes
*
* Return Value: number of characters written
*/
static int mgsl_write(struct tty_struct * tty,
const unsigned char *buf, int count)
{
int c, ret = 0;
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_write(%s) count=%d\n",
__FILE__,__LINE__,info->device_name,count);
if (mgsl_paranoia_check(info, tty->name, "mgsl_write"))
goto cleanup;
if (!info->xmit_buf)
goto cleanup;
if ( info->params.mode == MGSL_MODE_HDLC ||
info->params.mode == MGSL_MODE_RAW ) {
/* operating in synchronous (frame oriented) mode */
/* operating in synchronous (frame oriented) mode */
if (info->tx_active) {
if ( info->params.mode == MGSL_MODE_HDLC ) {
ret = 0;
goto cleanup;
}
/* transmitter is actively sending data -
* if we have multiple transmit dma and
* holding buffers, attempt to queue this
* frame for transmission at a later time.
*/
if (info->tx_holding_count >= info->num_tx_holding_buffers ) {
/* no tx holding buffers available */
ret = 0;
goto cleanup;
}
/* queue transmit frame request */
ret = count;
save_tx_buffer_request(info,buf,count);
/* if we have sufficient tx dma buffers,
* load the next buffered tx request
*/
spin_lock_irqsave(&info->irq_spinlock,flags);
load_next_tx_holding_buffer(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
goto cleanup;
}
/* if operating in HDLC LoopMode and the adapter */
/* has yet to be inserted into the loop, we can't */
/* transmit */
if ( (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) &&
!usc_loopmode_active(info) )
{
ret = 0;
goto cleanup;
}
if ( info->xmit_cnt ) {
/* Send accumulated from send_char() calls */
/* as frame and wait before accepting more data. */
ret = 0;
/* copy data from circular xmit_buf to */
/* transmit DMA buffer. */
mgsl_load_tx_dma_buffer(info,
info->xmit_buf,info->xmit_cnt);
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_write(%s) sync xmit_cnt flushing\n",
__FILE__,__LINE__,info->device_name);
} else {
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_write(%s) sync transmit accepted\n",
__FILE__,__LINE__,info->device_name);
ret = count;
info->xmit_cnt = count;
mgsl_load_tx_dma_buffer(info,buf,count);
}
} else {
while (1) {
spin_lock_irqsave(&info->irq_spinlock,flags);
c = min_t(int, count,
min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
SERIAL_XMIT_SIZE - info->xmit_head));
if (c <= 0) {
spin_unlock_irqrestore(&info->irq_spinlock,flags);
break;
}
memcpy(info->xmit_buf + info->xmit_head, buf, c);
info->xmit_head = ((info->xmit_head + c) &
(SERIAL_XMIT_SIZE-1));
info->xmit_cnt += c;
spin_unlock_irqrestore(&info->irq_spinlock,flags);
buf += c;
count -= c;
ret += c;
}
}
if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped) {
spin_lock_irqsave(&info->irq_spinlock,flags);
if (!info->tx_active)
usc_start_transmitter(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
cleanup:
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_write(%s) returning=%d\n",
__FILE__,__LINE__,info->device_name,ret);
return ret;
} /* end of mgsl_write() */
/* mgsl_write_room()
*
* Return the count of free bytes in transmit buffer
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static int mgsl_write_room(struct tty_struct *tty)
{
struct mgsl_struct *info = tty->driver_data;
int ret;
if (mgsl_paranoia_check(info, tty->name, "mgsl_write_room"))
return 0;
ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
if (ret < 0)
ret = 0;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_write_room(%s)=%d\n",
__FILE__,__LINE__, info->device_name,ret );
if ( info->params.mode == MGSL_MODE_HDLC ||
info->params.mode == MGSL_MODE_RAW ) {
/* operating in synchronous (frame oriented) mode */
if ( info->tx_active )
return 0;
else
return HDLC_MAX_FRAME_SIZE;
}
return ret;
} /* end of mgsl_write_room() */
/* mgsl_chars_in_buffer()
*
* Return the count of bytes in transmit buffer
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static int mgsl_chars_in_buffer(struct tty_struct *tty)
{
struct mgsl_struct *info = tty->driver_data;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_chars_in_buffer(%s)\n",
__FILE__,__LINE__, info->device_name );
if (mgsl_paranoia_check(info, tty->name, "mgsl_chars_in_buffer"))
return 0;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_chars_in_buffer(%s)=%d\n",
__FILE__,__LINE__, info->device_name,info->xmit_cnt );
if ( info->params.mode == MGSL_MODE_HDLC ||
info->params.mode == MGSL_MODE_RAW ) {
/* operating in synchronous (frame oriented) mode */
if ( info->tx_active )
return info->max_frame_size;
else
return 0;
}
return info->xmit_cnt;
} /* end of mgsl_chars_in_buffer() */
/* mgsl_flush_buffer()
*
* Discard all data in the send buffer
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static void mgsl_flush_buffer(struct tty_struct *tty)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_flush_buffer(%s) entry\n",
__FILE__,__LINE__, info->device_name );
if (mgsl_paranoia_check(info, tty->name, "mgsl_flush_buffer"))
return;
spin_lock_irqsave(&info->irq_spinlock,flags);
info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
del_timer(&info->tx_timer);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
tty_wakeup(tty);
}
/* mgsl_send_xchar()
*
* Send a high-priority XON/XOFF character
*
* Arguments: tty pointer to tty info structure
* ch character to send
* Return Value: None
*/
static void mgsl_send_xchar(struct tty_struct *tty, char ch)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_send_xchar(%s,%d)\n",
__FILE__,__LINE__, info->device_name, ch );
if (mgsl_paranoia_check(info, tty->name, "mgsl_send_xchar"))
return;
info->x_char = ch;
if (ch) {
/* Make sure transmit interrupts are on */
spin_lock_irqsave(&info->irq_spinlock,flags);
if (!info->tx_enabled)
usc_start_transmitter(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
} /* end of mgsl_send_xchar() */
/* mgsl_throttle()
*
* Signal remote device to throttle send data (our receive data)
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static void mgsl_throttle(struct tty_struct * tty)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_throttle(%s) entry\n",
__FILE__,__LINE__, info->device_name );
if (mgsl_paranoia_check(info, tty->name, "mgsl_throttle"))
return;
if (I_IXOFF(tty))
mgsl_send_xchar(tty, STOP_CHAR(tty));
if (tty->termios->c_cflag & CRTSCTS) {
spin_lock_irqsave(&info->irq_spinlock,flags);
info->serial_signals &= ~SerialSignal_RTS;
usc_set_serial_signals(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
} /* end of mgsl_throttle() */
/* mgsl_unthrottle()
*
* Signal remote device to stop throttling send data (our receive data)
*
* Arguments: tty pointer to tty info structure
* Return Value: None
*/
static void mgsl_unthrottle(struct tty_struct * tty)
{
struct mgsl_struct *info = tty->driver_data;
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_unthrottle(%s) entry\n",
__FILE__,__LINE__, info->device_name );
if (mgsl_paranoia_check(info, tty->name, "mgsl_unthrottle"))
return;
if (I_IXOFF(tty)) {
if (info->x_char)
info->x_char = 0;
else
mgsl_send_xchar(tty, START_CHAR(tty));
}
if (tty->termios->c_cflag & CRTSCTS) {
spin_lock_irqsave(&info->irq_spinlock,flags);
info->serial_signals |= SerialSignal_RTS;
usc_set_serial_signals(info);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
} /* end of mgsl_unthrottle() */
/* mgsl_get_stats()
*
* get the current serial parameters information
*
* Arguments: info pointer to device instance data
* user_icount pointer to buffer to hold returned stats
*
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_get_stats(struct mgsl_struct * info, struct mgsl_icount __user *user_icount)
{
int err;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_get_params(%s)\n",
__FILE__,__LINE__, info->device_name);
if (!user_icount) {
memset(&info->icount, 0, sizeof(info->icount));
} else {
COPY_TO_USER(err, user_icount, &info->icount, sizeof(struct mgsl_icount));
if (err)
return -EFAULT;
}
return 0;
} /* end of mgsl_get_stats() */
/* mgsl_get_params()
*
* get the current serial parameters information
*
* Arguments: info pointer to device instance data
* user_params pointer to buffer to hold returned params
*
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_get_params(struct mgsl_struct * info, MGSL_PARAMS __user *user_params)
{
int err;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_get_params(%s)\n",
__FILE__,__LINE__, info->device_name);
COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
if (err) {
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_get_params(%s) user buffer copy failed\n",
__FILE__,__LINE__,info->device_name);
return -EFAULT;
}
return 0;
} /* end of mgsl_get_params() */
/* mgsl_set_params()
*
* set the serial parameters
*
* Arguments:
*
* info pointer to device instance data
* new_params user buffer containing new serial params
*
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_set_params(struct mgsl_struct * info, MGSL_PARAMS __user *new_params)
{
unsigned long flags;
MGSL_PARAMS tmp_params;
int err;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_set_params %s\n", __FILE__,__LINE__,
info->device_name );
COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
if (err) {
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_set_params(%s) user buffer copy failed\n",
__FILE__,__LINE__,info->device_name);
return -EFAULT;
}
spin_lock_irqsave(&info->irq_spinlock,flags);
memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
spin_unlock_irqrestore(&info->irq_spinlock,flags);
mgsl_change_params(info);
return 0;
} /* end of mgsl_set_params() */
/* mgsl_get_txidle()
*
* get the current transmit idle mode
*
* Arguments: info pointer to device instance data
* idle_mode pointer to buffer to hold returned idle mode
*
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_get_txidle(struct mgsl_struct * info, int __user *idle_mode)
{
int err;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_get_txidle(%s)=%d\n",
__FILE__,__LINE__, info->device_name, info->idle_mode);
COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
if (err) {
if ( debug_level >= DEBUG_LEVEL_INFO )
printk( "%s(%d):mgsl_get_txidle(%s) user buffer copy failed\n",
__FILE__,__LINE__,info->device_name);
return -EFAULT;
}
return 0;
} /* end of mgsl_get_txidle() */
/* mgsl_set_txidle() service ioctl to set transmit idle mode
*
* Arguments: info pointer to device instance data
* idle_mode new idle mode
*
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_set_txidle(struct mgsl_struct * info, int idle_mode)
{
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_set_txidle(%s,%d)\n", __FILE__,__LINE__,
info->device_name, idle_mode );
spin_lock_irqsave(&info->irq_spinlock,flags);
info->idle_mode = idle_mode;
usc_set_txidle( info );
spin_unlock_irqrestore(&info->irq_spinlock,flags);
return 0;
} /* end of mgsl_set_txidle() */
/* mgsl_txenable()
*
* enable or disable the transmitter
*
* Arguments:
*
* info pointer to device instance data
* enable 1 = enable, 0 = disable
*
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_txenable(struct mgsl_struct * info, int enable)
{
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_txenable(%s,%d)\n", __FILE__,__LINE__,
info->device_name, enable);
spin_lock_irqsave(&info->irq_spinlock,flags);
if ( enable ) {
if ( !info->tx_enabled ) {
usc_start_transmitter(info);
/*--------------------------------------------------
* if HDLC/SDLC Loop mode, attempt to insert the
* station in the 'loop' by setting CMR:13. Upon
* receipt of the next GoAhead (RxAbort) sequence,
* the OnLoop indicator (CCSR:7) should go active
* to indicate that we are on the loop
*--------------------------------------------------*/
if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
usc_loopmode_insert_request( info );
}
} else {
if ( info->tx_enabled )
usc_stop_transmitter(info);
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
return 0;
} /* end of mgsl_txenable() */
/* mgsl_txabort() abort send HDLC frame
*
* Arguments: info pointer to device instance data
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_txabort(struct mgsl_struct * info)
{
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_txabort(%s)\n", __FILE__,__LINE__,
info->device_name);
spin_lock_irqsave(&info->irq_spinlock,flags);
if ( info->tx_active && info->params.mode == MGSL_MODE_HDLC )
{
if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
usc_loopmode_cancel_transmit( info );
else
usc_TCmd(info,TCmd_SendAbort);
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
return 0;
} /* end of mgsl_txabort() */
/* mgsl_rxenable() enable or disable the receiver
*
* Arguments: info pointer to device instance data
* enable 1 = enable, 0 = disable
* Return Value: 0 if success, otherwise error code
*/
static int mgsl_rxenable(struct mgsl_struct * info, int enable)
{
unsigned long flags;
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_rxenable(%s,%d)\n", __FILE__,__LINE__,
info->device_name, enable);
spin_lock_irqsave(&info->irq_spinlock,flags);
if ( enable ) {
if ( !info->rx_enabled )
usc_start_receiver(info);
} else {
if ( info->rx_enabled )
usc_stop_receiver(info);
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
return 0;
} /* end of mgsl_rxenable() */
/* mgsl_wait_event() wait for specified event to occur
*
* Arguments: info pointer to device instance data
* mask pointer to bitmask of events to wait for
* Return Value: 0 if successful and bit mask updated with
* of events triggerred,
* otherwise error code
*/
static int mgsl_wait_event(struct mgsl_struct * info, int __user * mask_ptr)
{
unsigned long flags;
int s;
int rc=0;
struct mgsl_icount cprev, cnow;
int events;
int mask;
struct _input_signal_events oldsigs, newsigs;
DECLARE_WAITQUEUE(wait, current);
COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
if (rc) {
return -EFAULT;
}
if (debug_level >= DEBUG_LEVEL_INFO)
printk("%s(%d):mgsl_wait_event(%s,%d)\n", __FILE__,__LINE__,
info->device_name, mask);
spin_lock_irqsave(&info->irq_spinlock,flags);
/* return immediately if state matches requested events */
usc_get_serial_signals(info);
s = info->serial_signals;
events = mask &
( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
((s & SerialSignal_RI) ? MgslEvent_RiActive :MgslEvent_RiInactive) );
if (events) {
spin_unlock_irqrestore(&info->irq_spinlock,flags);
goto exit;
}
/* save current irq counts */
cprev = info->icount;
oldsigs = info->input_signal_events;
/* enable hunt and idle irqs if needed */
if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
u16 oldreg = usc_InReg(info,RICR);
u16 newreg = oldreg +
(mask & MgslEvent_ExitHuntMode ? RXSTATUS_EXITED_HUNT:0) +
(mask & MgslEvent_IdleReceived ? RXSTATUS_IDLE_RECEIVED:0);
if (oldreg != newreg)
usc_OutReg(info, RICR, newreg);
}
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&info->event_wait_q, &wait);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
for(;;) {
schedule();
if (signal_pending(current)) {
rc = -ERESTARTSYS;
break;
}
/* get current irq counts */
spin_lock_irqsave(&info->irq_spinlock,flags);
cnow = info->icount;
newsigs = info->input_signal_events;
set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irqrestore(&info->irq_spinlock,flags);
/* if no change, wait aborted for some reason */
if (newsigs.dsr_up == oldsigs.dsr_up &&
newsigs.dsr_down == oldsigs.dsr_down &&
newsigs.dcd_up == oldsigs.dcd_up &&
newsigs.dcd_down == oldsigs.dcd_down &&
newsigs.cts_up == oldsigs.cts_up &&
newsigs.cts_down == oldsigs.cts_down &&
newsigs.ri_up == oldsigs.ri_up &&
newsigs.ri_down == oldsigs.ri_down &&
cnow.exithunt == cprev.exithunt &&
cnow.rxidle == cprev.rxidle) {
rc = -EIO;
break;
}
events = mask &
( (newsigs.dsr_up != oldsigs.dsr_up ? MgslEvent_DsrActive:0) +
(newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
(newsigs.dcd_up != oldsigs.dcd_up ? MgslEvent_DcdActive:0) +
(newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
(newsigs.cts_up != oldsigs.cts_up ? MgslEvent_CtsActive:0) +
(newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
(newsigs.ri_up != oldsigs.ri_up ? MgslEvent_RiActive:0) +
(newsigs.ri_down != oldsigs.ri_down ? MgslEvent_RiInactive:0) +
(cnow.exithunt != cprev.exithunt ? MgslEvent_ExitHuntMode:0) +
(cnow.rxidle != cprev.rxidle ? MgslEvent_IdleReceived:0) );
if (events)
break;
cprev = cnow;
oldsigs = newsigs;
}
remove_wait_queue(&info->event_wait_q, &wait);
set_current_state(TASK_RUNNING);
if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
spin_lock_irqsave(&info->irq_spinlock,flags);
if (!waitqueue_active(&info->event_wait_q)) {
/* disable enable exit hunt mode/idle rcvd IRQs */
usc_OutReg(info, RICR, usc_InReg(info,RICR) &
~(RXSTATUS_EXITED_HUNT + RXSTATUS_IDLE_RECEIVED));
}
spin_unlock_irqrestore(&info->irq_spinlock,flags);
}
exit:
if ( rc == 0 )
PUT_USER(rc, events, mask_ptr);
return rc;
} /* end of mgsl_wait_event() */
static int modem_input_wait(struct mgsl_struct *info,int arg)
{
unsigned long flags;
int rc;
struct mgsl_icount cprev, cnow;
DECLARE_WAITQUEUE(wait, current);
/* save current irq counts */
spin_lock_irqsave(&info->irq_spinlock,flags);
cprev = info->icount;
add_wait_queue(&info