@@ -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@quicinc.com>
+S: Maintained
+F: drivers/gpu/drm/panel/panel-simulation.c
+
DRM DRIVER FOR SITRONIX ST7586 PANELS
M: David Lechner <david@lechnology.com>
S: Maintained
@@ -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
@@ -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
new file mode 100644
@@ -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@quicinc.com>");
+MODULE_DESCRIPTION("DRM Driver for Simulated DSI Panels");
+MODULE_LICENSE("GPL");
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@quicinc.com> --- 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(+)