/* #define SERIAL_DEBUG_OPEN */
#define C_LOCAL(tty)    _C_FLAG((tty),CLOCAL)
#define uchar unsigned char

static char rcsid[] =
"$Id: cyclades.c,v 1.13 1994/07/21 23:08:57 Bentson Exp Bentson $";
/*
 *  linux/kernel/cyclades.c
 *
 * Much of the design and some of the code came from serial.c
 * This version does not support shared irq's.
 *
 * This module exports the following rs232 io functions:
 *   long cy_init(long);
 *   int  cy_open(struct tty_struct *tty, struct file *filp);
 *
 * $Log: cyclades.c,v $
 * Revision 1.13  1994/07/21  23:08:57  Bentson
 * add some diagnostic cruft; support 24 lines (for testing
 * both -8Y and -16Y cards; be more thorough in servicing all
 * chips during interrupt; add "volatile" a few places to
 * circumvent compiler optimizations; fix base & offset
 * computations in block_til_ready (was causing chip 0 to
 * stop operation)
 *
 * Revision 1.12  1994/07/19  16:42:11  Bentson
 * add some hackery for kernel version 1.1.8; expand
 * error messages; refine timing for delay loops and
 * declare loop params volatile
 *
 * Revision 1.11  1994/06/11  21:53:10  bentson
 * get use of save_car right in transmit interrupt service
 *
 * Revision 1.10.1.1  1994/06/11  21:31:18  bentson
 * add some diagnostic printing; try to fix save_car stuff
 *
 * Revision 1.10  1994/06/11  20:36:08  bentson
 * clean up compiler warnings
 *
 * Revision 1.9  1994/06/11  19:42:46  bentson
 * added a bunch of code to support modem signalling
 *
 * Revision 1.8  1994/06/11  17:57:07  bentson
 * recognize break & parity error
 *
 * Revision 1.7  1994/06/05  05:51:34  bentson
 * Reorder baud table to be monotonic; add cli to CP; discard
 * incoming characters and status if the line isn't open; start to
 * fold code into cy_throttle; start to port get_serial_info,
 * set_serial_info, get_modem_info, set_modem_info, and send_break
 * from serial.c; expand cy_ioctl; relocate and expand config_setup;
 * get flow control characters from tty struct; invalidate ports w/o
 * hardware;
 *
 * Revision 1.6  1994/05/31  18:42:21  bentson
 * add a loop-breaker in the interrupt service routine;
 * note when port is initialized so that it can be shut
 * down under the right conditions; receive works without
 * any obvious errors
 *
 * Revision 1.5  1994/05/30  00:55:02  bentson
 * transmit works without obvious errors
 *
 * Revision 1.4  1994/05/27  18:46:27  bentson
 * incorporated more code from lib_y.c; can now print short
 * strings under interrupt control to port zero; seems to
 * select ports/channels/lines correctly
 *
 * Revision 1.3  1994/05/25  22:12:44  bentson
 * shifting from multi-port on a card to proper multiplexor
 * data structures;  added skeletons of most routines
 *
 * Revision 1.2  1994/05/19  13:21:43  bentson
 * start to crib from other sources
 *
 */

#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/serial.h>
#include <linux/interrupt.h>
#include <linux/config.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/ptrace.h>
#include <linux/cyclades.h>
#include <linux/delay.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/bitops.h>

#define about_10us     29
#define about_100us   288
#define about_500us  1440
#define about_1000us 2880

/* loop long enough for some interval to have passed. */
#define small_delay(x) for(i=0;i<x;i++)j++;

/* loop for some interval or until a condition is met. */
#define conditional_delay(x,y) for(i=0;i<x;i++)if(y)break;

#define STD_COM_FLAGS (0)

/* This is the per-card data structure */
struct cyclades_card cy_card[] = {
     /* IRQ BASE_ADDR */
    {10,0xD4000},
    {11,0xD6000},
    {12,0xD8000},
    {15,0xDA000}
};
#define NR_CARDS        (sizeof(cy_card)/sizeof(struct cyclades_card))

/* This is the per-chip data structure */
struct cyclades_chip cy_chip[] = {
        {0}
};

/* This is the per-port data structure */
struct cyclades_port cy_port[] = {
      /* CARD#  */
        {-1 },      /* ttyC0 */
        {-1 },      /* ttyC1 */
        {-1 },      /* ttyC2 */
        {-1 },      /* ttyC3 */
        {-1 },      /* ttyC4 */
        {-1 },      /* ttyC5 */
        {-1 },      /* ttyC6 */
        {-1 },      /* ttyC7 */
        {-1 },      /* ttyC8 */
        {-1 },      /* ttyC9 */
        {-1 },      /* ttyC10 */
        {-1 },      /* ttyC11 */
        {-1 },      /* ttyC12 */
        {-1 },      /* ttyC13 */
        {-1 },      /* ttyC14 */
        {-1 },      /* ttyC15 */
        {-1 },      /* ttyC16 */
        {-1 },      /* ttyC17 */
        {-1 },      /* ttyC18 */
        {-1 },      /* ttyC19 */
        {-1 },      /* ttyC20 */
        {-1 },      /* ttyC21 */
        {-1 },      /* ttyC22 */
        {-1 },      /* ttyC23 */
        {-1 }       /* ttyC24 */
};
#define NR_PORTS        (sizeof(cy_port)/sizeof(struct cyclades_port))

/* This is the per-irq data structure,
               it maps an irq to the corresponding card */
struct cyclades_card * IRQ_cards[16];


/* This is a bitfield of pending events to be processed at the
   next clock tick.  We assume here that int's are 32 bits so
   that eight ints gives us enough bits for all possible minors.  */
static int cy_event[8];


/*
 * This is used to look up the divsor 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   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};

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};




static void shutdown(struct cyclades_port *);
static void startup (struct cyclades_port *);
static void cy_throttle(struct tty_struct *, int);
static void config_setup(struct cyclades_port *);
extern void console_print(const char *);
static void show_status(int);

char scrn[2];
void
CP(char data){
  unsigned long flags;
    save_flags(flags); cli();
    scrn[0] = data;
    console_print(scrn);
    restore_flags(flags);
}/* CP */

