patch-2.2.18 linux/drivers/char/ser_hpdca.c
Next file: linux/drivers/char/ser_hpdca.h
Previous file: linux/drivers/char/saa7185.c
Back to the patch index
Back to the overall index
- Lines: 998
- Date:
Sat Oct 14 00:04:25 2000
- Orig file:
v2.2.17/drivers/char/ser_hpdca.c
- Orig date:
Thu Jan 1 01:00:00 1970
diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/drivers/char/ser_hpdca.c linux/drivers/char/ser_hpdca.c
@@ -0,0 +1,997 @@
+/*
+ * Driver for the 98626/98644/internal serial interface on hp300/hp400
+ * (based on the National Semiconductor INS8250/NS16550AF/WD16C552 UARTs)
+ *
+ * This driver was written by Peter Maydell <pmaydell@chiark.greenend.org.uk>
+ * based on informaition gleaned from the NetBSD driver and the ser_ioext
+ * driver. Copyright(C) Peter Maydell 05/1998.
+ * The most significant difference between us and ser_ioext is that
+ * we have to worry about UARTs with no FIFO (ignoring the Amiga vs HP differences...)
+ *
+ * We worry about multiple boards in a system because ser_ioext does and
+ * it seems the sensible thing to do. It's untested, though. Also, are we
+ * duplicating work done by the hardware-independent m68k serial layer?
+ * (multiple devices seems like an obvious thing to go for...)
+ *
+ * The driver is called hpdca because the NetBSD driver is 'dca' and
+ * I wanted something less generic than hp300...
+ *
+ * N.B. On the hp700 and some hp300s, there is a "secret bit" with
+ * undocumented behavior. The third bit of the Modem Control Register
+ * (MCR_IEN == 0x08) must be set to enable interrupts. Failure to do
+ * so can result in deadlock on those machines, whereas there don't seem to
+ * be any harmful side-effects from setting this bit on non-affected
+ * machines.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#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/m68kserial.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/console.h>
+
+#include <asm/blinken.h>
+#include <asm/setup.h>
+#include <asm/irq.h>
+#include <asm/hwtest.h> /* hwreg_present() */
+#include <asm/io.h> /* readb(), writeb() */
+#include <linux/dio.h>
+
+#include "ser_hpdca.h"
+
+/* Set these to 0 when the driver is finished */
+#define HPDCA_DEBUG 1
+#define DEBUG_IRQ 1
+#define DEBUG 1
+#define DEBUG_FLOW 1
+
+#define FIFO_TRIGGER_LEVEL FIFO_TRIG_8
+
+/***************************** Prototypes *****************************/
+static void hpdca_ser_interrupt(volatile struct uart_16c550 *uart, int line,
+ int hasfifo, int *spurious_count);
+static void ser_init( struct m68k_async_struct *info );
+static void ser_deinit( struct m68k_async_struct *info, int leave_dtr );
+static void ser_enab_tx_int( struct m68k_async_struct *info, int enab_flag );
+static int ser_check_custom_divisor(struct m68k_async_struct *info,
+ int baud_base, int divisor);
+static void ser_change_speed( struct m68k_async_struct *info );
+static void ser_throttle( struct m68k_async_struct *info, int status );
+static void ser_set_break( struct m68k_async_struct *info, int break_flag );
+static void ser_get_serial_info( struct m68k_async_struct *info,
+ struct serial_struct *retinfo );
+static unsigned int ser_get_modem_info( struct m68k_async_struct *info );
+static int ser_set_modem_info( struct m68k_async_struct *info, int new_dtr,
+ int new_rts );
+static void ser_stop_receive(struct m68k_async_struct *info);
+static int ser_trans_empty(struct m68k_async_struct *info);
+/************************* End of Prototypes **************************/
+
+/*
+ * SERIALSWITCH structure for the dca
+ */
+
+static SERIALSWITCH hpdca_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
+};
+
+/* table of divisors to use for various baud rates. DCABRD() is a macro
+ * which gives the correct ratio for the clock speed (hp300 vs hp700)
+ */
+static int hpdca_baud_table[16] = {
+ /* B0 */ 0, /* Never use this value !!! */
+ /* B50 */ DCABRD(50),
+ /* B75 */ DCABRD(75),
+ /* B110 */ DCABRD(110),
+ /* B134 */ DCABRD(134),
+ /* B150 */ DCABRD(150),
+ /* B200 */ DCABRD(200),
+ /* B300 */ DCABRD(300),
+ /* B600 */ DCABRD(600),
+ /* B1200 */ DCABRD(1200),
+ /* B1800 */ DCABRD(1800),
+ /* B2400 */ DCABRD(2400),
+ /* B4800 */ DCABRD(4800),
+ /* B9600 */ DCABRD(9600),
+ /* B19200 */ DCABRD(19200),
+ /* B38400 */ DCABRD(38400) /* The last of the standard rates. */
+};
+
+int hpdca_num;
+hpdcaInfoType hpdca_info[MAX_HPDCA];
+
+/*
+ * Functions
+ *
+ * dev_id = hpdca_info.
+ *
+ */
+static void hpdca_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ /* We have to take this interrupt and work out which board it's for. */
+ int b;
+
+ if (hpdca_num == 0) {
+ /* This interrupt can't be for us */
+ return;
+ }
+
+ for (b = 0; b < hpdca_num; b++) {
+ hpdca_struct *board = hpdca_info[b].board;
+ hpdcaInfoType *board_info = &hpdca_info[b];
+
+ if ((board->dca_ic & (IC_IR|IC_IE)) != (IC_IR|IC_IE))
+ /* interrupts not enabled, not for us */
+ continue;
+
+ /* Service any uart irqs. */
+ hpdca_ser_interrupt(board_info->uart, board_info->line,
+ board_info->hasfifo, &board_info->spurious_count);
+
+ if (board_info->spurious_count > 10000) {
+ board->dca_ic &= ~IC_IE;
+ printk("Too many spurious interrupts, disabling board irq\n");
+ board_info->spurious_count = 0;
+ }
+ } /* for b */
+}
+
+/*
+** hpdca_ser_interrupt()
+**
+** Check for and handle interrupts for this uart.
+*/
+static void hpdca_ser_interrupt(volatile struct uart_16c550 *uart, int line,
+ int hasfifo, int *spurious_count)
+{
+ struct m68k_async_struct *info = rs_table + line;
+
+ u_char iir;
+ u_char lsr;
+ int ch;
+
+ iir = uart->IIR;
+
+ ++*spurious_count;
+
+#if DEBUG_IRQ
+ printk("ttyS%d: IER=%02X, IIR=%02X, LSR=%02X, MSR=%02X\n",
+ line, uart->IER, iir, uart->LSR, uart->MSR);
+#endif
+
+ while (!(iir & IRQ_PEND)) {
+ /* IRQ for this uart */
+#if DEBUG_IRQ
+ printk("IRQ_PEND on ttyS%d...\n", line);
+#endif
+
+ /* This really is our interrupt */
+ *spurious_count = 0;
+
+ switch (iir & (IRQ_ID1 | IRQ_ID2 | IRQ_ID3)) {
+ case IRQ_RLS: /* Receiver Line Status */
+#if DEBUG_FLOW
+ printk("RLS irq on ttyS%d\n", line);
+#endif
+ case IRQ_CTI: /* Character Timeout */
+ case IRQ_RDA: /* Received Data Available */
+#if DEBUG_IRQ
+ printk("irq (IIR=%02X) on ttyS%d\n", line, iir);
+#endif
+ /*
+ * Copy chars to the tty-queue ...
+ * Be careful that we aren't passing one of the
+ * Receiver Line Status interrupt-conditions without noticing.
+ */
+ if (!hasfifo)
+ {
+ /* with no FIFO reads are trivial */
+ ch = uart->RBR;
+ rs_receive_char(info, ch, 0);
+#ifdef DEBUG_IRQ
+ printk("Read a char from FIFOless uart: %02X\n", ch);
+#endif
+ }
+ else
+ {
+ int got = 0;
+
+ lsr = uart->LSR;
+#if DEBUG_IRQ
+ printk("uart->LSR & DR = %02X\n", lsr & DR);
+#endif
+ while (lsr & DR) {
+ u_char err = 0;
+ ch = uart->RBR;
+#if DEBUG_IRQ
+ printk("Read a char from the uart: %02X, lsr=%02X\n",
+ ch, lsr);
+#endif
+ if (lsr & BI) {
+ err = TTY_BREAK;
+ }
+ else if (lsr & PE) {
+ err = TTY_PARITY;
+ }
+ else if (lsr & OE) {
+ err = TTY_OVERRUN;
+ }
+ else if (lsr & FE) {
+ err = TTY_FRAME;
+ }
+#if DEBUG_IRQ
+ printk("rs_receive_char(ch=%02X, err=%02X)\n", ch, err);
+#endif
+ rs_receive_char(info, ch, err);
+ got++;
+ lsr = uart->LSR;
+ }
+#if DEBUG_FLOW
+ printk("[%d<]", got);
+#endif
+ }
+ break;
+
+ case IRQ_THRE: /* Transmitter holding register empty */
+ {
+ int fifo_space = (hasfifo ? 16 : 1); /* no FIFO => send one char */
+ int sent = 0;
+
+#if DEBUG_IRQ
+ printk("THRE-irq for ttyS%d\n", line);
+#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 */
+ if (info->xmit_cnt <= 0 || info->tty->stopped ||
+ info->tty->hw_stopped) {
+ /* Disable transmitter empty interrupt */
+ uart->IER &= ~(ETHREI);
+ /* Need to send a char to acknowledge the interrupt */
+ uart->THR = 0;
+#if DEBUG_FLOW
+ if (info->tty->hw_stopped) {
+ printk("[-]");
+ }
+ if (info->tty->stopped) {
+ printk("[*]");
+ }
+#endif
+ break;
+ }
+
+ /* Handle software flow control */
+ if (info->x_char) {
+#if DEBUG_FLOW
+ printk("[^%c]", info->x_char + '@');
+#endif
+ uart->THR = info->x_char;
+ info->x_char = 0;
+ fifo_space--;
+ sent++;
+ }
+
+ /* Fill the fifo */
+ while (fifo_space > 0) {
+ fifo_space--;
+#if DEBUG_IRQ
+ printk("Sending %02x to the uart.\n",
+ info->xmit_buf[info->xmit_tail]);
+#endif
+ uart->THR = info->xmit_buf[info->xmit_tail++];
+ sent++;
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ if (--info->xmit_cnt == 0) {
+ break;
+ }
+ }
+#if DEBUG_FLOW
+ printk("[>%d]", sent);
+#endif
+
+ if (info->xmit_cnt == 0) {
+#if DEBUG_IRQ
+ printk("Sent last char - turning off THRE interrupts\n");
+#endif
+ /* Don't need THR interrupts any more */
+ uart->IER &= ~(ETHREI);
+ }
+
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+ }
+ }
+ break;
+
+ case IRQ_MS: /* Must be modem status register interrupt? */
+ {
+ u_char msr = uart->MSR;
+#if DEBUG_IRQ
+ printk("MS-irq for ttyS%d: %02x\n", line, msr);
+#endif
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ if (msr & DCTS) {
+ rs_check_cts(info, (msr & CTS)); /* active high */
+#if DEBUG_FLOW
+ printk("[%c-%d]", (msr & CTS) ? '^' : 'v',
+ info->tty ? info->tty->hw_stopped : -1);
+#endif
+ }
+ if (msr & DDCD) {
+#if DEBUG
+ printk("rs_dcd_changed(%d)\n", !(msr & DCD));
+#endif
+ rs_dcd_changed(info, !(msr & DCD)); /* active low */
+ }
+ }
+ }
+ break;
+
+ default:
+#if DEBUG_IRQ
+ printk("Unexpected irq for ttyS%d\n", line);
+#endif
+ break;
+ } /* switch (iir) */
+ iir = uart->IIR;
+ } /* while IRQ_PEND */
+}
+
+static void ser_init(struct m68k_async_struct *info)
+{
+#if DEBUG
+ printk("ser_init\n");
+#endif
+
+ while ((curruart(info)->LSR) & DR) {
+#if HPDCA_DEBUG
+ printk("Emptying uart\n");
+#endif
+ (void)curruart(info)->RBR;
+ }
+
+ /* Set DTR and RTS */
+ curruart(info)->MCR |= (DTR | RTS | OUT2);
+
+ /* Enable interrupts. IF_EXTER irq has already been enabled in hpdca_init()*/
+ /* DON'T enable ETHREI here because there is nothing to send yet (murray) */
+ curruart(info)->IER |= (ERDAI | ELSI | EMSI);
+
+ MOD_INC_USE_COUNT;
+}
+
+
+static void ser_deinit(struct m68k_async_struct *info, int leave_dtr)
+{
+#if DEBUG
+ printk("ser_deinit\n");
+#endif
+
+ /* Wait for the uart to get empty */
+ while(!(curruart(info)->LSR & TEMT)) {
+#if HPDCA_DEBUG
+ printk("Waiting for the transmitter to finish\n");
+#endif
+ }
+
+ while(curruart(info)->LSR & DR) {
+#if HPDCA_DEBUG
+ printk("Uart not empty - flushing!\n");
+#endif
+ (void)curruart(info)->RBR;
+ }
+
+ /* No need to disable UART interrupts since this will already
+ * have been done via ser_enab_tx_int() and ser_stop_receive()
+ */
+
+ ser_RTSoff(info);
+ if (!leave_dtr) {
+ ser_DTRoff(info);
+ }
+
+ MOD_DEC_USE_COUNT;
+}
+
+/*
+** ser_enab_tx_int()
+**
+** Enable or disable tx interrupts.
+** Note that contrary to popular belief, it is not necessary to
+** send a character to cause an interrupt to occur. Whenever the
+** THR is empty and THRE interrupts are enabled, an interrupt will occur.
+** (murray)
+*/
+static void ser_enab_tx_int(struct m68k_async_struct *info, int enab_flag)
+{
+ if (enab_flag) {
+ curruart(info)->IER |= ETHREI;
+ }
+ else {
+ curruart(info)->IER &= ~(ETHREI);
+ }
+}
+
+static int ser_check_custom_divisor(struct m68k_async_struct *info,
+ int baud_base, int divisor)
+{
+ /* Always return 0 or else setserial spd_hi/spd_vhi doesn't work */
+ return 0;
+}
+
+static void ser_change_speed(struct m68k_async_struct *info)
+{
+ unsigned int cflag, baud, chsize, stopb, parity, aflags;
+ unsigned int div = 0, ctrl = 0;
+
+#if DEBUG
+ 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 DEBUG
+ printk("Changing to baud-rate %i\n", baud);
+#endif
+
+ if (baud & CBAUDEX) {
+ baud &= ~CBAUDEX;
+ if (baud < 1 || baud > 4)
+ info->tty->termios->c_cflag &= ~CBAUDEX;
+ else
+ baud += 15;
+ }
+
+ /* Maximum speed is 38400 */
+ if (baud > 15)
+ baud = 15;
+ div = hpdca_baud_table[baud];
+
+ if (!div) {
+ /* speed == 0 -> drop DTR */
+#if DEBUG
+ 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 as well.
+ */
+ ser_DTRon(info);
+
+ if (chsize == CS8) {
+#if DEBUG
+ printk("Setting serial word-length to 8-bits\n");
+#endif
+ ctrl |= data_8bit;
+ }
+ else if (chsize == CS7) {
+#if DEBUG
+ printk("Setting serial word-length to 7-bits\n");
+#endif
+ ctrl |= data_7bit;
+ }
+ else if (chsize == CS6) {
+#if DEBUG
+ printk("Setting serial word-length to 6-bits\n");
+#endif
+ ctrl |= data_6bit;
+ }
+ else if (chsize == CS5) {
+#if DEBUG
+ 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);
+
+ /* if parity disabled, ctrl |= 0
+ * if odd parity, ctrl |= PEN
+ * if even parity, ctrl |= PEN|EPS
+ * Not my code -- PMM :->
+ */
+ ctrl |= ((parity & PARENB) ?
+ ((parity & PARODD) ? (PEN) : (PEN | EPS)) : 0x00 );
+
+#if DEBUG
+ 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 m68k_async_struct *info, int status){
+
+#if DEBUG
+ printk("ser_throttle\n");
+#endif
+ if (status){
+ ser_RTSoff(info);
+ }
+ else{
+ ser_RTSon(info);
+ }
+}
+
+
+static void ser_set_break(struct m68k_async_struct *info, int break_flag)
+{
+#if HPDCA_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 m68k_async_struct *info,
+ struct serial_struct *retinfo)
+{
+ int b;
+
+#if DEBUG
+ printk("ser_get_serial_info\n");
+#endif
+
+ retinfo->baud_base = HPDCA_BAUD_BASE;
+ /* This field is currently ignored by the upper layers of
+ * the serial driver, but we set it anyway :->
+ * Is this really the best way to find out which board our caller
+ * is referring to???
+ */
+ for (b = 0; b < hpdca_num; b++)
+ if (hpdca_info[b].uart == curruart(info))
+ retinfo->xmit_fifo_size = (hpdca_info[b].hasfifo ? 16 : 1);
+
+ retinfo->custom_divisor = info->custom_divisor;
+}
+
+static unsigned int ser_get_modem_info(struct m68k_async_struct *info)
+{
+ unsigned char msr, mcr;
+
+#if DEBUG
+ 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) ? TIOCM_DTR : 0) |
+ ((mcr & RTS) ? TIOCM_RTS : 0) |
+
+ ((msr & DCD) ? 0 : TIOCM_CAR) | /* DCD is active low */
+ ((msr & CTS) ? TIOCM_CTS : 0) |
+ ((msr & DSR) ? TIOCM_DSR : 0) |
+ ((msr & RING_I) ? TIOCM_RNG : 0)
+ );
+}
+
+static int ser_set_modem_info(struct m68k_async_struct *info, int new_dtr,
+ int new_rts)
+{
+#if DEBUG
+ 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 m68k_async_struct *info)
+{
+ /* Disable uart receive and status interrupts */
+ curruart(info)->IER &= ~(ERDAI | ELSI | EMSI);
+}
+
+static int ser_trans_empty (struct m68k_async_struct *info)
+{
+ return (curruart(info)->LSR & THRE);
+}
+
+/*
+** init_hpdca_uart(): init the uart. Returns 1 if UART has a FIFO, 0 otherwise
+**
+*/
+static int init_hpdca_uart(struct uart_16c550 *uart)
+{
+ /* Wait for the uart to get empty */
+ while (!(uart->LSR & TEMT)) {
+#if HPDCA_DEBUG
+ printk("Waiting for transmitter to finish\n");
+#endif
+ }
+
+ /*
+ * Disable all uart interrups (they will be re-enabled in
+ * ser_init when they are needed).
+ */
+ uart->IER = 0;
+ /* 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 :-(
+ */
+ uart->MCR = OUT2;
+
+ /*
+ * Set the uart to a default setting of 8N1 - 9600
+ */
+ uart->LCR = (data_8bit | DLAB);
+ uart->DLM = 0;
+ uart->DLL = 48;
+ uart->LCR = (data_8bit);
+
+ /* Enable + reset the tx and rx FIFO's. Set the rx FIFO-trigger count.
+ * If we then find that the bits in IIR that indicate enabling of
+ * the FIFOs are zero, we conclude that this uart has no FIFOs.
+ */
+ uart->FCR = (FIFO_ENA | RCVR_FIFO_RES | XMIT_FIFO_RES | FIFO_TRIGGER_LEVEL);
+ udelay(100); /* wait for uart to get its act together */
+ return (uart->IIR & (FIFO_ENA1 | FIFO_ENA0));
+}
+
+#ifdef CONFIG_SERIAL_CONSOLE
+
+/*
+ * Support for a serial console on the DCA port. We used to do this in config.c
+ * so it got set up early (and even if you didn't ask for serial console), but we
+ * don't any more. (PB) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * [I don't believe this!-- PMM]
+ *
+ * However, we still want it set up earlier than the normal serial device driver,
+ * so I've added a hook in m68kserial.c's serial_console_init() which is IMHO the
+ * right place for it. -- PMM
+ */
+static volatile struct uart_16c550 *uart = NULL;
+static int conscode;
+
+/* Warning: we get called very early, so you can't allocate memory!
+ * [we could pass this function kmem_start, kmem_end if necessary]
+ */
+__initfunc(int hpdca_init_serial_console(int cflag))
+{
+ /* here cflag encodes rqd baud rate, parity, etc */
+ int baud = cflag & CBAUD;
+ int ctrl = 0;
+ int div, scode;
+ u_char stat;
+ int dly;
+
+ switch (cflag & CSIZE)
+ {
+ case CS5:
+ ctrl = data_5bit;
+ break;
+ case CS6:
+ ctrl = data_6bit;
+ break;
+ case CS7:
+ ctrl = data_7bit;
+ break;
+ case CS8:
+ ctrl = data_8bit;
+ break;
+ }
+
+ ctrl |= ((cflag & PARENB) ? ((cflag & PARODD) ? 0x8 : 0x18) : 0x00);
+
+ if (baud < B1200 || baud > B38400)
+ baud = B9600; /* sensible default for unimplemented rates */
+
+ div = hpdca_baud_table[baud];
+ /* Unfortunately we have to do a complete scan of the DIO bus in
+ * order to locate the serial port...
+ */
+ for (scode = 0; scode < DIO_SCMAX; ++scode)
+ {
+ u_char *va;
+ u_char id;
+
+ /* skip the hole and the internal HPIB controller */
+ if (DIO_SCINHOLE(scode) || DIO_ISIHPIB(scode))
+ continue;
+
+ va = dio_scodetoviraddr(scode);
+ if (!va)
+ continue;
+
+ if (!hwreg_present(va + DIO_IDOFF)) /* nothing there */
+ continue;
+
+ id = DIO_ID(va); /* get the ID byte */
+
+ if (id != DIO_ID_DCA0 && id != DIO_ID_DCA0REM
+ && id != DIO_ID_DCA1 && id != DIO_ID_DCA1REM)
+ continue;
+
+ printk("Console is HP DCA at select code %d\n", scode);
+ /* OK, this is the DCA, set it up */
+ writeb(0xff, va); /* DCA reset */
+ /* should udelay(100) here */
+ for(dly = 0; dly < 10000; dly++)
+ barrier(); /* defeat GCC optimisation! */
+
+ uart = (struct uart_16c550 *)(va+16); /* offset of the UART on the board */
+ conscode = scode; /* save so we don't init this board again */
+
+ break;
+ }
+
+ if (!uart) /* no DCA detected, abort */
+ return 0;
+
+ uart->IER = 0; /* disable interrupts */
+ uart->MCR = OUT2; /* master interrupt enable */
+ uart->LCR = (ctrl | DLAB); /* usual messing around to set divisor */
+ uart->DLM = ((div >> 8) & 0xff);
+ uart->DLL = (div & 0xff);
+ uart->LCR = ctrl;
+ for(dly = 0; dly < 10000; dly++)
+ barrier(); /* defeat GCC optimisation! */
+ stat = uart->IIR; /* ack any interrupts raised */
+
+ return 1; /* success */
+}
+
+static inline void hpdca_out(char c)
+{
+ u_char stat;
+ int timo;
+
+ timo = 50000;
+ while (!(uart->LSR & THRE) && --timo)
+ barrier();
+ uart->THR = c;
+ timo = 1500000;
+ while (!(uart->LSR & THRE) && --timo) /* wait for Tx to complete */
+ barrier();
+ stat = uart->IIR; /* ack any generated interrupts */
+}
+
+
+void hpdca_serial_console_write(struct console *co, const char *str,
+ unsigned int count)
+{
+ while (count--) {
+ if (*str == '\n')
+ hpdca_out('\r');
+ hpdca_out(*str++);
+ }
+}
+
+int hpdca_serial_console_wait_key(struct console *co)
+{
+ u_char stat;
+
+ while(!(uart->LSR & DR))
+ barrier();
+ stat = uart->IIR; /* clear any generated ints */
+ return(uart->RBR);
+}
+
+#ifdef UNDEF
+static struct console hpdca_console_driver = {
+ "debug",
+ hpdca_serial_console_write,
+ NULL, /* read */
+ NULL, /* device */
+ hpdca_serial_console_wait_key,
+ NULL, /* unblank */
+ NULL, /* setup */
+ CON_PRINTBUFFER,
+ -1,
+ 0,
+ NULL
+};
+#endif /* UNDEF */
+#endif /* CONFIG_SERIAL_CONSOLE */
+
+/*
+ * Detect and initialize all HP dca boards in this system.
+ */
+__initfunc(int hpdca_init(void))
+{
+ int isr_installed = 0;
+ unsigned int scode = 0;
+ static char support_string[50] = "HP 98644A dca serial";
+ int ipl;
+
+ if (!MACH_IS_HP300)
+ return -ENODEV;
+
+ memset(hpdca_info, 0, sizeof(hpdca_info));
+
+ for (;;) {
+ hpdca_struct *board;
+ int line;
+ struct uart_16c550 *uart;
+ struct serial_struct req;
+
+ /* We detect boards by looking for DIO boards which match a
+ * given subset of IDs. dio_find() returns the board's scancode.
+ * The scancode to physaddr mapping is a property of the hardware,
+ * as is the scancode to IPL (interrupt priority) mapping.
+ */
+ scode = dio_find(DIO_ID_DCA0);
+ if (!scode)
+ scode = dio_find(DIO_ID_DCA0REM);
+ if (!scode)
+ scode = dio_find(DIO_ID_DCA1);
+ if (!scode)
+ scode = dio_find(DIO_ID_DCA1REM);
+ if (!scode)
+ break; /* no, none at all */
+
+#ifdef HPDCA_DEBUG
+ printk("Found DCA scode %d",scode);
+#endif
+ board = (hpdca_struct *) dio_scodetoviraddr(scode);
+ ipl = dio_scodetoipl(scode);
+#ifdef HPDCA_DEBUG
+ printk(" at viraddr %08lX, ipl %d\n",(u_long)board, ipl);
+#endif
+ hpdca_info[hpdca_num].board = board;
+ hpdca_info[hpdca_num].scode = scode;
+#ifdef CONFIG_SERIAL_CONSOLE
+ if (scode == conscode)
+ {
+ printk("Whoops, that's the console!\n");
+ dio_config_board(scode);
+ /* What are we actually supposed to do here??? */
+ continue;
+ }
+#endif
+ /* Reset the DCA */
+ board->dca_reset = 0xff;
+ udelay(100);
+
+ /* Register the serial port device. */
+ uart = &board->uart;
+
+ req.line = -1; /* first free ttyS? device */
+ req.type = SER_HPDCA;
+ req.port = (int)uart; /* yuck */
+ if ((line = register_serial(&req)) < 0) {
+ printk( "Cannot register HP dca serial port: no free device\n" );
+ break;
+ }
+
+ /* Add this uart to our hpdca_info[] table */
+ hpdca_info[hpdca_num].line = line;
+ hpdca_info[hpdca_num].uart = uart;
+
+ rs_table[line].sw = &hpdca_ser_switch;
+
+ /* We don't use these values */
+ rs_table[line].nr_uarts = 0;
+ rs_table[line].board_base = board;
+
+ /* init the UART proper and find out if it's got a FIFO */
+ hpdca_info[hpdca_num].hasfifo = init_hpdca_uart(uart);
+
+ /* Install ISR if it hasn't been installed already */
+ if (!isr_installed) {
+ request_irq(ipl, hpdca_interrupt, 0,
+ support_string, hpdca_info);
+ isr_installed++;
+ }
+ hpdca_num++;
+
+ /* tell the DIO code that this board is configured */
+ dio_config_board(scode);
+ }
+
+ return(0);
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return(hpdca_init());
+}
+
+void cleanup_module(void)
+{
+ int i;
+
+ for (i = 0; i < hpdca_num; i++) {
+ hpdca_struct *board = hpdca_info[i].board;
+ int j;
+
+ /* Disable board-interrupts */
+ board->dca_ic &= ~IC_IE;
+
+ /* Disable "master" interrupt select on uart */
+ board->uart.MCR = 0;
+
+ /* Disable all uart interrupts */
+ board->uart.IER = 0;
+
+ unregister_serial(hpdca_info[i].line);
+
+ dio_unconfig_board(board->scode);
+ }
+
+ if (hpdca_num != 0) {
+ /* Indicate to the IRQ handler that nothing needs to be serviced */
+ hpdca_num = 0;
+ free_irq(dio_scodetoipl(hpdca_info[0].scode), hpdca_info);
+ }
+}
+#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)