/*
 * This code is (C) 1995 Jes Sorensen (jds@kom.auc.dk).
 *
 * This is a driver for the GVP IO-Extender (duart, midi, parallel
 * board) for the Amiga (so far only the serial part is implemented).
 *
 * The code is partly based on Roman's Atari drivers, Hamish'
 * driver the internal Amiga port and the PC drivers.
 *
 * The board info was kindly supplied by Erick Quackenbush, former GVP
 * employee - thanks. The missing info on about the "secret" register
 * was obtained from the GVP SCSI driver though.
 *
 * Thanks to Niels Pontoppidan (neil@dannug.dk), for letting me borrow
 * his IO-Extender, and thus making it possible for me to do this
 * driver. Also thanks to Ralph Babel and Roman Hodek for a few hints
 * and to Michael Goodwin for doing all the beta-testing - especially
 * after I returned the board I borrowed from Niels.
 *
 * If you can use this code (or parts of it) - feel free to copy it,
 * but please let me know what parts you can use and where you use it.
 *
 * As the code probably isn't bug-free yet, I would also like to know
 * if you discover any bugs.
 *
 * Facts: 
 *
 * The tty-assignment is done dynamically so only the needed number of
 * tty's is allocated, leaving room for other serial boards in the
 * system. This needed some changes in serial.c aswell as the other
 * drivers, but I do find this the best way to handle things - I hate
 * static allocation!
 *
 * Note: To use the serial ports at rates higher than 38400, you can
 * compile the driver with IOEXT_FAST_RATES = 1, and then select
 * one of these rates:
 *
 * 300 = 57600, 1200 = 115200
 *
 * The driver should be able to handle multiple IO-Extenders, but I
 * haven't been able to test this as I only got access to one board at
 * the moment. The number of extenders is limited to five (same as the
 * maximum number of Zorro slots) but if you wan't to use more than
 * two you should change "NR_PORTS" in serial.h to fit your needs.
 *
 * 07/13/95: Initial version - the code is definately _not_ perfect
 *           yet - it doesn't even work :-(
 * 10/01/95: First version, it seems to work pretty well, even at
 *           serial-rates higher than 19200.
 *           There is still one major problem, as I get lotsa overruns
 *           when the harddisk is accessed during receives. This is
 *           probably caused by the SCSI-driver running too long with
 *           interrupts off, so I'm not sure there is anything I can
 *           do about it in here.
 * 10/04/95: Added support for higher (57k6 and 115k2) baud-rates when
 *           the ASYNC_SPD_{HI,VHI} flags are set.
 * 95/10/19: Adapted to 1.2 (Andreas Schwab).
 *	     Compiles, but otherwise untestet!
 * 95/11/26: Updated for 1.2.13pl3 - updated the GVP detection aswell.
 * 96/01/17: Hopefully solved the GVP Zorro detection problem in
 *           1.2.13pl4 that occoured when a user had several GVP
 *           boards installed. (Jes)
 * 96/02/07: The driver actually decreases info->xmit_cnt-- now
 *           whenever a character is transmitted, so we don't just
 *           transmit the same char now. This is the reason why this
 *           driver hasn't worked under 1.2 so far. Thanks to Murray
 *           Bennett for this, found it in his patches.
 */

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/malloc.h>
#include <linux/termios.h>
#include <linux/tty.h>
#include <linux/serial.h>

#include <asm/bootinfo.h>
#include <asm/irq.h>
#include <asm/amigahw.h>
#include <asm/amigaints.h>
#include <asm/zorro.h>
#include <asm/amigatypes.h>

#include "io-extender.h"

#define IOEXT_DEBUG 0 /* Remove this when the driver is finished */

#define IOEXT_FAST_RATES 1   /*
			      * When this is set, the rates 110 and
			      * 134 will run 57600 and 115200
			      * respectively.
			      */

#define FIFO_TRIGGER_LEVEL FIFO_TRIG_8
                             /*
			      * There are 4 receiver FIFO-interrupt *
			      * trigger levels (FIFO_TRIG_x), that  *
			      * indicates how many bytes are to be  *
			      * allowed in the receiver-FIFO before *
			      * an interrupt is generated:          *
			      *                x =  1 =  1 byte	    *
			      *                x =  4 =  4 bytes    *
			      *                x =  8 =  8 bytes    *
			      *                x = 14 = 14 bytes    *
			      * 14 works for me, but if you keep    *
			      * getting overruns try lowering this  *
			      * value one step at a time.           *
			      * I left it at 8 bytes, just to be on *
			      * the safe side.                - Jes *
			      */


/***************************** Prototypes *****************************/
static void ser_init( struct async_struct *info );
static void ser_deinit( struct async_struct *info, int leave_dtr );
static void ser_enab_tx_int( struct async_struct *info, int enab_flag );
static int  ser_check_custom_divisor(struct async_struct *info,
				     int baud_base, int divisor);
static void ser_change_speed( struct async_struct *info );
static void ser_throttle( struct async_struct *info, int status );
static void ser_set_break( struct async_struct *info, int break_flag );
static void ser_get_serial_info( struct async_struct *info,
				struct serial_struct *retinfo );
static unsigned int ser_get_modem_info( struct async_struct *info );
static int ser_set_modem_info( struct async_struct *info, int new_dtr,
			      int new_rts );
static void ser_stop_receive(struct async_struct *info);
static int ser_trans_empty(struct async_struct *info);
/************************* End of Prototypes **************************/

/*
 * SERIALSWITCH structure for the GVP IO-Extender serial-board.
 */

static SERIALSWITCH ioext_ser_switch = {
    ser_init,
    ser_deinit,
    ser_enab_tx_int,
    ser_check_custom_divisor,
    ser_change_speed,
    ser_throttle,
    ser_set_break,
    ser_get_serial_info,
    ser_get_modem_info,
    ser_set_modem_info,
    NULL,
    ser_stop_receive, ser_trans_empty, NULL
};

static int ioext_baud_table[18] = {
 	/* B0     */ 0, /* Newer use this value !!! */
	/* B50    */ 9216,
	/* B75    */ 6144,
	/* B110   */ 4189, /* There's a little rounding on this one */
	/* B134	  */ 3439, /* Same here! */
	/* B150	  */ 3072,
	/* B200	  */ 2304,
#if IOEXT_FAST_RATES
	/* B57600 */ 8,
#else
	/* B300	  */ 1536,
#endif
	/* B600	  */ 768,
#if IOEXT_FAST_RATES
	/* B115k2 */ 4,
#else
	/* B1200  */ 384,
#endif
	/* B1800  */ 256,
	/* B2400  */ 192,
	/* B4800  */ 96,
	/* B9600  */ 48,
	/* B19200 */ 24,
	/* B38400 */ 12,   /* The last of the standard rates.  */
	/* B57600 */ 8,    /* ASYNC_SPD_HI                     */
	/* B115K2 */ 4     /* ASYNC_SPD_VHI                    */
};


static u_char isr_entered = 0;

/* Number of detected ports.  */
static int num_ioext = 0;

/*
 * Functions
 */

static void ser_interrupt(int ignore, struct intframe *fp,
			  struct async_struct *info)
{
  u_char i, irq, reg, err;
  u_short intenar;
  int ch;
  struct uart_16c550 *uart;


  /* Prevent re-entrance of the isr */
  intenar = (custom.intenar & IF_EXTER);
  custom.intena = IF_EXTER;

  if(isr_entered){
    printk("Re-entering IO-Extender isr.\n");
    custom.intena = (intenar | IF_SETCLR);
    return;
  }
  isr_entered = 1;

  custom.intena = (intenar | IF_SETCLR);


  irq = 0; /* Marks if there where any IRQ's on the GVP boards */


