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 <[email protected]>
---
 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 <[email protected]>
+S:     Maintained
+F:     drivers/gpu/drm/panel/panel-simulation.c
+
 DRM DRIVER FOR SITRONIX ST7586 PANELS
 M:     David Lechner <[email protected]>
 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 <[email protected]>");
+MODULE_DESCRIPTION("DRM Driver for Simulated DSI Panels");
+MODULE_LICENSE("GPL");

-- 
2.34.1

Reply via email to