void
CP1(int data)
{
    if(data<10){
        CP(data+'0');
    }else{
        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 */

ushort
write_cmd(uchar *base_addr, uchar cmd)
{
  unsigned long flags;
  volatile int  i;

    save_flags(flags); cli();
    conditional_delay(about_100us,
                (*(base_addr + CyCCR) == 0))
    if ( i == about_100us ) {
        restore_flags(flags);
        return (-1);
    }
    *(base_addr + CyCCR) = cmd;
    restore_flags(flags);
    return(0);
} /* write_cmd */


/* cy_start and cy_stop provide output flow control
   as a function of XON/XOFF and other such stuff. */

static void
cy_stop(struct tty_struct *tty)
{
  struct cyclades_card *cinfo;
  struct cyclades_port *info;
  unsigned char *base_addr;
  int line_num;
  int card,chip,channel;
  unsigned long flags;

    /* printk("cy_stop\n");*/
    if(NR_PORTS <= (line_num = DEV_TO_CY(tty->line))) return;
    info = &cy_port[line_num];
    if(-1 == (card = info->card)
      || NR_CARDS <= card) return;
    cinfo = &cy_card[card];
    channel = info->line - cinfo->first_line;
    chip = channel>>2;
    channel &= 0x03;
    base_addr = (unsigned char*)
                   (cy_card[card].base_addr + chip * CyRegSize);
        
    save_flags(flags); cli();
    *(base_addr+CyCAR)=(uchar)(channel&0x0003); /* index channel */
    *(base_addr + CySRER) = *(base_addr + CySRER) & ~CyTxRdy;
    restore_flags(flags);

    return;
} /* cy_stop */

static void
cy_start(struct tty_struct *tty)
{
  struct cyclades_card *cinfo;
  struct cyclades_port *info;
  unsigned char *base_addr;
  int line_num;
  int card,chip,channel;
  unsigned long flags;
        
    /* printk("cy_stop\n");*/
    if(NR_PORTS <= (line_num = DEV_TO_CY(tty->line))) return;
    info = &cy_port[line_num];
    if(-1 == (card = info->card)
      || NR_CARDS <= card) return;
    cinfo = &cy_card[card];
    channel = info->line - cinfo->first_line;
    chip = channel>>2;
    channel &= 0x03;
    base_addr = (unsigned char*)
                   (cy_card[card].base_addr + chip * CyRegSize);
        
    save_flags(flags); cli();
    *(base_addr + CyCAR) = (uchar)(channel&0x0003);
    *(base_addr + CySRER) = *(base_addr + CySRER) | CyTxRdy;
    restore_flags(flags);

    return;
} /* cy_start */

/*
 * This routine is used by the interrupt handler to schedule
 * processing in the software interrupt portion of the driver.
 */
static inline void
sched_event(struct cyclades_port *info, int event)
{
    info->event |= 1 << event; /* remember what kind of event */
    set_bit(info->line, cy_event); /* and who it belongs to */
    mark_bh(CYCLADES_BH);      /* then trigger event */
} /* sched_event */


/*
 * 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 sched_event(), and they get done here.
 */
static void
do_softint(void *unused)
{
  int  i;
  struct cyclades_port  *info;

    for (i = 0, info = cy_port; i < NR_PORTS; i++,info++) {
        if (clear_bit(i, cy_event)) {
            if (!info->tty)     
                continue;
            if (clear_bit(Cy_EVENT_READ_PROCESS, &info->event)) {
                TTY_READ_FLUSH(info->tty);
            }
            if (clear_bit(Cy_EVENT_WRITE_WAKEUP, &info->event)) {
                wake_up_interruptible(&info->tty->write_q.proc_list);
            }
            if (clear_bit(Cy_EVENT_HANGUP, &info->event)) {
                tty_hangup(info->tty);
                wake_up_interruptible(&info->open_wait);
                info->flags &= ~(ASYNC_NORMAL_ACTIVE|
                                     ASYNC_CALLOUT_ACTIVE);
            }
#ifdef nodef
Is this enough? !!!
            if (clear_bit(Cy_EVENT_BREAK, &info->event))
                handle_rs_break(info);
#endif
            if (clear_bit(Cy_EVENT_OPEN_WAKEUP, &info->event)) {
                wake_up_interruptible(&info->open_wait);
            }
        }
    }
} /* do_softint */


#define VLEFT ((tail-head-1)&(TTY_BUF_SIZE-1))
#define WAKEUP_CHARS (3*TTY_BUF_SIZE/4)

/* Called whenever the card wants its hand held. */
static void
cy_interrupt(int irq)
{
  int status;
  struct cyclades_card *cinfo;
  struct cyclades_port *info;
  volatile unsigned char *base_addr, *card_base_addr;
  int chip;
  int save_xir, channel, save_car;
  char data;
  volatile char vdata;
  struct tty_queue *queue;
  int head, tail;
  int char_count;
  int outch;
  int i,j;
  int too_many;
  int had_work;
  int mdm_change;
  int mdm_status;

    if((cinfo = IRQ_cards[irq]) == 0){
        return; /* spurious interrupt */
    }

    card_base_addr = (unsigned char *)cinfo->base_addr;

    /* loop checks all chips in the card */
    do{
	base_addr = (unsigned char *)cinfo->base_addr;
        had_work = 0;
        for ( chip = 0 ; chip < cinfo->num_chips ; chip ++) {
            too_many = 0;
            while ( (status = *(base_addr + CySVRR)) != 0x00) {
                had_work++;
                if(1000<too_many++){
CP('X');
                    break;
                }
                if (status & CySRReceive) {      /* reception interrupt */
                    save_xir = (uchar) *(base_addr + CyRIR);
                    channel = (ushort ) (save_xir & CyIRChannel);
                    i = channel + chip * 4;
                    info = &cy_port[i];
                    save_car = *(base_addr + CyCAR);
                    *(base_addr + CyCAR) = save_xir;
                    if(info->tty == 0){ /* nowhere to put the data, discard it */
                        *(base_addr + CySRER) = *(base_addr + CySRER) & ~CyTxRdy;
                        j = (*(base_addr + CyRIVR) & CyIVRMask);
                        if ( j == CyIVRRxEx ) { /* exception */
                            data = *(base_addr + CyRDSR);
                        } else { /* normal character reception */
                            char_count = *(base_addr + CyRDCR);
                            while(char_count--){
                                data = *(base_addr + CyRDSR);
                            }
                        }
                    }else{ /* there is an open port for this data */
                        j = (*(base_addr + CyRIVR) & CyIVRMask);

                        queue = &info->tty->read_q;
                        head = queue->head;
                        tail = queue->tail;
                        if ( j == CyIVRRxEx ) { /* exception */
                            data = *(base_addr + CyRDSR);
                            /* Should check VLEFT !!! */
                            if(data & CyBREAK){
                                queue->buf[head++] = TTY_BREAK;
                                sched_event(info,Cy_EVENT_BREAK);
                            }else if(data & CyPARITY){
                                queue->buf[head++] = TTY_PARITY;
                            }else if(data & CyFRAME){
                                queue->buf[head++] = TTY_FRAME;
                            /* }else if(data & CyTIMEOUT){ */
                            /* }else if(data & CySPECHAR){ */
                            /* }else if(data & CyOVERRUN){ */
                            }
                            head &= TTY_BUF_SIZE-1;

                        } else { /* normal character reception */
                            char_count = *(base_addr + CyRDCR);

                            while(char_count--){
                                data = *(base_addr + CyRDSR);
                                if (VLEFT < 2){
/* CP('V'); CP2(i); */
                                        continue;
                                }
                                queue->buf[head++] = data;
                                head &= TTY_BUF_SIZE-1;
                            }
                        }
                        queue->head = head;
                        if ((VLEFT < RQ_THRESHOLD_LW) &&
                            !set_bit(TTY_RQ_THROTTLED, &info->tty->flags)){
/* CP('T'); CP2(i); */
                            cy_throttle(info->tty, TTY_THROTTLE_RQ_FULL);
                        }
                        sched_event(info, Cy_EVENT_READ_PROCESS);
                    }
                    /* end of service */
                    *(base_addr + CyRIR) = (save_xir & 0x3f);
                    *(base_addr + CyCAR) = (save_car);
                }

                if (status & CySRTransmit) {     /* transmission interrupt */
                    /* Since we only get here when the transmit buffer is empty,
                        we know we can always stuff a dozen characters. */
                
                    save_xir = (uchar) *(base_addr + CyTIR);
                    channel = (ushort ) (save_xir & CyIRChannel);
                    i = channel + chip * 4 + cinfo->first_line;
                    save_car = *(base_addr + CyCAR);
                    if( (i<0) || (NR_PORTS <= i) ){
                        *(base_addr + CySRER) = *(base_addr + CySRER) & ~CyTxRdy;
                        goto txdone;
                    }
                    info = &cy_port[i];
                    *(base_addr + CyCAR) = save_xir;
                    if(info->tty == 0){
                        *(base_addr + CySRER) = *(base_addr + CySRER) & ~CyTxRdy;
                        goto txdone;
                    }

                    queue = &info->tty->write_q;
                    head = queue->head;
                    tail = queue->tail;
                    char_count = 0;

                    if(info->x_char) { /* send special char */
                        outch = info->x_char;
                        *(base_addr + CyTDR ) = outch;
                        char_count++;
                        info->x_char = 0;
                    }

                    if((tail == head)){ /* no more chars to send */
                        *(base_addr + CySRER) = *(base_addr + CySRER) & ~CyTxRdy;
                        goto txdone;
                    }

                    while((tail != head)          /* more characters to send */
                    && (char_count < CyMAX_CHAR_FIFO)){ /* and room for them */
                        outch = queue->buf[tail++];
                        *(base_addr + CyTDR ) = outch;
                        char_count++;
                        tail &= TTY_BUF_SIZE-1;
                        queue->tail = tail;
                    }

                    if (VLEFT > WAKEUP_CHARS) {
                        sched_event(info, Cy_EVENT_WRITE_WAKEUP);
                        if (info->tty->write_data_cnt) {
                            set_bit(info->tty->line, &tty_check_write);
                            mark_bh(CYCLADES_BH);
                        }
                    }

                    /* end of service */
        txdone:
                    *(base_addr + CyTIR) = (save_xir & 0x3f);
                    *(base_addr + CyCAR) = (save_car);
                }

                if (status & CySRModem) {        /* modem interrupt */
                    save_xir = (uchar) *(base_addr + CyMIR);
                    channel = (ushort ) (save_xir & CyIRChannel);
                    info = &cy_port[channel + chip * 4];
                    save_car = *(base_addr + CyCAR);
                    *(base_addr + CyCAR) = save_xir;

                    mdm_change = *(base_addr + CyMISR);
                    mdm_status = *(base_addr + CyMSVR1);

                    if(info->tty == 0){ /* nowhere to put the data, discard it */
                        ;
                    }else{
                        if(mdm_change & CyDCD){
                            if(mdm_status & CyDCD){
                                sched_event(info, Cy_EVENT_OPEN_WAKEUP);
                            }else if(!((info->flags & ASYNC_CALLOUT_ACTIVE)
                                     &&(info->flags & ASYNC_CALLOUT_NOHUP))){
                                sched_event(info, Cy_EVENT_HANGUP);
                            }
                        }
#ifdef nodef
                        if(C_RTSCTS(info->tty)){
                            if(info->tty->stopped){
                                 if(mdm_status & CyCTS){
                                     info->tty->stopped = 0;
                                     cy_start(info->tty);
                                 }
                            }else{
                                 if(!(mdm_status & CyCTS)){
                                     info->tty->stopped = 1;
                                     cy_stop(info->tty);
                                 }
                            }
                        }
#endif
                        if(mdm_status & CyDSR){
                        }
                        if(mdm_status & CyRI){
                        }
                    }
                    /* end of service */
                    *(base_addr + CyMIR) = (save_xir & 0x3f);
                    *(base_addr + CyCAR) = save_car;
                }
            }          /* end while status != 0 */
            base_addr += CyRegSize;
        }            /* end loop for chips... */
    }while(had_work);

   /* clear interrupts */
   vdata = *(card_base_addr + Cy_ClrIntr); /* Cy_ClrIntr is 0x1800 */

} /* cy_interrupt */


/* This routine gets called when tty_write has put something into
    the write_queue.  If the port is not already transmitting stuff,
    start it off by enabling interrupts.  The interrupt service
    routine will then ensure that the characters are sent.  If the
    port is already active, there is no need to kick it.
 */
static void
cy_write(struct tty_struct * tty)
{
  struct cyclades_port *info;
  int line_num;

    /*printk("cy_write\n");*/

    if (!tty || tty->stopped)
            return;

    if(NR_PORTS <= (line_num = DEV_TO_CY(tty->line))) return;
    info = &cy_port[line_num];
    startup(info);

    return;
} /* cy_write */


/* This routine is called by the upper-layer tty layer to signal that
    incoming characters should be throttled (and that the throttle
    should be released).
 */
static void
cy_throttle(struct tty_struct * tty, int status)
{
  struct cyclades_port *info;
  int line_num;
  unsigned long flags;

return;
    if(NR_PORTS <= (line_num = DEV_TO_CY(tty->line))) return;
    info = &cy_port[line_num];

    switch (status) {
    case TTY_THROTTLE_RQ_FULL:
        if (tty->termios->c_iflag & IXOFF) {
            info->x_char = STOP_CHAR(tty);
            startup(info);
        } else {
            save_flags(flags); cli();
            /*  This is what serial.c does !!!
            mcr = serial_inp(info, UART_MCR);
            mcr &= ~UART_MCR_RTS;
            serial_out(info, UART_MCR, mcr);
            !!!*/
            restore_flags(flags);
        }
        break;

    case TTY_THROTTLE_RQ_AVAIL:
        if (tty->termios->c_iflag & IXOFF) {
            if (info->x_char)
                info->x_char = 0;
            else
                info->x_char = START_CHAR(tty);
                startup(info);
        } else {
            save_flags(flags); cli();
            /* This is what serial.c does !!!
            mcr = serial_in(info, UART_MCR);
            mcr |= UART_MCR_RTS;
            serial_out(info, UART_MCR, mcr);
            !!!*/
            restore_flags(flags);
        }
        break;
    }

    return;
} /* cy_throttle */

static int
get_serial_info(struct cyclades_port * info,
                           struct serial_struct * retinfo)
{
  struct serial_struct tmp;
 
    if (!retinfo)
            return -EFAULT;
    memset(&tmp, 0, sizeof(tmp));
    tmp.type = PORT_CD1400;
    tmp.line = info->line;
    tmp.port = 0;               /*!!!*/
    tmp.irq = 0;                /*!!!*/
    tmp.flags = info->flags;
    tmp.baud_base = 0;          /*!!!*/
    tmp.close_delay = info->close_delay;
    tmp.custom_divisor = 0;     /*!!!*/
    tmp.hub6 = 0;               /*!!!*/
    memcpy_tofs(retinfo,&tmp,sizeof(*retinfo));
    return 0;
} /* get_serial_info */

static int
set_serial_info(struct cyclades_port * info,
                           struct serial_struct * new_info)
{
  struct serial_struct new_serial;
  struct cyclades_port old_info;

    return -EFAULT;
        if (!new_info)
                return -EFAULT;
        memcpy_fromfs(&new_serial,new_info,sizeof(new_serial));
        old_info = *info;

        if (!suser()) {
                if ((new_serial.close_delay != info->close_delay) ||
                    ((new_serial.flags & ASYNC_FLAGS & ~ASYNC_USR_MASK) !=
                     (info->flags & ASYNC_FLAGS & ~ASYNC_USR_MASK)))
                        return -EPERM;
                info->flags = ((info->flags & ~ASYNC_USR_MASK) |
                               (new_serial.flags & ASYNC_USR_MASK));
                goto check_and_exit;
        }


        /*
         * OK, past this point, all the error checking has been done.
         * At this point, we start making changes.....
         */

        info->flags = ((info->flags & ~ASYNC_FLAGS) |
                        (new_serial.flags & ASYNC_FLAGS));
        info->close_delay = new_serial.close_delay;

        
check_and_exit:
        startup(info);
        config_setup(info);

        return 0;
} /* set_serial_info */

static int
get_modem_info(struct cyclades_port * info, unsigned int *value)
{
  int card,chip,channel;
  unsigned char *base_addr;
  unsigned long flags;
  unsigned char status;
  unsigned int result;

    card = info->card;
    channel = (info->line) - (cy_card[card].first_line);
    chip = channel>>2;
    channel &= 0x03;
    base_addr = (unsigned char*)
                   (cy_card[card].base_addr + chip * CyRegSize);

    save_flags(flags); cli();
        *(base_addr + CyCAR)=(uchar)channel;
        status = *(base_addr + CyMSVR1) | *(base_addr + CyMSVR2);
    restore_flags(flags);

    result =  ((status  & CyRTS) ? TIOCM_RTS : 0)
            | ((status  & CyDTR) ? TIOCM_DTR : 0)
            | ((status  & CyDCD) ? TIOCM_CAR : 0)
            | ((status  & CyRI) ? TIOCM_RNG : 0)
            | ((status  & CyDSR) ? TIOCM_DSR : 0)
            | ((status  & CyCTS) ? TIOCM_CTS : 0);
    put_fs_long(result,(unsigned long *) value);
    return 0;
} /* get_modem_info */

static int
set_modem_info(struct cyclades_port * info, unsigned int cmd,
                          unsigned int *value)
{
  int card,chip,channel;
  unsigned char *base_addr;
  unsigned long flags;
  unsigned int arg = get_fs_long((unsigned long *) value);

    card = info->card;
    channel = (info->line) - (cy_card[card].first_line);
    chip = channel>>2;
    channel &= 0x03;
    base_addr = (unsigned char*)
                   (cy_card[card].base_addr + chip * CyRegSize);

    save_flags(flags); cli();
        *(base_addr + CyCAR)=(uchar)channel;

        switch (cmd) {
                case TIOCMBIS:
                        if (arg & TIOCM_RTS)
                            *(base_addr + CyMSVR1) = CyRTS;
                        if (arg & TIOCM_DTR)
                            *(base_addr + CyMSVR2) = CyDTR;
                        break;
                case TIOCMBIC:
                        if (arg & TIOCM_RTS)
                            *(base_addr + CyMSVR1) = 0;
                        if (arg & TIOCM_DTR)
                            *(base_addr + CyMSVR2) = 0;
                        break;
                case TIOCMSET:
                        if (arg & TIOCM_RTS)
                            *(base_addr + CyMSVR1) = CyRTS;
                        else
                            *(base_addr + CyMSVR1) = 0;
                        if (arg & TIOCM_DTR)
                            *(base_addr + CyMSVR2) = CyDTR;
                        else
                            *(base_addr + CyMSVR2) = 0;
                        break;
                default:
                        restore_flags(flags);
                        return -EINVAL;
        }
    restore_flags(flags);
    return 0;
} /* set_modem_info */

static void
send_break( struct cyclades_port * info, int duration)
{
        current->state = TASK_INTERRUPTIBLE;
        current->timeout = jiffies + duration;
        cli();
/* This is what serial.c did !!!
        serial_out(info, UART_LCR, serial_inp(info, UART_LCR) | UART_LCR_SBC);
        schedule();
        serial_out(info, UART_LCR, serial_inp(info, UART_LCR) & ~UART_LCR_SBC);
!!! */
        sti();
} /* send_break */


static int
cy_ioctl(struct tty_struct *tty, struct file * file,
            unsigned int cmd, unsigned long arg)
{
  int error, line;
  struct cyclades_port * info;

/* CP('I'); */
    line = DEV_TO_CY(tty->line);
    if (line < 0 || line >= NR_PORTS)
            return -ENODEV;
    info = &cy_port[line];
    
    switch (cmd) {
        case TCSBRK:    /* SVID version: non-zero arg --> no break */
            wait_until_sent(tty,0);
            if (!arg)
                send_break(info, HZ/4); /* 1/4 second */
            return 0;
        case TCSBRKP:   /* support for POSIX tcsendbreak() */
            wait_until_sent(tty,0);
            send_break(info, arg ? arg*(HZ/10) : HZ/4);
            return 0;
        case TIOCGSOFTCAR:
            error = verify_area(VERIFY_WRITE, (void *) arg
                                ,sizeof(unsigned int *));
            if (error)
                return error;
            put_fs_long(C_LOCAL(tty) ? 1 : 0,
                        (unsigned long *) arg);
            return 0;
        case TIOCSSOFTCAR:
            arg = get_fs_long((unsigned long *) arg);
            tty->termios->c_cflag =
                    ((tty->termios->c_cflag & ~CLOCAL) |
                     (arg ? CLOCAL : 0));
            return 0;
        case TIOCMGET:
            error = verify_area(VERIFY_WRITE, (void *) arg
                                ,sizeof(unsigned int *));
            if (error)
                return error;
            return get_modem_info(info, (unsigned int *) arg);
        case TIOCMBIS:
        case TIOCMBIC:
        case TIOCMSET:
            return set_modem_info(info, cmd, (unsigned int *) arg);
        case TIOCGSERIAL:
            error = verify_area(VERIFY_WRITE, (void *) arg
                                ,sizeof(struct serial_struct));
            if (error)
                return error;
            return get_serial_info(info,
                                   (struct serial_struct *) arg);
        case TIOCSSERIAL:
            return set_serial_info(info,
                                   (struct serial_struct *) arg);
        default:
            return -EINVAL;
    }
    return 0;
} /* cy_ioctl */




/*
 * This routine finds or computes the various line characteristics.
 */
static void
config_setup(struct cyclades_port * info)
{
  unsigned cflag;
  int   i;

    if (!info->tty || !info->tty->termios){
        return;
    }
    if (info->line == -1){
        return;
    }
    cflag = info->tty->termios->c_cflag;

    /* byte size and parity */
    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 & CSTOPB){
        info->cor1 |= Cy_2_STOP;
    }
    if (cflag & PARENB){
        if (cflag & PARODD){
            info->cor1 |= CyPARITY_O;
        }else{
            info->cor1 |= CyPARITY_E;
        }
    }else{
        info->cor1 |= CyPARITY_NONE;
    }
    info->cor2 = 0;
    info->cor3 = 1; /* _very_ small receive threshold */
    info->cor4 = 0;
    info->cor5 = 0;

    /* baud rate */
    i = cflag & CBAUD;
    if (i == 15) {
        if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
            i += 1;
        if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
            i += 2;
    }
    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]; /* 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;
    }

} /* config_setup */


static void
cy_set_termios(struct tty_struct *tty, struct termios * old_termios)
{
  struct cyclades_port *info;

/* CP('S'); */
    if (tty->termios->c_cflag == old_termios->c_cflag)
        return;

    info = &cy_port[DEV_TO_CY(tty->line)];

    config_setup(info);
    
    if ((old_termios->c_cflag & CRTSCTS) &&
        !(tty->termios->c_cflag & CRTSCTS)) {
            tty->stopped = 0;
            cy_write(tty);
    }

    if (!(old_termios->c_cflag & CLOCAL) &&
        (tty->termios->c_cflag & CLOCAL))
            wake_up_interruptible(&info->open_wait);

    /* if (I_INPCK(tty)) gotta do something !!! */
    return;
} /* cy_set_termios */


static void
cy_close(struct tty_struct * tty, struct file * filp)
{
  struct cyclades_port * info;
  int line;

    line = DEV_TO_CY(tty->line);
    if ((line < 0) || (line >= NR_PORTS)){
            return;
    }
    info = &cy_port[line];
#ifdef SERIAL_DEBUG_OPEN
    printk("cy_close ttyC%d, count = %d\n", info->line, info->count);/**/
#endif
    if (--info->count > 0){
            return;
    }
    tty->stopped = 0;           /* Force flush to succeed */
    cy_start(tty);
    wait_until_sent(tty,0);
    clear_bit(line, cy_event);
    info->event = 0;
    info->count = 0;

    if (info->blocked_open) {
            shutdown(info);
            if (info->close_delay) {
                    tty->count++; /* avoid race condition */
                    current->state = TASK_INTERRUPTIBLE;
                    current->timeout = jiffies + info->close_delay;
                    schedule();
                    tty->count--;
            }
            startup(info);
            info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
            if (tty->termios->c_cflag & CLOCAL){
                    wake_up_interruptible(&info->open_wait);
            }
            return;
    }
    if (info->flags & ASYNC_INITIALIZED) {
            shutdown(info);
    }
    info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
    info->tty = 0;

    /*printk("cy_close done\n");*/
    return;
} /* cy_close */


/* This is called whenever a port's characteristics are likely
    to have been changed, e.g., from an open or close. */
static void
startup(struct cyclades_port * info)
{
  unsigned long flags;
  unsigned char *base_addr;
  int card,chip,channel;

        card = info->card;
        channel = (info->line) - (cy_card[card].first_line);
        chip = channel>>2;
        channel &= 0x03;
        base_addr = (unsigned char*)
                       (cy_card[card].base_addr + chip * CyRegSize);

#ifdef nodef
        printk("startup card %d, chip %d, channel %d, base_addr %lx",
             card, chip, channel, (long)base_addr);/**/
#endif

        save_flags(flags); cli();
            *(base_addr + CyCAR)=(uchar)channel;

           /* tx and rx baud rate */

            *(base_addr + CyTCOR) = info->tco;
            *(base_addr + CyTBPR) = info->tbpr;
            *(base_addr + CyRCOR) = info->rco;
            *(base_addr + CyRBPR) = info->rbpr;
            
            /* set line characteristics  according configuration */

            *(base_addr + CySCHR1) = START_CHAR(info->tty);
            *(base_addr + CySCHR2) = STOP_CHAR(info->tty);
            *(base_addr + CyCOR1) = info->cor1;
            *(base_addr + CyCOR2) = info->cor2;
            *(base_addr + CyCOR3) = info->cor3;
            *(base_addr + CyCOR4) = info->cor4;
            *(base_addr + CyCOR5) = info->cor5;

            write_cmd(base_addr,CyCOR_CHANGE|CyCOR1ch|CyCOR2ch|CyCOR3ch);

            *(base_addr + CyCAR)=(uchar)channel;

            *(base_addr + CyRTPR) = 0x20; /* 32ms rx timeout */

            if (C_LOCAL(info->tty)) {
                info->IER = 0;       /* without modem intr */
                                        /* ignore 1->0 modem transitions */
                *(base_addr + CyMCOR1) = 0x0;
                                        /* ignore 0->1 modem transitions */
                *(base_addr + CyMCOR2) = 0x0;
            } else {
                info->IER = CyMdmCh; /* with modem intr */
                                        /* act on 1->0 modem transitions */
                *(base_addr + CyMCOR1) = CyDSR|CyCTS|CyRI|CyDCD;
                                        /* act on 0->1 modem transitions */
                *(base_addr + CyMCOR2) = CyDSR|CyCTS|CyRI|CyDCD;
            }

            write_cmd(base_addr,CyCHAN_CTL|CyENB_RCVR|CyENB_XMTR);

            *(base_addr + CyCAR)=(uchar)channel;

            *(base_addr + CyMSVR1) = CyRTS;
            *(base_addr + CyMSVR2) = CyDTR;
            *(base_addr + CySRER) = CyRxData | CyTxRdy | info->IER;
            info->flags |= ASYNC_INITIALIZED;
        restore_flags(flags);
#ifdef nodef
    printk(" done\n");/**/
#endif
    return;
} /* startup */


/*
 * 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;
  unsigned char *base_addr;
  int card,chip,channel;

        card = info->card;
        channel = info->line - cy_card[card].first_line;
        chip = channel>>2;
        channel &= 0x03;
        base_addr = (unsigned char*)
                       (cy_card[card].base_addr + chip * CyRegSize);
    /*printk("shutdown card %d, chip %d, channel %d, base_addr %x",
         card, chip, channel, base_addr);*/

        save_flags(flags); cli();
            *(base_addr + CyCAR)=(uchar)channel;

            *(base_addr + CyMSVR1) = 0x0; /* clear RTS */
            *(base_addr + CyMSVR2) = 0x0; /* clear DTR */
            write_cmd(base_addr,CyCHAN_CTL|CyDIS_RCVR|CyDIS_XMTR);
            info->flags &= ~ASYNC_INITIALIZED;
        restore_flags(flags);
    /*printk(" done\n");*/
    return;
} /* shutdown */


