Commit 25f34d0f authored by Alex Williamson's avatar Alex Williamson Committed by Russell King

[SERIAL] 8250 woraround for buggy uart

Patch from Alex Williamson

This patch adds support for detecting and working around a bug in
the A2 rev of the Exar ST16C2550 UART.  The chip incorrectly
advertises an EFR and mis-detects as having the wrong size FIFO.
Much of the patch below is Russell's proposed solution to the
problem.  The only changes I've made are to check the FIFO size
on the part (because there is a real part with the same divisor
ID and larger FIFO) and save and restore the LCR register around
the size_fifo() routine (it doesn't work correctly with a LCR
value of 0xBF).

Signed-off-by: Alex Williamson
Signed-off-by: Russell King
parent ae48fc35
...@@ -450,9 +450,11 @@ static void disable_rsa(struct uart_8250_port *up) ...@@ -450,9 +450,11 @@ static void disable_rsa(struct uart_8250_port *up)
*/ */
static int size_fifo(struct uart_8250_port *up) static int size_fifo(struct uart_8250_port *up)
{ {
unsigned char old_fcr, old_mcr, old_dll, old_dlm; unsigned char old_fcr, old_mcr, old_dll, old_dlm, old_lcr;
int count; int count;
old_lcr = serial_inp(up, UART_LCR);
serial_outp(up, UART_LCR, 0);
old_fcr = serial_inp(up, UART_FCR); old_fcr = serial_inp(up, UART_FCR);
old_mcr = serial_inp(up, UART_MCR); old_mcr = serial_inp(up, UART_MCR);
serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO | serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO |
...@@ -475,10 +477,39 @@ static int size_fifo(struct uart_8250_port *up) ...@@ -475,10 +477,39 @@ static int size_fifo(struct uart_8250_port *up)
serial_outp(up, UART_LCR, UART_LCR_DLAB); serial_outp(up, UART_LCR, UART_LCR_DLAB);
serial_outp(up, UART_DLL, old_dll); serial_outp(up, UART_DLL, old_dll);
serial_outp(up, UART_DLM, old_dlm); serial_outp(up, UART_DLM, old_dlm);
serial_outp(up, UART_LCR, old_lcr);
return count; return count;
} }
/*
* Read UART ID using the divisor method - set DLL and DLM to zero
* and the revision will be in DLL and device type in DLM. We
* preserve the device state across this.
*/
static unsigned int autoconfig_read_divisor_id(struct uart_8250_port *p)
{
unsigned char old_dll, old_dlm, old_lcr;
unsigned int id;
old_lcr = serial_inp(p, UART_LCR);
serial_outp(p, UART_LCR, UART_LCR_DLAB);
old_dll = serial_inp(p, UART_DLL);
old_dlm = serial_inp(p, UART_DLM);
serial_outp(p, UART_DLL, 0);
serial_outp(p, UART_DLM, 0);
id = serial_inp(p, UART_DLL) | serial_inp(p, UART_DLM) << 8;
serial_outp(p, UART_DLL, old_dll);
serial_outp(p, UART_DLM, old_dlm);
serial_outp(p, UART_LCR, old_lcr);
return id;
}
/* /*
* This is a helper routine to autodetect StarTech/Exar/Oxsemi UART's. * This is a helper routine to autodetect StarTech/Exar/Oxsemi UART's.
* When this function is called we know it is at least a StarTech * When this function is called we know it is at least a StarTech
...@@ -491,7 +522,7 @@ static int size_fifo(struct uart_8250_port *up) ...@@ -491,7 +522,7 @@ static int size_fifo(struct uart_8250_port *up)
*/ */
static void autoconfig_has_efr(struct uart_8250_port *up) static void autoconfig_has_efr(struct uart_8250_port *up)
{ {
unsigned char id1, id2, id3, rev, saved_dll, saved_dlm; unsigned int id1, id2, id3, rev;
/* /*
* Everything with an EFR has SLEEP * Everything with an EFR has SLEEP
...@@ -541,21 +572,13 @@ static void autoconfig_has_efr(struct uart_8250_port *up) ...@@ -541,21 +572,13 @@ static void autoconfig_has_efr(struct uart_8250_port *up)
* 0x12 - XR16C2850. * 0x12 - XR16C2850.
* 0x14 - XR16C854. * 0x14 - XR16C854.
*/ */
serial_outp(up, UART_LCR, UART_LCR_DLAB); id1 = autoconfig_read_divisor_id(up);
saved_dll = serial_inp(up, UART_DLL); DEBUG_AUTOCONF("850id=%04x ", id1);
saved_dlm = serial_inp(up, UART_DLM);
serial_outp(up, UART_DLL, 0); id2 = id1 >> 8;
serial_outp(up, UART_DLM, 0); if (id2 == 0x10 || id2 == 0x12 || id2 == 0x14) {
id2 = serial_inp(up, UART_DLL); if (id2 == 0x10)
id1 = serial_inp(up, UART_DLM); up->rev = id1 & 255;
serial_outp(up, UART_DLL, saved_dll);
serial_outp(up, UART_DLM, saved_dlm);
DEBUG_AUTOCONF("850id=%02x:%02x ", id1, id2);
if (id1 == 0x10 || id1 == 0x12 || id1 == 0x14) {
if (id1 == 0x10)
up->rev = id2;
up->port.type = PORT_16850; up->port.type = PORT_16850;
return; return;
} }
...@@ -597,6 +620,19 @@ static void autoconfig_8250(struct uart_8250_port *up) ...@@ -597,6 +620,19 @@ static void autoconfig_8250(struct uart_8250_port *up)
up->port.type = PORT_16450; up->port.type = PORT_16450;
} }
static int broken_efr(struct uart_8250_port *up)
{
/*
* Exar ST16C2550 "A2" devices incorrectly detect as
* having an EFR, and report an ID of 0x0201. See
* http://www.exar.com/info.php?pdf=dan180_oct2004.pdf
*/
if (autoconfig_read_divisor_id(up) == 0x0201 && size_fifo(up) == 16)
return 1;
return 0;
}
/* /*
* We know that the chip has FIFOs. Does it have an EFR? The * We know that the chip has FIFOs. Does it have an EFR? The
* EFR is located in the same register position as the IIR and * EFR is located in the same register position as the IIR and
...@@ -633,7 +669,7 @@ static void autoconfig_16550a(struct uart_8250_port *up) ...@@ -633,7 +669,7 @@ static void autoconfig_16550a(struct uart_8250_port *up)
* (other ST16C650V2 UARTs, TI16C752A, etc) * (other ST16C650V2 UARTs, TI16C752A, etc)
*/ */
serial_outp(up, UART_LCR, 0xBF); serial_outp(up, UART_LCR, 0xBF);
if (serial_in(up, UART_EFR) == 0) { if (serial_in(up, UART_EFR) == 0 && !broken_efr(up)) {
DEBUG_AUTOCONF("EFRv2 "); DEBUG_AUTOCONF("EFRv2 ");
autoconfig_has_efr(up); autoconfig_has_efr(up);
return; return;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment