| /* | |
| * Driver for OMAP-UART controller. | |
| * Based on drivers/serial/8250.c | |
| * | |
| * Copyright (C) 2010 Texas Instruments. | |
| * | |
| * Authors: | |
| * Govindraj R <govindraj.raja@ti.com> | |
| * Thara Gopinath <thara@ti.com> | |
| * | |
| * 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. | |
| * | |
| * Note: This driver is made separate from 8250 driver as we cannot | |
| * over load 8250 driver with omap platform specific configuration for | |
| * features like DMA, it makes easier to implement features like DMA and | |
| * hardware flow control and software flow control configuration with | |
| * this driver as required for the omap-platform. | |
| */ | |
| #if defined(CONFIG_SERIAL_OMAP_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) | |
| #define SUPPORT_SYSRQ | |
| #endif | |
| #include <linux/module.h> | |
| #include <linux/init.h> | |
| #include <linux/console.h> | |
| #include <linux/serial_reg.h> | |
| #include <linux/delay.h> | |
| #include <linux/slab.h> | |
| #include <linux/tty.h> | |
| #include <linux/tty_flip.h> | |
| #include <linux/io.h> | |
| #include <linux/dma-mapping.h> | |
| #include <linux/clk.h> | |
| #include <linux/serial_core.h> | |
| #include <linux/irq.h> | |
| #include <plat/dma.h> | |
| #include <plat/dmtimer.h> | |
| #include <plat/omap-serial.h> | |
| static struct uart_omap_port *ui[OMAP_MAX_HSUART_PORTS]; | |
| /* Forward declaration of functions */ | |
| static void uart_tx_dma_callback(int lch, u16 ch_status, void *data); | |
| static void serial_omap_rx_timeout(unsigned long uart_no); | |
| static int serial_omap_start_rxdma(struct uart_omap_port *up); | |
| static inline unsigned int serial_in(struct uart_omap_port *up, int offset) | |
| { | |
| offset <<= up->port.regshift; | |
| return readw(up->port.membase + offset); | |
| } | |
| static inline void serial_out(struct uart_omap_port *up, int offset, int value) | |
| { | |
| offset <<= up->port.regshift; | |
| writew(value, up->port.membase + offset); | |
| } | |
| static inline void serial_omap_clear_fifos(struct uart_omap_port *up) | |
| { | |
| serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO); | |
| serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO | | |
| UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); | |
| serial_out(up, UART_FCR, 0); | |
| } | |
| /* | |
| * serial_omap_get_divisor - calculate divisor value | |
| * @port: uart port info | |
| * @baud: baudrate for which divisor needs to be calculated. | |
| * | |
| * We have written our own function to get the divisor so as to support | |
| * 13x mode. 3Mbps Baudrate as an different divisor. | |
| * Reference OMAP TRM Chapter 17: | |
| * Table 17-1. UART Mode Baud Rates, Divisor Values, and Error Rates | |
| * referring to oversampling - divisor value | |
| * baudrate 460,800 to 3,686,400 all have divisor 13 | |
| * except 3,000,000 which has divisor value 16 | |
| */ | |
| static unsigned int | |
| serial_omap_get_divisor(struct uart_port *port, unsigned int baud) | |
| { | |
| unsigned int divisor; | |
| if (baud > OMAP_MODE13X_SPEED && baud != 3000000) | |
| divisor = 13; | |
| else | |
| divisor = 16; | |
| return port->uartclk/(baud * divisor); | |
| } | |
| static void serial_omap_stop_rxdma(struct uart_omap_port *up) | |
| { | |
| if (up->uart_dma.rx_dma_used) { | |
| del_timer(&up->uart_dma.rx_timer); | |
| omap_stop_dma(up->uart_dma.rx_dma_channel); | |
| omap_free_dma(up->uart_dma.rx_dma_channel); | |
| up->uart_dma.rx_dma_channel = OMAP_UART_DMA_CH_FREE; | |
| up->uart_dma.rx_dma_used = false; | |
| } | |
| } | |
| static void serial_omap_enable_ms(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| dev_dbg(up->port.dev, "serial_omap_enable_ms+%d\n", up->pdev->id); | |
| up->ier |= UART_IER_MSI; | |
| serial_out(up, UART_IER, up->ier); | |
| } | |
| static void serial_omap_stop_tx(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| if (up->use_dma && | |
| up->uart_dma.tx_dma_channel != OMAP_UART_DMA_CH_FREE) { | |
| /* | |
| * Check if dma is still active. If yes do nothing, | |
| * return. Else stop dma | |
| */ | |
| if (omap_get_dma_active_status(up->uart_dma.tx_dma_channel)) | |
| return; | |
| omap_stop_dma(up->uart_dma.tx_dma_channel); | |
| omap_free_dma(up->uart_dma.tx_dma_channel); | |
| up->uart_dma.tx_dma_channel = OMAP_UART_DMA_CH_FREE; | |
| } | |
| if (up->ier & UART_IER_THRI) { | |
| up->ier &= ~UART_IER_THRI; | |
| serial_out(up, UART_IER, up->ier); | |
| } | |
| } | |
| static void serial_omap_stop_rx(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| if (up->use_dma) | |
| serial_omap_stop_rxdma(up); | |
| up->ier &= ~UART_IER_RLSI; | |
| up->port.read_status_mask &= ~UART_LSR_DR; | |
| serial_out(up, UART_IER, up->ier); | |
| } | |
| static inline void receive_chars(struct uart_omap_port *up, int *status) | |
| { | |
| struct tty_struct *tty = up->port.state->port.tty; | |
| unsigned int flag; | |
| unsigned char ch, lsr = *status; | |
| int max_count = 256; | |
| do { | |
| if (likely(lsr & UART_LSR_DR)) | |
| ch = serial_in(up, UART_RX); | |
| flag = TTY_NORMAL; | |
| up->port.icount.rx++; | |
| if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) { | |
| /* | |
| * For statistics only | |
| */ | |
| if (lsr & UART_LSR_BI) { | |
| lsr &= ~(UART_LSR_FE | UART_LSR_PE); | |
| up->port.icount.brk++; | |
| /* | |
| * We do the SysRQ and SAK checking | |
| * here because otherwise the break | |
| * may get masked by ignore_status_mask | |
| * or read_status_mask. | |
| */ | |
| if (uart_handle_break(&up->port)) | |
| goto ignore_char; | |
| } else if (lsr & UART_LSR_PE) { | |
| up->port.icount.parity++; | |
| } else if (lsr & UART_LSR_FE) { | |
| up->port.icount.frame++; | |
| } | |
| if (lsr & UART_LSR_OE) | |
| up->port.icount.overrun++; | |
| /* | |
| * Mask off conditions which should be ignored. | |
| */ | |
| lsr &= up->port.read_status_mask; | |
| #ifdef CONFIG_SERIAL_OMAP_CONSOLE | |
| if (up->port.line == up->port.cons->index) { | |
| /* Recover the break flag from console xmit */ | |
| lsr |= up->lsr_break_flag; | |
| } | |
| #endif | |
| if (lsr & UART_LSR_BI) | |
| flag = TTY_BREAK; | |
| else if (lsr & UART_LSR_PE) | |
| flag = TTY_PARITY; | |
| else if (lsr & UART_LSR_FE) | |
| flag = TTY_FRAME; | |
| } | |
| if (uart_handle_sysrq_char(&up->port, ch)) | |
| goto ignore_char; | |
| uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag); | |
| ignore_char: | |
| lsr = serial_in(up, UART_LSR); | |
| } while ((lsr & (UART_LSR_DR | UART_LSR_BI)) && (max_count-- > 0)); | |
| spin_unlock(&up->port.lock); | |
| tty_flip_buffer_push(tty); | |
| spin_lock(&up->port.lock); | |
| } | |
| static void transmit_chars(struct uart_omap_port *up) | |
| { | |
| struct circ_buf *xmit = &up->port.state->xmit; | |
| int count; | |
| if (up->port.x_char) { | |
| serial_out(up, UART_TX, up->port.x_char); | |
| up->port.icount.tx++; | |
| up->port.x_char = 0; | |
| return; | |
| } | |
| if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) { | |
| serial_omap_stop_tx(&up->port); | |
| return; | |
| } | |
| count = up->port.fifosize / 4; | |
| do { | |
| serial_out(up, UART_TX, xmit->buf[xmit->tail]); | |
| xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); | |
| up->port.icount.tx++; | |
| if (uart_circ_empty(xmit)) | |
| break; | |
| } while (--count > 0); | |
| if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) | |
| uart_write_wakeup(&up->port); | |
| if (uart_circ_empty(xmit)) | |
| serial_omap_stop_tx(&up->port); | |
| } | |
| static inline void serial_omap_enable_ier_thri(struct uart_omap_port *up) | |
| { | |
| if (!(up->ier & UART_IER_THRI)) { | |
| up->ier |= UART_IER_THRI; | |
| serial_out(up, UART_IER, up->ier); | |
| } | |
| } | |
| static void serial_omap_start_tx(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| struct circ_buf *xmit; | |
| unsigned int start; | |
| int ret = 0; | |
| if (!up->use_dma) { | |
| serial_omap_enable_ier_thri(up); | |
| return; | |
| } | |
| if (up->uart_dma.tx_dma_used) | |
| return; | |
| xmit = &up->port.state->xmit; | |
| if (up->uart_dma.tx_dma_channel == OMAP_UART_DMA_CH_FREE) { | |
| ret = omap_request_dma(up->uart_dma.uart_dma_tx, | |
| "UART Tx DMA", | |
| (void *)uart_tx_dma_callback, up, | |
| &(up->uart_dma.tx_dma_channel)); | |
| if (ret < 0) { | |
| serial_omap_enable_ier_thri(up); | |
| return; | |
| } | |
| } | |
| spin_lock(&(up->uart_dma.tx_lock)); | |
| up->uart_dma.tx_dma_used = true; | |
| spin_unlock(&(up->uart_dma.tx_lock)); | |
| start = up->uart_dma.tx_buf_dma_phys + | |
| (xmit->tail & (UART_XMIT_SIZE - 1)); | |
| up->uart_dma.tx_buf_size = uart_circ_chars_pending(xmit); | |
| /* | |
| * It is a circular buffer. See if the buffer has wounded back. | |
| * If yes it will have to be transferred in two separate dma | |
| * transfers | |
| */ | |
| if (start + up->uart_dma.tx_buf_size >= | |
| up->uart_dma.tx_buf_dma_phys + UART_XMIT_SIZE) | |
| up->uart_dma.tx_buf_size = | |
| (up->uart_dma.tx_buf_dma_phys + | |
| UART_XMIT_SIZE) - start; | |
| omap_set_dma_dest_params(up->uart_dma.tx_dma_channel, 0, | |
| OMAP_DMA_AMODE_CONSTANT, | |
| up->uart_dma.uart_base, 0, 0); | |
| omap_set_dma_src_params(up->uart_dma.tx_dma_channel, 0, | |
| OMAP_DMA_AMODE_POST_INC, start, 0, 0); | |
| omap_set_dma_transfer_params(up->uart_dma.tx_dma_channel, | |
| OMAP_DMA_DATA_TYPE_S8, | |
| up->uart_dma.tx_buf_size, 1, | |
| OMAP_DMA_SYNC_ELEMENT, | |
| up->uart_dma.uart_dma_tx, 0); | |
| /* FIXME: Cache maintenance needed here? */ | |
| omap_start_dma(up->uart_dma.tx_dma_channel); | |
| } | |
| static unsigned int check_modem_status(struct uart_omap_port *up) | |
| { | |
| unsigned int status; | |
| status = serial_in(up, UART_MSR); | |
| status |= up->msr_saved_flags; | |
| up->msr_saved_flags = 0; | |
| if ((status & UART_MSR_ANY_DELTA) == 0) | |
| return status; | |
| if (status & UART_MSR_ANY_DELTA && up->ier & UART_IER_MSI && | |
| up->port.state != NULL) { | |
| if (status & UART_MSR_TERI) | |
| up->port.icount.rng++; | |
| if (status & UART_MSR_DDSR) | |
| up->port.icount.dsr++; | |
| if (status & UART_MSR_DDCD) | |
| uart_handle_dcd_change | |
| (&up->port, status & UART_MSR_DCD); | |
| if (status & UART_MSR_DCTS) | |
| uart_handle_cts_change | |
| (&up->port, status & UART_MSR_CTS); | |
| wake_up_interruptible(&up->port.state->port.delta_msr_wait); | |
| } | |
| return status; | |
| } | |
| /** | |
| * serial_omap_irq() - This handles the interrupt from one port | |
| * @irq: uart port irq number | |
| * @dev_id: uart port info | |
| */ | |
| static inline irqreturn_t serial_omap_irq(int irq, void *dev_id) | |
| { | |
| struct uart_omap_port *up = dev_id; | |
| unsigned int iir, lsr; | |
| unsigned long flags; | |
| iir = serial_in(up, UART_IIR); | |
| if (iir & UART_IIR_NO_INT) | |
| return IRQ_NONE; | |
| spin_lock_irqsave(&up->port.lock, flags); | |
| lsr = serial_in(up, UART_LSR); | |
| if (iir & UART_IIR_RLSI) { | |
| if (!up->use_dma) { | |
| if (lsr & UART_LSR_DR) | |
| receive_chars(up, &lsr); | |
| } else { | |
| up->ier &= ~(UART_IER_RDI | UART_IER_RLSI); | |
| serial_out(up, UART_IER, up->ier); | |
| if ((serial_omap_start_rxdma(up) != 0) && | |
| (lsr & UART_LSR_DR)) | |
| receive_chars(up, &lsr); | |
| } | |
| } | |
| check_modem_status(up); | |
| if ((lsr & UART_LSR_THRE) && (iir & UART_IIR_THRI)) | |
| transmit_chars(up); | |
| spin_unlock_irqrestore(&up->port.lock, flags); | |
| up->port_activity = jiffies; | |
| return IRQ_HANDLED; | |
| } | |
| static unsigned int serial_omap_tx_empty(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned long flags = 0; | |
| unsigned int ret = 0; | |
| dev_dbg(up->port.dev, "serial_omap_tx_empty+%d\n", up->pdev->id); | |
| spin_lock_irqsave(&up->port.lock, flags); | |
| ret = serial_in(up, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0; | |
| spin_unlock_irqrestore(&up->port.lock, flags); | |
| return ret; | |
| } | |
| static unsigned int serial_omap_get_mctrl(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned char status; | |
| unsigned int ret = 0; | |
| status = check_modem_status(up); | |
| dev_dbg(up->port.dev, "serial_omap_get_mctrl+%d\n", up->pdev->id); | |
| if (status & UART_MSR_DCD) | |
| ret |= TIOCM_CAR; | |
| if (status & UART_MSR_RI) | |
| ret |= TIOCM_RNG; | |
| if (status & UART_MSR_DSR) | |
| ret |= TIOCM_DSR; | |
| if (status & UART_MSR_CTS) | |
| ret |= TIOCM_CTS; | |
| return ret; | |
| } | |
| static void serial_omap_set_mctrl(struct uart_port *port, unsigned int mctrl) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned char mcr = 0; | |
| dev_dbg(up->port.dev, "serial_omap_set_mctrl+%d\n", up->pdev->id); | |
| if (mctrl & TIOCM_RTS) | |
| mcr |= UART_MCR_RTS; | |
| if (mctrl & TIOCM_DTR) | |
| mcr |= UART_MCR_DTR; | |
| if (mctrl & TIOCM_OUT1) | |
| mcr |= UART_MCR_OUT1; | |
| if (mctrl & TIOCM_OUT2) | |
| mcr |= UART_MCR_OUT2; | |
| if (mctrl & TIOCM_LOOP) | |
| mcr |= UART_MCR_LOOP; | |
| mcr |= up->mcr; | |
| serial_out(up, UART_MCR, mcr); | |
| } | |
| static void serial_omap_break_ctl(struct uart_port *port, int break_state) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned long flags = 0; | |
| dev_dbg(up->port.dev, "serial_omap_break_ctl+%d\n", up->pdev->id); | |
| spin_lock_irqsave(&up->port.lock, flags); | |
| if (break_state == -1) | |
| up->lcr |= UART_LCR_SBC; | |
| else | |
| up->lcr &= ~UART_LCR_SBC; | |
| serial_out(up, UART_LCR, up->lcr); | |
| spin_unlock_irqrestore(&up->port.lock, flags); | |
| } | |
| static int serial_omap_startup(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned long flags = 0; | |
| int retval; | |
| /* | |
| * Allocate the IRQ | |
| */ | |
| retval = request_irq(up->port.irq, serial_omap_irq, up->port.irqflags, | |
| up->name, up); | |
| if (retval) | |
| return retval; | |
| dev_dbg(up->port.dev, "serial_omap_startup+%d\n", up->pdev->id); | |
| /* | |
| * Clear the FIFO buffers and disable them. | |
| * (they will be reenabled in set_termios()) | |
| */ | |
| serial_omap_clear_fifos(up); | |
| /* For Hardware flow control */ | |
| serial_out(up, UART_MCR, UART_MCR_RTS); | |
| /* | |
| * Clear the interrupt registers. | |
| */ | |
| (void) serial_in(up, UART_LSR); | |
| if (serial_in(up, UART_LSR) & UART_LSR_DR) | |
| (void) serial_in(up, UART_RX); | |
| (void) serial_in(up, UART_IIR); | |
| (void) serial_in(up, UART_MSR); | |
| /* | |
| * Now, initialize the UART | |
| */ | |
| serial_out(up, UART_LCR, UART_LCR_WLEN8); | |
| spin_lock_irqsave(&up->port.lock, flags); | |
| /* | |
| * Most PC uarts need OUT2 raised to enable interrupts. | |
| */ | |
| up->port.mctrl |= TIOCM_OUT2; | |
| serial_omap_set_mctrl(&up->port, up->port.mctrl); | |
| spin_unlock_irqrestore(&up->port.lock, flags); | |
| up->msr_saved_flags = 0; | |
| if (up->use_dma) { | |
| free_page((unsigned long)up->port.state->xmit.buf); | |
| up->port.state->xmit.buf = dma_alloc_coherent(NULL, | |
| UART_XMIT_SIZE, | |
| (dma_addr_t *)&(up->uart_dma.tx_buf_dma_phys), | |
| 0); | |
| init_timer(&(up->uart_dma.rx_timer)); | |
| up->uart_dma.rx_timer.function = serial_omap_rx_timeout; | |
| up->uart_dma.rx_timer.data = up->pdev->id; | |
| /* Currently the buffer size is 4KB. Can increase it */ | |
| up->uart_dma.rx_buf = dma_alloc_coherent(NULL, | |
| up->uart_dma.rx_buf_size, | |
| (dma_addr_t *)&(up->uart_dma.rx_buf_dma_phys), 0); | |
| } | |
| /* | |
| * Finally, enable interrupts. Note: Modem status interrupts | |
| * are set via set_termios(), which will be occurring imminently | |
| * anyway, so we don't enable them here. | |
| */ | |
| up->ier = UART_IER_RLSI | UART_IER_RDI; | |
| serial_out(up, UART_IER, up->ier); | |
| /* Enable module level wake up */ | |
| serial_out(up, UART_OMAP_WER, OMAP_UART_WER_MOD_WKUP); | |
| up->port_activity = jiffies; | |
| return 0; | |
| } | |
| static void serial_omap_shutdown(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned long flags = 0; | |
| dev_dbg(up->port.dev, "serial_omap_shutdown+%d\n", up->pdev->id); | |
| /* | |
| * Disable interrupts from this port | |
| */ | |
| up->ier = 0; | |
| serial_out(up, UART_IER, 0); | |
| spin_lock_irqsave(&up->port.lock, flags); | |
| up->port.mctrl &= ~TIOCM_OUT2; | |
| serial_omap_set_mctrl(&up->port, up->port.mctrl); | |
| spin_unlock_irqrestore(&up->port.lock, flags); | |
| /* | |
| * Disable break condition and FIFOs | |
| */ | |
| serial_out(up, UART_LCR, serial_in(up, UART_LCR) & ~UART_LCR_SBC); | |
| serial_omap_clear_fifos(up); | |
| /* | |
| * Read data port to reset things, and then free the irq | |
| */ | |
| if (serial_in(up, UART_LSR) & UART_LSR_DR) | |
| (void) serial_in(up, UART_RX); | |
| if (up->use_dma) { | |
| dma_free_coherent(up->port.dev, | |
| UART_XMIT_SIZE, up->port.state->xmit.buf, | |
| up->uart_dma.tx_buf_dma_phys); | |
| up->port.state->xmit.buf = NULL; | |
| serial_omap_stop_rx(port); | |
| dma_free_coherent(up->port.dev, | |
| up->uart_dma.rx_buf_size, up->uart_dma.rx_buf, | |
| up->uart_dma.rx_buf_dma_phys); | |
| up->uart_dma.rx_buf = NULL; | |
| } | |
| free_irq(up->port.irq, up); | |
| } | |
| static inline void | |
| serial_omap_configure_xonxoff | |
| (struct uart_omap_port *up, struct ktermios *termios) | |
| { | |
| unsigned char efr = 0; | |
| up->lcr = serial_in(up, UART_LCR); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| up->efr = serial_in(up, UART_EFR); | |
| serial_out(up, UART_EFR, up->efr & ~UART_EFR_ECB); | |
| serial_out(up, UART_XON1, termios->c_cc[VSTART]); | |
| serial_out(up, UART_XOFF1, termios->c_cc[VSTOP]); | |
| /* clear SW control mode bits */ | |
| efr = up->efr; | |
| efr &= OMAP_UART_SW_CLR; | |
| /* | |
| * IXON Flag: | |
| * Enable XON/XOFF flow control on output. | |
| * Transmit XON1, XOFF1 | |
| */ | |
| if (termios->c_iflag & IXON) | |
| efr |= OMAP_UART_SW_TX; | |
| /* | |
| * IXOFF Flag: | |
| * Enable XON/XOFF flow control on input. | |
| * Receiver compares XON1, XOFF1. | |
| */ | |
| if (termios->c_iflag & IXOFF) | |
| efr |= OMAP_UART_SW_RX; | |
| serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| up->mcr = serial_in(up, UART_MCR); | |
| /* | |
| * IXANY Flag: | |
| * Enable any character to restart output. | |
| * Operation resumes after receiving any | |
| * character after recognition of the XOFF character | |
| */ | |
| if (termios->c_iflag & IXANY) | |
| up->mcr |= UART_MCR_XONANY; | |
| serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); | |
| /* Enable special char function UARTi.EFR_REG[5] and | |
| * load the new software flow control mode IXON or IXOFF | |
| * and restore the UARTi.EFR_REG[4] ENHANCED_EN value. | |
| */ | |
| serial_out(up, UART_EFR, efr | UART_EFR_SCD); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| serial_out(up, UART_MCR, up->mcr & ~UART_MCR_TCRTLR); | |
| serial_out(up, UART_LCR, up->lcr); | |
| } | |
| static void | |
| serial_omap_set_termios(struct uart_port *port, struct ktermios *termios, | |
| struct ktermios *old) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned char cval = 0; | |
| unsigned char efr = 0; | |
| unsigned long flags = 0; | |
| unsigned int baud, quot; | |
| switch (termios->c_cflag & CSIZE) { | |
| case CS5: | |
| cval = UART_LCR_WLEN5; | |
| break; | |
| case CS6: | |
| cval = UART_LCR_WLEN6; | |
| break; | |
| case CS7: | |
| cval = UART_LCR_WLEN7; | |
| break; | |
| default: | |
| case CS8: | |
| cval = UART_LCR_WLEN8; | |
| break; | |
| } | |
| if (termios->c_cflag & CSTOPB) | |
| cval |= UART_LCR_STOP; | |
| if (termios->c_cflag & PARENB) | |
| cval |= UART_LCR_PARITY; | |
| if (!(termios->c_cflag & PARODD)) | |
| cval |= UART_LCR_EPAR; | |
| /* | |
| * Ask the core to calculate the divisor for us. | |
| */ | |
| baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/13); | |
| quot = serial_omap_get_divisor(port, baud); | |
| up->fcr = UART_FCR_R_TRIG_01 | UART_FCR_T_TRIG_01 | | |
| UART_FCR_ENABLE_FIFO; | |
| if (up->use_dma) | |
| up->fcr |= UART_FCR_DMA_SELECT; | |
| /* | |
| * Ok, we're now changing the port state. Do it with | |
| * interrupts disabled. | |
| */ | |
| spin_lock_irqsave(&up->port.lock, flags); | |
| /* | |
| * Update the per-port timeout. | |
| */ | |
| uart_update_timeout(port, termios->c_cflag, baud); | |
| up->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; | |
| if (termios->c_iflag & INPCK) | |
| up->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE; | |
| if (termios->c_iflag & (BRKINT | PARMRK)) | |
| up->port.read_status_mask |= UART_LSR_BI; | |
| /* | |
| * Characters to ignore | |
| */ | |
| up->port.ignore_status_mask = 0; | |
| if (termios->c_iflag & IGNPAR) | |
| up->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; | |
| if (termios->c_iflag & IGNBRK) { | |
| up->port.ignore_status_mask |= UART_LSR_BI; | |
| /* | |
| * If we're ignoring parity and break indicators, | |
| * ignore overruns too (for real raw support). | |
| */ | |
| if (termios->c_iflag & IGNPAR) | |
| up->port.ignore_status_mask |= UART_LSR_OE; | |
| } | |
| /* | |
| * ignore all characters if CREAD is not set | |
| */ | |
| if ((termios->c_cflag & CREAD) == 0) | |
| up->port.ignore_status_mask |= UART_LSR_DR; | |
| /* | |
| * Modem status interrupts | |
| */ | |
| up->ier &= ~UART_IER_MSI; | |
| if (UART_ENABLE_MS(&up->port, termios->c_cflag)) | |
| up->ier |= UART_IER_MSI; | |
| serial_out(up, UART_IER, up->ier); | |
| serial_out(up, UART_LCR, cval); /* reset DLAB */ | |
| /* FIFOs and DMA Settings */ | |
| /* FCR can be changed only when the | |
| * baud clock is not running | |
| * DLL_REG and DLH_REG set to 0. | |
| */ | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| serial_out(up, UART_DLL, 0); | |
| serial_out(up, UART_DLM, 0); | |
| serial_out(up, UART_LCR, 0); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| up->efr = serial_in(up, UART_EFR); | |
| serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| up->mcr = serial_in(up, UART_MCR); | |
| serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); | |
| /* FIFO ENABLE, DMA MODE */ | |
| serial_out(up, UART_FCR, up->fcr); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| if (up->use_dma) { | |
| serial_out(up, UART_TI752_TLR, 0); | |
| serial_out(up, UART_OMAP_SCR, | |
| (UART_FCR_TRIGGER_4 | UART_FCR_TRIGGER_8)); | |
| } | |
| serial_out(up, UART_EFR, up->efr); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| serial_out(up, UART_MCR, up->mcr); | |
| /* Protocol, Baud Rate, and Interrupt Settings */ | |
| serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_DISABLE); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| up->efr = serial_in(up, UART_EFR); | |
| serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); | |
| serial_out(up, UART_LCR, 0); | |
| serial_out(up, UART_IER, 0); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| serial_out(up, UART_DLL, quot & 0xff); /* LS of divisor */ | |
| serial_out(up, UART_DLM, quot >> 8); /* MS of divisor */ | |
| serial_out(up, UART_LCR, 0); | |
| serial_out(up, UART_IER, up->ier); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| serial_out(up, UART_EFR, up->efr); | |
| serial_out(up, UART_LCR, cval); | |
| if (baud > 230400 && baud != 3000000) | |
| serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_13X_MODE); | |
| else | |
| serial_out(up, UART_OMAP_MDR1, UART_OMAP_MDR1_16X_MODE); | |
| /* Hardware Flow Control Configuration */ | |
| if (termios->c_cflag & CRTSCTS) { | |
| efr |= (UART_EFR_CTS | UART_EFR_RTS); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| up->mcr = serial_in(up, UART_MCR); | |
| serial_out(up, UART_MCR, up->mcr | UART_MCR_TCRTLR); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| up->efr = serial_in(up, UART_EFR); | |
| serial_out(up, UART_EFR, up->efr | UART_EFR_ECB); | |
| serial_out(up, UART_TI752_TCR, OMAP_UART_TCR_TRIG); | |
| serial_out(up, UART_EFR, efr); /* Enable AUTORTS and AUTOCTS */ | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_A); | |
| serial_out(up, UART_MCR, up->mcr | UART_MCR_RTS); | |
| serial_out(up, UART_LCR, cval); | |
| } | |
| serial_omap_set_mctrl(&up->port, up->port.mctrl); | |
| /* Software Flow Control Configuration */ | |
| serial_omap_configure_xonxoff(up, termios); | |
| spin_unlock_irqrestore(&up->port.lock, flags); | |
| dev_dbg(up->port.dev, "serial_omap_set_termios+%d\n", up->pdev->id); | |
| } | |
| static void | |
| serial_omap_pm(struct uart_port *port, unsigned int state, | |
| unsigned int oldstate) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned char efr; | |
| dev_dbg(up->port.dev, "serial_omap_pm+%d\n", up->pdev->id); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| efr = serial_in(up, UART_EFR); | |
| serial_out(up, UART_EFR, efr | UART_EFR_ECB); | |
| serial_out(up, UART_LCR, 0); | |
| serial_out(up, UART_IER, (state != 0) ? UART_IERX_SLEEP : 0); | |
| serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); | |
| serial_out(up, UART_EFR, efr); | |
| serial_out(up, UART_LCR, 0); | |
| } | |
| static void serial_omap_release_port(struct uart_port *port) | |
| { | |
| dev_dbg(port->dev, "serial_omap_release_port+\n"); | |
| } | |
| static int serial_omap_request_port(struct uart_port *port) | |
| { | |
| dev_dbg(port->dev, "serial_omap_request_port+\n"); | |
| return 0; | |
| } | |
| static void serial_omap_config_port(struct uart_port *port, int flags) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| dev_dbg(up->port.dev, "serial_omap_config_port+%d\n", | |
| up->pdev->id); | |
| up->port.type = PORT_OMAP; | |
| } | |
| static int | |
| serial_omap_verify_port(struct uart_port *port, struct serial_struct *ser) | |
| { | |
| /* we don't want the core code to modify any port params */ | |
| dev_dbg(port->dev, "serial_omap_verify_port+\n"); | |
| return -EINVAL; | |
| } | |
| static const char * | |
| serial_omap_type(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| dev_dbg(up->port.dev, "serial_omap_type+%d\n", up->pdev->id); | |
| return up->name; | |
| } | |
| #define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) | |
| static inline void wait_for_xmitr(struct uart_omap_port *up) | |
| { | |
| unsigned int status, tmout = 10000; | |
| /* Wait up to 10ms for the character(s) to be sent. */ | |
| do { | |
| status = serial_in(up, UART_LSR); | |
| if (status & UART_LSR_BI) | |
| up->lsr_break_flag = UART_LSR_BI; | |
| if (--tmout == 0) | |
| break; | |
| udelay(1); | |
| } while ((status & BOTH_EMPTY) != BOTH_EMPTY); | |
| /* Wait up to 1s for flow control if necessary */ | |
| if (up->port.flags & UPF_CONS_FLOW) { | |
| tmout = 1000000; | |
| for (tmout = 1000000; tmout; tmout--) { | |
| unsigned int msr = serial_in(up, UART_MSR); | |
| up->msr_saved_flags |= msr & MSR_SAVE_FLAGS; | |
| if (msr & UART_MSR_CTS) | |
| break; | |
| udelay(1); | |
| } | |
| } | |
| } | |
| #ifdef CONFIG_CONSOLE_POLL | |
| static void serial_omap_poll_put_char(struct uart_port *port, unsigned char ch) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| wait_for_xmitr(up); | |
| serial_out(up, UART_TX, ch); | |
| } | |
| static int serial_omap_poll_get_char(struct uart_port *port) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| unsigned int status = serial_in(up, UART_LSR); | |
| if (!(status & UART_LSR_DR)) | |
| return NO_POLL_CHAR; | |
| return serial_in(up, UART_RX); | |
| } | |
| #endif /* CONFIG_CONSOLE_POLL */ | |
| #ifdef CONFIG_SERIAL_OMAP_CONSOLE | |
| static struct uart_omap_port *serial_omap_console_ports[4]; | |
| static struct uart_driver serial_omap_reg; | |
| static void serial_omap_console_putchar(struct uart_port *port, int ch) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)port; | |
| wait_for_xmitr(up); | |
| serial_out(up, UART_TX, ch); | |
| } | |
| static void | |
| serial_omap_console_write(struct console *co, const char *s, | |
| unsigned int count) | |
| { | |
| struct uart_omap_port *up = serial_omap_console_ports[co->index]; | |
| unsigned long flags; | |
| unsigned int ier; | |
| int locked = 1; | |
| local_irq_save(flags); | |
| if (up->port.sysrq) | |
| locked = 0; | |
| else if (oops_in_progress) | |
| locked = spin_trylock(&up->port.lock); | |
| else | |
| spin_lock(&up->port.lock); | |
| /* | |
| * First save the IER then disable the interrupts | |
| */ | |
| ier = serial_in(up, UART_IER); | |
| serial_out(up, UART_IER, 0); | |
| uart_console_write(&up->port, s, count, serial_omap_console_putchar); | |
| /* | |
| * Finally, wait for transmitter to become empty | |
| * and restore the IER | |
| */ | |
| wait_for_xmitr(up); | |
| serial_out(up, UART_IER, ier); | |
| /* | |
| * The receive handling will happen properly because the | |
| * receive ready bit will still be set; it is not cleared | |
| * on read. However, modem control will not, we must | |
| * call it if we have saved something in the saved flags | |
| * while processing with interrupts off. | |
| */ | |
| if (up->msr_saved_flags) | |
| check_modem_status(up); | |
| if (locked) | |
| spin_unlock(&up->port.lock); | |
| local_irq_restore(flags); | |
| } | |
| static int __init | |
| serial_omap_console_setup(struct console *co, char *options) | |
| { | |
| struct uart_omap_port *up; | |
| int baud = 115200; | |
| int bits = 8; | |
| int parity = 'n'; | |
| int flow = 'n'; | |
| if (serial_omap_console_ports[co->index] == NULL) | |
| return -ENODEV; | |
| up = serial_omap_console_ports[co->index]; | |
| if (options) | |
| uart_parse_options(options, &baud, &parity, &bits, &flow); | |
| return uart_set_options(&up->port, co, baud, parity, bits, flow); | |
| } | |
| static struct console serial_omap_console = { | |
| .name = OMAP_SERIAL_NAME, | |
| .write = serial_omap_console_write, | |
| .device = uart_console_device, | |
| .setup = serial_omap_console_setup, | |
| .flags = CON_PRINTBUFFER, | |
| .index = -1, | |
| .data = &serial_omap_reg, | |
| }; | |
| static void serial_omap_add_console_port(struct uart_omap_port *up) | |
| { | |
| serial_omap_console_ports[up->pdev->id] = up; | |
| } | |
| #define OMAP_CONSOLE (&serial_omap_console) | |
| #else | |
| #define OMAP_CONSOLE NULL | |
| static inline void serial_omap_add_console_port(struct uart_omap_port *up) | |
| {} | |
| #endif | |
| static struct uart_ops serial_omap_pops = { | |
| .tx_empty = serial_omap_tx_empty, | |
| .set_mctrl = serial_omap_set_mctrl, | |
| .get_mctrl = serial_omap_get_mctrl, | |
| .stop_tx = serial_omap_stop_tx, | |
| .start_tx = serial_omap_start_tx, | |
| .stop_rx = serial_omap_stop_rx, | |
| .enable_ms = serial_omap_enable_ms, | |
| .break_ctl = serial_omap_break_ctl, | |
| .startup = serial_omap_startup, | |
| .shutdown = serial_omap_shutdown, | |
| .set_termios = serial_omap_set_termios, | |
| .pm = serial_omap_pm, | |
| .type = serial_omap_type, | |
| .release_port = serial_omap_release_port, | |
| .request_port = serial_omap_request_port, | |
| .config_port = serial_omap_config_port, | |
| .verify_port = serial_omap_verify_port, | |
| #ifdef CONFIG_CONSOLE_POLL | |
| .poll_put_char = serial_omap_poll_put_char, | |
| .poll_get_char = serial_omap_poll_get_char, | |
| #endif | |
| }; | |
| static struct uart_driver serial_omap_reg = { | |
| .owner = THIS_MODULE, | |
| .driver_name = "OMAP-SERIAL", | |
| .dev_name = OMAP_SERIAL_NAME, | |
| .nr = OMAP_MAX_HSUART_PORTS, | |
| .cons = OMAP_CONSOLE, | |
| }; | |
| static int | |
| serial_omap_suspend(struct platform_device *pdev, pm_message_t state) | |
| { | |
| struct uart_omap_port *up = platform_get_drvdata(pdev); | |
| if (up) | |
| uart_suspend_port(&serial_omap_reg, &up->port); | |
| return 0; | |
| } | |
| static int serial_omap_resume(struct platform_device *dev) | |
| { | |
| struct uart_omap_port *up = platform_get_drvdata(dev); | |
| if (up) | |
| uart_resume_port(&serial_omap_reg, &up->port); | |
| return 0; | |
| } | |
| static void serial_omap_rx_timeout(unsigned long uart_no) | |
| { | |
| struct uart_omap_port *up = ui[uart_no]; | |
| unsigned int curr_dma_pos, curr_transmitted_size; | |
| int ret = 0; | |
| curr_dma_pos = omap_get_dma_dst_pos(up->uart_dma.rx_dma_channel); | |
| if ((curr_dma_pos == up->uart_dma.prev_rx_dma_pos) || | |
| (curr_dma_pos == 0)) { | |
| if (jiffies_to_msecs(jiffies - up->port_activity) < | |
| RX_TIMEOUT) { | |
| mod_timer(&up->uart_dma.rx_timer, jiffies + | |
| usecs_to_jiffies(up->uart_dma.rx_timeout)); | |
| } else { | |
| serial_omap_stop_rxdma(up); | |
| up->ier |= (UART_IER_RDI | UART_IER_RLSI); | |
| serial_out(up, UART_IER, up->ier); | |
| } | |
| return; | |
| } | |
| curr_transmitted_size = curr_dma_pos - | |
| up->uart_dma.prev_rx_dma_pos; | |
| up->port.icount.rx += curr_transmitted_size; | |
| tty_insert_flip_string(up->port.state->port.tty, | |
| up->uart_dma.rx_buf + | |
| (up->uart_dma.prev_rx_dma_pos - | |
| up->uart_dma.rx_buf_dma_phys), | |
| curr_transmitted_size); | |
| tty_flip_buffer_push(up->port.state->port.tty); | |
| up->uart_dma.prev_rx_dma_pos = curr_dma_pos; | |
| if (up->uart_dma.rx_buf_size + | |
| up->uart_dma.rx_buf_dma_phys == curr_dma_pos) { | |
| ret = serial_omap_start_rxdma(up); | |
| if (ret < 0) { | |
| serial_omap_stop_rxdma(up); | |
| up->ier |= (UART_IER_RDI | UART_IER_RLSI); | |
| serial_out(up, UART_IER, up->ier); | |
| } | |
| } else { | |
| mod_timer(&up->uart_dma.rx_timer, jiffies + | |
| usecs_to_jiffies(up->uart_dma.rx_timeout)); | |
| } | |
| up->port_activity = jiffies; | |
| } | |
| static void uart_rx_dma_callback(int lch, u16 ch_status, void *data) | |
| { | |
| return; | |
| } | |
| static int serial_omap_start_rxdma(struct uart_omap_port *up) | |
| { | |
| int ret = 0; | |
| if (up->uart_dma.rx_dma_channel == -1) { | |
| ret = omap_request_dma(up->uart_dma.uart_dma_rx, | |
| "UART Rx DMA", | |
| (void *)uart_rx_dma_callback, up, | |
| &(up->uart_dma.rx_dma_channel)); | |
| if (ret < 0) | |
| return ret; | |
| omap_set_dma_src_params(up->uart_dma.rx_dma_channel, 0, | |
| OMAP_DMA_AMODE_CONSTANT, | |
| up->uart_dma.uart_base, 0, 0); | |
| omap_set_dma_dest_params(up->uart_dma.rx_dma_channel, 0, | |
| OMAP_DMA_AMODE_POST_INC, | |
| up->uart_dma.rx_buf_dma_phys, 0, 0); | |
| omap_set_dma_transfer_params(up->uart_dma.rx_dma_channel, | |
| OMAP_DMA_DATA_TYPE_S8, | |
| up->uart_dma.rx_buf_size, 1, | |
| OMAP_DMA_SYNC_ELEMENT, | |
| up->uart_dma.uart_dma_rx, 0); | |
| } | |
| up->uart_dma.prev_rx_dma_pos = up->uart_dma.rx_buf_dma_phys; | |
| /* FIXME: Cache maintenance needed here? */ | |
| omap_start_dma(up->uart_dma.rx_dma_channel); | |
| mod_timer(&up->uart_dma.rx_timer, jiffies + | |
| usecs_to_jiffies(up->uart_dma.rx_timeout)); | |
| up->uart_dma.rx_dma_used = true; | |
| return ret; | |
| } | |
| static void serial_omap_continue_tx(struct uart_omap_port *up) | |
| { | |
| struct circ_buf *xmit = &up->port.state->xmit; | |
| unsigned int start = up->uart_dma.tx_buf_dma_phys | |
| + (xmit->tail & (UART_XMIT_SIZE - 1)); | |
| if (uart_circ_empty(xmit)) | |
| return; | |
| up->uart_dma.tx_buf_size = uart_circ_chars_pending(xmit); | |
| /* | |
| * It is a circular buffer. See if the buffer has wounded back. | |
| * If yes it will have to be transferred in two separate dma | |
| * transfers | |
| */ | |
| if (start + up->uart_dma.tx_buf_size >= | |
| up->uart_dma.tx_buf_dma_phys + UART_XMIT_SIZE) | |
| up->uart_dma.tx_buf_size = | |
| (up->uart_dma.tx_buf_dma_phys + UART_XMIT_SIZE) - start; | |
| omap_set_dma_dest_params(up->uart_dma.tx_dma_channel, 0, | |
| OMAP_DMA_AMODE_CONSTANT, | |
| up->uart_dma.uart_base, 0, 0); | |
| omap_set_dma_src_params(up->uart_dma.tx_dma_channel, 0, | |
| OMAP_DMA_AMODE_POST_INC, start, 0, 0); | |
| omap_set_dma_transfer_params(up->uart_dma.tx_dma_channel, | |
| OMAP_DMA_DATA_TYPE_S8, | |
| up->uart_dma.tx_buf_size, 1, | |
| OMAP_DMA_SYNC_ELEMENT, | |
| up->uart_dma.uart_dma_tx, 0); | |
| /* FIXME: Cache maintenance needed here? */ | |
| omap_start_dma(up->uart_dma.tx_dma_channel); | |
| } | |
| static void uart_tx_dma_callback(int lch, u16 ch_status, void *data) | |
| { | |
| struct uart_omap_port *up = (struct uart_omap_port *)data; | |
| struct circ_buf *xmit = &up->port.state->xmit; | |
| xmit->tail = (xmit->tail + up->uart_dma.tx_buf_size) & \ | |
| (UART_XMIT_SIZE - 1); | |
| up->port.icount.tx += up->uart_dma.tx_buf_size; | |
| if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) | |
| uart_write_wakeup(&up->port); | |
| if (uart_circ_empty(xmit)) { | |
| spin_lock(&(up->uart_dma.tx_lock)); | |
| serial_omap_stop_tx(&up->port); | |
| up->uart_dma.tx_dma_used = false; | |
| spin_unlock(&(up->uart_dma.tx_lock)); | |
| } else { | |
| omap_stop_dma(up->uart_dma.tx_dma_channel); | |
| serial_omap_continue_tx(up); | |
| } | |
| up->port_activity = jiffies; | |
| return; | |
| } | |
| static int serial_omap_probe(struct platform_device *pdev) | |
| { | |
| struct uart_omap_port *up; | |
| struct resource *mem, *irq, *dma_tx, *dma_rx; | |
| struct omap_uart_port_info *omap_up_info = pdev->dev.platform_data; | |
| int ret = -ENOSPC; | |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
| if (!mem) { | |
| dev_err(&pdev->dev, "no mem resource?\n"); | |
| return -ENODEV; | |
| } | |
| irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
| if (!irq) { | |
| dev_err(&pdev->dev, "no irq resource?\n"); | |
| return -ENODEV; | |
| } | |
| if (!request_mem_region(mem->start, (mem->end - mem->start) + 1, | |
| pdev->dev.driver->name)) { | |
| dev_err(&pdev->dev, "memory region already claimed\n"); | |
| return -EBUSY; | |
| } | |
| dma_rx = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); | |
| if (!dma_rx) { | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| dma_tx = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); | |
| if (!dma_tx) { | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| up = kzalloc(sizeof(*up), GFP_KERNEL); | |
| if (up == NULL) { | |
| ret = -ENOMEM; | |
| goto do_release_region; | |
| } | |
| sprintf(up->name, "OMAP UART%d", pdev->id); | |
| up->pdev = pdev; | |
| up->port.dev = &pdev->dev; | |
| up->port.type = PORT_OMAP; | |
| up->port.iotype = UPIO_MEM; | |
| up->port.irq = irq->start; | |
| up->port.regshift = 2; | |
| up->port.fifosize = 64; | |
| up->port.ops = &serial_omap_pops; | |
| up->port.line = pdev->id; | |
| up->port.membase = omap_up_info->membase; | |
| up->port.mapbase = omap_up_info->mapbase; | |
| up->port.flags = omap_up_info->flags; | |
| up->port.irqflags = omap_up_info->irqflags; | |
| up->port.uartclk = omap_up_info->uartclk; | |
| up->uart_dma.uart_base = mem->start; | |
| if (omap_up_info->dma_enabled) { | |
| up->uart_dma.uart_dma_tx = dma_tx->start; | |
| up->uart_dma.uart_dma_rx = dma_rx->start; | |
| up->use_dma = 1; | |
| up->uart_dma.rx_buf_size = 4096; | |
| up->uart_dma.rx_timeout = 2; | |
| spin_lock_init(&(up->uart_dma.tx_lock)); | |
| spin_lock_init(&(up->uart_dma.rx_lock)); | |
| up->uart_dma.tx_dma_channel = OMAP_UART_DMA_CH_FREE; | |
| up->uart_dma.rx_dma_channel = OMAP_UART_DMA_CH_FREE; | |
| } | |
| ui[pdev->id] = up; | |
| serial_omap_add_console_port(up); | |
| ret = uart_add_one_port(&serial_omap_reg, &up->port); | |
| if (ret != 0) | |
| goto do_release_region; | |
| platform_set_drvdata(pdev, up); | |
| return 0; | |
| err: | |
| dev_err(&pdev->dev, "[UART%d]: failure [%s]: %d\n", | |
| pdev->id, __func__, ret); | |
| do_release_region: | |
| release_mem_region(mem->start, (mem->end - mem->start) + 1); | |
| return ret; | |
| } | |
| static int serial_omap_remove(struct platform_device *dev) | |
| { | |
| struct uart_omap_port *up = platform_get_drvdata(dev); | |
| platform_set_drvdata(dev, NULL); | |
| if (up) { | |
| uart_remove_one_port(&serial_omap_reg, &up->port); | |
| kfree(up); | |
| } | |
| return 0; | |
| } | |
| static struct platform_driver serial_omap_driver = { | |
| .probe = serial_omap_probe, | |
| .remove = serial_omap_remove, | |
| .suspend = serial_omap_suspend, | |
| .resume = serial_omap_resume, | |
| .driver = { | |
| .name = DRIVER_NAME, | |
| }, | |
| }; | |
| static int __init serial_omap_init(void) | |
| { | |
| int ret; | |
| ret = uart_register_driver(&serial_omap_reg); | |
| if (ret != 0) | |
| return ret; | |
| ret = platform_driver_register(&serial_omap_driver); | |
| if (ret != 0) | |
| uart_unregister_driver(&serial_omap_reg); | |
| return ret; | |
| } | |
| static void __exit serial_omap_exit(void) | |
| { | |
| platform_driver_unregister(&serial_omap_driver); | |
| uart_unregister_driver(&serial_omap_reg); | |
| } | |
| module_init(serial_omap_init); | |
| module_exit(serial_omap_exit); | |
| MODULE_DESCRIPTION("OMAP High Speed UART driver"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_AUTHOR("Texas Instruments Inc"); |