  for (i = 0; i < num_ioext; i++){

    /* Check if any irq is pending for the current board.            */
    /* If there aren't any IRQ from the board, there is no reason to */
    /* check any of the uarts, and so (nr_uarts-1) is added to i (-1 */
    /* because the "for"-loop automatically does i++ for each loop). */
    /* Note: The IRQ_PEND-bit=0 if an irq is pending.                */

    reg = ((IOEXT_struct *) info[i].board_base)->CNTR;
    if(!(reg & GVP_IRQ_PEND)){
      i = i+info[i].nr_uarts-1;
      continue;
    }

    uart = (struct uart_16c550 *) info[i].port;

#if 0
    printk("Checking uart at base-address 0x%08x, using ttyS%i\n",
	   (u_int) uart, i);
#endif


    reg = uart->IIR;

    if (reg & IRQ_PEND)
      continue;

    irq++; /* Mark that an IRQ occoured on this uart */

#if 0
    printk("GVP IRQ occoured on ttyS%i...\n", i);
#endif

    switch (reg & (IRQ_ID1 | IRQ_ID2 | IRQ_ID3)){
      /*
       * "Receiver Line Status"-interrupt (ie. something went wrong).
       */
    case IRQ_RLS:
#if 0
      printk("RLS-irq on ttyS%i\n", i);
#endif
      reg = uart->LSR;
      if (reg & BI){
	err = TTY_BREAK;
#if IOEXT_DEBUG
	printk("Break interrupt for ttyS%i.\n", i);
#endif
      }else
      if (reg & PE){
	err = TTY_PARITY;
#if IOEXT_DEBUG
	printk("Parity error for ttyS%i.\n", i);
#endif
      }else
      if (reg & FE){
	err = TTY_FRAME;
#if IOEXT_DEBUG
	printk("Framing error for ttyS%i.\n", i);
#endif
      }else
      if (reg & OE){
	err = TTY_OVERRUN;
#if IOEXT_DEBUG
	printk("Overrun error for ttyS%i.\n", i);
#endif
      }else{
	printk("Spurious `receiver line status'-interrupt on ttyS%i\n", i);
	break;
      }

      ch = uart->RBR;
      if (!(&info[i].tty == NULL)){
	rs_receive_char(&info[i], ch, err);
      }
      break;

      /*
       * Received Data Available or Charater Time-out Indication
       */
    case IRQ_RDA: case IRQ_CTI:
#if 0
      printk("RDA or CTI-irq on ttyS%i\n", i);
#endif
      /*
       * Copy the contents of the entire fifo to the tty-queue ...
       * we have to be careful that we aren't passing one of the
       * Receiver Line Status interrupt-conditions without noticing
       * though.
       */
      err = 0;
      while (((reg = uart->LSR) & DR) && (!(reg & (PE|FE|OE|BI)))){
	ch = uart->RBR;
#if 0
	printk("Read a char from the uart: %02x.\n", ch);
#endif
	rs_receive_char(&info[i], ch, err);
      }
      break;

      /*
       * Transmitter Holding Register Empty
       */
    case IRQ_THRE:
#if 0
      printk("THRE-irq for ttyS%i\n", i);
#endif
      /* If the uart is ready to receive data and there are chars in */
      /* the queue we transfer all we can to the uart's FIFO         */
      while (uart->LSR & THRE && (ch = rs_get_tx_char(&info[i])) >= 0){
#if 0
	printk("Uart is ready to receive data.\n");
#endif
	uart->THR = ch;
      }
      
      if (rs_no_more_tx(&info[i]))
	{
#if 0
	  printk("No data in the queue.\n");
#endif
	  /* Disable transmitter empty interrupt */
	  uart->IER &= ~(ETHREI);
	}
      break;

      /*
       * Modem Status Register
       */
    case IRQ_MS:
      reg = uart->MSR;
#if 0
      if (!(reg & (DCTS | DDCD)))
	printk("MS-irq for ttyS%i: %02x\n", i, reg);
#endif

      if (info->flags & ASYNC_INITIALIZED){
	if (reg & DCTS)
	  rs_check_cts(&info[i], !(reg & CTS)); /* active low */
	if (reg & DDCD)
	  rs_dcd_changed(&info[i], !(reg & DCD)); /* active low */
      }
      break;

    default:
      printk("Spurious IRQ on ttyS%i\n", i);
    }

#if 0
    reg = board->uart0.IIR;
    if (!(reg & IRQ_PEND))
      printk("IRQ on IO-Extender %i, uart 0.\n", i);
    else
      printk("IRQ on IO-Extender %i, uart 1.\n", i);
#endif

  }

  if (irq){
#if 0
    printk("Leaving ISR\n");
#endif
    custom.intreq = IF_EXTER;
  }

  /* Re-allow the isr to be entered */
  isr_entered = 0;
};

static void ser_init(struct async_struct *info){

#if 0
  printk("ser_init\n");
#endif

  while ((curruart(info)->LSR) & DR){
#if IOEXT_DEBUG
    printk("Emptying uart\n");
#endif
    (void)curruart(info)->RBR;
  }

  /* Set DTR and RTS */
  curruart(info)->MCR |= (DTR | RTS | OUT2);

  custom.intena = IF_EXTER;

  /* Enable interrupts - we expect the IF_EXTER irq to be enabled */
  curruart(info)->IER |= (ETHREI | ERDAI | ELSI | EMSI);
  custom.intena = (IF_EXTER | IF_SETCLR);
};


static void ser_deinit(struct async_struct *info, int leave_dtr){

  u_short intenar;

#if 0
  printk("ser_deinit\n");
#endif

  /* Wait for the uart to get empty */
  while(!(curruart(info)->LSR & TEMT))
#if IOEXT_DEBUG
    printk("Waiting for the transmitter to finish\n");
#endif
    ;
  while(curruart(info)->LSR & DR){
#if IOEXT_DEBUG
    printk("Uart not empty - flushing!\n");
#endif
    (void)curruart(info)->RBR;
  }
  intenar = (custom.intenar & IF_EXTER);
  custom.intena = IF_EXTER;

  /* Disable interrupts - again only the uart registers are changed */

  curruart(info)->IER &= ~(ETHREI | ERDAI | ELSI | EMSI);
  custom.intena = (intenar | IF_SETCLR);


  ser_RTSoff(info);
  if (!leave_dtr)
    ser_DTRoff(info);

};

static void ser_enab_tx_int(struct async_struct *info, int enab_flag){

  unsigned long flags;
  int ch;

#if 0
  printk("ser_enab_tx - ");
#endif
  if (enab_flag){
#if 0
    printk("enabling_tx\n");
#endif

    /*
     * Someone actually tried to solve the problem with the
     * "transmitter holding register empty"-interrupt not being
     * generated when re-enabling the tx-interrupt, by calling
     * ser_interrupt. This was done by the restart-function in the 0.9
     * kernel, but now it has to be done manually. Unfortunately
     * calling ser_interrupt doesn't help, as the first done by this
     * function is to check if an interrupt actually was generated by
     * the board and leave at once, if not.
     */

    /*
     * If the uart is ready to receive data and there are chars in
     * the queue we transfer all we can to the uart's FIFO.
     */

    save_flags(flags);
    curruart(info)->IER |= ETHREI;
    cli();

    while (curruart(info)->LSR & THRE && ((ch = rs_get_tx_char( info )) >= 0))
	curruart(info)->THR = ch;
    if (rs_no_more_tx( info ))
	/* disable tx interrupts */
	curruart(info)->IER &= ~(ETHREI);

    restore_flags(flags);
  }else{
#if 0
  printk("disabling_tx\n");
#endif
    curruart(info)->IER &= ~(ETHREI);
  }
};


static int  ser_check_custom_divisor(struct async_struct *info,
				     int baud_base, int divisor)
{

#if 0
  printk("ser_check_custom_divisor\n");
#endif
  if (divisor != 0)
    return 0;
  else
    return 1;
};

