Add a driver for simulating panels. This module also supports a mode parameter for users to specify a custom mode. If no custom mode is set, it will fall back to a custom, hard-coded mode. Signed-off-by: Jessica Zhang <quic_jesszhan@xxxxxxxxxxx> --- drivers/gpu/drm/panel/Kconfig | 9 ++ drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-simulation.c | 147 +++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 99e14dc212ecb..d711ec170c586 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -107,6 +107,15 @@ config DRM_PANEL_SIMPLE that it can be automatically turned off when the panel goes into a low power state. +config DRM_PANEL_SIMULATION + tristate "support for simulation panels" + depends on DRM_MIPI_DSI + help + DRM panel driver for simulated DSI panels. Enabling this config will + cause the physical panel driver to not be attached to the DT panel + node. After the kernel boots, users can load the module and specify a + custom mode using the driver modparams. + config DRM_PANEL_EDP tristate "support for simple Embedded DisplayPort panels" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index d10c3de51c6db..5bc55357714ad 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -9,6 +9,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 0000000000000..081c03bea188d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-simulation.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_modes.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +static char sim_panel_mode[PATH_MAX]; + +module_param_string(mode, sim_panel_mode, sizeof(sim_panel_mode), 0644); +MODULE_PARM_DESC(mode, "Sim panel mode"); + +struct panel_simulation { + struct drm_panel base; + struct platform_device *platform; +} *sim_panel; + +static struct drm_display_mode panel_simulation_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, +}; + +static int panel_simulation_parse_mode(void) +{ + int count; + struct drm_display_mode user_mode = { 0 }; + unsigned int vrefresh; + + if (sim_panel_mode[0] == '\0') + return 0; + + count = sscanf(sim_panel_mode, "%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu-%u", + &user_mode.hdisplay, &user_mode.hsync_start, + &user_mode.hsync_end, &user_mode.htotal, + &user_mode.vdisplay, &user_mode.vsync_start, + &user_mode.vsync_end, &user_mode.vtotal, &vrefresh); + + if (count != 9) + return -EINVAL; + + user_mode.clock = user_mode.htotal * user_mode.vtotal * vrefresh / 1000; + memcpy(&panel_simulation_mode, &user_mode, sizeof(struct drm_display_mode)); + + return 0; +} + +static int panel_simulation_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + int ret; + + ret = panel_simulation_parse_mode(); + + mode = drm_mode_duplicate(connector->dev, &panel_simulation_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + 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 1; +} + +static const struct drm_panel_funcs panel_simulation_funcs = { + .get_modes = panel_simulation_get_modes, +}; + +static int panel_simulation_probe(struct mipi_dsi_device *dsi) +{ + struct panel_simulation *panel; + struct device *dev = &dsi->dev; + int ret; + + panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, panel); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS; + + 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, +}; +module_mipi_dsi_driver(panel_simulation_driver); + +MODULE_AUTHOR("Jessica Zhang <quic_jesszhan@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("DRM Driver for Simulated Panels"); +MODULE_LICENSE("GPL"); -- 2.43.0