/*
 * ------------------------------------------------------------
 * cy_open() and friends
 * ------------------------------------------------------------
 */

static int
block_til_ready(struct tty_struct *tty, struct file * filp,
                           struct cyclades_port *info)
{
  struct wait_queue wait = { current, NULL };
  int           retval;
  int           do_clocal = C_LOCAL(tty);
  struct termios        orig_termios;
  int           tty_line = tty->line;
  char *base_addr;
  int chip,channel;
  struct cyclades_card *cinfo;

        /*
         * If this is a callout device, then just make sure the normal
         * device isn't being used.
         */
        if (MAJOR(filp->f_rdev) == 5) {
                if (info->flags & ASYNC_NORMAL_ACTIVE){
                        return -EBUSY;
                }
                if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
                    (info->flags & ASYNC_SESSION_LOCKOUT) &&
                    (info->session != current->session)){
                    return -EBUSY;
                }
                if ((info->flags & ASYNC_CALLOUT_ACTIVE) &&
                    (info->flags & ASYNC_PGRP_LOCKOUT) &&
                    (info->pgrp != current->pgrp)){
                    return -EBUSY;
                }
                info->flags |= ASYNC_CALLOUT_ACTIVE;
                return 0;
        }
        
        /*
         * If non-blocking mode is set, then make the check up front
         * and then exit.
         */
        if (filp->f_flags & O_NONBLOCK) {
                if (info->flags & ASYNC_CALLOUT_ACTIVE){
                        return -EBUSY;
                }
                info->flags |= ASYNC_NORMAL_ACTIVE;
                return 0;
        }

        /*
         * Block waiting for the carrier detect and the line to become
         * free (i.e., not in use by the callout).  While we are in
         * this loop, info->count is dropped by one, so that
         * cy_close() knows when to free things.  We restore it upon
         * exit, either normal or abnormal.
         */
        retval = 0;
        add_wait_queue(&info->open_wait, &wait);
