On Thu, May 25, 2023 at 02:30:30PM +0300, Tony Lindgren wrote: > We want to enable runtime PM for serial port device drivers in a generic > way. To do this, we want to have the serial core layer manage the > registered physical serial controller devices. > > To manage serial controllers, let's set up a struct bus and struct device > for the serial core controller as suggested by Greg and Jiri. The serial > core controller devices are children of the physical serial port device. > The serial core controller device is needed to support multiple different > kind of ports connected to single physical serial port device. > > Let's also set up a struct device for the serial core port. The serial > core port instances are children of the serial core controller device. > > With the serial core port device we can now flush pending TX on the > runtime PM resume as suggested by Johan. Cool development, thank you! Reviewed-by: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx> > Suggested-by: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx> > Suggested-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> > Suggested-by: Jiri Slaby <jirislaby@xxxxxxxxxx> > Suggested-by: Johan Hovold <johan@xxxxxxxxxx> > Signed-off-by: Tony Lindgren <tony@xxxxxxxxxxx> > --- > Changes since v11: > - Reworked code to add struct serial_ctrl_device and serial_port_device > wrappers around struct device for type checking as suggested by Greg > > Changes since v10: > > - Added missing handling for unknown type in serial_base_device_add() > as noted by kernel test robot > > - Use y instead of objs fro serial_base in Makefile as noted by Andy > > - Improve serial_port pm_ops alignment as noted by Andy > > Changes since v9: > > - Built in serial_base and core related components into serial_base.ko as > suggested by Greg. We now have module_init only in serial_base.c that > calls serial_base_ctrl_init() and serial_base_port_init(). I renamed > serial_bus.c to serial_base_bus.c to build serial_base.ko. Note that > if we wanted to build these into serial_core.ko, renaming serial_core.c > would be needed which is not necessarily nice for folks that may have > pending patches > > - Dropped string comparison for ctrl and port, and switched to using > struct device_type as suggested by Greg > > - Dropped port->state checks in serial_base_get_port() as noted by > Greg > > - Dropped EXPORT_SYMBOL_NS(), these are no longer needed with components > built into serial_base.ko. I also noticed that we have some dependency > loops if components are not built into serial_base.ko. And we would > have hard time later on moving port specific functions to serial_port.c > for example > > - Dropped checks for negative ctrl_id in serial_core_ctrl_find() as > suggested by Greg > > - Stopped resetting ctrl_id in serial_core_remove_one_port(), instead > let's properly init it in serial8250_init_port(). The ctrl_id is > optionally passed to uart_add_one_port() and zero otherwise > > - Moved port_mutex and UPF_DEAD handling from serial_core_add_one_port() > to serial_core_register_port() to simplify things a bit > > - Updated license and copyright as suggested by Greg > > - Dropped Andy's reviewed-by, things still changed quite a bit > > Changes since v8: > > - Drop unnecessary free for name noticed by Andy, the name is freed > on put_device() > > - Cosmetic fix for comments in serial_port.c noted by Andy > > - Spelling fix for word uninitialized in serial_base_get_port() > > Changes since v7: > > - Add release() put_device() to serial_base.c as noted by Andy > > - Make struct serial_base_device private to serial_base.c by adding > serial_base_get_port() > > - Add more comments to __uart_start() > > - Coding style improvments for serial_base.c from Andy > > Changes since v6: > > - Fix up a memory leak and a bunch of issues in serial_base.c as noted > by Andy > > - Replace bool with new_ctrl_dev for freeing the added device on > error path > > - Drop unused pm ops for serial_ctrl.c as noted by kernel test robot > > - Drop documentation updates for acpi devices for now to avoid a merge > conflict and make testing easier between -rc2 and Linux next > > Changes since v5: > > - Replace platform bus and device with bus_add() and device_add(), > Greg did not like platform bus and device here. This also gets > rid of the need for platform data with struct serial_base_device, > see new file serial_base.c > > - Update documentation to drop reference to struct uart_device as > suggested by Andy > > Changes since v4: > > - Fix issue noted by Ilpo by calling serial_core_add_one_port() after > the devices are created > > Changes since v3: > > - Simplify things by adding a serial core control device as the child of > the physical serial port as suggested by Jiri > > - Drop the tinkering of the physical serial port device for runtime PM. > Serial core just needs to manage port->port_dev with the addition of > the serial core control device and the device hierarchy will keep the > pysical serial port device enabled as needed > > - Simplify patch description with all the runtime PM tinkering gone > > - Coding style improvments as noted by Andy > > - Post as a single RFC patch as we're close to the merge window > > Changes since v2: > > - Make each serial port a proper device as suggested by Greg. This is > a separate patch that flushes the TX on runtime PM resume > > Changes since v1: > > - Use kref as suggested by Andy > > - Fix memory leak on error as noted by Andy > > - Use use unsigned char for supports_autosuspend as suggested by Andy > > - Coding style improvments as suggested by Andy > --- > drivers/tty/serial/8250/8250_core.c | 1 + > drivers/tty/serial/8250/8250_port.c | 1 + > drivers/tty/serial/Makefile | 3 +- > drivers/tty/serial/serial_base.h | 46 ++++++ > drivers/tty/serial/serial_base_bus.c | 200 +++++++++++++++++++++++++++ > drivers/tty/serial/serial_core.c | 192 ++++++++++++++++++++++--- > drivers/tty/serial/serial_ctrl.c | 68 +++++++++ > drivers/tty/serial/serial_port.c | 105 ++++++++++++++ > include/linux/serial_core.h | 5 +- > 9 files changed, 598 insertions(+), 23 deletions(-) > create mode 100644 drivers/tty/serial/serial_base.h > create mode 100644 drivers/tty/serial/serial_base_bus.c > create mode 100644 drivers/tty/serial/serial_ctrl.c > create mode 100644 drivers/tty/serial/serial_port.c > > diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c > --- a/drivers/tty/serial/8250/8250_core.c > +++ b/drivers/tty/serial/8250/8250_core.c > @@ -1039,6 +1039,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) > if (uart->port.dev) > uart_remove_one_port(&serial8250_reg, &uart->port); > > + uart->port.ctrl_id = up->port.ctrl_id; > uart->port.iobase = up->port.iobase; > uart->port.membase = up->port.membase; > uart->port.irq = up->port.irq; > diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c > --- a/drivers/tty/serial/8250/8250_port.c > +++ b/drivers/tty/serial/8250/8250_port.c > @@ -3214,6 +3214,7 @@ void serial8250_init_port(struct uart_8250_port *up) > struct uart_port *port = &up->port; > > spin_lock_init(&port->lock); > + port->ctrl_id = 0; > port->ops = &serial8250_pops; > port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE); > > diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile > --- a/drivers/tty/serial/Makefile > +++ b/drivers/tty/serial/Makefile > @@ -3,7 +3,8 @@ > # Makefile for the kernel serial device drivers. > # > > -obj-$(CONFIG_SERIAL_CORE) += serial_core.o > +obj-$(CONFIG_SERIAL_CORE) += serial_base.o > +serial_base-y := serial_core.o serial_base_bus.o serial_ctrl.o serial_port.o > > obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o > obj-$(CONFIG_SERIAL_EARLYCON_SEMIHOST) += earlycon-semihost.o > diff --git a/drivers/tty/serial/serial_base.h b/drivers/tty/serial/serial_base.h > new file mode 100644 > --- /dev/null > +++ b/drivers/tty/serial/serial_base.h > @@ -0,0 +1,46 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Serial core related functions, serial port device drivers do not need this. > + * > + * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/ > + * Author: Tony Lindgren <tony@xxxxxxxxxxx> > + */ > + > +#define to_serial_base_ctrl_device(d) container_of((d), struct serial_ctrl_device, dev) > +#define to_serial_base_port_device(d) container_of((d), struct serial_port_device, dev) > + > +struct uart_driver; > +struct uart_port; > +struct device_driver; > +struct device; > + > +struct serial_ctrl_device { > + struct device dev; > +}; > + > +struct serial_port_device { > + struct device dev; > + struct uart_port *port; > +}; > + > +int serial_base_ctrl_init(void); > +void serial_base_ctrl_exit(void); > + > +int serial_base_port_init(void); > +void serial_base_port_exit(void); > + > +int serial_base_driver_register(struct device_driver *driver); > +void serial_base_driver_unregister(struct device_driver *driver); > + > +struct serial_ctrl_device *serial_base_ctrl_add(struct uart_port *port, > + struct device *parent); > +struct serial_port_device *serial_base_port_add(struct uart_port *port, > + struct serial_ctrl_device *parent); > +void serial_base_ctrl_device_remove(struct serial_ctrl_device *ctrl_dev); > +void serial_base_port_device_remove(struct serial_port_device *port_dev); > + > +int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port); > +void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port); > + > +int serial_core_register_port(struct uart_driver *drv, struct uart_port *port); > +void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port); > diff --git a/drivers/tty/serial/serial_base_bus.c b/drivers/tty/serial/serial_base_bus.c > new file mode 100644 > --- /dev/null > +++ b/drivers/tty/serial/serial_base_bus.c > @@ -0,0 +1,200 @@ > +// 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@xxxxxxxxxxx> > + * > + * The serial core bus manages the serial core controller instances. > + */ > + > +#include <linux/container_of.h> > +#include <linux/device.h> > +#include <linux/module.h> > +#include <linux/serial_core.h> > +#include <linux/slab.h> > +#include <linux/spinlock.h> > + > +#include "serial_base.h" > + > +static int serial_base_match(struct device *dev, struct device_driver *drv) > +{ > + int len = strlen(drv->name); > + > + return !strncmp(dev_name(dev), drv->name, len); > +} > + > +static 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); > +} > + > +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), > + int id) > +{ > + device_initialize(dev); > + dev->type = type; > + dev->parent = parent_dev; > + dev->bus = &serial_base_bus_type; > + dev->release = release; > + > + return dev_set_name(dev, "%s.%s.%d", type->name, dev_name(port->dev), id); > +} > + > +static const struct device_type serial_ctrl_type = { > + .name = "ctrl", > +}; > + > +static void serial_base_ctrl_release(struct device *dev) > +{ > + struct serial_ctrl_device *ctrl_dev = to_serial_base_ctrl_device(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); > +} > + > +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(sizeof(*ctrl_dev), GFP_KERNEL); > + if (!ctrl_dev) > + return ERR_PTR(-ENOMEM); > + > + err = serial_base_device_init(port, &ctrl_dev->dev, > + parent, &serial_ctrl_type, > + serial_base_ctrl_release, > + port->ctrl_id); > + if (err) > + goto err_free_ctrl_dev; > + > + err = device_add(&ctrl_dev->dev); > + if (err) > + goto err_put_device; > + > + return ctrl_dev; > + > +err_put_device: > + put_device(&ctrl_dev->dev); > +err_free_ctrl_dev: > + kfree(ctrl_dev); > + > + return ERR_PTR(err); > +} > + > +static const struct device_type serial_port_type = { > + .name = "port", > +}; > + > +static void serial_base_port_release(struct device *dev) > +{ > + struct serial_port_device *port_dev = to_serial_base_port_device(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 err; > + > + port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); > + if (!port_dev) > + return ERR_PTR(-ENOMEM); > + > + err = serial_base_device_init(port, &port_dev->dev, > + &ctrl_dev->dev, &serial_port_type, > + serial_base_port_release, > + port->line); > + if (err) > + goto err_free_port_dev; > + > + 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); > +err_free_port_dev: > + kfree(port_dev); > + > + return ERR_PTR(err); > +} > + > +void serial_base_port_device_remove(struct serial_port_device *port_dev) > +{ > + if (!port_dev) > + return; > + > + device_del(&port_dev->dev); > +} > + > +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; > + > + return 0; > + > +err_ctrl_exit: > + serial_base_ctrl_exit(); > + > +err_bus_unregister: > + bus_unregister(&serial_base_bus_type); > + > + return ret; > +} > +module_init(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@xxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Serial core bus"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c > --- a/drivers/tty/serial/serial_core.c > +++ b/drivers/tty/serial/serial_core.c > @@ -17,6 +17,7 @@ > #include <linux/gpio/consumer.h> > #include <linux/kernel.h> > #include <linux/of.h> > +#include <linux/pm_runtime.h> > #include <linux/proc_fs.h> > #include <linux/seq_file.h> > #include <linux/device.h> > @@ -31,6 +32,8 @@ > #include <linux/irq.h> > #include <linux/uaccess.h> > > +#include "serial_base.h" > + > /* > * This is used to lock changes in serial line configuration. > */ > @@ -134,9 +137,30 @@ static void __uart_start(struct tty_struct *tty) > { > struct uart_state *state = tty->driver_data; > struct uart_port *port = state->uart_port; > + struct serial_port_device *port_dev; > + int err; > > - if (port && !(port->flags & UPF_DEAD) && !uart_tx_stopped(port)) > + if (!port || port->flags & UPF_DEAD || uart_tx_stopped(port)) > + return; > + > + port_dev = port->port_dev; > + > + /* Increment the runtime PM usage count for the active check below */ > + err = pm_runtime_get(&port_dev->dev); > + if (err < 0) { > + pm_runtime_put_noidle(&port_dev->dev); > + return; > + } > + > + /* > + * Start TX if enabled, and kick runtime PM. If the device is not > + * enabled, serial_port_runtime_resume() calls start_tx() again > + * after enabling the device. > + */ > + if (pm_runtime_active(&port_dev->dev)) > port->ops->start_tx(port); > + pm_runtime_mark_last_busy(&port_dev->dev); > + pm_runtime_put_autosuspend(&port_dev->dev); > } > > static void uart_start(struct tty_struct *tty) > @@ -3042,7 +3066,7 @@ static const struct attribute_group tty_dev_attr_group = { > }; > > /** > - * uart_add_one_port - attach a driver-defined port structure > + * serial_core_add_one_port - attach a driver-defined port structure > * @drv: pointer to the uart low level driver structure for this port > * @uport: uart port structure to use for this port. > * > @@ -3051,8 +3075,9 @@ static const struct attribute_group tty_dev_attr_group = { > * This allows the driver @drv to register its own uart_port structure with the > * core driver. The main purpose is to allow the low level uart drivers to > * expand uart_port, rather than having yet more levels of structures. > + * Caller must hold port_mutex. > */ > -int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport) > +static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *uport) > { > struct uart_state *state; > struct tty_port *port; > @@ -3066,7 +3091,6 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport) > state = drv->state + uport->line; > port = &state->port; > > - mutex_lock(&port_mutex); > mutex_lock(&port->mutex); > if (state->uart_port) { > ret = -EINVAL; > @@ -3131,21 +3155,14 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport) > uport->line); > } > > - /* > - * Ensure UPF_DEAD is not set. > - */ > - uport->flags &= ~UPF_DEAD; > - > out: > mutex_unlock(&port->mutex); > - mutex_unlock(&port_mutex); > > return ret; > } > -EXPORT_SYMBOL(uart_add_one_port); > > /** > - * uart_remove_one_port - detach a driver defined port structure > + * serial_core_remove_one_port - detach a driver defined port structure > * @drv: pointer to the uart low level driver structure for this port > * @uport: uart port structure for this port > * > @@ -3153,20 +3170,16 @@ EXPORT_SYMBOL(uart_add_one_port); > * > * This unhooks (and hangs up) the specified port structure from the core > * driver. No further calls will be made to the low-level code for this port. > + * Caller must hold port_mutex. > */ > -void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport) > +static void serial_core_remove_one_port(struct uart_driver *drv, > + struct uart_port *uport) > { > struct uart_state *state = drv->state + uport->line; > struct tty_port *port = &state->port; > struct uart_port *uart_port; > struct tty_struct *tty; > > - mutex_lock(&port_mutex); > - > - /* > - * Mark the port "dead" - this prevents any opens from > - * succeeding while we shut down the port. > - */ > mutex_lock(&port->mutex); > uart_port = uart_port_check(state); > if (uart_port != uport) > @@ -3177,7 +3190,6 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport) > mutex_unlock(&port->mutex); > goto out; > } > - uport->flags |= UPF_DEAD; > mutex_unlock(&port->mutex); > > /* > @@ -3209,6 +3221,7 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport) > * Indicate that there isn't a port here anymore. > */ > uport->type = PORT_UNKNOWN; > + uport->port_dev = NULL; > > mutex_lock(&port->mutex); > WARN_ON(atomic_dec_return(&state->refcount) < 0); > @@ -3218,7 +3231,6 @@ void uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport) > out: > mutex_unlock(&port_mutex); > } > -EXPORT_SYMBOL(uart_remove_one_port); > > /** > * uart_match_port - are the two ports equivalent? > @@ -3253,6 +3265,144 @@ bool uart_match_port(const struct uart_port *port1, > } > EXPORT_SYMBOL(uart_match_port); > > +static struct serial_ctrl_device * > +serial_core_get_ctrl_dev(struct serial_port_device *port_dev) > +{ > + struct device *dev = &port_dev->dev; > + > + return to_serial_base_ctrl_device(dev->parent); > +} > + > +/* > + * Find a registered serial core controller device if one exists. Returns > + * the first device matching the ctrl_id. Caller must hold port_mutex. > + */ > +static struct serial_ctrl_device *serial_core_ctrl_find(struct uart_driver *drv, > + struct device *phys_dev, > + int ctrl_id) > +{ > + struct uart_state *state; > + int i; > + > + lockdep_assert_held(&port_mutex); > + > + for (i = 0; i < drv->nr; i++) { > + state = drv->state + i; > + if (!state->uart_port || !state->uart_port->port_dev) > + continue; > + > + if (state->uart_port->dev == phys_dev && > + state->uart_port->ctrl_id == ctrl_id) > + return serial_core_get_ctrl_dev(state->uart_port->port_dev); > + } > + > + return NULL; > +} > + > +static struct serial_ctrl_device *serial_core_ctrl_device_add(struct uart_port *port) > +{ > + return serial_base_ctrl_add(port, port->dev); > +} > + > +static int serial_core_port_device_add(struct serial_ctrl_device *ctrl_dev, > + struct uart_port *port) > +{ > + struct serial_port_device *port_dev; > + > + port_dev = serial_base_port_add(port, ctrl_dev); > + if (IS_ERR(port_dev)) > + return PTR_ERR(port_dev); > + > + port->port_dev = port_dev; > + > + return 0; > +} > + > +/* > + * Initialize a serial core port device, and a controller device if needed. > + */ > +int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) > +{ > + struct serial_ctrl_device *ctrl_dev, *new_ctrl_dev = NULL; > + int ret; > + > + mutex_lock(&port_mutex); > + > + /* > + * Prevent serial_port_runtime_resume() from trying to use the port > + * until serial_core_add_one_port() has completed > + */ > + port->flags |= UPF_DEAD; > + > + /* Inititalize a serial core controller device if needed */ > + ctrl_dev = serial_core_ctrl_find(drv, port->dev, port->ctrl_id); > + if (!ctrl_dev) { > + new_ctrl_dev = serial_core_ctrl_device_add(port); > + if (!new_ctrl_dev) { > + ret = -ENODEV; > + goto err_unlock; > + } > + ctrl_dev = new_ctrl_dev; > + } > + > + /* > + * Initialize a serial core port device. Tag the port dead to prevent > + * serial_port_runtime_resume() trying to do anything until port has > + * been registered. It gets cleared by serial_core_add_one_port(). > + */ > + ret = serial_core_port_device_add(ctrl_dev, port); > + if (ret) > + goto err_unregister_ctrl_dev; > + > + ret = serial_core_add_one_port(drv, port); > + if (ret) > + goto err_unregister_port_dev; > + > + port->flags &= ~UPF_DEAD; > + > + mutex_unlock(&port_mutex); > + > + return 0; > + > +err_unregister_port_dev: > + serial_base_port_device_remove(port->port_dev); > + > +err_unregister_ctrl_dev: > + serial_base_ctrl_device_remove(new_ctrl_dev); > + > +err_unlock: > + mutex_unlock(&port_mutex); > + > + return ret; > +} > + > +/* > + * Removes a serial core port device, and the related serial core controller > + * device if the last instance. > + */ > +void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port) > +{ > + struct device *phys_dev = port->dev; > + struct serial_port_device *port_dev = port->port_dev; > + struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev); > + int ctrl_id = port->ctrl_id; > + > + mutex_lock(&port_mutex); > + > + port->flags |= UPF_DEAD; > + > + serial_core_remove_one_port(drv, port); > + > + /* Note that struct uart_port *port is no longer valid at this point */ > + serial_base_port_device_remove(port_dev); > + > + /* Drop the serial core controller device if no ports are using it */ > + if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id)) > + serial_base_ctrl_device_remove(ctrl_dev); > + > + mutex_unlock(&port_mutex); > +} > + > /** > * uart_handle_dcd_change - handle a change of carrier detect state > * @uport: uart_port structure for the open port > diff --git a/drivers/tty/serial/serial_ctrl.c b/drivers/tty/serial/serial_ctrl.c > new file mode 100644 > --- /dev/null > +++ b/drivers/tty/serial/serial_ctrl.c > @@ -0,0 +1,68 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Serial core controller driver > + * > + * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/ > + * Author: Tony Lindgren <tony@xxxxxxxxxxx> > + * > + * This driver manages the serial core controller struct device instances. > + * The serial core controller devices are children of the physical serial > + * port device. > + */ > + > +#include <linux/device.h> > +#include <linux/module.h> > +#include <linux/pm_runtime.h> > +#include <linux/serial_core.h> > +#include <linux/spinlock.h> > + > +#include "serial_base.h" > + > +static int serial_ctrl_probe(struct device *dev) > +{ > + pm_runtime_enable(dev); > + > + return 0; > +} > + > +static int serial_ctrl_remove(struct device *dev) > +{ > + pm_runtime_disable(dev); > + > + return 0; > +} > + > +/* > + * Serial core controller device init functions. Note that the physical > + * serial port device driver may not have completed probe at this point. > + */ > +int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port) > +{ > + return serial_core_register_port(drv, port); > +} > + > +void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port) > +{ > + serial_core_unregister_port(drv, port); > +} > + > +static struct device_driver serial_ctrl_driver = { > + .name = "ctrl", > + .suppress_bind_attrs = true, > + .probe = serial_ctrl_probe, > + .remove = serial_ctrl_remove, > +}; > + > +int serial_base_ctrl_init(void) > +{ > + return serial_base_driver_register(&serial_ctrl_driver); > +} > + > +void serial_base_ctrl_exit(void) > +{ > + serial_base_driver_unregister(&serial_ctrl_driver); > +} > + > +MODULE_AUTHOR("Tony Lindgren <tony@xxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Serial core controller driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/tty/serial/serial_port.c b/drivers/tty/serial/serial_port.c > new file mode 100644 > --- /dev/null > +++ b/drivers/tty/serial/serial_port.c > @@ -0,0 +1,105 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Serial core port device driver > + * > + * Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/ > + * Author: Tony Lindgren <tony@xxxxxxxxxxx> > + */ > + > +#include <linux/device.h> > +#include <linux/module.h> > +#include <linux/pm_runtime.h> > +#include <linux/serial_core.h> > +#include <linux/spinlock.h> > + > +#include "serial_base.h" > + > +#define SERIAL_PORT_AUTOSUSPEND_DELAY_MS 500 > + > +/* Only considers pending TX for now. Caller must take care of locking */ > +static int __serial_port_busy(struct uart_port *port) > +{ > + return !uart_tx_stopped(port) && > + uart_circ_chars_pending(&port->state->xmit); > +} > + > +static int serial_port_runtime_resume(struct device *dev) > +{ > + struct serial_port_device *port_dev = to_serial_base_port_device(dev); > + struct uart_port *port; > + unsigned long flags; > + > + port = port_dev->port; > + > + if (port->flags & UPF_DEAD) > + goto out; > + > + /* Flush any pending TX for the port */ > + spin_lock_irqsave(&port->lock, flags); > + if (__serial_port_busy(port)) > + port->ops->start_tx(port); > + spin_unlock_irqrestore(&port->lock, flags); > + > +out: > + pm_runtime_mark_last_busy(dev); > + > + return 0; > +} > + > +static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm, > + NULL, serial_port_runtime_resume, NULL); > + > +static int serial_port_probe(struct device *dev) > +{ > + pm_runtime_enable(dev); > + pm_runtime_set_autosuspend_delay(dev, SERIAL_PORT_AUTOSUSPEND_DELAY_MS); > + pm_runtime_use_autosuspend(dev); > + > + return 0; > +} > + > +static int serial_port_remove(struct device *dev) > +{ > + pm_runtime_dont_use_autosuspend(dev); > + pm_runtime_disable(dev); > + > + return 0; > +} > + > +/* > + * Serial core port device init functions. Note that the physical serial > + * port device driver may not have completed probe at this point. > + */ > +int uart_add_one_port(struct uart_driver *drv, struct uart_port *port) > +{ > + return serial_ctrl_register_port(drv, port); > +} > +EXPORT_SYMBOL(uart_add_one_port); > + > +void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port) > +{ > + serial_ctrl_unregister_port(drv, port); > +} > +EXPORT_SYMBOL(uart_remove_one_port); > + > +static struct device_driver serial_port_driver = { > + .name = "port", > + .suppress_bind_attrs = true, > + .probe = serial_port_probe, > + .remove = serial_port_remove, > + .pm = pm_ptr(&serial_port_pm), > +}; > + > +int serial_base_port_init(void) > +{ > + return serial_base_driver_register(&serial_port_driver); > +} > + > +void serial_base_port_exit(void) > +{ > + serial_base_driver_unregister(&serial_port_driver); > +} > + > +MODULE_AUTHOR("Tony Lindgren <tony@xxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Serial controller port driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h > --- a/include/linux/serial_core.h > +++ b/include/linux/serial_core.h > @@ -28,6 +28,7 @@ > > struct uart_port; > struct serial_struct; > +struct serial_port_device; > struct device; > struct gpio_desc; > > @@ -458,6 +459,7 @@ struct uart_port { > struct serial_rs485 *rs485); > int (*iso7816_config)(struct uart_port *, > struct serial_iso7816 *iso7816); > + int ctrl_id; /* optional serial core controller id */ > unsigned int irq; /* irq number */ > unsigned long irqflags; /* irq flags */ > unsigned int uartclk; /* base uart clock */ > @@ -563,7 +565,8 @@ struct uart_port { > unsigned int minor; > resource_size_t mapbase; /* for ioremap */ > resource_size_t mapsize; > - struct device *dev; /* parent device */ > + struct device *dev; /* serial port physical parent device */ > + struct serial_port_device *port_dev; /* serial core port device */ > > unsigned long sysrq; /* sysrq timeout */ > unsigned int sysrq_ch; /* char for sysrq */ > -- > 2.40.1 -- With Best Regards, Andy Shevchenko