Commit e894b600 authored by Andy Shevchenko's avatar Andy Shevchenko Committed by Greg Kroah-Hartman
Browse files

serial: port: Introduce a common helper to read properties



Several serial drivers want to read the same or similar set of
the port properties. Make a common helper for them.

Signed-off-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20240304123035.758700-4-andriy.shevchenko@linux.intel.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 79d713ba
Loading
Loading
Loading
Loading
+145 −0
Original line number Diff line number Diff line
@@ -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>

@@ -82,6 +85,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,
+2 −0
Original line number Diff line number Diff line
@@ -963,6 +963,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);