From: Mikko Perttunen <[email protected]> The MIPI DBI bus allows modeling DBI hosts and peripherals using the Linux driver model. DBI hosts are registered by their respective drivers. During registration, DBI peripherals are created from the device tree nodes provided by the host.
DBI hosts expose operations that can be used by peripheral drivers to access associated devices. The original code was authored by Mikko Perttunen; I have aligned it with the existing MIPI DSI implementation. Co-developed-by: Svyatoslav Ryhel <[email protected]> Signed-off-by: Svyatoslav Ryhel <[email protected]> --- drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/drm_mipi_dbi_bus.c | 232 +++++++++++++++++++++++++++++ include/drm/drm_mipi_dbi_bus.h | 103 +++++++++++++ 3 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_mipi_dbi_bus.c create mode 100644 include/drm/drm_mipi_dbi_bus.h diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 2c6bc093a923a..880f0dc930b85 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -166,7 +166,7 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o obj-y += tests/ -obj-$(CONFIG_DRM_MIPI_DBI) += drm_mipi_dbi.o +obj-$(CONFIG_DRM_MIPI_DBI) += drm_mipi_dbi.o drm_mipi_dbi_bus.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-y += arm/ obj-y += clients/ diff --git a/drivers/gpu/drm/drm_mipi_dbi_bus.c b/drivers/gpu/drm/drm_mipi_dbi_bus.c new file mode 100644 index 0000000000000..afad1f1508064 --- /dev/null +++ b/drivers/gpu/drm/drm_mipi_dbi_bus.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MIPI DBI devices with command transport through parallel interface + * + * Copyright 2026 Mikko Perttunen <[email protected]> + * Copyright 2026 Svyatoslav Ryhel <[email protected]> + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_mipi_dbi_bus.h> + +static int mipi_dbi_match(struct device *dev, const struct device_driver *driver) +{ + return of_driver_match_device(dev, driver); +} + +static int mipi_dbi_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + return of_device_uevent_modalias(dev, env); +} + +static const struct dev_pm_ops mipi_dbi_device_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +}; + +static const struct bus_type mipi_dbi_bus_type = { + .name = "mipi-dbi", + .match = mipi_dbi_match, + .uevent = mipi_dbi_uevent, + .pm = &mipi_dbi_device_pm_ops, +}; + +static void mipi_dbi_device_release(struct device *dev) +{ + of_node_put(dev->of_node); + kfree(to_mipi_dbi_device(dev)); +} + +static void mipi_dbi_device_unregister(void *data) +{ + struct device *dev = data; + + of_node_clear_flag(dev->of_node, OF_POPULATED); + device_unregister(dev); +} + +/** + * devm_mipi_dbi_device_register() - create a managed MIPI DBI device + * @dev: device to tie the MIPI-DBI device lifetime to + * @host: DBI host to which this device is connected + * @np: device tree node associated with the panel MIPI DBI device + * + * This function is called by DBI host driver to create panel device. + * It automatically calls mipi_dbi_device_unregister() when @dev is + * unbound. It does not automatically free passed device node pointer, + * it must be put manually. + * + * Returns: + * A pointer to the newly created MIPI DBI device, or, a pointer encoded + * with an error. + */ +struct mipi_dbi_device * +devm_mipi_dbi_device_register(struct device *dev, struct mipi_dbi_host *host, + struct device_node *np) +{ + struct mipi_dbi_device *dbidev; + int ret; + + if (!host || !host->dev || !host->dbi || !host->dbi->command || + !host->ops || !host->ops->attach || !host->ops->detach || !np) + return ERR_PTR(-EINVAL); + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + dbidev = kzalloc_obj(*dbidev); + if (!dbidev) + return ERR_PTR(-ENOMEM); + + if (of_node_test_and_set_flag(np, OF_POPULATED)) { + kfree(dbidev); + return ERR_PTR(-EBUSY); + } + + dbidev->host = host; + dbidev->dbi = host->dbi; + device_initialize(&dbidev->dev); + dbidev->dev.parent = host->dev; + dbidev->dev.bus = &mipi_dbi_bus_type; + dbidev->dev.release = mipi_dbi_device_release; + device_set_node(&dbidev->dev, of_fwnode_handle(of_node_get(np))); + + ret = dev_set_name(&dbidev->dev, "%s:%s", dev_name(host->dev), np->full_name); + if (ret) + goto put_device; + + ret = device_add(&dbidev->dev); + if (ret) + goto put_device; + + ret = devm_add_action_or_reset(dev, mipi_dbi_device_unregister, + &dbidev->dev); + if (ret) + return ERR_PTR(ret); + + return dbidev; + +put_device: + of_node_clear_flag(np, OF_POPULATED); + put_device(&dbidev->dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(devm_mipi_dbi_device_register); + +static void mipi_dbi_detach(void *data) +{ + struct mipi_dbi_device *dbidev = data; + + dbidev->host->ops->detach(dbidev->host, dbidev); + dbidev->attached = false; +} + +/** + * devm_mipi_dbi_attach() - managed attach of DBI device to its DBI host + * @dev: device to tie the MIPI-DBI device lifetime to + * @dbidev: DBI peripheral + */ +int devm_mipi_dbi_attach(struct device *dev, struct mipi_dbi_device *dbidev) +{ + int ret; + + if (dbidev->attached) + return -EBUSY; + + ret = dbidev->host->ops->attach(dbidev->host, dbidev); + if (ret) + return ret; + + dbidev->attached = true; + return devm_add_action_or_reset(dev, mipi_dbi_detach, dbidev); +} +EXPORT_SYMBOL_GPL(devm_mipi_dbi_attach); + +static int mipi_dbi_drv_probe(struct device *dev) +{ + struct mipi_dbi_driver *drv = to_mipi_dbi_driver(dev->driver); + struct mipi_dbi_device *dbidev = to_mipi_dbi_device(dev); + + return drv->probe(dbidev); +} + +static int mipi_dbi_drv_remove(struct device *dev) +{ + struct mipi_dbi_driver *drv = to_mipi_dbi_driver(dev->driver); + struct mipi_dbi_device *dbidev = to_mipi_dbi_device(dev); + + drv->remove(dbidev); + + return 0; +} + +static void mipi_dbi_drv_shutdown(struct device *dev) +{ + struct mipi_dbi_driver *drv = to_mipi_dbi_driver(dev->driver); + struct mipi_dbi_device *dbidev = to_mipi_dbi_device(dev); + + drv->shutdown(dbidev); +} + +/** + * __mipi_dbi_driver_register() - register a driver for DBI devices + * @drv: DBI driver structure + * @owner: owner module + * + * Return: 0 on success or a negative error code on failure. + */ +int __mipi_dbi_driver_register(struct mipi_dbi_driver *drv, struct module *owner) +{ + drv->driver.bus = &mipi_dbi_bus_type; + drv->driver.owner = owner; + + if (drv->probe) + drv->driver.probe = mipi_dbi_drv_probe; + if (drv->remove) + drv->driver.remove = mipi_dbi_drv_remove; + if (drv->shutdown) + drv->driver.shutdown = mipi_dbi_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__mipi_dbi_driver_register); + +/** + * mipi_dbi_driver_unregister() - unregister a driver for DBI devices + * @drv: DBI driver structure + * + * Return: 0 on success or a negative error code on failure. + */ +void mipi_dbi_driver_unregister(struct mipi_dbi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dbi_driver_unregister); + +static int __init mipi_dbi_bus_init(void) +{ + return bus_register(&mipi_dbi_bus_type); +} +subsys_initcall(mipi_dbi_bus_init); + +static void __exit mipi_dbi_bus_exit(void) +{ + bus_unregister(&mipi_dbi_bus_type); +} +module_exit(mipi_dbi_bus_exit); + +MODULE_DESCRIPTION("MIPI DBI devices with command transport through parallel interface"); +MODULE_LICENSE("GPL"); diff --git a/include/drm/drm_mipi_dbi_bus.h b/include/drm/drm_mipi_dbi_bus.h new file mode 100644 index 0000000000000..2b8b59bffd0b8 --- /dev/null +++ b/include/drm/drm_mipi_dbi_bus.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * MIPI DBI devices with command transport through parallel interface + * + * Copyright 2026 Mikko Perttunen <[email protected]> + * Copyright 2026 Svyatoslav Ryhel <[email protected]> + */ + +#ifndef __DRM_MIPI_DBI_BUS_H__ +#define __DRM_MIPI_DBI_BUS_H__ + +#include <linux/device.h> +#include <linux/module.h> + +struct device_node; +struct mipi_dbi; +struct mipi_dbi_device; +struct mipi_dbi_host; + +/** + * struct mipi_dbi_host_ops - DBI bus operations + * @attach: attach DBI device to DBI host + * @detach: detach DBI device from DBI host + */ +struct mipi_dbi_host_ops { + int (*attach)(struct mipi_dbi_host *host, struct mipi_dbi_device *dev); + void (*detach)(struct mipi_dbi_host *host, struct mipi_dbi_device *dev); +}; + +/** + * struct mipi_dbi_host - DBI host device + * @dev: driver model device node for this DBI host + * @dbi: MIPI DBI interface + * @ops: DBI host operations + */ +struct mipi_dbi_host { + struct device *dev; + struct mipi_dbi *dbi; + const struct mipi_dbi_host_ops *ops; +}; + +/** + * struct mipi_dbi_device - DBI peripheral device + * @dev: driver model device node for this peripheral + * @dbi: MIPI DBI interface + * @host: DBI host for this peripheral + * @attached: the DBI device has been successfully attached + */ +struct mipi_dbi_device { + struct device dev; + struct mipi_dbi *dbi; + struct mipi_dbi_host *host; + bool attached; +}; + +#define to_mipi_dbi_device(__dev) container_of_const(__dev, struct mipi_dbi_device, dev) + +struct mipi_dbi_device * +devm_mipi_dbi_device_register(struct device *dev, struct mipi_dbi_host *host, + struct device_node *np); +int devm_mipi_dbi_attach(struct device *dev, struct mipi_dbi_device *dbidev); + +/** + * struct mipi_dbi_driver - DBI driver + * @driver: device driver model driver + * @probe: callback for device binding + * @remove: callback for device unbinding + * @shutdown: called at shutdown time to quiesce the device + */ +struct mipi_dbi_driver { + struct device_driver driver; + int (*probe)(struct mipi_dbi_device *dbi); + void (*remove)(struct mipi_dbi_device *dbi); + void (*shutdown)(struct mipi_dbi_device *dbi); +}; + +static inline struct mipi_dbi_driver * +to_mipi_dbi_driver(struct device_driver *driver) +{ + return container_of(driver, struct mipi_dbi_driver, driver); +} + +static inline void *mipi_dbi_get_drvdata(const struct mipi_dbi_device *dbi) +{ + return dev_get_drvdata(&dbi->dev); +} + +static inline void mipi_dbi_set_drvdata(struct mipi_dbi_device *dbi, void *data) +{ + dev_set_drvdata(&dbi->dev, data); +} + +int __mipi_dbi_driver_register(struct mipi_dbi_driver *drv, struct module *owner); +void mipi_dbi_driver_unregister(struct mipi_dbi_driver *drv); + +#define mipi_dbi_driver_register(driver) \ + __mipi_dbi_driver_register(driver, THIS_MODULE) + +#define module_mipi_dbi_driver(__mipi_dbi_driver) \ + module_driver(__mipi_dbi_driver, mipi_dbi_driver_register, \ + mipi_dbi_driver_unregister) + +#endif /* __DRM_MIPI_DBI_BUS__ */ -- 2.53.0
