patch-2.4.23 linux-2.4.23/arch/ia64/sn/io/sn2/ioc4/sio_ioc4.c
Next file: linux-2.4.23/arch/ia64/sn/io/sn2/klconflib.c
Previous file: linux-2.4.23/arch/ia64/sn/io/sn2/ioc4/ioc4.c
Back to the patch index
Back to the overall index
- Lines: 2267
- Date:
2003-11-28 10:26:19.000000000 -0800
- Orig file:
linux-2.4.22/arch/ia64/sn/io/sn2/ioc4/sio_ioc4.c
- Orig date:
1969-12-31 16:00:00.000000000 -0800
diff -urN linux-2.4.22/arch/ia64/sn/io/sn2/ioc4/sio_ioc4.c linux-2.4.23/arch/ia64/sn/io/sn2/ioc4/sio_ioc4.c
@@ -0,0 +1,2266 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2003 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+/*
+ * This is a lower level module for the modular serial I/O driver. This
+ * module implements all hardware dependent functions for doing serial
+ * I/O on the IOC4 serial ports.
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <asm/sn/types.h>
+#include <asm/sn/sgi.h>
+#include <asm/sn/invent.h>
+#include <asm/sn/driver.h>
+#include <asm/sn/iograph.h>
+#include <asm/param.h>
+#include <asm/atomic.h>
+#include <asm/delay.h>
+#include <asm/semaphore.h>
+#include <asm/sn/pio.h>
+#include <asm/sn/xtalk/xwidget.h>
+#include <asm/sn/io.h>
+#include <asm/sn/pci/pci_defs.h>
+#include <asm/sn/pci/pciio.h>
+#include <asm/sn/ioc4.h>
+#include <asm/sn/serialio.h>
+#include <asm/sn/uart16550.h>
+
+/* #define IOC4_SIO_DEBUG */
+/* define USE_64BIT_DMA */
+
+#define PENDING(port) (PCI_INW(&(port)->ip_ioc4->sio_ir) & port->ip_ienb)
+
+/* Default to 4k buffers */
+#ifdef IOC4_1K_BUFFERS
+#define RING_BUF_SIZE 1024
+#define IOC4_BUF_SIZE_BIT 0
+#define PROD_CONS_MASK IOC4_PROD_CONS_PTR_1K
+#else
+#define RING_BUF_SIZE 4096
+#define IOC4_BUF_SIZE_BIT IOC4_SBBR_L_SIZE
+#define PROD_CONS_MASK IOC4_PROD_CONS_PTR_4K
+#endif
+
+#define TOTAL_RING_BUF_SIZE (RING_BUF_SIZE * 4)
+
+#if PAGE_SIZE < TOTAL_RING_BUF_SIZE
+#include <sys/pfdat.h>
+#endif
+
+
+#ifdef DPRINTF
+#define dprintf(x) printk x
+#else
+#define dprintf(x)
+#endif
+
+#define NEWA(ptr,n) (ptr = snia_kmem_zalloc((n)*sizeof (*(ptr))))
+
+#define contig_memalloc(a,b,c) kmem_zalloc(PAGE_SIZE * (a))
+#define sio_port_islocked(a) 0 // FIXME: ?????
+
+#define KM_PHYSCONTIG 0x0008
+#define VM_DIRECT KM_PHYSCONTIG
+#define VM_PHYSCONTIG KM_PHYSCONTIG
+
+#ifdef DEBUG
+#define PROGRESS() printk("%s : %d\n", __FUNCTION__, __LINE__)
+#define NOT_PROGRESS() printk("%s : %d - Error\n", __FUNCTION__, __LINE__)
+#else
+#define PROGRESS() ;
+#define NOT_PROGRESS() ;
+#endif
+
+static __inline__ void *
+kvpalloc(size_t size, int flags, int colour)
+{
+ if (flags & (VM_DIRECT|VM_PHYSCONTIG)) {
+ int order = 0;
+ while ((PAGE_SIZE << order) < (size << PAGE_SHIFT))
+ order++;
+ return (void *) __get_free_pages(GFP_KERNEL, order);
+ } else
+ return vmalloc(size << PAGE_SHIFT);
+}
+
+/* Local port info for the IOC4 serial ports. This contains as its
+ * first member the global sio port private data.
+ */
+typedef struct ioc4port {
+ sioport_t ip_sioport; /* Must be first struct entry! */
+
+ vertex_hdl_t ip_conn_vhdl; /* vhdl to use for pciio requests */
+ vertex_hdl_t ip_port_vhdl; /* vhdl for the serial port */
+
+ /* Base piomap addr of the ioc4 board this port is on
+ * and associated serial map; serial map includes uart registers.
+ */
+ ioc4_mem_t *ip_ioc4;
+ ioc4_sregs_t *ip_serial;
+ ioc4_uart_t *ip_uart;
+
+ /* Ring buffer page for this port */
+ caddr_t ip_ring_buf_k0; /* Ring buffer location in K0 space */
+
+ /* Rings for this port */
+ struct ring *ip_inring;
+ struct ring *ip_outring;
+
+ /* Hook to port specific values for this port */
+ struct hooks *ip_hooks;
+
+ int ip_flags;
+
+ /* Cache of DCD/CTS bits last received */
+ char ip_modem_bits;
+
+ /* Various rx/tx parameters */
+ int ip_baud;
+ int ip_tx_lowat;
+ int ip_rx_timeout;
+
+ /* Copy of notification bits */
+ int ip_notify;
+
+ /* Shadow copies of various registers so we don't need to PIO
+ * read them constantly
+ */
+ ioc4reg_t ip_ienb; /* Enabled interrupts */
+
+ ioc4reg_t ip_sscr;
+
+ ioc4reg_t ip_tx_prod;
+ ioc4reg_t ip_rx_cons;
+
+ /* Back pointer to ioc4 soft area */
+ void *ip_ioc4_soft;
+} ioc4port_t;
+
+#if DEBUG
+#define MAXSAVEPORT 256
+static int next_saveport = 0;
+static ioc4port_t *saveport[MAXSAVEPORT];
+#endif
+
+/* TX low water mark. We need to notify the driver whenever TX is getting
+ * close to empty so it can refill the TX buffer and keep things going.
+ * Let's assume that if we interrupt 1 ms before the TX goes idle, we'll
+ * have no trouble getting in more chars in time (I certainly hope so).
+ */
+#define TX_LOWAT_LATENCY 1000
+#define TX_LOWAT_HZ (1000000 / TX_LOWAT_LATENCY)
+#define TX_LOWAT_CHARS(baud) (baud / 10 / TX_LOWAT_HZ)
+
+/* Flags per port */
+#define INPUT_HIGH 0x01
+#define DCD_ON 0x02
+#define LOWAT_WRITTEN 0x04
+#define READ_ABORTED 0x08
+#define TX_DISABLED 0x10
+
+/* Get local port type from global sio port type */
+#define LPORT(port) ((ioc4port_t *) (port))
+
+/* Get global port from local port type */
+#define GPORT(port) ((sioport_t *) (port))
+
+/* Since each port has different register offsets and bitmasks
+ * for everything, we'll store those that we need in tables so we
+ * don't have to be constantly checking the port we are dealing with.
+ */
+struct hooks {
+ ioc4reg_t intr_delta_dcd;
+ ioc4reg_t intr_delta_cts;
+ ioc4reg_t intr_tx_mt;
+ ioc4reg_t intr_rx_timer;
+ ioc4reg_t intr_rx_high;
+ ioc4reg_t intr_tx_explicit;
+ ioc4reg_t intr_dma_error;
+ ioc4reg_t intr_clear;
+ ioc4reg_t intr_all;
+ char rs422_select_pin;
+};
+
+static struct hooks hooks_array[4] =
+{
+ /* Values for port 0 */
+ {
+ IOC4_SIO_IR_S0_DELTA_DCD,
+ IOC4_SIO_IR_S0_DELTA_CTS,
+ IOC4_SIO_IR_S0_TX_MT,
+ IOC4_SIO_IR_S0_RX_TIMER,
+ IOC4_SIO_IR_S0_RX_HIGH,
+ IOC4_SIO_IR_S0_TX_EXPLICIT,
+ IOC4_OTHER_IR_S0_MEMERR,
+ (IOC4_SIO_IR_S0_TX_MT | IOC4_SIO_IR_S0_RX_FULL |
+ IOC4_SIO_IR_S0_RX_HIGH | IOC4_SIO_IR_S0_RX_TIMER |
+ IOC4_SIO_IR_S0_DELTA_DCD | IOC4_SIO_IR_S0_DELTA_CTS |
+ IOC4_SIO_IR_S0_INT | IOC4_SIO_IR_S0_TX_EXPLICIT),
+ IOC4_SIO_IR_S0,
+ IOC4_GPPR_UART0_MODESEL_PIN,
+ },
+
+ /* Values for port 1 */
+ {
+ IOC4_SIO_IR_S1_DELTA_DCD,
+ IOC4_SIO_IR_S1_DELTA_CTS,
+ IOC4_SIO_IR_S1_TX_MT,
+ IOC4_SIO_IR_S1_RX_TIMER,
+ IOC4_SIO_IR_S1_RX_HIGH,
+ IOC4_SIO_IR_S1_TX_EXPLICIT,
+ IOC4_OTHER_IR_S1_MEMERR,
+ (IOC4_SIO_IR_S1_TX_MT | IOC4_SIO_IR_S1_RX_FULL |
+ IOC4_SIO_IR_S1_RX_HIGH | IOC4_SIO_IR_S1_RX_TIMER |
+ IOC4_SIO_IR_S1_DELTA_DCD | IOC4_SIO_IR_S1_DELTA_CTS |
+ IOC4_SIO_IR_S1_INT | IOC4_SIO_IR_S1_TX_EXPLICIT),
+ IOC4_SIO_IR_S1,
+ IOC4_GPPR_UART1_MODESEL_PIN,
+ },
+
+ /* Values for port 2 */
+ {
+ IOC4_SIO_IR_S2_DELTA_DCD,
+ IOC4_SIO_IR_S2_DELTA_CTS,
+ IOC4_SIO_IR_S2_TX_MT,
+ IOC4_SIO_IR_S2_RX_TIMER,
+ IOC4_SIO_IR_S2_RX_HIGH,
+ IOC4_SIO_IR_S2_TX_EXPLICIT,
+ IOC4_OTHER_IR_S2_MEMERR,
+ (IOC4_SIO_IR_S2_TX_MT | IOC4_SIO_IR_S2_RX_FULL |
+ IOC4_SIO_IR_S2_RX_HIGH | IOC4_SIO_IR_S2_RX_TIMER |
+ IOC4_SIO_IR_S2_DELTA_DCD | IOC4_SIO_IR_S2_DELTA_CTS |
+ IOC4_SIO_IR_S2_INT | IOC4_SIO_IR_S2_TX_EXPLICIT),
+ IOC4_SIO_IR_S2,
+ IOC4_GPPR_UART2_MODESEL_PIN,
+ },
+
+ /* Values for port 3 */
+ {
+ IOC4_SIO_IR_S3_DELTA_DCD,
+ IOC4_SIO_IR_S3_DELTA_CTS,
+ IOC4_SIO_IR_S3_TX_MT,
+ IOC4_SIO_IR_S3_RX_TIMER,
+ IOC4_SIO_IR_S3_RX_HIGH,
+ IOC4_SIO_IR_S3_TX_EXPLICIT,
+ IOC4_OTHER_IR_S3_MEMERR,
+ (IOC4_SIO_IR_S3_TX_MT | IOC4_SIO_IR_S3_RX_FULL |
+ IOC4_SIO_IR_S3_RX_HIGH | IOC4_SIO_IR_S3_RX_TIMER |
+ IOC4_SIO_IR_S3_DELTA_DCD | IOC4_SIO_IR_S3_DELTA_CTS |
+ IOC4_SIO_IR_S3_INT | IOC4_SIO_IR_S3_TX_EXPLICIT),
+ IOC4_SIO_IR_S3,
+ IOC4_GPPR_UART3_MODESEL_PIN,
+ }
+};
+
+/* Macros to get into the port hooks. Require a variable called
+ * hooks set to port->hooks
+ */
+#define H_INTR_TX_MT hooks->intr_tx_mt
+#define H_INTR_RX_TIMER hooks->intr_rx_timer
+#define H_INTR_RX_HIGH hooks->intr_rx_high
+#define H_INTR_TX_EXPLICIT hooks->intr_tx_explicit
+#define H_INTR_DMA_ERROR hooks->intr_dma_error
+#define H_INTR_CLEAR hooks->intr_clear
+#define H_INTR_DELTA_DCD hooks->intr_delta_dcd
+#define H_INTR_DELTA_CTS hooks->intr_delta_cts
+#define H_INTR_ALL hooks->intr_all
+#define H_RS422 hooks->rs422_select_pin
+
+/* A ring buffer entry */
+struct ring_entry {
+ union {
+ struct {
+ uint32_t alldata;
+ uint32_t allsc;
+ } all;
+ struct {
+ char data[4]; /* data bytes */
+ char sc[4]; /* status/control */
+ } s;
+ } u;
+};
+
+/* Test the valid bits in any of the 4 sc chars using "allsc" member */
+#define RING_ANY_VALID \
+ ((uint32_t) (IOC4_RXSB_MODEM_VALID | IOC4_RXSB_DATA_VALID) * 0x01010101)
+
+#define ring_sc u.s.sc
+#define ring_data u.s.data
+#define ring_allsc u.all.allsc
+
+/* Number of entries per ring buffer. */
+#define ENTRIES_PER_RING (RING_BUF_SIZE / (int) sizeof(struct ring_entry))
+
+/* An individual ring */
+struct ring {
+ struct ring_entry entries[ENTRIES_PER_RING];
+};
+
+/* The whole enchilada */
+struct ring_buffer {
+ struct ring TX_0_OR_2;
+ struct ring RX_0_OR_2;
+ struct ring TX_1_OR_3;
+ struct ring RX_1_OR_3;
+};
+
+/* Get a ring from a port struct */
+#define RING(port, which) \
+ &(((struct ring_buffer *) ((port)->ip_ring_buf_k0))->which)
+
+/* Local functions: */
+static int ioc4_open (sioport_t *port);
+static int ioc4_config (sioport_t *port, int baud, int byte_size,
+ int stop_bits, int parenb, int parodd);
+static int ioc4_enable_hfc (sioport_t *port, int enable);
+static int ioc4_set_extclk (sioport_t *port, int clock_factor);
+
+/* Data transmission */
+static int do_ioc4_write (sioport_t *port, char *buf, int len);
+static int ioc4_write (sioport_t *port, char *buf, int len);
+static int ioc4_sync_write (sioport_t *port, char *buf, int len);
+static void ioc4_wrflush (sioport_t *port);
+static int ioc4_break (sioport_t *port, int brk);
+static int ioc4_enable_tx (sioport_t *port, int enb);
+
+/* Data reception */
+static int ioc4_read (sioport_t *port, char *buf, int len);
+
+/* Event notification */
+static int ioc4_notification (sioport_t *port, int mask, int on);
+static int ioc4_rx_timeout (sioport_t *port, int timeout);
+
+/* Modem control */
+static int ioc4_set_DTR (sioport_t *port, int dtr);
+static int ioc4_set_RTS (sioport_t *port, int rts);
+static int ioc4_query_DCD (sioport_t *port);
+static int ioc4_query_CTS (sioport_t *port);
+
+/* Output mode */
+static int ioc4_set_proto (sioport_t *port, enum sio_proto proto);
+
+/* User mapped driver support */
+static int ioc4_get_mapid (sioport_t *port, void *arg);
+static int ioc4_set_sscr (sioport_t *port, int arg, int flag);
+
+static struct serial_calldown ioc4_calldown = {
+ ioc4_open,
+ ioc4_config,
+ ioc4_enable_hfc,
+ ioc4_set_extclk,
+ ioc4_write,
+ ioc4_sync_write,
+ ioc4_wrflush, /* du flush */
+ ioc4_break,
+ ioc4_enable_tx,
+ ioc4_read,
+ ioc4_notification,
+ ioc4_rx_timeout,
+ ioc4_set_DTR,
+ ioc4_set_RTS,
+ ioc4_query_DCD,
+ ioc4_query_CTS,
+ ioc4_set_proto,
+ ioc4_get_mapid,
+ 0,
+ 0,
+ ioc4_set_sscr
+};
+
+/* Baud rate stuff */
+#define SET_BAUD(p, b) set_baud_ti(p, b)
+static int set_baud_ti(ioc4port_t *, int);
+
+#ifdef DEBUG
+/* Performance characterization logging */
+#define DEBUGINC(x,i) stats.x += i
+
+static struct {
+
+ /* Ports present */
+ uint ports;
+
+ /* Ports killed */
+ uint killed;
+
+ /* Interrupt counts */
+ uint total_intr;
+ uint port_0_intr;
+ uint port_1_intr;
+ uint ddcd_intr;
+ uint dcts_intr;
+ uint rx_timer_intr;
+ uint rx_high_intr;
+ uint explicit_intr;
+ uint mt_intr;
+ uint mt_lowat_intr;
+
+ /* Write characteristics */
+ uint write_bytes;
+ uint write_cnt;
+ uint wrote_bytes;
+ uint tx_buf_used;
+ uint tx_buf_cnt;
+ uint tx_pio_cnt;691
+
+ /* Read characteristics */
+ uint read_bytes;
+ uint read_cnt;
+ uint drain;
+ uint drainwait;
+ uint resetdma;
+ uint read_ddcd;
+ uint rx_overrun;
+ uint parity;
+ uint framing;
+ uint brk;
+ uint red_bytes;
+ uint rx_buf_used;
+ uint rx_buf_cnt;
+
+ /* Errors */
+ uint dma_lost;
+ uint read_aborted;
+ uint read_aborted_detected;
+} stats;
+
+#else
+#define DEBUGINC(x,i)
+#endif
+
+/* Infinite loop detection.
+ */
+#define MAXITER 1000000
+#define SPIN(cond, success) \
+{ \
+ int spiniter = 0; \
+ success = 1; \
+ while(cond) { \
+ spiniter++; \
+ if (spiniter > MAXITER) { \
+ success = 0; \
+ break; \
+ } \
+ } \
+}
+
+
+static iopaddr_t
+ring_dmatrans(vertex_hdl_t conn_vhdl, caddr_t vaddr)
+{
+ extern iopaddr_t pciio_dma_addr (vertex_hdl_t, device_desc_t, paddr_t,
+ size_t, pciio_dmamap_t *, unsigned);
+ iopaddr_t paddr = (iopaddr_t)vaddr;
+
+ if (conn_vhdl != GRAPH_VERTEX_NONE)
+#ifdef USE_64BIT_DMA
+ /* Use 64-bit DMA address when the IOC4 supports it */
+ return pciio_dmatrans_addr (conn_vhdl, 0, paddr, TOTAL_RING_BUF_SIZE, PCIIO_DMA_A64 | PCIIO_BYTE_STREAM);
+
+#else
+ /* Use 32-bit DMA address for current IOC4 */
+ return pciio_dma_addr (conn_vhdl, 0, paddr, TOTAL_RING_BUF_SIZE, NULL, PCIIO_BYTE_STREAM);
+#endif
+
+ return paddr;
+}
+
+
+/* If interrupt routine called enable_intrs, then would need to write
+ * mask_enable_intrs() routine.
+ */
+static inline void
+mask_disable_intrs(ioc4port_t *port, ioc4reg_t mask)
+{
+ port->ip_ienb &= ~mask;
+}
+
+
+static void
+enable_intrs(ioc4port_t *port, ioc4reg_t mask)
+{
+ struct hooks *hooks = port->ip_hooks;
+
+ if ((port->ip_ienb & mask) != mask) {
+ IOC4_WRITE_IES(port->ip_ioc4_soft, mask, ioc4_sio_intr_type);
+ port->ip_ienb |= mask;
+ }
+
+ if (port->ip_ienb)
+ IOC4_WRITE_IES(port->ip_ioc4_soft, H_INTR_DMA_ERROR, ioc4_other_intr_type);
+}
+
+
+static void
+disable_intrs(ioc4port_t *port, ioc4reg_t mask)
+{
+ struct hooks *hooks = port->ip_hooks;
+
+ if (port->ip_ienb & mask) {
+ IOC4_WRITE_IEC(port->ip_ioc4_soft, mask, ioc4_sio_intr_type);
+ port->ip_ienb &= ~mask;
+ }
+
+ if (!port->ip_ienb)
+ IOC4_WRITE_IEC(port->ip_ioc4_soft, H_INTR_DMA_ERROR, ioc4_other_intr_type);
+}
+
+
+/* Service any pending interrupts on the given port */
+static void
+ioc4_serial_intr(intr_arg_t arg, ioc4reg_t sio_ir)
+{
+ ioc4port_t *port = (ioc4port_t *) arg;
+ sioport_t *gp = GPORT(port);
+ struct hooks *hooks = port->ip_hooks;
+ unsigned rx_high_rd_aborted = 0;
+ unsigned int flags;
+
+ PROGRESS();
+#ifdef NOT_YET
+ ASSERT(sio_port_islocked(gp) == 0);
+#endif
+
+ /* Possible race condition here: The TX_MT interrupt bit may be
+ * cleared without the intervention of the interrupt handler,
+ * e.g. by a write. If the top level interrupt handler reads a
+ * TX_MT, then some other processor does a write, starting up
+ * output, then we come in here, see the TX_MT and stop DMA, the
+ * output started by the other processor will hang. Thus we can
+ * only rely on TX_MT being legitimate if it is read while the
+ * port lock is held. Therefore this bit must be ignored in the
+ * passed in interrupt mask which was read by the top level
+ * interrupt handler since the port lock was not held at the time
+ * it was read. We can only rely on this bit being accurate if it
+ * is read while the port lock is held. So we'll clear it for now,
+ * and reload it later once we have the port lock.
+ */
+ sio_ir &= ~(H_INTR_TX_MT);
+
+ SIO_LOCK_PORT(gp, flags);
+
+ dprintf(("interrupt: sio_ir 0x%x\n", sio_ir));
+
+ do {
+ ioc4reg_t shadow;
+
+ /* Handle a DCD change */
+ if (sio_ir & H_INTR_DELTA_DCD) {
+ DEBUGINC(ddcd_intr, 1);
+
+ PROGRESS();
+ /* ACK the interrupt */
+ PCI_OUTW(&port->ip_ioc4->sio_ir, H_INTR_DELTA_DCD);
+
+ /* If DCD has raised, notify upper layer. Otherwise
+ * wait for a record to be posted to notify of a dropped DCD.
+ */
+ shadow = PCI_INW(&port->ip_serial->shadow);
+
+ if (port->ip_notify & N_DDCD) {
+ PROGRESS();
+ if (shadow & IOC4_SHADOW_DCD) /* Notify upper layer of DCD */
+ UP_DDCD(gp, 1);
+ else
+ port->ip_flags |= DCD_ON; /* Flag delta DCD/no DCD */
+ }
+ }
+
+ /* Handle a CTS change */
+ if (sio_ir & H_INTR_DELTA_CTS) {
+ DEBUGINC(dcts_intr, 1);
+ PROGRESS();
+
+ /* ACK the interrupt */
+ PCI_OUTW(&port->ip_ioc4->sio_ir, H_INTR_DELTA_CTS);
+
+ shadow = PCI_INW(&port->ip_serial->shadow);
+
+ /* Notify upper layer */
+ if (port->ip_notify & N_DCTS) {
+ if (shadow & IOC4_SHADOW_CTS)
+ UP_DCTS(gp, 1);
+ else
+ UP_DCTS(gp, 0);
+ }
+ }
+
+ /* RX timeout interrupt. Must be some data available. Put this
+ * before the check for RX_HIGH since servicing this condition
+ * may cause that condition to clear.
+ */
+ if (sio_ir & H_INTR_RX_TIMER) {
+ PROGRESS();
+ DEBUGINC(rx_timer_intr, 1);
+
+ /* ACK the interrupt */
+ PCI_OUTW(&port->ip_ioc4->sio_ir, H_INTR_RX_TIMER);
+
+ if (port->ip_notify & N_DATA_READY)
+ UP_DATA_READY(gp);
+ }
+
+ /* RX high interrupt. Must be after RX_TIMER.
+ */
+ else if (sio_ir & H_INTR_RX_HIGH) {
+ DEBUGINC(rx_high_intr, 1);
+
+ PROGRESS();
+ /* Data available, notify upper layer */
+ if (port->ip_notify & N_DATA_READY)
+ UP_DATA_READY(gp);
+
+ /* We can't ACK this interrupt. If up_data_ready didn't
+ * cause the condition to clear, we'll have to disable
+ * the interrupt until the data is drained by the upper layer.
+ * If the read was aborted, don't disable the interrupt as
+ * this may cause us to hang indefinitely. An aborted read
+ * generally means that this interrupt hasn't been delivered
+ * to the cpu yet anyway, even though we see it as asserted
+ * when we read the sio_ir.
+ */
+ if ((sio_ir = PENDING(port)) & H_INTR_RX_HIGH) {
+ PROGRESS();
+ if ((port->ip_flags & READ_ABORTED) == 0) {
+ mask_disable_intrs(port, H_INTR_RX_HIGH);
+ port->ip_flags |= INPUT_HIGH;
+ }
+ else {
+ DEBUGINC(read_aborted_detected, 1);
+ /* We will be stuck in this loop forever,
+ * higher level will never get time to finish
+ */
+ rx_high_rd_aborted++;
+ }
+ }
+ }
+
+ /* We got a low water interrupt: notify upper layer to
+ * send more data. Must come before TX_MT since servicing
+ * this condition may cause that condition to clear.
+ */
+ if (sio_ir & H_INTR_TX_EXPLICIT) {
+ DEBUGINC(explicit_intr, 1);
+ PROGRESS();
+
+ port->ip_flags &= ~LOWAT_WRITTEN;
+
+ /* ACK the interrupt */
+ PCI_OUTW(&port->ip_ioc4->sio_ir, H_INTR_TX_EXPLICIT);
+
+ if (port->ip_notify & N_OUTPUT_LOWAT)
+ UP_OUTPUT_LOWAT(gp);
+ }
+
+ /* Handle TX_MT. Must come after TX_EXPLICIT.
+ */
+ else if (sio_ir & H_INTR_TX_MT) {
+ DEBUGINC(mt_intr, 1);
+ PROGRESS();
+
+ /* If the upper layer is expecting a lowat notification
+ * and we get to this point it probably means that for
+ * some reason the TX_EXPLICIT didn't work as expected
+ * (that can legitimately happen if the output buffer is
+ * filled up in just the right way). So sent the notification
+ * now.
+ */
+ if (port->ip_notify & N_OUTPUT_LOWAT) {
+ DEBUGINC(mt_lowat_intr, 1);
+ PROGRESS();
+
+ if (port->ip_notify & N_OUTPUT_LOWAT)
+ UP_OUTPUT_LOWAT(gp);
+
+ /* We need to reload the sio_ir since the upcall may
+ * have caused another write to occur, clearing
+ * the TX_MT condition.
+ */
+ sio_ir = PENDING(port);
+ }
+
+ /* If the TX_MT condition still persists even after the upcall,
+ * we've got some work to do.
+ */
+ if (sio_ir & H_INTR_TX_MT) {
+
+ PROGRESS();
+
+ /* If we are not currently expecting DMA input, and the
+ * transmitter has just gone idle, there is no longer any
+ * reason for DMA, so disable it.
+ */
+ if (!(port->ip_notify & (N_DATA_READY | N_DDCD))) {
+ ASSERT(port->ip_sscr & IOC4_SSCR_DMA_EN);
+ port->ip_sscr &= ~IOC4_SSCR_DMA_EN;
+ PCI_OUTW(&port->ip_serial->sscr, port->ip_sscr);
+ }
+
+ /* Prevent infinite TX_MT interrupt */
+ mask_disable_intrs(port, H_INTR_TX_MT);
+ }
+ }
+
+ sio_ir = PENDING(port);
+
+ /* if the read was aborted and only H_INTR_RX_HIGH,
+ * clear H_INTR_RX_HIGH, so we do not loop forever.
+ */
+
+ if ( rx_high_rd_aborted && (sio_ir == H_INTR_RX_HIGH) ) {
+ sio_ir &= ~H_INTR_RX_HIGH;
+ }
+ } while (sio_ir & H_INTR_ALL);
+
+ SIO_UNLOCK_PORT(gp, flags);
+
+ /* Re-enable interrupts before returning from interrupt handler.
+ * Getting interrupted here is okay. It'll just v() our semaphore, and
+ * we'll come through the loop again.
+ */
+
+ IOC4_WRITE_IES(port->ip_ioc4_soft, port->ip_ienb, ioc4_sio_intr_type);
+}
+
+
+/*ARGSUSED*/
+
+/* Service any pending DMA error interrupts on the given port */
+static void
+ioc4_dma_error_intr(intr_arg_t arg, ioc4reg_t other_ir)
+{
+ ioc4port_t *port = (ioc4port_t *) arg;
+ sioport_t *gp = GPORT(port);
+ struct hooks *hooks = port->ip_hooks;
+ unsigned int flags;
+
+ SIO_LOCK_PORT(gp, flags);
+
+ dprintf(("interrupt: other_ir 0x%x\n", other_ir));
+
+ /* ACK the interrupt */
+ PCI_OUTW(&port->ip_ioc4->other_ir, H_INTR_DMA_ERROR);
+
+ printk( "DMA error on serial port %p\n", (void *)port->ip_port_vhdl);
+
+ if (port->ip_ioc4->pci_err_addr_l & IOC4_PCI_ERR_ADDR_VLD) {
+ printk( "PCI error address is 0x%lx, master is serial port %c %s\n",
+ ((uint64_t) port->ip_ioc4->pci_err_addr_h << 32) |
+ (port->ip_ioc4->pci_err_addr_l & IOC4_PCI_ERR_ADDR_ADDR_MSK),
+ '1' + (char) ((port->ip_ioc4->pci_err_addr_l &
+ IOC4_PCI_ERR_ADDR_MST_NUM_MSK) >> 1),
+ (port->ip_ioc4->pci_err_addr_l & IOC4_PCI_ERR_ADDR_MST_TYP_MSK)
+ ? "RX" : "TX");
+
+ if (port->ip_ioc4->pci_err_addr_l & IOC4_PCI_ERR_ADDR_MUL_ERR)
+ printk( "Multiple errors occurred\n");
+ }
+
+ SIO_UNLOCK_PORT(gp, flags);
+
+ /* Re-enable DMA error interrupts */
+ IOC4_WRITE_IES(port->ip_ioc4_soft, H_INTR_DMA_ERROR, ioc4_other_intr_type);
+}
+
+
+/* Baud rate setting code */
+static int
+set_baud_ti(ioc4port_t *port, int baud)
+{
+ int actual_baud;
+ int diff;
+ int lcr;
+ unsigned short divisor;
+
+ divisor = SER_DIVISOR(baud, IOC4_SER_XIN_CLK);
+ if (!divisor)
+ return(1);
+ actual_baud = DIVISOR_TO_BAUD(divisor, IOC4_SER_XIN_CLK);
+
+ diff = actual_baud - baud;
+ if (diff < 0)
+ diff = -diff;
+
+ /* If we're within 1%, we've found a match */
+ if (diff * 100 > actual_baud)
+ return(1);
+
+ lcr = PCI_INB(&port->ip_uart->i4u_lcr);
+
+ PCI_OUTB(&port->ip_uart->i4u_lcr, lcr | LCR_DLAB);
+
+ PCI_OUTB(&port->ip_uart->i4u_dll, (char) divisor);
+
+ PCI_OUTB(&port->ip_uart->i4u_dlm, (char) (divisor >> 8));
+
+ PCI_OUTB(&port->ip_uart->i4u_lcr, lcr);
+
+ return(0);
+}
+
+
+/* Initialize the sio and ioc4 hardware for a given port */
+static int
+hardware_init(ioc4port_t *port)
+{
+ ioc4reg_t sio_cr;
+ struct hooks *hooks = port->ip_hooks;
+
+ DEBUGINC(ports, 1);
+
+ /* Idle the IOC4 serial interface */
+ PCI_OUTW(&port->ip_serial->sscr, IOC4_SSCR_RESET);
+
+ /* Wait until any pending bus activity for this port has ceased */
+ do sio_cr = PCI_INW(&port->ip_ioc4->sio_cr);
+ while(!(sio_cr & IOC4_SIO_CR_SIO_DIAG_IDLE));
+
+ /* Finish reset sequence */
+ PCI_OUTW(&port->ip_serial->sscr, 0);
+
+ /* Once RESET is done, reload cached tx_prod and rx_cons values
+ * and set rings to empty by making prod == cons
+ */
+ port->ip_tx_prod = PCI_INW(&port->ip_serial->stcir) & PROD_CONS_MASK;
+ PCI_OUTW(&port->ip_serial->stpir, port->ip_tx_prod);
+
+ port->ip_rx_cons = PCI_INW(&port->ip_serial->srpir) & PROD_CONS_MASK;
+ PCI_OUTW(&port->ip_serial->srcir, port->ip_rx_cons);
+
+ /* Disable interrupts for this 16550 */
+ PCI_OUTB(&port->ip_uart->i4u_lcr, 0); /* clear DLAB */
+ PCI_OUTB(&port->ip_uart->i4u_ier, 0);
+
+ /* Set the default baud */
+ SET_BAUD(port, port->ip_baud);
+
+ /* Set line control to 8 bits no parity */
+ PCI_OUTB(&port->ip_uart->i4u_lcr, LCR_BITS8 | LCR_1_STOP_BITS);
+
+ /* Enable the FIFOs */
+ PCI_OUTB(&port->ip_uart->i4u_fcr, FCR_FIFOEN);
+ /* then reset 16550 FIFOs */
+ PCI_OUTB(&port->ip_uart->i4u_fcr,
+ FCR_FIFOEN | FCR_RxFIFO | FCR_TxFIFO);
+
+ /* Clear modem control register */
+ PCI_OUTB(&port->ip_uart->i4u_mcr, 0);
+
+ /* Clear deltas in modem status register */
+ PCI_INB(&port->ip_uart->i4u_msr);
+
+ /* Only do this once per port pair */
+ if (port->ip_hooks == &hooks_array[0] || port->ip_hooks == &hooks_array[2]) {
+ iopaddr_t ring_pci_addr;
+ volatile ioc4reg_t *sbbr_l;
+ volatile ioc4reg_t *sbbr_h;
+
+ if(port->ip_hooks == &hooks_array[0]) {
+ sbbr_l = &port->ip_ioc4->sbbr01_l;
+ sbbr_h = &port->ip_ioc4->sbbr01_h;
+ }
+ else {
+ sbbr_l = &port->ip_ioc4->sbbr23_l;
+ sbbr_h = &port->ip_ioc4->sbbr23_h;
+ }
+
+ /* Set the DMA address */
+ ring_pci_addr = ring_dmatrans(port->ip_conn_vhdl,
+ port->ip_ring_buf_k0);
+
+ PCI_OUTW(sbbr_h,
+ (ioc4reg_t) ((__psunsigned_t) ring_pci_addr >> 32));
+
+ PCI_OUTW(sbbr_l,
+ ((ioc4reg_t) (int64_t) ring_pci_addr | IOC4_BUF_SIZE_BIT));
+
+#ifdef IOC4_SIO_DEBUG
+ {
+ unsigned int tmp1, tmp2;
+
+ tmp1 = PCI_INW(sbbr_l);
+ tmp2 = PCI_INW(sbbr_h);
+ printk("========== %s : sbbr_l [%p]/0x%x sbbr_h [%p]/0x%x\n",
+ __FUNCTION__, (void *)sbbr_l, tmp1, (void *)sbbr_h, tmp2);
+ }
+#endif
+ }
+
+ /* Set the receive timeout value to 10 msec */
+ PCI_OUTW(&port->ip_serial->srtr, IOC4_SRTR_HZ / 100);
+
+ /* Set RX threshold, enable DMA */
+ /* Set high water mark at 3/4 of full ring */
+ port->ip_sscr = (ENTRIES_PER_RING * 3 / 4);
+
+ PCI_OUTW(&port->ip_serial->sscr, port->ip_sscr);
+
+ /* Disable and clear all serial related interrupt bits */
+ IOC4_WRITE_IEC(port->ip_ioc4_soft, H_INTR_CLEAR, ioc4_sio_intr_type);
+ port->ip_ienb &= ~H_INTR_CLEAR;
+ PCI_OUTW(&port->ip_ioc4->sio_ir, H_INTR_CLEAR);
+
+ return(0);
+}
+
+
+/*
+ * Device initialization.
+ * Called at *_attach() time for each
+ * IOC4 with serial ports in the system.
+ * If vhdl is GRAPH_VERTEX_NONE, do not do
+ * any graph related work; otherwise, it
+ * is the IOC4 vertex that should be used
+ * for requesting pciio services.
+ */
+int
+ioc4_serial_attach(vertex_hdl_t conn_vhdl, void *ioc4)
+{
+ /*REFERENCED*/
+ graph_error_t rc;
+ ioc4_mem_t *ioc4_mem;
+ vertex_hdl_t port_vhdl, ioc4_vhdl;
+ vertex_hdl_t intr_dev_vhdl;
+ ioc4port_t *port;
+ ioc4port_t *ports[4];
+ static char *names[] = { "tty/1", "tty/2", "tty/3", "tty/4" };
+ int x, first_port = -1, last_port = -1;
+ void *ioc4_soft;
+ unsigned int ioc4_revid_min = 62;
+ unsigned int ioc4_revid;
+
+
+ /* IOC4 firmware must be at least rev 62 */
+ ioc4_revid = pciio_config_get(conn_vhdl, PCI_CFG_REV_ID, 1);
+
+ if (ioc4_revid < ioc4_revid_min) {
+ printk( "IOC4 serial ports not supported on firmware rev %d, please upgrade to rev %d or higher\n", ioc4_revid, ioc4_revid_min);
+ return -1;
+ }
+
+ first_port = 0;
+ last_port = 3;
+
+ /* Get back pointer to the ioc4 soft area */
+ rc = hwgraph_traverse(conn_vhdl, EDGE_LBL_IOC4, &ioc4_vhdl);
+ ASSERT(rc == GRAPH_SUCCESS);
+ ioc4_soft = (void *)hwgraph_fastinfo_get(ioc4_vhdl);
+
+ /* grab the PIO address */
+ ioc4_mem = (ioc4_mem_t *)ioc4;
+ ASSERT(ioc4_mem != NULL);
+
+ /*
+ * Create port structures for each port
+ */
+ NEWA(port, 4);
+#ifdef IOC4_SIO_DEBUG
+ printk("%s : [addr 0x%p]\n", __FUNCTION__, (void *)port);
+#endif
+ ports[0] = port++;
+ ports[1] = port++;
+ ports[2] = port++;
+ ports[3] = port++;
+
+#if DEBUG
+ {
+ int slot = atomicAddInt(&next_saveport, 4) - 4;
+ saveport[slot] = ports[0];
+ saveport[slot + 1] = ports[1];
+ saveport[slot + 2] = ports[2];
+ saveport[slot + 3] = ports[3];
+ ASSERT(slot < MAXSAVEPORT);
+ }
+#endif
+
+#ifdef DEBUG
+ if ((caddr_t) port != (caddr_t) &(port->ip_sioport))
+ panic("sioport is not first member of ioc4port struct\n");
+#endif
+
+ /* Allocate buffers and jumpstart the hardware.
+ */
+ for (x = first_port; x < (last_port + 1); x++) {
+
+ port = ports[x];
+#ifdef IOC4_SIO_DEBUG
+ printk("%s : initialize port %d [addr 0x%p/0x%p]\n", __FUNCTION__, x, (void *)port,
+ (void *)GPORT(port));
+#endif
+ port->ip_ioc4_soft = ioc4_soft;
+ rc = hwgraph_path_add(conn_vhdl, names[x], &port_vhdl);
+ ASSERT(rc == GRAPH_SUCCESS);
+ port->ip_conn_vhdl = conn_vhdl;
+ port->ip_port_vhdl = port_vhdl;
+ port->ip_ienb = 0;
+ hwgraph_fastinfo_set(port_vhdl, (arbitrary_info_t) port);
+
+ /* Perform upper layer initialization. Create all device node
+ * types including rs422 ports.
+ */
+ ioc4_serial_initport(GPORT(port), x);
+ port->ip_baud = 9600;
+
+ /* Attach the calldown hooks so upper layer can call our
+ * routines.
+ */
+ port->ip_sioport.sio_calldown = &ioc4_calldown;
+
+ /* Map in the IOC4 register area */
+ port->ip_ioc4 = ioc4_mem;
+ }
+
+ {
+ /* Port 0 */
+ port = ports[0];
+ port->ip_hooks = &hooks_array[0];
+
+ /* Get direct hooks to the serial regs and uart regs
+ * for this port
+ */
+ port->ip_serial = &(port->ip_ioc4->port_0);
+ port->ip_uart = &(port->ip_ioc4->uart_0);
+#ifdef IOC4_SIO_DEBUG
+ printk("==== %s : serial port 0 address 0x%p uart address 0x%p\n",
+ __FUNCTION__, (void *)port->ip_serial, (void *)port->ip_uart);
+#endif
+
+ /* If we don't already have a ring buffer,
+ * set one up.
+ */
+ if (port->ip_ring_buf_k0 == 0) {
+
+#if PAGE_SIZE >= TOTAL_RING_BUF_SIZE
+ if ((port->ip_ring_buf_k0 = kvpalloc(1, VM_DIRECT, 0)) == 0)
+ panic("ioc4_uart driver cannot allocate page\n");
+#else
+ /* We need to allocate a chunk of memory on a
+ * TOTAL_RING_BUF_SIZE boundary.
+ */
+ {
+ pgno_t pfn;
+ caddr_t vaddr;
+ if ((pfn = contig_memalloc(TOTAL_RING_BUF_SIZE / PAGE_SIZE,
+ TOTAL_RING_BUF_SIZE / PAGE_SIZE,
+ VM_DIRECT)) == 0)
+ panic("ioc4_uart driver cannot allocate page\n");
+ ASSERT(small_pfn(pfn));
+ vaddr = small_pfntova_K0(pfn);
+ (void) COLOR_VALIDATION(pfdat + pfn,
+ colorof(vaddr),
+ 0, VM_DIRECT);
+ port->ip_ring_buf_k0 = vaddr;
+ }
+#endif
+ }
+ ASSERT((((int64_t)port->ip_ring_buf_k0) &
+ (TOTAL_RING_BUF_SIZE - 1)) == 0);
+ memset(port->ip_ring_buf_k0, 0, TOTAL_RING_BUF_SIZE);
+ port->ip_inring = RING(port, RX_0_OR_2);
+ port->ip_outring = RING(port, TX_0_OR_2);
+
+ /* Initialize the hardware for IOC4 */
+ hardware_init(port);
+
+ if (hwgraph_edge_get(ports[0]->ip_port_vhdl, "d", &intr_dev_vhdl) !=
+ GRAPH_SUCCESS) {
+ intr_dev_vhdl = ports[0]->ip_port_vhdl;
+ }
+
+ /* Attach interrupt handlers */
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_sio_intr_type,
+ IOC4_SIO_IR_S0,
+ ioc4_serial_intr,
+ ports[0],
+ ports[0]->ip_port_vhdl,
+ intr_dev_vhdl);
+
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_other_intr_type,
+ IOC4_OTHER_IR_S0_MEMERR,
+ ioc4_dma_error_intr,
+ ports[0],
+ ports[0]->ip_port_vhdl,
+ intr_dev_vhdl);
+ }
+
+ {
+
+ /* Port 1 */
+ port = ports[1];
+ port->ip_hooks = &hooks_array[1];
+
+ port->ip_serial = &(port->ip_ioc4->port_1);
+ port->ip_uart = &(port->ip_ioc4->uart_1);
+#ifdef IOC4_SIO_DEBUG
+ printk("==== %s : serial port 1 address 0x%p uart address 0x%p\n",
+ __FUNCTION__, (void *)port->ip_serial, (void *)port->ip_uart);
+#endif
+
+ port->ip_ring_buf_k0 = ports[0]->ip_ring_buf_k0;
+ port->ip_inring = RING(port, RX_1_OR_3);
+ port->ip_outring = RING(port, TX_1_OR_3);
+
+ /* Initialize the hardware for IOC4 */
+ hardware_init(port);
+
+ if (hwgraph_edge_get(ports[1]->ip_port_vhdl, "d", &intr_dev_vhdl) !=
+ GRAPH_SUCCESS) {
+ intr_dev_vhdl = ports[1]->ip_port_vhdl;
+ }
+
+ /* Attach interrupt handler */
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_sio_intr_type,
+ IOC4_SIO_IR_S1,
+ ioc4_serial_intr,
+ ports[1],
+ ports[1]->ip_port_vhdl,
+ intr_dev_vhdl);
+
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_other_intr_type,
+ IOC4_OTHER_IR_S1_MEMERR,
+ ioc4_dma_error_intr,
+ ports[1],
+ ports[1]->ip_port_vhdl,
+ intr_dev_vhdl);
+ }
+
+ {
+
+ /* Port 2 */
+ port = ports[2];
+ port->ip_hooks = &hooks_array[2];
+
+ /* Get direct hooks to the serial regs and uart regs
+ * for this port
+ */
+ port->ip_serial = &(port->ip_ioc4->port_2);
+ port->ip_uart = &(port->ip_ioc4->uart_2);
+#ifdef IOC4_SIO_DEBUG
+ printk("==== %s : serial port 2 address 0x%p uart address 0x%p\n",
+ __FUNCTION__, (void *)port->ip_serial, (void *)port->ip_uart);
+#endif
+
+ /* If we don't already have a ring buffer,
+ * set one up.
+ */
+ if (port->ip_ring_buf_k0 == 0) {
+
+#if PAGE_SIZE >= TOTAL_RING_BUF_SIZE
+ if ((port->ip_ring_buf_k0 = kvpalloc(1, VM_DIRECT, 0)) == 0)
+ panic("ioc4_uart driver cannot allocate page\n");
+#else
+
+ /* We need to allocate a chunk of memory on a
+ * TOTAL_RING_BUF_SIZE boundary.
+ */
+ {
+ pgno_t pfn;
+ caddr_t vaddr;
+ if ((pfn = contig_memalloc(TOTAL_RING_BUF_SIZE / PAGE_SIZE,
+ TOTAL_RING_BUF_SIZE / PAGE_SIZE,
+ VM_DIRECT)) == 0)
+ panic("ioc4_uart driver cannot allocate page\n");
+ ASSERT(small_pfn(pfn));
+ vaddr = small_pfntova_K0(pfn);
+ (void) COLOR_VALIDATION(pfdat + pfn,
+ colorof(vaddr),
+ 0, VM_DIRECT);
+ port->ip_ring_buf_k0 = vaddr;
+ }
+#endif
+
+ }
+ ASSERT((((int64_t)port->ip_ring_buf_k0) &
+ (TOTAL_RING_BUF_SIZE - 1)) == 0);
+ memset(port->ip_ring_buf_k0, 0, TOTAL_RING_BUF_SIZE);
+ port->ip_inring = RING(port, RX_0_OR_2);
+ port->ip_outring = RING(port, TX_0_OR_2);
+
+ /* Initialize the hardware for IOC4 */
+ hardware_init(port);
+
+ if (hwgraph_edge_get(ports[0]->ip_port_vhdl, "d", &intr_dev_vhdl) !=
+ GRAPH_SUCCESS) {
+ intr_dev_vhdl = ports[2]->ip_port_vhdl;
+ }
+
+ /* Attach interrupt handler */
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_sio_intr_type,
+ IOC4_SIO_IR_S2,
+ ioc4_serial_intr,
+ ports[2],
+ ports[2]->ip_port_vhdl,
+ intr_dev_vhdl);
+
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_other_intr_type,
+ IOC4_OTHER_IR_S2_MEMERR,
+ ioc4_dma_error_intr,
+ ports[2],
+ ports[2]->ip_port_vhdl,
+ intr_dev_vhdl);
+ }
+
+ {
+
+ /* Port 3 */
+ port = ports[3];
+ port->ip_hooks = &hooks_array[3];
+
+ port->ip_serial = &(port->ip_ioc4->port_3);
+ port->ip_uart = &(port->ip_ioc4->uart_3);
+#ifdef IOC4_SIO_DEBUG
+ printk("==== %s : serial port 3 address 0x%p uart address 0x%p\n",
+ __FUNCTION__, (void *)port->ip_serial, (void *)port->ip_uart);
+#endif
+
+ port->ip_ring_buf_k0 = ports[2]->ip_ring_buf_k0;
+ port->ip_inring = RING(port, RX_1_OR_3);
+ port->ip_outring = RING(port, TX_1_OR_3);
+
+ /* Initialize the hardware for IOC4 */
+ hardware_init(port);
+
+ if (hwgraph_edge_get(ports[3]->ip_port_vhdl, "d", &intr_dev_vhdl) !=
+ GRAPH_SUCCESS) {
+ intr_dev_vhdl = ports[3]->ip_port_vhdl;
+ }
+
+ /* Attach interrupt handler */
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_sio_intr_type,
+ IOC4_SIO_IR_S3,
+ ioc4_serial_intr,
+ ports[3],
+ ports[3]->ip_port_vhdl,
+ intr_dev_vhdl);
+
+ ioc4_intr_connect(conn_vhdl,
+ ioc4_other_intr_type,
+ IOC4_OTHER_IR_S3_MEMERR,
+ ioc4_dma_error_intr,
+ ports[3],
+ ports[3]->ip_port_vhdl,
+ intr_dev_vhdl);
+ }
+
+#ifdef DEBUG
+ idbg_addfunc( "ioc4dump", idbg_ioc4dump );
+#endif
+
+ return 0;
+}
+
+
+/* Shut down an IOC4 */
+/* ARGSUSED1 */
+void
+ioc4_serial_kill(ioc4port_t *port)
+{
+ DEBUGINC(killed, 1);
+
+ /* Notify upper layer that this port is no longer usable */
+ UP_DETACH(GPORT(port));
+
+ /* Clear everything in the sscr */
+ PCI_OUTW(&port->ip_serial->sscr, 0);
+ port->ip_sscr = 0;
+
+#ifdef DEBUG
+ /* Make sure nobody gets past the lock and accesses the hardware */
+ port->ip_ioc4 = 0;
+ port->ip_serial = 0;
+#endif
+
+}
+
+
+/*
+ * Open a port
+ */
+static int
+ioc4_open(sioport_t *port)
+{
+ ioc4port_t *p = LPORT(port);
+ int spin_success;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_OPEN));
+#endif
+
+ p->ip_flags = 0;
+ p->ip_modem_bits = 0;
+
+ /* Pause the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr | IOC4_SSCR_DMA_PAUSE);
+ SPIN((PCI_INW(&p->ip_serial->sscr) & IOC4_SSCR_PAUSE_STATE) == 0,
+ spin_success);
+ if (!spin_success) {
+ NOT_PROGRESS();
+ return(-1);
+ }
+ }
+
+ /* Reset the input fifo. If the uart received chars while the port
+ * was closed and DMA is not enabled, the uart may have a bunch of
+ * chars hanging around in its RX fifo which will not be discarded
+ * by rclr in the upper layer. We must get rid of them here.
+ */
+ PCI_OUTB(&p->ip_uart->i4u_fcr, FCR_FIFOEN | FCR_RxFIFO);
+
+ /* Set defaults */
+ SET_BAUD(p, 9600);
+
+ PCI_OUTB(&p->ip_uart->i4u_lcr, LCR_BITS8 | LCR_1_STOP_BITS);
+
+ /* Re-enable DMA, set default threshold to intr whenever there is
+ * data available.
+ */
+ p->ip_sscr &= ~IOC4_SSCR_RX_THRESHOLD;
+ p->ip_sscr |= 1; /* default threshold */
+
+ /* Plug in the new sscr. This implicitly clears the DMA_PAUSE
+ * flag if it was set above
+ */
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+
+ PCI_OUTW(&p->ip_serial->srtr, 0);
+
+ p->ip_tx_lowat = 1;
+
+ dprintf(("ioc4 open successful\n"));
+
+ return(0);
+}
+
+
+/*
+ * Config hardware
+ */
+static int
+ioc4_config(sioport_t *port,
+ int baud,
+ int byte_size,
+ int stop_bits,
+ int parenb,
+ int parodd)
+{
+ ioc4port_t *p = LPORT(port);
+ char lcr, sizebits;
+ int spin_success;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_CONFIG));
+#endif
+
+ if (SET_BAUD(p, baud))
+ return(1);
+
+ switch(byte_size) {
+ case 5:
+ sizebits = LCR_BITS5;
+ break;
+ case 6:
+ sizebits = LCR_BITS6;
+ break;
+ case 7:
+ sizebits = LCR_BITS7;
+ break;
+ case 8:
+ sizebits = LCR_BITS8;
+ break;
+ default:
+ dprintf(("invalid byte size port 0x%x size %d\n", port, byte_size));
+ return(1);
+ }
+
+ /* Pause the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr | IOC4_SSCR_DMA_PAUSE);
+ SPIN((PCI_INW(&p->ip_serial->sscr) & IOC4_SSCR_PAUSE_STATE) == 0,
+ spin_success);
+ if (!spin_success)
+ return(-1);
+ }
+
+ /* Clear relevant fields in lcr */
+ lcr = PCI_INB(&p->ip_uart->i4u_lcr);
+ lcr &= ~(LCR_MASK_BITS_CHAR | LCR_EPS |
+ LCR_PEN | LCR_MASK_STOP_BITS);
+
+ /* Set byte size in lcr */
+ lcr |= sizebits;
+
+ /* Set parity */
+ if (parenb) {
+ lcr |= LCR_PEN;
+ if (!parodd)
+ lcr |= LCR_EPS;
+ }
+
+ /* Set stop bits */
+ if (stop_bits)
+ lcr |= LCR_2_STOP_BITS;
+
+ PCI_OUTB(&p->ip_uart->i4u_lcr, lcr);
+
+ dprintf(("ioc4_config: lcr bits 0x%x\n", lcr));
+
+ /* Re-enable the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+ }
+
+ p->ip_baud = baud;
+
+ /* When we get within this number of ring entries of filling the
+ * entire ring on TX, place an EXPLICIT intr to generate a lowat
+ * notification when output has drained.
+ */
+ p->ip_tx_lowat = (TX_LOWAT_CHARS(baud) + 3) / 4;
+ if (p->ip_tx_lowat == 0)
+ p->ip_tx_lowat = 1;
+
+ ioc4_rx_timeout(port, p->ip_rx_timeout);
+
+ return(0);
+}
+
+
+/*
+ * Enable hardware flow control
+ */
+static int
+ioc4_enable_hfc(sioport_t *port, int enable)
+{
+ ioc4port_t *p = LPORT(port);
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_ENABLE_HFC));
+#endif
+
+ dprintf(("enable hfc port 0x%p, enb %d\n", (void *)port, enable));
+
+ if (enable)
+ p->ip_sscr |= IOC4_SSCR_HFC_EN;
+ else
+ p->ip_sscr &= ~IOC4_SSCR_HFC_EN;
+
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+
+ return(0);
+}
+
+
+/*
+ * Set external clock
+ */
+/*ARGSUSED*/
+static int
+ioc4_set_extclk(sioport_t *port, int clock_factor)
+{
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_SET_EXTCLK));
+ /* XXX still todo */
+#endif
+
+ /* only support 0 (no external clock) */
+ return(clock_factor);
+}
+
+
+/*
+ * Write bytes to the hardware. Returns the number of bytes
+ * actually written.
+ */
+static int
+do_ioc4_write(sioport_t *port, char *buf, int len)
+{
+ int prod_ptr, cons_ptr, total;
+ struct ring *outring;
+ struct ring_entry *entry;
+ ioc4port_t *p = LPORT(port);
+ struct hooks *hooks = p->ip_hooks;
+
+ DEBUGINC(write_bytes, len);
+ DEBUGINC(write_cnt, 1);
+
+ dprintf(("write port 0x%p, len %d\n", (void *)port, len));
+
+ ASSERT(len >= 0);
+
+ prod_ptr = p->ip_tx_prod;
+ cons_ptr = PCI_INW(&p->ip_serial->stcir) & PROD_CONS_MASK;
+ outring = p->ip_outring;
+
+ /* Maintain a 1-entry red-zone. The ring buffer is full when
+ * (cons - prod) % ring_size is 1. Rather than do this subtraction
+ * in the body of the loop, I'll do it now.
+ */
+ cons_ptr = (cons_ptr - (int) sizeof(struct ring_entry)) & PROD_CONS_MASK;
+
+ total = 0;
+ /* Stuff the bytes into the output */
+ while ((prod_ptr != cons_ptr) && (len > 0)) {
+ int x;
+
+ /* Go 4 bytes (one ring entry) at a time */
+ entry = (struct ring_entry*) ((caddr_t)outring + prod_ptr);
+
+ /* Invalidate all entries */
+ entry->ring_allsc = 0;
+
+ /* Copy in some bytes */
+ for(x = 0; (x < 4) && (len > 0); x++) {
+ entry->ring_data[x] = *buf++;
+ entry->ring_sc[x] = IOC4_TXCB_VALID;
+ len--;
+ total++;
+ }
+
+ DEBUGINC(tx_buf_used, x);
+ DEBUGINC(tx_buf_cnt, 1);
+
+ /* If we are within some small threshold of filling up the entire
+ * ring buffer, we must place an EXPLICIT intr here to generate
+ * a lowat interrupt in case we subsequently really do fill up
+ * the ring and the caller goes to sleep. No need to place
+ * more than one though.
+ */
+ if (!(p->ip_flags & LOWAT_WRITTEN) &&
+ ((cons_ptr - prod_ptr) & PROD_CONS_MASK) <=
+ p->ip_tx_lowat * (int)sizeof(struct ring_entry)) {
+ p->ip_flags |= LOWAT_WRITTEN;
+ entry->ring_sc[0] |= IOC4_TXCB_INT_WHEN_DONE;
+ dprintf(("write placing TX_EXPLICIT\n"));
+ }
+
+ /* Go on to next entry */
+ prod_ptr = (prod_ptr + (int) sizeof(struct ring_entry)) & PROD_CONS_MASK;
+ }
+
+ /* If we sent something, start DMA if necessary */
+ if (total > 0 && !(p->ip_sscr & IOC4_SSCR_DMA_EN)) {
+ p->ip_sscr |= IOC4_SSCR_DMA_EN;
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+ }
+
+ /* Store the new producer pointer. If TX is disabled, we stuff the
+ * data into the ring buffer, but we don't actually start TX.
+ */
+ if (!(p->ip_flags & TX_DISABLED)) {
+ PCI_OUTW(&p->ip_serial->stpir, prod_ptr);
+
+ /* If we are now transmitting, enable TX_MT interrupt so we
+ * can disable DMA if necessary when the TX finishes.
+ */
+ if (total > 0)
+ enable_intrs(p, H_INTR_TX_MT);
+ }
+ p->ip_tx_prod = prod_ptr;
+
+ dprintf(("write port 0x%p, wrote %d\n", (void *)port, total));
+ DEBUGINC(wrote_bytes, total);
+ return(total);
+}
+
+
+/* Asynchronous write */
+static int
+ioc4_write(sioport_t *port, char *buf, int len)
+{
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_WRITE));
+#endif
+ return(do_ioc4_write(port, buf, len));
+}
+
+
+/* Synchronous write */
+static int
+ioc4_sync_write(sioport_t *port, char *buf, int len)
+{
+ int bytes;
+
+ ASSERT(sio_port_islocked(port));
+ bytes = do_ioc4_write(port, buf, len);
+
+ /* Don't allow the system to hang if XOFF is in force */
+ if (len > 0 && bytes == 0 && (LPORT(port)->ip_flags & TX_DISABLED))
+ ioc4_enable_tx(port, 1);
+
+ return(bytes);
+}
+
+
+/* Write flush */
+static void
+ioc4_wrflush(sioport_t *port)
+{
+ ioc4port_t *p = LPORT(port);
+
+ ASSERT(sio_port_islocked(port));
+
+ /* We can't flush if TX is disabled due to XOFF. */
+ if (!(PCI_INW(&p->ip_ioc4->sio_ir) & IOC4_SIO_IR_S0_TX_MT) &&
+ (p->ip_flags & TX_DISABLED))
+ ioc4_enable_tx(port, 1);
+
+ /* Spin waiting for TX_MT to assert only if DMA is enabled. If we
+ * are panicking and one of the other processors is already in
+ * symmon, DMA will be disabled and TX_MT will never be asserted.
+ * There may also be legitimate cases in the kernel where DMA is
+ * disabled and we won't flush correctly here.
+ */
+
+ while ((PCI_INW(&p->ip_serial->sscr) & (IOC4_SSCR_DMA_EN |
+ IOC4_SSCR_PAUSE_STATE)) == IOC4_SSCR_DMA_EN &&
+ !(PCI_INW(&p->ip_ioc4->sio_ir) & IOC4_SIO_IR_S0_TX_MT)) {
+ udelay(5);
+ }
+}
+
+
+/*
+ * Set or clear break condition on output
+ */
+static int
+ioc4_break(sioport_t *port, int brk)
+{
+ ioc4port_t *p = LPORT(port);
+ char lcr;
+ int spin_success;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_BREAK));
+#endif
+
+ /* Pause the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr | IOC4_SSCR_DMA_PAUSE);
+ SPIN((PCI_INW(&p->ip_serial->sscr) & IOC4_SSCR_PAUSE_STATE) == 0,
+ spin_success);
+ if (!spin_success)
+ return(-1);
+ }
+
+ lcr = PCI_INB(&p->ip_uart->i4u_lcr);
+ if (brk) {
+ /* Set break */
+ PCI_OUTB(&p->ip_uart->i4u_lcr, lcr | LCR_SNDBRK);
+ }
+ else {
+ /* Clear break */
+ PCI_OUTB(&p->ip_uart->i4u_lcr, lcr & ~LCR_SNDBRK);
+ }
+
+ /* Re-enable the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+ }
+
+ dprintf(("break port 0x%p, brk %d\n", (void *)port, brk));
+
+ return(0);
+}
+
+
+static int
+ioc4_enable_tx(sioport_t *port, int enb)
+{
+ ioc4port_t *p = LPORT(port);
+ struct hooks *hooks = p->ip_hooks;
+ int spin_success;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_ENABLE_TX));
+#endif
+
+ /* If we're already in the desired state, we're done */
+ if ((enb && !(p->ip_flags & TX_DISABLED)) ||
+ (!enb && (p->ip_flags & TX_DISABLED)))
+ return(0);
+
+ /* Pause DMA */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr | IOC4_SSCR_DMA_PAUSE);
+ SPIN((PCI_INW(&p->ip_serial->sscr) & IOC4_SSCR_PAUSE_STATE) == 0,
+ spin_success);
+ if (!spin_success)
+ return(-1);
+ }
+
+ if (enb) {
+ p->ip_flags &= ~TX_DISABLED;
+ PCI_OUTW(&p->ip_serial->stpir, p->ip_tx_prod);
+ enable_intrs(p, H_INTR_TX_MT);
+ }
+ else {
+ ioc4reg_t txcons = PCI_INW(&p->ip_serial->stcir) & PROD_CONS_MASK;
+ p->ip_flags |= TX_DISABLED;
+ disable_intrs(p, H_INTR_TX_MT);
+
+ /* Only move the transmit producer pointer back if the
+ * transmitter is not already empty, otherwise we'll be
+ * generating a bogus entry.
+ */
+ if (txcons != p->ip_tx_prod)
+ PCI_OUTW(&p->ip_serial->stpir,
+ (txcons + (int) sizeof(struct ring_entry)) & PROD_CONS_MASK);
+ }
+
+ /* Re-enable the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN)
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+
+ return(0);
+}
+
+
+/*
+ * Read in bytes from the hardware. Return the number of bytes
+ * actually read.
+ */
+static int
+ioc4_read(sioport_t *port, char *buf, int len)
+{
+ int prod_ptr, cons_ptr, total, x, spin_success;
+ struct ring *inring;
+ ioc4port_t *p = LPORT(port);
+ struct hooks *hooks = p->ip_hooks;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_READ));
+#endif
+
+ dprintf(("read port 0x%p, len %d\n", (void *)port, len));
+
+ DEBUGINC(read_bytes, len);
+ DEBUGINC(read_cnt, 1);
+
+ ASSERT(len >= 0);
+
+ /* There is a nasty timing issue in the IOC4. When the RX_TIMER
+ * expires or the RX_HIGH condition arises, we take an interrupt.
+ * At some point while servicing the interrupt, we read bytes from
+ * the ring buffer and re-arm the RX_TIMER. However the RX_TIMER is
+ * not started until the first byte is received *after* it is armed,
+ * and any bytes pending in the RX construction buffers are not drained
+ * to memory until either there are 4 bytes available or the RX_TIMER
+ * expires. This leads to a potential situation where data is left
+ * in the construction buffers forever because 1 to 3 bytes were received
+ * after the interrupt was generated but before the RX_TIMER was re-armed.
+ * At that point as long as no subsequent bytes are received the
+ * timer will never be started and the bytes will remain in the
+ * construction buffer forever. The solution is to execute a DRAIN
+ * command after rearming the timer. This way any bytes received before
+ * the DRAIN will be drained to memory, and any bytes received after
+ * the DRAIN will start the TIMER and be drained when it expires.
+ * Luckily, this only needs to be done when the DMA buffer is empty
+ * since there is no requirement that this function return all
+ * available data as long as it returns some.
+ */
+ /* Re-arm the timer */
+ PCI_OUTW(&p->ip_serial->srcir, p->ip_rx_cons | IOC4_SRCIR_ARM);
+
+ prod_ptr = PCI_INW(&p->ip_serial->srpir) & PROD_CONS_MASK;
+ cons_ptr = p->ip_rx_cons;
+
+ if (prod_ptr == cons_ptr) {
+ int reset_dma = 0;
+
+ /* Input buffer appears empty, do a flush. */
+
+ /* DMA must be enabled for this to work. */
+ if (!(p->ip_sscr & IOC4_SSCR_DMA_EN)) {
+ p->ip_sscr |= IOC4_SSCR_DMA_EN;
+ reset_dma = 1;
+ }
+
+ /* Potential race condition: we must reload the srpir after
+ * issuing the drain command, otherwise we could think the RX
+ * buffer is empty, then take a very long interrupt, and when
+ * we come back it's full and we wait forever for the drain to
+ * complete.
+ */
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr | IOC4_SSCR_RX_DRAIN);
+ prod_ptr = PCI_INW(&p->ip_serial->srpir) & PROD_CONS_MASK;
+
+ DEBUGINC(drain, 1);
+
+ /* We must not wait for the DRAIN to complete unless there are
+ * at least 8 bytes (2 ring entries) available to receive the data
+ * otherwise the DRAIN will never complete and we'll deadlock here.
+ * In fact, to make things easier, I'll just ignore the flush if
+ * there is any data at all now available.
+ */
+ if (prod_ptr == cons_ptr) {
+ DEBUGINC(drainwait, 1);
+ SPIN(PCI_INW(&p->ip_serial->sscr) & IOC4_SSCR_RX_DRAIN, spin_success);
+ if (!spin_success)
+ return(-1);
+
+ /* SIGH. We have to reload the prod_ptr *again* since
+ * the drain may have caused it to change
+ */
+ prod_ptr = PCI_INW(&p->ip_serial->srpir) & PROD_CONS_MASK;
+ }
+
+ if (reset_dma) {
+ DEBUGINC(resetdma, 1);
+ p->ip_sscr &= ~IOC4_SSCR_DMA_EN;
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+ }
+ }
+ inring = p->ip_inring;
+
+ p->ip_flags &= ~READ_ABORTED;
+
+ total = 0;
+ /* Grab bytes from the hardware */
+ while(prod_ptr != cons_ptr && len > 0) {
+ struct ring_entry *entry;
+
+ entry = (struct ring_entry *) ((caddr_t)inring + cons_ptr);
+
+ /* According to the producer pointer, this ring entry
+ * must contain some data. But if the PIO happened faster
+ * than the DMA, the data may not be available yet, so let's
+ * wait until it arrives.
+ */
+ if ((((volatile struct ring_entry *) entry)->ring_allsc &
+ RING_ANY_VALID) == 0) {
+
+ /* Indicate the read is aborted so we don't disable
+ * the interrupt thinking that the consumer is
+ * congested.
+ */
+ p->ip_flags |= READ_ABORTED;
+
+ DEBUGINC(read_aborted, 1);
+ len = 0;
+ break;
+
+ }
+
+ /* Load the bytes/status out of the ring entry */
+ for(x = 0; x < 4 && len > 0; x++) {
+ char *sc = &(entry->ring_sc[x]);
+
+ /* Check for change in modem state or overrun */
+ if (*sc & IOC4_RXSB_MODEM_VALID) {
+ if (p->ip_notify & N_DDCD) {
+
+ /* Notify upper layer if DCD dropped */
+ if ((p->ip_flags & DCD_ON) && !(*sc & IOC4_RXSB_DCD)) {
+
+ /* If we have already copied some data, return
+ * it. We'll pick up the carrier drop on the next
+ * pass. That way we don't throw away the data
+ * that has already been copied back to the caller's
+ * buffer.
+ */
+ if (total > 0) {
+ len = 0;
+ break;
+ }
+
+ p->ip_flags &= ~DCD_ON;
+
+ /* Turn off this notification so the carrier
+ * drop protocol won't see it again when it
+ * does a read.
+ */
+ *sc &= ~IOC4_RXSB_MODEM_VALID;
+
+ /* To keep things consistent, we need to update
+ * the consumer pointer so the next reader won't
+ * come in and try to read the same ring entries
+ * again. This must be done here before the call
+ * to UP_DDCD since UP_DDCD may do a recursive
+ * read!
+ */
+ if ((entry->ring_allsc & RING_ANY_VALID) == 0)
+ cons_ptr =
+ (cons_ptr + (int) sizeof(struct ring_entry)) &
+ PROD_CONS_MASK;
+
+ PCI_OUTW(&p->ip_serial->srcir, cons_ptr);
+ p->ip_rx_cons = cons_ptr;
+
+ /* Notify upper layer of carrier drop */
+ if (p->ip_notify & N_DDCD)
+ UP_DDCD(port, 0);
+
+ DEBUGINC(read_ddcd, 1);
+
+ /* If we had any data to return, we would have
+ * returned it above.
+ */
+ return(0);
+ }
+ }
+
+ /* Notify upper layer that an input overrun occurred */
+ if ((*sc & IOC4_RXSB_OVERRUN) && (p->ip_notify & N_OVERRUN_ERROR)) {
+ DEBUGINC(rx_overrun, 1);
+ UP_NCS(port, NCS_OVERRUN);
+ }
+
+ /* Don't look at this byte again */
+ *sc &= ~IOC4_RXSB_MODEM_VALID;
+ }
+
+ /* Check for valid data or RX errors */
+ if (*sc & IOC4_RXSB_DATA_VALID) {
+ if ((*sc & (IOC4_RXSB_PAR_ERR | IOC4_RXSB_FRAME_ERR |
+ IOC4_RXSB_BREAK)) &&
+ (p->ip_notify & (N_PARITY_ERROR | N_FRAMING_ERROR | N_BREAK))) {
+
+ /* There is an error condition on the next byte. If
+ * we have already transferred some bytes, we'll stop
+ * here. Otherwise if this is the first byte to be read,
+ * we'll just transfer it alone after notifying the
+ * upper layer of its status.
+ */
+ if (total > 0) {
+ len = 0;
+ break;
+ }
+ else {
+ if ((*sc & IOC4_RXSB_PAR_ERR) &&
+ (p->ip_notify & N_PARITY_ERROR)) {
+ DEBUGINC(parity, 1);
+ UP_NCS(port, NCS_PARITY);
+ }
+
+ if ((*sc & IOC4_RXSB_FRAME_ERR) &&
+ (p->ip_notify & N_FRAMING_ERROR)) {
+ DEBUGINC(framing, 1);
+ UP_NCS(port, NCS_FRAMING);
+ }
+
+ if ((*sc & IOC4_RXSB_BREAK) &&
+ (p->ip_notify & N_BREAK)) {
+ DEBUGINC(brk, 1);
+ UP_NCS(port, NCS_BREAK);
+ }
+ len = 1;
+ }
+ }
+
+ *sc &= ~IOC4_RXSB_DATA_VALID;
+ *buf++ = entry->ring_data[x];
+ len--;
+ total++;
+ }
+ }
+
+ DEBUGINC(rx_buf_used, x);
+ DEBUGINC(rx_buf_cnt, 1);
+
+ /* If we used up this entry entirely, go on to the next one,
+ * otherwise we must have run out of buffer space, so
+ * leave the consumer pointer here for the next read in case
+ * there are still unread bytes in this entry.
+ */
+ if ((entry->ring_allsc & RING_ANY_VALID) == 0)
+ cons_ptr = (cons_ptr + (int) sizeof(struct ring_entry)) &
+ PROD_CONS_MASK;
+ }
+
+ /* Update consumer pointer and re-arm RX timer interrupt */
+ PCI_OUTW(&p->ip_serial->srcir, cons_ptr);
+ p->ip_rx_cons = cons_ptr;
+
+ /* If we have now dipped below the RX high water mark and we have
+ * RX_HIGH interrupt turned off, we can now turn it back on again.
+ */
+ if ((p->ip_flags & INPUT_HIGH) &&
+ (((prod_ptr - cons_ptr) & PROD_CONS_MASK) <
+ ((p->ip_sscr & IOC4_SSCR_RX_THRESHOLD) << IOC4_PROD_CONS_PTR_OFF))) {
+ p->ip_flags &= ~INPUT_HIGH;
+ enable_intrs(p, H_INTR_RX_HIGH);
+ }
+
+ DEBUGINC(red_bytes, total);
+
+ return(total);
+}
+
+
+/*
+ * Modify event notification
+ */
+static int
+ioc4_notification(sioport_t *port, int mask, int on)
+{
+ ioc4port_t *p = LPORT(port);
+ struct hooks *hooks = p->ip_hooks;
+ ioc4reg_t intrbits, sscrbits;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_NOTIFICATION));
+#endif
+ ASSERT(mask);
+
+ intrbits = sscrbits = 0;
+
+ if (mask & N_DATA_READY)
+ intrbits |= (H_INTR_RX_TIMER | H_INTR_RX_HIGH);
+ if (mask & N_OUTPUT_LOWAT)
+ intrbits |= H_INTR_TX_EXPLICIT;
+ if (mask & N_DDCD) {
+ intrbits |= H_INTR_DELTA_DCD;
+ sscrbits |= IOC4_SSCR_RX_RING_DCD;
+ }
+ if (mask & N_DCTS)
+ intrbits |= H_INTR_DELTA_CTS;
+
+ if (on) {
+ enable_intrs(p, intrbits);
+ p->ip_notify |= mask;
+ p->ip_sscr |= sscrbits;
+ }
+ else {
+ disable_intrs(p, intrbits);
+ p->ip_notify &= ~mask;
+ p->ip_sscr &= ~sscrbits;
+ }
+
+ /* We require DMA if either DATA_READY or DDCD notification is
+ * currently requested. If neither of these is requested and
+ * there is currently no TX in progress, DMA may be disabled.
+ */
+ if (p->ip_notify & (N_DATA_READY | N_DDCD))
+ p->ip_sscr |= IOC4_SSCR_DMA_EN;
+ else if (!(p->ip_ienb & H_INTR_TX_MT))
+ p->ip_sscr &= ~IOC4_SSCR_DMA_EN;
+
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+ return(0);
+}
+
+
+/*
+ * Set RX timeout and threshold values. The upper layer passes in a
+ * timeout value. In all cases it would like to be notified at least this
+ * often when there are RX chars coming in. We set the RX timeout and
+ * RX threshold (based on baud) to ensure that the upper layer is called
+ * at roughly this interval during normal RX.
+ * The input timeout value is in ticks.
+ */
+static int
+ioc4_rx_timeout(sioport_t *port, int timeout)
+{
+ int threshold;
+ ioc4port_t *p = LPORT(port);
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_RX_TIMEOUT));
+#endif
+
+ p->ip_rx_timeout = timeout;
+
+ /* Timeout is in ticks. Let's figure out how many chars we
+ * can receive at the current baud rate in that interval
+ * and set the RX threshold to that amount. There are 4 chars
+ * per ring entry, so we'll divide the number of chars that will
+ * arrive in timeout by 4.
+ */
+ threshold = timeout * p->ip_baud / 10 / HZ / 4;
+ if (threshold == 0)
+ threshold = 1; /* otherwise we'll intr all the time! */
+
+ if ((unsigned) threshold > (unsigned) IOC4_SSCR_RX_THRESHOLD)
+ return(1);
+
+ p->ip_sscr &= ~IOC4_SSCR_RX_THRESHOLD;
+ p->ip_sscr |= threshold;
+
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+
+ /* Now set the RX timeout to the given value */
+ timeout = timeout * IOC4_SRTR_HZ / HZ;
+ if (timeout > IOC4_SRTR_CNT)
+ timeout = IOC4_SRTR_CNT;
+
+ PCI_OUTW(&p->ip_serial->srtr, timeout);
+
+ return(0);
+}
+
+
+static int
+set_DTRRTS(sioport_t *port, int val, int mask1, int mask2)
+{
+ ioc4port_t *p = LPORT(port);
+ ioc4reg_t shadow;
+ int spin_success;
+ char mcr;
+
+ /* XXX need lock for pretty much this entire routine. Makes
+ * me nervous to hold it for so long. If we crash or hit
+ * a breakpoint in here, we're hosed.
+ */
+
+ /* Pause the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN) {
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr | IOC4_SSCR_DMA_PAUSE);
+ SPIN((PCI_INW(&p->ip_serial->sscr) & IOC4_SSCR_PAUSE_STATE) == 0,
+ spin_success);
+ if (!spin_success)
+ return(-1);
+ }
+
+ shadow = PCI_INW(&p->ip_serial->shadow);
+ mcr = (shadow & 0xff000000) >> 24;
+
+ /* Set new value */
+ if (val) {
+ mcr |= mask1;
+ shadow |= mask2;
+ }
+ else {
+ mcr &= ~mask1;
+ shadow &= ~mask2;
+ }
+
+ PCI_OUTB(&p->ip_uart->i4u_mcr, mcr);
+
+ PCI_OUTW(&p->ip_serial->shadow, shadow);
+
+ /* Re-enable the DMA interface if necessary */
+ if (p->ip_sscr & IOC4_SSCR_DMA_EN)
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+
+ return(0);
+}
+
+
+static int
+ioc4_set_DTR(sioport_t *port, int dtr)
+{
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_SET_DTR));
+#endif
+
+ dprintf(("set dtr port 0x%p, dtr %d\n", (void *)port, dtr));
+ return(set_DTRRTS(port, dtr, MCR_DTR, IOC4_SHADOW_DTR));
+}
+
+
+static int
+ioc4_set_RTS(sioport_t *port, int rts)
+{
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_SET_RTS));
+#endif
+
+ dprintf(("set rts port 0x%p, rts %d\n", (void *)port, rts));
+ return(set_DTRRTS(port, rts, MCR_RTS, IOC4_SHADOW_RTS));
+}
+
+
+static int
+ioc4_query_DCD(sioport_t *port)
+{
+ ioc4port_t *p = LPORT(port);
+ ioc4reg_t shadow;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_QUERY_DCD));
+#endif
+
+ dprintf(("get dcd port 0x%p\n", (void *)port));
+
+ shadow = PCI_INW(&p->ip_serial->shadow);
+
+ return(shadow & IOC4_SHADOW_DCD);
+}
+
+
+static int
+ioc4_query_CTS(sioport_t *port)
+{
+ ioc4port_t *p = LPORT(port);
+ ioc4reg_t shadow;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_QUERY_CTS));
+#endif
+
+ dprintf(("get cts port 0x%p\n", (void *)port));
+
+ shadow = PCI_INW(&p->ip_serial->shadow);
+
+ return(shadow & IOC4_SHADOW_CTS);
+}
+
+
+static int
+ioc4_set_proto(sioport_t *port, enum sio_proto proto)
+{
+ ioc4port_t *p = LPORT(port);
+ struct hooks *hooks = p->ip_hooks;
+
+#ifdef NOT_YET
+ ASSERT(L_LOCKED(port, L_SET_PROTOCOL));
+#endif
+
+ switch(proto) {
+ case PROTO_RS232:
+ /* Clear the appropriate GIO pin */
+ PCI_OUTW((&p->ip_ioc4->gppr_0 + H_RS422), 0);
+ break;
+
+ case PROTO_RS422:
+ /* Set the appropriate GIO pin */
+ PCI_OUTW((&p->ip_ioc4->gppr_0 + H_RS422), 1);
+ break;
+
+ default:
+ return(1);
+ }
+
+ return(0);
+}
+
+
+// #define IS_PORT_0(p) ((p)->ip_hooks == &hooks_array[0])
+
+static int
+ioc4_get_mapid(sioport_t *port, void *arg)
+{
+ return(0);
+}
+
+
+static int
+ioc4_set_sscr(sioport_t *port, int arg, int flag)
+{
+ ioc4port_t *p = LPORT(port);
+
+ if ( flag ) { /* reset arg bits in p->ip_sscr */
+ p->ip_sscr &= ~arg;
+ } else { /* set bits in p->ip_sscr */
+ p->ip_sscr |= arg;
+ }
+ PCI_OUTW(&p->ip_serial->sscr, p->ip_sscr);
+ return(p->ip_sscr);
+}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)