?? s3c2410.c
字號:
/* * linux/drivers/serial/s3c2410.c * * Driver for onboard UARTs on the Samsung S3C24XX * * Based on drivers/char/serial.c and drivers/char/21285.c * * Ben Dooks, (c) 2003-2005 Simtec Electronics * http://www.simtec.co.uk/products/SWLINUX/ * * Changelog: * * 22-Jul-2004 BJD Finished off device rewrite * * 21-Jul-2004 BJD Thanks to <herbet@13thfloor.at> for pointing out * problems with baud rate and loss of IR settings. Update * to add configuration via platform_device structure * * 28-Sep-2004 BJD Re-write for the following items * - S3C2410 and S3C2440 serial support * - Power Management support * - Fix console via IrDA devices * - SysReq (Herbert P鰐zl) * - Break character handling (Herbert P鰐zl) * - spin-lock initialisation (Dimitry Andric) * - added clock control * - updated init code to use platform_device info * * 06-Mar-2005 BJD Add s3c2440 fclk clock source * * 09-Mar-2005 BJD Add s3c2400 support * * 10-Mar-2005 LCVR Changed S3C2410_VA_UART to S3C24XX_VA_UART*//* Note on 2440 fclk clock source handling * * Whilst it is possible to use the fclk as clock source, the method * of properly switching too/from this is currently un-implemented, so * whichever way is configured at startup is the one that will be used.*//* Hote on 2410 error handling * * The s3c2410 manual has a love/hate affair with the contents of the * UERSTAT register in the UART blocks, and keeps marking some of the * error bits as reserved. Having checked with the s3c2410x01, * it copes with BREAKs properly, so I am happy to ignore the RESERVED * feature from the latter versions of the manual. * * If it becomes aparrent that latter versions of the 2410 remove these * bits, then action will have to be taken to differentiate the versions * and change the policy on BREAK * * BJD, 04-Nov-2004*/#include <linux/config.h>#if defined(CONFIG_SERIAL_S3C2410_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)#define SUPPORT_SYSRQ#endif#include <linux/module.h>#include <linux/ioport.h>#include <linux/platform_device.h>#include <linux/init.h>#include <linux/sysrq.h>#include <linux/console.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/serial_core.h>#include <linux/serial.h>#include <linux/delay.h>#include <linux/clk.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/hardware.h>#include <asm/arch/regs-serial.h>#include <asm/arch/regs-gpio.h>/* structures */struct s3c24xx_uart_info { char *name; unsigned int type; unsigned int fifosize; unsigned long rx_fifomask; unsigned long rx_fifoshift; unsigned long rx_fifofull; unsigned long tx_fifomask; unsigned long tx_fifoshift; unsigned long tx_fifofull; /* clock source control */ int (*get_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk); int (*set_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk); /* uart controls */ int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *);};struct s3c24xx_uart_port { unsigned char rx_claimed; unsigned char tx_claimed; struct s3c24xx_uart_info *info; struct s3c24xx_uart_clksrc *clksrc; struct clk *clk; struct clk *baudclk; struct uart_port port;};/* configuration defines */#if 0#if 1/* send debug to the low-level output routines */extern void printascii(const char *);static voids3c24xx_serial_dbg(const char *fmt, ...){ va_list va; char buff[256]; va_start(va, fmt); vsprintf(buff, fmt, va); va_end(va); printascii(buff);}#define dbg(x...) s3c24xx_serial_dbg(x)#else#define dbg(x...) printk(KERN_DEBUG "s3c24xx: ");#endif#else /* no debug */#define dbg(x...) do {} while(0)#endif/* UART name and device definitions */#define S3C24XX_SERIAL_NAME "ttySAC"#define S3C24XX_SERIAL_DEVFS "tts/"#define S3C24XX_SERIAL_MAJOR 204#define S3C24XX_SERIAL_MINOR 64/* conversion functions */#define s3c24xx_dev_to_port(__dev) (struct uart_port *)dev_get_drvdata(__dev)#define s3c24xx_dev_to_cfg(__dev) (struct s3c2410_uartcfg *)((__dev)->platform_data)/* we can support 3 uarts, but not always use them */#ifdef CONFIG_CPU_S3C2400#define NR_PORTS (2)#else#define NR_PORTS (3)#endif/* port irq numbers */#define TX_IRQ(port) ((port)->irq + 1)#define RX_IRQ(port) ((port)->irq)/* register access controls */#define portaddr(port, reg) ((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)/* macros to change one thing to another */#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)static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port){ return container_of(port, struct s3c24xx_uart_port, port);}/* translate a port to the device name */static inline const char *s3c24xx_serial_portname(struct uart_port *port){ return to_platform_device(port->dev)->name;}static int s3c24xx_serial_txempty_nofifo(struct uart_port *port){ return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);}static void s3c24xx_serial_rx_enable(struct uart_port *port){ unsigned long flags; unsigned int ucon, ufcon; int count = 10000; spin_lock_irqsave(&port->lock, flags); while (--count && !s3c24xx_serial_txempty_nofifo(port)) udelay(100); ufcon = rd_regl(port, S3C2410_UFCON); ufcon |= S3C2410_UFCON_RESETRX; wr_regl(port, S3C2410_UFCON, ufcon); ucon = rd_regl(port, S3C2410_UCON); ucon |= S3C2410_UCON_RXIRQMODE; wr_regl(port, S3C2410_UCON, ucon); rx_enabled(port) = 1; spin_unlock_irqrestore(&port->lock, flags);}static void s3c24xx_serial_rx_disable(struct uart_port *port){ unsigned long flags; unsigned int ucon; spin_lock_irqsave(&port->lock, flags); ucon = rd_regl(port, S3C2410_UCON); ucon &= ~S3C2410_UCON_RXIRQMODE; wr_regl(port, S3C2410_UCON, ucon); rx_enabled(port) = 0; spin_unlock_irqrestore(&port->lock, flags);}static void s3c24xx_serial_stop_tx(struct uart_port *port){ if (tx_enabled(port)) { disable_irq(TX_IRQ(port)); tx_enabled(port) = 0; if (port->flags & UPF_CONS_FLOW) s3c24xx_serial_rx_enable(port); }}static void s3c24xx_serial_start_tx(struct uart_port *port){ if (!tx_enabled(port)) { if (port->flags & UPF_CONS_FLOW) s3c24xx_serial_rx_disable(port); enable_irq(TX_IRQ(port)); tx_enabled(port) = 1; }}static void s3c24xx_serial_stop_rx(struct uart_port *port){ if (rx_enabled(port)) { dbg("s3c24xx_serial_stop_rx: port=%p\n", port); disable_irq(RX_IRQ(port)); rx_enabled(port) = 0; }}static void s3c24xx_serial_enable_ms(struct uart_port *port){}static inline struct s3c24xx_uart_info *s3c24xx_port_to_info(struct uart_port *port){ return to_ourport(port)->info;}static inline struct s3c2410_uartcfg *s3c24xx_port_to_cfg(struct uart_port *port){ if (port->dev == NULL) return NULL; return (struct s3c2410_uartcfg *)port->dev->platform_data;}static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport, unsigned long ufstat){ struct s3c24xx_uart_info *info = ourport->info; if (ufstat & info->rx_fifofull) return info->fifosize; return (ufstat & info->rx_fifomask) >> info->rx_fifoshift;}/* ? - where has parity gone?? */#define S3C2410_UERSTAT_PARITY (0x1000)static irqreturn_ts3c24xx_serial_rx_chars(int irq, void *dev_id, struct pt_regs *regs){ struct s3c24xx_uart_port *ourport = dev_id; struct uart_port *port = &ourport->port; struct tty_struct *tty = port->info->tty; unsigned int ufcon, ch, flag, ufstat, uerstat; int max_count = 64; while (max_count-- > 0) { ufcon = rd_regl(port, S3C2410_UFCON); ufstat = rd_regl(port, S3C2410_UFSTAT); if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0) break; uerstat = rd_regl(port, S3C2410_UERSTAT); ch = rd_regb(port, S3C2410_URXH); if (port->flags & UPF_CONS_FLOW) { int txe = s3c24xx_serial_txempty_nofifo(port); if (rx_enabled(port)) { if (!txe) { rx_enabled(port) = 0; continue; } } else { if (txe) { ufcon |= S3C2410_UFCON_RESETRX; wr_regl(port, S3C2410_UFCON, ufcon); rx_enabled(port) = 1; goto out; } continue; } } /* insert the character into the buffer */ flag = TTY_NORMAL; port->icount.rx++; if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) { dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n", ch, uerstat); /* check for break */ if (uerstat & S3C2410_UERSTAT_BREAK) { dbg("break!\n"); port->icount.brk++; if (uart_handle_break(port)) goto ignore_char; } if (uerstat & S3C2410_UERSTAT_FRAME) port->icount.frame++; if (uerstat & S3C2410_UERSTAT_OVERRUN) port->icount.overrun++; uerstat &= port->read_status_mask; if (uerstat & S3C2410_UERSTAT_BREAK) flag = TTY_BREAK; else if (uerstat & S3C2410_UERSTAT_PARITY) flag = TTY_PARITY; else if (uerstat & ( S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN)) flag = TTY_FRAME; } if (uart_handle_sysrq_char(port, ch, regs)) goto ignore_char; uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag); ignore_char: continue; } tty_flip_buffer_push(tty); out: return IRQ_HANDLED;}static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id, struct pt_regs *regs){ struct s3c24xx_uart_port *ourport = id; struct uart_port *port = &ourport->port; 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)) { s3c24xx_serial_stop_tx(port); goto out; } /* try and drain the buffer... */ while (!uart_circ_empty(xmit) && count-- > 0) { if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) 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)) s3c24xx_serial_stop_tx(port); out: return IRQ_HANDLED;}static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port){ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT); unsigned long ufcon = rd_regl(port, S3C2410_UFCON); if (ufcon & S3C2410_UFCON_FIFOMODE) { if ((ufstat & info->tx_fifomask) != 0 || (ufstat & info->tx_fifofull)) return 0; return 1; } return s3c24xx_serial_txempty_nofifo(port);}/* no modem control lines */static unsigned int s3c24xx_serial_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 void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl){ /* todo - possibly remove AFC and do manual CTS */}static void s3c24xx_serial_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 void s3c24xx_serial_shutdown(struct uart_port *port){ struct s3c24xx_uart_port *ourport = to_ourport(port); if (ourport->tx_claimed) { free_irq(TX_IRQ(port), ourport); tx_enabled(port) = 0; ourport->tx_claimed = 0; } if (ourport->rx_claimed) { free_irq(RX_IRQ(port), ourport); ourport->rx_claimed = 0; rx_enabled(port) = 0; }}static int s3c24xx_serial_startup(struct uart_port *port){ struct s3c24xx_uart_port *ourport = to_ourport(port); int ret; dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n", port->mapbase, port->membase); rx_enabled(port) = 1; ret = request_irq(RX_IRQ(port), s3c24xx_serial_rx_chars, 0, s3c24xx_serial_portname(port), ourport); if (ret != 0) { printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port)); return ret; } ourport->rx_claimed = 1; dbg("requesting tx irq...\n"); tx_enabled(port) = 1; ret = request_irq(TX_IRQ(port), s3c24xx_serial_tx_chars, 0, s3c24xx_serial_portname(port), ourport); if (ret) { printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port)); goto err; } ourport->tx_claimed = 1; dbg("s3c24xx_serial_startup ok\n"); /* the port reset code should have done the correct * register setup for the port controls */ return ret; err: s3c24xx_serial_shutdown(port); return ret;}/* power power management control */static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level, unsigned int old){ struct s3c24xx_uart_port *ourport = to_ourport(port); switch (level) { case 3: if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL) clk_disable(ourport->baudclk); clk_disable(ourport->clk); break; case 0: clk_enable(ourport->clk); if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL) clk_enable(ourport->baudclk); break; default: printk(KERN_ERR "s3c24xx_serial: unknown pm %d\n", level); }}/* baud rate calculation * * The UARTs on the S3C2410/S3C2440 can take their clocks from a number * of different sources, including the peripheral clock ("pclk") and an * external clock ("uclk"). The S3C2440 also adds the core clock ("fclk") * with a programmable extra divisor. * * The following code goes through the clock sources, and calculates the * baud clocks (and the resultant actual baud rates) and then tries to * pick the closest one and select that. **/#define MAX_CLKS (8)
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -