diff options
Diffstat (limited to 'drivers/tty/serial/rpi-fw-uart.c')
| -rw-r--r-- | drivers/tty/serial/rpi-fw-uart.c | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/drivers/tty/serial/rpi-fw-uart.c b/drivers/tty/serial/rpi-fw-uart.c new file mode 100644 index 000000000000..075f68ae81ac --- /dev/null +++ b/drivers/tty/serial/rpi-fw-uart.c @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024, Raspberry Pi Ltd. All rights reserved. + */ + +#include <linux/console.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <soc/bcm2835/raspberrypi-firmware.h> +#include <linux/dma-mapping.h> + +#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; +} + +static 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 tty_port *tport = &port->state->port; + + for (;;) { + unsigned int consumed; + unsigned char *tail; + unsigned int count; + + count = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, port->fifosize); + if (!count) + break; + + consumed = rpi_fw_write(port, 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_dbg(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_setup(&rfu->trigger_start_rx, rpi_fw_uart_trigger_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + 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 void 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); +} + +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 <[email protected]>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Raspberry Pi Firmware Software UART driver"); |
