diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
index b023a7e0741d67..367cbf437e1b0f 100644
--- a/arch/arm/boot/dts/overlays/Makefile
+++ b/arch/arm/boot/dts/overlays/Makefile
@@ -233,6 +233,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
rpi-dacpro.dtbo \
rpi-digiampplus.dtbo \
rpi-ft5406.dtbo \
+ rpi-fw-uart.dtbo \
rpi-poe.dtbo \
rpi-poe-plus.dtbo \
rpi-sense.dtbo \
diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README
index 2da54f781e3b94..f3c0eb2fef05ff 100644
--- a/arch/arm/boot/dts/overlays/README
+++ b/arch/arm/boot/dts/overlays/README
@@ -4141,6 +4141,18 @@ Params: touchscreen-size-x Touchscreen X resolution (default 800)
touchscreen-swapped-x-y Swap X and Y cordinates (default 0);
+Name: rpi-fw-uart
+Info: Configures the firmware software UART driver.
+ This driver requires exclusive usage of the second VPU core. The
+ following config.txt entries should be set when this driver is used.
+ dtparam=audio=off
+ isp_use_vpu0=1
+Load: dtoverlay=rpi-fw-uart,[=]
+Params: txd0_pin GPIO pin for TXD0 (any free - default 20)
+
+ rxd0_pin GPIO pin for RXD0 (any free - default 21)
+
+
Name: rpi-poe
Info: Raspberry Pi PoE HAT fan
Load: dtoverlay=rpi-poe,[=]
diff --git a/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts
new file mode 100644
index 00000000000000..c17556e91105d8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+// Overlay for the Raspberry Pi Firmware UART driver
+/dts-v1/;
+/plugin/;
+
+/{
+ compatible = "brcm,bcm2835";
+
+ fragment@0 {
+ target = <&gpio>;
+ __overlay__ {
+ rpi_fw_uart_pins: rpi_fw_uart_pins@4 {
+ brcm,pins = <20 21>;
+ brcm,function = <1 0>; /* output input */
+ brcm,pull = <0 2>; /* none pull-up */
+ };
+ };
+ };
+
+ fragment@1 {
+ target = <&soc>;
+ __overlay__ {
+ rpi_fw_uart: rpi_fw_uart@7e000000 {
+ compatible = "raspberrypi,firmware-uart";
+ reg = <0x7e000000 0x100>; /* VideoCore MS sync regs */
+ firmware = <&firmware>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&rpi_fw_uart_pins>;
+ tx-gpios = <&gpio 20 0>;
+ rx-gpios = <&gpio 21 0>;
+ };
+ };
+ };
+
+ __overrides__ {
+ txd0_pin = <&rpi_fw_uart>,"tx-gpios:4",
+ <&rpi_fw_uart_pins>, "brcm,pins:0";
+ rxd0_pin = <&rpi_fw_uart>,"rx-gpios:4",
+ <&rpi_fw_uart_pins>, "brcm,pins:4";
+ };
+};
diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig
index c8402e98544813..963641976cfb8c 100644
--- a/arch/arm/configs/bcm2709_defconfig
+++ b/arch/arm/configs/bcm2709_defconfig
@@ -671,6 +671,7 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_SC16IS7XX=m
CONFIG_SERIAL_SC16IS7XX_SPI=y
+CONFIG_SERIAL_RPI_FW=m
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
CONFIG_HW_RANDOM=y
diff --git a/arch/arm/configs/bcm2711_defconfig b/arch/arm/configs/bcm2711_defconfig
index e90b9b6e7cf24c..513b8b4b731692 100644
--- a/arch/arm/configs/bcm2711_defconfig
+++ b/arch/arm/configs/bcm2711_defconfig
@@ -689,6 +689,7 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_SC16IS7XX=m
CONFIG_SERIAL_SC16IS7XX_SPI=y
+CONFIG_SERIAL_RPI_FW=m
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
CONFIG_HW_RANDOM=y
diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig
index f1051be76fe95b..a88bad286cf7a6 100644
--- a/arch/arm/configs/bcmrpi_defconfig
+++ b/arch/arm/configs/bcmrpi_defconfig
@@ -666,6 +666,7 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_SC16IS7XX=m
CONFIG_SERIAL_SC16IS7XX_SPI=y
+CONFIG_SERIAL_RPI_FW=m
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
CONFIG_HW_RANDOM=y
diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig
index d099ebfe5a8c3f..7b6d2a0c1a2b83 100644
--- a/arch/arm64/configs/bcm2711_defconfig
+++ b/arch/arm64/configs/bcm2711_defconfig
@@ -712,6 +712,7 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_SC16IS7XX=m
CONFIG_SERIAL_SC16IS7XX_SPI=y
+CONFIG_SERIAL_RPI_FW=m
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
CONFIG_HW_RANDOM=y
diff --git a/arch/arm64/configs/bcmrpi3_defconfig b/arch/arm64/configs/bcmrpi3_defconfig
index 1a22245321d823..951dcc6915c094 100644
--- a/arch/arm64/configs/bcmrpi3_defconfig
+++ b/arch/arm64/configs/bcmrpi3_defconfig
@@ -669,6 +669,7 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_SC16IS7XX=m
CONFIG_SERIAL_SC16IS7XX_SPI=y
+CONFIG_SERIAL_RPI_FW=m
CONFIG_SERIAL_DEV_BUS=y
CONFIG_TTY_PRINTK=y
CONFIG_HW_RANDOM=y
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index bdc568a4ab6693..99b4808dbe2f78 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1578,6 +1578,17 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE
but you can alter that using a kernel command line option such as
"console=ttyNVTx".
+config SERIAL_RPI_FW
+ tristate "Raspberry Pi Firmware software UART support"
+ depends on ARM_AMBA || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ This selects the Raspberry Pi firmware UART. This is a bit-bashed
+ implementation running on the Raspbery Pi VPU core.
+ This is not supported on Raspberry Pi 5 or newer platforms.
+
+ If unsure, say N.
+
endmenu
config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 138abbc8973812..ef0cc5d6fc4cc8 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -88,6 +88,7 @@ obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o
obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
obj-$(CONFIG_SERIAL_SUNPLUS) += sunplus-uart.o
+obj-$(CONFIG_SERIAL_RPI_FW) += rpi-fw-uart.o
# GPIOLIB helpers for modem control lines
obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/rpi-fw-uart.c b/drivers/tty/serial/rpi-fw-uart.c
new file mode 100644
index 00000000000000..bcf6c8ac4a4f94
--- /dev/null
+++ b/drivers/tty/serial/rpi-fw-uart.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024, Raspberry Pi Ltd. All rights reserved.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define RPI_FW_UART_RX_FIFO_RD 0xb0
+#define RPI_FW_UART_RX_FIFO_WR 0xb4
+#define RPI_FW_UART_TX_FIFO_RD 0xb8
+#define RPI_FW_UART_TX_FIFO_WR 0xbc
+
+#define RPI_FW_UART_FIFO_SIZE 32
+#define RPI_FW_UART_FIFO_SIZE_MASK (RPI_FW_UART_FIFO_SIZE - 1)
+
+#define RPI_FW_UART_MIN_VERSION 3
+
+struct rpi_fw_uart_params {
+ u32 start;
+ u32 baud;
+ u32 data_bits;
+ u32 stop_bits;
+ u32 gpio_rx;
+ u32 gpio_tx;
+ u32 flags;
+ u32 fifosize;
+ u32 rx_buffer;
+ u32 tx_buffer;
+ u32 version;
+ u32 fifo_reg_base;
+};
+
+struct rpi_fw_uart {
+ struct uart_driver driver;
+ struct uart_port port;
+ struct rpi_firmware *firmware;
+ struct gpio_desc *rx_gpiod;
+ struct gpio_desc *tx_gpiod;
+ unsigned int rx_gpio;
+ unsigned int tx_gpio;
+ unsigned int baud;
+ unsigned int data_bits;
+ unsigned int stop_bits;
+ unsigned char __iomem *base;
+ size_t dma_buffer_size;
+
+ struct hrtimer trigger_start_rx;
+ ktime_t rx_poll_delay;
+ void *rx_buffer;
+ dma_addr_t rx_buffer_dma_addr;
+ int rx_stop;
+
+ void *tx_buffer;
+ dma_addr_t tx_buffer_dma_addr;
+};
+
+static unsigned int rpi_fw_uart_tx_is_full(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+ u32 rd, wr;
+
+ rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD);
+ wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR);
+ return ((wr + 1) & RPI_FW_UART_FIFO_SIZE_MASK) == rd;
+}
+
+static unsigned int rpi_fw_uart_tx_is_empty(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+ u32 rd, wr;
+
+ if (!rfu->tx_buffer)
+ return 1;
+
+ rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD);
+ wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR);
+
+ return rd == wr;
+}
+
+unsigned int rpi_fw_uart_rx_is_empty(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+ u32 rd, wr;
+
+ if (!rfu->rx_buffer)
+ return 1;
+
+ rd = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD);
+ wr = readl(rfu->base + RPI_FW_UART_RX_FIFO_WR);
+
+ return rd == wr;
+}
+
+static unsigned int rpi_fw_uart_tx_empty(struct uart_port *port)
+{
+ return rpi_fw_uart_tx_is_empty(port) ? TIOCSER_TEMT : 0;
+}
+
+static void rpi_fw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ /*
+ * No hardware flow control, firmware automatically configures
+ * TX to output high and RX to input low.
+ */
+ dev_dbg(port->dev, "%s mctrl %u\n", __func__, mctrl);
+}
+
+static unsigned int rpi_fw_uart_get_mctrl(struct uart_port *port)
+{
+ /* No hardware flow control */
+ return TIOCM_CTS;
+}
+
+static void rpi_fw_uart_stop(struct uart_port *port)
+{
+ struct rpi_fw_uart_params msg = {.start = 0};
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+ hrtimer_cancel(&rfu->trigger_start_rx);
+
+ if (rpi_firmware_property(rfu->firmware,
+ RPI_FIRMWARE_SET_SW_UART,
+ &msg, sizeof(msg)))
+ dev_warn(port->dev,
+ "Failed to shutdown rpi-fw uart. Firmware not configured?");
+}
+
+static void rpi_fw_uart_stop_tx(struct uart_port *port)
+{
+ /* No supported by the current firmware APIs. */
+}
+
+static void rpi_fw_uart_stop_rx(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+ rfu->rx_stop = 1;
+}
+
+static unsigned int rpi_fw_write(struct uart_port *port, const char *s,
+ unsigned int count)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+ u8 *out = rfu->tx_buffer;
+ unsigned int consumed = 0;
+
+ while (consumed < count && !rpi_fw_uart_tx_is_full(port)) {
+ u32 wp = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR)
+ & RPI_FW_UART_FIFO_SIZE_MASK;
+ out[wp] = s[consumed++];
+ wp = (wp + 1) & RPI_FW_UART_FIFO_SIZE_MASK;
+ writel(wp, rfu->base + RPI_FW_UART_TX_FIFO_WR);
+ }
+ return consumed;
+}
+
+/* Called with port.lock taken */
+static void rpi_fw_uart_start_tx(struct uart_port *port)
+{
+ struct circ_buf *xmit;
+
+ xmit = &port->state->xmit;
+ for (;;) {
+ unsigned int consumed;
+ unsigned long count = CIRC_CNT_TO_END(xmit->head, xmit->tail,
+ UART_XMIT_SIZE);
+ if (!count)
+ break;
+
+ consumed = rpi_fw_write(port, &xmit->buf[xmit->tail], count);
+ uart_xmit_advance(port, consumed);
+ }
+ uart_write_wakeup(port);
+}
+
+/* Called with port.lock taken */
+static void rpi_fw_uart_start_rx(struct uart_port *port)
+{
+ struct tty_port *tty_port = &port->state->port;
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+ int count = 0;
+
+ /*
+ * RX is polled, read up to a full buffer of data before trying again
+ * so that this can be interrupted if the firmware is filling the
+ * buffer too fast
+ */
+ while (!rpi_fw_uart_rx_is_empty(port) && count < port->fifosize) {
+ const u8 *in = rfu->rx_buffer;
+ u32 rp = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD)
+ & RPI_FW_UART_FIFO_SIZE_MASK;
+
+ tty_insert_flip_char(tty_port, in[rp], TTY_NORMAL);
+ rp = (rp + 1) & RPI_FW_UART_FIFO_SIZE_MASK;
+ writel(rp, rfu->base + RPI_FW_UART_RX_FIFO_RD);
+ count++;
+ }
+ if (count)
+ tty_flip_buffer_push(tty_port);
+}
+
+static enum hrtimer_restart rpi_fw_uart_trigger_rx(struct hrtimer *t)
+{
+ unsigned long flags;
+ struct rpi_fw_uart *rfu = container_of(t, struct rpi_fw_uart,
+ trigger_start_rx);
+
+ spin_lock_irqsave(&rfu->port.lock, flags);
+ if (rfu->rx_stop) {
+ spin_unlock_irqrestore(&rfu->port.lock, flags);
+ return HRTIMER_NORESTART;
+ }
+
+ rpi_fw_uart_start_rx(&rfu->port);
+ spin_unlock_irqrestore(&rfu->port.lock, flags);
+ hrtimer_forward_now(t, rfu->rx_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static void rpi_fw_uart_break_ctl(struct uart_port *port, int ctl)
+{
+ dev_dbg(port->dev, "%s ctl %d\n", __func__, ctl);
+}
+
+static int rpi_fw_uart_configure(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+ struct rpi_fw_uart_params msg;
+ unsigned long flags;
+ int rc;
+
+ rpi_fw_uart_stop(port);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.start = 1;
+ msg.gpio_rx = rfu->rx_gpio;
+ msg.gpio_tx = rfu->tx_gpio;
+ msg.data_bits = rfu->data_bits;
+ msg.stop_bits = rfu->stop_bits;
+ msg.baud = rfu->baud;
+ msg.fifosize = RPI_FW_UART_FIFO_SIZE;
+ msg.rx_buffer = (u32) rfu->rx_buffer_dma_addr;
+ msg.tx_buffer = (u32) rfu->tx_buffer_dma_addr;
+
+ rfu->rx_poll_delay = ms_to_ktime(50);
+
+ /*
+ * Reconfigures the firmware UART with the new settings. On the first
+ * call retrieve the addresses of the FIFO buffers. The buffers are
+ * allocated at startup and are not de-allocated.
+ * NB rpi_firmware_property can block
+ */
+ rc = rpi_firmware_property(rfu->firmware,
+ RPI_FIRMWARE_SET_SW_UART,
+ &msg, sizeof(msg));
+ if (rc)
+ goto fail;
+
+ rc = rpi_firmware_property(rfu->firmware,
+ RPI_FIRMWARE_GET_SW_UART,
+ &msg, sizeof(msg));
+ if (rc)
+ goto fail;
+
+ dev_dbg(port->dev, "version %08x, reg addr %x\n", msg.version,
+ msg.fifo_reg_base);
+
+ dev_info(port->dev, "started %d baud %u data %u stop %u rx %u tx %u flags %u fifosize %u\n",
+ msg.start, msg.baud, msg.data_bits, msg.stop_bits,
+ msg.gpio_rx, msg.gpio_tx, msg.flags, msg.fifosize);
+
+ if (msg.fifosize != port->fifosize) {
+ dev_err(port->dev, "Expected fifo size %u actual %u",
+ port->fifosize, msg.fifosize);
+ rc = -EINVAL;
+ goto fail;
+ }
+
+ if (!msg.start) {
+ dev_err(port->dev, "Firmware service not running\n");
+ rc = -EINVAL;
+ }
+
+ spin_lock_irqsave(&rfu->port.lock, flags);
+ rfu->rx_stop = 0;
+ hrtimer_start(&rfu->trigger_start_rx,
+ rfu->rx_poll_delay, HRTIMER_MODE_REL);
+ spin_unlock_irqrestore(&rfu->port.lock, flags);
+ return 0;
+fail:
+ dev_err(port->dev, "Failed to configure rpi-fw uart. Firmware not configured?");
+ return rc;
+}
+
+static void rpi_fw_uart_free_buffers(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+ if (rfu->rx_buffer)
+ dma_free_coherent(port->dev, rfu->dma_buffer_size,
+ rfu->rx_buffer, GFP_ATOMIC);
+
+ if (rfu->tx_buffer)
+ dma_free_coherent(port->dev, rfu->dma_buffer_size,
+ rfu->tx_buffer, GFP_ATOMIC);
+
+ rfu->rx_buffer = NULL;
+ rfu->tx_buffer = NULL;
+ rfu->rx_buffer_dma_addr = 0;
+ rfu->tx_buffer_dma_addr = 0;
+}
+
+static int rpi_fw_uart_alloc_buffers(struct uart_port *port)
+{
+ struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+ if (rfu->tx_buffer)
+ return 0;
+
+ rfu->dma_buffer_size = PAGE_ALIGN(RPI_FW_UART_FIFO_SIZE);
+
+ rfu->rx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size,
+ &rfu->rx_buffer_dma_addr, GFP_ATOMIC);
+
+ if (!rfu->rx_buffer)
+ goto alloc_fail;
+
+ rfu->tx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size,
+ &rfu->tx_buffer_dma_addr, GFP_ATOMIC);
+
+ if (!rfu->tx_buffer)
+ goto alloc_fail;
+
+ dev_dbg(port->dev, "alloc-buffers %p %x %p %x\n",
+ rfu->rx_buffer, (u32) rfu->rx_buffer_dma_addr,
+ rfu->tx_buffer, (u32) rfu->tx_buffer_dma_addr);
+ return 0;
+
+alloc_fail:
+ dev_err(port->dev, "%s uart buffer allocation failed\n", __func__);
+ rpi_fw_uart_free_buffers(port);
+ return -ENOMEM;
+}
+
+static int rpi_fw_uart_startup(struct uart_port *port)
+{
+ int rc;
+
+ rc = rpi_fw_uart_alloc_buffers(port);
+ if (rc)
+ dev_err(port->dev, "Failed to start\n");
+ return rc;
+}
+
+static void rpi_fw_uart_shutdown(struct uart_port *port)
+{
+ rpi_fw_uart_stop(port);
+ rpi_fw_uart_free_buffers(port);
+}
+
+static void rpi_fw_uart_set_termios(struct uart_port *port,
+ struct ktermios *new,
+ const struct ktermios *old)
+{
+ struct rpi_fw_uart *rfu =
+ container_of(port, struct rpi_fw_uart, port);
+ rfu->baud = uart_get_baud_rate(port, new, old, 50, 115200);
+ rfu->stop_bits = (new->c_cflag & CSTOPB) ? 2 : 1;
+
+ rpi_fw_uart_configure(port);
+}
+
+static const struct uart_ops rpi_fw_uart_ops = {
+ .tx_empty = rpi_fw_uart_tx_empty,
+ .set_mctrl = rpi_fw_uart_set_mctrl,
+ .get_mctrl = rpi_fw_uart_get_mctrl,
+ .stop_rx = rpi_fw_uart_stop_rx,
+ .stop_tx = rpi_fw_uart_stop_tx,
+ .start_tx = rpi_fw_uart_start_tx,
+ .break_ctl = rpi_fw_uart_break_ctl,
+ .startup = rpi_fw_uart_startup,
+ .shutdown = rpi_fw_uart_shutdown,
+ .set_termios = rpi_fw_uart_set_termios,
+};
+
+static int rpi_fw_uart_get_gpio_offset(struct device *dev, const char *name)
+{
+ struct of_phandle_args of_args = { 0 };
+ bool is_bcm28xx;
+
+ /* This really shouldn't fail, given that we have a gpiod */
+ if (of_parse_phandle_with_args(dev->of_node, name, "#gpio-cells", 0, &of_args))
+ return dev_err_probe(dev, -EINVAL, "can't find gpio declaration\n");
+
+ is_bcm28xx = of_device_is_compatible(of_args.np, "brcm,bcm2835-gpio") ||
+ of_device_is_compatible(of_args.np, "brcm,bcm2711-gpio");
+ of_node_put(of_args.np);
+ if (!is_bcm28xx || of_args.args_count != 2)
+ return dev_err_probe(dev, -EINVAL, "not a BCM28xx gpio\n");
+
+ return of_args.args[0];
+}
+
+static int rpi_fw_uart_probe(struct platform_device *pdev)
+{
+ struct device_node *firmware_node;
+ struct device *dev = &pdev->dev;
+ struct rpi_firmware *firmware;
+ struct uart_port *port;
+ struct rpi_fw_uart *rfu;
+ struct rpi_fw_uart_params msg;
+ int version_major;
+ int err;
+
+ dev_dbg(dev, "%s of_node %p\n", __func__, dev->of_node);
+
+ /*
+ * We can be probed either through the an old-fashioned
+ * platform device registration or through a DT node that is a
+ * child of the firmware node. Handle both cases.
+ */
+ if (dev->of_node)
+ firmware_node = of_parse_phandle(dev->of_node, "firmware", 0);
+ else
+ firmware_node = of_find_compatible_node(NULL, NULL,
+ "raspberrypi,bcm2835-firmware");
+ if (!firmware_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ firmware = devm_rpi_firmware_get(dev, firmware_node);
+ of_node_put(firmware_node);
+ if (!firmware)
+ return -EPROBE_DEFER;
+
+ rfu = devm_kzalloc(dev, sizeof(*rfu), GFP_KERNEL);
+ if (!rfu)
+ return -ENOMEM;
+
+ rfu->firmware = firmware;
+
+ err = rpi_firmware_property(rfu->firmware, RPI_FIRMWARE_GET_SW_UART,
+ &msg, sizeof(msg));
+ if (err) {
+ dev_err(dev, "VC firmware does not support rpi-fw-uart\n");
+ return err;
+ }
+
+ version_major = msg.version >> 16;
+ if (msg.version < RPI_FW_UART_MIN_VERSION) {
+ dev_err(dev, "rpi-fw-uart fw version %d is too old min version %d\n",
+ version_major, RPI_FW_UART_MIN_VERSION);
+ return -EINVAL;
+ }
+
+ rfu->rx_gpiod = devm_gpiod_get(dev, "rx", GPIOD_IN);
+ if (IS_ERR(rfu->rx_gpiod))
+ return PTR_ERR(rfu->rx_gpiod);
+
+ rfu->tx_gpiod = devm_gpiod_get(dev, "tx", GPIOD_OUT_HIGH);
+ if (IS_ERR(rfu->tx_gpiod))
+ return PTR_ERR(rfu->tx_gpiod);
+
+ rfu->rx_gpio = rpi_fw_uart_get_gpio_offset(dev, "rx-gpios");
+ if (rfu->rx_gpio < 0)
+ return rfu->rx_gpio;
+ rfu->tx_gpio = rpi_fw_uart_get_gpio_offset(dev, "tx-gpios");
+ if (rfu->tx_gpio < 0)
+ return rfu->tx_gpio;
+
+ rfu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rfu->base))
+ return PTR_ERR(rfu->base);
+
+ /* setup the driver */
+ rfu->driver.owner = THIS_MODULE;
+ rfu->driver.driver_name = "ttyRFU";
+ rfu->driver.dev_name = "ttyRFU";
+ rfu->driver.nr = 1;
+ rfu->data_bits = 8;
+
+ /* RX is polled */
+ hrtimer_init(&rfu->trigger_start_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ rfu->trigger_start_rx.function = rpi_fw_uart_trigger_rx;
+
+ err = uart_register_driver(&rfu->driver);
+ if (err) {
+ dev_err(dev, "failed to register UART driver: %d\n",
+ err);
+ return err;
+ }
+
+ /* setup the port */
+ port = &rfu->port;
+ spin_lock_init(&port->lock);
+ port->dev = &pdev->dev;
+ port->type = PORT_RPI_FW;
+ port->ops = &rpi_fw_uart_ops;
+ port->fifosize = RPI_FW_UART_FIFO_SIZE;
+ port->iotype = UPIO_MEM;
+ port->flags = UPF_BOOT_AUTOCONF;
+ port->private_data = rfu;
+
+ err = uart_add_one_port(&rfu->driver, port);
+ if (err) {
+ dev_err(dev, "failed to add UART port: %d\n", err);
+ goto unregister_uart;
+ }
+ platform_set_drvdata(pdev, rfu);
+
+ dev_info(dev, "version %d.%d gpios tx %u rx %u\n",
+ msg.version >> 16, msg.version & 0xffff,
+ rfu->tx_gpio, rfu->rx_gpio);
+ return 0;
+
+unregister_uart:
+ uart_unregister_driver(&rfu->driver);
+
+ return err;
+}
+
+static int rpi_fw_uart_remove(struct platform_device *pdev)
+{
+ struct rpi_fw_uart *rfu = platform_get_drvdata(pdev);
+
+ uart_remove_one_port(&rfu->driver, &rfu->port);
+ uart_unregister_driver(&rfu->driver);
+
+ return 0;
+}
+
+static const struct of_device_id rpi_fw_match[] = {
+ { .compatible = "raspberrypi,firmware-uart" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rpi_fw_match);
+
+static struct platform_driver rpi_fw_driver = {
+ .driver = {
+ .name = "rpi_fw-uart",
+ .of_match_table = rpi_fw_match,
+ },
+ .probe = rpi_fw_uart_probe,
+ .remove = rpi_fw_uart_remove,
+};
+module_platform_driver(rpi_fw_driver);
+
+MODULE_AUTHOR("Tim Gover ");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Raspberry Pi Firmware Software UART driver");
diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h
index b4bc8b675607c8..3e71d45477a422 100644
--- a/include/soc/bcm2835/raspberrypi-firmware.h
+++ b/include/soc/bcm2835/raspberrypi-firmware.h
@@ -98,6 +98,8 @@ enum rpi_firmware_property_tag {
RPI_FIRMWARE_GET_REBOOT_FLAGS = 0x00030064,
RPI_FIRMWARE_SET_REBOOT_FLAGS = 0x00038064,
RPI_FIRMWARE_NOTIFY_DISPLAY_DONE = 0x00030066,
+ RPI_FIRMWARE_GET_SW_UART = 0x0003008a,
+ RPI_FIRMWARE_SET_SW_UART = 0x0003808a,
/* Dispmanx TAGS */
RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE = 0x00040001,
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index add349889d0a39..6e0a49c65b1196 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -245,4 +245,7 @@
/* Sunplus UART */
#define PORT_SUNPLUS 123
+/* RPi firmware UART */
+#define PORT_RPI_FW 124
+
#endif /* _UAPILINUX_SERIAL_CORE_H */