#ifdef SERIAL_DEBUG_OPEN
        printk("block_til_ready before block: ttyC%d, count = %d\n",
               info->line, info->count);/**/
#endif
        info->count--;
        info->blocked_open++;
        memset(&orig_termios, 0, sizeof(orig_termios));
        if (tty_termios[tty_line])
                orig_termios = *tty_termios[tty_line];
        cinfo = &cy_card[info->card];
        channel = info->line - cinfo->first_line;
        chip = channel>>2;
        channel &= 0x03;
        base_addr = (char *) (cinfo->base_addr + chip * CyRegSize);
        while (1) {
                cli();
                if (!(info->flags & ASYNC_CALLOUT_ACTIVE)){
                        *(base_addr + CyCAR)=(uchar)channel;
                        *(base_addr + CyMSVR1) = CyRTS;
                        *(base_addr + CyMSVR2) = CyDTR;
                }
                sti();
                current->state = TASK_INTERRUPTIBLE;
                if (tty_hung_up_p(filp) && (info->flags & ASYNC_HUP_NOTIFY)) {
                        retval = -EAGAIN;
                        break;
                }
                cli();
                *(base_addr + CyCAR)=(uchar)channel;
                if (!(info->flags & ASYNC_CALLOUT_ACTIVE) &&
                    (do_clocal || (*(base_addr + CyMSVR1) & CyDCD))) {
                        if (tty_hung_up_p(filp))
                                retval = -ERESTARTNOINTR;
                        sti();
                        break;
                }
                sti();
                if (current->signal & ~current->blocked) {
                        retval = -ERESTARTSYS;
                        break;
                }
#ifdef SERIAL_DEBUG_OPEN
                printk("block_til_ready blocking: ttys%d, count = %d\n",
                       info->line, info->count);/**/
#endif
                schedule();
        }
        current->state = TASK_RUNNING;
        remove_wait_queue(&info->open_wait, &wait);
        info->count++;
        info->blocked_open--;
