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/pcmcia |
Linux-2.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/pcmcia')
-rw-r--r-- | drivers/char/pcmcia/Kconfig | 22 | ||||
-rw-r--r-- | drivers/char/pcmcia/Makefile | 7 | ||||
-rw-r--r-- | drivers/char/pcmcia/synclink_cs.c | 4611 |
3 files changed, 4640 insertions, 0 deletions
diff --git a/drivers/char/pcmcia/Kconfig b/drivers/char/pcmcia/Kconfig new file mode 100644 index 000000000000..d22bfdc13563 --- /dev/null +++ b/drivers/char/pcmcia/Kconfig @@ -0,0 +1,22 @@ +# +# PCMCIA character device configuration +# + +menu "PCMCIA character devices" + depends on HOTPLUG && PCMCIA!=n + +config SYNCLINK_CS + tristate "SyncLink PC Card support" + depends on PCMCIA + help + Enable support for the SyncLink PC Card serial adapter, running + asynchronous and HDLC communications up to 512Kbps. The port is + selectable for RS-232, V.35, RS-449, RS-530, and X.21 + + This driver may be built as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called synclinkmp. If you want to do that, say M + here. + +endmenu + diff --git a/drivers/char/pcmcia/Makefile b/drivers/char/pcmcia/Makefile new file mode 100644 index 000000000000..1fcd4c591958 --- /dev/null +++ b/drivers/char/pcmcia/Makefile @@ -0,0 +1,7 @@ +# +# drivers/char/pcmcia/Makefile +# +# Makefile for the Linux PCMCIA char device drivers. +# + +obj-$(CONFIG_SYNCLINK_CS) += synclink_cs.o diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c new file mode 100644 index 000000000000..1c8d866a49dc --- /dev/null +++ b/drivers/char/pcmcia/synclink_cs.c @@ -0,0 +1,4611 @@ +/* + * linux/drivers/char/pcmcia/synclink_cs.c + * + * $Id: synclink_cs.c,v 4.26 2004/08/11 19:30:02 paulkf Exp $ + * + * Device driver for Microgate SyncLink PC Card + * multiprotocol serial adapter. + * + * written by Paul Fulghum for Microgate Corporation + * paulkf@microgate.com + * + * Microgate and SyncLink are trademarks of Microgate Corporation + * + * This code is released under the GNU General Public License (GPL) + * + * 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. + */ + +#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq)) +#if defined(__i386__) +# define BREAKPOINT() asm(" int $3"); +#else +# define BREAKPOINT() { } +#endif + +#define MAX_DEVICE_COUNT 4 + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/time.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/slab.h> +#include <linux/netdevice.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <asm/serial.h> +#include <linux/delay.h> +#include <linux/ioctl.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 <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> + +#ifdef CONFIG_HDLC_MODULE +#define CONFIG_HDLC 1 +#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> + +#include "linux/synclink.h" + +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; */ +}; + +typedef struct +{ + int count; + unsigned char status; + char data[1]; +} RXBUF; + +/* 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 + +#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +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; +}; + + +/* + * Device instance data structure + */ + +typedef struct _mgslpc_info { + void *if_ptr; /* General purpose pointer (used by SPPP) */ + int magic; + int flags; + int count; /* count of opens */ + int line; + unsigned short close_delay; + unsigned short closing_wait; /* time to wait before closing */ + + struct mgsl_icount icount; + + struct tty_struct *tty; + int timeout; + int x_char; /* xon/xoff character */ + int blocked_open; /* # of blocked opens */ + unsigned char read_status_mask; + unsigned char ignore_status_mask; + + unsigned char *tx_buf; + int tx_put; + int tx_get; + int tx_count; + + /* circular list of fixed length rx buffers */ + + unsigned char *rx_buf; /* memory allocated for all rx buffers */ + int rx_buf_total_size; /* size of memory allocated for rx buffers */ + int rx_put; /* index of next empty rx buffer */ + int rx_get; /* index of next full rx buffer */ + int rx_buf_size; /* size in bytes of single rx buffer */ + int rx_buf_count; /* total number of rx buffers */ + int rx_frame_count; /* number of full rx buffers */ + + wait_queue_head_t open_wait; + wait_queue_head_t close_wait; + + 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 _mgslpc_info *next_device; /* device list link */ + + unsigned short imra_value; + unsigned short imrb_value; + unsigned char pim_value; + + spinlock_t lock; + struct work_struct task; /* task structure for scheduling bh */ + + u32 max_frame_size; + + u32 pending_bh; + + int bh_running; + int 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; + + int rx_enabled; + int rx_overflow; + + int tx_enabled; + int tx_active; + int tx_aborting; + u32 idle_mode; + + int if_mode; /* serial interface selection (RS-232, v.35 etc) */ + + char device_name[25]; /* device instance name */ + + unsigned int io_base; /* base I/O address of adapter */ + unsigned int irq_level; + + MGSL_PARAMS params; /* communications parameters */ + + unsigned char serial_signals; /* current serial signal states */ + + char irq_occurred; /* for diagnostics use */ + char testing_irq; + unsigned int init_error; /* startup error (DIAGS) */ + + char flag_buf[MAX_ASYNC_BUFFER_SIZE]; + BOOLEAN drop_rts_on_tx_done; + + struct _input_signal_events input_signal_events; + + /* PCMCIA support */ + dev_link_t link; + dev_node_t node; + int stop; + + /* SPPP/Cisco HDLC device parts */ + int netcount; + int dosyncppp; + spinlock_t netlock; + +#ifdef CONFIG_HDLC + struct net_device *netdev; +#endif + +} MGSLPC_INFO; + +#define MGSLPC_MAGIC 0x5402 + +/* + * The size of the serial xmit buffer is 1 page, or 4096 bytes + */ +#define TXBUFSIZE 4096 + + +#define CHA 0x00 /* channel A offset */ +#define CHB 0x40 /* channel B offset */ + +/* + * FIXME: PPC has PVR defined in asm/reg.h. For now we just undef it. + */ +#undef PVR + +#define RXFIFO 0 +#define TXFIFO 0 +#define STAR 0x20 +#define CMDR 0x20 +#define RSTA 0x21 +#define PRE 0x21 +#define MODE 0x22 +#define TIMR 0x23 +#define XAD1 0x24 +#define XAD2 0x25 +#define RAH1 0x26 +#define RAH2 0x27 +#define DAFO 0x27 +#define RAL1 0x28 +#define RFC 0x28 +#define RHCR 0x29 +#define RAL2 0x29 +#define RBCL 0x2a +#define XBCL 0x2a +#define RBCH 0x2b +#define XBCH 0x2b +#define CCR0 0x2c +#define CCR1 0x2d +#define CCR2 0x2e +#define CCR3 0x2f +#define VSTR 0x34 +#define BGR 0x34 +#define RLCR 0x35 +#define AML 0x36 +#define AMH 0x37 +#define GIS 0x38 +#define IVA 0x38 +#define IPC 0x39 +#define ISR 0x3a +#define IMR 0x3a +#define PVR 0x3c +#define PIS 0x3d +#define PIM 0x3d +#define PCR 0x3e +#define CCR4 0x3f + +// IMR/ISR + +#define IRQ_BREAK_ON BIT15 // rx break detected +#define IRQ_DATAOVERRUN BIT14 // receive data overflow +#define IRQ_ALLSENT BIT13 // all sent +#define IRQ_UNDERRUN BIT12 // transmit data underrun +#define IRQ_TIMER BIT11 // timer interrupt +#define IRQ_CTS BIT10 // CTS status change +#define IRQ_TXREPEAT BIT9 // tx message repeat +#define IRQ_TXFIFO BIT8 // transmit pool ready +#define IRQ_RXEOM BIT7 // receive message end +#define IRQ_EXITHUNT BIT6 // receive frame start +#define IRQ_RXTIME BIT6 // rx char timeout +#define IRQ_DCD BIT2 // carrier detect status change +#define IRQ_OVERRUN BIT1 // receive frame overflow +#define IRQ_RXFIFO BIT0 // receive pool full + +// STAR + +#define XFW BIT6 // transmit FIFO write enable +#define CEC BIT2 // command executing +#define CTS BIT1 // CTS state + +#define PVR_DTR BIT0 +#define PVR_DSR BIT1 +#define PVR_RI BIT2 +#define PVR_AUTOCTS BIT3 +#define PVR_RS232 0x20 /* 0010b */ +#define PVR_V35 0xe0 /* 1110b */ +#define PVR_RS422 0x40 /* 0100b */ + +/* Register access functions */ + +#define write_reg(info, reg, val) outb((val),(info)->io_base + (reg)) +#define read_reg(info, reg) inb((info)->io_base + (reg)) + +#define read_reg16(info, reg) inw((info)->io_base + (reg)) +#define write_reg16(info, reg, val) outw((val), (info)->io_base + (reg)) + +#define set_reg_bits(info, reg, mask) \ + write_reg(info, (reg), \ + (unsigned char) (read_reg(info, (reg)) | (mask))) +#define clear_reg_bits(info, reg, mask) \ + write_reg(info, (reg), \ + (unsigned char) (read_reg(info, (reg)) & ~(mask))) +/* + * interrupt enable/disable routines + */ +static void irq_disable(MGSLPC_INFO *info, unsigned char channel, unsigned short mask) +{ + if (channel == CHA) { + info->imra_value |= mask; + write_reg16(info, CHA + IMR, info->imra_value); + } else { + info->imrb_value |= mask; + write_reg16(info, CHB + IMR, info->imrb_value); + } +} +static void irq_enable(MGSLPC_INFO *info, unsigned char channel, unsigned short mask) +{ + if (channel == CHA) { + info->imra_value &= ~mask; + write_reg16(info, CHA + IMR, info->imra_value); + } else { + info->imrb_value &= ~mask; + write_reg16(info, CHB + IMR, info->imrb_value); + } +} + +#define port_irq_disable(info, mask) \ + { info->pim_value |= (mask); write_reg(info, PIM, info->pim_value); } + +#define port_irq_enable(info, mask) \ + { info->pim_value &= ~(mask); write_reg(info, PIM, info->pim_value); } + +static void rx_start(MGSLPC_INFO *info); +static void rx_stop(MGSLPC_INFO *info); + +static void tx_start(MGSLPC_INFO *info); +static void tx_stop(MGSLPC_INFO *info); +static void tx_set_idle(MGSLPC_INFO *info); + +static void get_signals(MGSLPC_INFO *info); +static void set_signals(MGSLPC_INFO *info); + +static void reset_device(MGSLPC_INFO *info); + +static void hdlc_mode(MGSLPC_INFO *info); +static void async_mode(MGSLPC_INFO *info); + +static void tx_timeout(unsigned long context); + +static int ioctl_common(MGSLPC_INFO *info, unsigned int cmd, unsigned long arg); + +#ifdef CONFIG_HDLC +#define dev_to_port(D) (dev_to_hdlc(D)->priv) +static void hdlcdev_tx_done(MGSLPC_INFO *info); +static void hdlcdev_rx(MGSLPC_INFO *info, char *buf, int size); +static int hdlcdev_init(MGSLPC_INFO *info); +static void hdlcdev_exit(MGSLPC_INFO *info); +#endif + +static void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit); + +static BOOLEAN register_test(MGSLPC_INFO *info); +static BOOLEAN irq_test(MGSLPC_INFO *info); +static int adapter_test(MGSLPC_INFO *info); + +static int claim_resources(MGSLPC_INFO *info); +static void release_resources(MGSLPC_INFO *info); +static void mgslpc_add_device(MGSLPC_INFO *info); +static void mgslpc_remove_device(MGSLPC_INFO *info); + +static int rx_get_frame(MGSLPC_INFO *info); +static void rx_reset_buffers(MGSLPC_INFO *info); +static int rx_alloc_buffers(MGSLPC_INFO *info); +static void rx_free_buffers(MGSLPC_INFO *info); + +static irqreturn_t mgslpc_isr(int irq, void *dev_id, struct pt_regs * regs); + +/* + * Bottom half interrupt handlers + */ +static void bh_handler(void* Context); +static void bh_transmit(MGSLPC_INFO *info); +static void bh_status(MGSLPC_INFO *info); + +/* + * ioctl 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 get_stats(MGSLPC_INFO *info, struct mgsl_icount __user *user_icount); +static int get_params(MGSLPC_INFO *info, MGSL_PARAMS __user *user_params); +static int set_params(MGSLPC_INFO *info, MGSL_PARAMS __user *new_params); +static int get_txidle(MGSLPC_INFO *info, int __user *idle_mode); +static int set_txidle(MGSLPC_INFO *info, int idle_mode); +static int set_txenable(MGSLPC_INFO *info, int enable); +static int tx_abort(MGSLPC_INFO *info); +static int set_rxenable(MGSLPC_INFO *info, int enable); +static int wait_events(MGSLPC_INFO *info, int __user *mask); + +static MGSLPC_INFO *mgslpc_device_list = NULL; +static int mgslpc_device_count = 0; + +/* + * 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=0; + +/* + * Driver major number, defaults to zero to get auto + * assigned major number. May be forced as module parameter. + */ +static int ttymajor=0; + +static int debug_level = 0; +static int maxframe[MAX_DEVICE_COUNT] = {0,}; +static int dosyncppp[MAX_DEVICE_COUNT] = {1,1,1,1}; + +module_param(break_on_load, bool, 0); +module_param(ttymajor, int, 0); +module_param(debug_level, int, 0); +module_param_array(maxframe, int, NULL, 0); +module_param_array(dosyncppp, int, NULL, 0); + +MODULE_LICENSE("GPL"); + +static char *driver_name = "SyncLink PC Card driver"; +static char *driver_version = "$Revision: 4.26 $"; + +static struct tty_driver *serial_driver; + +/* number of characters left in xmit buffer before we ask for more */ +#define WAKEUP_CHARS 256 + +static void mgslpc_change_params(MGSLPC_INFO *info); +static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout); + +/* PCMCIA prototypes */ + +static void mgslpc_config(dev_link_t *link); +static void mgslpc_release(u_long arg); +static int mgslpc_event(event_t event, int priority, + event_callback_args_t *args); +static dev_link_t *mgslpc_attach(void); +static void mgslpc_detach(dev_link_t *); + +static dev_info_t dev_info = "synclink_cs"; +static dev_link_t *dev_list = NULL; + +/* + * 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* mgslpc_get_text_ptr(void) +{ + return mgslpc_get_text_ptr; +} + +/** + * line discipline callback wrappers + * + * The wrappers maintain line discipline references + * while calling into the line discipline. + * + * ldisc_flush_buffer - flush line discipline receive buffers + * ldisc_receive_buf - pass receive data to line discipline + */ + +static void ldisc_flush_buffer(struct tty_struct *tty) +{ + struct tty_ldisc *ld = tty_ldisc_ref(tty); + if (ld) { + if (ld->flush_buffer) + ld->flush_buffer(tty); + tty_ldisc_deref(ld); + } +} + +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->receive_buf) + ld->receive_buf(tty, data, flags, count); + tty_ldisc_deref(ld); + } +} + +static dev_link_t *mgslpc_attach(void) +{ + MGSLPC_INFO *info; + dev_link_t *link; + client_reg_t client_reg; + int ret; + + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_attach\n"); + + info = (MGSLPC_INFO *)kmalloc(sizeof(MGSLPC_INFO), GFP_KERNEL); + if (!info) { + printk("Error can't allocate device instance data\n"); + return NULL; + } + + memset(info, 0, sizeof(MGSLPC_INFO)); + info->magic = MGSLPC_MAGIC; + INIT_WORK(&info->task, bh_handler, info); + info->max_frame_size = 4096; + info->close_delay = 5*HZ/10; + info->closing_wait = 30*HZ; + init_waitqueue_head(&info->open_wait); + init_waitqueue_head(&info->close_wait); + init_waitqueue_head(&info->status_event_wait_q); + init_waitqueue_head(&info->event_wait_q); + spin_lock_init(&info->lock); + spin_lock_init(&info->netlock); + memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS)); + info->idle_mode = HDLC_TXIDLE_FLAGS; + info->imra_value = 0xffff; + info->imrb_value = 0xffff; + info->pim_value = 0xff; + + link = &info->link; + link->priv = info; + + /* Initialize the dev_link_t structure */ + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID; + link->irq.Handler = NULL; + + link->conf.Attributes = 0; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &mgslpc_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + mgslpc_detach(link); + return NULL; + } + + mgslpc_add_device(info); + + return link; +} + +/* Card has been inserted. + */ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void mgslpc_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + MGSLPC_INFO *info = link->priv; + tuple_t tuple; + cisparse_t parse; + int last_fn, last_ret; + u_char buf[64]; + config_info_t conf; + cistpl_cftable_entry_t dflt = { 0 }; + cistpl_cftable_entry_t *cfg; + + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_config(0x%p)\n", link); + + /* read CONFIG tuple to find its configuration registers */ + tuple.DesiredTuple = CISTPL_CONFIG; + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up the current Vcc */ + CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &conf)); + link->conf.Vcc = conf.Vcc; + + /* get CIS configuration entry */ + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + + cfg = &(parse.cftable_entry); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + + if (cfg->flags & CISTPL_CFTABLE_DEFAULT) dflt = *cfg; + if (cfg->index == 0) + goto cs_failed; + + link->conf.ConfigIndex = cfg->index; + link->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window settings */ + link->io.NumPorts1 = 0; + if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) { + cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (!(io->flags & CISTPL_IO_8BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + if (!(io->flags & CISTPL_IO_16BIT)) + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + link->io.BasePort1 = io->win[0].base; + link->io.NumPorts1 = io->win[0].len; + CS_CHECK(RequestIO, pcmcia_request_io(link->handle, &link->io)); + } + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 8; + link->conf.Present = PRESENT_OPTION; + + link->irq.Attributes |= IRQ_HANDLE_PRESENT; + link->irq.Handler = mgslpc_isr; + link->irq.Instance = info; + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + info->io_base = link->io.BasePort1; + info->irq_level = link->irq.AssignedIRQ; + + /* add to linked list of devices */ + sprintf(info->node.dev_name, "mgslpc0"); + info->node.major = info->node.minor = 0; + link->dev = &info->node; + + printk(KERN_INFO "%s: index 0x%02x:", + info->node.dev_name, link->conf.ConfigIndex); + if (link->conf.Attributes & CONF_ENABLE_IRQ) + printk(", irq %d", link->irq.AssignedIRQ); + if (link->io.NumPorts1) + printk(", io 0x%04x-0x%04x", link->io.BasePort1, + link->io.BasePort1+link->io.NumPorts1-1); + printk("\n"); + + link->state &= ~DEV_CONFIG_PENDING; + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); + mgslpc_release((u_long)link); +} + +/* Card has been removed. + * Unregister device and release PCMCIA configuration. + * If device is open, postpone until it is closed. + */ +static void mgslpc_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_release(0x%p)\n", link); + + /* Unlink the device chain */ + link->dev = NULL; + link->state &= ~DEV_CONFIG; + + pcmcia_release_configuration(link->handle); + if (link->io.NumPorts1) + pcmcia_release_io(link->handle, &link->io); + if (link->irq.AssignedIRQ) + pcmcia_release_irq(link->handle, &link->irq); + if (link->state & DEV_STALE_LINK) + mgslpc_detach(link); +} + +static void mgslpc_detach(dev_link_t *link) +{ + dev_link_t **linkp; + + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_detach(0x%p)\n", link); + + /* find device */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) { + /* device is configured/active, mark it so when + * release() is called a proper detach() occurs. + */ + if (debug_level >= DEBUG_LEVEL_INFO) + printk(KERN_DEBUG "synclinkpc: detach postponed, '%s' " + "still locked\n", link->dev->dev_name); + link->state |= DEV_STALE_LINK; + return; + } + + /* Break the link with Card Services */ + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, and free it */ + *linkp = link->next; + mgslpc_remove_device((MGSLPC_INFO *)link->priv); +} + +static int mgslpc_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + MGSLPC_INFO *info = link->priv; + + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + ((MGSLPC_INFO *)link->priv)->stop = 1; + mgslpc_release((u_long)link); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + mgslpc_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + /* Mark the device as stopped, to block IO until later */ + info->stop = 1; + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) + pcmcia_request_configuration(link->handle, &link->conf); + info->stop = 0; + break; + } + return 0; +} + +static inline int mgslpc_paranoia_check(MGSLPC_INFO *info, + char *name, const char *routine) +{ +#ifdef MGSLPC_PARANOIA_CHECK + static const char *badmagic = + "Warning: bad magic number for mgsl struct (%s) in %s\n"; + static const char *badinfo = + "Warning: null mgslpc_info for (%s) in %s\n"; + + if (!info) { + printk(badinfo, name, routine); + return 1; + } + if (info->magic != MGSLPC_MAGIC) { + printk(badmagic, name, routine); + return 1; + } +#else + if (!info) + return 1; +#endif + return 0; +} + + +#define CMD_RXFIFO BIT7 // release current rx FIFO +#define CMD_RXRESET BIT6 // receiver reset +#define CMD_RXFIFO_READ BIT5 +#define CMD_START_TIMER BIT4 +#define CMD_TXFIFO BIT3 // release current tx FIFO +#define CMD_TXEOM BIT1 // transmit end message +#define CMD_TXRESET BIT0 // transmit reset + +static BOOLEAN wait_command_complete(MGSLPC_INFO *info, unsigned char channel) +{ + int i = 0; + /* wait for command completion */ + while (read_reg(info, (unsigned char)(channel+STAR)) & BIT2) { + udelay(1); + if (i++ == 1000) + return FALSE; + } + return TRUE; +} + +static void issue_command(MGSLPC_INFO *info, unsigned char channel, unsigned char cmd) +{ + wait_command_complete(info, channel); + write_reg(info, (unsigned char) (channel + CMDR), cmd); +} + +static void tx_pause(struct tty_struct *tty) +{ + MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data; + unsigned long flags; + + if (mgslpc_paranoia_check(info, tty->name, "tx_pause")) + return; + if (debug_level >= DEBUG_LEVEL_INFO) + printk("tx_pause(%s)\n",info->device_name); + + spin_lock_irqsave(&info->lock,flags); + if (info->tx_enabled) + tx_stop(info); + spin_unlock_irqrestore(&info->lock,flags); +} + +static void tx_release(struct tty_struct *tty) +{ + MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data; + unsigned long flags; + + if (mgslpc_paranoia_check(info, tty->name, "tx_release")) + return; + if (debug_level >= DEBUG_LEVEL_INFO) + printk("tx_release(%s)\n",info->device_name); + + spin_lock_irqsave(&info->lock,flags); + if (!info->tx_enabled) + tx_start(info); + spin_unlock_irqrestore(&info->lock,flags); +} + +/* Return next bottom half action to perform. + * or 0 if nothing to do. + */ +static int bh_action(MGSLPC_INFO *info) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&info->lock,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 = 0; + info->bh_requested = 0; + } + + spin_unlock_irqrestore(&info->lock,flags); + + return rc; +} + +void bh_handler(void* Context) +{ + MGSLPC_INFO *info = (MGSLPC_INFO*)Context; + int action; + + if (!info) + return; + + if (debug_level >= DEBUG_LEVEL_BH) + printk( "%s(%d):bh_handler(%s) entry\n", + __FILE__,__LINE__,info->device_name); + + info->bh_running = 1; + + while((action = bh_action(info)) != 0) { + + /* Process work item */ + if ( debug_level >= DEBUG_LEVEL_BH ) + printk( "%s(%d):bh_handler() work item action=%d\n", + __FILE__,__LINE__,action); + + switch (action) { + + case BH_RECEIVE: + while(rx_get_frame(info)); + break; + case BH_TRANSMIT: + bh_transmit(info); + break; + case BH_STATUS: + 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):bh_handler(%s) exit\n", + __FILE__,__LINE__,info->device_name); +} + +void bh_transmit(MGSLPC_INFO *info) +{ + struct tty_struct *tty = info->tty; + if (debug_level >= DEBUG_LEVEL_BH) + printk("bh_transmit() entry on %s\n", info->device_name); + + if (tty) { + tty_wakeup(tty); + wake_up_interruptible(&tty->write_wait); + } +} + +void bh_status(MGSLPC_INFO *info) +{ + info->ri_chkcount = 0; + info->dsr_chkcount = 0; + info->dcd_chkcount = 0; + info->cts_chkcount = 0; +} + +/* eom: non-zero = end of frame */ +static void rx_ready_hdlc(MGSLPC_INFO *info, int eom) +{ + unsigned char data[2]; + unsigned char fifo_count, read_count, i; + RXBUF *buf = (RXBUF*)(info->rx_buf + (info->rx_put * info->rx_buf_size)); + + if (debug_level >= DEBUG_LEVEL_ISR) + printk("%s(%d):rx_ready_hdlc(eom=%d)\n",__FILE__,__LINE__,eom); + + if (!info->rx_enabled) + return; + + if (info->rx_frame_count >= info->rx_buf_count) { + /* no more free buffers */ + issue_command(info, CHA, CMD_RXRESET); + info->pending_bh |= BH_RECEIVE; + info->rx_overflow = 1; + info->icount.buf_overrun++; + return; + } + + if (eom) { + /* end of frame, get FIFO count from RBCL register */ + if (!(fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f))) + fifo_count = 32; + } else + fifo_count = 32; + + do { + if (fifo_count == 1) { + read_count = 1; + data[0] = read_reg(info, CHA + RXFIFO); + } else { + read_count = 2; + *((unsigned short *) data) = read_reg16(info, CHA + RXFIFO); + } + fifo_count -= read_count; + if (!fifo_count && eom) + buf->status = data[--read_count]; + + for (i = 0; i < read_count; i++) { + if (buf->count >= info->max_frame_size) { + /* frame too large, reset receiver and reset current buffer */ + issue_command(info, CHA, CMD_RXRESET); + buf->count = 0; + return; + } + *(buf->data + buf->count) = data[i]; + buf->count++; + } + } while (fifo_count); + + if (eom) { + info->pending_bh |= BH_RECEIVE; + info->rx_frame_count++; + info->rx_put++; + if (info->rx_put >= info->rx_buf_count) + info->rx_put = 0; + } + issue_command(info, CHA, CMD_RXFIFO); +} + +static void rx_ready_async(MGSLPC_INFO *info, int tcd) +{ + unsigned char data, status; + int fifo_count; + struct tty_struct *tty = info->tty; + struct mgsl_icount *icount = &info->icount; + + if (tcd) { + /* early termination, get FIFO count from RBCL register */ |