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/serial167.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/serial167.c')
-rw-r--r-- | drivers/char/serial167.c | 2858 |
1 files changed, 2858 insertions, 0 deletions
diff --git a/drivers/char/serial167.c b/drivers/char/serial167.c new file mode 100644 index 00000000000..c2deac968bd --- /dev/null +++ b/drivers/char/serial167.c @@ -0,0 +1,2858 @@ +/* + * linux/drivers/char/serial167.c + * + * Driver for MVME166/7 board serial ports, which are via a CD2401. + * Based very much on cyclades.c. + * + * MVME166/7 work by Richard Hirst [richard@sleepie.demon.co.uk] + * + * ============================================================== + * + * static char rcsid[] = + * "$Revision: 1.36.1.4 $$Date: 1995/03/29 06:14:14 $"; + * + * linux/kernel/cyclades.c + * + * Maintained by Marcio Saito (cyclades@netcom.com) and + * Randolph Bentson (bentson@grieg.seaslug.org) + * + * Much of the design and some of the code came from serial.c + * which was copyright (C) 1991, 1992 Linus Torvalds. It was + * extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92, + * and then fixed as suggested by Michael K. Johnson 12/12/92. + * + * This version does not support shared irq's. + * + * $Log: cyclades.c,v $ + * Revision 1.36.1.4 1995/03/29 06:14:14 bentson + * disambiguate between Cyclom-16Y and Cyclom-32Ye; + * + * Changes: + * + * 200 lines of changes record removed - RGH 11-10-95, starting work on + * converting this to drive serial ports on mvme166 (cd2401). + * + * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 2000/08/25 + * - get rid of verify_area + * - use get_user to access memory from userspace in set_threshold, + * set_default_threshold and set_timeout + * - don't use the panic function in serial167_init + * - do resource release on failure on serial167_init + * - include missing restore_flags in mvme167_serial_console_setup + * + * Kars de Jong <jongk@linux-m68k.org> - 2004/09/06 + * - replace bottom half handler with task queue handler + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/tty.h> +#include <linux/interrupt.h> +#include <linux/serial.h> +#include <linux/serialP.h> +#include <linux/string.h> +#include <linux/fcntl.h> +#include <linux/ptrace.h> +#include <linux/serial167.h> +#include <linux/delay.h> +#include <linux/major.h> +#include <linux/mm.h> +#include <linux/console.h> +#include <linux/module.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/mvme16xhw.h> +#include <asm/bootinfo.h> +#include <asm/setup.h> + +#include <linux/types.h> +#include <linux/kernel.h> + +#include <asm/uaccess.h> +#include <linux/init.h> + +#define SERIAL_PARANOIA_CHECK +#undef SERIAL_DEBUG_OPEN +#undef SERIAL_DEBUG_THROTTLE +#undef SERIAL_DEBUG_OTHER +#undef SERIAL_DEBUG_IO +#undef SERIAL_DEBUG_COUNT +#undef SERIAL_DEBUG_DTR +#undef CYCLOM_16Y_HACK +#define CYCLOM_ENABLE_MONITORING + +#define WAKEUP_CHARS 256 + +#define STD_COM_FLAGS (0) + +#define SERIAL_TYPE_NORMAL 1 + +static struct tty_driver *cy_serial_driver; +extern int serial_console; +static struct cyclades_port *serial_console_info = NULL; +static unsigned int serial_console_cflag = 0; +u_char initial_console_speed; + +/* Base address of cd2401 chip on mvme166/7 */ + +#define BASE_ADDR (0xfff45000) +#define pcc2chip ((volatile u_char *)0xfff42000) +#define PccSCCMICR 0x1d +#define PccSCCTICR 0x1e +#define PccSCCRICR 0x1f +#define PccTPIACKR 0x25 +#define PccRPIACKR 0x27 +#define PccIMLR 0x3f + +/* This is the per-port data structure */ +struct cyclades_port cy_port[] = { + /* CARD# */ + {-1 }, /* ttyS0 */ + {-1 }, /* ttyS1 */ + {-1 }, /* ttyS2 */ + {-1 }, /* ttyS3 */ +}; +#define NR_PORTS (sizeof(cy_port)/sizeof(struct cyclades_port)) + +/* + * tmp_buf is used as a temporary buffer by serial_write. We need to + * lock it in case the copy_from_user blocks while swapping in a page, + * and some other program tries to do a serial write at the same time. + * Since the lock will only come under contention when the system is + * swapping and available memory is low, it makes sense to share one + * buffer across all the serial ports, since it significantly saves + * memory if large numbers of serial ports are open. + */ +static unsigned char *tmp_buf = 0; +DECLARE_MUTEX(tmp_buf_sem); + +/* + * This is used to look up the divisor speeds and the timeouts + * We're normally limited to 15 distinct baud rates. The extra + * are accessed via settings in info->flags. + * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + * 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + * HI VHI + */ +static int baud_table[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, + 1800, 2400, 4800, 9600, 19200, 38400, 57600, 76800,115200,150000, + 0}; + +#if 0 +static char baud_co[] = { /* 25 MHz clock option table */ + /* value => 00 01 02 03 04 */ + /* divide by 8 32 128 512 2048 */ + 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, + 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static char baud_bpr[] = { /* 25 MHz baud rate period table */ + 0x00, 0xf5, 0xa3, 0x6f, 0x5c, 0x51, 0xf5, 0xa3, 0x51, 0xa3, + 0x6d, 0x51, 0xa3, 0x51, 0xa3, 0x51, 0x36, 0x29, 0x1b, 0x15}; +#endif + +/* I think 166 brd clocks 2401 at 20MHz.... */ + +/* These values are written directly to tcor, and >> 5 for writing to rcor */ +static u_char baud_co[] = { /* 20 MHz clock option table */ + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x60, 0x60, 0x40, + 0x40, 0x40, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* These values written directly to tbpr/rbpr */ +static u_char baud_bpr[] = { /* 20 MHz baud rate period table */ + 0x00, 0xc0, 0x80, 0x58, 0x6c, 0x40, 0xc0, 0x81, 0x40, 0x81, + 0x57, 0x40, 0x81, 0x40, 0x81, 0x40, 0x2b, 0x20, 0x15, 0x10}; + +static u_char baud_cor4[] = { /* receive threshold */ + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x07}; + + + +static void shutdown(struct cyclades_port *); +static int startup (struct cyclades_port *); +static void cy_throttle(struct tty_struct *); +static void cy_unthrottle(struct tty_struct *); +static void config_setup(struct cyclades_port *); +extern void console_print(const char *); +#ifdef CYCLOM_SHOW_STATUS +static void show_status(int); +#endif + +#ifdef CONFIG_REMOTE_DEBUG +static void debug_setup(void); +void queueDebugChar (int c); +int getDebugChar(void); + +#define DEBUG_PORT 1 +#define DEBUG_LEN 256 + +typedef struct { + int in; + int out; + unsigned char buf[DEBUG_LEN]; +} debugq; + +debugq debugiq; +#endif + +/* + * I have my own version of udelay(), as it is needed when initialising + * the chip, before the delay loop has been calibrated. Should probably + * reference one of the vmechip2 or pccchip2 counter for an accurate + * delay, but this wild guess will do for now. + */ + +void my_udelay (long us) +{ + u_char x; + volatile u_char *p = &x; + int i; + + while (us--) + for (i = 100; i; i--) + x |= *p; +} + +static inline int +serial_paranoia_check(struct cyclades_port *info, char *name, + const char *routine) +{ +#ifdef SERIAL_PARANOIA_CHECK + static const char *badmagic = + "Warning: bad magic number for serial struct (%s) in %s\n"; + static const char *badinfo = + "Warning: null cyclades_port for (%s) in %s\n"; + static const char *badrange = + "Warning: cyclades_port out of range for (%s) in %s\n"; + + if (!info) { + printk(badinfo, name, routine); + return 1; + } + + if( (long)info < (long)(&cy_port[0]) + || (long)(&cy_port[NR_PORTS]) < (long)info ){ + printk(badrange, name, routine); + return 1; + } + + if (info->magic != CYCLADES_MAGIC) { + printk(badmagic, name, routine); + return 1; + } +#endif + return 0; +} /* serial_paranoia_check */ + +#if 0 +/* The following diagnostic routines allow the driver to spew + information on the screen, even (especially!) during interrupts. + */ +void +SP(char *data){ + unsigned long flags; + local_irq_save(flags); + console_print(data); + local_irq_restore(flags); +} +char scrn[2]; +void +CP(char data){ + unsigned long flags; + local_irq_save(flags); + scrn[0] = data; + console_print(scrn); + local_irq_restore(flags); +}/* CP */ + +void CP1(int data) { (data<10)? CP(data+'0'): CP(data+'A'-10); }/* CP1 */ +void CP2(int data) { CP1((data>>4) & 0x0f); CP1( data & 0x0f); }/* CP2 */ +void CP4(int data) { CP2((data>>8) & 0xff); CP2(data & 0xff); }/* CP4 */ +void CP8(long data) { CP4((data>>16) & 0xffff); CP4(data & 0xffff); }/* CP8 */ +#endif + +/* This routine waits up to 1000 micro-seconds for the previous + command to the Cirrus chip to complete and then issues the + new command. An error is returned if the previous command + didn't finish within the time limit. + */ +u_short +write_cy_cmd(volatile u_char *base_addr, u_char cmd) +{ + unsigned long flags; + volatile int i; + + local_irq_save(flags); + /* Check to see that the previous command has completed */ + for(i = 0 ; i < 100 ; i++){ + if (base_addr[CyCCR] == 0){ + break; + } + my_udelay(10L); + } + /* if the CCR never cleared, the previous command + didn't finish within the "reasonable time" */ + if ( i == 10 ) { + local_irq_restore(flags); + return (-1); + } + + /* Issue the new command */ + base_addr[CyCCR] = cmd; + local_irq_restore(flags); + return(0); +} /* write_cy_cmd */ + + +/* cy_start and cy_stop provide software output flow control as a + function of XON/XOFF, software CTS, and other such stuff. */ + +static void +cy_stop(struct tty_struct *tty) +{ + struct cyclades_port *info = (struct cyclades_port *)tty->driver_data; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + int channel; + unsigned long flags; + +#ifdef SERIAL_DEBUG_OTHER + printk("cy_stop %s\n", tty->name); /* */ +#endif + + if (serial_paranoia_check(info, tty->name, "cy_stop")) + return; + + channel = info->line; + + local_irq_save(flags); + base_addr[CyCAR] = (u_char)(channel); /* index channel */ + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + local_irq_restore(flags); + + return; +} /* cy_stop */ + +static void +cy_start(struct tty_struct *tty) +{ + struct cyclades_port *info = (struct cyclades_port *)tty->driver_data; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + int channel; + unsigned long flags; + +#ifdef SERIAL_DEBUG_OTHER + printk("cy_start %s\n", tty->name); /* */ +#endif + + if (serial_paranoia_check(info, tty->name, "cy_start")) + return; + + channel = info->line; + + local_irq_save(flags); + base_addr[CyCAR] = (u_char)(channel); + base_addr[CyIER] |= CyTxMpty; + local_irq_restore(flags); + + return; +} /* cy_start */ + + +/* + * This routine is used by the interrupt handler to schedule + * processing in the software interrupt portion of the driver + * (also known as the "bottom half"). This can be called any + * number of times for any channel without harm. + */ +static inline void +cy_sched_event(struct cyclades_port *info, int event) +{ + info->event |= 1 << event; /* remember what kind of event and who */ + schedule_work(&info->tqueue); +} /* cy_sched_event */ + + +/* The real interrupt service routines are called + whenever the card wants its hand held--chars + received, out buffer empty, modem change, etc. + */ +static irqreturn_t +cd2401_rxerr_interrupt(int irq, void *dev_id, struct pt_regs *fp) +{ + struct tty_struct *tty; + struct cyclades_port *info; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + unsigned char err, rfoc; + int channel; + char data; + + /* determine the channel and change to that context */ + channel = (u_short ) (base_addr[CyLICR] >> 2); + info = &cy_port[channel]; + info->last_active = jiffies; + + if ((err = base_addr[CyRISR]) & CyTIMEOUT) { + /* This is a receive timeout interrupt, ignore it */ + base_addr[CyREOIR] = CyNOTRANS; + return IRQ_HANDLED; + } + + /* Read a byte of data if there is any - assume the error + * is associated with this character */ + + if ((rfoc = base_addr[CyRFOC]) != 0) + data = base_addr[CyRDR]; + else + data = 0; + + /* if there is nowhere to put the data, discard it */ + if(info->tty == 0) { + base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS; + return IRQ_HANDLED; + } + else { /* there is an open port for this data */ + tty = info->tty; + if(err & info->ignore_status_mask){ + base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS; + return IRQ_HANDLED; + } + if (tty->flip.count < TTY_FLIPBUF_SIZE){ + tty->flip.count++; + if (err & info->read_status_mask){ + if(err & CyBREAK){ + *tty->flip.flag_buf_ptr++ = TTY_BREAK; + *tty->flip.char_buf_ptr++ = data; + if (info->flags & ASYNC_SAK){ + do_SAK(tty); + } + }else if(err & CyFRAME){ + *tty->flip.flag_buf_ptr++ = TTY_FRAME; + *tty->flip.char_buf_ptr++ = data; + }else if(err & CyPARITY){ + *tty->flip.flag_buf_ptr++ = TTY_PARITY; + *tty->flip.char_buf_ptr++ = data; + }else if(err & CyOVERRUN){ + *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; + *tty->flip.char_buf_ptr++ = 0; + /* + If the flip buffer itself is + overflowing, we still loose + the next incoming character. + */ + if(tty->flip.count < TTY_FLIPBUF_SIZE){ + tty->flip.count++; + *tty->flip.flag_buf_ptr++ = TTY_NORMAL; + *tty->flip.char_buf_ptr++ = data; + } + /* These two conditions may imply */ + /* a normal read should be done. */ + /* else if(data & CyTIMEOUT) */ + /* else if(data & CySPECHAR) */ + }else{ + *tty->flip.flag_buf_ptr++ = 0; + *tty->flip.char_buf_ptr++ = 0; + } + }else{ + *tty->flip.flag_buf_ptr++ = 0; + *tty->flip.char_buf_ptr++ = 0; + } + }else{ + /* there was a software buffer overrun + and nothing could be done about it!!! */ + } + } + schedule_delayed_work(&tty->flip.work, 1); + /* end of service */ + base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS; + return IRQ_HANDLED; +} /* cy_rxerr_interrupt */ + +static irqreturn_t +cd2401_modem_interrupt(int irq, void *dev_id, struct pt_regs *fp) +{ + struct cyclades_port *info; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + int channel; + int mdm_change; + int mdm_status; + + + /* determine the channel and change to that context */ + channel = (u_short ) (base_addr[CyLICR] >> 2); + info = &cy_port[channel]; + info->last_active = jiffies; + + mdm_change = base_addr[CyMISR]; + mdm_status = base_addr[CyMSVR1]; + + if(info->tty == 0){ /* nowhere to put the data, ignore it */ + ; + }else{ + if((mdm_change & CyDCD) + && (info->flags & ASYNC_CHECK_CD)){ + if(mdm_status & CyDCD){ +/* CP('!'); */ + cy_sched_event(info, Cy_EVENT_OPEN_WAKEUP); + } else { +/* CP('@'); */ + cy_sched_event(info, Cy_EVENT_HANGUP); + } + } + if((mdm_change & CyCTS) + && (info->flags & ASYNC_CTS_FLOW)){ + if(info->tty->stopped){ + if(mdm_status & CyCTS){ + /* !!! cy_start isn't used because... */ + info->tty->stopped = 0; + base_addr[CyIER] |= CyTxMpty; + cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP); + } + }else{ + if(!(mdm_status & CyCTS)){ + /* !!! cy_stop isn't used because... */ + info->tty->stopped = 1; + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + } + } + } + if(mdm_status & CyDSR){ + } + } + base_addr[CyMEOIR] = 0; + return IRQ_HANDLED; +} /* cy_modem_interrupt */ + +static irqreturn_t +cd2401_tx_interrupt(int irq, void *dev_id, struct pt_regs *fp) +{ + struct cyclades_port *info; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + int channel; + int char_count, saved_cnt; + int outch; + + /* determine the channel and change to that context */ + channel = (u_short ) (base_addr[CyLICR] >> 2); + +#ifdef CONFIG_REMOTE_DEBUG + if (channel == DEBUG_PORT) { + panic ("TxInt on debug port!!!"); + } +#endif + + info = &cy_port[channel]; + + /* validate the port number (as configured and open) */ + if( (channel < 0) || (NR_PORTS <= channel) ){ + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + base_addr[CyTEOIR] = CyNOTRANS; + return IRQ_HANDLED; + } + info->last_active = jiffies; + if(info->tty == 0){ + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + if (info->xmit_cnt < WAKEUP_CHARS) { + cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP); + } + base_addr[CyTEOIR] = CyNOTRANS; + return IRQ_HANDLED; + } + + /* load the on-chip space available for outbound data */ + saved_cnt = char_count = base_addr[CyTFTC]; + + if(info->x_char) { /* send special char */ + outch = info->x_char; + base_addr[CyTDR] = outch; + char_count--; + info->x_char = 0; + } + + if (info->x_break){ + /* The Cirrus chip requires the "Embedded Transmit + Commands" of start break, delay, and end break + sequences to be sent. The duration of the + break is given in TICs, which runs at HZ + (typically 100) and the PPR runs at 200 Hz, + so the delay is duration * 200/HZ, and thus a + break can run from 1/100 sec to about 5/4 sec. + Need to check these values - RGH 141095. + */ + base_addr[CyTDR] = 0; /* start break */ + base_addr[CyTDR] = 0x81; + base_addr[CyTDR] = 0; /* delay a bit */ + base_addr[CyTDR] = 0x82; + base_addr[CyTDR] = info->x_break*200/HZ; + base_addr[CyTDR] = 0; /* terminate break */ + base_addr[CyTDR] = 0x83; + char_count -= 7; + info->x_break = 0; + } + + while (char_count > 0){ + if (!info->xmit_cnt){ + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + break; + } + if (info->xmit_buf == 0){ + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + break; + } + if (info->tty->stopped || info->tty->hw_stopped){ + base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy); + break; + } + /* Because the Embedded Transmit Commands have been + enabled, we must check to see if the escape + character, NULL, is being sent. If it is, we + must ensure that there is room for it to be + doubled in the output stream. Therefore we + no longer advance the pointer when the character + is fetched, but rather wait until after the check + for a NULL output character. (This is necessary + because there may not be room for the two chars + needed to send a NULL. + */ + outch = info->xmit_buf[info->xmit_tail]; + if( outch ){ + info->xmit_cnt--; + info->xmit_tail = (info->xmit_tail + 1) + & (PAGE_SIZE - 1); + base_addr[CyTDR] = outch; + char_count--; + }else{ + if(char_count > 1){ + info->xmit_cnt--; + info->xmit_tail = (info->xmit_tail + 1) + & (PAGE_SIZE - 1); + base_addr[CyTDR] = outch; + base_addr[CyTDR] = 0; + char_count--; + char_count--; + }else{ + break; + } + } + } + + if (info->xmit_cnt < WAKEUP_CHARS) { + cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP); + } + base_addr[CyTEOIR] = (char_count != saved_cnt) ? 0 : CyNOTRANS; + return IRQ_HANDLED; +} /* cy_tx_interrupt */ + +static irqreturn_t +cd2401_rx_interrupt(int irq, void *dev_id, struct pt_regs *fp) +{ + struct tty_struct *tty; + struct cyclades_port *info; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + int channel; + char data; + int char_count; + int save_cnt; + + /* determine the channel and change to that context */ + channel = (u_short ) (base_addr[CyLICR] >> 2); + info = &cy_port[channel]; + info->last_active = jiffies; + save_cnt = char_count = base_addr[CyRFOC]; + +#ifdef CONFIG_REMOTE_DEBUG + if (channel == DEBUG_PORT) { + while (char_count--) { + data = base_addr[CyRDR]; + queueDebugChar(data); + } + } + else +#endif + /* if there is nowhere to put the data, discard it */ + if(info->tty == 0){ + while(char_count--){ + data = base_addr[CyRDR]; + } + }else{ /* there is an open port for this data */ + tty = info->tty; + /* load # characters available from the chip */ + +#ifdef CYCLOM_ENABLE_MONITORING + ++info->mon.int_count; + info->mon.char_count += char_count; + if (char_count > info->mon.char_max) + info->mon.char_max = char_count; + info->mon.char_last = char_count; +#endif + while(char_count--){ + data = base_addr[CyRDR]; + if (tty->flip.count >= TTY_FLIPBUF_SIZE){ + continue; + } + tty->flip.count++; + *tty->flip.flag_buf_ptr++ = TTY_NORMAL; + *tty->flip.char_buf_ptr++ = data; +#ifdef CYCLOM_16Y_HACK + udelay(10L); +#endif + } + schedule_delayed_work(&tty->flip.work, 1); + } + /* end of service */ + base_addr[CyREOIR] = save_cnt ? 0 : CyNOTRANS; + return IRQ_HANDLED; +} /* cy_rx_interrupt */ + +/* + * This routine is used to handle the "bottom half" processing for the + * serial driver, known also the "software interrupt" processing. + * This processing is done at the kernel interrupt level, after the + * cy#/_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This + * is where time-consuming activities which can not be done in the + * interrupt driver proper are done; the interrupt driver schedules + * them using cy_sched_event(), and they get done here. + * + * This is done through one level of indirection--the task queue. + * When a hardware interrupt service routine wants service by the + * driver's bottom half, it enqueues the appropriate tq_struct (one + * per port) to the keventd work queue and sets a request flag + * that the work queue be processed. + * + * Although this may seem unwieldy, it gives the system a way to + * pass an argument (in this case the pointer to the cyclades_port + * structure) to the bottom half of the driver. Previous kernels + * had to poll every port to see if that port needed servicing. + */ +static void +do_softint(void *private_) +{ + struct cyclades_port *info = (struct cyclades_port *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + if (test_and_clear_bit(Cy_EVENT_HANGUP, &info->event)) { + tty_hangup(info->tty); + wake_up_interruptible(&info->open_wait); + info->flags &= ~ASYNC_NORMAL_ACTIVE; + } + if (test_and_clear_bit(Cy_EVENT_OPEN_WAKEUP, &info->event)) { + wake_up_interruptible(&info->open_wait); + } + if (test_and_clear_bit(Cy_EVENT_WRITE_WAKEUP, &info->event)) { + tty_wakeup(tty); + } +} /* do_softint */ + + +/* This is called whenever a port becomes active; + interrupts are enabled and DTR & RTS are turned on. + */ +static int +startup(struct cyclades_port * info) +{ + unsigned long flags; + volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR; + int channel; + + if (info->flags & ASYNC_INITIALIZED){ + return 0; + } + + if (!info->type){ + if (info->tty){ + set_bit(TTY_IO_ERROR, &info->tty->flags); + } + return 0; + } + if (!info->xmit_buf){ + info->xmit_buf = (unsigned char *) get_zeroed_page (GFP_KERNEL); + if (!info->xmit_buf){ + return -ENOMEM; + } + } + + config_setup(info); + + channel = info->line; + +#ifdef SERIAL_DEBUG_OPEN + printk("startup channel %d\n", channel); +#endif + + local_irq_save(flags); + base_addr[CyCAR] = (u_char)channel; + write_cy_cmd(base_addr,CyENB_RCVR|CyENB_XMTR); + + base_addr[CyCAR] = (u_char)channel; /* !!! Is this needed? */ + base_addr[CyMSVR1] = CyRTS; +/* CP('S');CP('1'); */ + base_addr[CyMSVR2] = CyDTR; + +#ifdef SERIAL_DEBUG_DTR + printk("cyc: %d: raising DTR\n", __LINE__); + printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]); +#endif + + base_addr[CyIER] |= CyRxData; + info->flags |= ASYNC_INITIALIZED; + + if (info->tty){ + clear_bit(TTY_IO_ERROR, &info->tty->flags); + } + info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; + + local_irq_restore(flags); + +#ifdef SERIAL_DEBUG_OPEN + printk(" done\n"); +#endif + return 0; +} /* startup */ + +void +start_xmit( struct cyclades_port *info ) +{ + unsigned long flags; + volatile unsigned char *base_addr = (u_char *)BASE_ADDR; + int channel; + + channel = info->line; + local_irq_save(flags); + base_addr[CyCAR] = channel; + base_addr[CyIER] |= CyTxMpty; + local_irq_restore(flags); +} /* start_xmit */ + +/* + * This routine shuts down a serial port; interrupts are disabled, + * and DTR is dropped if the hangup on close termio flag is on. + */ +static void +shutdown(struct cyclades_port * info) +{ + unsigned long flags; + volatile unsigned char *base_addr = (u_char *)BASE_ADDR; + int channel; + + if (!(info->flags & ASYNC_INITIALIZED)){ +/* CP('$'); */ + return; + } + + channel = info->line; + +#ifdef SERIAL_DEBUG_OPEN + printk("shutdown channel %d\n", channel); +#endif + + /* !!! REALLY MUST WAIT FOR LAST CHARACTER TO BE + SENT BEFORE DROPPING THE LINE !!! (Perhaps + set some flag that is read when XMTY happens.) + Other choices are to delay some fixed interval + or schedule some later processing. + */ + local_irq_save(flags); + if (info->xmit_buf){ + free_page((unsigned long) info->xmit_buf); + info->xmit_buf = 0; + } + + base_addr[CyCAR] = (u_char)channel; + if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { + base_addr[CyMSVR1] = 0; +/* CP('C');CP('1'); */ + base_addr[CyMSVR2] = 0; +#ifdef SERIAL_DEBUG_DTR + printk("cyc: %d: dropping DTR\n", __LINE__); + printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]); +#endif + } + write_cy_cmd(base_addr,CyDIS_RCVR); + /* it may be appropriate to clear _XMIT at + some later date (after testing)!!! */ + + if (info->tty){ + set_bit(TTY_IO_ERROR, &info->tty->flags); + } + info->flags &= ~ASYNC_INITIALIZED; + local_irq_restore(flags); + +#ifdef SERIAL_DEBUG_OPEN + printk(" done\n"); +#endif + return; +} /* shutdown */ + +/* + * This routine finds or computes the various line characteristics. + */ +static void +config_setup(struct cyclades_port * info) +{ + unsigned long flags; + volatile unsigned char *base_addr = (u_char *)BASE_ADDR; + int channel; + unsigned cflag; + int i; + unsigned char ti, need_init_chan = 0; + + if (!info->tty || !info->tty->termios){ + return; + } + if (info->line == -1){ + return; + } + cflag = info->tty->termios->c_cflag; + + /* baud rate */ + i = cflag & CBAUD; +#ifdef CBAUDEX +/* Starting with kernel 1.1.65, there is direct support for + higher baud rates. The following code supports those + changes. The conditional aspect allows this driver to be + used for earlier as well as later kernel versions. (The + mapping is slightly different from serial.c because there + is still the possibility of supporting 75 kbit/sec with + the Cyclades board.) + */ + if (i & CBAUDEX) { + if (i == B57600) + i = 16; + else if(i == B115200) + i = 18; +#ifdef B78600 + else if(i == B78600) + i = 17; +#endif + else + info->tty->termios->c_cflag &= ~CBAUDEX; + } +#endif + if (i == 15) { + if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + i += 1; + if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + i += 3; + } + /* Don't ever change the speed of the console port. It will + * run at the speed specified in bootinfo, or at 19.2K */ + /* Actually, it should run at whatever speed 166Bug was using */ + /* Note info->timeout isn't used at present */ + if (info != serial_console_info) { + info->tbpr = baud_bpr[i]; /* Tx BPR */ + info->tco = baud_co[i]; /* Tx CO */ + info->rbpr = baud_bpr[i]; /* Rx BPR */ + info->rco = baud_co[i] >> 5; /* Rx CO */ + if (baud_table[i] == 134) { + info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2; + /* get it right for 134.5 baud */ + } else if (baud_table[i]) { + info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2; + /* this needs to be propagated into the card info */ + } else { + info->timeout = 0; + } + } + /* By tradition (is it a standard?) a baud rate of zero + implies the line should be/has been closed. A bit + later in this routine such a test is performed. */ + + /* byte size and parity */ + info->cor7 = 0; + info->cor6 = 0; + info->cor5 = 0; + info->cor4 = (info->default_threshold + ? info->default_threshold + : baud_cor4[i]); /* receive threshold */ + /* Following two lines added 101295, RGH. */ + /* It is obviously wrong to access CyCORx, and not info->corx here, + * try and remember to fix it later! */ + channel = info->line; + base_addr[CyCAR] = (u_char)channel; + if (C_CLOCAL(info->tty)) { + if (base_addr[CyIER] & CyMdmCh) + base_addr[CyIER] &= ~CyMdmCh; /* without modem intr */ + /* ignore 1->0 modem transitions */ + if (base_addr[CyCOR4] & (CyDSR|CyCTS|CyDCD)) + base_addr[CyCOR4] &= ~(CyDSR|CyCTS|CyDCD); + /* ignore 0->1 modem transitions */ + if (base_addr[CyCOR5] & (CyDSR|CyCTS|CyDCD)) + base_addr[CyCOR5] &= ~(CyDSR|CyCTS|CyDCD); + } else { + if ((base_addr[CyIER] & CyMdmCh) != CyMdmCh) + base_addr[CyIER] |= CyMdmCh; /* with modem intr */ + /* act on 1->0 modem transitions */ + if ((base_addr[CyCOR4] & (CyDSR|CyCTS|CyDCD)) != (CyDSR|CyCTS|CyDCD)) + base_addr[CyCOR4] |= CyDSR|CyCTS|CyDCD; + /* act on 0->1 modem transitions */ + if ((base_addr[CyCOR5] & (CyDSR|CyCTS|CyDCD)) != (CyDSR|CyCTS|CyDCD)) + base_addr[CyCOR5] |= CyDSR|CyCTS|CyDCD; + } + info->cor3 = (cflag & CSTOPB) ? Cy_2_STOP : Cy_1_STOP; + info->cor2 = CyETC; + switch(cflag & CSIZE){ + case CS5: + info->cor1 = Cy_5_BITS; + break; + case CS6: + info->cor1 = Cy_6_BITS; + break; + case CS7: + info->cor1 = Cy_7_BITS; + break; + case CS8: + info->cor1 = Cy_8_BITS; + break; + } + if (cflag & PARENB){ + if (cflag & PARODD){ + info->cor1 |= CyPARITY_O; + }else{ + info->cor1 |= CyPARITY_E; + } + }else{ + info->cor1 |= CyPARITY_NONE; + } + + /* CTS flow control flag */ +#if 0 + /* Don't complcate matters for now! RGH 141095 */ + if (cflag & CRTSCTS){ + info->flags |= ASYNC_CTS_FLOW; + info->cor2 |= CyCtsAE; + }else{ + info->flags &= ~ASYNC_CTS_FLOW; + info->cor2 &= ~CyCtsAE; + } +#endif + if (cflag & CLOCAL) + info->flags &= ~ASYNC_CHECK_CD; + else + info->flags |= ASYNC_CHECK_CD; + + /*********************************************** + The hardware option, CyRtsAO, presents RTS when + the chip has characters to send. Since most modems + use RTS as reverse (inbound) flow control, this + option is not used. If inbound flow control is + necessary, DTR can be programmed to provide the + appropriate signals for use with a non-standard + cable. Contact Marcio Saito for details. + ***********************************************/ + + channel = info->line; + + local_irq_save(flags); + base_addr[CyCAR] = (u_char)channel; + + /* CyCMR set once only in mvme167_init_serial() */ + if (base_addr[CyLICR] != channel << 2) + base_addr[CyLICR] = channel << 2; + if (base_addr[CyLIVR] != 0x5c) + base_addr[CyLIVR] = 0x5c; + + /* tx and rx baud rate */ + + if (base_addr[CyCOR1] != info->cor1) + need_init_chan = 1; + if (base_addr[CyTCOR] != info->tco) + base_addr[CyTCOR] = info->tco; + if (base_addr[CyTBPR] != info->tbpr) + base_addr[CyTBPR] = info->tbpr; + if (base_addr[CyRCOR] != info->rco) + base_addr[CyRCOR] = info->rco; + if (base_addr[CyRBPR] != info->rbpr) + base_addr[CyRBPR] = info->rbpr; + + /* set line characteristics according configuration */ + + if (base_addr[CySCHR1] != START_CHAR(info->tty)) + base_addr[CySCHR1] = START_CHAR(info->tty); + if (base_addr[CySCHR2] != STOP_CHAR(info->tty)) + base_addr[CySCHR2] = STOP_CHAR(info->tty); + if (base_addr[CySCRL] != START_CHAR(info->tty)) + base_addr[CySCRL] = START_CHAR(info->tty); + if (base_addr[CySCRH] != START_CHAR(info->tty)) + base_addr[CySCRH] = START_CHAR(info->tty); + if (base_addr[CyCOR1] != info->cor1) + base_addr[CyCOR1] = info->cor1; + if (base_addr[CyCOR2] != info->cor2) + base_addr[CyCOR2] = info->cor2; + if (base_addr[CyCOR3] != info->cor3) + base_addr[CyCOR3] = info->cor3; + if (base_addr[CyCOR4] != info->cor4) + base_addr[CyCOR4] = info->cor4; + if (base_addr[CyCOR5] != info->cor5) + base_addr[CyCOR5] = info->cor5; + if (base_addr[CyCOR6] != info->cor6) + base_addr[CyCOR6] = info->cor6; + if (base_addr[CyCOR7] != info->cor7) + base_addr[CyCOR7] = info->cor7; + + if (need_init_chan) + write_cy_cmd(base_addr,CyINIT_CHAN); |