Medfield HSU serial
cover Medfield HSU serial
With the header file escape fixed (sorry about that)
---
Feng Tang (4):
hsu: call PCI pm hooks in suspend/resume function
hsu: some code cleanup
hsu: add a periodic timer to check dma rx channel
hsu: driver for Medfield High Speed UART device
drivers/serial/Kconfig | 10 drivers/serial/Makefile | 1 drivers/serial/mfd.c | 1498 +++++++++++++++++++++++++++++++++++++++++++ include/linux/serial_core.h | 2 include/linux/serial_mfd.h | 47 + include/linux/serial_reg.h | 16 6 files changed, 1574 insertions(+), 0 deletions(-) create mode 100644 drivers/serial/mfd.c create mode 100644 include/linux/serial_mfd.h
PATCH 1/4 hsu: driver for Medfield High Speed UART device
From: Feng Tang <[email protected]>
This is a PCI & UART driver, which suppors both PIO and DMA mode UART operation. It has 3 identical UART ports and one internal DMA controller.
Current FW will export 4 pci devices for hsu: 3 uart ports and 1 dma controller, each has one IRQ line. And we need to discuss the device model, one PCI device covering whole HSU should be a better model, but there is a problem of how to export the 4 IRQs info
Current driver set the highest baud rate to 2746800bps, which is easy to scale down to 115200/230400.... To suport higher baud rate, we need add special process, change DLAB/DLH/PS/DIV/MUL registers all together.
921600 is the highest baud rate that has been tested with Bluetooth modem connected to HSU port 0. Will test more when there is right BT firmware.
Current version contains several work around for A0's Silicon bugs
---
drivers/serial/Kconfig | 10 drivers/serial/Makefile | 1 drivers/serial/mfd.c | 1488 +++++++++++++++++++++++++++++++++++++++++++ include/linux/serial_core.h | 2 include/linux/serial_mfd.h | 47 + include/linux/serial_reg.h | 16 6 files changed, 1564 insertions(+), 0 deletions(-) create mode 100644 drivers/serial/mfd.c create mode 100644 include/linux/serial_mfd.h
drivers/serial/Kconfig +10 -0
@@ -724,6 +724,16 @@ config MRST_MAX3110_IRQ help This has to be enabled after Moorestown GPIO driver is loaded +config SERIAL_MFD_HSU + tristate "Medfield High Speed UART support" + depends on PCI + select SERIAL_CORE + +config SERIAL_MFD_HSU_CONSOLE + boolean "Medfile HSU serial console support" + depends on SERIAL_MFD_HSU=y + select SERIAL_CORE_CONSOLE + config SERIAL_BFIN tristate "Blackfin serial port support" depends on BLACKFIN
drivers/serial/Makefile +1 -0
@@ -87,3 +87,4 @@ obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o obj-$(CONFIG_SERIAL_MRST_MAX3110) += mrst_max3110.o +obj-$(CONFIG_SERIAL_MFD_HSU) += mfd.o
drivers/serial/mfd.c new file +1488 -0
1,488 lines not on this page · raw message
include/linux/serial_core.h +2 -0
@@ -192,6 +192,8 @@/* MAX3107 */ #define PORT_MAX3107 94 +/* High Speed UART for Medfield */ +#define PORT_MFD 95#ifdef __KERNEL__
include/linux/serial_mfd.h new file +47 -0
47 lines not on this page · raw message
include/linux/serial_reg.h +16 -0
@@ -221,8 +221,24 @@#define UART_FCR_PXAR16 0x80/* receive FIFO threshold = 16 */ #define UART_FCR_PXAR32 0xc0/* receive FIFO threshold = 32 */ +/* +* Intel MID on-chip HSU (High Speed UART) defined bits +*/ +#define UART_FCR_HSU_64_1B 0x00/* receive FIFO treshold = 1 */ +#define UART_FCR_HSU_64_16B 0x40/* receive FIFO treshold = 16 */ +#define UART_FCR_HSU_64_32B 0x80/* receive FIFO treshold = 32 */ +#define UART_FCR_HSU_64_56B 0xc0/* receive FIFO treshold = 56 */ + +#define UART_FCR_HSU_16_1B 0x00/* receive FIFO treshold = 1 */ +#define UART_FCR_HSU_16_4B 0x40/* receive FIFO treshold = 4 */ +#define UART_FCR_HSU_16_8B 0x80/* receive FIFO treshold = 8 */ +#define UART_FCR_HSU_16_14B 0xc0/* receive FIFO treshold = 14 */ +#define UART_FCR_HSU_64B_FIFO 0x20/* chose 64 bytes FIFO */ +#define UART_FCR_HSU_16B_FIFO 0x00/* chose 16 bytes FIFO */ +#define UART_FCR_HALF_EMPT_TXI 0x00/* trigger TX_EMPT IRQ for half empty */ +#define UART_FCR_FULL_EMPT_TXI 0x08/* trigger TX_EMPT IRQ for full empty */ /* * These register definitions are for the 16C950
PATCH 2/4 hsu: add a periodic timer to check dma rx channel
In reply to Alan CoxFrom: Feng Tang <[email protected]>
A general problem for uart rx dma channel is you never know when and how much data will be received, so usually preset it a DMA descriptor with a big size, and rely on DMA RX timeout IRQ to know there is some data in rx channel.
For a RX data size of multiple of MOTSR, there will be no timeout IRQ issued, thus OS will never be notified about that.
This is a work around for that, current timer frequency is 5 times per second, it should vary according to the baud rate
When future silicon version fix the problem, this workaround need be removed
---
drivers/serial/mfd.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 47 insertions(+), 3 deletions(-)
drivers/serial/mfd.c +47 -3
@@ -64,6 +64,8 @@#define mfd_readl (obj, offset) readl(obj->reg + offset)#define mfd_writel (obj, offset, val) writel(val, obj->reg + offset) +#define HSU_DMA_TIMEOUT_CHECK_FREQ (HZ/10) +struct hsu_dma_buffer {u8 *buf;dma_addr_t dma_addr;
@@ -75,7 +77,8 @@ struct hsu_dma_chan {u32 id;u32 dirt;/* to or from device */ struct uart_hsu_port *uport; -void __iomem *reg; +void __iomem *reg; +struct timer_list rx_timer;/* only needed by RX channel */ };struct uart_hsu_port {
@@ -377,6 +380,8 @@ void hsu_dma_start_rx_chan(struct hsu_dma_chan *rxc, struct hsu_dma_buffer *dbuf | (0x1 << 24) /* timeout bit, see HSU Errata 1 */ );chan_writel (rxc,HSU_CH_CR ,0x3 ); + +mod_timer (&rxc->rx_timer, jiffies +HSU_DMA_TIMEOUT_CHECK_FREQ ); }/* Protected by spin_lock_irqsave(port->lock) */
@@ -437,8 +442,13 @@ void hsu_dma_rx(struct uart_hsu_port *up, u32 int_sts)/* We can use 2 ways to calc the actual transfer len */ count =chan_readl (chan,HSU_CH_D0SAR ) - dbuf->dma_addr; -if (!count) +if (!count) { +/* restart the channel before we leave */ +chan_writel (chan,HSU_CH_CR ,0x3 );return ; + } + +del_timer (&chan->rx_timer);dma_sync_single_for_cpu (port->dev, dbuf->dma_addr, dbuf->dma_size,DMA_FROM_DEVICE );
@@ -463,9 +473,12 @@ void hsu_dma_rx(struct uart_hsu_port *up, u32 int_sts) | (0x1 << 16) | (0x1 <<24 )/* timeout bit, see HSU Errata 1 */ ); +tty_flip_buffer_push (tty); +chan_writel (chan,HSU_CH_CR ,0x3 ); + chan->rx_timer.expires = jiffies +HSU_DMA_TIMEOUT_CHECK_FREQ ; +add_timer (&chan->rx_timer); -tty_flip_buffer_push (tty); }static void serial_hsu_stop_rx (struct uart_port *port)
@@ -893,6 +906,8 @@ static void serial_hsu_shutdown(struct uart_port *port) container_of(port, struct uart_hsu_port, port);unsigned long flags; +del_timer_sync (&up->rxc->rx_timer); +/* Disable interrupts from this port */ up->ier =0 ;serial_out (up,UART_IER ,0 );
@@ -1348,6 +1363,28 @@ err_disable:return ret; } +static void hsu_dma_rx_timeout (unsigned long data) +{ +struct hsu_dma_chan *chan = (void *)data; +struct uart_hsu_port *up = chan->uport; +struct hsu_dma_buffer *dbuf = &up->rxbuf; +int count =0 ; +unsigned long flags; + +spin_lock_irqsave (&up->port.lock, flags); + + count =chan_readl (chan,HSU_CH_D0SAR ) - dbuf->dma_addr; + +if (!count) { +mod_timer (&chan->rx_timer, jiffies +HSU_DMA_TIMEOUT_CHECK_FREQ ); + goto exit; + } + +hsu_dma_rx (up,0 ); +exit: +spin_unlock_irqrestore (&up->port.lock, flags); +} +static void hsu_global_init (void ) {struct hsu_port *hsu;
@@ -1409,6 +1446,13 @@ static void hsu_global_init(void) dchan->uport = &hsu->port[i/2 ]; dchan->reg = hsu->reg +HSU_DMA_CHANS_REG_OFFSET + i *HSU_DMA_CHANS_REG_LENGTH ; + +/* Work around for RX */ +if (dchan->dirt ==DMA_FROM_DEVICE ) { +init_timer (&dchan->rx_timer); + dchan->rx_timer.function = hsu_dma_rx_timeout; + dchan->rx_timer.data = (unsigned long )dchan; + } dchan++; }
PATCH 3/4 hsu: some code cleanup
In reply to Alan CoxFrom: Feng Tang <[email protected]>
Major changes are:
* refine the comments in the driver
* remove unused member from structure "hsu_port"
* extended spin_lock protoction for dma mode in port_irq()
---
drivers/serial/mfd.c | 85 +++++++++++--------------------------------------- 1 files changed, 19 insertions(+), 66 deletions(-)
drivers/serial/mfd.c +19 -66
@@ -3,7 +3,7 @@* * Refer pxa.c, 8250.c and some other drivers in drivers/serial/ * -* (C) Copyright 2009 Intel Corporation +* (C) Copyright 2010 Intel Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License
@@ -11,30 +11,16 @@* of the License. */ -/* Notes: -* 1. there should be 2 types of register access method, one for -* UART ports, the other for the general purpose registers -* -* 2. It used to have a Irda port, but was defeatured recently -* -* 3. Based on the info from HSU MAS, 0/1 channel are assigned to -* port0, 2/3 chan to port 1, 4/5 chan to port 3. Even number -* chan will be read, odd chan for write -* -* 4. HUS supports both the 64B and 16B FIFO version, but this driver -* will only use 64B version +* 1. DMA channel allocation: 0/1 channel are assigned to port 0, +* 2/3 chan to port 1, 4/5 chan to port 3. Even number chans +* are used for RX, odd chans for TX * -* 5. In A0 stepping, UART will not support TX half empty flag, thus -* need add a #ifdef judgement +* 2. In A0 stepping, UART will not support TX half empty flag * -* 6. One more bug for A0, the loopback mode won't support AFC -* auto-flow control -* -* 7. HSU has some special FCR control bits, we add it to serial_reg.h -* -* 8. The RI/DSR/DCD/DTR are not pinned out, DCD & DSR are always asserted, -* only when the HW is reset the DDCD and DDSR will be triggered +* 3. The RI/DSR/DCD/DTR are not pinned out, DCD & DSR are always +* asserted, only when the HW is reset the DDCD and DDSR will +* be triggered */ #include <linux/module.h>
@@ -75,7 +61,7 @@ struct hsu_dma_buffer {struct hsu_dma_chan {u32 id; -u32 dirt;/* to or from device */ +enum dma_data_direction dirt;struct uart_hsu_port *uport;void __iomem *reg;struct timer_list rx_timer;/* only needed by RX channel */
@@ -102,8 +88,6 @@ struct uart_hsu_port {/* Top level data structure of HSU */ struct hsu_port { -struct pci_device *pdev; -void __iomem *reg;unsigned long paddr;unsigned long iolen;
@@ -112,23 +96,9 @@ struct hsu_port {struct uart_hsu_port port[3 ];struct hsu_dma_chan chans[10 ]; -#ifdef CONFIG_DEBUG_FS struct dentry *debugfs; -#endif }; -static inline void hexdump (char *str,u8 *addr,int cnt) -{ -int i; - -for (i =0 ; i < cnt; i +=8 ) { -printk ("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x" , - addr[i], addr[i+1 ], addr[i+2 ], addr[i+3 ], - addr[i+4 ], addr[i+5 ], addr[i+6 ], addr[i+7 ]); -printk ("\n" ); - } -} -static inline unsigned int serial_in (struct uart_hsu_port *up,int offset) {unsigned int val;
@@ -353,9 +323,6 @@ void hsu_dma_tx(struct uart_hsu_port *up) | (0x1 << 8) | (0x1 <<16 ) | (0x1 << 24)); - -WARN (chan_readl (up->txc,HSU_CH_CR ) &0x1 , -"TX channel has already be started!!\n" ); up->dma_tx_on =1 ;chan_writel (up->txc,HSU_CH_CR ,0x1 ); }
@@ -367,7 +334,6 @@ void hsu_dma_tx(struct uart_hsu_port *up)/* The buffer is already cache coherent */ void hsu_dma_start_rx_chan (struct hsu_dma_chan *rxc,struct hsu_dma_buffer *dbuf) { -/* Need start RX dma channel here */ dbuf->ofs =0 ;chan_writel (rxc,HSU_CH_BSR ,32 );
@@ -426,35 +392,32 @@ void hsu_dma_rx(struct uart_hsu_port *up, u32 int_sts)return ;/* -* first need to know how many is already transferred, +* First need to know how many is already transferred, * then check if its a timeout DMA irq, and return * the trail bytes out, push them up and reenable the -* channel, better to use 2 descriptors at the same time +* channel */ -/* timeout IRQ, need wait some time, see Errata 2 */ +/* Timeout IRQ, need wait some time, see Errata 2 */ if (int_sts &0xf00 )udelay (2 );/* Stop the channel */ chan_writel (chan,HSU_CH_CR ,0x0 ); -/* We can use 2 ways to calc the actual transfer len */ count =chan_readl (chan,HSU_CH_D0SAR ) - dbuf->dma_addr; -if (!count) { -/* restart the channel before we leave */ +/* Restart the channel before we leave */ chan_writel (chan,HSU_CH_CR ,0x3 );return ; } -del_timer (&chan->rx_timer);dma_sync_single_for_cpu (port->dev, dbuf->dma_addr, dbuf->dma_size,DMA_FROM_DEVICE );/* -* head will only wrap around when we recycle +* Head will only wrap around when we recycle * the DMA buffer, and when that happens, we * explicitly set tail to 0. So head will * always be greater than tail.
@@ -496,10 +459,6 @@ static void serial_hsu_stop_rx(struct uart_port *port) } } -/* -* if there is error flag, should we just reset the FIFO or keeps -* working on it -*/ static inline void receive_chars (struct uart_hsu_port *up,int *status) {struct tty_struct *tty = up->port.state->port.tty;
@@ -571,7 +530,6 @@ static void transmit_chars(struct uart_hsu_port *up) {struct circ_buf *xmit = &up->port.state->xmit;int count; -int i =0 ;/* for debug use */ if (up->port.x_char) {serial_out (up,UART_TX , up->port.x_char);
@@ -592,13 +550,11 @@ static void transmit_chars(struct uart_hsu_port *up)* into it won't clear the EMPT bit, so we may need be cautious * by useing a shorter buffer */ -/* count = up->port.fifosize; */ count = up->port.fifosize -4 ;#endif do { serial_out(up, UART_TX, xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - i++; up->port.icount.tx++; if (uart_circ_empty(xmit))
@@ -628,7 +584,7 @@ static inline void check_modem_status(struct uart_hsu_port *up)/* We may only get DDCD when HW init and reset */ if (status &UART_MSR_DDCD )uart_handle_dcd_change (&up->port, status &UART_MSR_DCD ); -/* will start/stop_tx accordingly */ +/* Will start/stop_tx accordingly */ if (status &UART_MSR_DCTS )uart_handle_cts_change (&up->port, status &UART_MSR_CTS );
@@ -647,6 +603,7 @@ static irqreturn_t port_irq(int irq, void *dev_id)if (unlikely (!up->running))return IRQ_NONE ; +spin_lock_irqsave (&up->port.lock, flags);if (up->use_dma) { lsr =serial_in (up,UART_LSR ); if (unlikely(lsr & (UART_LSR_BI | UART_LSR_PE |
@@ -655,10 +612,10 @@ static irqreturn_t port_irq(int irq, void *dev_id)"Got lsr irq while using DMA, lsr = 0x%2x\n" , lsr);check_modem_status (up); +spin_unlock_irqrestore (&up->port.lock, flags);return IRQ_HANDLED ; } -spin_lock_irqsave (&up->port.lock, flags); iir =serial_in (up,UART_IIR );if (iir &UART_IIR_NO_INT ) {spin_unlock_irqrestore (&up->port.lock, flags);
@@ -666,9 +623,9 @@ static irqreturn_t port_irq(int irq, void *dev_id) } lsr =serial_in (up,UART_LSR ); -if (lsr &UART_LSR_DR )receive_chars (up, &lsr); +check_modem_status (up);/* lsr will be renewed during the receive_chars */ if (lsr &UART_LSR_THRE )
@@ -701,7 +658,6 @@ static inline void dma_chan_irq(struct hsu_dma_chan *chan)/* Tx channel */ if (chan->dirt ==DMA_TO_DEVICE ) { -/* dma for irq should be done */ chan_writel (chan,HSU_CH_CR ,0x0 ); up->dma_tx_on =0 ;hsu_dma_tx (up);
@@ -851,7 +807,6 @@ static int serial_hsu_startup(struct uart_port *port)spin_unlock_irqrestore (&up->port.lock, flags);/* DMA init */ -/* When use DMA, TX/RX's FIFO and IRQ should be disabled */ if (up->use_dma) {struct hsu_dma_buffer *dbuf;struct circ_buf *xmit = &port->state->xmit;
@@ -1090,11 +1045,9 @@ static int serial_hsu_request_port(struct uart_port *port)static void serial_hsu_config_port (struct uart_port *port,int flags) { -#if 0 struct uart_hsu_port *up = container_of(port, struct uart_hsu_port, port); up->port.type =PORT_MFD ; -#endif } static int
@@ -1426,7 +1379,7 @@ static void hsu_global_init(void) uport->port.ops = &serial_hsu_pops; uport->port.line = i; uport->port.flags =UPF_IOREMAP ; -/* make the maxim support rate to 2746800 bps */ +/* set the scalable maxim support rate to 2746800 bps */ uport->port.uartclk =115200 *24 *16 ; uport->running =0 ;
PATCH 4/4 hsu: call PCI pm hooks in suspend/resume function
In reply to Alan CoxFrom: Feng Tang <[email protected]>
Also add check for dma controller or the uart ports.
---
drivers/serial/mfd.c | 31 ++++++++++++++++++++++--------- 1 files changed, 22 insertions(+), 9 deletions(-)
drivers/serial/mfd.c +22 -9
@@ -1217,25 +1217,38 @@ static struct uart_driver serial_hsu_reg = {#ifdef CONFIG_PM static int serial_hsu_suspend (struct pci_dev *pdev,pm_message_t state) { +void *priv =pci_get_drvdata (pdev);struct uart_hsu_port *up; - up =pci_get_drvdata (pdev); -if (!up) -return 0 ; - -uart_suspend_port (&serial_hsu_reg, &up->port); +/* Make sure this is not the internal dma controller */ +if (priv && (pdev->device !=0x081E )) { + up = priv; +uart_suspend_port (&serial_hsu_reg, &up->port); + } +pci_save_state (pdev); +pci_set_power_state (pdev,pci_choose_state (pdev, state));return 0 ; }static int serial_hsu_resume (struct pci_dev *pdev) { +void *priv =pci_get_drvdata (pdev);struct uart_hsu_port *up; +int ret; - up =pci_get_drvdata (pdev); -if (!up) -return 0 ; -uart_resume_port (&serial_hsu_reg, &up->port); +pci_set_power_state (pdev,PCI_D0 ); +pci_restore_state (pdev); + + ret =pci_enable_device (pdev); +if (ret) +dev_warn (&pdev->dev, +"HSU: can't re-enable device, try to continue\n" ); + +if (priv && (pdev->device !=0x081E )) { + up = priv; +uart_resume_port (&serial_hsu_reg, &up->port); + }return 0 ; }#else
Quoted 1 line · Alan Cox
With the header file escape fixed (sorry about that)
All queued up.
thanks,
greg k-h