Skip to content

Commit

Permalink
amba pl011: workaround for uart registers lockup
Browse files Browse the repository at this point in the history
This workaround aims to break the deadlock situation
which raises during continuous transfer of data for long
duration over uart with hardware flow control. It is
observed that CTS interrupt cannot be cleared in uart
interrupt register (ICR). Hence further transfer over
uart gets blocked.

It is seen that during such deadlock condition ICR
don't get cleared even on multiple write. This leads
pass_counter to decrease and finally reach zero. This
can be taken as trigger point to run this UART_BT_WA.

Workaround backups the register configuration, does soft
reset of UART using BIT-0 of PRCC_K_SOFTRST_SET/CLEAR
registers and restores the registers.

This patch also provides support for uart init and exit
function calls if present.

Signed-off-by: Shreshtha Kumar Sahu <shreshthakumar.sahu@stericsson.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Shreshtha Kumar Sahu authored and Greg Kroah-Hartman committed Jun 16, 2011
1 parent 57f2104 commit c16d51a
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 1 deletion.
123 changes: 122 additions & 1 deletion drivers/tty/serial/amba-pl011.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/delay.h>

#include <asm/io.h>
#include <asm/sizes.h>
Expand All @@ -65,13 +66,38 @@
#define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE)
#define UART_DUMMY_DR_RX (1 << 16)


#define UART_WA_SAVE_NR 14

static void pl011_lockup_wa(unsigned long data);
static const u32 uart_wa_reg[UART_WA_SAVE_NR] = {
ST_UART011_DMAWM,
ST_UART011_TIMEOUT,
ST_UART011_LCRH_RX,
UART011_IBRD,
UART011_FBRD,
ST_UART011_LCRH_TX,
UART011_IFLS,
ST_UART011_XFCR,
ST_UART011_XON1,
ST_UART011_XON2,
ST_UART011_XOFF1,
ST_UART011_XOFF2,
UART011_CR,
UART011_IMSC
};

static u32 uart_wa_regdata[UART_WA_SAVE_NR];
static DECLARE_TASKLET(pl011_lockup_tlet, pl011_lockup_wa, 0);

/* There is by now at least one vendor with differing details, so handle it */
struct vendor_data {
unsigned int ifls;
unsigned int fifosize;
unsigned int lcrh_tx;
unsigned int lcrh_rx;
bool oversampling;
bool interrupt_may_hang; /* vendor-specific */
bool dma_threshold;
};

Expand All @@ -90,9 +116,12 @@ static struct vendor_data vendor_st = {
.lcrh_tx = ST_UART011_LCRH_TX,
.lcrh_rx = ST_UART011_LCRH_RX,
.oversampling = true,
.interrupt_may_hang = true,
.dma_threshold = true,
};

static struct uart_amba_port *amba_ports[UART_NR];

/* Deals with DMA transactions */

struct pl011_sgbuf {
Expand Down Expand Up @@ -132,6 +161,7 @@ struct uart_amba_port {
unsigned int lcrh_rx; /* vendor-specific */
bool autorts;
char type[12];
bool interrupt_may_hang; /* vendor-specific */
#ifdef CONFIG_DMA_ENGINE
/* DMA stuff */
bool using_tx_dma;
Expand Down Expand Up @@ -1008,6 +1038,68 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
#endif


/*
* pl011_lockup_wa
* This workaround aims to break the deadlock situation
* when after long transfer over uart in hardware flow
* control, uart interrupt registers cannot be cleared.
* Hence uart transfer gets blocked.
*
* It is seen that during such deadlock condition ICR
* don't get cleared even on multiple write. This leads
* pass_counter to decrease and finally reach zero. This
* can be taken as trigger point to run this UART_BT_WA.
*
*/
static void pl011_lockup_wa(unsigned long data)
{
struct uart_amba_port *uap = amba_ports[0];
void __iomem *base = uap->port.membase;
struct circ_buf *xmit = &uap->port.state->xmit;
struct tty_struct *tty = uap->port.state->port.tty;
int buf_empty_retries = 200;
int loop;

/* Stop HCI layer from submitting data for tx */
tty->hw_stopped = 1;
while (!uart_circ_empty(xmit)) {
if (buf_empty_retries-- == 0)
break;
udelay(100);
}

/* Backup registers */
for (loop = 0; loop < UART_WA_SAVE_NR; loop++)
uart_wa_regdata[loop] = readl(base + uart_wa_reg[loop]);

/* Disable UART so that FIFO data is flushed out */
writew(0x00, uap->port.membase + UART011_CR);

/* Soft reset UART module */
if (uap->port.dev->platform_data) {
struct amba_pl011_data *plat;

plat = uap->port.dev->platform_data;
if (plat->reset)
plat->reset();
}

/* Restore registers */
for (loop = 0; loop < UART_WA_SAVE_NR; loop++)
writew(uart_wa_regdata[loop] ,
uap->port.membase + uart_wa_reg[loop]);

/* Initialise the old status of the modem signals */
uap->old_status = readw(uap->port.membase + UART01x_FR) &
UART01x_FR_MODEM_ANY;

if (readl(base + UART011_MIS) & 0x2)
printk(KERN_EMERG "UART_BT_WA: ***FAILED***\n");

/* Start Tx/Rx */
tty->hw_stopped = 0;
}

static void pl011_stop_tx(struct uart_port *port)
{
struct uart_amba_port *uap = (struct uart_amba_port *)port;
Expand Down Expand Up @@ -1158,8 +1250,11 @@ static irqreturn_t pl011_int(int irq, void *dev_id)
if (status & UART011_TXIS)
pl011_tx_chars(uap);

if (pass_counter-- == 0)
if (pass_counter-- == 0) {
if (uap->interrupt_may_hang)
tasklet_schedule(&pl011_lockup_tlet);
break;
}

status = readw(uap->port.membase + UART011_MIS);
} while (status != 0);
Expand Down Expand Up @@ -1339,6 +1434,14 @@ static int pl011_startup(struct uart_port *port)
writew(uap->im, uap->port.membase + UART011_IMSC);
spin_unlock_irq(&uap->port.lock);

if (uap->port.dev->platform_data) {
struct amba_pl011_data *plat;

plat = uap->port.dev->platform_data;
if (plat->init)
plat->init();
}

return 0;

clk_dis:
Expand Down Expand Up @@ -1394,6 +1497,15 @@ static void pl011_shutdown(struct uart_port *port)
* Shut down the clock producer
*/
clk_disable(uap->clk);

if (uap->port.dev->platform_data) {
struct amba_pl011_data *plat;

plat = uap->port.dev->platform_data;
if (plat->exit)
plat->exit();
}

}

static void
Expand Down Expand Up @@ -1700,6 +1812,14 @@ static int __init pl011_console_setup(struct console *co, char *options)
if (!uap)
return -ENODEV;

if (uap->port.dev->platform_data) {
struct amba_pl011_data *plat;

plat = uap->port.dev->platform_data;
if (plat->init)
plat->init();
}

uap->port.uartclk = clk_get_rate(uap->clk);

if (options)
Expand Down Expand Up @@ -1774,6 +1894,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id)
uap->lcrh_rx = vendor->lcrh_rx;
uap->lcrh_tx = vendor->lcrh_tx;
uap->fifosize = vendor->fifosize;
uap->interrupt_may_hang = vendor->interrupt_may_hang;
uap->port.dev = &dev->dev;
uap->port.mapbase = dev->res.start;
uap->port.membase = base;
Expand Down
3 changes: 3 additions & 0 deletions include/linux/amba/serial.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ struct amba_pl011_data {
bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
void *dma_rx_param;
void *dma_tx_param;
void (*init) (void);
void (*exit) (void);
void (*reset) (void);
};
#endif

Expand Down

0 comments on commit c16d51a

Please sign in to comment.