[v2,2/3] serial: 8250_dw: configure the Allwinner H616 UART
Commit Message
H616 has 256-byte transmit and receive FIFOs, but its optional DesignWare
component parameter register reads as zero. Generic 16550 probing leaves
the port using the 16-byte FIFO configuration and its trigger-level table.
DMA attachment also depends on a FIFO size being available during probe,
so these ports do not reach the existing 8250 DMA path.
Keep PORT_16550A and supply the FIFO size, transmit load and capabilities
when matching the H616 platform data. Provide receive-trigger callbacks
for the documented thresholds of 1, 64, 128 and 254 bytes. The normal
16550A half-full trigger selects 128 bytes on this integration. Serialize
trigger changes with termios and hold a runtime PM reference while
programming FCR, preserving the FIFO contents.
Advertise automatic flow control so CRTSCTS enables MCR.AFCE. The
DesignWare termios path selects hardware CTS handling, so the capability
must be present for the corresponding hardware control to be enabled.
Skip optional-register probing for this fixed configuration. H616 has
debug-divisor and RS485 control registers at the offsets used to detect
DesignWare hardware RS485 and fractional-divisor support. Probing these
offsets writes unrelated registers and can select incompatible callbacks.
Keep the BUSY-safe integer divisor hook and software RS485 handling.
Use the existing SKIP_SET_RATE quirk to keep the UART clock enabled during
termios changes. Its clock is a gate on the shared APB2 source, rather than
an independent baud-rate generator.
Attach DMA from the fixed configuration before registering the port.
Configure single-byte RX and TX bursts because the peripheral request can
correspond to just one character. The existing startup path requests the
firmware-described channels and falls back to PIO when they are unavailable
or the port is a kernel console.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/tty/serial/8250/8250_dw.c | 87 +++++++++++++++++++++++++++++++++++----
1 file changed, 79 insertions(+), 8 deletions(-)
@@ -19,6 +19,7 @@
#include <linux/io.h>
#include <linux/lockdep.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
@@ -66,6 +67,11 @@ struct dw8250_platform_data {
unsigned int quirks;
};
+static const struct dw8250_platform_data dw8250_h616_data = {
+ .usr_reg = DW_UART_USR,
+ .quirks = DW_UART_QUIRK_SKIP_SET_RATE,
+};
+
struct dw8250_data {
struct dw8250_port_data data;
const struct dw8250_platform_data *pdata;
@@ -630,6 +636,66 @@ static void dw8250_shutdown(struct uart_port *port)
d->no_int_count = 0;
}
+static const u8 dw8250_h616_rxtrig_bytes[] = { 1, 64, 128, 254 };
+
+static int dw8250_h616_get_rxtrig(struct uart_port *p)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ guard(uart_port_lock_irqsave)(p);
+
+ return dw8250_h616_rxtrig_bytes[UART_FCR_R_TRIG_BITS(up->fcr)];
+}
+
+static int dw8250_h616_set_rxtrig(struct uart_port *p, unsigned char bytes)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ int i, ret;
+
+ /* Use the nearest lower threshold, as the generic 8250 implementation does. */
+ for (i = 1; i < ARRAY_SIZE(dw8250_h616_rxtrig_bytes); i++)
+ if (bytes < dw8250_h616_rxtrig_bytes[i])
+ break;
+
+ guard(mutex)(&p->state->port.mutex);
+
+ ret = pm_runtime_resume_and_get(p->dev);
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(uart_port_lock_irqsave, p) {
+ up->fcr &= ~UART_FCR_TRIGGER_MASK;
+ up->fcr |= (i - 1) << UART_FCR_R_TRIG_SHIFT;
+ /* Changing the trigger alone preserves the FIFO contents. */
+ serial_port_out(p, UART_FCR, up->fcr);
+ }
+ pm_runtime_put(p->dev);
+
+ return 0;
+}
+
+static void dw8250_h616_setup(struct uart_8250_port *up, struct dw8250_data *data)
+{
+ struct uart_port *p = &up->port;
+
+ p->type = PORT_16550A;
+ p->flags |= UPF_FIXED_TYPE;
+ p->fifosize = 256;
+ up->tx_loadsz = 256;
+ up->capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT | UART_CAP_AFE;
+ p->get_rxtrig = dw8250_h616_get_rxtrig;
+ p->set_rxtrig = dw8250_h616_set_rxtrig;
+ p->rs485_config = serial8250_em485_config;
+ p->rs485_supported = serial8250_em485_supported;
+ up->rs485_start_tx = serial8250_em485_start_tx;
+ up->rs485_stop_tx = serial8250_em485_stop_tx;
+
+ /* A peripheral request can correspond to just one character. */
+ data->data.dma.rxconf.src_maxburst = 1;
+ data->data.dma.txconf.dst_maxburst = 1;
+ up->dma = &data->data.dma;
+}
+
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {}, *up = &uart;
@@ -765,14 +831,18 @@ static int dw8250_probe(struct platform_device *pdev)
dw8250_setup_dma_filter(p, data);
- if (!data->skip_autocfg)
- dw8250_setup_port(p);
-
- /* If we have a valid fifosize, try hooking up DMA */
- if (p->fifosize) {
- data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
- data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
- up->dma = &data->data.dma;
+ if (data->pdata == &dw8250_h616_data) {
+ dw8250_h616_setup(up, data);
+ } else {
+ if (!data->skip_autocfg)
+ dw8250_setup_port(p);
+
+ /* If we have a valid fifosize, try hooking up DMA */
+ if (p->fifosize) {
+ data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
+ data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
+ up->dma = &data->data.dma;
+ }
}
data->data.uport = serial8250_register_8250_port(up);
@@ -904,6 +974,7 @@ static const struct dw8250_platform_data dw8250_tda54 = {
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
+ { .compatible = "allwinner,sun50i-h616-uart", .data = &dw8250_h616_data },
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
{ .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },
{ .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data },