diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/char/stallion.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/char/stallion.c')
-rw-r--r-- | drivers/char/stallion.c | 5197 |
1 files changed, 5197 insertions, 0 deletions
diff --git a/drivers/char/stallion.c b/drivers/char/stallion.c new file mode 100644 index 00000000000..de166608c59 --- /dev/null +++ b/drivers/char/stallion.c @@ -0,0 +1,5197 @@ +/*****************************************************************************/ + +/* + * stallion.c -- stallion multiport serial driver. + * + * Copyright (C) 1996-1999 Stallion Technologies + * Copyright (C) 1994-1996 Greg Ungerer. + * + * This code is loosely based on the Linux serial driver, written by + * Linus Torvalds, Theodore T'so and others. + * + * 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. + */ + +/*****************************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/cd1400.h> +#include <linux/sc26198.h> +#include <linux/comstats.h> +#include <linux/stallion.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/device.h> +#include <linux/delay.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#ifdef CONFIG_PCI +#include <linux/pci.h> +#endif + +/*****************************************************************************/ + +/* + * Define different board types. Use the standard Stallion "assigned" + * board numbers. Boards supported in this driver are abbreviated as + * EIO = EasyIO and ECH = EasyConnection 8/32. + */ +#define BRD_EASYIO 20 +#define BRD_ECH 21 +#define BRD_ECHMC 22 +#define BRD_ECHPCI 26 +#define BRD_ECH64PCI 27 +#define BRD_EASYIOPCI 28 + +/* + * Define a configuration structure to hold the board configuration. + * Need to set this up in the code (for now) with the boards that are + * to be configured into the system. This is what needs to be modified + * when adding/removing/modifying boards. Each line entry in the + * stl_brdconf[] array is a board. Each line contains io/irq/memory + * ranges for that board (as well as what type of board it is). + * Some examples: + * { BRD_EASYIO, 0x2a0, 0, 0, 10, 0 }, + * This line would configure an EasyIO board (4 or 8, no difference), + * at io address 2a0 and irq 10. + * Another example: + * { BRD_ECH, 0x2a8, 0x280, 0, 12, 0 }, + * This line will configure an EasyConnection 8/32 board at primary io + * address 2a8, secondary io address 280 and irq 12. + * Enter as many lines into this array as you want (only the first 4 + * will actually be used!). Any combination of EasyIO and EasyConnection + * boards can be specified. EasyConnection 8/32 boards can share their + * secondary io addresses between each other. + * + * NOTE: there is no need to put any entries in this table for PCI + * boards. They will be found automatically by the driver - provided + * PCI BIOS32 support is compiled into the kernel. + */ + +typedef struct { + int brdtype; + int ioaddr1; + int ioaddr2; + unsigned long memaddr; + int irq; + int irqtype; +} stlconf_t; + +static stlconf_t stl_brdconf[] = { + /*{ BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },*/ +}; + +static int stl_nrbrds = sizeof(stl_brdconf) / sizeof(stlconf_t); + +/*****************************************************************************/ + +/* + * Define some important driver characteristics. Device major numbers + * allocated as per Linux Device Registry. + */ +#ifndef STL_SIOMEMMAJOR +#define STL_SIOMEMMAJOR 28 +#endif +#ifndef STL_SERIALMAJOR +#define STL_SERIALMAJOR 24 +#endif +#ifndef STL_CALLOUTMAJOR +#define STL_CALLOUTMAJOR 25 +#endif + +/* + * Set the TX buffer size. Bigger is better, but we don't want + * to chew too much memory with buffers! + */ +#define STL_TXBUFLOW 512 +#define STL_TXBUFSIZE 4096 + +/*****************************************************************************/ + +/* + * Define our local driver identity first. Set up stuff to deal with + * all the local structures required by a serial tty driver. + */ +static char *stl_drvtitle = "Stallion Multiport Serial Driver"; +static char *stl_drvname = "stallion"; +static char *stl_drvversion = "5.6.0"; + +static struct tty_driver *stl_serial; + +/* + * We will need to allocate a temporary write buffer for chars that + * come direct from user space. The problem is that a copy from user + * space might cause a page fault (typically on a system that is + * swapping!). All ports will share one buffer - since if the system + * is already swapping a shared buffer won't make things any worse. + */ +static char *stl_tmpwritebuf; +static DECLARE_MUTEX(stl_tmpwritesem); + +/* + * Define a local default termios struct. All ports will be created + * with this termios initially. Basically all it defines is a raw port + * at 9600, 8 data bits, 1 stop bit. + */ +static struct termios stl_deftermios = { + .c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL), + .c_cc = INIT_C_CC, +}; + +/* + * Define global stats structures. Not used often, and can be + * re-used for each stats call. + */ +static comstats_t stl_comstats; +static combrd_t stl_brdstats; +static stlbrd_t stl_dummybrd; +static stlport_t stl_dummyport; + +/* + * Define global place to put buffer overflow characters. + */ +static char stl_unwanted[SC26198_RXFIFOSIZE]; + +/*****************************************************************************/ + +static stlbrd_t *stl_brds[STL_MAXBRDS]; + +/* + * Per board state flags. Used with the state field of the board struct. + * Not really much here! + */ +#define BRD_FOUND 0x1 + +/* + * Define the port structure istate flags. These set of flags are + * modified at interrupt time - so setting and reseting them needs + * to be atomic. Use the bit clear/setting routines for this. + */ +#define ASYI_TXBUSY 1 +#define ASYI_TXLOW 2 +#define ASYI_DCDCHANGE 3 +#define ASYI_TXFLOWED 4 + +/* + * Define an array of board names as printable strings. Handy for + * referencing boards when printing trace and stuff. + */ +static char *stl_brdnames[] = { + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + "EasyIO", + "EC8/32-AT", + "EC8/32-MC", + (char *) NULL, + (char *) NULL, + (char *) NULL, + "EC8/32-PCI", + "EC8/64-PCI", + "EasyIO-PCI", +}; + +/*****************************************************************************/ + +/* + * Define some string labels for arguments passed from the module + * load line. These allow for easy board definitions, and easy + * modification of the io, memory and irq resoucres. + */ +static int stl_nargs = 0; +static char *board0[4]; +static char *board1[4]; +static char *board2[4]; +static char *board3[4]; + +static char **stl_brdsp[] = { + (char **) &board0, + (char **) &board1, + (char **) &board2, + (char **) &board3 +}; + +/* + * Define a set of common board names, and types. This is used to + * parse any module arguments. + */ + +typedef struct stlbrdtype { + char *name; + int type; +} stlbrdtype_t; + +static stlbrdtype_t stl_brdstr[] = { + { "easyio", BRD_EASYIO }, + { "eio", BRD_EASYIO }, + { "20", BRD_EASYIO }, + { "ec8/32", BRD_ECH }, + { "ec8/32-at", BRD_ECH }, + { "ec8/32-isa", BRD_ECH }, + { "ech", BRD_ECH }, + { "echat", BRD_ECH }, + { "21", BRD_ECH }, + { "ec8/32-mc", BRD_ECHMC }, + { "ec8/32-mca", BRD_ECHMC }, + { "echmc", BRD_ECHMC }, + { "echmca", BRD_ECHMC }, + { "22", BRD_ECHMC }, + { "ec8/32-pc", BRD_ECHPCI }, + { "ec8/32-pci", BRD_ECHPCI }, + { "26", BRD_ECHPCI }, + { "ec8/64-pc", BRD_ECH64PCI }, + { "ec8/64-pci", BRD_ECH64PCI }, + { "ech-pci", BRD_ECH64PCI }, + { "echpci", BRD_ECH64PCI }, + { "echpc", BRD_ECH64PCI }, + { "27", BRD_ECH64PCI }, + { "easyio-pc", BRD_EASYIOPCI }, + { "easyio-pci", BRD_EASYIOPCI }, + { "eio-pci", BRD_EASYIOPCI }, + { "eiopci", BRD_EASYIOPCI }, + { "28", BRD_EASYIOPCI }, +}; + +/* + * Define the module agruments. + */ +MODULE_AUTHOR("Greg Ungerer"); +MODULE_DESCRIPTION("Stallion Multiport Serial Driver"); +MODULE_LICENSE("GPL"); + +module_param_array(board0, charp, &stl_nargs, 0); +MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,ioaddr2][,irq]]"); +module_param_array(board1, charp, &stl_nargs, 0); +MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,ioaddr2][,irq]]"); +module_param_array(board2, charp, &stl_nargs, 0); +MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,ioaddr2][,irq]]"); +module_param_array(board3, charp, &stl_nargs, 0); +MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,ioaddr2][,irq]]"); + +/*****************************************************************************/ + +/* + * Hardware ID bits for the EasyIO and ECH boards. These defines apply + * to the directly accessible io ports of these boards (not the uarts - + * they are in cd1400.h and sc26198.h). + */ +#define EIO_8PORTRS 0x04 +#define EIO_4PORTRS 0x05 +#define EIO_8PORTDI 0x00 +#define EIO_8PORTM 0x06 +#define EIO_MK3 0x03 +#define EIO_IDBITMASK 0x07 + +#define EIO_BRDMASK 0xf0 +#define ID_BRD4 0x10 +#define ID_BRD8 0x20 +#define ID_BRD16 0x30 + +#define EIO_INTRPEND 0x08 +#define EIO_INTEDGE 0x00 +#define EIO_INTLEVEL 0x08 +#define EIO_0WS 0x10 + +#define ECH_ID 0xa0 +#define ECH_IDBITMASK 0xe0 +#define ECH_BRDENABLE 0x08 +#define ECH_BRDDISABLE 0x00 +#define ECH_INTENABLE 0x01 +#define ECH_INTDISABLE 0x00 +#define ECH_INTLEVEL 0x02 +#define ECH_INTEDGE 0x00 +#define ECH_INTRPEND 0x01 +#define ECH_BRDRESET 0x01 + +#define ECHMC_INTENABLE 0x01 +#define ECHMC_BRDRESET 0x02 + +#define ECH_PNLSTATUS 2 +#define ECH_PNL16PORT 0x20 +#define ECH_PNLIDMASK 0x07 +#define ECH_PNLXPID 0x40 +#define ECH_PNLINTRPEND 0x80 + +#define ECH_ADDR2MASK 0x1e0 + +/* + * Define the vector mapping bits for the programmable interrupt board + * hardware. These bits encode the interrupt for the board to use - it + * is software selectable (except the EIO-8M). + */ +static unsigned char stl_vecmap[] = { + 0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07, + 0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03 +}; + +/* + * Set up enable and disable macros for the ECH boards. They require + * the secondary io address space to be activated and deactivated. + * This way all ECH boards can share their secondary io region. + * If this is an ECH-PCI board then also need to set the page pointer + * to point to the correct page. + */ +#define BRDENABLE(brdnr,pagenr) \ + if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \ + outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE), \ + stl_brds[(brdnr)]->ioctrl); \ + else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI) \ + outb((pagenr), stl_brds[(brdnr)]->ioctrl); + +#define BRDDISABLE(brdnr) \ + if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \ + outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE), \ + stl_brds[(brdnr)]->ioctrl); + +#define STL_CD1400MAXBAUD 230400 +#define STL_SC26198MAXBAUD 460800 + +#define STL_BAUDBASE 115200 +#define STL_CLOSEDELAY (5 * HZ / 10) + +/*****************************************************************************/ + +#ifdef CONFIG_PCI + +/* + * Define the Stallion PCI vendor and device IDs. + */ +#ifndef PCI_VENDOR_ID_STALLION +#define PCI_VENDOR_ID_STALLION 0x124d +#endif +#ifndef PCI_DEVICE_ID_ECHPCI832 +#define PCI_DEVICE_ID_ECHPCI832 0x0000 +#endif +#ifndef PCI_DEVICE_ID_ECHPCI864 +#define PCI_DEVICE_ID_ECHPCI864 0x0002 +#endif +#ifndef PCI_DEVICE_ID_EIOPCI +#define PCI_DEVICE_ID_EIOPCI 0x0003 +#endif + +/* + * Define structure to hold all Stallion PCI boards. + */ +typedef struct stlpcibrd { + unsigned short vendid; + unsigned short devid; + int brdtype; +} stlpcibrd_t; + +static stlpcibrd_t stl_pcibrds[] = { + { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI864, BRD_ECH64PCI }, + { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_EIOPCI, BRD_EASYIOPCI }, + { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI832, BRD_ECHPCI }, + { PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87410, BRD_ECHPCI }, +}; + +static int stl_nrpcibrds = sizeof(stl_pcibrds) / sizeof(stlpcibrd_t); + +#endif + +/*****************************************************************************/ + +/* + * Define macros to extract a brd/port number from a minor number. + */ +#define MINOR2BRD(min) (((min) & 0xc0) >> 6) +#define MINOR2PORT(min) ((min) & 0x3f) + +/* + * Define a baud rate table that converts termios baud rate selector + * into the actual baud rate value. All baud rate calculations are + * based on the actual baud rate required. + */ +static unsigned int stl_baudrates[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 +}; + +/* + * Define some handy local macros... + */ +#undef MIN +#define MIN(a,b) (((a) <= (b)) ? (a) : (b)) + +#undef TOLOWER +#define TOLOWER(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x)) + +/*****************************************************************************/ + +/* + * Declare all those functions in this driver! + */ + +static void stl_argbrds(void); +static int stl_parsebrd(stlconf_t *confp, char **argp); + +static unsigned long stl_atol(char *str); + +int stl_init(void); +static int stl_open(struct tty_struct *tty, struct file *filp); +static void stl_close(struct tty_struct *tty, struct file *filp); +static int stl_write(struct tty_struct *tty, const unsigned char *buf, int count); +static void stl_putchar(struct tty_struct *tty, unsigned char ch); +static void stl_flushchars(struct tty_struct *tty); +static int stl_writeroom(struct tty_struct *tty); +static int stl_charsinbuffer(struct tty_struct *tty); +static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg); +static void stl_settermios(struct tty_struct *tty, struct termios *old); +static void stl_throttle(struct tty_struct *tty); +static void stl_unthrottle(struct tty_struct *tty); +static void stl_stop(struct tty_struct *tty); +static void stl_start(struct tty_struct *tty); +static void stl_flushbuffer(struct tty_struct *tty); +static void stl_breakctl(struct tty_struct *tty, int state); +static void stl_waituntilsent(struct tty_struct *tty, int timeout); +static void stl_sendxchar(struct tty_struct *tty, char ch); +static void stl_hangup(struct tty_struct *tty); +static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg); +static int stl_portinfo(stlport_t *portp, int portnr, char *pos); +static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data); + +static int stl_brdinit(stlbrd_t *brdp); +static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp); +static int stl_getserial(stlport_t *portp, struct serial_struct __user *sp); +static int stl_setserial(stlport_t *portp, struct serial_struct __user *sp); +static int stl_getbrdstats(combrd_t __user *bp); +static int stl_getportstats(stlport_t *portp, comstats_t __user *cp); +static int stl_clrportstats(stlport_t *portp, comstats_t __user *cp); +static int stl_getportstruct(stlport_t __user *arg); +static int stl_getbrdstruct(stlbrd_t __user *arg); +static int stl_waitcarrier(stlport_t *portp, struct file *filp); +static int stl_eiointr(stlbrd_t *brdp); +static int stl_echatintr(stlbrd_t *brdp); +static int stl_echmcaintr(stlbrd_t *brdp); +static int stl_echpciintr(stlbrd_t *brdp); +static int stl_echpci64intr(stlbrd_t *brdp); +static void stl_offintr(void *private); +static void *stl_memalloc(int len); +static stlbrd_t *stl_allocbrd(void); +static stlport_t *stl_getport(int brdnr, int panelnr, int portnr); + +static inline int stl_initbrds(void); +static inline int stl_initeio(stlbrd_t *brdp); +static inline int stl_initech(stlbrd_t *brdp); +static inline int stl_getbrdnr(void); + +#ifdef CONFIG_PCI +static inline int stl_findpcibrds(void); +static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp); +#endif + +/* + * CD1400 uart specific handling functions. + */ +static void stl_cd1400setreg(stlport_t *portp, int regnr, int value); +static int stl_cd1400getreg(stlport_t *portp, int regnr); +static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value); +static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp); +static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp); +static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp); +static int stl_cd1400getsignals(stlport_t *portp); +static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts); +static void stl_cd1400ccrwait(stlport_t *portp); +static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx); +static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx); +static void stl_cd1400disableintrs(stlport_t *portp); +static void stl_cd1400sendbreak(stlport_t *portp, int len); +static void stl_cd1400flowctrl(stlport_t *portp, int state); +static void stl_cd1400sendflow(stlport_t *portp, int state); +static void stl_cd1400flush(stlport_t *portp); +static int stl_cd1400datastate(stlport_t *portp); +static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase); +static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase); +static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr); +static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr); +static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr); + +static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr); + +/* + * SC26198 uart specific handling functions. + */ +static void stl_sc26198setreg(stlport_t *portp, int regnr, int value); +static int stl_sc26198getreg(stlport_t *portp, int regnr); +static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value); +static int stl_sc26198getglobreg(stlport_t *portp, int regnr); +static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp); +static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp); +static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp); +static int stl_sc26198getsignals(stlport_t *portp); +static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts); +static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx); +static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx); +static void stl_sc26198disableintrs(stlport_t *portp); +static void stl_sc26198sendbreak(stlport_t *portp, int len); +static void stl_sc26198flowctrl(stlport_t *portp, int state); +static void stl_sc26198sendflow(stlport_t *portp, int state); +static void stl_sc26198flush(stlport_t *portp); +static int stl_sc26198datastate(stlport_t *portp); +static void stl_sc26198wait(stlport_t *portp); +static void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty); +static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase); +static void stl_sc26198txisr(stlport_t *port); +static void stl_sc26198rxisr(stlport_t *port, unsigned int iack); +static void stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch); +static void stl_sc26198rxbadchars(stlport_t *portp); +static void stl_sc26198otherisr(stlport_t *port, unsigned int iack); + +/*****************************************************************************/ + +/* + * Generic UART support structure. + */ +typedef struct uart { + int (*panelinit)(stlbrd_t *brdp, stlpanel_t *panelp); + void (*portinit)(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp); + void (*setport)(stlport_t *portp, struct termios *tiosp); + int (*getsignals)(stlport_t *portp); + void (*setsignals)(stlport_t *portp, int dtr, int rts); + void (*enablerxtx)(stlport_t *portp, int rx, int tx); + void (*startrxtx)(stlport_t *portp, int rx, int tx); + void (*disableintrs)(stlport_t *portp); + void (*sendbreak)(stlport_t *portp, int len); + void (*flowctrl)(stlport_t *portp, int state); + void (*sendflow)(stlport_t *portp, int state); + void (*flush)(stlport_t *portp); + int (*datastate)(stlport_t *portp); + void (*intr)(stlpanel_t *panelp, unsigned int iobase); +} uart_t; + +/* + * Define some macros to make calling these functions nice and clean. + */ +#define stl_panelinit (* ((uart_t *) panelp->uartp)->panelinit) +#define stl_portinit (* ((uart_t *) portp->uartp)->portinit) +#define stl_setport (* ((uart_t *) portp->uartp)->setport) +#define stl_getsignals (* ((uart_t *) portp->uartp)->getsignals) +#define stl_setsignals (* ((uart_t *) portp->uartp)->setsignals) +#define stl_enablerxtx (* ((uart_t *) portp->uartp)->enablerxtx) +#define stl_startrxtx (* ((uart_t *) portp->uartp)->startrxtx) +#define stl_disableintrs (* ((uart_t *) portp->uartp)->disableintrs) +#define stl_sendbreak (* ((uart_t *) portp->uartp)->sendbreak) +#define stl_flowctrl (* ((uart_t *) portp->uartp)->flowctrl) +#define stl_sendflow (* ((uart_t *) portp->uartp)->sendflow) +#define stl_flush (* ((uart_t *) portp->uartp)->flush) +#define stl_datastate (* ((uart_t *) portp->uartp)->datastate) + +/*****************************************************************************/ + +/* + * CD1400 UART specific data initialization. + */ +static uart_t stl_cd1400uart = { + stl_cd1400panelinit, + stl_cd1400portinit, + stl_cd1400setport, + stl_cd1400getsignals, + stl_cd1400setsignals, + stl_cd1400enablerxtx, + stl_cd1400startrxtx, + stl_cd1400disableintrs, + stl_cd1400sendbreak, + stl_cd1400flowctrl, + stl_cd1400sendflow, + stl_cd1400flush, + stl_cd1400datastate, + stl_cd1400eiointr +}; + +/* + * Define the offsets within the register bank of a cd1400 based panel. + * These io address offsets are common to the EasyIO board as well. + */ +#define EREG_ADDR 0 +#define EREG_DATA 4 +#define EREG_RXACK 5 +#define EREG_TXACK 6 +#define EREG_MDACK 7 + +#define EREG_BANKSIZE 8 + +#define CD1400_CLK 25000000 +#define CD1400_CLK8M 20000000 + +/* + * Define the cd1400 baud rate clocks. These are used when calculating + * what clock and divisor to use for the required baud rate. Also + * define the maximum baud rate allowed, and the default base baud. + */ +static int stl_cd1400clkdivs[] = { + CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4 +}; + +/*****************************************************************************/ + +/* + * SC26198 UART specific data initization. + */ +static uart_t stl_sc26198uart = { + stl_sc26198panelinit, + stl_sc26198portinit, + stl_sc26198setport, + stl_sc26198getsignals, + stl_sc26198setsignals, + stl_sc26198enablerxtx, + stl_sc26198startrxtx, + stl_sc26198disableintrs, + stl_sc26198sendbreak, + stl_sc26198flowctrl, + stl_sc26198sendflow, + stl_sc26198flush, + stl_sc26198datastate, + stl_sc26198intr +}; + +/* + * Define the offsets within the register bank of a sc26198 based panel. + */ +#define XP_DATA 0 +#define XP_ADDR 1 +#define XP_MODID 2 +#define XP_STATUS 2 +#define XP_IACK 3 + +#define XP_BANKSIZE 4 + +/* + * Define the sc26198 baud rate table. Offsets within the table + * represent the actual baud rate selector of sc26198 registers. + */ +static unsigned int sc26198_baudtable[] = { + 50, 75, 150, 200, 300, 450, 600, 900, 1200, 1800, 2400, 3600, + 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200, + 230400, 460800, 921600 +}; + +#define SC26198_NRBAUDS (sizeof(sc26198_baudtable) / sizeof(unsigned int)) + +/*****************************************************************************/ + +/* + * Define the driver info for a user level control device. Used mainly + * to get at port stats - only not using the port device itself. + */ +static struct file_operations stl_fsiomem = { + .owner = THIS_MODULE, + .ioctl = stl_memioctl, +}; + +/*****************************************************************************/ + +static struct class_simple *stallion_class; + +/* + * Loadable module initialization stuff. + */ + +static int __init stallion_module_init(void) +{ + unsigned long flags; + +#ifdef DEBUG + printk("init_module()\n"); +#endif + + save_flags(flags); + cli(); + stl_init(); + restore_flags(flags); + + return(0); +} + +/*****************************************************************************/ + +static void __exit stallion_module_exit(void) +{ + stlbrd_t *brdp; + stlpanel_t *panelp; + stlport_t *portp; + unsigned long flags; + int i, j, k; + +#ifdef DEBUG + printk("cleanup_module()\n"); +#endif + + printk(KERN_INFO "Unloading %s: version %s\n", stl_drvtitle, + stl_drvversion); + + save_flags(flags); + cli(); + +/* + * Free up all allocated resources used by the ports. This includes + * memory and interrupts. As part of this process we will also do + * a hangup on every open port - to try to flush out any processes + * hanging onto ports. + */ + i = tty_unregister_driver(stl_serial); + put_tty_driver(stl_serial); + if (i) { + printk("STALLION: failed to un-register tty driver, " + "errno=%d\n", -i); + restore_flags(flags); + return; + } + for (i = 0; i < 4; i++) { + devfs_remove("staliomem/%d", i); + class_simple_device_remove(MKDEV(STL_SIOMEMMAJOR, i)); + } + devfs_remove("staliomem"); + if ((i = unregister_chrdev(STL_SIOMEMMAJOR, "staliomem"))) + printk("STALLION: failed to un-register serial memory device, " + "errno=%d\n", -i); + class_simple_destroy(stallion_class); + + if (stl_tmpwritebuf != (char *) NULL) + kfree(stl_tmpwritebuf); + + for (i = 0; (i < stl_nrbrds); i++) { + if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL) + continue; + + free_irq(brdp->irq, brdp); + + for (j = 0; (j < STL_MAXPANELS); j++) { + panelp = brdp->panels[j]; + if (panelp == (stlpanel_t *) NULL) + continue; + for (k = 0; (k < STL_PORTSPERPANEL); k++) { + portp = panelp->ports[k]; + if (portp == (stlport_t *) NULL) + continue; + if (portp->tty != (struct tty_struct *) NULL) + stl_hangup(portp->tty); + if (portp->tx.buf != (char *) NULL) + kfree(portp->tx.buf); + kfree(portp); + } + kfree(panelp); + } + + release_region(brdp->ioaddr1, brdp->iosize1); + if (brdp->iosize2 > 0) + release_region(brdp->ioaddr2, brdp->iosize2); + + kfree(brdp); + stl_brds[i] = (stlbrd_t *) NULL; + } + + restore_flags(flags); +} + +module_init(stallion_module_init); +module_exit(stallion_module_exit); + +/*****************************************************************************/ + +/* + * Check for any arguments passed in on the module load command line. + */ + +static void stl_argbrds(void) +{ + stlconf_t conf; + stlbrd_t *brdp; + int i; + +#ifdef DEBUG + printk("stl_argbrds()\n"); +#endif + + for (i = stl_nrbrds; (i < stl_nargs); i++) { + memset(&conf, 0, sizeof(conf)); + if (stl_parsebrd(&conf, stl_brdsp[i]) == 0) + continue; + if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL) + continue; + stl_nrbrds = i + 1; + brdp->brdnr = i; + brdp->brdtype = conf.brdtype; + brdp->ioaddr1 = conf.ioaddr1; + brdp->ioaddr2 = conf.ioaddr2; + brdp->irq = conf.irq; + brdp->irqtype = conf.irqtype; + stl_brdinit(brdp); + } +} + +/*****************************************************************************/ + +/* + * Convert an ascii string number into an unsigned long. + */ + +static unsigned long stl_atol(char *str) +{ + unsigned long val; + int base, c; + char *sp; + + val = 0; + sp = str; + if ((*sp == '0') && (*(sp+1) == 'x')) { + base = 16; + sp += 2; + } else if (*sp == '0') { + base = 8; + sp++; + } else { + base = 10; + } + + for (; (*sp != 0); sp++) { + c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0'); + if ((c < 0) || (c >= base)) { + printk("STALLION: invalid argument %s\n", str); + val = 0; + break; + } + val = (val * base) + c; + } + return(val); +} + +/*****************************************************************************/ + +/* + * Parse the supplied argument string, into the board conf struct. + */ + +static int stl_parsebrd(stlconf_t *confp, char **argp) +{ + char *sp; + int nrbrdnames, i; + +#ifdef DEBUG + printk("stl_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp); +#endif + + if ((argp[0] == (char *) NULL) || (*argp[0] == 0)) + return(0); + + for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++) + *sp = TOLOWER(*sp); + + nrbrdnames = sizeof(stl_brdstr) / sizeof(stlbrdtype_t); + for (i = 0; (i < nrbrdnames); i++) { + if (strcmp(stl_brdstr[i].name, argp[0]) == 0) + break; + } + if (i >= nrbrdnames) { + printk("STALLION: unknown board name, %s?\n", argp[0]); + return(0); + } + + confp->brdtype = stl_brdstr[i].type; + + i = 1; + if ((argp[i] != (char *) NULL) && (*argp[i] != 0)) + confp->ioaddr1 = stl_atol(argp[i]); + i++; + if (confp->brdtype == BRD_ECH) { + if ((argp[i] != (char *) NULL) && (*argp[i] != 0)) + confp->ioaddr2 = stl_atol(argp[i]); + i++; + } + if ((argp[i] != (char *) NULL) && (*argp[i] != 0)) + confp->irq = stl_atol(argp[i]); + return(1); +} + +/*****************************************************************************/ + +/* + * Local driver kernel memory allocation routine. + */ + +static void *stl_memalloc(int len) +{ + return((void *) kmalloc(len, GFP_KERNEL)); +} + +/*****************************************************************************/ + +/* + * Allocate a new board structure. Fill out the basic info in it. + */ + +static stlbrd_t *stl_allocbrd(void) +{ + stlbrd_t *brdp; + + brdp = (stlbrd_t *) stl_memalloc(sizeof(stlbrd_t)); + if (brdp == (stlbrd_t *) NULL) { + printk("STALLION: failed to allocate memory (size=%d)\n", + sizeof(stlbrd_t)); + return((stlbrd_t *) NULL); + } + + memset(brdp, 0, sizeof(stlbrd_t)); + brdp->magic = STL_BOARDMAGIC; + return(brdp); +} + +/*****************************************************************************/ + +static int stl_open(struct tty_struct *tty, struct file *filp) +{ + stlport_t *portp; + stlbrd_t *brdp; + unsigned int minordev; + int brdnr, panelnr, portnr, rc; + +#ifdef DEBUG + printk("stl_open(tty=%x,filp=%x): device=%s\n", (int) tty, + (int) filp, tty->name); +#endif + + minordev = tty->index; + brdnr = MINOR2BRD(minordev); + if (brdnr >= stl_nrbrds) + return(-ENODEV); + brdp = stl_brds[brdnr]; + if (brdp == (stlbrd_t *) NULL) + return(-ENODEV); + minordev = MINOR2PORT(minordev); + for (portnr = -1, panelnr = 0; (panelnr < STL_MAXPANELS); panelnr++) { + if (brdp->panels[panelnr] == (stlpanel_t *) NULL) + break; + if (minordev < brdp->panels[panelnr]->nrports) { + portnr = minordev; + break; + } + minordev -= brdp->panels[panelnr]->nrports; + } + if (portnr < 0) + return(-ENODEV); + + portp = brdp->panels[panelnr]->ports[portnr]; + if (portp == (stlport_t *) NULL) + return(-ENODEV); + +/* + * On the first open of the device setup the port hardware, and + * initialize the per port data structure. + */ + portp->tty = tty; + tty->driver_data = portp; + portp->refcount++; + + if ((portp->flags & ASYNC_INITIALIZED) == 0) { + if (portp->tx.buf == (char *) NULL) { + portp->tx.buf = (char *) stl_memalloc(STL_TXBUFSIZE); + if (portp->tx.buf == (char *) NULL) + return(-ENOMEM); + portp->tx.head = portp->tx.buf; + portp->tx.tail = portp->tx.buf; + } + stl_setport(portp, tty->termios); + portp->sigs = stl_getsignals(portp); + stl_setsignals(portp, 1, 1); + stl_enablerxtx(portp, 1, 1); + stl_startrxtx(portp, 1, 0); + clear_bit(TTY_IO_ERROR, &tty->flags); + portp->flags |= ASYNC_INITIALIZED; + } + +/* + * Check if this port is in the middle of closing. If so then wait + * until it is closed then return error status, based on flag settings. + * The sleep here does not need interrupt protection since the wakeup + * for it is done with the same context. + */ + if (portp->flags & ASYNC_CLOSING) { + interruptible_sleep_on(&portp->close_wait); + if (portp->flags & ASYNC_HUP_NOTIFY) + return(-EAGAIN); + return(-ERESTARTSYS); + } + +/* + * Based on type of open being done check if it can overlap with any + * previous opens still in effect. If we are a normal serial device + * then also we might have to wait for carrier. + */ + if (!(filp->f_flags & O_NONBLOCK)) { + if ((rc = stl_waitcarrier(portp, filp)) != 0) + return(rc); + } + portp->flags |= ASYNC_NORMAL_ACTIVE; + + return(0); +} + +/*****************************************************************************/ + +/* + * Possibly need to wait for carrier (DCD signal) to come high. Say + * maybe because if we are clocal then we don't need to wait... + */ + +static int stl_waitcarrier(stlport_t *portp, struct file *filp) +{ + unsigned long flags; + int rc, doclocal; + +#ifdef DEBUG + printk("stl_waitcarrier(portp=%x,filp=%x)\n", (int) portp, (int) filp); +#endif + + rc = 0; + doclocal = 0; + + if (portp->tty->termios->c_cflag & CLOCAL) + doclocal++; + + save_flags(flags); + cli(); + portp->openwaitcnt++; + if (! tty_hung_up_p(filp)) + portp->refcount--; + + for (;;) { + stl_setsignals(portp, 1, 1); + if (tty_hung_up_p(filp) || + ((portp->flags & ASYNC_INITIALIZED) == 0)) { + if (portp->flags & ASYNC_HUP_NOTIFY) + rc = -EBUSY; + else + rc = -ERESTARTSYS; + break; + } + if (((portp->flags & ASYNC_CLOSING) == 0) && + (doclocal || (portp->sigs & TIOC |