specialix.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 2,265 行 · 第 1/4 页

C
2,265
字号
/* *      specialix.c  -- specialix IO8+ multiport serial driver. * *      Copyright (C) 1997  Roger Wolff (R.E.Wolff@BitWizard.nl) *      Copyright (C) 1994-1996  Dmitry Gorodchanin (pgmdsg@ibi.com) * *      Specialix pays for the development and support of this driver. *      Please DO contact io8-linux@specialix.co.uk if you require *      support. But please read the documentation (specialix.txt) *      first. * *      This driver was developped in the BitWizard linux device *      driver service. If you require a linux device driver for your *      product, please contact devices@BitWizard.nl for a quote. * *      This code is firmly based on the riscom/8 serial driver, *      written by Dmitry Gorodchanin. The specialix IO8+ card *      programming information was obtained from the CL-CD1865 Data *      Book, and Specialix document number 6200059: IO8+ Hardware *      Functional Specification. * *      This program is free software; you can redistribute it and/or *      modify it under the terms of the GNU General Public License as *      published by the Free Software Foundation; either version 2 of *      the License, or (at your option) any later version. * *      This program is distributed in the hope that it will be *      useful, but WITHOUT ANY WARRANTY; without even the implied *      warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *      PURPOSE.  See the GNU General Public License for more details. * *      You should have received a copy of the GNU General Public *      License along with this program; if not, write to the Free *      Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, *      USA. * * Revision history: * * Revision 1.0:  April 1st 1997. *                Initial release for alpha testing. * Revision 1.1:  April 14th 1997.  *                Incorporated Richard Hudsons suggestions,  *                removed some debugging printk's. * Revision 1.2:  April 15th 1997. *                Ported to 2.1.x kernels. * Revision 1.3:  April 17th 1997  *                Backported to 2.0. (Compatibility macros).  * Revision 1.4:  April 18th 1997 *                Fixed DTR/RTS bug that caused the card to indicate  *                "don't send data" to a modem after the password prompt.   *                Fixed bug for premature (fake) interrupts. * Revision 1.5:  April 19th 1997 *                fixed a minor typo in the header file, cleanup a little.  *                performance warnings are now MAXed at once per minute. * Revision 1.6:  May 23 1997 *                Changed the specialix=... format to include interrupt. * Revision 1.7:  May 27 1997 *                Made many more debug printk's a compile time option. * Revision 1.8:  Jul 1  1997 *                port to linux-2.1.43 kernel. * Revision 1.9:  Oct 9  1998 *                Added stuff for the IO8+/PCI version. * Revision 1.10: Oct 22  1999 / Jan 21 2000.  *                Added stuff for setserial.  *                Nicolas Mailhot (Nicolas.Mailhot@email.enst.fr) *  */#define VERSION "1.10"/* * There is a bunch of documentation about the card, jumpers, config * settings, restrictions, cables, device names and numbers in * Documentation/specialix.txt */#include <linux/config.h>#include <linux/module.h>#include <asm/io.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/ioport.h>#include <linux/interrupt.h>#include <linux/errno.h>#include <linux/tty.h>#include <linux/mm.h>#include <linux/serial.h>#include <linux/fcntl.h>#include <linux/major.h>#include <linux/delay.h>#include <linux/version.h>#include <linux/pci.h>#include <linux/init.h>#include <asm/uaccess.h>#include "specialix_io8.h"#include "cd1865.h"/* Configurable options: *//* Am I paranoid or not ? ;-) */#define SPECIALIX_PARANOIA_CHECK/* Do I trust the IRQ from the card? (enabeling it doesn't seem to help)   When the IRQ routine leaves the chip in a state that is keeps on   requiring attention, the timer doesn't help either. */#undef SPECIALIX_TIMER/*  * The following defines are mostly for testing purposes. But if you need * some nice reporting in your syslog, you can define them also. */#undef SX_REPORT_FIFO#undef SX_REPORT_OVERRUN#ifdef CONFIG_SPECIALIX_RTSCTS#define SX_CRTSCTS(bla) 1#else#define SX_CRTSCTS(tty) C_CRTSCTS(tty)#endif/* Used to be outb (0xff, 0x80); */#define short_pause() udelay (1)#define SPECIALIX_LEGAL_FLAGS \	(ASYNC_HUP_NOTIFY   | ASYNC_SAK          | ASYNC_SPLIT_TERMIOS   | \	 ASYNC_SPD_HI       | ASYNC_SPEED_VHI    | ASYNC_SESSION_LOCKOUT | \	 ASYNC_PGRP_LOCKOUT | ASYNC_CALLOUT_NOHUP)#undef RS_EVENT_WRITE_WAKEUP#define RS_EVENT_WRITE_WAKEUP	0static struct tty_driver *specialix_driver;static unsigned char * tmp_buf;static DECLARE_MUTEX(tmp_buf_sem);static unsigned long baud_table[] =  {	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,	9600, 19200, 38400, 57600, 115200, 0, };static struct specialix_board sx_board[SX_NBOARD] =  {	{ 0, SX_IOBASE1,  9, },	{ 0, SX_IOBASE2, 11, },	{ 0, SX_IOBASE3, 12, },	{ 0, SX_IOBASE4, 15, },};static struct specialix_port sx_port[SX_NBOARD * SX_NPORT];#ifdef SPECIALIX_TIMERstatic struct timer_list missed_irq_timer;static irqreturn_t sx_interrupt(int irq, void * dev_id, struct pt_regs * regs);#endifstatic inline int sx_paranoia_check(struct specialix_port const * port,				    char *name, const char *routine){#ifdef SPECIALIX_PARANOIA_CHECK	static const char *badmagic =		KERN_ERR "sx: Warning: bad specialix port magic number for device %s in %s\n";	static const char *badinfo =		KERN_ERR "sx: Warning: null specialix port for device %s in %s\n"; 	if (!port) {		printk(badinfo, name, routine);		return 1;	}	if (port->magic != SPECIALIX_MAGIC) {		printk(badmagic, name, routine);		return 1;	}#endif	return 0;}/* *  *  Service functions for specialix IO8+ driver. *  *//* Get board number from pointer */static inline int board_No (struct specialix_board * bp){	return bp - sx_board;}/* Get port number from pointer */static inline int port_No (struct specialix_port const * port){	return SX_PORT(port - sx_port); }/* Get pointer to board from pointer to port */static inline struct specialix_board * port_Board(struct specialix_port const * port){	return &sx_board[SX_BOARD(port - sx_port)];}/* Input Byte from CL CD186x register */static inline unsigned char sx_in(struct specialix_board  * bp, unsigned short reg){	bp->reg = reg | 0x80;	outb (reg | 0x80, bp->base + SX_ADDR_REG);	return inb  (bp->base + SX_DATA_REG);}/* Output Byte to CL CD186x register */static inline void sx_out(struct specialix_board  * bp, unsigned short reg,			  unsigned char val){	bp->reg = reg | 0x80;	outb (reg | 0x80, bp->base + SX_ADDR_REG);	outb (val, bp->base + SX_DATA_REG);}/* Input Byte from CL CD186x register */static inline unsigned char sx_in_off(struct specialix_board  * bp, unsigned short reg){	bp->reg = reg;	outb (reg, bp->base + SX_ADDR_REG);	return inb  (bp->base + SX_DATA_REG);}/* Output Byte to CL CD186x register */static inline void sx_out_off(struct specialix_board  * bp, unsigned short reg,			  unsigned char val){	bp->reg = reg;	outb (reg, bp->base + SX_ADDR_REG);	outb (val, bp->base + SX_DATA_REG);}/* Wait for Channel Command Register ready */static inline void sx_wait_CCR(struct specialix_board  * bp){	unsigned long delay;	for (delay = SX_CCR_TIMEOUT; delay; delay--) 		if (!sx_in(bp, CD186x_CCR))			return;		printk(KERN_ERR "sx%d: Timeout waiting for CCR.\n", board_No(bp));}/* Wait for Channel Command Register ready */static inline void sx_wait_CCR_off(struct specialix_board  * bp){	unsigned long delay;	for (delay = SX_CCR_TIMEOUT; delay; delay--) 		if (!sx_in_off(bp, CD186x_CCR))			return;		printk(KERN_ERR "sx%d: Timeout waiting for CCR.\n", board_No(bp));}/* *  specialix IO8+ IO range functions. */static inline int sx_check_io_range(struct specialix_board * bp){	return check_region (bp->base, SX_IO_SPACE);}static inline void sx_request_io_range(struct specialix_board * bp){	request_region(bp->base, 	               bp->flags&SX_BOARD_IS_PCI?SX_PCI_IO_SPACE:SX_IO_SPACE,	               "specialix IO8+" );}static inline void sx_release_io_range(struct specialix_board * bp){	release_region(bp->base, 	               bp->flags&SX_BOARD_IS_PCI?SX_PCI_IO_SPACE:SX_IO_SPACE);}	/* Must be called with enabled interrupts *//* Ugly. Very ugly. Don't use this for anything else than initialization    code */static inline void sx_long_delay(unsigned long delay){	unsigned long i;		for (i = jiffies + delay; time_after(i, jiffies); ) ;}/* Set the IRQ using the RTS lines that run to the PAL on the board.... */int sx_set_irq ( struct specialix_board *bp){	int virq;	int i;	if (bp->flags & SX_BOARD_IS_PCI) 		return 1;	switch (bp->irq) {	/* In the same order as in the docs... */	case 15: virq = 0;break;	case 12: virq = 1;break;	case 11: virq = 2;break;	case 9:  virq = 3;break;	default: printk (KERN_ERR "Speclialix: cannot set irq to %d.\n", bp->irq);	         return 0;	}	for (i=0;i<2;i++) {		sx_out(bp, CD186x_CAR, i);		sx_out(bp, CD186x_MSVRTS, ((virq >> i) & 0x1)? MSVR_RTS:0);	}	return 1;}/* Reset and setup CD186x chip */static int sx_init_CD186x(struct specialix_board  * bp){	unsigned long flags;	int scaler;	int rv = 1;		save_flags(flags); cli();	sx_wait_CCR_off(bp);			   /* Wait for CCR ready        */	sx_out_off(bp, CD186x_CCR, CCR_HARDRESET);      /* Reset CD186x chip          */	sti();	sx_long_delay(HZ/20);                      /* Delay 0.05 sec            */	cli();	sx_out_off(bp, CD186x_GIVR, SX_ID);             /* Set ID for this chip      */	sx_out_off(bp, CD186x_GICR, 0);                 /* Clear all bits            */	sx_out_off(bp, CD186x_PILR1, SX_ACK_MINT);      /* Prio for modem intr       */	sx_out_off(bp, CD186x_PILR2, SX_ACK_TINT);      /* Prio for transmitter intr */	sx_out_off(bp, CD186x_PILR3, SX_ACK_RINT);      /* Prio for receiver intr    */	/* Set RegAckEn */	sx_out_off(bp, CD186x_SRCR, sx_in (bp, CD186x_SRCR) | SRCR_REGACKEN);		/* Setting up prescaler. We need 4 ticks per 1 ms */	scaler =  SX_OSCFREQ/SPECIALIX_TPS;	sx_out_off(bp, CD186x_PPRH, scaler >> 8);	sx_out_off(bp, CD186x_PPRL, scaler & 0xff);	if (!sx_set_irq (bp)) {		/* Figure out how to pass this along... */		printk (KERN_ERR "Cannot set irq to %d.\n", bp->irq);		rv = 0;	}	restore_flags(flags);	return rv;}int read_cross_byte (struct specialix_board *bp, int reg, int bit){	int i;	int t;	for (i=0, t=0;i<8;i++) {		sx_out_off (bp, CD186x_CAR, i);		if (sx_in_off (bp, reg) & bit) 			t |= 1 << i;	}	return t;}#ifdef SPECIALIX_TIMERvoid missed_irq (unsigned long data){	if (sx_in ((struct specialix_board *)data, CD186x_SRSR) &  	                                            (SRSR_RREQint |	                                             SRSR_TREQint |	                                             SRSR_MREQint)) {		printk (KERN_INFO "Missed interrupt... Calling int from timer. \n");		sx_interrupt (((struct specialix_board *)data)->irq, 		              NULL, NULL);	}	missed_irq_timer.expires = jiffies + HZ;	add_timer (&missed_irq_timer);}#endif/* Main probing routine, also sets irq. */static int sx_probe(struct specialix_board *bp){	unsigned char val1, val2;#if 0	int irqs = 0;	int retries;#endif	int rev;	int chip;	if (sx_check_io_range(bp)) 		return 1;	/* Are the I/O ports here ? */	sx_out_off(bp, CD186x_PPRL, 0x5a);	short_pause ();	val1 = sx_in_off(bp, CD186x_PPRL);	sx_out_off(bp, CD186x_PPRL, 0xa5);	short_pause ();	val2 = sx_in_off(bp, CD186x_PPRL);		if ((val1 != 0x5a) || (val2 != 0xa5)) {		printk(KERN_INFO "sx%d: specialix IO8+ Board at 0x%03x not found.\n",		       board_No(bp), bp->base);		return 1;	}	/* Check the DSR lines that Specialix uses as board 	   identification */	val1 = read_cross_byte (bp, CD186x_MSVR, MSVR_DSR);	val2 = read_cross_byte (bp, CD186x_MSVR, MSVR_RTS);#ifdef SPECIALIX_DEBUG	printk (KERN_DEBUG "sx%d: DSR lines are: %02x, rts lines are: %02x\n", 	        board_No(bp),  val1, val2);#endif	/* They managed to switch the bit order between the docs and	   the IO8+ card. The new PCI card now conforms to old docs.	   They changed the PCI docs to reflect the situation on the	   old card. */	val2 = (bp->flags & SX_BOARD_IS_PCI)?0x4d : 0xb2;	if (val1 != val2) {		printk(KERN_INFO "sx%d: specialix IO8+ ID %02x at 0x%03x not found (%02x).\n",		       board_No(bp), val2, bp->base, val1);		return 1;	}#if 0	/* It's time to find IRQ for this board */	for (retries = 0; retries < 5 && irqs <= 0; retries++) {		irqs = probe_irq_on();		sx_init_CD186x(bp);	       		/* Reset CD186x chip       */		sx_out(bp, CD186x_CAR, 2);               /* Select port 2          */		sx_wait_CCR(bp);		sx_out(bp, CD186x_CCR, CCR_TXEN);        /* Enable transmitter     */		sx_out(bp, CD186x_IER, IER_TXRDY);       /* Enable tx empty intr   */		sx_long_delay(HZ/20);	       				irqs = probe_irq_off(irqs);#if SPECIALIX_DEBUG > 2		printk (KERN_DEBUG "SRSR = %02x, ",  sx_in(bp, CD186x_SRSR));		printk (           "TRAR = %02x, ",  sx_in(bp, CD186x_TRAR));		printk (           "GIVR = %02x, ",  sx_in(bp, CD186x_GIVR));		printk (           "GICR = %02x, ",  sx_in(bp, CD186x_GICR));		printk (           "\n");#endif		/* Reset CD186x again      */		if (!sx_init_CD186x(bp)) {			/* Hmmm. This is dead code anyway. */		}#if SPECIALIX_DEBUG > 2		printk (KERN_DEBUG "val1 = %02x, val2 = %02x, val3 = %02x.\n", 		        val1, val2, val3); #endif		}	#if 0	if (irqs <= 0) {		printk(KERN_ERR "sx%d: Can't find IRQ for specialix IO8+ board at 0x%03x.\n",		       board_No(bp), bp->base);		return 1;	}#endif	printk (KERN_INFO "Started with irq=%d, but now have irq=%d.\n", bp->irq, irqs);	if (irqs > 0)		bp->irq = irqs;#endif	/* Reset CD186x again  */	if (!sx_init_CD186x(bp)) {		return -EIO;	}	sx_request_io_range(bp);	bp->flags |= SX_BOARD_PRESENT;		/* Chip           revcode   pkgtype	                  GFRCR     SRCR bit 7	   CD180 rev B    0x81      0	   CD180 rev C    0x82      0	   CD1864 rev A   0x82      1	   CD1865 rev A   0x83      1  -- Do not use!!! Does not work. 	   CD1865 rev B   0x84      1	 -- Thanks to Gwen Wang, Cirrus Logic.	 */	switch (sx_in_off(bp, CD186x_GFRCR)) {	case 0x82:chip = 1864;rev='A';break;	case 0x83:chip = 1865;rev='A';break;	case 0x84:chip = 1865;rev='B';break;	case 0x85:chip = 1865;rev='C';break; /* Does not exist at this time */	default:chip=-1;rev='x';	}#if SPECIALIX_DEBUG > 2	printk (KERN_DEBUG " GFCR = 0x%02x\n", sx_in_off(bp, CD186x_GFRCR) );#endif#ifdef SPECIALIX_TIMER	init_timer (&missed_irq_timer);	missed_irq_timer.function = missed_irq;	missed_irq_timer.data = (unsigned long) bp;	missed_irq_timer.expires = jiffies + HZ;	add_timer (&missed_irq_timer);#endif	printk(KERN_INFO"sx%d: specialix IO8+ board detected at 0x%03x, IRQ %d, CD%d Rev. %c.\n",	       board_No(bp),	       bp->base, bp->irq,	       chip, rev);	return 0;}/*  *  *  Interrupt processing routines. * */static inline void sx_mark_event(struct specialix_port * port, int event){	set_bit(event, &port->event);	schedule_work(&port->tqueue);}static inline struct specialix_port * sx_get_port(struct specialix_board * bp,					       unsigned char const * what){	unsigned char channel;	struct specialix_port * port;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?