static void ser_change_speed(struct async_struct *info){

  u_int cflag, baud, chsize, stopb, parity, aflags;
  u_int div = 0, ctrl = 0;

#if 0
  printk("ser_change_speed\n");
#endif

  if (!info->tty || !info->tty->termios) return;

  cflag  = info->tty->termios->c_cflag;
  baud   = cflag & CBAUD;
  chsize = cflag & CSIZE;
  stopb  = cflag & CSTOPB;
  parity = cflag & (PARENB | PARODD);
  aflags = info->flags & ASYNC_SPD_MASK;

  if (cflag & CRTSCTS)
      info->flags |= ASYNC_CTS_FLOW;
  else
      info->flags &= ~ASYNC_CTS_FLOW;
  if (cflag & CLOCAL)
      info->flags &= ~ASYNC_CHECK_CD;
  else
      info->flags |= ASYNC_CHECK_CD;

#if 0
  printk("Changing to baud-rate %i\n", baud);
#endif

  if (baud & CBAUDEX) {
    baud &= ~CBAUDEX;
    if (baud < 1 || baud > 2)
      info->tty->termios->c_cflag &= ~CBAUDEX;
    else
      baud += 15;
  }
  if (baud == 15){
    if (aflags == ASYNC_SPD_HI)   /*  57k6 */
      baud += 1;
    if (aflags == ASYNC_SPD_VHI)  /* 115k2 */
      baud += 2;
    if (aflags == ASYNC_SPD_CUST)
      div = info->custom_divisor;
  }
  if (!div){
      /* Maximum speed is 115200 */
      if (baud > 17) baud = 17;
      div = ioext_baud_table[baud];
  }

  if (!div) {
    /* speed == 0 -> drop DTR */
#if 0
    printk("Dropping DTR\n");
#endif
    ser_DTRoff(info);
    return;
  }

  /*
   * We have to set DTR when a valid rate is chosen, otherwise DTR
   * might get lost when programs use this sequence to clear the line:
   *
   * change_speed(baud = B0);
   * sleep(1);
   * change_speed(baud = Bx); x != 0
   *
   * The pc-guys do this aswell.
   */
  ser_DTRon(info);

  if (chsize == CS8){
#if 0
    printk("Setting serial word-length to 8-bits\n");
#endif
    ctrl |= data_8bit;
  } else if (chsize == CS7){
#if 0
    printk("Setting serial word-length to 7-bits\n");
#endif
    ctrl |= data_7bit;
  } else if (chsize == CS6){
#if 0
    printk("Setting serial word-length to 6-bits\n");
#endif
    ctrl |= data_6bit;
  } else if (chsize == CS5){
#if 0
    printk("Setting serial word-length to 5-bits\n");
#endif
    ctrl |= data_5bit;
   };


  /* If stopb is true we set STB which means 2 stop-bits - */
  /* otherwise we  only get 1 stop-bit.                    */
  ctrl |= (stopb ? STB : 0);
     
  ctrl |= ((parity & PARENB) ? ((parity & PARODD) ? (PEN) : (PEN |
							     EPS)) :
	   0x00 ); 

#if 0
  printk ("Storing serial-divisor %i\n", div);
#endif

  curruart(info)->LCR = (ctrl | DLAB);

  /* Store high byte of divisor */
  curruart(info)->DLM = ((div >> 8) & 0xff);
  
  /* Store low byte of divisor */

  curruart(info)->DLL = (div & 0xff);

  curruart(info)->LCR = ctrl;
};


static void ser_throttle(struct async_struct *info, int status){

#if 0
  printk("ser_throttle\n");
#endif
  if (status){
    ser_RTSoff(info);
  }
  else{
    ser_RTSon(info);
  }
};


static void ser_set_break(struct async_struct *info, int break_flag){

#if IOEXT_DEBUG
  printk("ser_set_break\n");
#endif
    if (break_flag)
	curruart(info)->LCR |= SET_BREAK;
    else
	curruart(info)->LCR &= ~SET_BREAK;
};


static void ser_get_serial_info(struct async_struct *info,
				struct serial_struct *retinfo){
#if 0
  printk("ser_get_serial_info\n");
#endif


  retinfo->baud_base = IOEXT_BAUD_BASE;
  retinfo->xmit_fifo_size = 16; /* This field is currently ignored, */
				/* by the upper layers of the       */
				/* serial-driver.                   */
  retinfo->custom_divisor = info->custom_divisor;

};

