patch-2.1.53 linux/drivers/sbus/char/su.c
Next file: linux/drivers/sbus/char/suncons.c
Previous file: linux/drivers/sbus/char/sbuscons.c
Back to the patch index
Back to the overall index
- Lines: 679
- Date:
Thu Sep 4 12:54:49 1997
- Orig file:
v2.1.52/linux/drivers/sbus/char/su.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.1.52/linux/drivers/sbus/char/su.c linux/drivers/sbus/char/su.c
@@ -0,0 +1,678 @@
+/* $Id: su.c,v 1.3 1997/09/03 11:54:56 ecd Exp $
+ * su.c: Small serial driver for keyboard/mouse interface on Ultra/AX
+ *
+ * Copyright (C) 1997 Eddie C. Dost ([email protected])
+ *
+ * This is mainly a very stripped down version of drivers/char/serial.c,
+ * credits go to authors mentioned therein.
+ */
+
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/interrupt.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/string.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/malloc.h>
+#include <linux/init.h>
+
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/ebus.h>
+
+#include "sunserial.h"
+#include "sunkbd.h"
+#include "sunmouse.h"
+
+static char *serial_name = "kbd/mouse serial driver";
+static char *serial_version = "1.0";
+
+/* Set of debugging defines */
+#undef SERIAL_DEBUG_INTR
+#undef SERIAL_DEBUG_OPEN
+
+/* We are on a NS PC87303 clocked with 24.0 MHz, which results
+ * in a UART clock of 1.8462 MHz.
+ */
+#define BAUD_BASE (1846200 / 16)
+
+struct su_struct {
+ int magic;
+ unsigned long port;
+ int baud_base;
+ int type;
+ int irq;
+ int flags;
+ unsigned char IER;
+ unsigned char MCR;
+ int line;
+ int cflag;
+ int kbd_node;
+ int ms_node;
+ char name[16];
+};
+
+static struct su_struct su_table[] = {
+ { 0, 0, BAUD_BASE, PORT_UNKNOWN },
+ { 0, 0, BAUD_BASE, PORT_UNKNOWN }
+};
+
+#define NR_PORTS (sizeof(su_table) / sizeof(struct su_struct))
+
+static void autoconfig(struct su_struct * info);
+static void change_speed(struct su_struct *info);
+
+/*
+ * Here we define the default xmit fifo size used for each type of
+ * UART
+ */
+static struct serial_uart_config uart_config[] = {
+ { "unknown", 1, 0 },
+ { "8250", 1, 0 },
+ { "16450", 1, 0 },
+ { "16550", 1, 0 },
+ { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO },
+ { "cirrus", 1, 0 },
+ { "ST16650", 1, UART_CLEAR_FIFO |UART_STARTECH },
+ { "ST16650V2", 32, UART_CLEAR_FIFO | UART_USE_FIFO |
+ UART_STARTECH },
+ { "TI16750", 64, UART_CLEAR_FIFO | UART_USE_FIFO},
+ { 0, 0}
+};
+
+/*
+ * This is used to figure out the divisor speeds and the timeouts
+ */
+static int baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 0 };
+
+static inline
+unsigned int su_inb(struct su_struct *info, unsigned long offset)
+{
+ return inb(info->port + offset);
+}
+
+static inline void
+su_outb(struct su_struct *info, unsigned long offset, int value)
+{
+ outb(value, info->port + offset);
+}
+
+static inline void
+receive_chars(struct su_struct *info, struct pt_regs *regs)
+{
+ unsigned char status = 0;
+ unsigned char ch;
+
+ do {
+ ch = su_inb(info, UART_RX);
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("DR%02x:%02x...", ch, status);
+#endif
+
+ if (info->kbd_node) {
+ if(ch == SUNKBD_RESET) {
+ l1a_state.kbd_id = 1;
+ l1a_state.l1_down = 0;
+ } else if(l1a_state.kbd_id) {
+ l1a_state.kbd_id = 0;
+ } else if(ch == SUNKBD_L1) {
+ l1a_state.l1_down = 1;
+ } else if(ch == (SUNKBD_L1|SUNKBD_UP)) {
+ l1a_state.l1_down = 0;
+ } else if(ch == SUNKBD_A && l1a_state.l1_down) {
+ /* whee... */
+ batten_down_hatches();
+ /* Continue execution... */
+ l1a_state.l1_down = 0;
+ l1a_state.kbd_id = 0;
+ return;
+ }
+ sunkbd_inchar(ch, regs);
+ } else {
+ sun_mouse_inbyte(ch);
+ }
+
+ status = su_inb(info, UART_LSR);
+ } while (status & UART_LSR_DR);
+}
+
+/*
+ * This is the serial driver's generic interrupt routine
+ */
+static void
+su_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct su_struct *info = (struct su_struct *)dev_id;
+ unsigned char status;
+
+ /*
+ * We might share interrupts with ps2kbd/ms driver,
+ * in case we want to use the 16550A as general serial
+ * driver in the presence of ps2 devices, so do a
+ * sanity check here, needs to be done in ps2kbd/ms
+ * driver, too.
+ */
+ if (!info || info->magic != SERIAL_MAGIC)
+ return;
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("su_interrupt(%d)...", irq);
+#endif
+
+ if (su_inb(info, UART_IIR) & UART_IIR_NO_INT)
+ return;
+
+ status = su_inb(info, UART_LSR);
+#ifdef SERIAL_DEBUG_INTR
+ printk("status = %x...", status);
+#endif
+ if (status & UART_LSR_DR)
+ receive_chars(info, regs);
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("end.\n");
+#endif
+}
+
+static int
+startup(struct su_struct * info)
+{
+ unsigned long flags;
+ int retval=0;
+
+ save_flags(flags); cli();
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ goto errout;
+ }
+
+ if (!info->port || !info->type) {
+ goto errout;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("starting up su%d (irq %x)...", info->line, info->irq);
+#endif
+
+ if (info->type == PORT_16750)
+ su_outb(info, UART_IER, 0);
+
+ /*
+ * Clear the FIFO buffers and disable them
+ * (they will be reenabled in change_speed())
+ */
+ if (uart_config[info->type].flags & UART_CLEAR_FIFO)
+ su_outb(info, UART_FCR, (UART_FCR_CLEAR_RCVR |
+ UART_FCR_CLEAR_XMIT));
+
+ /*
+ * At this point there's no way the LSR could still be 0xFF;
+ * if it is, then bail out, because there's likely no UART
+ * here.
+ */
+ if (su_inb(info, UART_LSR) == 0xff) {
+ retval = -ENODEV;
+ goto errout;
+ }
+
+ /*
+ * Allocate the IRQ if necessary
+ */
+ retval = request_irq(info->irq, su_interrupt, SA_SHIRQ,
+ info->name, info);
+ if (retval)
+ goto errout;
+
+ /*
+ * Clear the interrupt registers.
+ */
+ su_inb(info, UART_RX);
+ su_inb(info, UART_IIR);
+ su_inb(info, UART_MSR);
+
+ /*
+ * Now, initialize the UART
+ */
+ su_outb(info, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */
+
+ info->MCR = UART_MCR_OUT2;
+ su_outb(info, UART_MCR, info->MCR);
+
+ /*
+ * Finally, enable interrupts
+ */
+ info->IER = UART_IER_RLSI | UART_IER_RDI;
+ su_outb(info, UART_IER, info->IER); /* enable interrupts */
+
+ /*
+ * And clear the interrupt registers again for luck.
+ */
+ su_inb(info, UART_LSR);
+ su_inb(info, UART_RX);
+ su_inb(info, UART_IIR);
+ su_inb(info, UART_MSR);
+
+ /*
+ * and set the speed of the serial port
+ */
+ change_speed(info);
+
+ info->flags |= ASYNC_INITIALIZED;
+ restore_flags(flags);
+ return 0;
+
+errout:
+ restore_flags(flags);
+ return retval;
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static void
+change_speed(struct su_struct *info)
+{
+ unsigned char cval, fcr = 0;
+ int quot = 0;
+ unsigned long flags;
+ int i;
+
+ /* byte size and parity */
+ switch (info->cflag & CSIZE) {
+ case CS5: cval = 0x00; break;
+ case CS6: cval = 0x01; break;
+ case CS7: cval = 0x02; break;
+ case CS8: cval = 0x03; break;
+ /* Never happens, but GCC is too dumb to figure it out */
+ default: cval = 0x00; break;
+ }
+ if (info->cflag & CSTOPB) {
+ cval |= 0x04;
+ }
+ if (info->cflag & PARENB) {
+ cval |= UART_LCR_PARITY;
+ }
+ if (!(info->cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+ if (info->cflag & CMSPAR)
+ cval |= UART_LCR_SPAR;
+#endif
+
+ /* Determine divisor based on baud rate */
+ i = info->cflag & CBAUD;
+ if (i & CBAUDEX) {
+ i &= ~CBAUDEX;
+ if (i < 1 || i > 4)
+ info->cflag &= ~CBAUDEX;
+ else
+ i += 15;
+ }
+ if (!quot) {
+ if (baud_table[i] == 134)
+ /* Special case since 134 is really 134.5 */
+ quot = (2 * info->baud_base / 269);
+ else if (baud_table[i])
+ quot = info->baud_base / baud_table[i];
+ /* If the quotient is ever zero, default to 1200 bps */
+ if (!quot)
+ quot = info->baud_base / 1200;
+ }
+
+ /* Set up FIFO's */
+ if (uart_config[info->type].flags & UART_USE_FIFO)
+ fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
+
+ su_outb(info, UART_IER, info->IER);
+
+ save_flags(flags); cli();
+ su_outb(info, UART_LCR, cval | UART_LCR_DLAB); /* set DLAB */
+ su_outb(info, UART_DLL, quot & 0xff); /* LS of divisor */
+ su_outb(info, UART_DLM, quot >> 8); /* MS of divisor */
+ if (info->type == PORT_16750)
+ su_outb(info, UART_FCR, fcr); /* set fcr */
+ su_outb(info, UART_LCR, cval); /* reset DLAB */
+ if (info->type != PORT_16750)
+ su_outb(info, UART_FCR, fcr); /* set fcr */
+ restore_flags(flags);
+}
+
+static void
+su_put_char(unsigned char c)
+{
+ struct su_struct *info = su_table;
+ int lsr;
+
+ if (!info->kbd_node)
+ ++info;
+
+ do {
+ lsr = inb(info->port + UART_LSR);
+ } while (!(lsr & UART_LSR_THRE));
+
+ /* Send the character out. */
+ su_outb(info, UART_TX, c);
+}
+
+static void
+su_change_mouse_baud(int baud)
+{
+ struct su_struct *info = su_table;
+
+ if (!info->ms_node)
+ ++info;
+ if (!info)
+ return;
+
+ info->cflag &= ~(CBAUDEX | CBAUD);
+ switch(baud) {
+ case 1200:
+ info->cflag |= B1200;
+ break;
+ case 2400:
+ info->cflag |= B2400;
+ break;
+ case 4800:
+ info->cflag |= B4800;
+ break;
+ case 9600:
+ info->cflag |= B9600;
+ break;
+ default:
+ printk("su_change_mouse_baud: unknown baud rate %d, "
+ "defaulting to 1200\n", baud);
+ info->cflag |= 1200;
+ break;
+ }
+ change_speed(info);
+}
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+static inline void
+show_su_version(void)
+{
+ printk(KERN_INFO "%s version %s\n", serial_name, serial_version);
+}
+
+/*
+ * This routine is called by su_init() to initialize a specific serial
+ * port. It determines what type of UART chip this serial port is
+ * using: 8250, 16450, 16550, 16550A. The important question is
+ * whether or not this UART is a 16550A or not, since this will
+ * determine whether or not we can use its FIFO features or not.
+ */
+static void
+autoconfig(struct su_struct *info)
+{
+ unsigned char status1, status2, scratch, scratch2;
+ struct linux_ebus_device *dev;
+ struct linux_ebus *ebus;
+ unsigned long flags;
+
+ for_all_ebusdev(dev, ebus) {
+ if (!strncmp(dev->prom_name, "su", 2)) {
+ if (dev->prom_node == info->kbd_node)
+ break;
+ if (dev->prom_node == info->ms_node)
+ break;
+ }
+ }
+ if (!dev)
+ return;
+
+ info->port = dev->base_address[0];
+ if (check_region(info->port, 8))
+ return;
+
+ info->irq = dev->irqs[0];
+
+#ifdef DEBUG_SERIAL_OPEN
+ printk("Found 'su' at %016lx IRQ %08x\n",
+ dev->base_address[0], dev->irqs[0]);
+#endif
+
+ info->magic = SERIAL_MAGIC;
+
+ save_flags(flags); cli();
+
+ /*
+ * Do a simple existence test first; if we fail this, there's
+ * no point trying anything else.
+ *
+ * 0x80 is used as a nonsense port to prevent against false
+ * positives due to ISA bus float. The assumption is that
+ * 0x80 is a non-existent port; which should be safe since
+ * include/asm/io.h also makes this assumption.
+ */
+ scratch = su_inb(info, UART_IER);
+ su_outb(info, UART_IER, 0);
+ scratch2 = su_inb(info, UART_IER);
+ su_outb(info, UART_IER, scratch);
+ if (scratch2) {
+ restore_flags(flags);
+ return; /* We failed; there's nothing here */
+ }
+
+ scratch = su_inb(info, UART_MCR);
+ su_outb(info, UART_MCR, UART_MCR_LOOP | scratch);
+ scratch2 = su_inb(info, UART_MSR);
+ su_outb(info, UART_MCR, UART_MCR_LOOP | 0x0A);
+ status1 = su_inb(info, UART_MSR) & 0xF0;
+ su_outb(info, UART_MCR, scratch);
+ su_outb(info, UART_MSR, scratch2);
+ if (status1 != 0x90) {
+ restore_flags(flags);
+ return;
+ }
+
+ scratch2 = su_inb(info, UART_LCR);
+ su_outb(info, UART_LCR, 0xBF); /* set up for StarTech test */
+ su_outb(info, UART_EFR, 0); /* EFR is the same as FCR */
+ su_outb(info, UART_LCR, 0);
+ su_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO);
+ scratch = su_inb(info, UART_IIR) >> 6;
+ switch (scratch) {
+ case 0:
+ info->type = PORT_16450;
+ break;
+ case 1:
+ info->type = PORT_UNKNOWN;
+ break;
+ case 2:
+ info->type = PORT_16550;
+ break;
+ case 3:
+ info->type = PORT_16550A;
+ break;
+ }
+ if (info->type == PORT_16550A) {
+ /* Check for Startech UART's */
+ su_outb(info, UART_LCR, scratch2 | UART_LCR_DLAB);
+ if (su_inb(info, UART_EFR) == 0) {
+ info->type = PORT_16650;
+ } else {
+ su_outb(info, UART_LCR, 0xBF);
+ if (su_inb(info, UART_EFR) == 0)
+ info->type = PORT_16650V2;
+ }
+ }
+ if (info->type == PORT_16550A) {
+ /* Check for TI 16750 */
+ su_outb(info, UART_LCR, scratch2 | UART_LCR_DLAB);
+ su_outb(info, UART_FCR,
+ UART_FCR_ENABLE_FIFO | UART_FCR7_64BYTE);
+ scratch = su_inb(info, UART_IIR) >> 5;
+ if (scratch == 7) {
+ su_outb(info, UART_LCR, 0);
+ su_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO);
+ scratch = su_inb(info, UART_IIR) >> 5;
+ if (scratch == 6)
+ info->type = PORT_16750;
+ }
+ su_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO);
+ }
+ su_outb(info, UART_LCR, scratch2);
+ if (info->type == PORT_16450) {
+ scratch = su_inb(info, UART_SCR);
+ su_outb(info, UART_SCR, 0xa5);
+ status1 = su_inb(info, UART_SCR);
+ su_outb(info, UART_SCR, 0x5a);
+ status2 = su_inb(info, UART_SCR);
+ su_outb(info, UART_SCR, scratch);
+
+ if ((status1 != 0xa5) || (status2 != 0x5a))
+ info->type = PORT_8250;
+ }
+
+ if (info->type == PORT_UNKNOWN) {
+ restore_flags(flags);
+ return;
+ }
+
+ sprintf(info->name, "su(%s)", info->ms_node ? "mouse" : "kbd");
+ request_region(info->port, 8, info->name);
+
+ /*
+ * Reset the UART.
+ */
+ su_outb(info, UART_MCR, 0x00);
+ su_outb(info, UART_FCR, (UART_FCR_CLEAR_RCVR |
+ UART_FCR_CLEAR_XMIT));
+ su_inb(info, UART_RX);
+
+ restore_flags(flags);
+}
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+__initfunc(int su_init(void))
+{
+ int i;
+ struct su_struct *info;
+
+ show_su_version();
+
+ for (i = 0, info = su_table; i < NR_PORTS; i++, info++) {
+ info->line = i;
+ if (info->kbd_node)
+ info->cflag = B1200 | CS8 | CREAD;
+ else
+ info->cflag = B4800 | CS8 | CREAD;
+
+ autoconfig(info);
+ if (info->type == PORT_UNKNOWN)
+ continue;
+
+ printk(KERN_INFO "%s at %16lx (irq = %08x) is a %s\n",
+ info->name, info->port, info->irq,
+ uart_config[info->type].name);
+
+ startup(info);
+ if (info->kbd_node)
+ keyboard_zsinit(su_put_char);
+ else
+ sun_mouse_zsinit();
+ }
+ return 0;
+}
+
+__initfunc(int su_probe (unsigned long *memory_start))
+{
+ struct su_struct *info = su_table;
+ int node, enode, sunode;
+ int kbnode = 0, msnode = 0;
+ int devices = 0;
+ char prop[128];
+ int len;
+
+ /*
+ * Get the nodes for keyboard and mouse from 'aliases'...
+ */
+ node = prom_getchild(prom_root_node);
+ node = prom_searchsiblings(node, "aliases");
+ if (!node)
+ return -ENODEV;
+
+ len = prom_getproperty(node, "keyboard", prop, sizeof(prop));
+ if (len > 0)
+ kbnode = prom_pathtoinode(prop);
+ if (!kbnode)
+ return -ENODEV;
+
+ len = prom_getproperty(node, "mouse", prop, sizeof(prop));
+ if (len > 0)
+ msnode = prom_pathtoinode(prop);
+ if (!msnode)
+ return -ENODEV;
+
+ /*
+ * Find matching EBus nodes...
+ */
+ node = prom_getchild(prom_root_node);
+ node = prom_searchsiblings(node, "pci");
+
+ /*
+ * For each PCI bus...
+ */
+ while (node) {
+ enode = prom_getchild(node);
+ enode = prom_searchsiblings(enode, "ebus");
+
+ /*
+ * For each EBus on this PCI...
+ */
+ while (enode) {
+ sunode = prom_getchild(enode);
+ sunode = prom_searchsiblings(sunode, "su");
+
+ /*
+ * For each 'su' on this EBus...
+ */
+ while (sunode) {
+ /*
+ * Does it match?
+ */
+ if (sunode == kbnode) {
+ info->kbd_node = kbnode;
+ ++info;
+ ++devices;
+ }
+ if (sunode == msnode) {
+ info->ms_node = msnode;
+ ++info;
+ ++devices;
+ }
+
+ /*
+ * Found everything we need?
+ */
+ if (devices == NR_PORTS)
+ goto found;
+
+ sunode = prom_getsibling(sunode);
+ sunode = prom_searchsiblings(sunode, "su");
+ }
+ enode = prom_getsibling(enode);
+ enode = prom_searchsiblings(enode, "ebus");
+ }
+ node = prom_getsibling(node);
+ node = prom_searchsiblings(node, "pci");
+ }
+ return -ENODEV;
+
+found:
+ sunserial_setinitfunc(memory_start, su_init);
+ rs_ops.rs_change_mouse_baud = su_change_mouse_baud;
+ return 0;
+}
FUNET's LINUX-ADM group, [email protected]
TCL-scripts by Sam Shen, [email protected]