Skip to content

Commit d8a4995

Browse files
covracergregkh
authored andcommitted
tty: pl011: Work around QDF2400 E44 stuck BUSY bit
The Qualcomm Datacenter Technologies QDF2400 family of SoCs contains a custom (non-PrimeCell) implementation of the SBSA UART. Occasionally the BUSY bit in the Flag Register gets stuck as 1, erratum 44 for both 2432v1 and 2400v1 SoCs.Checking that the Transmit FIFO Empty (TXFE) bit is 0, instead of checking that the BUSY bit is 1, works around the issue. To facilitate this substitution of flags and values, introduce vendor-specific inversion of Feature Register bits when UART AMBA Port (UAP) data is available. For the earlycon case, prior to UAP availability, implement alternative putc and early_write functions. Similar to what how ARMv8 ACPI PCI quirks are detected during MCFG parsing, check the OEM fields of the Serial Port Console Redirection (SPCR) ACPI table to determine if the current platform is known to be affected by the erratum. Signed-off-by: Christopher Covington <cov@codeaurora.org> Acked-by: Russell King <rmk+kernel@armlinux.org.uk> Acked-by: Timur Tabi <timur@codeaurora.org> Acked-by: Mark Rutland <mark.rutland@arm.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 2867af2 commit d8a4995

File tree

2 files changed

+82
-7
lines changed

2 files changed

+82
-7
lines changed

drivers/acpi/spcr.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@
1616
#include <linux/kernel.h>
1717
#include <linux/serial_core.h>
1818