#ifdef SERIAL_DEBUG_OPEN
        printk("block_til_ready after blocking: ttys%d, count = %d\n",
               info->line, info->count);/**/
#endif
        if (retval)
                return retval;
        info->flags |= ASYNC_NORMAL_ACTIVE;
        if ((info->flags & ASYNC_SPLIT_TERMIOS) &&
            tty_termios[tty_line])
                *tty_termios[tty_line] = orig_termios;
        return 0;
} /* block_til_ready */ 

/*
 * This routine is called whenever a serial port is opened.  It
 * performs the serial-specific initalization for the tty structure.
 */
int
cy_open(struct tty_struct *tty, struct file * filp)
{
  struct cyclades_port  *info;
  int retval, line;

    line = DEV_TO_CY(tty->line);
    if ((line < 0) || (line >= NR_PORTS)){
        if(0){
	    show_status(3);
	    show_status(4);
        }
        return -ENODEV;
    }
    info = &cy_port[line];
    if((info->line != line) || (info->card == -1)){
        return -ENODEV;
    }
#ifdef SERIAL_DEBUG_OPEN
    printk("cy_open ttyC%d, count = %d\n", info->line, info->count);/**/
#endif
    info->count++;
    info->tty = tty;
    
    tty->write = cy_write;
    tty->close = cy_close;
    tty->ioctl = cy_ioctl;
    tty->throttle = cy_throttle;
    tty->set_termios = cy_set_termios;
    tty->stop = cy_stop;
    tty->start = cy_start;

    if (!(info->flags & ASYNC_INITIALIZED)) {
        config_setup(info);
        startup(info);
    }

    retval = block_til_ready(tty, filp, info);
    if (retval)
        return retval;

    info->session = current->session;
    info->pgrp = current->pgrp;

#ifdef SERIAL_DEBUG_OPEN
    printk("cy_open done\n");/**/
#endif
/* CP('O'); */
    return 0;
} /* cy_open */


