| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Serial base bus layer for controllers | |
| * | |
| * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/ | |
| * Author: Tony Lindgren <tony@atomide.com> | |
| * | |
| * The serial core bus manages the serial core controller instances. | |
| */ | |
| #include <linux/cleanup.h> | |
| #include <linux/container_of.h> | |
| #include <linux/device.h> | |
| #include <linux/idr.h> | |
| #include <linux/module.h> | |
| #include <linux/property.h> | |
| #include <linux/serial_core.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include "serial_base.h" | |
| static bool serial_base_initialized; | |
| static const struct device_type serial_ctrl_type = { | |
| .name = "ctrl", | |
| }; | |
| static const struct device_type serial_port_type = { | |
| .name = "port", | |
| }; | |
| static int serial_base_match(struct device *dev, const struct device_driver *drv) | |
| { | |
| if (dev->type == &serial_ctrl_type && | |
| str_has_prefix(drv->name, serial_ctrl_type.name)) | |
| return 1; | |
| if (dev->type == &serial_port_type && | |
| str_has_prefix(drv->name, serial_port_type.name)) | |
| return 1; | |
| return 0; | |
| } | |
| static const struct bus_type serial_base_bus_type = { | |
| .name = "serial-base", | |
| .match = serial_base_match, | |
| }; | |
| int serial_base_driver_register(struct device_driver *driver) | |
| { | |
| driver->bus = &serial_base_bus_type; | |
| return driver_register(driver); | |
| } | |
| void serial_base_driver_unregister(struct device_driver *driver) | |
| { | |
| driver_unregister(driver); | |
| } | |
| /* On failure the caller must put device @dev with put_device() */ | |
| static int serial_base_device_init(struct uart_port *port, | |
| struct device *dev, | |
| struct device *parent_dev, | |
| const struct device_type *type, | |
| void (*release)(struct device *dev), | |
| unsigned int ctrl_id, | |
| unsigned int port_id) | |
| { | |
| device_initialize(dev); | |
| dev->type = type; | |
| dev->parent = parent_dev; | |
| dev->bus = &serial_base_bus_type; | |
| dev->release = release; | |
| dev_set_of_node_reused(dev); | |
| device_set_node(dev, fwnode_handle_get(dev_fwnode(parent_dev))); | |
| if (!serial_base_initialized) { | |
| dev_dbg(port->dev, "uart_add_one_port() called before arch_initcall()?\n"); | |
| return -EPROBE_DEFER; | |
| } | |
| if (type == &serial_ctrl_type) | |
| return dev_set_name(dev, "%s:%d", dev_name(port->dev), ctrl_id); | |
| if (type == &serial_port_type) | |
| return dev_set_name(dev, "%s:%d.%d", dev_name(port->dev), | |
| ctrl_id, port_id); | |
| return -EINVAL; | |
| } | |
| static void serial_base_ctrl_release(struct device *dev) | |
| { | |
| struct serial_ctrl_device *ctrl_dev = to_serial_base_ctrl_device(dev); | |
| fwnode_handle_put(dev_fwnode(dev)); | |
| kfree(ctrl_dev); | |
| } | |
| void serial_base_ctrl_device_remove(struct serial_ctrl_device *ctrl_dev) | |
| { | |
| if (!ctrl_dev) | |
| return; | |
| device_del(&ctrl_dev->dev); | |
| put_device(&ctrl_dev->dev); | |
| } | |
| struct serial_ctrl_device *serial_base_ctrl_add(struct uart_port *port, | |
| struct device *parent) | |
| { | |
| struct serial_ctrl_device *ctrl_dev; | |
| int err; | |
| ctrl_dev = kzalloc_obj(*ctrl_dev); | |
| if (!ctrl_dev) | |
| return ERR_PTR(-ENOMEM); | |
| ida_init(&ctrl_dev->port_ida); | |
| err = serial_base_device_init(port, &ctrl_dev->dev, | |
| parent, &serial_ctrl_type, | |
| serial_base_ctrl_release, | |
| port->ctrl_id, 0); | |
| if (err) | |
| goto err_put_device; | |
| err = device_add(&ctrl_dev->dev); | |
| if (err) | |
| goto err_put_device; | |
| return ctrl_dev; | |
| err_put_device: | |
| put_device(&ctrl_dev->dev); | |
| return ERR_PTR(err); | |
| } | |
| static void serial_base_port_release(struct device *dev) | |
| { | |
| struct serial_port_device *port_dev = to_serial_base_port_device(dev); | |
| fwnode_handle_put(dev_fwnode(dev)); | |
| kfree(port_dev); | |
| } | |
| struct serial_port_device *serial_base_port_add(struct uart_port *port, | |
| struct serial_ctrl_device *ctrl_dev) | |
| { | |
| struct serial_port_device *port_dev; | |
| int min = 0, max = -1; /* Use -1 for max to apply IDA defaults */ | |
| int err; | |
| port_dev = kzalloc_obj(*port_dev); | |
| if (!port_dev) | |
| return ERR_PTR(-ENOMEM); | |
| /* Device driver specified port_id vs automatic assignment? */ | |
| if (port->port_id) { | |
| min = port->port_id; | |
| max = port->port_id; | |
| } | |
| err = ida_alloc_range(&ctrl_dev->port_ida, min, max, GFP_KERNEL); | |
| if (err < 0) { | |
| kfree(port_dev); | |
| return ERR_PTR(err); | |
| } | |
| port->port_id = err; | |
| err = serial_base_device_init(port, &port_dev->dev, | |
| &ctrl_dev->dev, &serial_port_type, | |
| serial_base_port_release, | |
| port->ctrl_id, port->port_id); | |
| if (err) | |
| goto err_put_device; | |
| port_dev->port = port; | |
| err = device_add(&port_dev->dev); | |
| if (err) | |
| goto err_put_device; | |
| return port_dev; | |
| err_put_device: | |
| put_device(&port_dev->dev); | |
| ida_free(&ctrl_dev->port_ida, port->port_id); | |
| return ERR_PTR(err); | |
| } | |
| void serial_base_port_device_remove(struct serial_port_device *port_dev) | |
| { | |
| struct serial_ctrl_device *ctrl_dev; | |
| struct device *parent; | |
| if (!port_dev) | |
| return; | |
| parent = port_dev->dev.parent; | |
| ctrl_dev = to_serial_base_ctrl_device(parent); | |
| device_del(&port_dev->dev); | |
| ida_free(&ctrl_dev->port_ida, port_dev->port->port_id); | |
| put_device(&port_dev->dev); | |
| } | |
| #ifdef CONFIG_SERIAL_CORE_CONSOLE | |
| /** | |
| * serial_base_match_and_update_preferred_console - Match and update a preferred console | |
| * @drv: Serial port device driver | |
| * @port: Serial port instance | |
| * | |
| * Tries to match and update the preferred console for a serial port for | |
| * the kernel command line option console=DEVNAME:0.0. | |
| * | |
| * Cannot be called early for ISA ports, depends on struct device. | |
| * | |
| * Return: 0 on success, negative error code on failure. | |
| */ | |
| int serial_base_match_and_update_preferred_console(struct uart_driver *drv, | |
| struct uart_port *port) | |
| { | |
| const char *port_match __free(kfree) = NULL; | |
| int ret; | |
| port_match = kasprintf(GFP_KERNEL, "%s:%d.%d", dev_name(port->dev), | |
| port->ctrl_id, port->port_id); | |
| if (!port_match) | |
| return -ENOMEM; | |
| ret = match_devname_and_update_preferred_console(port_match, | |
| drv->dev_name, | |
| port->line); | |
| if (ret == -ENOENT) | |
| return 0; | |
| return ret; | |
| } | |
| #endif | |
| static int serial_base_init(void) | |
| { | |
| int ret; | |
| ret = bus_register(&serial_base_bus_type); | |
| if (ret) | |
| return ret; | |
| ret = serial_base_ctrl_init(); | |
| if (ret) | |
| goto err_bus_unregister; | |
| ret = serial_base_port_init(); | |
| if (ret) | |
| goto err_ctrl_exit; | |
| serial_base_initialized = true; | |
| return 0; | |
| err_ctrl_exit: | |
| serial_base_ctrl_exit(); | |
| err_bus_unregister: | |
| bus_unregister(&serial_base_bus_type); | |
| return ret; | |
| } | |
| arch_initcall(serial_base_init); | |
| static void serial_base_exit(void) | |
| { | |
| serial_base_port_exit(); | |
| serial_base_ctrl_exit(); | |
| bus_unregister(&serial_base_bus_type); | |
| } | |
| module_exit(serial_base_exit); | |
| MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); | |
| MODULE_DESCRIPTION("Serial core bus"); | |
| MODULE_LICENSE("GPL"); |