aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/tty/serial/amba-pl011.c
diff options
context:
space:
mode:
authorRajanikanth H.V <rajanikanth.hv@stericsson.com>2012-03-26 05:17:02 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-04-18 18:06:38 -0400
commit4fd0690bb0c3955983560bb2767ee82e2b197f9b (patch)
tree71ecf0b5369814f22ffef5a4321e299c9300814a /drivers/tty/serial/amba-pl011.c
parente695b28664827eaad4c8a4b6f921d3fae3e0f526 (diff)
serial: pl011: implement workaround for CTS clear event issue
Problem Observed: - interrupt status is set by rising or falling edge on CTS line - interrupt status is cleared on a .0. to .1. transition of the interrupt-clear register bit 1. - interrupt-clear register is reset by hardware once the interrupt status is .0.. Remark: It seems not possible to read this register back by the CPU though, but internally this register exists. - when simultaneous set and reset event on the interrupt status happens, then the set-event has priority and the status remains .1.. As a result the interrupt-clear register is not reset to .0., and no new .0. to .1. transition can be detected on it when writing a .1. to it. This implies race condition, the clear must be performed at least one UARTCLK the riding edge of CTS RIS interrupt. Fix: Instead of resetting UART as done in commit c16d51a32bbb61ac8fd96f78b5ce2fccfe0fb4c3 "amba pl011: workaround for uart registers lockup" do the following: write .0. and then .1. to the interrupt-clear register to make sure that this transition is detected. According to the datasheet writing a .0. does not have any effect, but actually it allows to reset the internal interrupt-clear register. Take into account: The .0. needs to last at least for one clk_uart clock period (~ 38 MHz, 26.08ns) This way we can do away with the tasklet and keep only a tiny fix triggered by the variant flag introduced in this patch. Signed-off-by: Guillaume Jaunet <guillaume.jaunet@stericsson.com> Signed-off-by: Christophe Arnal <christophe.arnal@stericsson.com> Signed-off-by: Matthias Locher <Matthias.Locher@stericsson.com> Signed-off-by: Rajanikanth H.V <rajanikanth.hv@stericsson.com> Reviewed-by: Srinidhi Kasagar <srinidhi.kasagar@stericsson.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty/serial/amba-pl011.c')
-rw-r--r--drivers/tty/serial/amba-pl011.c109
1 files changed, 18 insertions, 91 deletions
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
index 0c65c9e66986..aee85238ccfc 100644
--- a/drivers/tty/serial/amba-pl011.c
+++ b/drivers/tty/serial/amba-pl011.c
@@ -67,30 +67,6 @@
67#define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE) 67#define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE)
68#define UART_DUMMY_DR_RX (1 << 16) 68#define UART_DUMMY_DR_RX (1 << 16)
69 69
70
71#define UART_WA_SAVE_NR 14
72
73static void pl011_lockup_wa(unsigned long data);
74static const u32 uart_wa_reg[UART_WA_SAVE_NR] = {
75 ST_UART011_DMAWM,
76 ST_UART011_TIMEOUT,
77 ST_UART011_LCRH_RX,
78 UART011_IBRD,
79 UART011_FBRD,
80 ST_UART011_LCRH_TX,
81 UART011_IFLS,
82 ST_UART011_XFCR,
83 ST_UART011_XON1,
84 ST_UART011_XON2,
85 ST_UART011_XOFF1,
86 ST_UART011_XOFF2,
87 UART011_CR,
88 UART011_IMSC
89};
90
91static u32 uart_wa_regdata[UART_WA_SAVE_NR];
92static DECLARE_TASKLET(pl011_lockup_tlet, pl011_lockup_wa, 0);
93
94/* There is by now at least one vendor with differing details, so handle it */ 70/* There is by now at least one vendor with differing details, so handle it */
95struct vendor_data { 71struct vendor_data {
96 unsigned int ifls; 72 unsigned int ifls;
@@ -100,6 +76,7 @@ struct vendor_data {
100 bool oversampling; 76 bool oversampling;
101 bool interrupt_may_hang; /* vendor-specific */ 77 bool interrupt_may_hang; /* vendor-specific */
102 bool dma_threshold; 78 bool dma_threshold;
79 bool cts_event_workaround;
103}; 80};
104 81
105static struct vendor_data vendor_arm = { 82static struct vendor_data vendor_arm = {
@@ -109,6 +86,7 @@ static struct vendor_data vendor_arm = {
109 .lcrh_rx = UART011_LCRH, 86 .lcrh_rx = UART011_LCRH,
110 .oversampling = false, 87 .oversampling = false,
111 .dma_threshold = false, 88 .dma_threshold = false,
89 .cts_event_workaround = false,
112}; 90};
113 91
114static struct vendor_data vendor_st = { 92static struct vendor_data vendor_st = {
@@ -119,6 +97,7 @@ static struct vendor_data vendor_st = {
119 .oversampling = true, 97 .oversampling = true,
120 .interrupt_may_hang = true, 98 .interrupt_may_hang = true,
121 .dma_threshold = true, 99 .dma_threshold = true,
100 .cts_event_workaround = true,
122}; 101};
123 102
124static struct uart_amba_port *amba_ports[UART_NR]; 103static struct uart_amba_port *amba_ports[UART_NR];
@@ -1054,69 +1033,6 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
1054#define pl011_dma_flush_buffer NULL 1033#define pl011_dma_flush_buffer NULL
1055#endif 1034#endif
1056 1035
1057
1058/*
1059 * pl011_lockup_wa
1060 * This workaround aims to break the deadlock situation
1061 * when after long transfer over uart in hardware flow
1062 * control, uart interrupt registers cannot be cleared.
1063 * Hence uart transfer gets blocked.
1064 *
1065 * It is seen that during such deadlock condition ICR
1066 * don't get cleared even on multiple write. This leads
1067 * pass_counter to decrease and finally reach zero. This
1068 * can be taken as trigger point to run this UART_BT_WA.
1069 *
1070 */
1071static void pl011_lockup_wa(unsigned long data)
1072{
1073 struct uart_amba_port *uap = amba_ports[0];
1074 void __iomem *base = uap->port.membase;
1075 struct circ_buf *xmit = &uap->port.state->xmit;
1076 struct tty_struct *tty = uap->port.state->port.tty;
1077 int buf_empty_retries = 200;
1078 int loop;
1079
1080 /* Stop HCI layer from submitting data for tx */
1081 tty->hw_stopped = 1;
1082 while (!uart_circ_empty(xmit)) {
1083 if (buf_empty_retries-- == 0)
1084 break;
1085 udelay(100);
1086 }
1087
1088 /* Backup registers */
1089 for (loop = 0; loop < UART_WA_SAVE_NR; loop++)
1090 uart_wa_regdata[loop] = readl(base + uart_wa_reg[loop]);
1091
1092 /* Disable UART so that FIFO data is flushed out */
1093 writew(0x00, uap->port.membase + UART011_CR);
1094
1095 /* Soft reset UART module */
1096 if (uap->port.dev->platform_data) {
1097 struct amba_pl011_data *plat;
1098
1099 plat = uap->port.dev->platform_data;
1100 if (plat->reset)
1101 plat->reset();
1102 }
1103
1104 /* Restore registers */
1105 for (loop = 0; loop < UART_WA_SAVE_NR; loop++)
1106 writew(uart_wa_regdata[loop] ,
1107 uap->port.membase + uart_wa_reg[loop]);
1108
1109 /* Initialise the old status of the modem signals */
1110 uap->old_status = readw(uap->port.membase + UART01x_FR) &
1111 UART01x_FR_MODEM_ANY;
1112
1113 if (readl(base + UART011_MIS) & 0x2)
1114 printk(KERN_EMERG "UART_BT_WA: ***FAILED***\n");
1115
1116 /* Start Tx/Rx */
1117 tty->hw_stopped = 0;
1118}
1119
1120static void pl011_stop_tx(struct uart_port *port) 1036static void pl011_stop_tx(struct uart_port *port)
1121{ 1037{
1122 struct uart_amba_port *uap = (struct uart_amba_port *)port; 1038 struct uart_amba_port *uap = (struct uart_amba_port *)port;
@@ -1245,12 +1161,26 @@ static irqreturn_t pl011_int(int irq, void *dev_id)
1245 unsigned long flags; 1161 unsigned long flags;
1246 unsigned int status, pass_counter = AMBA_ISR_PASS_LIMIT; 1162 unsigned int status, pass_counter = AMBA_ISR_PASS_LIMIT;
1247 int handled = 0; 1163 int handled = 0;
1164 unsigned int dummy_read;
1248 1165
1249 spin_lock_irqsave(&uap->port.lock, flags); 1166 spin_lock_irqsave(&uap->port.lock, flags);
1250 1167
1251 status = readw(uap->port.membase + UART011_MIS); 1168 status = readw(uap->port.membase + UART011_MIS);
1252 if (status) { 1169 if (status) {
1253 do { 1170 do {
1171 if (uap->vendor->cts_event_workaround) {
1172 /* workaround to make sure that all bits are unlocked.. */
1173 writew(0x00, uap->port.membase + UART011_ICR);
1174
1175 /*
1176 * WA: introduce 26ns(1 uart clk) delay before W1C;
1177 * single apb access will incur 2 pclk(133.12Mhz) delay,
1178 * so add 2 dummy reads
1179 */
1180 dummy_read = readw(uap->port.membase + UART011_ICR);
1181 dummy_read = readw(uap->port.membase + UART011_ICR);
1182 }
1183
1254 writew(status & ~(UART011_TXIS|UART011_RTIS| 1184 writew(status & ~(UART011_TXIS|UART011_RTIS|
1255 UART011_RXIS), 1185 UART011_RXIS),
1256 uap->port.membase + UART011_ICR); 1186 uap->port.membase + UART011_ICR);
@@ -1267,11 +1197,8 @@ static irqreturn_t pl011_int(int irq, void *dev_id)
1267 if (status & UART011_TXIS) 1197 if (status & UART011_TXIS)
1268 pl011_tx_chars(uap); 1198 pl011_tx_chars(uap);
1269 1199
1270 if (pass_counter-- == 0) { 1200 if (pass_counter-- == 0)
1271 if (uap->interrupt_may_hang)
1272 tasklet_schedule(&pl011_lockup_tlet);
1273 break; 1201 break;
1274 }
1275 1202
1276 status = readw(uap->port.membase + UART011_MIS); 1203 status = readw(uap->port.membase + UART011_MIS);
1277 } while (status != 0); 1204 } while (status != 0);