Add a driver for simulating DSI panels. The DSI simulated panel can be configured via the sim_panel configfs. Currently, the simulated panel supports configuring a supported DRM mode (modes) and setting the DSI mode flags (mode_flags). To enable the simulated panel, the user must write the DSI host device name to the "enable" node. Signed-off-by: Jessica Zhang <quic_jesszhan@xxxxxxxxxxx> --- MAINTAINERS | 5 + drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-simulation.c | 371 +++++++++++++++++++++++++++++++ 4 files changed, 386 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c27f3190737f8b85779bde5489639c8b899f4fd8..cd7019cf3b492691059d897a739dc746266e6ae8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7383,6 +7383,11 @@ S: Maintained F: Documentation/devicetree/bindings/display/panel/samsung,s6d7aa0.yaml F: drivers/gpu/drm/panel/panel-samsung-s6d7aa0.c +DRM DRIVER FOR SIMULATED DSI PANELS +M: Jessica Zhang <quic_jesszhan@xxxxxxxxxxx> +S: Maintained +F: drivers/gpu/drm/panel/panel-simulation.c + DRM DRIVER FOR SITRONIX ST7586 PANELS M: David Lechner <david@xxxxxxxxxxxxxx> S: Maintained diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index d3a9a9fafe4ec7c276214871cc43be099f3a5534..d9aacb8c287371f072fe6652c7e884d5764be567 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -96,6 +96,15 @@ config DRM_PANEL_BOE_TV101WUM_LL2 Say Y here if you want to support for BOE TV101WUM-LL2 WUXGA PANEL DSI Video Mode panel +config DRM_PANEL_SIMULATION + tristate "support for simulation panels" + depends on DRM_MIPI_DSI + help + Say Y here if you want to simulate a DSI panel. This module will allow + users to configure a simulated DSI panel driver via the configfs. + Enabling this config will cause the physical panel driver to not be + attached to its DSI host. + config DRM_PANEL_EBBG_FT8719 tristate "EBBG FT8719 panel driver" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 987a0870241035c6184a25c412c17caf03465dff..75038f6d93ba3d93c82744429044e2ec8dfe51f8 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o +obj-$(CONFIG_DRM_PANEL_SIMULATION) += panel-simulation.o obj-$(CONFIG_DRM_PANEL_EDP) += panel-edp.o obj-$(CONFIG_DRM_PANEL_EBBG_FT8719) += panel-ebbg-ft8719.o obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o diff --git a/drivers/gpu/drm/panel/panel-simulation.c b/drivers/gpu/drm/panel/panel-simulation.c new file mode 100644 index 0000000000000000000000000000000000000000..2f14bd36062ec876cf573637f9e4d6193a3a2864 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-simulation.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + +#include <linux/module.h> +#include <linux/configfs.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#define MAX_DSI_COUNT 2 + +static struct mipi_dsi_driver panel_simulation_driver; +struct panel_simulation; +struct panel_simulation_cfs { + struct config_group group; + struct panel_simulation *panel; + char *dev_name; + unsigned int num_modes; + struct drm_display_mode *display_modes; + u32 mode_flags; +}; + +struct panel_simulation { + struct drm_panel base; + unsigned int num_modes; + struct drm_display_mode *display_modes; + struct mipi_dsi_device *dsi; +}; + +static struct drm_display_mode sim_panel_default_mode = { + .clock = 345830, + .hdisplay = 1080, + .hsync_start = 1175, + .hsync_end = 1176, + .htotal = 1216, + .vdisplay = 2340, + .vsync_start = 2365, + .vsync_end = 2366, + .vtotal = 2370, + .width_mm = 0, + .height_mm = 0, + .type = DRM_MODE_TYPE_DRIVER, + .name = "FOO", +}; + +static inline struct panel_simulation *to_sim_panel(struct drm_panel *panel) +{ + return container_of(panel, struct panel_simulation, base); +} + +static int panel_simulation_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_simulation *sim_panel = to_sim_panel(panel); + struct drm_display_mode *mode; + u32 num_modes = 0; + + for (int i = 0; i < sim_panel->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &sim_panel->display_modes[i]); + if (!mode) { + dev_err(panel->dev, "failed to add mode %s\n", + sim_panel->display_modes[i].name); + continue; + } + + drm_mode_set_name(mode); + num_modes++; + } + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return num_modes; +} + +static const struct drm_panel_funcs panel_simulation_funcs = { + .get_modes = panel_simulation_get_modes, +}; + +static inline struct panel_simulation_cfs *get_sim_panel_config(struct config_item *item) +{ + return container_of(to_config_group(item), + struct panel_simulation_cfs, group); +} + +static ssize_t sim_panel_cfs_item_enable_show(struct config_item *item, char *page) +{ + struct panel_simulation_cfs *config = get_sim_panel_config(item); + + return sprintf(page, "%s\n", config->dev_name); +} + +static ssize_t sim_panel_cfs_item_enable_store(struct config_item *item, + const char *page, size_t count) +{ + struct panel_simulation_cfs *config = get_sim_panel_config(item); + struct device *dev; + int ret; + char name[216]; + + if (config->dev_name) + return count; + + strscpy(name, page, strcspn(page, "\n")); + name[strcspn(page, "\n")] = '\0'; + + dev = bus_find_device_by_name(panel_simulation_driver.driver.bus, + NULL, name); + if (!dev) + return -EINVAL; + + config->dev_name = name; + + dev_set_drvdata(dev, config); + ret = device_reprobe(dev); + if (ret) + dev_warn(dev, "failed to reprobe: %d\n", ret); + + return count; +} +CONFIGFS_ATTR(sim_panel_cfs_item_, enable); + +static ssize_t sim_panel_cfs_item_modes_show(struct config_item *item, char *page) +{ + struct panel_simulation_cfs *configfs = get_sim_panel_config(item); + int count = 0; + + for (int i = 0; i < configfs->num_modes; i++) { + count += sprintf(page, DRM_MODE_FMT "\n", + DRM_MODE_ARG(&configfs->display_modes[i])); + } + + return count; +} + +static struct drm_display_mode *panel_simulation_parse_modes(const char *buf, + struct panel_simulation_cfs *config) +{ + int ret, num_modes; + struct drm_display_mode *modes = drm_mode_create(NULL); + + if (!modes) + return NULL; + + ret = sscanf(buf, "%d %hu %hu %hu %hu %hu %hu %hu %hu 0x%hhx 0x%x", + &modes->clock, &modes->hdisplay, &modes->hsync_start, + &modes->hsync_end, &modes->htotal, &modes->vdisplay, + &modes->vsync_start, &modes->vsync_end, &modes->vtotal, + &modes->type, &modes->flags); + if (ret != 11) + return NULL; + + snprintf(modes->name, sizeof(modes->name), "custom%dx%d@%d", + modes->hdisplay, modes->vdisplay, + drm_mode_vrefresh(modes)); + num_modes = 1; + + config->num_modes = num_modes; + + return modes; +} + +static ssize_t sim_panel_cfs_item_modes_store(struct config_item *item, + const char *page, size_t count) +{ + struct panel_simulation_cfs *config = get_sim_panel_config(item); + struct drm_display_mode *new_modes = NULL; + + if (config->dev_name) + return count; + + new_modes = panel_simulation_parse_modes(page, config); + if (!new_modes) + return -EINVAL; + + config->display_modes = new_modes; + + return count; +} +CONFIGFS_ATTR(sim_panel_cfs_item_, modes); + +static ssize_t sim_panel_cfs_item_mode_flags_show(struct config_item *item, + char *page) +{ + struct panel_simulation_cfs *configfs = get_sim_panel_config(item); + + return sprintf(page, "%d\n", configfs->mode_flags); +} + +static ssize_t sim_panel_cfs_item_mode_flags_store(struct config_item *item, + const char *page, size_t count) +{ + struct panel_simulation_cfs *configfs = get_sim_panel_config(item); + int ret, mode_flags; + + ret = kstrtoint(page, 0, &mode_flags); + if (ret < 0) + return ret; + + configfs->mode_flags = mode_flags; + + return 0; +} +CONFIGFS_ATTR(sim_panel_cfs_item_, mode_flags); + +static struct configfs_attribute *sim_panel_cfs_item_attrs[] = { + &sim_panel_cfs_item_attr_enable, + &sim_panel_cfs_item_attr_modes, + &sim_panel_cfs_item_attr_mode_flags, + NULL, +}; + +static void sim_panel_cfs_item_release(struct config_item *item) +{ + kfree(get_sim_panel_config(item)); +} + +static struct configfs_item_operations sim_panel_cfs_item_ops = { + .release = sim_panel_cfs_item_release, +}; + +static const struct config_item_type sim_panel_cfs_item_type = { + .ct_item_ops = &sim_panel_cfs_item_ops, + .ct_attrs = sim_panel_cfs_item_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *sim_panel_cfs_make_group(struct config_group *group, + const char *name) +{ + struct panel_simulation_cfs *sim_panel; + + sim_panel = kzalloc(sizeof(struct panel_simulation_cfs), GFP_KERNEL); + if (!sim_panel) + return ERR_PTR(-ENOMEM); + + sim_panel->num_modes = 1; + sim_panel->display_modes = &sim_panel_default_mode; + sim_panel->mode_flags = 0; + + config_group_init_type_name(&sim_panel->group, name, + &sim_panel_cfs_item_type); + + return &sim_panel->group; +} + +static struct configfs_attribute *sim_panel_cfs_attrs[] = { + NULL, +}; + +static struct configfs_group_operations sim_panel_cfs_group_ops = { + .make_group = sim_panel_cfs_make_group, +}; + +static const struct config_item_type sim_panel_cfs_group_type = { + .ct_group_ops = &sim_panel_cfs_group_ops, + .ct_attrs = sim_panel_cfs_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem sim_panel_cfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "sim_panel", + .ci_type = &sim_panel_cfs_group_type, + }, + }, +}; + +static int panel_simulation_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct panel_simulation *panel; + int ret = 0; + struct panel_simulation_cfs *configfs = mipi_dsi_get_drvdata(dsi); + + if (!configfs) + return dev_err_probe(dev, -EPROBE_DEFER, + "Cannot get configfs\n"); + + panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + panel->display_modes = configfs->display_modes; + panel->num_modes = configfs->num_modes; + + mipi_dsi_set_drvdata(dsi, panel); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = configfs->mode_flags; + + drm_panel_init(&panel->base, dev, &panel_simulation_funcs, + DRM_MODE_CONNECTOR_DSI); + drm_panel_add(&panel->base); + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&panel->base); + + return ret; +} + +static void panel_simulation_remove(struct mipi_dsi_device *dsi) +{ + struct panel_simulation *panel = mipi_dsi_get_drvdata(dsi); + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + drm_panel_remove(&panel->base); + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); +} + +static void panel_simulation_shutdown(struct mipi_dsi_device *dsi) +{ + struct panel_simulation *panel = dev_get_drvdata(&dsi->dev); + + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); +} + +static struct mipi_dsi_driver panel_simulation_driver = { + .driver = { + .name = "panel_simulation", + }, + .probe = panel_simulation_probe, + .remove = panel_simulation_remove, + .shutdown = panel_simulation_shutdown, +}; + +static int __init panel_simulation_init(void) +{ + int ret; + + ret = mipi_dsi_driver_register(&panel_simulation_driver); + if (ret < 0) + return ret; + + config_group_init(&sim_panel_cfs_subsys.su_group); + mutex_init(&sim_panel_cfs_subsys.su_mutex); + ret = configfs_register_subsystem(&sim_panel_cfs_subsys); + if (ret) { + mutex_destroy(&sim_panel_cfs_subsys.su_mutex); + mipi_dsi_driver_unregister(&panel_simulation_driver); + return ret; + } + + return 0; +} +module_init(panel_simulation_init); + +static void __exit panel_simulation_exit(void) +{ + configfs_unregister_subsystem(&sim_panel_cfs_subsys); + mipi_dsi_driver_unregister(&panel_simulation_driver); +} +module_exit(panel_simulation_exit); + +MODULE_AUTHOR("Jessica Zhang <quic_jesszhan@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("DRM Driver for Simulated DSI Panels"); +MODULE_LICENSE("GPL"); -- 2.34.1