diff options
author | Rajanikanth H.V <rajanikanth.hv@stericsson.com> | 2012-03-26 05:17:02 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-04-18 18:06:38 -0400 |
commit | 4fd0690bb0c3955983560bb2767ee82e2b197f9b (patch) | |
tree | 71ecf0b5369814f22ffef5a4321e299c9300814a /drivers/tty | |
parent | e695b28664827eaad4c8a4b6f921d3fae3e0f526 (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')
-rw-r--r-- | drivers/tty/serial/amba-pl011.c | 109 |
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 | |||
73 | static void pl011_lockup_wa(unsigned long data); | ||
74 | static 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 | |||
91 | static u32 uart_wa_regdata[UART_WA_SAVE_NR]; | ||
92 | static 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 */ |
95 | struct vendor_data { | 71 | struct 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 | ||
105 | static struct vendor_data vendor_arm = { | 82 | static 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 | ||
114 | static struct vendor_data vendor_st = { | 92 | static 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 | ||
124 | static struct uart_amba_port *amba_ports[UART_NR]; | 103 | static 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 | */ | ||
1071 | static 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 | |||
1120 | static void pl011_stop_tx(struct uart_port *port) | 1036 | static 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); |