| /* |
| * drivers/serial/sb1250-duart.c |
| * |
| * Support for the asynchronous serial interface (DUART) included |
| * in the BCM1250 and derived System-On-a-Chip (SOC) devices. |
| * |
| * Copyright (c) 2007 Maciej W. Rozycki |
| * |
| * Derived from drivers/char/sb1250_duart.c for which the following |
| * copyright applies: |
| * |
| * 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. |
| * |
| * References: |
| * |
| * "BCM1250/BCM1125/BCM1125H User Manual", Broadcom Corporation |
| */ |
| |
| #if defined(CONFIG_SERIAL_SB1250_DUART_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) |
| #define SUPPORT_SYSRQ |
| #endif |
| |
| #include <linux/compiler.h> |
| #include <linux/console.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioport.h> |
| #include <linux/kernel.h> |
| #include <linux/major.h> |
| #include <linux/serial.h> |
| #include <linux/serial_core.h> |
| #include <linux/spinlock.h> |
| #include <linux/sysrq.h> |
| #include <linux/tty.h> |
| #include <linux/types.h> |
| |
| #include <asm/atomic.h> |
| #include <asm/io.h> |
| #include <asm/war.h> |
| |
| #include <asm/sibyte/sb1250.h> |
| #include <asm/sibyte/sb1250_uart.h> |
| #include <asm/sibyte/swarm.h> |
| |
| |
| #if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) |
| #include <asm/sibyte/bcm1480_regs.h> |
| #include <asm/sibyte/bcm1480_int.h> |
| |
| #define SBD_CHANREGS(line) A_BCM1480_DUART_CHANREG((line), 0) |
| #define SBD_CTRLREGS(line) A_BCM1480_DUART_CTRLREG((line), 0) |
| #define SBD_INT(line) (K_BCM1480_INT_UART_0 + (line)) |
| |
| #define DUART_CHANREG_SPACING BCM1480_DUART_CHANREG_SPACING |
| |
| #define R_DUART_IMRREG(line) R_BCM1480_DUART_IMRREG(line) |
| #define R_DUART_INCHREG(line) R_BCM1480_DUART_INCHREG(line) |
| #define R_DUART_ISRREG(line) R_BCM1480_DUART_ISRREG(line) |
| |
| #elif defined(CONFIG_SIBYTE_SB1250) || defined(CONFIG_SIBYTE_BCM112X) |
| #include <asm/sibyte/sb1250_regs.h> |
| #include <asm/sibyte/sb1250_int.h> |
| |
| #define SBD_CHANREGS(line) A_DUART_CHANREG((line), 0) |
| #define SBD_CTRLREGS(line) A_DUART_CTRLREG(0) |
| #define SBD_INT(line) (K_INT_UART_0 + (line)) |
| |
| #else |
| #error invalid SB1250 UART configuration |
| |
| #endif |
| |
| |
| MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>"); |
| MODULE_DESCRIPTION("BCM1xxx on-chip DUART serial driver"); |
| MODULE_LICENSE("GPL"); |
| |
| |
| #define DUART_MAX_CHIP 2 |
| #define DUART_MAX_SIDE 2 |
| |
| /* |
| * Per-port state. |
| */ |
| struct sbd_port { |
| struct sbd_duart *duart; |
| struct uart_port port; |
| unsigned char __iomem *memctrl; |
| int tx_stopped; |
| int initialised; |
| }; |
| |
| /* |
| * Per-DUART state for the shared register space. |
| */ |
| struct sbd_duart { |
| struct sbd_port sport[2]; |
| unsigned long mapctrl; |
| atomic_t map_guard; |
| }; |
| |
| #define to_sport(uport) container_of(uport, struct sbd_port, port) |
| |
| static struct sbd_duart sbd_duarts[DUART_MAX_CHIP]; |
| |
| |
| /* |
| * Reading and writing SB1250 DUART registers. |
| * |
| * There are three register spaces: two per-channel ones and |
| * a shared one. We have to define accessors appropriately. |
| * All registers are 64-bit and all but the Baud Rate Clock |
| * registers only define 8 least significant bits. There is |
| * also a workaround to take into account. Raw accessors use |
| * the full register width, but cooked ones truncate it |
| * intentionally so that the rest of the driver does not care. |
| */ |
| static u64 __read_sbdchn(struct sbd_port *sport, int reg) |
| { |
| void __iomem *csr = sport->port.membase + reg; |
| |
| return __raw_readq(csr); |
| } |
| |
| static u64 __read_sbdshr(struct sbd_port *sport, int reg) |
| { |
| void __iomem *csr = sport->memctrl + reg; |
| |
| return __raw_readq(csr); |
| } |
| |
| static void __write_sbdchn(struct sbd_port *sport, int reg, u64 value) |
| { |
| void __iomem *csr = sport->port.membase + reg; |
| |
| __raw_writeq(value, csr); |
| } |
| |
| static void __write_sbdshr(struct sbd_port *sport, int reg, u64 value) |
| { |
| void __iomem *csr = sport->memctrl + reg; |
| |
| __raw_writeq(value, csr); |
| } |
| |
| /* |
| * In bug 1956, we get glitches that can mess up uart registers. This |
| * "read-mode-reg after any register access" is an accepted workaround. |
| */ |
| static void __war_sbd1956(struct sbd_port *sport) |
| { |
| __read_sbdchn(sport, R_DUART_MODE_REG_1); |
| __read_sbdchn(sport, R_DUART_MODE_REG_2); |
| } |
| |
| static unsigned char read_sbdchn(struct sbd_port *sport, int reg) |
| { |
| unsigned char retval; |
| |
| retval = __read_sbdchn(sport, reg); |
| if (SIBYTE_1956_WAR) |
| __war_sbd1956(sport); |
| return retval; |
| } |
| |
| static unsigned char read_sbdshr(struct sbd_port *sport, int reg) |
| { |
| unsigned char retval; |
| |
| retval = __read_sbdshr(sport, reg); |
| if (SIBYTE_1956_WAR) |
| __war_sbd1956(sport); |
| return retval; |
| } |
| |
| static void write_sbdchn(struct sbd_port *sport, int reg, unsigned int value) |
| { |
| __write_sbdchn(sport, reg, value); |
| if (SIBYTE_1956_WAR) |
| __war_sbd1956(sport); |
| } |
| |
| static void write_sbdshr(struct sbd_port *sport, int reg, unsigned int value) |
| { |
| __write_sbdshr(sport, reg, value); |
| if (SIBYTE_1956_WAR) |
| __war_sbd1956(sport); |
| } |
| |
| |
| static int sbd_receive_ready(struct sbd_port *sport) |
| { |
| return read_sbdchn(sport, R_DUART_STATUS) & M_DUART_RX_RDY; |
| } |
| |
| static int sbd_receive_drain(struct sbd_port *sport) |
| { |
| int loops = 10000; |
| |
| while (sbd_receive_ready(sport) && loops--) |
| read_sbdchn(sport, R_DUART_RX_HOLD); |
| return loops; |
| } |
| |
| static int __maybe_unused sbd_transmit_ready(struct sbd_port *sport) |
| { |
| return read_sbdchn(sport, R_DUART_STATUS) & M_DUART_TX_RDY; |
| } |
| |
| static int __maybe_unused sbd_transmit_drain(struct sbd_port *sport) |
| { |
| int loops = 10000; |
| |
| while (!sbd_transmit_ready(sport) && loops--) |
| udelay(2); |
| return loops; |
| } |
| |
| static int sbd_transmit_empty(struct sbd_port *sport) |
| { |
| return read_sbdchn(sport, R_DUART_STATUS) & M_DUART_TX_EMT; |
| } |
| |
| static int sbd_line_drain(struct sbd_port *sport) |
| { |
| int loops = 10000; |
| |
| while (!sbd_transmit_empty(sport) && loops--) |
| udelay(2); |
| return loops; |
| } |
| |
| |
| static unsigned int sbd_tx_empty(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| return sbd_transmit_empty(sport) ? TIOCSER_TEMT : 0; |
| } |
| |
| static unsigned int sbd_get_mctrl(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| unsigned int mctrl, status; |
| |
| status = read_sbdshr(sport, R_DUART_IN_PORT); |
| status >>= (uport->line) % 2; |
| mctrl = (!(status & M_DUART_IN_PIN0_VAL) ? TIOCM_CTS : 0) | |
| (!(status & M_DUART_IN_PIN4_VAL) ? TIOCM_CAR : 0) | |
| (!(status & M_DUART_RIN0_PIN) ? TIOCM_RNG : 0) | |
| (!(status & M_DUART_IN_PIN2_VAL) ? TIOCM_DSR : 0); |
| return mctrl; |
| } |
| |
| static void sbd_set_mctrl(struct uart_port *uport, unsigned int mctrl) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| unsigned int clr = 0, set = 0, mode2; |
| |
| if (mctrl & TIOCM_DTR) |
| set |= M_DUART_SET_OPR2; |
| else |
| clr |= M_DUART_CLR_OPR2; |
| if (mctrl & TIOCM_RTS) |
| set |= M_DUART_SET_OPR0; |
| else |
| clr |= M_DUART_CLR_OPR0; |
| clr <<= (uport->line) % 2; |
| set <<= (uport->line) % 2; |
| |
| mode2 = read_sbdchn(sport, R_DUART_MODE_REG_2); |
| mode2 &= ~M_DUART_CHAN_MODE; |
| if (mctrl & TIOCM_LOOP) |
| mode2 |= V_DUART_CHAN_MODE_LCL_LOOP; |
| else |
| mode2 |= V_DUART_CHAN_MODE_NORMAL; |
| |
| write_sbdshr(sport, R_DUART_CLEAR_OPR, clr); |
| write_sbdshr(sport, R_DUART_SET_OPR, set); |
| write_sbdchn(sport, R_DUART_MODE_REG_2, mode2); |
| } |
| |
| static void sbd_stop_tx(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS); |
| sport->tx_stopped = 1; |
| }; |
| |
| static void sbd_start_tx(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| unsigned int mask; |
| |
| /* Enable tx interrupts. */ |
| mask = read_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2)); |
| mask |= M_DUART_IMR_TX; |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), mask); |
| |
| /* Go!, go!, go!... */ |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_EN); |
| sport->tx_stopped = 0; |
| }; |
| |
| static void sbd_stop_rx(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), 0); |
| }; |
| |
| static void sbd_enable_ms(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| write_sbdchn(sport, R_DUART_AUXCTL_X, |
| M_DUART_CIN_CHNG_ENA | M_DUART_CTS_CHNG_ENA); |
| } |
| |
| static void sbd_break_ctl(struct uart_port *uport, int break_state) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| if (break_state == -1) |
| write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_START_BREAK); |
| else |
| write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_STOP_BREAK); |
| } |
| |
| |
| static void sbd_receive_chars(struct sbd_port *sport) |
| { |
| struct uart_port *uport = &sport->port; |
| struct uart_icount *icount; |
| unsigned int status, ch, flag; |
| int count; |
| |
| for (count = 16; count; count--) { |
| status = read_sbdchn(sport, R_DUART_STATUS); |
| if (!(status & M_DUART_RX_RDY)) |
| break; |
| |
| ch = read_sbdchn(sport, R_DUART_RX_HOLD); |
| |
| flag = TTY_NORMAL; |
| |
| icount = &uport->icount; |
| icount->rx++; |
| |
| if (unlikely(status & |
| (M_DUART_RCVD_BRK | M_DUART_FRM_ERR | |
| M_DUART_PARITY_ERR | M_DUART_OVRUN_ERR))) { |
| if (status & M_DUART_RCVD_BRK) { |
| icount->brk++; |
| if (uart_handle_break(uport)) |
| continue; |
| } else if (status & M_DUART_FRM_ERR) |
| icount->frame++; |
| else if (status & M_DUART_PARITY_ERR) |
| icount->parity++; |
| if (status & M_DUART_OVRUN_ERR) |
| icount->overrun++; |
| |
| status &= uport->read_status_mask; |
| if (status & M_DUART_RCVD_BRK) |
| flag = TTY_BREAK; |
| else if (status & M_DUART_FRM_ERR) |
| flag = TTY_FRAME; |
| else if (status & M_DUART_PARITY_ERR) |
| flag = TTY_PARITY; |
| } |
| |
| if (uart_handle_sysrq_char(uport, ch)) |
| continue; |
| |
| uart_insert_char(uport, status, M_DUART_OVRUN_ERR, ch, flag); |
| } |
| |
| tty_flip_buffer_push(uport->info->tty); |
| } |
| |
| static void sbd_transmit_chars(struct sbd_port *sport) |
| { |
| struct uart_port *uport = &sport->port; |
| struct circ_buf *xmit = &sport->port.info->xmit; |
| unsigned int mask; |
| int stop_tx; |
| |
| /* XON/XOFF chars. */ |
| if (sport->port.x_char) { |
| write_sbdchn(sport, R_DUART_TX_HOLD, sport->port.x_char); |
| sport->port.icount.tx++; |
| sport->port.x_char = 0; |
| return; |
| } |
| |
| /* If nothing to do or stopped or hardware stopped. */ |
| stop_tx = (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)); |
| |
| /* Send char. */ |
| if (!stop_tx) { |
| write_sbdchn(sport, R_DUART_TX_HOLD, xmit->buf[xmit->tail]); |
| xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); |
| sport->port.icount.tx++; |
| |
| if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) |
| uart_write_wakeup(&sport->port); |
| } |
| |
| /* Are we are done? */ |
| if (stop_tx || uart_circ_empty(xmit)) { |
| /* Disable tx interrupts. */ |
| mask = read_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2)); |
| mask &= ~M_DUART_IMR_TX; |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), mask); |
| } |
| } |
| |
| static void sbd_status_handle(struct sbd_port *sport) |
| { |
| struct uart_port *uport = &sport->port; |
| unsigned int delta; |
| |
| delta = read_sbdshr(sport, R_DUART_INCHREG((uport->line) % 2)); |
| delta >>= (uport->line) % 2; |
| |
| if (delta & (M_DUART_IN_PIN0_VAL << S_DUART_IN_PIN_CHNG)) |
| uart_handle_cts_change(uport, !(delta & M_DUART_IN_PIN0_VAL)); |
| |
| if (delta & (M_DUART_IN_PIN2_VAL << S_DUART_IN_PIN_CHNG)) |
| uport->icount.dsr++; |
| |
| if (delta & ((M_DUART_IN_PIN2_VAL | M_DUART_IN_PIN0_VAL) << |
| S_DUART_IN_PIN_CHNG)) |
| wake_up_interruptible(&uport->info->delta_msr_wait); |
| } |
| |
| static irqreturn_t sbd_interrupt(int irq, void *dev_id) |
| { |
| struct sbd_port *sport = dev_id; |
| struct uart_port *uport = &sport->port; |
| irqreturn_t status = IRQ_NONE; |
| unsigned int intstat; |
| int count; |
| |
| for (count = 16; count; count--) { |
| intstat = read_sbdshr(sport, |
| R_DUART_ISRREG((uport->line) % 2)); |
| intstat &= read_sbdshr(sport, |
| R_DUART_IMRREG((uport->line) % 2)); |
| intstat &= M_DUART_ISR_ALL; |
| if (!intstat) |
| break; |
| |
| if (intstat & M_DUART_ISR_RX) |
| sbd_receive_chars(sport); |
| if (intstat & M_DUART_ISR_IN) |
| sbd_status_handle(sport); |
| if (intstat & M_DUART_ISR_TX) |
| sbd_transmit_chars(sport); |
| |
| status = IRQ_HANDLED; |
| } |
| |
| return status; |
| } |
| |
| |
| static int sbd_startup(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| unsigned int mode1; |
| int ret; |
| |
| ret = request_irq(sport->port.irq, sbd_interrupt, |
| IRQF_SHARED, "sb1250-duart", sport); |
| if (ret) |
| return ret; |
| |
| /* Clear the receive FIFO. */ |
| sbd_receive_drain(sport); |
| |
| /* Clear the interrupt registers. */ |
| write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_RESET_BREAK_INT); |
| read_sbdshr(sport, R_DUART_INCHREG((uport->line) % 2)); |
| |
| /* Set rx/tx interrupt to FIFO available. */ |
| mode1 = read_sbdchn(sport, R_DUART_MODE_REG_1); |
| mode1 &= ~(M_DUART_RX_IRQ_SEL_RXFULL | M_DUART_TX_IRQ_SEL_TXEMPT); |
| write_sbdchn(sport, R_DUART_MODE_REG_1, mode1); |
| |
| /* Disable tx, enable rx. */ |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS | M_DUART_RX_EN); |
| sport->tx_stopped = 1; |
| |
| /* Enable interrupts. */ |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), |
| M_DUART_IMR_IN | M_DUART_IMR_RX); |
| |
| return 0; |
| } |
| |
| static void sbd_shutdown(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS | M_DUART_RX_DIS); |
| sport->tx_stopped = 1; |
| free_irq(sport->port.irq, sport); |
| } |
| |
| |
| static void sbd_init_port(struct sbd_port *sport) |
| { |
| struct uart_port *uport = &sport->port; |
| |
| if (sport->initialised) |
| return; |
| |
| /* There is no DUART reset feature, so just set some sane defaults. */ |
| write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_RESET_TX); |
| write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_RESET_RX); |
| write_sbdchn(sport, R_DUART_MODE_REG_1, V_DUART_BITS_PER_CHAR_8); |
| write_sbdchn(sport, R_DUART_MODE_REG_2, 0); |
| write_sbdchn(sport, R_DUART_FULL_CTL, |
| V_DUART_INT_TIME(0) | V_DUART_SIG_FULL(15)); |
| write_sbdchn(sport, R_DUART_OPCR_X, 0); |
| write_sbdchn(sport, R_DUART_AUXCTL_X, 0); |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), 0); |
| |
| sport->initialised = 1; |
| } |
| |
| static void sbd_set_termios(struct uart_port *uport, struct ktermios *termios, |
| struct ktermios *old_termios) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| unsigned int mode1 = 0, mode2 = 0, aux = 0; |
| unsigned int mode1mask = 0, mode2mask = 0, auxmask = 0; |
| unsigned int oldmode1, oldmode2, oldaux; |
| unsigned int baud, brg; |
| unsigned int command; |
| |
| mode1mask |= ~(M_DUART_PARITY_MODE | M_DUART_PARITY_TYPE_ODD | |
| M_DUART_BITS_PER_CHAR); |
| mode2mask |= ~M_DUART_STOP_BIT_LEN_2; |
| auxmask |= ~M_DUART_CTS_CHNG_ENA; |
| |
| /* Byte size. */ |
| switch (termios->c_cflag & CSIZE) { |
| case CS5: |
| case CS6: |
| /* Unsupported, leave unchanged. */ |
| mode1mask |= M_DUART_PARITY_MODE; |
| break; |
| case CS7: |
| mode1 |= V_DUART_BITS_PER_CHAR_7; |
| break; |
| case CS8: |
| default: |
| mode1 |= V_DUART_BITS_PER_CHAR_8; |
| break; |
| } |
| |
| /* Parity and stop bits. */ |
| if (termios->c_cflag & CSTOPB) |
| mode2 |= M_DUART_STOP_BIT_LEN_2; |
| else |
| mode2 |= M_DUART_STOP_BIT_LEN_1; |
| if (termios->c_cflag & PARENB) |
| mode1 |= V_DUART_PARITY_MODE_ADD; |
| else |
| mode1 |= V_DUART_PARITY_MODE_NONE; |
| if (termios->c_cflag & PARODD) |
| mode1 |= M_DUART_PARITY_TYPE_ODD; |
| else |
| mode1 |= M_DUART_PARITY_TYPE_EVEN; |
| |
| baud = uart_get_baud_rate(uport, termios, old_termios, 1200, 5000000); |
| brg = V_DUART_BAUD_RATE(baud); |
| /* The actual lower bound is 1221bps, so compensate. */ |
| if (brg > M_DUART_CLK_COUNTER) |
| brg = M_DUART_CLK_COUNTER; |
| |
| uart_update_timeout(uport, termios->c_cflag, baud); |
| |
| uport->read_status_mask = M_DUART_OVRUN_ERR; |
| if (termios->c_iflag & INPCK) |
| uport->read_status_mask |= M_DUART_FRM_ERR | |
| M_DUART_PARITY_ERR; |
| if (termios->c_iflag & (BRKINT | PARMRK)) |
| uport->read_status_mask |= M_DUART_RCVD_BRK; |
| |
| uport->ignore_status_mask = 0; |
| if (termios->c_iflag & IGNPAR) |
| uport->ignore_status_mask |= M_DUART_FRM_ERR | |
| M_DUART_PARITY_ERR; |
| if (termios->c_iflag & IGNBRK) { |
| uport->ignore_status_mask |= M_DUART_RCVD_BRK; |
| if (termios->c_iflag & IGNPAR) |
| uport->ignore_status_mask |= M_DUART_OVRUN_ERR; |
| } |
| |
| if (termios->c_cflag & CREAD) |
| command = M_DUART_RX_EN; |
| else |
| command = M_DUART_RX_DIS; |
| |
| if (termios->c_cflag & CRTSCTS) |
| aux |= M_DUART_CTS_CHNG_ENA; |
| else |
| aux &= ~M_DUART_CTS_CHNG_ENA; |
| |
| spin_lock(&uport->lock); |
| |
| if (sport->tx_stopped) |
| command |= M_DUART_TX_DIS; |
| else |
| command |= M_DUART_TX_EN; |
| |
| oldmode1 = read_sbdchn(sport, R_DUART_MODE_REG_1) & mode1mask; |
| oldmode2 = read_sbdchn(sport, R_DUART_MODE_REG_2) & mode2mask; |
| oldaux = read_sbdchn(sport, R_DUART_AUXCTL_X) & auxmask; |
| |
| if (!sport->tx_stopped) |
| sbd_line_drain(sport); |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS | M_DUART_RX_DIS); |
| |
| write_sbdchn(sport, R_DUART_MODE_REG_1, mode1 | oldmode1); |
| write_sbdchn(sport, R_DUART_MODE_REG_2, mode2 | oldmode2); |
| write_sbdchn(sport, R_DUART_CLK_SEL, brg); |
| write_sbdchn(sport, R_DUART_AUXCTL_X, aux | oldaux); |
| |
| write_sbdchn(sport, R_DUART_CMD, command); |
| |
| spin_unlock(&uport->lock); |
| } |
| |
| |
| static const char *sbd_type(struct uart_port *uport) |
| { |
| return "SB1250 DUART"; |
| } |
| |
| static void sbd_release_port(struct uart_port *uport) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| struct sbd_duart *duart = sport->duart; |
| int map_guard; |
| |
| iounmap(sport->memctrl); |
| sport->memctrl = NULL; |
| iounmap(uport->membase); |
| uport->membase = NULL; |
| |
| map_guard = atomic_add_return(-1, &duart->map_guard); |
| if (!map_guard) |
| release_mem_region(duart->mapctrl, DUART_CHANREG_SPACING); |
| release_mem_region(uport->mapbase, DUART_CHANREG_SPACING); |
| } |
| |
| static int sbd_map_port(struct uart_port *uport) |
| { |
| const char *err = KERN_ERR "sbd: Cannot map MMIO\n"; |
| struct sbd_port *sport = to_sport(uport); |
| struct sbd_duart *duart = sport->duart; |
| |
| if (!uport->membase) |
| uport->membase = ioremap_nocache(uport->mapbase, |
| DUART_CHANREG_SPACING); |
| if (!uport->membase) { |
| printk(err); |
| return -ENOMEM; |
| } |
| |
| if (!sport->memctrl) |
| sport->memctrl = ioremap_nocache(duart->mapctrl, |
| DUART_CHANREG_SPACING); |
| if (!sport->memctrl) { |
| printk(err); |
| iounmap(uport->membase); |
| uport->membase = NULL; |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int sbd_request_port(struct uart_port *uport) |
| { |
| const char *err = KERN_ERR "sbd: Unable to reserve MMIO resource\n"; |
| struct sbd_duart *duart = to_sport(uport)->duart; |
| int map_guard; |
| int ret = 0; |
| |
| if (!request_mem_region(uport->mapbase, DUART_CHANREG_SPACING, |
| "sb1250-duart")) { |
| printk(err); |
| return -EBUSY; |
| } |
| map_guard = atomic_add_return(1, &duart->map_guard); |
| if (map_guard == 1) { |
| if (!request_mem_region(duart->mapctrl, DUART_CHANREG_SPACING, |
| "sb1250-duart")) { |
| atomic_add(-1, &duart->map_guard); |
| printk(err); |
| ret = -EBUSY; |
| } |
| } |
| if (!ret) { |
| ret = sbd_map_port(uport); |
| if (ret) { |
| map_guard = atomic_add_return(-1, &duart->map_guard); |
| if (!map_guard) |
| release_mem_region(duart->mapctrl, |
| DUART_CHANREG_SPACING); |
| } |
| } |
| if (ret) { |
| release_mem_region(uport->mapbase, DUART_CHANREG_SPACING); |
| return ret; |
| } |
| return 0; |
| } |
| |
| static void sbd_config_port(struct uart_port *uport, int flags) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| if (flags & UART_CONFIG_TYPE) { |
| if (sbd_request_port(uport)) |
| return; |
| |
| uport->type = PORT_SB1250_DUART; |
| |
| sbd_init_port(sport); |
| } |
| } |
| |
| static int sbd_verify_port(struct uart_port *uport, struct serial_struct *ser) |
| { |
| int ret = 0; |
| |
| if (ser->type != PORT_UNKNOWN && ser->type != PORT_SB1250_DUART) |
| ret = -EINVAL; |
| if (ser->irq != uport->irq) |
| ret = -EINVAL; |
| if (ser->baud_base != uport->uartclk / 16) |
| ret = -EINVAL; |
| return ret; |
| } |
| |
| |
| static const struct uart_ops sbd_ops = { |
| .tx_empty = sbd_tx_empty, |
| .set_mctrl = sbd_set_mctrl, |
| .get_mctrl = sbd_get_mctrl, |
| .stop_tx = sbd_stop_tx, |
| .start_tx = sbd_start_tx, |
| .stop_rx = sbd_stop_rx, |
| .enable_ms = sbd_enable_ms, |
| .break_ctl = sbd_break_ctl, |
| .startup = sbd_startup, |
| .shutdown = sbd_shutdown, |
| .set_termios = sbd_set_termios, |
| .type = sbd_type, |
| .release_port = sbd_release_port, |
| .request_port = sbd_request_port, |
| .config_port = sbd_config_port, |
| .verify_port = sbd_verify_port, |
| }; |
| |
| /* Initialize SB1250 DUART port structures. */ |
| static void __init sbd_probe_duarts(void) |
| { |
| static int probed; |
| int chip, side; |
| int max_lines, line; |
| |
| if (probed) |
| return; |
| |
| /* 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; |
| } |
| |
| probed = 1; |
| |
| for (chip = 0, line = 0; chip < DUART_MAX_CHIP && line < max_lines; |
| chip++) { |
| sbd_duarts[chip].mapctrl = SBD_CTRLREGS(line); |
| |
| for (side = 0; side < DUART_MAX_SIDE && line < max_lines; |
| side++, line++) { |
| struct sbd_port *sport = &sbd_duarts[chip].sport[side]; |
| struct uart_port *uport = &sport->port; |
| |
| sport->duart = &sbd_duarts[chip]; |
| |
| uport->irq = SBD_INT(line); |
| uport->uartclk = 100000000 / 20 * 16; |
| uport->fifosize = 16; |
| uport->iotype = UPIO_MEM; |
| uport->flags = UPF_BOOT_AUTOCONF; |
| uport->ops = &sbd_ops; |
| uport->line = line; |
| uport->mapbase = SBD_CHANREGS(line); |
| } |
| } |
| } |
| |
| |
| #ifdef CONFIG_SERIAL_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. |
| */ |
| static void sbd_console_putchar(struct uart_port *uport, int ch) |
| { |
| struct sbd_port *sport = to_sport(uport); |
| |
| sbd_transmit_drain(sport); |
| write_sbdchn(sport, R_DUART_TX_HOLD, ch); |
| } |
| |
| static void sbd_console_write(struct console *co, const char *s, |
| unsigned int count) |
| { |
| int chip = co->index / DUART_MAX_SIDE; |
| int side = co->index % DUART_MAX_SIDE; |
| struct sbd_port *sport = &sbd_duarts[chip].sport[side]; |
| struct uart_port *uport = &sport->port; |
| unsigned long flags; |
| unsigned int mask; |
| |
| /* Disable transmit interrupts and enable the transmitter. */ |
| spin_lock_irqsave(&uport->lock, flags); |
| mask = read_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2)); |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), |
| mask & ~M_DUART_IMR_TX); |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_EN); |
| spin_unlock_irqrestore(&uport->lock, flags); |
| |
| uart_console_write(&sport->port, s, count, sbd_console_putchar); |
| |
| /* Restore transmit interrupts and the transmitter enable. */ |
| spin_lock_irqsave(&uport->lock, flags); |
| sbd_line_drain(sport); |
| if (sport->tx_stopped) |
| write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS); |
| write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), mask); |
| spin_unlock_irqrestore(&uport->lock, flags); |
| } |
| |
| static int __init sbd_console_setup(struct console *co, char *options) |
| { |
| int chip = co->index / DUART_MAX_SIDE; |
| int side = co->index % DUART_MAX_SIDE; |
| struct sbd_port *sport = &sbd_duarts[chip].sport[side]; |
| struct uart_port *uport = &sport->port; |
| int baud = 115200; |
| int bits = 8; |
| int parity = 'n'; |
| int flow = 'n'; |
| int ret; |
| |
| if (!sport->duart) |
| return -ENXIO; |
| |
| ret = sbd_map_port(uport); |
| if (ret) |
| return ret; |
| |
| sbd_init_port(sport); |
| |
| if (options) |
| uart_parse_options(options, &baud, &parity, &bits, &flow); |
| return uart_set_options(uport, co, baud, parity, bits, flow); |
| } |
| |
| static struct uart_driver sbd_reg; |
| static struct console sbd_console = { |
| .name = "duart", |
| .write = sbd_console_write, |
| .device = uart_console_device, |
| .setup = sbd_console_setup, |
| .flags = CON_PRINTBUFFER, |
| .index = -1, |
| .data = &sbd_reg |
| }; |
| |
| static int __init sbd_serial_console_init(void) |
| { |
| sbd_probe_duarts(); |
| register_console(&sbd_console); |
| |
| return 0; |
| } |
| |
| console_initcall(sbd_serial_console_init); |
| |
| #define SERIAL_SB1250_DUART_CONSOLE &sbd_console |
| #else |
| #define SERIAL_SB1250_DUART_CONSOLE NULL |
| #endif /* CONFIG_SERIAL_SB1250_DUART_CONSOLE */ |
| |
| |
| static struct uart_driver sbd_reg = { |
| .owner = THIS_MODULE, |
| .driver_name = "sb1250_duart", |
| .dev_name = "duart", |
| .major = TTY_MAJOR, |
| .minor = SB1250_DUART_MINOR_BASE, |
| .nr = DUART_MAX_CHIP * DUART_MAX_SIDE, |
| .cons = SERIAL_SB1250_DUART_CONSOLE, |
| }; |
| |
| /* Set up the driver and register it. */ |
| static int __init sbd_init(void) |
| { |
| int i, ret; |
| |
| sbd_probe_duarts(); |
| |
| ret = uart_register_driver(&sbd_reg); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < DUART_MAX_CHIP * DUART_MAX_SIDE; i++) { |
| struct sbd_duart *duart = &sbd_duarts[i / DUART_MAX_SIDE]; |
| struct sbd_port *sport = &duart->sport[i % DUART_MAX_SIDE]; |
| struct uart_port *uport = &sport->port; |
| |
| if (sport->duart) |
| uart_add_one_port(&sbd_reg, uport); |
| } |
| |
| return 0; |
| } |
| |
| /* Unload the driver. Unregister stuff, get ready to go away. */ |
| static void __exit sbd_exit(void) |
| { |
| int i; |
| |
| for (i = DUART_MAX_CHIP * DUART_MAX_SIDE - 1; i >= 0; i--) { |
| struct sbd_duart *duart = &sbd_duarts[i / DUART_MAX_SIDE]; |
| struct sbd_port *sport = &duart->sport[i % DUART_MAX_SIDE]; |
| struct uart_port *uport = &sport->port; |
| |
| if (sport->duart) |
| uart_remove_one_port(&sbd_reg, uport); |
| } |
| |
| uart_unregister_driver(&sbd_reg); |
| } |
| |
| module_init(sbd_init); |
| module_exit(sbd_exit); |