aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/uart/uart_dev_pl011.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/uart/uart_dev_pl011.c')
-rw-r--r--sys/dev/uart/uart_dev_pl011.c48
1 files changed, 44 insertions, 4 deletions
diff --git a/sys/dev/uart/uart_dev_pl011.c b/sys/dev/uart/uart_dev_pl011.c
index daba9d19704c..a0d5a5b1c7e2 100644
--- a/sys/dev/uart/uart_dev_pl011.c
+++ b/sys/dev/uart/uart_dev_pl011.c
@@ -172,6 +172,27 @@ static int
uart_pl011_probe(struct uart_bas *bas)
{
+ /*
+ * Versions of QEMU before 41f7b58b634e (8.3) reported bogus values for
+ * this tabel. The PL011 IP is always 32-bits wide and should be shifted
+ * 2 to match the 4-byte size of the data. QEMU reported these values
+ * incorrectly before that.
+ * https://github.com/qemu/qemu/commit/41f7b58b634ec3b60ae874375d2bbb61d790971e
+ *
+ * In additon, other hardware vendors also reported this value
+ * incorrectly. It's not tied to what the ACPI device node is, but was a
+ * misunderstanding coupled with a Linux driver that didn't need the
+ * right values. Quirks used to be used to ignore the bad values, now we
+ * detect the historic mistake and override (to allow for a future where
+ * we may need to override these values).
+ *
+ * PL011 Docs: https://developer.arm.com/documentation/ddi0183/latest/
+ */
+ if (bas->regshft == 0 || bas->regiowidth == 1) {
+ bas->regshft = 2;
+ bas->regiowidth = 4;
+ }
+
return (0);
}
@@ -231,6 +252,24 @@ uart_pl011_param(struct uart_bas *bas, int baudrate, int databits, int stopbits,
__uart_setreg(bas, UART_IFLS, FIFO_IFLS_BITS);
__uart_setreg(bas, UART_CR, ctrl);
+
+ /*
+ * Loader tells us to infer the rclk when it sets xo to 0 in
+ * hw.uart.console. The APCI SPCR code does likewise. We know the
+ * baudrate was set by the firmware, so calculate rclk from baudrate and
+ * the divisor register. If 'div' is actually 0, the resulting 0 value
+ * will have us fall back to other rclk methods. This method should be
+ * good to 5% or better because the error in baud rates needs to be
+ * below this for devices to communicate.
+ */
+ if (bas->rclk == 0 && baudrate > 0 && bas->rclk_guess) {
+ uint32_t div;
+
+ div = ((__uart_getreg(bas, UART_IBRD) & IBRD_BDIVINT) << 6) |
+ (__uart_getreg(bas, UART_FBRD) & FBRD_BDIVFRAC);
+ bas->rclk = (div * baudrate) / 4;
+ }
+
}
static void
@@ -338,7 +377,8 @@ static struct uart_class uart_pl011_class = {
.uc_ops = &uart_pl011_ops,
.uc_range = 0x48,
.uc_rclk = 0,
- .uc_rshift = 2
+ .uc_rshift = 2,
+ .uc_riowidth = 4,
};
UART_CLASS(uart_pl011_class);
@@ -352,9 +392,9 @@ UART_FDT_CLASS_AND_DEVICE(fdt_compat_data);
#ifdef DEV_ACPI
static struct acpi_uart_compat_data acpi_compat_data[] = {
- {"ARMH0011", &uart_pl011_class, ACPI_DBG2_ARM_PL011, 2, 0, 0, UART_F_IGNORE_SPCR_REGSHFT, "uart pl011"},
- {"ARMHB000", &uart_pl011_class, ACPI_DBG2_ARM_SBSA_GENERIC, 2, 0, 0, UART_F_IGNORE_SPCR_REGSHFT, "uart pl011"},
- {"ARMHB000", &uart_pl011_class, ACPI_DBG2_ARM_SBSA_32BIT, 2, 0, 0, UART_F_IGNORE_SPCR_REGSHFT, "uart pl011"},
+ {"ARMH0011", &uart_pl011_class, ACPI_DBG2_ARM_PL011, 2, 0, 0, 0, "uart pl011"},
+ {"ARMHB000", &uart_pl011_class, ACPI_DBG2_ARM_SBSA_GENERIC, 2, 0, 0, 0, "uart pl011"},
+ {"ARMHB000", &uart_pl011_class, ACPI_DBG2_ARM_SBSA_32BIT, 2, 0, 0, 0, "uart pl011"},
{NULL, NULL, 0, 0, 0, 0, 0, NULL},
};
UART_ACPI_CLASS_AND_DEVICE(acpi_compat_data);