19+
/*
20+
* Some Qualcomm Datacenter Technologies SoCs have a defective UART BUSY bit.
21+
* Detect them by examining the OEM fields in the SPCR header, similiar to PCI
22+
* quirk detection in pci_mcfg.c.
23+
*/
24+
static bool qdf2400_erratum_44_present(struct acpi_table_header *h)
25+
{
26+
if (memcmp(h->oem_id, "QCOM ", ACPI_OEM_ID_SIZE))
27+
return false;
28+
29+
if (!memcmp(h->oem_table_id, "QDF2432 ", ACPI_OEM_TABLE_ID_SIZE))
30+
return true;
31+
32+
if (!memcmp(h->oem_table_id, "QDF2400 ", ACPI_OEM_TABLE_ID_SIZE) &&
33+
h->oem_revision == 0)
34+
return true;
35+
36+
return false;
37+
}
38+
1939
/**
2040
* parse_spcr() - parse ACPI SPCR table and add preferred console
2141
*
@@ -93,6 +113,9 @@ int __init parse_spcr(bool earlycon)
93113
goto done;
94114
}
95115

116+
if (qdf2400_erratum_44_present(&table->header))
117+
uart = "qdf2400_e44";
118+
96119
snprintf(opts, sizeof(opts), "%s,%s,0x%llx,%d", uart, iotype,
97120
table->serial_port.address, baud_rate);
98121

drivers/tty/serial/amba-pl011.c

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ struct vendor_data {
9797
unsigned int fr_dsr;
9898
unsigned int fr_cts;
9999
unsigned int fr_ri;
100+
unsigned int inv_fr;
100101
bool access_32b;
101102
bool oversampling;
102103
bool dma_threshold;
@@ -141,6 +142,30 @@ static struct vendor_data vendor_sbsa = {
141142
.fixed_options = true,
142143
};
143144

145+
/*
146+
* Erratum 44 for QDF2432v1 and QDF2400v1 SoCs describes the BUSY bit as
147+
* occasionally getting stuck as 1. To avoid the potential for a hang, check
148+
* TXFE == 0 instead of BUSY == 1. This may not be suitable for all UART
149+
* implementations, so only do so if an affected platform is detected in
150+
* parse_spcr().
151+
*/
152+
static bool qdf2400_e44_present = false;
153+
154+
static struct vendor_data vendor_qdt_qdf2400_e44 = {
155+
.reg_offset = pl011_std_offsets,
156+
.fr_busy = UART011_FR_TXFE,
157+
.fr_dsr = UART01x_FR_DSR,
158+
.fr_cts = UART01x_FR_CTS,
159+
.fr_ri = UART011_FR_RI,
160+
.inv_fr = UART011_FR_TXFE,
161+
.access_32b = true,
162+
.oversampling = false,
163+
.dma_threshold = false,
164+
.cts_event_workaround = false,
165+
.always_enabled = true,
166+
.fixed_options = true,
167+
};
168+
144169
static u16 pl011_st_offsets[REG_ARRAY_SIZE] = {
145170
[REG_DR] = UART01x_DR,
146171
[REG_ST_DMAWM] = ST_UART011_DMAWM,
@@ -1518,7 +1543,10 @@ static unsigned int pl011_tx_empty(struct uart_port *port)
15181543
{
15191544
struct uart_amba_port *uap =
15201545
container_of(port, struct uart_amba_port, port);
1521-
unsigned int status = pl011_read(uap, REG_FR);
1546+
1547+
/* Allow feature register bits to be inverted to work around errata */
1548+
unsigned int status = pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr;
1549+
15221550
return status & (uap->vendor->fr_busy | UART01x_FR_TXFF) ?
15231551
0 : TIOCSER_TEMT;
15241552
}
@@ -2215,10 +2243,12 @@ pl011_console_write(struct console *co, const char *s, unsigned int count)
22152243
uart_console_write(&uap->port, s, count, pl011_console_putchar);
22162244

22172245
/*
2218-
* Finally, wait for transmitter to become empty
2219-
* and restore the TCR
2246+
* Finally, wait for transmitter to become empty and restore the
2247+
* TCR. Allow feature register bits to be inverted to work around
2248+
* errata.
22202249
*/
2221-
while (pl011_read(uap, REG_FR) & uap->vendor->fr_busy)
2250+
while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr)
2251+
& uap->vendor->fr_busy)
22222252
cpu_relax();
22232253
if (!uap->vendor->always_enabled)
22242254
pl011_write(old_cr, uap, REG_CR);
@@ -2340,8 +2370,12 @@ static int __init pl011_console_match(struct console *co, char *name, int idx,
23402370
resource_size_t addr;
23412371
int i;
23422372

2343-
if (strcmp(name, "pl011") != 0 || strcmp(name, "ttyAMA") != 0)
2373+
if (strcmp(name, "qdf2400_e44") == 0) {
2374+
pr_info_once("UART: Working around QDF2400 SoC erratum 44");
2375+
qdf2400_e44_present = true;
2376+
} else if (strcmp(name, "pl011") != 0 || strcmp(name, "ttyAMA") != 0) {
23442377
return -ENODEV;
2378+
}
23452379

23462380
if (uart_parse_earlycon(options, &iotype, &addr, &options))
23472381
return -ENODEV;
@@ -2383,6 +2417,22 @@ static struct console amba_console = {
23832417

23842418
#define AMBA_CONSOLE (&amba_console)
23852419

2420+
static void qdf2400_e44_putc(struct uart_port *port, int c)
2421+
{
2422+
while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
2423+
cpu_relax();
2424+
writel(c, port->membase + UART01x_DR);
2425+
while (!(readl(port->membase + UART01x_FR) & UART011_FR_TXFE))
2426+
cpu_relax();
2427+
}
2428+
2429+
static void qdf2400_e44_early_write(struct console *con, const char *s, unsigned n)
2430+
{
2431+
struct earlycon_device *dev = con->data;
2432+
2433+
uart_console_write(&dev->port, s, n, qdf2400_e44_putc);
2434+
}
2435+
23862436
static void pl011_putc(struct uart_port *port, int c)
23872437
{
23882438
while (readl(port->membase + UART01x_FR) & UART01x_FR_TXFF)
@@ -2408,7 +2458,8 @@ static int __init pl011_early_console_setup(struct earlycon_device *device,
24082458
if (!device->port.membase)
24092459
return -ENODEV;
24102460

2411-
device->con->write = pl011_early_write;
2461+
device->con->write = qdf2400_e44_present ?
2462+
qdf2400_e44_early_write : pl011_early_write;
24122463
return 0;
24132464
}
24142465
OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup);
@@ -2645,7 +2696,8 @@ static int sbsa_uart_probe(struct platform_device *pdev)
26452696
uap->port.irq = ret;
26462697

26472698
uap->reg_offset = vendor_sbsa.reg_offset;
2648-
uap->vendor = &vendor_sbsa;
2699+
uap->vendor = qdf2400_e44_present ?
2700+
&vendor_qdt_qdf2400_e44 : &vendor_sbsa;
26492701
uap->fifosize = 32;
26502702
uap->port.iotype = vendor_sbsa.access_32b ? UPIO_MEM32 : UPIO_MEM;
26512703
uap->port.ops = &sbsa_uart_pops;

0 commit comments

Comments
 (0)