|
@@ -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>
|
|
@@ -65,6 +66,30 @@
|
|
|
#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;
|
|
@@ -72,6 +97,7 @@ struct vendor_data {
|
|
|
unsigned int lcrh_tx;
|
|
|
unsigned int lcrh_rx;
|
|
|
bool oversampling;
|
|
|
+ bool interrupt_may_hang; /* vendor-specific */
|
|
|
bool dma_threshold;
|
|
|
};
|
|
|
|
|
@@ -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 {
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -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);
|
|
@@ -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:
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -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;
|