mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-22 14:04:37 +02:00
9e26bd65a8
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@7332 3c298f89-4303-0410-b956-a3cf2f4a3e73
1026 lines
27 KiB
Diff
1026 lines
27 KiB
Diff
diff -Nur linux-2.6.21.1/drivers/char/Kconfig linux-2.6.21.1-owrt/drivers/char/Kconfig
|
|
--- linux-2.6.21.1/drivers/char/Kconfig 2007-04-27 23:49:26.000000000 +0200
|
|
+++ linux-2.6.21.1-owrt/drivers/char/Kconfig 2007-05-24 22:28:54.000000000 +0200
|
|
@@ -386,6 +386,14 @@
|
|
If you have an Alchemy AU1000 processor (MIPS based) and you want
|
|
to use a console on a serial port, say Y. Otherwise, say N.
|
|
|
|
+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 SERIAL_DEC
|
|
bool "DECstation serial support"
|
|
depends on MACH_DECSTATION
|
|
diff -Nur linux-2.6.21.1/drivers/char/Makefile linux-2.6.21.1-owrt/drivers/char/Makefile
|
|
--- linux-2.6.21.1/drivers/char/Makefile 2007-04-27 23:49:26.000000000 +0200
|
|
+++ linux-2.6.21.1-owrt/drivers/char/Makefile 2007-05-24 22:32:31.000000000 +0200
|
|
@@ -32,6 +32,7 @@
|
|
obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o
|
|
obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
|
|
obj-$(CONFIG_MOXA_SMARTIO_NEW) += mxser_new.o
|
|
+obj-$(CONFIG_SIBYTE_SB1250_DUART) += sb1250_duart.o
|
|
obj-$(CONFIG_COMPUTONE) += ip2/
|
|
obj-$(CONFIG_RISCOM8) += riscom8.o
|
|
obj-$(CONFIG_ISI) += isicom.o
|
|
diff -Nur linux-2.6.21.1/drivers/char/sb1250_duart.c linux-2.6.21.1-owrt/drivers/char/sb1250_duart.c
|
|
--- linux-2.6.21.1/drivers/char/sb1250_duart.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-2.6.21.1-owrt/drivers/char/sb1250_duart.c 2007-05-24 22:10:12.000000000 +0200
|
|
@@ -0,0 +1,979 @@
|
|
+/*
|
|
+ * 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/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);
|
|
+
|
|
+/*
|
|
+ * In bug 1956, we get glitches that can mess up uart registers. This
|
|
+ * "read-mode-reg after any register access" is an accepted workaround.
|
|
+ */
|
|
+#if SIBYTE_1956_WAR
|
|
+# define SB1_SER1956_WAR { \
|
|
+ u32 ignore; \
|
|
+ ignore = csr_in32(uart_states[line].mode_1); \
|
|
+ ignore = csr_in32(uart_states[line].mode_2); \
|
|
+ }
|
|
+#else
|
|
+# define SB1_SER1956_WAR
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * 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 */
|
|
+ unsigned int *status;
|
|
+ unsigned int *imr;
|
|
+ unsigned int *tx_hold;
|
|
+ unsigned int *rx_hold;
|
|
+ unsigned int *mode_1;
|
|
+ unsigned int *mode_2;
|
|
+ unsigned int *clk_sel;
|
|
+ unsigned int *cmd;
|
|
+} uart_state_t;
|
|
+
|
|
+static uart_state_t uart_states[DUART_MAX_LINE];
|
|
+
|
|
+/*
|
|
+ * Inline functions local to this module
|
|
+ */
|
|
+
|
|
+static inline u32 READ_SERCSR(volatile u32 *addr, int line)
|
|
+{
|
|
+ u32 val = csr_in32(addr);
|
|
+ SB1_SER1956_WAR;
|
|
+ return val;
|
|
+}
|
|
+
|
|
+static inline void WRITE_SERCSR(u32 val, volatile u32 *addr, int line)
|
|
+{
|
|
+ csr_out32(val, addr);
|
|
+ SB1_SER1956_WAR;
|
|
+}
|
|
+
|
|
+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->last_cflags = DEFAULT_CFLAGS;
|
|
+ spin_lock_init(&port->outp_lock);
|
|
+ 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)
|
|
+{
|
|
+ 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;
|
|
+ case B9600: clk_divisor = 519; break;
|
|
+ case B19200: clk_divisor = 259; break;
|
|
+ case B38400: clk_divisor = 129; break;
|
|
+ default:
|
|
+ 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, (void *)&tty->read_wait,
|
|
+ (void *)&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);
|
|
+ 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");
|
|
+ }
|
|
+ /*
|
|
+ * this generic write to a register does not implement the 1956
|
|
+ * WAR and sometimes output gets corrupted afterwards,
|
|
+ * especially if the port was in use as a console.
|
|
+ */
|
|
+ __raw_writel(M_DUART_RX_EN|M_DUART_TX_EN, port->cmd);
|
|
+
|
|
+ /*
|
|
+ * we should really check to see if it's registered as a console
|
|
+ * before trashing those settings
|
|
+ */
|
|
+ duart_set_cflag(i, port->last_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;
|
|
+ u32 cflags = DEFAULT_CFLAGS;
|
|
+
|
|
+ if (!sb1250_duart_present[i])
|
|
+ continue;
|
|
+
|
|
+ init_duart_port(port, i);
|
|
+ if (str) {
|
|
+ int speed;
|
|
+ char par = 'n';
|
|
+ int cbits = 8;
|
|
+
|
|
+ cflags = 0;
|
|
+
|
|
+ /*
|
|
+ * format is in Documentation/serial_console.txt
|
|
+ */
|
|
+ sscanf(str, "%d%c%d", &speed, &par, &cbits);
|
|
+
|
|
+ switch (speed) {
|
|
+ case 200:
|
|
+ case 300:
|
|
+ case 1200:
|
|
+ cflags |= B1200;
|
|
+ break;
|
|
+ case 1800:
|
|
+ cflags |= B1800;
|
|
+ break;
|
|
+ case 2400:
|
|
+ cflags |= B2400;
|
|
+ break;
|
|
+ case 4800:
|
|
+ cflags |= B4800;
|
|
+ break;
|
|
+ default:
|
|
+ case 9600:
|
|
+ cflags |= B9600;
|
|
+ break;
|
|
+ case 19200:
|
|
+ cflags |= B19200;
|
|
+ break;
|
|
+ case 38400:
|
|
+ cflags |= B38400;
|
|
+ break;
|
|
+ case 57600:
|
|
+ cflags |= B57600;
|
|
+ break;
|
|
+ case 115200:
|
|
+ cflags |= B115200;
|
|
+ break;
|
|
+ }
|
|
+ switch (par) {
|
|
+ case 'o':
|
|
+ cflags |= PARODD;
|
|
+ case 'e':
|
|
+ cflags |= PARENB;
|
|
+ }
|
|
+ switch (cbits) {
|
|
+ default: // we only do 7 or 8
|
|
+ case 8:
|
|
+ cflags |= CS8;
|
|
+ break;
|
|
+ case 7:
|
|
+ cflags |= CS7;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ duart_set_cflag(i, cflags);
|
|
+ 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)
|
|
+{
|
|
+ //add_preferred_console("duart", 0, "57600n8");
|
|
+ register_console(&sb1250_ser_cons);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+console_initcall(sb1250_serial_console_init);
|
|
+
|
|
+#endif /* CONFIG_SIBYTE_SB1250_DUART_CONSOLE */
|
|
diff -Nur linux-2.6.21.1/include/linux/serial.h linux-2.6.21.1-owrt/include/linux/serial.h
|
|
--- linux-2.6.21.1/include/linux/serial.h 2007-04-27 23:49:26.000000000 +0200
|
|
+++ linux-2.6.21.1-owrt/include/linux/serial.h 2007-05-24 22:10:29.000000000 +0200
|
|
@@ -76,7 +76,8 @@
|
|
#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
|