| From 8c87d3224d9c3a9548b67d635397d6e96de3f984 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Mon, 4 Mar 2024 14:27:04 +0200 |
| Subject: serial: port: Introduce a common helper to read properties |
| |
| From: Andy Shevchenko <andriy.shevchenko@linux.intel.com> |
| |
| [ Upstream commit e894b6005dce0ed621b2788d6a249708fb6f95f9 ] |
| |
| Several serial drivers want to read the same or similar set of |
| the port properties. Make a common helper for them. |
| |
| Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> |
| Link: https://lore.kernel.org/r/20240304123035.758700-4-andriy.shevchenko@linux.intel.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Stable-dep-of: 87d80bfbd577 ("serial: 8250_dw: Don't use struct dw8250_data outside of 8250_dw") |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/tty/serial/serial_port.c | 145 +++++++++++++++++++++++++++++++ |
| include/linux/serial_core.h | 2 + |
| 2 files changed, 147 insertions(+) |
| |
| diff --git a/drivers/tty/serial/serial_port.c b/drivers/tty/serial/serial_port.c |
| index ed3953bd04073..469ad26cde487 100644 |
| --- a/drivers/tty/serial/serial_port.c |
| +++ b/drivers/tty/serial/serial_port.c |
| @@ -8,7 +8,10 @@ |
| |
| #include <linux/device.h> |
| #include <linux/module.h> |
| +#include <linux/of.h> |
| +#include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| +#include <linux/property.h> |
| #include <linux/serial_core.h> |
| #include <linux/spinlock.h> |
| |
| @@ -146,6 +149,148 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port) |
| } |
| EXPORT_SYMBOL(uart_remove_one_port); |
| |
| +/** |
| + * __uart_read_properties - read firmware properties of the given UART port |
| + * @port: corresponding port |
| + * @use_defaults: apply defaults (when %true) or validate the values (when %false) |
| + * |
| + * The following device properties are supported: |
| + * - clock-frequency (optional) |
| + * - fifo-size (optional) |
| + * - no-loopback-test (optional) |
| + * - reg-shift (defaults may apply) |
| + * - reg-offset (value may be validated) |
| + * - reg-io-width (defaults may apply or value may be validated) |
| + * - interrupts (OF only) |
| + * - serial [alias ID] (OF only) |
| + * |
| + * If the port->dev is of struct platform_device type the interrupt line |
| + * will be retrieved via platform_get_irq() call against that device. |
| + * Otherwise it will be assigned by fwnode_irq_get() call. In both cases |
| + * the index 0 of the resource is used. |
| + * |
| + * The caller is responsible to initialize the following fields of the @port |
| + * ->dev (must be valid) |
| + * ->flags |
| + * ->mapbase |
| + * ->mapsize |
| + * ->regshift (if @use_defaults is false) |
| + * before calling this function. Alternatively the above mentioned fields |
| + * may be zeroed, in such case the only ones, that have associated properties |
| + * found, will be set to the respective values. |
| + * |
| + * If no error happened, the ->irq, ->mapbase, ->mapsize will be altered. |
| + * The ->iotype is always altered. |
| + * |
| + * When @use_defaults is true and the respective property is not found |
| + * the following values will be applied: |
| + * ->regshift = 0 |
| + * In this case IRQ must be provided, otherwise an error will be returned. |
| + * |
| + * When @use_defaults is false and the respective property is found |
| + * the following values will be validated: |
| + * - reg-io-width (->iotype) |
| + * - reg-offset (->mapsize against ->mapbase) |
| + * |
| + * Returns: 0 on success or negative errno on failure |
| + */ |
| +static int __uart_read_properties(struct uart_port *port, bool use_defaults) |
| +{ |
| + struct device *dev = port->dev; |
| + u32 value; |
| + int ret; |
| + |
| + /* Read optional UART functional clock frequency */ |
| + device_property_read_u32(dev, "clock-frequency", &port->uartclk); |
| + |
| + /* Read the registers alignment (default: 8-bit) */ |
| + ret = device_property_read_u32(dev, "reg-shift", &value); |
| + if (ret) |
| + port->regshift = use_defaults ? 0 : port->regshift; |
| + else |
| + port->regshift = value; |
| + |
| + /* Read the registers I/O access type (default: MMIO 8-bit) */ |
| + ret = device_property_read_u32(dev, "reg-io-width", &value); |
| + if (ret) { |
| + port->iotype = UPIO_MEM; |
| + } else { |
| + switch (value) { |
| + case 1: |
| + port->iotype = UPIO_MEM; |
| + break; |
| + case 2: |
| + port->iotype = UPIO_MEM16; |
| + break; |
| + case 4: |
| + port->iotype = device_is_big_endian(dev) ? UPIO_MEM32BE : UPIO_MEM32; |
| + break; |
| + default: |
| + if (!use_defaults) { |
| + dev_err(dev, "Unsupported reg-io-width (%u)\n", value); |
| + return -EINVAL; |
| + } |
| + port->iotype = UPIO_UNKNOWN; |
| + break; |
| + } |
| + } |
| + |
| + /* Read the address mapping base offset (default: no offset) */ |
| + ret = device_property_read_u32(dev, "reg-offset", &value); |
| + if (ret) |
| + value = 0; |
| + |
| + /* Check for shifted address mapping overflow */ |
| + if (!use_defaults && port->mapsize < value) { |
| + dev_err(dev, "reg-offset %u exceeds region size %pa\n", value, &port->mapsize); |
| + return -EINVAL; |
| + } |
| + |
| + port->mapbase += value; |
| + port->mapsize -= value; |
| + |
| + /* Read optional FIFO size */ |
| + device_property_read_u32(dev, "fifo-size", &port->fifosize); |
| + |
| + if (device_property_read_bool(dev, "no-loopback-test")) |
| + port->flags |= UPF_SKIP_TEST; |
| + |
| + /* Get index of serial line, if found in DT aliases */ |
| + ret = of_alias_get_id(dev_of_node(dev), "serial"); |
| + if (ret >= 0) |
| + port->line = ret; |
| + |
| + if (dev_is_platform(dev)) |
| + ret = platform_get_irq(to_platform_device(dev), 0); |
| + else |
| + ret = fwnode_irq_get(dev_fwnode(dev), 0); |
| + if (ret == -EPROBE_DEFER) |
| + return ret; |
| + if (ret > 0) |
| + port->irq = ret; |
| + else if (use_defaults) |
| + /* By default IRQ support is mandatory */ |
| + return ret; |
| + else |
| + port->irq = 0; |
| + |
| + port->flags |= UPF_SHARE_IRQ; |
| + |
| + return 0; |
| +} |
| + |
| +int uart_read_port_properties(struct uart_port *port) |
| +{ |
| + return __uart_read_properties(port, true); |
| +} |
| +EXPORT_SYMBOL_GPL(uart_read_port_properties); |
| + |
| +int uart_read_and_validate_port_properties(struct uart_port *port) |
| +{ |
| + return __uart_read_properties(port, false); |
| +} |
| +EXPORT_SYMBOL_GPL(uart_read_and_validate_port_properties); |
| + |
| static struct device_driver serial_port_driver = { |
| .name = "port", |
| .suppress_bind_attrs = true, |
| diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h |
| index 412de73547521..5da5eb719f614 100644 |
| --- a/include/linux/serial_core.h |
| +++ b/include/linux/serial_core.h |
| @@ -961,6 +961,8 @@ int uart_register_driver(struct uart_driver *uart); |
| void uart_unregister_driver(struct uart_driver *uart); |
| int uart_add_one_port(struct uart_driver *reg, struct uart_port *port); |
| void uart_remove_one_port(struct uart_driver *reg, struct uart_port *port); |
| +int uart_read_port_properties(struct uart_port *port); |
| +int uart_read_and_validate_port_properties(struct uart_port *port); |
| bool uart_match_port(const struct uart_port *port1, |
| const struct uart_port *port2); |
| |
| -- |
| 2.43.0 |
| |