Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751032Ab0KIFBb (ORCPT ); Tue, 9 Nov 2010 00:01:31 -0500 Received: from sm-d311v.smileserver.ne.jp ([203.211.202.206]:22967 "EHLO sm-d311v.smileserver.ne.jp" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750695Ab0KIFBa (ORCPT ); Tue, 9 Nov 2010 00:01:30 -0500 Message-ID: <4CD8D5A2.1080202@dsn.okisemi.com> Date: Tue, 09 Nov 2010 14:01:22 +0900 From: Tomoya MORINAGA User-Agent: Mozilla/5.0 (X11; U; Linux i686; ja; rv:1.9.1.11) Gecko/20100711 Thunderbird/3.0.6 MIME-Version: 1.0 To: Greg Kroah-Hartman , Ben Dooks , Alan Cox , Kukjin Kim , Mike Frysinger , Feng Tang , Tobias Klauser , linux-kernel@vger.kernel.org CC: yong.y.wang@intel.com, qi.wang@intel.com, kok.howg.ewe@intel.com, andrew.chih.howe.khor@intel.com Subject: [PATCH] EG20T: Update PCH_UART driver to 2.6.36 Content-Type: text/plain; charset=ISO-2022-JP Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 42118 Lines: 1604 UART driver of Intel EG20T(Topcliff) PCH Intel EG20T PCH is the platform controller hub that is going to be used in Intel's general embedded platform. All IO peripherals in Intel EG20T PCH are actually devices sitting on AMBA bus. Intel EG20T PCH has UART I/F. Using this I/F, it is able to access system devices connected to UART. Signed-off-by: Tomoya MORINAGA --- drivers/serial/Kconfig | 8 + drivers/serial/Makefile | 1 + drivers/serial/pch_uart.c | 1549 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1558 insertions(+), 0 deletions(-) create mode 100644 drivers/serial/pch_uart.c diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index aff9dcd..52cf0ab 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1632,4 +1632,12 @@ config SERIAL_ALTERA_UART_CONSOLE help Enable a Altera UART port to be the system console. +config SERIAL_PCH_UART + tristate "Intel EG20T PCH UART" + select SERIAL_CORE + depends on PCI + help + This driver is for PCH(Platform controller Hub) UART of Intel EG20T + which is an IOH(Input/Output Hub) for x86 embedded processor. + Enabling PCH_DMA, this PCH UART works as DMA mode. endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index c570576..ce4c9a5 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -89,3 +89,4 @@ obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o obj-$(CONFIG_SERIAL_MRST_MAX3110) += mrst_max3110.o obj-$(CONFIG_SERIAL_MFD_HSU) += mfd.o obj-$(CONFIG_SERIAL_OMAP) += omap-serial.o +obj-$(CONFIG_SERIAL_PCH_UART) += pch_uart.o diff --git a/drivers/serial/pch_uart.c b/drivers/serial/pch_uart.c new file mode 100644 index 0000000..0a866d6 --- /dev/null +++ b/drivers/serial/pch_uart.c @@ -0,0 +1,1549 @@ +/* + *Copyright (C) 2010 OKI SEMICONDUCTOR CO., LTD. + * + *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; version 2 of the License. + * + *This program is distributed in the hope that it will be useful, + *but WITHOUT ANY WARRANTY; without even the implied warranty of + *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *GNU General Public License for more details. + * + *You should have received a copy of the GNU General Public License + *along with this program; if not, write to the Free Software + *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_PCH_DMA + #include + #include +#endif + +enum { + PCH_UART_HANDLED_RX_INT_SHIFT, + PCH_UART_HANDLED_TX_INT_SHIFT, + PCH_UART_HANDLED_RX_ERR_INT_SHIFT, + PCH_UART_HANDLED_RX_TRG_INT_SHIFT, + PCH_UART_HANDLED_MS_INT_SHIFT, +}; + +enum { + PCH_UART_HAL_INVALID_PARAM = EINVAL, + PCH_UART_HAL_NOT_INITIALIZED = 256, + PCH_UART_HAL_NOT_REQUESTED, + PCH_UART_HAL_BASE_NOT_SET, + PCH_UART_HAL_INVALID_BAUD, + PCH_UART_HAL_INVALID_PARITY, + PCH_UART_HAL_INVALID_WLS, + PCH_UART_HAL_INVALID_FIFO_CLR, + PCH_UART_HAL_INVALID_DMAMODE, + PCH_UART_HAL_INVALID_FIFOSIZE, + PCH_UART_HAL_INVALID_TRIGGER, + PCH_UART_HAL_INVALID_STB, + PCH_UART_HAL_READ_ERROR, +}; + +enum { + PCH_UART_8LINE, + PCH_UART_2LINE, +}; + +#if !defined(PORT_PCH_256FIFO) || !defined(PORT_PCH_64FIFO) + #undef PORT_PCH_256FIFO + #undef PORT_PCH_64FIFO + #define PORT_PCH_256FIFO (PORT_MAX_8250+1) /* PCH UART with 256 byte + FIFO */ + #define PORT_PCH_64FIFO (PORT_MAX_8250+2) /* PCH UART with 64 byte + FIFO */ +#endif + +#define PCH_UART_DRIVER_DEVICE "ttyPCH" + +#define PCH_UART_NR_GE_256FIFO 1 +#define PCH_UART_NR_GE_64FIFO 3 +#define PCH_UART_NR_GE (PCH_UART_NR_GE_256FIFO+PCH_UART_NR_GE_64FIFO) +#define PCH_UART_NR PCH_UART_NR_GE + +#define PCH_UART_HANDLED_RX_INT (1<<((PCH_UART_HANDLED_RX_INT_SHIFT)<<1)) +#define PCH_UART_HANDLED_TX_INT (1<<((PCH_UART_HANDLED_TX_INT_SHIFT)<<1)) +#define PCH_UART_HANDLED_RX_ERR_INT (1<<((\ + PCH_UART_HANDLED_RX_ERR_INT_SHIFT)<<1)) +#define PCH_UART_HANDLED_RX_TRG_INT (1<<((\ + PCH_UART_HANDLED_RX_TRG_INT_SHIFT)<<1)) +#define PCH_UART_HANDLED_MS_INT (1<<((PCH_UART_HANDLED_MS_INT_SHIFT)<<1)) + +#ifndef PCH_UART_DMA_REG_BOUNDARY +# define PCH_UART_DMA_REG_BOUNDARY 1 +#endif + +#if PCH_UART_DMA_REG_BOUNDARY == 1 +# define PCH_UART_REG_SHIFT 0 +#else +# define PCH_UART_REG_SHIFT 2 +#endif + +#define PCH_UART_RBR (0x00<trigger_level = 1; + priv->fcr = 0; +} + +static int __get_msr(struct eg20t_port *priv, void __iomem *base) +{ + unsigned int msr; + + msr = rr(base + PCH_UART_MSR); + priv->dmsr |= msr & PCH_UART_MSR_DELTA; + + return (int)msr; +} + +static int __pch_uart_hal_enable_interrupt(void __iomem *base, + unsigned int flag) +{ + unsigned int ier; + + ier = rr(base + PCH_UART_IER); + ier |= flag & PCH_UART_IER_MASK; + wr(base + PCH_UART_IER, ier); + + return 0; +} + +static int pch_uart_hal_enable_interrupt(struct eg20t_port *priv, + unsigned int flag) +{ + void __iomem *base; + int ret; + + base = priv->membase; + ret = __pch_uart_hal_enable_interrupt(base, flag); + + return ret; +} + +static int __pch_uart_hal_disable_interrupt(void __iomem *base, + unsigned int flag) +{ + unsigned int ier; + + ier = rr(base + PCH_UART_IER); + ier &= ~(flag & PCH_UART_IER_MASK); + wr(base + PCH_UART_IER, ier); + + return 0; +} + +static int pch_uart_hal_disable_interrupt(struct eg20t_port *priv, + unsigned int flag) +{ + void __iomem *base; + int ret; + + base = priv->membase; + ret = __pch_uart_hal_disable_interrupt(base, flag); + return ret; +} + +static int pch_uart_hal_set_line(struct eg20t_port *priv, int baud, + unsigned int parity, unsigned int bits, + unsigned int stb) +{ + unsigned int dll, dlm, lcr; + void __iomem *base; + int div; + + div = DIV_ROUND(priv->base_baud / 16, baud); + if (div < 0 || USHRT_MAX <= div) + return -PCH_UART_HAL_INVALID_BAUD; + + dll = (unsigned int)div & 0x00FFU; + dlm = ((unsigned int)div >> 8) & 0x00FFU; + + if (parity & ~(PCH_UART_LCR_PEN | PCH_UART_LCR_EPS | PCH_UART_LCR_SP)) + return -PCH_UART_HAL_INVALID_PARITY; + + if (bits & ~PCH_UART_LCR_WLS) + return -PCH_UART_HAL_INVALID_WLS; + + if (stb & ~PCH_UART_LCR_STB) + return -PCH_UART_HAL_INVALID_STB; + + lcr = parity; + lcr |= bits; + lcr |= stb; + + base = priv->membase; + pr_debug("%s:baud = %d, div = %04x, lcr = %02x (%lu)\n", + __func__, baud, div, lcr, jiffies); + wr(base + PCH_UART_LCR, PCH_UART_LCR_DLAB); + wr(base + PCH_UART_DLL, dll); + wr(base + PCH_UART_DLM, dlm); + wr(base + PCH_UART_LCR, lcr); + + return 0; +} + +static int __pch_uart_hal_fifo_reset(struct eg20t_port *priv, + void __iomem *base, + unsigned int flag) +{ + unsigned int fcr; + if (flag & ~(PCH_UART_FCR_TFR | PCH_UART_FCR_RFR)) + return -PCH_UART_HAL_INVALID_FIFO_CLR; + + fcr = priv->fcr; + wr(base + PCH_UART_FCR, PCH_UART_FCR_FIFOE | fcr); + wr(base + PCH_UART_FCR, PCH_UART_FCR_FIFOE | fcr | flag); + wr(base + PCH_UART_FCR, fcr); + + return 0; +} + +static int pch_uart_hal_fifo_reset(struct eg20t_port *priv, + unsigned int flag) +{ + void __iomem *base; + int ret; + + base = priv->membase; + ret = __pch_uart_hal_fifo_reset(priv, base, flag); + return ret; +} + +static int pch_uart_hal_set_fifo(struct eg20t_port *priv, + unsigned int dmamode, + unsigned int fifo_size, unsigned int trigger) +{ + void __iomem *base; + unsigned int fcr; + + if (dmamode & ~PCH_UART_FCR_DMS) + return -PCH_UART_HAL_INVALID_DMAMODE; + + if (fifo_size & ~(PCH_UART_FCR_FIFOE | PCH_UART_FCR_FIFO256)) + return -PCH_UART_HAL_INVALID_FIFOSIZE; + + if (trigger & ~PCH_UART_FCR_RFTL) + return -PCH_UART_HAL_INVALID_TRIGGER; + + switch (priv->fifo_size) { + case 256: + priv->trigger_level = + trigger_level_256[trigger >> PCH_UART_FCR_RFTL_SHIFT]; + break; + case 64: + priv->trigger_level = + trigger_level_64[trigger >> PCH_UART_FCR_RFTL_SHIFT]; + break; + case 16: + priv->trigger_level = + trigger_level_16[trigger >> PCH_UART_FCR_RFTL_SHIFT]; + break; + default: + priv->trigger_level = + trigger_level_1[trigger >> PCH_UART_FCR_RFTL_SHIFT]; + break; + } + base = priv->membase; + fcr = + dmamode | fifo_size | trigger | PCH_UART_FCR_RFR | PCH_UART_FCR_TFR; + wr(base + PCH_UART_FCR, PCH_UART_FCR_FIFOE); + wr(base + PCH_UART_FCR, + PCH_UART_FCR_FIFOE | PCH_UART_FCR_RFR | PCH_UART_FCR_TFR); + wr(base + PCH_UART_FCR, fcr); + priv->fcr = fcr; + + return 0; +} + +static int pch_uart_hal_get_modem(struct eg20t_port *priv, + unsigned int *modem) +{ + void __iomem *base; + unsigned int msr; + + base = priv->membase; + msr = __get_msr(priv, base); + priv->dmsr = 0; + *modem = msr; + + return 0; +} + +static int pch_uart_hal_write(struct eg20t_port *priv, + const unsigned char *buf, int tx_size) +{ + void __iomem *base; + int i; + unsigned int thr; + + base = priv->membase; + for (i = 0; i < tx_size;) { + thr = buf[i++]; + wr(base + PCH_UART_THR, thr); + } + return i; +} + +static int pch_uart_hal_read(struct eg20t_port *priv, unsigned char *buf, + int rx_size) +{ + void __iomem *base; + int i; + unsigned int rbr, lsr; + + base = priv->membase; + lsr = rr(base + PCH_UART_LSR); + for (i = 0, lsr = rr(base + PCH_UART_LSR); + i < rx_size && lsr & PCH_UART_LSR_DR; + lsr = rr(base + PCH_UART_LSR)) { + rbr = rr(base + PCH_UART_RBR); + buf[i++] = (unsigned char)rbr; + } + return i; +} + +static int pch_uart_hal_get_iid(struct eg20t_port *priv) +{ + void __iomem *base; + unsigned int iir; + int ret; + + base = priv->membase; + iir = rr(base + PCH_UART_IIR); + ret = + (int)(iir & + (PCH_UART_IIR_IID | PCH_UART_IIR_TOI | PCH_UART_IIR_IP)); + return ret; +} + +static int pch_uart_hal_get_line_status(struct eg20t_port *priv) +{ + void __iomem *base; + unsigned int lsr; + int ret; + + base = priv->membase; + lsr = rr(base + PCH_UART_LSR); + ret = (int)lsr; + return ret; +} + +static void pch_uart_hal_set_break(struct eg20t_port *priv, int on) +{ + void __iomem *base; + unsigned int lcr; + + base = priv->membase; + lcr = rr(base + PCH_UART_LCR); + if (on) + lcr |= PCH_UART_LCR_SB; + else + lcr &= ~PCH_UART_LCR_SB; + + wr(base + PCH_UART_LCR, lcr); +} + +static int push_rx(struct eg20t_port *priv, const unsigned char *buf, + int size) +{ + struct uart_port *port; + struct tty_struct *tty; + int sz, i, j; + int loop; + int pushed; + + port = &priv->port; + tty = port->state->port.tty; + for (pushed = 0, i = 0, loop = 1; (pushed < size) && loop; + pushed += sz, i++) { + sz = tty_insert_flip_string(tty, &buf[pushed], size - pushed); + for (j = 0; (j < 100000) && (sz == 0); j++) { + tty_flip_buffer_push(tty); + sz = tty_insert_flip_string(tty, &buf[pushed], + size - pushed); + } + if (sz == 0) + loop = 0; + } + tty_flip_buffer_push(tty); + + pr_debug("%s:%d characters. Remained %d characters. (%lu)\n", __func__, + pushed, size - pushed, jiffies); + + return 0; +} + +static int pop_tx(struct eg20t_port *priv, unsigned char *buf, int size) +{ + int count = 0; + struct uart_port *port = &priv->port; + struct circ_buf *xmit = &port->state->xmit; + + if (uart_tx_stopped(port) || uart_circ_empty(xmit) || count >= size) + goto pop_tx_end; + + do { + int cnt_to_end = + CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE); + int sz = min(size - count, cnt_to_end); + memcpy(&buf[count], &xmit->buf[xmit->tail], sz); + xmit->tail = (xmit->tail + sz) & (UART_XMIT_SIZE - 1); + count += sz; + } while (!uart_circ_empty(xmit) && count < size); + +pop_tx_end: + pr_debug("%d characters. Remained %d characters. (%lu)\n", + count, size - count, jiffies); + + return count; +} + +static int pop_tx_x(struct eg20t_port *priv, unsigned char *buf) +{ + int ret; + struct uart_port *port = &priv->port; + + if (port->x_char) { + pr_debug("%s:X character send %02x (%lu)\n", __func__, + port->x_char, jiffies); + buf[0] = port->x_char; + port->x_char = 0; + ret = 1; + } else { + ret = 0; + } + + return ret; +} + +static int handle_rx_to(struct eg20t_port *priv) +{ + struct pch_uart_buffer *buf; + int rx_size; + int ret; + + if (!priv->start_rx) { + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_RX_INT); + return 0; + } + buf = &priv->rxbuf; + do { + rx_size = + pch_uart_hal_read(priv, buf->buf, buf->size); + ret = push_rx(priv, buf->buf, rx_size); + } while (rx_size == buf->size); + + return PCH_UART_HANDLED_RX_INT; +} + +static int handle_rx_err(struct eg20t_port *priv) +{ + unsigned int lsr; + int ret; + + ret = pch_uart_hal_get_line_status(priv); + if (ret >= 0) { + lsr = (unsigned int)ret; + ret = PCH_UART_HANDLED_RX_ERR_INT; + } + return ret; +} + +static int handle_error(struct eg20t_port *priv, int err) +{ + int ret = 0; + return ret; +} + +#ifndef CONFIG_PCH_DMA +static int handle_rx(struct eg20t_port *priv) +{ + return handle_rx_to(priv); +} + +static unsigned int handle_tx(struct eg20t_port *priv) +{ + struct pch_uart_buffer *buf; + struct uart_port *port = &priv->port; + int ret; + int fifo_size; + int tx_size; + int size; + int tx_empty; + + if (!priv->start_tx) { + pr_info("%s:Tx isn't started. (%lu)\n", __func__, jiffies); + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_TX_INT); + priv->tx_empty = 1; + return 0; + } + + buf = &priv->txbuf; + fifo_size = max(priv->fifo_size, 1); + tx_empty = 1; + if (pop_tx_x(priv, buf->buf)) { + pch_uart_hal_write(priv, buf->buf, 1); + port->icount.tx++; + tx_empty = 0; + fifo_size--; + } + size = min(buf->size, fifo_size); + tx_size = pop_tx(priv, buf->buf, size); + if (tx_size > 0) { + ret = pch_uart_hal_write(priv, buf->buf, tx_size); + port->icount.tx += ret; + tx_empty = 0; + } + + priv->tx_empty = tx_empty; + + if (tx_empty) + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_TX_INT); + + return PCH_UART_HANDLED_TX_INT; +} + +#else +static int dma_push_rx(struct eg20t_port *priv, int size) +{ + struct tty_struct *tty; + int i; + int room; + struct uart_port *port = &priv->port; + port = &priv->port; + tty = port->state->port.tty; + + room = tty_buffer_request_room(tty, size); + + if (room < size) + dev_warn(port->dev, "Rx overrun: dropping %u bytes\n", + size - room); + if (!room) + return room; + + for (i = 0; i < room; i++) { + tty_insert_flip_char(tty, ((u8 *)sg_virt(&priv->sg_rx))[i], + TTY_NORMAL); + } + + port->icount.rx += room; + + return room; +} + +static void pch_dma_rx_complete(void *arg) +{ + struct eg20t_port *priv = arg; + struct uart_port *port = &priv->port; + struct tty_struct *tty = port->state->port.tty; + int count; + + count = dma_push_rx(priv, priv->trigger_level); + if (count) + tty_flip_buffer_push(tty); +} + +static int dma_handle_rx(struct eg20t_port *priv) +{ + struct uart_port *port = &priv->port; + struct dma_async_tx_descriptor *desc; + struct scatterlist *sg; + + priv = container_of(port, struct eg20t_port, port); + sg = &priv->sg_rx; + + sg_init_table(&priv->sg_rx, 1); /* Initialize SG table */ + + sg_dma_len(sg) = priv->fifo_size; + + sg_set_page(&priv->sg_rx, virt_to_page(priv->rxbuf.buf), + sg_dma_len(sg), (int)priv->rxbuf.buf & ~PAGE_MASK); + + sg_dma_address(sg) = (dma_addr_t)virt_to_page(priv->rxbuf.buf); + + desc = priv->chan_rx->device->device_prep_slave_sg(priv->chan_rx, + sg, 1, DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT); + if (!desc) + return 0; + + priv->desc_rx = desc; + desc->callback = pch_dma_rx_complete; + desc->callback_param = priv; + desc->tx_submit(desc); + dma_async_issue_pending(priv->chan_rx); + + return PCH_UART_HANDLED_RX_INT; +} + +static void pch_dma_tx_complete(void *arg) +{ + struct eg20t_port *priv = arg; + struct uart_port *port = &priv->port; + struct circ_buf *xmit = &port->state->xmit; + + xmit->tail += sg_dma_len(&priv->sg_tx); + xmit->tail &= UART_XMIT_SIZE - 1; + port->icount.tx += sg_dma_len(&priv->sg_tx); + + async_tx_ack(priv->desc_tx); + priv->tx_dma_use = 0; +} + +static int handle_tx_dma(struct eg20t_port *priv) +{ + struct uart_port *port = &priv->port; + struct circ_buf *xmit = &port->state->xmit; + struct scatterlist *sg = &priv->sg_tx; + int nent; + int fifo_size; + int tx_size; + int size; + int tx_empty; + struct dma_async_tx_descriptor *desc; + + if (!priv->start_tx) { + pr_info("%s:Tx isn't started. (%lu)\n", __func__, jiffies); + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_TX_INT); + priv->tx_empty = 1; + return 0; + } + + fifo_size = max(priv->fifo_size, 1); + tx_empty = 1; + if (pop_tx_x(priv, xmit->buf)) { + pch_uart_hal_write(priv, xmit->buf, 1); + port->icount.tx++; + tx_empty = 0; + fifo_size--; + } + size = min(xmit->tail - xmit->head, fifo_size); + + tx_size = pop_tx(priv, xmit->buf, size); + + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_TX_INT); + + priv->tx_dma_use = 1; + + sg_init_table(&priv->sg_tx, 1); /* Initialize SG table */ + + sg_set_page(&priv->sg_tx, virt_to_page(xmit->buf), + UART_XMIT_SIZE, (int)xmit->buf & ~PAGE_MASK); + + nent = dma_map_sg(port->dev, &priv->sg_tx, 1, DMA_TO_DEVICE); + if (!nent) { + pr_err("%s:dma_map_sg Failed\n", __func__); + return 0; + } + + sg->offset = xmit->tail & (UART_XMIT_SIZE - 1); + sg_dma_address(sg) = (sg_dma_address(sg) & ~(UART_XMIT_SIZE - 1)) + + sg->offset; + sg_dma_len(sg) = min((int)CIRC_CNT(xmit->head, xmit->tail, + UART_XMIT_SIZE), CIRC_CNT_TO_END(xmit->head, + xmit->tail, UART_XMIT_SIZE)); + + desc = priv->chan_tx->device->device_prep_slave_sg(priv->chan_tx, + sg, nent, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + pr_err("%s:device_prep_slave_sg Failed\n", __func__); + return 0; + } + + dma_sync_sg_for_device(port->dev, sg, 1, DMA_TO_DEVICE); + + priv->desc_tx = desc; + desc->callback = pch_dma_tx_complete; + desc->callback_param = priv; + + desc->tx_submit(desc); + + dma_async_issue_pending(priv->chan_tx); + + return PCH_UART_HANDLED_TX_INT; +} + +static void pch_free_dma(struct uart_port *port) +{ + struct eg20t_port *priv; + priv = container_of(port, struct eg20t_port, port); + + if (priv->chan_tx) { + dma_release_channel(priv->chan_tx); + priv->chan_tx = NULL; + } + if (priv->chan_rx) { + dma_release_channel(priv->chan_rx); + priv->chan_rx = NULL; + } + return; +} + +static bool filter(struct dma_chan *chan, void *slave) +{ + struct pch_dma_slave *param = slave; + + if ((chan->chan_id == param->chan_id) && (param->dma_dev == + chan->device->dev)) { + chan->private = param; + return true; + } else { + return false; + } +} + +static void pch_request_dma(struct uart_port *port) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + struct pci_dev *dma_dev; + struct pch_dma_slave *param; + struct eg20t_port *priv = + container_of(port, struct eg20t_port, port); + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + dma_dev = pci_get_bus_and_slot(2, PCI_DEVFN(0xa, 0)); /* Get DMA's dev + information */ + + /* Set Tx DMA */ + param = &priv->param_tx; + param->dma_dev = &dma_dev->dev; + param->chan_id = priv->port.line; + param->tx_reg = port->mapbase + UART_TX; + chan = dma_request_channel(mask, filter, param); + if (!chan) { + pr_err("%s:dma_request_channel FAILS(Tx)\n", __func__); + return; + } + priv->chan_tx = chan; + + /* Set Rx DMA */ + param = &priv->param_rx; + param->dma_dev = &dma_dev->dev; + param->chan_id = priv->port.line + 1; /* Rx = Tx + 1 */ + param->rx_reg = port->mapbase + UART_RX; + chan = dma_request_channel(mask, filter, param); + if (!chan) { + pr_err("%s:dma_request_channel FAILS(Rx)\n", __func__); + dma_release_channel(priv->chan_tx); + return; + } + priv->chan_rx = chan; +} +#endif + +static irqreturn_t pch_uart_interrupt(int irq, void *dev_id) +{ + struct eg20t_port *priv = dev_id; + unsigned int handled; + int ret; + int iid; + spin_lock(&priv->port.lock); + handled = 0; + while ((iid = pch_uart_hal_get_iid(priv)) > 1) { + switch (iid) { + case PCH_UART_IID_RLS: /* Receiver Line Status */ + ret = handle_rx_err(priv); + break; + case PCH_UART_IID_RDR: /* Received Data Ready */ +#ifdef CONFIG_PCH_DMA + ret = dma_handle_rx(priv); +#else + ret = handle_rx(priv); +#endif + break; + case PCH_UART_IID_RDR_TO: /* Received Data Ready + (FIFO Timeout) */ + ret = handle_rx_to(priv); + break; + case PCH_UART_IID_THRE: /* Transmitter Holding Register + Empty */ +#ifdef CONFIG_PCH_DMA + ret = handle_tx_dma(priv); +#else + ret = handle_tx(priv); +#endif + break; + case PCH_UART_IID_MS: /* Modem Status */ + ret = PCH_UART_HANDLED_MS_INT; + break; + default: /* Never junp to this label */ + pr_err("%s:iid=%d (%lu)\n", __func__, iid, jiffies); + ret = -1; + break; + } + if (ret < 0) { + handle_error(priv, ret); + handled = 1; + goto interrupt_end; + } else { + handled |= (unsigned int)ret; + } + } + if (handled == 0 && iid <= 1) { + if (priv->int_dis_flag) + priv->int_dis_flag = 0; + } +interrupt_end: + spin_unlock(&priv->port.lock); + return IRQ_RETVAL(handled); +} + +/* This function tests whether the transmitter fifo and shifter for the port + described by 'port' is empty. */ +static unsigned int pch_uart_tx_empty(struct uart_port *port) +{ + struct eg20t_port *priv; + int ret; + priv = container_of(port, struct eg20t_port, port); + if (priv->tx_empty) + ret = TIOCSER_TEMT; + else + ret = 0; + + return ret; +} + +static void pch_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +/* Returns the current state of modem control inputs. */ +static unsigned int pch_uart_get_mctrl(struct uart_port *port) +{ + struct eg20t_port *priv; + unsigned int modem; + unsigned int ret; + priv = container_of(port, struct eg20t_port, port); + pch_uart_hal_get_modem(priv, &modem); + + ret = 0; + if (modem & UART_MSR_DCD) + ret |= TIOCM_CAR; + + if (modem & UART_MSR_RI) + ret |= TIOCM_RNG; + + if (modem & UART_MSR_DSR) + ret |= TIOCM_DSR; + + if (modem & UART_MSR_CTS) + ret |= TIOCM_CTS; + + return ret; +} + +static void pch_uart_stop_tx(struct uart_port *port) +{ + struct eg20t_port *priv; + priv = container_of(port, struct eg20t_port, port); + priv->start_tx = 0; +#ifdef CONFIG_PCH_DMA + priv->tx_dma_use = 0; +#endif +} + +static void pch_uart_start_tx(struct uart_port *port) +{ + struct eg20t_port *priv; + + priv = container_of(port, struct eg20t_port, port); +#ifdef CONFIG_PCH_DMA + if (priv->tx_dma_use) + return; +#endif + + priv->start_tx = 1; + pch_uart_hal_enable_interrupt(priv, PCH_UART_HAL_TX_INT); +} + +static void pch_uart_send_xchar(struct uart_port *port, char ch) +{ +} + +static void pch_uart_stop_rx(struct uart_port *port) +{ + struct eg20t_port *priv; + priv = container_of(port, struct eg20t_port, port); + priv->start_rx = 0; + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_RX_INT); + priv->int_dis_flag = 1; +} + +/* Enable the modem status interrupts. */ +static void pch_uart_enable_ms(struct uart_port *port) +{ + struct eg20t_port *priv; + priv = container_of(port, struct eg20t_port, port); + pch_uart_hal_enable_interrupt(priv, PCH_UART_HAL_MS_INT); +} + +/* Control the transmission of a break signal. */ +static void pch_uart_break_ctl(struct uart_port *port, int ctl) +{ + struct eg20t_port *priv; + unsigned long flags; + + priv = container_of(port, struct eg20t_port, port); + spin_lock_irqsave(&port->lock, flags); + pch_uart_hal_set_break(priv, ctl); + spin_unlock_irqrestore(&port->lock, flags); +} + +/* Grab any interrupt resources and initialise any low level driver state. */ +static int pch_uart_startup(struct uart_port *port) +{ + struct eg20t_port *priv; + int ret; + int fifo_size, trigger; + int trigger_level; + + priv = container_of(port, struct eg20t_port, port); + priv->tx_empty = 1; + port->uartclk = priv->base_baud; + ret = pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_ALL_INT); + ret = pch_uart_hal_set_line(priv, default_baud, + PCH_UART_HAL_PARITY_NONE, PCH_UART_HAL_8BIT, + PCH_UART_HAL_STB1); + + if (ret < 0) + return ret; + + switch (priv->fifo_size) { + case 256: + fifo_size = PCH_UART_HAL_FIFO256; + break; + case 64: + fifo_size = PCH_UART_HAL_FIFO64; + break; + case 16: + fifo_size = PCH_UART_HAL_FIFO16; + case 1: + default: + fifo_size = PCH_UART_HAL_FIFO_DIS; + break; + } + + trigger = pch_uart_trigger[priv->port_type]; + + switch (trigger) { + case PCH_UART_HAL_TRIGGER1: + trigger_level = 1; + break; + case PCH_UART_HAL_TRIGGER_L: + trigger_level = priv->fifo_size / 4; + break; + case PCH_UART_HAL_TRIGGER_M: + trigger_level = priv->fifo_size / 2; + break; + case PCH_UART_HAL_TRIGGER_H: + default: + trigger_level = priv->fifo_size - (priv->fifo_size / 8); + break; + } + + priv->trigger_level = trigger_level; + ret = pch_uart_hal_set_fifo(priv, PCH_UART_HAL_DMA_MODE0, + fifo_size, trigger); + + ret = request_irq(priv->port.irq, pch_uart_interrupt, IRQF_SHARED, + KBUILD_MODNAME, priv); + + if (ret < 0) + return ret; + +#ifdef CONFIG_PCH_DMA + pch_request_dma(port); +#endif + priv->start_rx = 1; + ret = pch_uart_hal_enable_interrupt(priv, PCH_UART_HAL_RX_INT); + uart_update_timeout(port, CS8, default_baud); + + return 0; +} + +static void pch_uart_shutdown(struct uart_port *port) +{ + struct eg20t_port *priv; + priv = container_of(port, struct eg20t_port, port); + pch_uart_hal_disable_interrupt(priv, PCH_UART_HAL_ALL_INT); + pch_uart_hal_fifo_reset(priv, PCH_UART_HAL_CLR_ALL_FIFO); + pch_uart_hal_set_fifo(priv, PCH_UART_HAL_DMA_MODE0, + PCH_UART_HAL_FIFO_DIS, PCH_UART_HAL_TRIGGER1); + +#ifdef CONFIG_PCH_DMA + pch_free_dma(port); +#endif + + free_irq(priv->port.irq, priv); +} + +/* Change the port parameters, including word length, parity, stop + *bits. Update read_status_mask and ignore_status_mask to indicate + *the types of events we are interested in receiving. */ +static void pch_uart_set_termios(struct uart_port *port, + struct ktermios *termios, struct ktermios *old) +{ + int baud; + unsigned int parity, bits, stb; + struct eg20t_port *priv; + unsigned long flags; + + priv = container_of(port, struct eg20t_port, port); + switch (termios->c_cflag & CSIZE) { + case CS5: + bits = PCH_UART_HAL_5BIT; + break; + case CS6: + bits = PCH_UART_HAL_6BIT; + break; + case CS7: + bits = PCH_UART_HAL_7BIT; + break; + default: /* CS8 */ + bits = PCH_UART_HAL_8BIT; + break; + } + if (termios->c_cflag & CSTOPB) + stb = PCH_UART_HAL_STB2; + else + stb = PCH_UART_HAL_STB1; + + if (termios->c_cflag & PARENB) { + if (!(termios->c_cflag & PARODD)) + parity = PCH_UART_HAL_PARITY_ODD; + else + parity = PCH_UART_HAL_PARITY_EVEN; + + } else { + parity = PCH_UART_HAL_PARITY_NONE; + } + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16); + + spin_lock_irqsave(&port->lock, flags); + + uart_update_timeout(port, termios->c_cflag, baud); + pch_uart_hal_set_line(priv, baud, parity, bits, stb); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *pch_uart_type(struct uart_port *port) +{ + return KBUILD_MODNAME; +} + +static void pch_uart_release_port(struct uart_port *port) +{ + struct eg20t_port *priv; + + priv = container_of(port, struct eg20t_port, port); + pci_iounmap(priv->pdev, priv->membase); + pci_release_regions(priv->pdev); +} + +static int pch_uart_request_port(struct uart_port *port) +{ + struct eg20t_port *priv; + int ret; + void __iomem *membase; + + priv = container_of(port, struct eg20t_port, port); + ret = pci_request_regions(priv->pdev, KBUILD_MODNAME); + if (ret < 0) + return -EBUSY; + + membase = pci_iomap(priv->pdev, 1, 0); + if (!membase) { + pci_release_regions(priv->pdev); + return -EBUSY; + } + priv->membase = port->membase = membase; + + return 0; +} + +static void pch_uart_config_port(struct uart_port *port, int type) +{ + struct eg20t_port *priv; + + priv = container_of(port, struct eg20t_port, port); + if (type & UART_CONFIG_TYPE) { + port->type = pch_uart_port_type[priv->port_type]; + pch_uart_request_port(port); + } +} + +static int pch_uart_verify_port(struct uart_port *port, + struct serial_struct *serinfo) +{ + return 0; +} + +static struct uart_ops pch_uart_ops = { + .tx_empty = pch_uart_tx_empty, + .set_mctrl = pch_uart_set_mctrl, + .get_mctrl = pch_uart_get_mctrl, + .stop_tx = pch_uart_stop_tx, + .start_tx = pch_uart_start_tx, + .send_xchar = pch_uart_send_xchar, + .stop_rx = pch_uart_stop_rx, + .enable_ms = pch_uart_enable_ms, + .break_ctl = pch_uart_break_ctl, + .startup = pch_uart_startup, + .shutdown = pch_uart_shutdown, + .set_termios = pch_uart_set_termios, +/* .pm = pch_uart_pm, Not supported yet */ +/* .set_wake = pch_uart_set_wake, Not supported yet */ + .type = pch_uart_type, + .release_port = pch_uart_release_port, + .request_port = pch_uart_request_port, + .config_port = pch_uart_config_port, + .verify_port = pch_uart_verify_port +}; + +static struct uart_driver pch_uart_driver = { + .owner = THIS_MODULE, + .driver_name = KBUILD_MODNAME, + .dev_name = PCH_UART_DRIVER_DEVICE, + .major = 0, + .minor = 0, + .nr = PCH_UART_NR, +}; + +static struct eg20t_port *pch_uart_init_port(struct pci_dev *pdev, + int port_type) +{ + struct eg20t_port *priv; + int ret; + unsigned int iobase; + unsigned int mapbase; + unsigned int txbuf, rxbuf; + int fifosize, base_baud; + static int num; + priv = kzalloc(sizeof(struct eg20t_port), GFP_KERNEL); + if (priv == NULL) + goto init_port_alloc_err; + + txbuf = __get_free_page(GFP_KERNEL|GFP_DMA); + if (!txbuf) + goto init_port_error_end; + + rxbuf = __get_free_page(GFP_KERNEL|GFP_DMA); + if (!rxbuf) + goto init_port_free_txbuf; + + fifosize = pch_uart_fifosize[port_type]; + base_baud = pch_uart_base_baud[port_type]; + + iobase = pci_resource_start(pdev, 0); + mapbase = pci_resource_start(pdev, 1); + priv->mapbase = mapbase; + priv->iobase = iobase; + priv->pdev = pdev; + priv->tx_empty = 1; + priv->txbuf.buf = (unsigned char *)txbuf; + priv->txbuf.size = PAGE_SIZE; + priv->rxbuf.buf = (unsigned char *)rxbuf; + priv->rxbuf.size = PAGE_SIZE; + + priv->fifo_size = fifosize; + priv->base_baud = base_baud; + priv->port_type = port_type; + priv->port.dev = &pdev->dev; + priv->port.iobase = iobase; + priv->port.membase = NULL; + priv->port.mapbase = mapbase; + priv->port.irq = pdev->irq; + priv->port.iotype = UPIO_PORT; + priv->port.ops = &pch_uart_ops; + priv->port.flags = UPF_BOOT_AUTOCONF; + priv->port.fifosize = fifosize; + priv->port.line = num++; + + pci_set_drvdata(pdev, priv); + pch_uart_hal_request(pdev, fifosize, base_baud); + ret = uart_add_one_port(&pch_uart_driver, &priv->port); + if (ret < 0) + goto init_port_hal_free; + + return priv; + +init_port_hal_free: + free_page(rxbuf); +init_port_free_txbuf: + free_page(txbuf); +init_port_error_end: + kfree(priv); +init_port_alloc_err: + + return NULL; +} + +static void pch_uart_exit_port(struct eg20t_port *priv) +{ + unsigned int rxbuf; + + rxbuf = (unsigned int)priv->rxbuf.buf; + uart_remove_one_port(&pch_uart_driver, &priv->port); + pci_set_drvdata(priv->pdev, NULL); + free_page(rxbuf); +} + +static void pch_uart_pci_remove(struct pci_dev *pdev) +{ + struct eg20t_port *priv; + + priv = (struct eg20t_port *)pci_get_drvdata(pdev); + pch_uart_exit_port(priv); + pci_disable_device(pdev); + kfree(priv); + return; +} +#ifdef CONFIG_PM +static int pch_uart_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct eg20t_port *priv = pci_get_drvdata(pdev); + + uart_suspend_port(&pch_uart_driver, &priv->port); + + pci_save_state(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +static int pch_uart_pci_resume(struct pci_dev *pdev) +{ + struct eg20t_port *priv = pci_get_drvdata(pdev); + int ret; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, + "%s-pci_enable_device failed(ret=%d) ", __func__, ret); + return ret; + } + + uart_resume_port(&pch_uart_driver, &priv->port); + + return 0; +} +#else +#define pch_uart_pci_suspend NULL +#define pch_uart_pci_resume NULL +#endif + +static DEFINE_PCI_DEVICE_TABLE(pch_uart_pci_id) = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8811), + .driver_data = PCH_UART_8LINE}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8812), + .driver_data = PCH_UART_2LINE}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8813), + .driver_data = PCH_UART_2LINE}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8814), + .driver_data = PCH_UART_2LINE}, + {0,}, +}; + +static int __devinit pch_uart_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret; + struct eg20t_port *priv; + + ret = pci_enable_device(pdev); + if (ret < 0) + goto probe_error; + + priv = pch_uart_init_port(pdev, id->driver_data); + if (!priv) { + ret = -EBUSY; + goto probe_disable_device; + } + pci_set_drvdata(pdev, priv); + + return ret; + +probe_disable_device: + pci_disable_device(pdev); +probe_error: + return ret; +} + +static struct pci_driver pch_uart_pci_driver = { + .name = "pch_uart", + .id_table = pch_uart_pci_id, + .probe = pch_uart_pci_probe, + .remove = __devexit_p(pch_uart_pci_remove), + .suspend = pch_uart_pci_suspend, + .resume = pch_uart_pci_resume, +}; + +static int __init pch_uart_module_init(void) +{ + int ret; + + /* register as UART driver */ + ret = uart_register_driver(&pch_uart_driver); + if (ret < 0) + return ret; + + /* register as PCI driver */ + ret = pci_register_driver(&pch_uart_pci_driver); + if (ret < 0) + uart_unregister_driver(&pch_uart_driver); + + return ret; +} +module_init(pch_uart_module_init); + +static void __exit pch_uart_module_exit(void) +{ + pci_unregister_driver(&pch_uart_pci_driver); + uart_unregister_driver(&pch_uart_driver); +} +module_exit(pch_uart_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel EG20T PCH UART PCI Driver"); +module_param(default_baud, uint, S_IRUGO); -- 1.6.0.6 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/