/*
 * ---------------------------------------------------------------------
 * cy_init() and friends
 *
 * cy_init() is called at boot-time to initialize the serial driver.
 * ---------------------------------------------------------------------
 */

/*
 * This routine prints out the appropriate serial driver version
 * number, and identifies which options were configured into this
 * driver.
 */
static void
show_version(void)
{
    printk("Cyclades driver\n  %s\n",rcsid);
} /* show_version */

/* initialize chips on card -- return number of valid
   chips (which is number of ports/4) */
int
cy_init_card(unsigned char *base_addr)
{
  volatile unsigned short discard;
  volatile unsigned short  i,j;
  unsigned char *reg_ptr;
  unsigned int chip_number;

    discard = *(base_addr + Cy_HwReset); /* Cy_HwReset is 0x1400 */
    /* small_delay(about_10us); */
    discard = *(base_addr + Cy_ClrIntr); /* Cy_ClrIntr is 0x1800 */
    small_delay(about_500us);

    for(chip_number=0; chip_number<CyMaxChipsPerCard; chip_number++){
        reg_ptr = base_addr + CyCCR;
        conditional_delay(about_1000us,(*reg_ptr == 0x00));
        if(i == about_1000us){
            printk(" chip #%d at %#6lx is never idle (CCR != 0)\n",
               chip_number, (unsigned long)base_addr);
            return chip_number;
        }

        *(base_addr + CyGFRCR ) = 0;
        small_delay(about_10us);

        *reg_ptr = CyCHIP_RESET;
        small_delay(about_10us);

        reg_ptr = base_addr + CyGFRCR;
        conditional_delay(about_1000us,(*reg_ptr != 0x00));
        if(i==about_1000us){
            printk(" chip #%d at %#6lx is not responding (GFRCR stayed 0)\n",
               chip_number, (unsigned long)base_addr);
            return chip_number;
        }
        if((0xf0 & *reg_ptr) != 0x40){
            printk(" chip #%d at %#6lx is not valid (GFRCR == %#2x)\n",
               chip_number, (unsigned long)base_addr, *reg_ptr);
            return chip_number;
        }
        *(base_addr + CyGCR) = CyCH0_SERIAL;
        *(base_addr + CyPPR) = CyCLOCK_25_1MS;

        printk(" chip #%d at %#6lx is rev 0x%2x\n",
               chip_number, (unsigned long)base_addr, *reg_ptr);

        /* advance to next chip on card */
        base_addr += CyRegSize;
    }

    return chip_number;
} /* cy_init_card */

/* The serial driver boot-time initialization code!
    Hardware I/O ports are mapped to character special devices on a
    first found, first allocated manner.  That is, this code searches
    for Cyclades cards in the system.  As each is found, it is probed
    to discover how many chips (and thus how many ports) are present.
    These ports are mapped to the tty ports 32 and upward in monotonic
    fashion.  If an 8-port card is replaced with a 16-port card, the
    port mapping on another card will shift.

    This approach is different from what is used in the other serial
    device driver because the Cyclades is more properly a multiplexer,
    not just an aggregation of serial ports on one card.

    If there are more cards with more ports than have been statically
    allocated above, a warning is printed and the extra ports are ignored.
 */
long
cy_init(long kmem_start)
{
    struct cyclades_port *info;
    struct cyclades_card *cinfo;
    int good_ports = 0;
    int port_num = 0;
    int index;
    struct sigaction    sa;
    int retval;
        
scrn[1] = '\0';
        show_version();
        
        memset(&cy_event, 0, sizeof(cy_event));
        bh_base[CYCLADES_BH].routine = do_softint;

        for (index = 0; index < 16; index++) {
                IRQ_cards[index] = 0;
        }

        port_num = 0;
        info = cy_port;
        for (index = 0, cinfo = cy_card; index < NR_CARDS; index++,cinfo++) {
            /*** initialize card ***/
            if(0 == (cinfo->num_chips =
                        cy_init_card((unsigned char *)cinfo->base_addr))){
                /* this card is not present */
                continue;
            }
            printk("  share IRQ %d: ", cinfo->irq);
            good_ports = 4 * cinfo->num_chips;

            /** bind IRQ to card **/
            if (cinfo->irq && (IRQ_cards[cinfo->irq] == 0)) {
                    IRQ_cards[cinfo->irq] = cinfo;
                    sa.sa_handler = cy_interrupt;
                    sa.sa_flags = (SA_INTERRUPT);
                    sa.sa_mask = 0;
                    sa.sa_restorer = NULL;
                    retval = irqaction(cinfo->irq,&sa);
                    if (retval)
                            return retval;
            }
            if(port_num < NR_PORTS){
                cinfo->first_line = port_num;
                while( good_ports-- && port_num < NR_PORTS){
                    /*** initialize port ***/
                    info->card = index;
                    info->line = port_num;
                    info->flags = STD_COM_FLAGS;
                    info->tty = 0;
                    /* info->read_status_mask */
                    /* info->timeout */
                    /* info->xmit_fifo_size */
                    info->cor1 = CyPARITY_NONE|Cy_1_STOP|Cy_8_BITS;
                    info->cor2 = 0;
                    info->cor3 = 1; /* _very_ small receive threshold */
                    info->cor4 = 0;
                    info->cor5 = 0;
                    info->tbpr = baud_bpr[13]; /* Tx BPR */
                    info->tco = baud_co[13]; /* Tx CO */
                    info->rbpr = baud_bpr[13]; /* Rx BPR */
                    info->rco = baud_co[13]; /* Rx CO */
                    info->close_delay = 50;
                    info->IER = 0;
                    info->event = 0;
                    info->count = 0;
                    info->x_char = 0;
                    info->blocked_open = 0;
                    /* info->session */
                    /* info->pgrp */
                    info->open_wait = 0;

                    printk("ttyC%1d ", info->line);

                    port_num++;info++;
                }
            }else{
                cinfo->first_line = -1;
            }
            printk("\n");
        }
        while( port_num < NR_PORTS){
            info->line = -1;
            port_num++;info++;
        }
        return kmem_start;
} /* cy_init */