static unsigned int ser_get_modem_info(struct async_struct *info){

  u_char msr, mcr;

#if 0
  printk("ser_get_modem_info\n");
#endif

  msr = curruart(info)->MSR;
  mcr = curruart(info)->MCR; /* The DTR and RTS are located in the */
			     /* ModemControlRegister ...           */

  return(

/*	 ((mcr & DTR) ? 0 : TIOCM_DTR) |
	 ((mcr & RTS) ? 0 : TIOCM_RTS) |
	 ((msr & DCD) ? 0 : TIOCM_CAR) |
	 ((msr & CTS) ? 0 : TIOCM_CTS) |
	 ((msr & DSR) ? 0 : TIOCM_DSR) |
*/

	 ((mcr & DTR) ? TIOCM_DTR : 0) |
	 ((mcr & RTS) ? TIOCM_RTS : 0) |

	 ((msr & DCD) ? TIOCM_CAR : 0) | /* Somehow carrier detect   */
					 /* (DCD) has to be negated. */
	 ((msr & CTS) ? TIOCM_CTS : 0) |
	 ((msr & DSR) ? TIOCM_DSR : 0) |
	 ((msr & RING_I) ? TIOCM_RNG : 0)
	 );
};
static int ser_set_modem_info(struct async_struct *info, int new_dtr,
			      int new_rts){
#if 0
  printk("ser_set_modem_info new_dtr=%i new_rts=%i\n", new_dtr, new_rts);
#endif
  if (new_dtr == 0)
    ser_DTRoff(info);
  else if (new_dtr == 1)
    ser_DTRon(info);

  if (new_rts == 0){
    ser_RTSoff(info);
  }else{
    if (new_rts == 1){
      ser_RTSon(info);
    }
  }

  return 0;
};

static void ser_stop_receive (struct async_struct *info)
{
  /* disable receive interrupts */
  curruart(info)->IER &= ~ERDAI;
}

static int ser_trans_empty (struct async_struct *info)
{
  return (curruart(info)->LSR & THRE);
}

/*
 * Detect and initialize all IO-Extenders in this system.
 */
