📄 s3c2410.c
字号:
/* * linux/drivers/char/s3c2410.c * * Driver for onboard UARTs on the Samsung S3C2410 * * Based on drivers/char/serial.c and drivers/char/21285.c * * Ben Dooks, (c) 2003 Simtec Electronics * * Changelog: * */#include <linux/config.h>#include <linux/module.h>#include <linux/tty.h>#include <linux/ioport.h>#include <linux/device.h>#include <linux/init.h>#include <linux/console.h>#include <linux/serial_core.h>#include <linux/serial.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/hardware.h>#include <asm/arch/regs-serial.h>#if 0#include <asm/debug-ll.h>#define dbg(x...) llprintk(x)#else#define dbg(x...)#endif#define SERIAL_S3C2410_NAME "ttySAC"#define SERIAL_S3C2410_MAJOR 204#define SERIAL_S3C2410_MINOR 64/* we can support 3 uarts, but not always use them */#define NR_PORTS (3)static const char serial_s3c2410_name[] = "Samsung S3C2410 UART";/* port irq numbers */#define TX_IRQ(port) ((port)->irq + 1)#define RX_IRQ(port) ((port)->irq)#define tx_enabled(port) ((port)->unused[0])#define rx_enabled(port) ((port)->unused[1])/* flag to ignore all characters comming in */#define RXSTAT_DUMMY_READ (0x10000000)/* access functions */#define portaddr(port, reg) ((void *)((port)->membase + (reg)))#define rd_regb(port, reg) (__raw_readb(portaddr(port, reg)))#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg)))#define wr_regb(port, reg, val) \ do { __raw_writeb(val, portaddr(port, reg)); } while(0)#define wr_regl(port, reg, val) \ do { __raw_writel(val, portaddr(port, reg)); } while(0)/* code */static voidserial_s3c2410_stop_tx(struct uart_port *port, unsigned int tty_stop){ if (tx_enabled(port)) { disable_irq(TX_IRQ(port)); tx_enabled(port) = 0; }}static voidserial_s3c2410_start_tx(struct uart_port *port, unsigned int tty_start){ if (!tx_enabled(port)) { enable_irq(TX_IRQ(port)); tx_enabled(port) = 1; }}static void serial_s3c2410_stop_rx(struct uart_port *port){ if (rx_enabled(port)) { dbg("serial_s3c2410_stop_rx: port=%p\n", port); disable_irq(RX_IRQ(port)); rx_enabled(port) = 0; }}static void serial_s3c2410_enable_ms(struct uart_port *port){}/* ? - where has parity gone?? */#define S3C2410_UERSTAT_PARITY (0x1000)static irqreturn_tserial_s3c2410_rx_chars(int irq, void *dev_id, struct pt_regs *regs){ struct uart_port *port = dev_id; struct tty_struct *tty = port->info->tty; unsigned int ufcon, ch, rxs, ufstat; int max_count = 256; while (max_count-- > 0) { ufcon = rd_regl(port, S3C2410_UFCON); ufstat = rd_regl(port, S3C2410_UFSTAT); if (S3C2410_UFCON_RXC(ufstat) == 0) break; if (tty->flip.count >= TTY_FLIPBUF_SIZE) { tty->flip.work.func((void *)tty); if (tty->flip.count >= TTY_FLIPBUF_SIZE) { printk(KERN_WARNING "TTY_DONT_FLIP set\n"); goto out; } } ch = rd_regb(port, S3C2410_URXH); *tty->flip.char_buf_ptr = ch; *tty->flip.flag_buf_ptr = TTY_NORMAL; port->icount.rx++; rxs = rd_regb(port, S3C2410_UERSTAT) | RXSTAT_DUMMY_READ; if (rxs & S3C2410_UERSTAT_ANY) { if (rxs & S3C2410_UERSTAT_FRAME) port->icount.frame++; if (rxs & S3C2410_UERSTAT_OVERRUN) port->icount.overrun++; rxs &= port->read_status_mask; if (rxs & S3C2410_UERSTAT_PARITY) *tty->flip.flag_buf_ptr = TTY_PARITY; else if (rxs & ( S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN)) *tty->flip.flag_buf_ptr = TTY_FRAME; } if ((rxs & port->ignore_status_mask) == 0) { tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } if ((rxs & S3C2410_UERSTAT_OVERRUN) && tty->flip.count < TTY_FLIPBUF_SIZE) { /* * Overrun is special, since it's reported * immediately, and doesn't affect the current * character. */ *tty->flip.char_buf_ptr++ = 0; *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; tty->flip.count++; } } tty_flip_buffer_push(tty); out: return IRQ_HANDLED;}static irqreturn_tserial_s3c2410_tx_chars(int irq, void *dev_id, struct pt_regs *regs){ struct uart_port *port = (struct uart_port *)dev_id; struct circ_buf *xmit = &port->info->xmit; int count = 256; if (port->x_char) { wr_regb(port, S3C2410_UTXH, port->x_char); port->icount.tx++; port->x_char = 0; goto out; } /* if there isnt anything more to transmit, or the uart is now * stopped, disable the uart and exit */ if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { serial_s3c2410_stop_tx(port, 0); goto out; } /* try and drain the buffer... */ while (!uart_circ_empty(xmit) && count-- > 0) { if (rd_regl(port, S3C2410_UFSTAT) & S3C2410_UFSTAT_TXFULL) break; wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; } if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); if (uart_circ_empty(xmit)) serial_s3c2410_stop_tx(port, 0); out: return IRQ_HANDLED;}static unsigned intserial_s3c2410_tx_empty(struct uart_port *port){ unsigned int ufcon = rd_regl(port, S3C2410_UFCON); return (S3C2410_UFCON_TXC(ufcon) != 0) ? 0 : TIOCSER_TEMT;}/* no modem control lines */static unsigned intserial_s3c2410_get_mctrl(struct uart_port *port){ unsigned int umstat = rd_regb(port,S3C2410_UMSTAT); if (umstat & S3C2410_UMSTAT_CTS) return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; else return TIOCM_CAR | TIOCM_DSR;}static voidserial_s3c2410_set_mctrl(struct uart_port *port, unsigned int mctrl){ /* todo - possibly remove AFC and do manual CTS */}static void serial_s3c2410_break_ctl(struct uart_port *port, int break_state){ unsigned long flags; unsigned int ucon; spin_lock_irqsave(&port->lock, flags); ucon = rd_regl(port, S3C2410_UCON); if (break_state) ucon |= S3C2410_UCON_SBREAK; else ucon &= ~S3C2410_UCON_SBREAK; wr_regl(port, S3C2410_UCON, ucon); spin_unlock_irqrestore(&port->lock, flags);}static int serial_s3c2410_startup(struct uart_port *port){ int ret; tx_enabled(port) = 1; rx_enabled(port) = 1; dbg("serial_s3c2410_startup: port=%p (%p)\n", port, port->mapbase); ret = request_irq(RX_IRQ(port), serial_s3c2410_rx_chars, 0, serial_s3c2410_name, port); if (ret != 0) return ret; ret = request_irq(TX_IRQ(port), serial_s3c2410_tx_chars, 0, serial_s3c2410_name, port); if (ret) { free_irq(RX_IRQ(port), port); return ret; } /* the port reset code should have done the correct * register setup for the port controls */ return ret;}static void serial_s3c2410_shutdown(struct uart_port *port){ free_irq(TX_IRQ(port), port); free_irq(RX_IRQ(port), port);}static voidserial_s3c2410_set_termios(struct uart_port *port, struct termios *termios, struct termios *old){ unsigned long flags; unsigned int baud, quot; unsigned int ulcon; /* * We don't support modem control lines. */ termios->c_cflag &= ~(HUPCL | CRTSCTS | CMSPAR); termios->c_cflag |= CLOCAL; /* * We don't support BREAK character recognition. */ termios->c_iflag &= ~(IGNBRK | BRKINT); /* * Ask the core to calculate the divisor for us. */ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); quot = uart_get_divisor(port, baud); switch (termios->c_cflag & CSIZE) { case CS5: dbg("config: 5bits/char\n"); ulcon = S3C2410_LCON_CS5; break; case CS6: dbg("config: 6bits/char\n"); ulcon = S3C2410_LCON_CS6; break; case CS7: dbg("config: 7bits/char\n"); ulcon = S3C2410_LCON_CS7; break; case CS8: default: dbg("config: 8bits/char\n"); ulcon = S3C2410_LCON_CS8; break; } if (termios->c_cflag & CSTOPB) ulcon |= S3C2410_LCON_STOPB; if (termios->c_cflag & PARENB) { if (!(termios->c_cflag & PARODD)) ulcon |= S3C2410_LCON_PODD; else ulcon |= S3C2410_LCON_PEVEN; } else { ulcon |= S3C2410_LCON_PNONE; } /* if (port->fifosize) enable_fifo() */ spin_lock_irqsave(&port->lock, flags); dbg("setting ulcon to %08x\n", ulcon); //dbg("<flushing output from serial>\n"); /* set the ulcon register */ wr_regl(port, S3C2410_ULCON, ulcon); dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n", rd_regl(port, S3C2410_ULCON), rd_regl(port, S3C2410_UCON), rd_regl(port, S3C2410_UFCON)); /* * Update the per-port timeout. */ uart_update_timeout(port, termios->c_cflag, baud); /* * Which character status flags are we interested in? */ port->read_status_mask = S3C2410_UERSTAT_OVERRUN; if (termios->c_iflag & INPCK) port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY; /* * Which character status flags should we ignore? */ port->ignore_status_mask = 0; if (termios->c_iflag & IGNPAR) port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN; if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR) port->ignore_status_mask |= S3C2410_UERSTAT_FRAME; /* * Ignore all characters if CREAD is not set. */ if ((termios->c_cflag & CREAD) == 0) port->ignore_status_mask |= RXSTAT_DUMMY_READ; spin_unlock_irqrestore(&port->lock, flags);}static const char *serial_s3c2410_type(struct uart_port *port){ return port->type == PORT_S3C2410 ? "S3C2410" : NULL;}#define MAP_SIZE (0x100)static voidserial_s3c2410_release_port(struct uart_port *port){ release_mem_region(port->mapbase, MAP_SIZE);}static intserial_s3c2410_request_port(struct uart_port *port){ return request_mem_region(port->mapbase, MAP_SIZE, serial_s3c2410_name) != NULL ? 0 : -EBUSY;}static voidserial_s3c2410_config_port(struct uart_port *port, int flags){ if (flags & UART_CONFIG_TYPE &&
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -