Add support for Broadcom SWARM Duart. Signed-off-by: Manish Lachwani <mlachwani@xxxxxxxxxx> Signed-off-by: Deepak Saxena <dsaxena@xxxxxxxxxx> --- Applies to 2.6.21-rc4 with some fuzz in Kconfig. I am not orginal author and not a MIPS person, just trying to get this out of our internal-only tree. drivers/char/Kconfig | 8 drivers/char/Makefile | 1 drivers/char/sb1250_duart.c | 910 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/serial.h | 3 4 files changed, 921 insertions(+), 1 deletion(-) Index: linux-2.6.18/drivers/char/Kconfig =================================================================== --- linux-2.6.18.orig/drivers/char/Kconfig +++ linux-2.6.18/drivers/char/Kconfig @@ -357,6 +357,14 @@ config ISTALLION To compile this driver as a module, choose M here: the module will be called istallion. +config SIBYTE_SB1250_DUART + bool "Support for BCM1xxx onchip DUART" + depends on MIPS && SIBYTE_SB1xxx_SOC=y + +config SIBYTE_SB1250_DUART_CONSOLE + bool "Console on BCM1xxx DUART" + depends on SIBYTE_SB1250_DUART + config AU1000_UART bool "Enable Au1000 UART Support" depends on SERIAL_NONSTANDARD && MIPS Index: linux-2.6.18/drivers/char/Makefile =================================================================== --- linux-2.6.18.orig/drivers/char/Makefile +++ linux-2.6.18/drivers/char/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_ISTALLION) += istallion.o obj-$(CONFIG_DIGIEPCA) += epca.o obj-$(CONFIG_SPECIALIX) += specialix.o obj-$(CONFIG_MOXA_INTELLIO) += moxa.o +obj-$(CONFIG_SIBYTE_SB1250_DUART) += sb1250_duart.o obj-$(CONFIG_A2232) += ser_a2232.o generic_serial.o obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o obj-$(CONFIG_MOXA_SMARTIO) += mxser.o Index: linux-2.6.18/drivers/char/sb1250_duart.c =================================================================== --- /dev/null +++ linux-2.6.18/drivers/char/sb1250_duart.c @@ -0,0 +1,910 @@ +/* + * Copyright (C) 2000,2001,2002,2003,2004 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Driver support for the on-chip sb1250 dual-channel serial port, + * running in asynchronous mode. Also, support for doing a serial console + * on one of those ports + */ +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/serial.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/console.h> +#include <linux/kdev_t.h> +#include <linux/major.h> +#include <linux/termios.h> +#include <linux/spinlock.h> +#include <linux/irq.h> +#include <linux/errno.h> +#include <linux/tty.h> +#include <linux/sched.h> +#include <linux/tty_flip.h> +#include <linux/timer.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <asm/delay.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/sibyte/swarm.h> +#include <asm/sibyte/sb1250.h> +#if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) +#include <asm/sibyte/bcm1480_regs.h> +#include <asm/sibyte/bcm1480_int.h> +#elif defined(CONFIG_SIBYTE_SB1250) || defined(CONFIG_SIBYTE_BCM112X) +#include <asm/sibyte/sb1250_regs.h> +#include <asm/sibyte/sb1250_int.h> +#else +#error invalid SiByte UART configuation +#endif +#include <asm/sibyte/sb1250_uart.h> +#include <asm/war.h> + +#if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) +#define UNIT_CHANREG(n,reg) A_BCM1480_DUART_CHANREG((n),(reg)) +#define UNIT_IMRREG(n) A_BCM1480_DUART_IMRREG(n) +#define UNIT_INT(n) (K_BCM1480_INT_UART_0 + (n)) +#elif defined(CONFIG_SIBYTE_SB1250) || defined(CONFIG_SIBYTE_BCM112X) +#define UNIT_CHANREG(n,reg) A_DUART_CHANREG((n),(reg)) +#define UNIT_IMRREG(n) A_DUART_IMRREG(n) +#define UNIT_INT(n) (K_INT_UART_0 + (n)) +#else +#error invalid SiByte UART configuation +#endif + +/* Toggle spewing of debugging output */ +#undef DEBUG + +#define DEFAULT_CFLAGS (CS8 | B115200) + +#define TX_INTEN 1 +#define DUART_INITIALIZED 2 + +#define DUART_MAX_LINE 4 +char sb1250_duart_present[DUART_MAX_LINE]; +EXPORT_SYMBOL(sb1250_duart_present); + +/* + * Still not sure what the termios structures set up here are for, + * but we have to supply pointers to them to register the tty driver + */ +static struct tty_driver *sb1250_duart_driver; //, sb1250_duart_callout_driver; + +/* + * This lock protects both the open flags for all the uart states as + * well as the reference count for the module + */ +static DEFINE_SPINLOCK(open_lock); + +typedef struct { + unsigned char outp_buf[SERIAL_XMIT_SIZE]; + unsigned int outp_head; + unsigned int outp_tail; + unsigned int outp_count; + spinlock_t outp_lock; + unsigned int open; + unsigned int line; + unsigned int last_cflags; + unsigned long flags; + struct tty_struct *tty; + /* CSR addresses */ + volatile u32 *status; + volatile u32 *imr; + volatile u32 *tx_hold; + volatile u32 *rx_hold; + volatile u32 *mode_1; + volatile u32 *mode_2; + volatile u32 *clk_sel; + volatile u32 *cmd; +} uart_state_t; + +static uart_state_t uart_states[DUART_MAX_LINE]; + +/* + * Inline functions local to this module + */ + +/* + * In bug 1956, we get glitches that can mess up uart registers. This + * "write-mode-1 after any register access" is the accepted + * workaround. + */ +#if SIBYTE_1956_WAR +static unsigned int last_mode1[DUART_MAX_LINE]; +#endif + +static inline u32 READ_SERCSR(volatile u32 *addr, int line) +{ + u32 val = csr_in32(addr); +#if SIBYTE_1956_WAR + csr_out32(last_mode1[line], uart_states[line].mode_1); +#endif + return val; +} + +static inline void WRITE_SERCSR(u32 val, volatile u32 *addr, int line) +{ + csr_out32(val, addr); +#if SIBYTE_1956_WAR + csr_out32(last_mode1[line], uart_states[line].mode_1); +#endif +} + +static void init_duart_port(uart_state_t *port, int line) +{ + if (!(port->flags & DUART_INITIALIZED)) { + port->line = line; + port->status = IOADDR(UNIT_CHANREG(line, R_DUART_STATUS)); + port->imr = IOADDR(UNIT_IMRREG(line)); + port->tx_hold = IOADDR(UNIT_CHANREG(line, R_DUART_TX_HOLD)); + port->rx_hold = IOADDR(UNIT_CHANREG(line, R_DUART_RX_HOLD)); + port->mode_1 = IOADDR(UNIT_CHANREG(line, R_DUART_MODE_REG_1)); + port->mode_2 = IOADDR(UNIT_CHANREG(line, R_DUART_MODE_REG_2)); + port->clk_sel = IOADDR(UNIT_CHANREG(line, R_DUART_CLK_SEL)); + port->cmd = IOADDR(UNIT_CHANREG(line, R_DUART_CMD)); + port->flags |= DUART_INITIALIZED; + } +} + +/* + * Mask out the passed interrupt lines at the duart level. This should be + * called while holding the associated outp_lock. + */ +static inline void duart_mask_ints(unsigned int line, unsigned int mask) +{ + uart_state_t *port = uart_states + line; + u64 tmp = READ_SERCSR(port->imr, line); + WRITE_SERCSR(tmp & ~mask, port->imr, line); +} + + +/* Unmask the passed interrupt lines at the duart level */ +static inline void duart_unmask_ints(unsigned int line, unsigned int mask) +{ + uart_state_t *port = uart_states + line; + u64 tmp = READ_SERCSR(port->imr, line); + WRITE_SERCSR(tmp | mask, port->imr, line); +} + +static inline void transmit_char_pio(uart_state_t *us) +{ + struct tty_struct *tty = us->tty; + int blocked = 0; + + if (spin_trylock(&us->outp_lock)) { + for (;;) { + if (!(READ_SERCSR(us->status, us->line) & M_DUART_TX_RDY)) + break; + if (us->outp_count <= 0 || tty->stopped || tty->hw_stopped) { + break; + } else { + WRITE_SERCSR(us->outp_buf[us->outp_head], + us->tx_hold, us->line); + us->outp_head = (us->outp_head + 1) & (SERIAL_XMIT_SIZE-1); + if (--us->outp_count <= 0) + break; + } + udelay(10); + } + spin_unlock(&us->outp_lock); + } else { + blocked = 1; + } + + if (!us->outp_count || tty->stopped || + tty->hw_stopped || blocked) { + us->flags &= ~TX_INTEN; + duart_mask_ints(us->line, M_DUART_IMR_TX); + } + + if (us->open && + (us->outp_count < (SERIAL_XMIT_SIZE/2))) { + /* + * We told the discipline at one point that we had no + * space, so it went to sleep. Wake it up when we hit + * half empty + */ + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + tty->ldisc.write_wakeup(tty); + wake_up_interruptible(&tty->write_wait); + } +} + +/* + * Generic interrupt handler for both channels. dev_id is a pointer + * to the proper uart_states structure, so from that we can derive + * which port interrupted + */ + +static irqreturn_t duart_int(int irq, void *dev_id, struct pt_regs *regs) +{ + uart_state_t *us = (uart_state_t *)dev_id; + struct tty_struct *tty = us->tty; + unsigned int status = READ_SERCSR(us->status, us->line); + + pr_debug("DUART INT\n"); + + if (status & M_DUART_RX_RDY) { + int counter = 2048; + unsigned int ch; + + if (status & M_DUART_OVRUN_ERR) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + if (status & M_DUART_PARITY_ERR) { + printk("Parity error!\n"); + } else if (status & M_DUART_FRM_ERR) { + printk("Frame error!\n"); + } + + while (counter > 0) { + if (!(READ_SERCSR(us->status, us->line) & M_DUART_RX_RDY)) + break; + ch = READ_SERCSR(us->rx_hold, us->line); + tty_insert_flip_char(tty, ch, 0); + udelay(1); + counter--; + } + tty_flip_buffer_push(tty); + } + + if (status & M_DUART_TX_RDY) { + transmit_char_pio(us); + } + + return IRQ_HANDLED; +} + +/* + * Actual driver functions + */ + +/* Return the number of characters we can accomodate in a write at this instant */ +static int duart_write_room(struct tty_struct *tty) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + int retval; + + retval = SERIAL_XMIT_SIZE - us->outp_count; + + pr_debug("duart_write_room called, returning %i\n", retval); + + return retval; +} + +/* memcpy the data from src to destination, but take extra care if the + data is coming from user space */ +static inline int copy_buf(char *dest, const char *src, int size, int from_user) +{ + if (from_user) { + (void) copy_from_user(dest, src, size); + } else { + memcpy(dest, src, size); + } + return size; +} + +/* + * Buffer up to count characters from buf to be written. If we don't have + * other characters buffered, enable the tx interrupt to start sending + */ +static int duart_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + uart_state_t *us; + int c, t, total = 0; + unsigned long flags; + + if (!tty) return 0; + + us = tty->driver_data; + if (!us) return 0; + + pr_debug("duart_write called for %i chars by %i (%s)\n", count, current->pid, current->comm); + + spin_lock_irqsave(&us->outp_lock, flags); + + for (;;) { + c = count; + + t = SERIAL_XMIT_SIZE - us->outp_tail; + if (t < c) c = t; + + t = SERIAL_XMIT_SIZE - 1 - us->outp_count; + if (t < c) c = t; + + if (c <= 0) break; + + memcpy(us->outp_buf + us->outp_tail, buf, c); + + us->outp_count += c; + us->outp_tail = (us->outp_tail + c) & (SERIAL_XMIT_SIZE - 1); + buf += c; + count -= c; + total += c; + } + + spin_unlock_irqrestore(&us->outp_lock, flags); + + if (us->outp_count && !tty->stopped && + !tty->hw_stopped && !(us->flags & TX_INTEN)) { + us->flags |= TX_INTEN; + duart_unmask_ints(us->line, M_DUART_IMR_TX); + } + + return total; +} + + +/* Buffer one character to be written. If there's not room for it, just drop + it on the floor. This is used for echo, among other things */ +static void duart_put_char(struct tty_struct *tty, u_char ch) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + unsigned long flags; + + pr_debug("duart_put_char called. Char is %x (%c)\n", (int)ch, ch); + + spin_lock_irqsave(&us->outp_lock, flags); + + if (us->outp_count == SERIAL_XMIT_SIZE) { + spin_unlock_irqrestore(&us->outp_lock, flags); + return; + } + + us->outp_buf[us->outp_tail] = ch; + us->outp_tail = (us->outp_tail + 1) &(SERIAL_XMIT_SIZE-1); + us->outp_count++; + + spin_unlock_irqrestore(&us->outp_lock, flags); +} + +static void duart_flush_chars(struct tty_struct * tty) +{ + uart_state_t *port; + + if (!tty) return; + + port = tty->driver_data; + + if (!port) return; + + if (port->outp_count <= 0 || tty->stopped || tty->hw_stopped) { + return; + } + + port->flags |= TX_INTEN; + duart_unmask_ints(port->line, M_DUART_IMR_TX); +} + +/* Return the number of characters in the output buffer that have yet to be + written */ +static int duart_chars_in_buffer(struct tty_struct *tty) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + int retval; + + retval = us->outp_count; + + pr_debug("duart_chars_in_buffer returning %i\n", retval); + + return retval; +} + +/* Kill everything we haven't yet shoved into the FIFO. Turn off the + transmit interrupt since we've nothing more to transmit */ +static void duart_flush_buffer(struct tty_struct *tty) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + unsigned long flags; + + pr_debug("duart_flush_buffer called\n"); + spin_lock_irqsave(&us->outp_lock, flags); + us->outp_head = us->outp_tail = us->outp_count = 0; + spin_unlock_irqrestore(&us->outp_lock, flags); + + wake_up_interruptible(&us->tty->write_wait); + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + tty->ldisc.write_wakeup(tty); +} + + +/* See sb1250 user manual for details on these registers */ +static inline void duart_set_cflag(unsigned int line, unsigned int cflag) +{ + unsigned int mode_reg1 = 0, mode_reg2 = 0; + unsigned int clk_divisor; + uart_state_t *port = uart_states + line; + + switch (cflag & CSIZE) { + case CS7: + mode_reg1 |= V_DUART_BITS_PER_CHAR_7; + + default: + /* We don't handle CS5 or CS6...is there a way we're supposed to flag this? + right now we just force them to CS8 */ + mode_reg1 |= 0x0; + break; + } + if (cflag & CSTOPB) { + mode_reg2 |= M_DUART_STOP_BIT_LEN_2; + } + if (!(cflag & PARENB)) { + mode_reg1 |= V_DUART_PARITY_MODE_NONE; + } + if (cflag & PARODD) { + mode_reg1 |= M_DUART_PARITY_TYPE_ODD; + } + + /* Formula for this is (5000000/baud)-1, but we saturate + at 12 bits, which means we can't actually do anything less + that 1200 baud */ + switch (cflag & CBAUD) { + case B200: + case B300: + case B1200: clk_divisor = 4095; break; + case B1800: clk_divisor = 2776; break; + case B2400: clk_divisor = 2082; break; + case B4800: clk_divisor = 1040; break; + default: + case B9600: clk_divisor = 519; break; + case B19200: clk_divisor = 259; break; + case B38400: clk_divisor = 129; break; + case B57600: clk_divisor = 85; break; + case B115200: clk_divisor = 42; break; + } + WRITE_SERCSR(mode_reg1, port->mode_1, port->line); + WRITE_SERCSR(mode_reg2, port->mode_2, port->line); + WRITE_SERCSR(clk_divisor, port->clk_sel, port->line); + port->last_cflags = cflag; +} + + +/* Handle notification of a termios change. */ +static void duart_set_termios(struct tty_struct *tty, struct termios *old) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + + pr_debug("duart_set_termios called by %i (%s)\n", current->pid, current->comm); + if (old && tty->termios->c_cflag == old->c_cflag) + return; + duart_set_cflag(us->line, tty->termios->c_cflag); +} + +static int get_serial_info(uart_state_t *us, struct serial_struct * retinfo) { + + struct serial_struct tmp; + + memset(&tmp, 0, sizeof(tmp)); + + tmp.type=PORT_SB1250; + tmp.line=us->line; + tmp.port=UNIT_CHANREG(tmp.line,0); + tmp.irq=UNIT_INT(tmp.line); + tmp.xmit_fifo_size=16; /* fixed by hw */ + tmp.baud_base=5000000; + tmp.io_type=SERIAL_IO_MEM; + + if (copy_to_user(retinfo,&tmp,sizeof(*retinfo))) + return -EFAULT; + + return 0; +} + +static int duart_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + +/* if (serial_paranoia_check(info, tty->device, "rs_ioctl")) + return -ENODEV;*/ + switch (cmd) { + case TIOCMGET: + printk("Ignoring TIOCMGET\n"); + break; + case TIOCMBIS: + printk("Ignoring TIOCMBIS\n"); + break; + case TIOCMBIC: + printk("Ignoring TIOCMBIC\n"); + break; + case TIOCMSET: + printk("Ignoring TIOCMSET\n"); + break; + case TIOCGSERIAL: + return get_serial_info(us,(struct serial_struct *) arg); + case TIOCSSERIAL: + printk("Ignoring TIOCSSERIAL\n"); + break; + case TIOCSERCONFIG: + printk("Ignoring TIOCSERCONFIG\n"); + break; + case TIOCSERGETLSR: /* Get line status register */ + printk("Ignoring TIOCSERGETLSR\n"); + break; + case TIOCSERGSTRUCT: + printk("Ignoring TIOCSERGSTRUCT\n"); + break; + case TIOCMIWAIT: + printk("Ignoring TIOCMIWAIT\n"); + break; + case TIOCGICOUNT: + printk("Ignoring TIOCGICOUNT\n"); + break; + case TIOCSERGWILD: + printk("Ignoring TIOCSERGWILD\n"); + break; + case TIOCSERSWILD: + printk("Ignoring TIOCSERSWILD\n"); + break; + default: + break; + } +// printk("Ignoring IOCTL %x from pid %i (%s)\n", cmd, current->pid, current->comm); + return -ENOIOCTLCMD; +} + +/* XXXKW locking? */ +static void duart_start(struct tty_struct *tty) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + + pr_debug("duart_start called\n"); + + if (us->outp_count && !(us->flags & TX_INTEN)) { + us->flags |= TX_INTEN; + duart_unmask_ints(us->line, M_DUART_IMR_TX); + } +} + +/* XXXKW locking? */ +static void duart_stop(struct tty_struct *tty) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + + pr_debug("duart_stop called\n"); + + if (us->outp_count && (us->flags & TX_INTEN)) { + us->flags &= ~TX_INTEN; + duart_mask_ints(us->line, M_DUART_IMR_TX); + } +} + +/* Not sure on the semantics of this; are we supposed to wait until the stuff + already in the hardware FIFO drains, or are we supposed to wait until + we've drained the output buffer, too? I'm assuming the former, 'cause thats + what the other drivers seem to assume +*/ + +static void duart_wait_until_sent(struct tty_struct *tty, int timeout) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + unsigned long orig_jiffies; + + orig_jiffies = jiffies; + pr_debug("duart_wait_until_sent(%d)+\n", timeout); + while (!(READ_SERCSR(us->status, us->line) & M_DUART_TX_EMT)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + if (timeout && time_after(jiffies, orig_jiffies + timeout)) + break; + } + pr_debug("duart_wait_until_sent()-\n"); +} + +/* + * duart_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +static void duart_hangup(struct tty_struct *tty) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + + duart_flush_buffer(tty); + us->open = 0; + us->tty = 0; +} + +/* + * Open a tty line. Note that this can be called multiple times, so ->open can + * be >1. Only set up the tty struct if this is a "new" open, e.g. ->open was + * zero + */ +static int duart_open(struct tty_struct *tty, struct file *filp) +{ + uart_state_t *us; + unsigned int line = tty->index; + unsigned long flags; + + if ((line >= tty->driver->num) || !sb1250_duart_present[line]) + return -ENODEV; + + pr_debug("duart_open called by %i (%s), tty is %p, rw is %p, ww is %p\n", + current->pid, current->comm, tty, tty->read_wait, + tty->write_wait); + + us = uart_states + line; + tty->driver_data = us; + + spin_lock_irqsave(&open_lock, flags); + if (!us->open) { + us->tty = tty; + us->tty->termios->c_cflag = us->last_cflags; + } + us->open++; + us->flags &= ~TX_INTEN; + duart_unmask_ints(line, M_DUART_IMR_RX); + spin_unlock_irqrestore(&open_lock, flags); + + return 0; +} + + +/* + * Close a reference count out. If reference count hits zero, null the + * tty, kill the interrupts. The tty_io driver is responsible for making + * sure we've cleared out our internal buffers before calling close() + */ +static void duart_close(struct tty_struct *tty, struct file *filp) +{ + uart_state_t *us = (uart_state_t *) tty->driver_data; + unsigned long flags; + + pr_debug("duart_close called by %i (%s)\n", current->pid, current->comm); + + if (!us || !us->open) + return; + + spin_lock_irqsave(&open_lock, flags); + if (tty_hung_up_p(filp)) { + spin_unlock_irqrestore(&open_lock, flags); + return; + } + + if (--us->open < 0) { + us->open = 0; + printk(KERN_ERR "duart: bad open count: %d\n", us->open); + } + if (us->open) { + spin_unlock_irqrestore(&open_lock, flags); + return; + } + + spin_unlock_irqrestore(&open_lock, flags); + + tty->closing = 1; + + /* Stop accepting input */ + duart_mask_ints(us->line, M_DUART_IMR_RX); + /* Wait for FIFO to drain */ + while (!(READ_SERCSR(us->status, us->line) & M_DUART_TX_EMT)) + ; + + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + tty->closing = 0; +} + + +static struct tty_operations duart_ops = { + .open = duart_open, + .close = duart_close, + .write = duart_write, + .put_char = duart_put_char, + .flush_chars = duart_flush_chars, + .write_room = duart_write_room, + .chars_in_buffer = duart_chars_in_buffer, + .flush_buffer = duart_flush_buffer, + .ioctl = duart_ioctl, +// .throttle = duart_throttle, +// .unthrottle = duart_unthrottle, + .set_termios = duart_set_termios, + .stop = duart_stop, + .start = duart_start, + .hangup = duart_hangup, + .wait_until_sent = duart_wait_until_sent, +}; + +/* Initialize the sb1250_duart_present array based on SOC type. */ +static void __init sb1250_duart_init_present_lines(void) +{ + int i, max_lines; + + /* Set the number of available units based on the SOC type. */ + switch (soc_type) { + case K_SYS_SOC_TYPE_BCM1x55: + case K_SYS_SOC_TYPE_BCM1x80: + max_lines = 4; + break; + default: + /* Assume at least two serial ports at the normal address. */ + max_lines = 2; + break; + } + if (max_lines > DUART_MAX_LINE) + max_lines = DUART_MAX_LINE; + + for (i = 0; i < max_lines; i++) + sb1250_duart_present[i] = 1; +} + +/* Set up the driver and register it, register the UART interrupts. This + is called from tty_init, or as a part of the module init */ +static int __init sb1250_duart_init(void) +{ + int i; + + sb1250_duart_init_present_lines(); + + sb1250_duart_driver = alloc_tty_driver(DUART_MAX_LINE); + if (!sb1250_duart_driver) + return -ENOMEM; + + sb1250_duart_driver->owner = THIS_MODULE; + sb1250_duart_driver->name = "duart"; + sb1250_duart_driver->major = TTY_MAJOR; + sb1250_duart_driver->minor_start = SB1250_DUART_MINOR_BASE; + sb1250_duart_driver->type = TTY_DRIVER_TYPE_SERIAL; + sb1250_duart_driver->subtype = SERIAL_TYPE_NORMAL; + sb1250_duart_driver->init_termios = tty_std_termios; + sb1250_duart_driver->flags = TTY_DRIVER_REAL_RAW; + tty_set_operations(sb1250_duart_driver, &duart_ops); + + for (i=0; i<DUART_MAX_LINE; i++) { + uart_state_t *port = uart_states + i; + + if (!sb1250_duart_present[i]) + continue; + + init_duart_port(port, i); + spin_lock_init(&port->outp_lock); + duart_mask_ints(i, M_DUART_IMR_ALL); + if (request_irq(UNIT_INT(i), duart_int, 0, "uart", port)) { + panic("Couldn't get uart0 interrupt line"); + } + __raw_writeq(M_DUART_RX_EN|M_DUART_TX_EN, + IOADDR(UNIT_CHANREG(i, R_DUART_CMD))); + duart_set_cflag(i, DEFAULT_CFLAGS); + } + + /* Interrupts are now active, our ISR can be called. */ + + if (tty_register_driver(sb1250_duart_driver)) { + printk(KERN_ERR "Couldn't register sb1250 duart serial driver\n"); + put_tty_driver(sb1250_duart_driver); + return 1; + } + return 0; +} + +/* Unload the driver. Unregister stuff, get ready to go away */ +static void __exit sb1250_duart_fini(void) +{ + unsigned long flags; + int i; + + local_irq_save(flags); + tty_unregister_driver(sb1250_duart_driver); + put_tty_driver(sb1250_duart_driver); + + for (i=0; i<DUART_MAX_LINE; i++) { + if (!sb1250_duart_present[i]) + continue; + free_irq(UNIT_INT(i), &uart_states[i]); + disable_irq(UNIT_INT(i)); + } + local_irq_restore(flags); +} + +module_init(sb1250_duart_init); +module_exit(sb1250_duart_fini); +MODULE_DESCRIPTION("SB1250 Duart serial driver"); +MODULE_AUTHOR("Broadcom Corp."); + +#ifdef CONFIG_SIBYTE_SB1250_DUART_CONSOLE + +/* + * Serial console stuff. Very basic, polling driver for doing serial + * console output. The console_sem is held by the caller, so we + * shouldn't be interrupted for more console activity. + * XXXKW What about getting interrupted by uart driver activity? + */ + +void serial_outc(unsigned char c, int line) +{ + uart_state_t *port = uart_states + line; + while (!(READ_SERCSR(port->status, line) & M_DUART_TX_RDY)) ; + WRITE_SERCSR(c, port->tx_hold, line); + while (!(READ_SERCSR(port->status, port->line) & M_DUART_TX_EMT)) ; +} + +static void ser_console_write(struct console *cons, const char *s, + unsigned int count) +{ + int line = cons->index; + uart_state_t *port = uart_states + line; + u32 imr; + + imr = READ_SERCSR(port->imr, line); + WRITE_SERCSR(0, port->imr, line); + while (count--) { + if (*s == '\n') + serial_outc('\r', line); + serial_outc(*s++, line); + } + WRITE_SERCSR(imr, port->imr, line); +} + +static struct tty_driver *ser_console_device(struct console *c, int *index) +{ + *index = c->index; + return sb1250_duart_driver; +} + +static int ser_console_setup(struct console *cons, char *str) +{ + int i; + + sb1250_duart_init_present_lines(); + + for (i=0; i<DUART_MAX_LINE; i++) { + uart_state_t *port = uart_states + i; + + if (!sb1250_duart_present[i]) + continue; + + init_duart_port(port, i); +#if SIBYTE_1956_WAR + last_mode1[i] = V_DUART_PARITY_MODE_NONE|V_DUART_BITS_PER_CHAR_8; +#endif + WRITE_SERCSR(V_DUART_PARITY_MODE_NONE|V_DUART_BITS_PER_CHAR_8, + port->mode_1, i); + WRITE_SERCSR(M_DUART_STOP_BIT_LEN_1, + port->mode_2, i); + WRITE_SERCSR(V_DUART_BAUD_RATE(115200), + port->clk_sel, i); + WRITE_SERCSR(M_DUART_RX_EN|M_DUART_TX_EN, + port->cmd, i); + } + return 0; +} + +static struct console sb1250_ser_cons = { + .name = "duart", + .write = ser_console_write, + .device = ser_console_device, + .setup = ser_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +static int __init sb1250_serial_console_init(void) +{ + register_console(&sb1250_ser_cons); + return 0; +} + +console_initcall(sb1250_serial_console_init); + +#endif /* CONFIG_SIBYTE_SB1250_DUART_CONSOLE */ Index: linux-2.6.18/include/linux/serial.h =================================================================== --- linux-2.6.18.orig/include/linux/serial.h +++ linux-2.6.18/include/linux/serial.h @@ -76,7 +76,8 @@ struct serial_struct { #define PORT_16654 11 #define PORT_16850 12 #define PORT_RSA 13 /* RSA-DV II/S card */ -#define PORT_MAX 13 +#define PORT_SB1250 14 +#define PORT_MAX 14 #define SERIAL_IO_PORT 0 #define SERIAL_IO_HUB6 1 -- Deepak Saxena - dsaxena@xxxxxxxxxxx - http://www.plexity.net In the end, they will not say, "those were dark times," they will ask "why were their poets silent?" - Bertolt Brecht