int ioext_init(void){

  int key = 0;
  struct ConfigDev *cd;
  caddr_t address;
  enum GVP_ident epc;
  short i, line1, line2;
  u_char isr_installed = 0;
  struct serial_struct req;
  struct async_struct *tmp_info;        /* Used when setting tty-info  */
  IOEXT_struct *board;                  /* Used as a pointer to the    */
				        /* board-address               */
  IOEXT_struct *ioext_bases[MAX_IOEXT]; /* Just used to inform the     */
					/* user of the location of the */
					/* boards found during         */
					/* configuration.              */     

  if (!MACH_IS_AMIGA)
      return -ENODEV;
  
  for (i=0 ; i < MAX_IOEXT; i++)
    ioext_bases[i] = NULL;

  /*
   * key++ is not needed anymore when a board is found, as zorro_find
   * returns board_nr + 1 and it adds 1 to the index before it starts
   * searching.
   */
  while ((key = zorro_find(MANUF_GVP, PROD_GVP, 0, key))) {

    cd = zorro_get_board(key);

    /* As GVP uses the same product code for several kind of boards, */
    /* we have to check the extended product code, to see if this */
    /* really is an io-extender. */
    /* check extended product code */
    address = cd->cd_BoardAddr;
    epc = *(enum GVP_ident *)ZTWO_VADDR(address + 0x8000) & GVP_EPCMASK;

    if (epc != GVP_IOEXT){
      continue;
    }

    /* Wait for the board to be ready and disable everything */
    board = (IOEXT_struct *) ZTWO_VADDR(address);

    req.line              = -1; /* first free ttyS? device */
    req.type              = SER_IOEXT;
    req.port              = (int) &board->uart0;
    if ((line1 = register_serial( &req )) < 0) {
	printk( "Cannot register IO-Extender serial port: no free device\n" );
	return -EBUSY;
    }
    tmp_info = &rs_table[line1];
    tmp_info->nr_uarts          = 2; /* number of uarts left, */
				     /* including this one.   */
    tmp_info->board_base        = board;
    tmp_info->sw                = &ioext_ser_switch;

    req.line              = -1; /* first free ttyS? device */
    req.type              = SER_IOEXT;
    req.port              = (int) &board->uart1;
    if ((line2 = register_serial( &req )) < 0) {
	printk( "Cannot register IO-Extender serial port: no free device\n" );
	unregister_serial( line1 );
	return -EBUSY;
    }
    tmp_info = &rs_table[line2];
    tmp_info->nr_uarts          = 1; /* number of uarts left, */
				     /* including this one.   */
    tmp_info->board_base        = board;
    tmp_info->sw                = &ioext_ser_switch;

    ioext_bases[num_ioext] = board;

    while(!board->CNTR & GVP_IRQ_PEND)
	printk("GVP-irq pending \n");
      
    /*
     * Disable board-interrupts for the moment.
     */
    board->CNTR = 0;

    /* Install ISR if it hasn't been installed already */
    if (!isr_installed){
	add_isr (IRQ_AMIGA_EXTER, (isrfunc) ser_interrupt, 0, rs_table + line1,
			 "io-extender serial");
	isr_installed++;
    }

    /*
     * Set the board to INT6 and RTSx port-control.
     * (GVP uses this setup).
     */
    board->CTRL = (IRQ_SEL|PORT0_CTRL|PORT1_CTRL);

    /* Wait for the uarts to get empty */
    while(!(board->uart0.LSR & TEMT))
#if IOEXT_DEBUG
	printk("Waiting for transmitter 0 to finish\n");
#endif

  /* Wait for the uart to get empty */
    while(!(board->uart1.LSR & TEMT))
#if IOEXT_DEBUG
	printk("Waiting for transmitter 1 to finish\n");
#endif

    /*
     * "Clear" pending interrupts
     */
    while (!((board->uart0.IIR) & IRQ_PEND))
#if IOEXT_DEBUG
	printk("IRQ-pending for IO-Extender %i, uart 0\n", i);
#endif
    while (!((board->uart1.IIR) & IRQ_PEND))
#if IOEXT_DEBUG
	printk("IRQ-pending for IO-Extender %i, uart 1\n", i);
#endif

    /*
     * Set the uarts to a default setting og 8N1 - 9600
     */

    board->uart0.LCR = (data_8bit | DLAB);
    board->uart0.DLM = 0;
    board->uart0.DLL = 48;
    board->uart0.LCR = (data_8bit);

    board->uart1.LCR = (data_8bit | DLAB);
    board->uart1.DLM = 0;
    board->uart1.DLL = 48;
    board->uart1.LCR = (data_8bit);


    /*
     * Enable + reset the FIFO's and set the FIFO-trigger count
     * to 14 (maximum - the actual FIFO-size is 16).
     */

    board->uart0.FCR =  ( FIFO_ENA      | RCVR_FIFO_RES |
		          XMIT_FIFO_RES | FIFO_TRIGGER_LEVEL );

    board->uart1.FCR =  ( FIFO_ENA      | RCVR_FIFO_RES |
			  XMIT_FIFO_RES | FIFO_TRIGGER_LEVEL );

    /* disable loopback for both uarts */

#if 0
    printk ("uart.MCR addresses: 0x%08x, 0x%08x\n", (u_int)
	    &(board->uart0.MCR), (u_int) &(board->uart1.MCR));
#endif

    /* 
     * Enable interrupts and set init for the parallel port.
     * There's not really any reason to do this, but better safe
     * than sorry.
     */
    board->par.CTRL = (INIT|INT2EN);


    /*
     * Master interrupt enable - National semconductors doesn't like
     * to follow standards, so their version of the chip is
     * different and I only had a copy of their data-sheets :-(
     */
    board->uart0.MCR = OUT2;
    board->uart1.MCR = OUT2;

    /*
     * Disable all uart interrups (they will be re-enabled in
     * ser_init when they are needed).
     */
    board->uart0.IER = 0;
    board->uart1.IER = 0;

    /*
     * Read both interrupt identification registers, to clear a
     * possible pending interrupt - I don't think this has any
     * affect at all.
     */
    (void)board->uart0.IIR;
    (void)board->uart1.IIR;

    /*
     * Setting the necessary tty-stuff.
     */

    /*
     * Re-enable all IRQ's for the current board (board interrupt).
     */
    board->CNTR |= GVP_IRQ_ENA;

    num_ioext++;
    zorro_config_board(key, 0);
  }

  /* Print number of IO-Extenders found and their base-addresses.*/
  if (num_ioext > 0){
    printk ("Detected %i GVP IO-Extender(s) at: ", num_ioext);
    for (i = 0 ; i < num_ioext ; i++){
      printk ("0x%08x", (unsigned int) ioext_bases[i]);
      if (i+1 < num_ioext)
	printk(", ");
    }
    printk("\n");
  }
  return 0;
}