static void
show_status(int line_num)
{
  unsigned char *base_addr;
  int card,chip,channel;
  struct cyclades_port * info;
  unsigned long flags;

    printk("status via cy_open\n");

    info = &cy_port[line_num];
    card = info->card;
    channel = (info->line) - (cy_card[card].first_line);
    chip = channel>>2;
    channel &= 0x03;
    printk("  card %d, chip %d, channel %d\n", card, chip, channel);/**/

    printk(" cy_card\n");
    printk("  irq base_addr num_chips first_line = %d %lx %d %d\n",
           cy_card[card].irq, (long)cy_card[card].base_addr,
           cy_card[card].num_chips, cy_card[card].first_line);

    printk(" cy_port\n");
    printk("  card line flags = %d %d %x\n",
                 info->card, info->line, info->flags);
    printk("  *tty read_status_mask timeout xmit_fifo_size = %lx %x %x %x\n",
                 (long)info->tty, info->read_status_mask,
                 info->timeout, info->xmit_fifo_size);
    printk("  cor1,cor2,cor3,cor4,cor5 = %x %x %x %x %x\n",
             info->cor1, info->cor2, info->cor3, info->cor4, info->cor5);
    printk("  tbpr,tco,rbpr,rco = %d %d %d %d\n",
             info->tbpr, info->tco, info->rbpr, info->rco);
    printk("  close_delay IER event count = %d %x %d %d\n",
             info->close_delay, info->IER, info->event, info->count);
    printk("  x_char blocked_open = %x %x\n",
             info->x_char, info->blocked_open);
    printk("  session pgrp open_wait = %lx %lx %lx\n",
             info->session, info->pgrp, (long)info->open_wait);


    save_flags(flags); cli();

	base_addr = (unsigned char*)
		       (cy_card[card].base_addr + chip * CyRegSize);

/* Global Registers */

	printk(" CyGFRCR %x\n", *(base_addr + CyGFRCR));
	printk(" CyCAR %x\n", *(base_addr + CyCAR));
	printk(" CyGCR %x\n", *(base_addr + CyGCR));
	printk(" CySVRR %x\n", *(base_addr + CySVRR));
	printk(" CyRICR %x\n", *(base_addr + CyRICR));
	printk(" CyTICR %x\n", *(base_addr + CyTICR));
	printk(" CyMICR %x\n", *(base_addr + CyMICR));
	printk(" CyRIR %x\n", *(base_addr + CyRIR));
	printk(" CyTIR %x\n", *(base_addr + CyTIR));
	printk(" CyMIR %x\n", *(base_addr + CyMIR));
	printk(" CyPPR %x\n", *(base_addr + CyPPR));

	*(base_addr + CyCAR)=(uchar)channel;

/* Virtual Registers */

	printk(" CyRIVR %x\n", *(base_addr + CyRIVR));
	printk(" CyTIVR %x\n", *(base_addr + CyTIVR));
	printk(" CyMIVR %x\n", *(base_addr + CyMIVR));
	printk(" CyMISR %x\n", *(base_addr + CyMISR));

/* Channel Registers */

	printk(" CyCCR %x\n", *(base_addr + CyCCR));
	printk(" CySRER %x\n", *(base_addr + CySRER));
	printk(" CyCOR1 %x\n", *(base_addr + CyCOR1));
	printk(" CyCOR2 %x\n", *(base_addr + CyCOR2));
	printk(" CyCOR3 %x\n", *(base_addr + CyCOR3));
	printk(" CyCOR4 %x\n", *(base_addr + CyCOR4));
	printk(" CyCOR5 %x\n", *(base_addr + CyCOR5));
	printk(" CyCCSR %x\n", *(base_addr + CyCCSR));
	printk(" CyRDCR %x\n", *(base_addr + CyRDCR));
	printk(" CySCHR1 %x\n", *(base_addr + CySCHR1));
	printk(" CySCHR2 %x\n", *(base_addr + CySCHR2));
	printk(" CySCHR3 %x\n", *(base_addr + CySCHR3));
	printk(" CySCHR4 %x\n", *(base_addr + CySCHR4));
	printk(" CySCRL %x\n", *(base_addr + CySCRL));
	printk(" CySCRH %x\n", *(base_addr + CySCRH));
	printk(" CyLNC %x\n", *(base_addr + CyLNC));
	printk(" CyMCOR1 %x\n", *(base_addr + CyMCOR1));
	printk(" CyMCOR2 %x\n", *(base_addr + CyMCOR2));
	printk(" CyRTPR %x\n", *(base_addr + CyRTPR));
	printk(" CyMSVR1 %x\n", *(base_addr + CyMSVR1));
	printk(" CyMSVR2 %x\n", *(base_addr + CyMSVR2));
	printk(" CyRBPR %x\n", *(base_addr + CyRBPR));
	printk(" CyRCOR %x\n", *(base_addr + CyRCOR));
	printk(" CyTBPR %x\n", *(base_addr + CyTBPR));
	printk(" CyTCOR %x\n", *(base_addr + CyTCOR));

    restore_flags(flags);
} /* show_status */
