📄 sb1250-duart.c
字号:
/* * 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#endifMODULE_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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -