Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

drm: panel: add support for Synaptics TDDI series DSI panels

Synaptics TDDI (Touch/Display Integration) panels utilize a single chip
for display and touch controllers. Implement a simple device driver for
such panels, along with its built-in LED backlight controller, and add
support for TD4101 and TD4300 panels in the driver.

Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://patch.msgid.link/20251009-panel-synaptics-tddi-v5-2-59390997644e@disroot.org

authored by

Kaustabh Chakraborty and committed by
Neil Armstrong
3eae8250 5c42579b

+288
+11
drivers/gpu/drm/panel/Kconfig
··· 1073 1073 Say Y if you want to enable support for panels based on the 1074 1074 Synaptics R63353 controller. 1075 1075 1076 + config DRM_PANEL_SYNAPTICS_TDDI 1077 + tristate "Synaptics TDDI display panels" 1078 + depends on OF 1079 + depends on DRM_MIPI_DSI 1080 + depends on BACKLIGHT_CLASS_DEVICE 1081 + help 1082 + Say Y if you want to enable support for the Synaptics TDDI display 1083 + panels. There are multiple MIPI DSI panels manufactured under the TDDI 1084 + namesake, with varying resolutions and data lanes. They also have a 1085 + built-in LED backlight and a touch controller. 1086 + 1076 1087 config DRM_PANEL_TDO_TL070WSH30 1077 1088 tristate "TDO TL070WSH30 DSI panel" 1078 1089 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 102 102 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o 103 103 obj-$(CONFIG_DRM_PANEL_SUMMIT) += panel-summit.o 104 104 obj-$(CONFIG_DRM_PANEL_SYNAPTICS_R63353) += panel-synaptics-r63353.o 105 + obj-$(CONFIG_DRM_PANEL_SYNAPTICS_TDDI) += panel-synaptics-tddi.o 105 106 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o 106 107 obj-$(CONFIG_DRM_PANEL_SONY_TD4353_JDI) += panel-sony-td4353-jdi.o 107 108 obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.o
+276
drivers/gpu/drm/panel/panel-synaptics-tddi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Synaptics TDDI display panel driver. 4 + * 5 + * Copyright (C) 2025 Kaustabh Chakraborty <kauschluss@disroot.org> 6 + */ 7 + 8 + #include <linux/backlight.h> 9 + #include <linux/gpio/consumer.h> 10 + #include <linux/module.h> 11 + #include <linux/of.h> 12 + 13 + #include <video/mipi_display.h> 14 + 15 + #include <drm/drm_mipi_dsi.h> 16 + #include <drm/drm_modes.h> 17 + #include <drm/drm_panel.h> 18 + #include <drm/drm_probe_helper.h> 19 + 20 + struct tddi_panel_data { 21 + u8 lanes; 22 + /* wait timings for panel enable */ 23 + u8 delay_ms_sleep_exit; 24 + u8 delay_ms_display_on; 25 + /* wait timings for panel disable */ 26 + u8 delay_ms_display_off; 27 + u8 delay_ms_sleep_enter; 28 + }; 29 + 30 + struct tddi_ctx { 31 + struct drm_panel panel; 32 + struct mipi_dsi_device *dsi; 33 + struct drm_display_mode mode; 34 + struct backlight_device *backlight; 35 + const struct tddi_panel_data *data; 36 + struct regulator_bulk_data *supplies; 37 + struct gpio_desc *reset_gpio; 38 + struct gpio_desc *backlight_gpio; 39 + }; 40 + 41 + static const struct regulator_bulk_data tddi_supplies[] = { 42 + { .supply = "vio" }, 43 + { .supply = "vsn" }, 44 + { .supply = "vsp" }, 45 + }; 46 + 47 + static inline struct tddi_ctx *to_tddi_ctx(struct drm_panel *panel) 48 + { 49 + return container_of(panel, struct tddi_ctx, panel); 50 + } 51 + 52 + static int tddi_update_status(struct backlight_device *backlight) 53 + { 54 + struct tddi_ctx *ctx = bl_get_data(backlight); 55 + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; 56 + u8 brightness = backlight_get_brightness(backlight); 57 + 58 + if (!ctx->panel.enabled) 59 + return 0; 60 + 61 + mipi_dsi_dcs_set_display_brightness_multi(&dsi, brightness); 62 + 63 + return dsi.accum_err; 64 + } 65 + 66 + static int tddi_prepare(struct drm_panel *panel) 67 + { 68 + struct tddi_ctx *ctx = to_tddi_ctx(panel); 69 + struct device *dev = &ctx->dsi->dev; 70 + int ret; 71 + 72 + ret = regulator_bulk_enable(ARRAY_SIZE(tddi_supplies), ctx->supplies); 73 + if (ret < 0) { 74 + dev_err(dev, "failed to enable regulators: %d\n", ret); 75 + return ret; 76 + } 77 + 78 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 79 + usleep_range(5000, 6000); 80 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 81 + usleep_range(5000, 6000); 82 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 83 + usleep_range(10000, 11000); 84 + 85 + gpiod_set_value_cansleep(ctx->backlight_gpio, 0); 86 + usleep_range(5000, 6000); 87 + 88 + return 0; 89 + } 90 + 91 + static int tddi_unprepare(struct drm_panel *panel) 92 + { 93 + struct tddi_ctx *ctx = to_tddi_ctx(panel); 94 + 95 + gpiod_set_value_cansleep(ctx->backlight_gpio, 1); 96 + usleep_range(5000, 6000); 97 + 98 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 99 + usleep_range(5000, 6000); 100 + 101 + regulator_bulk_disable(ARRAY_SIZE(tddi_supplies), ctx->supplies); 102 + 103 + return 0; 104 + } 105 + 106 + static int tddi_enable(struct drm_panel *panel) 107 + { 108 + struct tddi_ctx *ctx = to_tddi_ctx(panel); 109 + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; 110 + u8 brightness = ctx->backlight->props.brightness; 111 + 112 + mipi_dsi_dcs_write_seq_multi(&dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); 113 + mipi_dsi_dcs_write_seq_multi(&dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x0c); 114 + 115 + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi); 116 + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_sleep_exit); 117 + 118 + /* sync the panel with the backlight's brightness level */ 119 + mipi_dsi_dcs_set_display_brightness_multi(&dsi, brightness); 120 + 121 + mipi_dsi_dcs_set_display_on_multi(&dsi); 122 + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_display_on); 123 + 124 + return dsi.accum_err; 125 + }; 126 + 127 + static int tddi_disable(struct drm_panel *panel) 128 + { 129 + struct tddi_ctx *ctx = to_tddi_ctx(panel); 130 + struct mipi_dsi_multi_context dsi = { .dsi = ctx->dsi }; 131 + 132 + mipi_dsi_dcs_set_display_off_multi(&dsi); 133 + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_display_off); 134 + 135 + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi); 136 + mipi_dsi_msleep(&dsi, ctx->data->delay_ms_sleep_enter); 137 + 138 + return dsi.accum_err; 139 + } 140 + 141 + static int tddi_get_modes(struct drm_panel *panel, 142 + struct drm_connector *connector) 143 + { 144 + struct tddi_ctx *ctx = to_tddi_ctx(panel); 145 + 146 + return drm_connector_helper_get_modes_fixed(connector, &ctx->mode); 147 + } 148 + 149 + static const struct backlight_ops tddi_bl_ops = { 150 + .update_status = tddi_update_status, 151 + }; 152 + 153 + static const struct backlight_properties tddi_bl_props = { 154 + .type = BACKLIGHT_PLATFORM, 155 + .brightness = 255, 156 + .max_brightness = 255, 157 + }; 158 + 159 + static const struct drm_panel_funcs tddi_drm_panel_funcs = { 160 + .prepare = tddi_prepare, 161 + .unprepare = tddi_unprepare, 162 + .enable = tddi_enable, 163 + .disable = tddi_disable, 164 + .get_modes = tddi_get_modes, 165 + }; 166 + 167 + static int tddi_probe(struct mipi_dsi_device *dsi) 168 + { 169 + struct device *dev = &dsi->dev; 170 + struct tddi_ctx *ctx; 171 + int ret; 172 + 173 + ctx = devm_drm_panel_alloc(dev, struct tddi_ctx, panel, 174 + &tddi_drm_panel_funcs, DRM_MODE_CONNECTOR_DSI); 175 + if (IS_ERR(ctx)) 176 + return PTR_ERR(ctx); 177 + 178 + ctx->data = of_device_get_match_data(dev); 179 + 180 + ctx->dsi = dsi; 181 + mipi_dsi_set_drvdata(dsi, ctx); 182 + 183 + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(tddi_supplies), 184 + tddi_supplies, &ctx->supplies); 185 + if (ret < 0) 186 + return dev_err_probe(dev, ret, "failed to get regulators\n"); 187 + 188 + ctx->backlight_gpio = devm_gpiod_get_optional(dev, "backlight", GPIOD_ASIS); 189 + if (IS_ERR(ctx->backlight_gpio)) 190 + return dev_err_probe(dev, PTR_ERR(ctx->backlight_gpio), 191 + "failed to get backlight-gpios\n"); 192 + 193 + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); 194 + if (IS_ERR(ctx->reset_gpio)) 195 + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 196 + "failed to get reset-gpios\n"); 197 + 198 + ret = of_get_drm_panel_display_mode(dev->of_node, &ctx->mode, NULL); 199 + if (ret < 0) 200 + return dev_err_probe(dev, ret, "failed to get panel timings\n"); 201 + 202 + ctx->backlight = devm_backlight_device_register(dev, dev_name(dev), dev, 203 + ctx, &tddi_bl_ops, 204 + &tddi_bl_props); 205 + if (IS_ERR(ctx->backlight)) 206 + return dev_err_probe(dev, PTR_ERR(ctx->backlight), 207 + "failed to register backlight device"); 208 + 209 + dsi->lanes = ctx->data->lanes; 210 + dsi->format = MIPI_DSI_FMT_RGB888; 211 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | 212 + MIPI_DSI_MODE_VIDEO_NO_HFP; 213 + 214 + ctx->panel.prepare_prev_first = true; 215 + drm_panel_add(&ctx->panel); 216 + 217 + ret = devm_mipi_dsi_attach(dev, dsi); 218 + if (ret < 0) { 219 + drm_panel_remove(&ctx->panel); 220 + return dev_err_probe(dev, ret, "failed to attach to DSI host\n"); 221 + } 222 + 223 + return 0; 224 + } 225 + 226 + static void tddi_remove(struct mipi_dsi_device *dsi) 227 + { 228 + struct tddi_ctx *ctx = mipi_dsi_get_drvdata(dsi); 229 + 230 + drm_panel_remove(&ctx->panel); 231 + } 232 + 233 + static const struct tddi_panel_data td4101_panel_data = { 234 + .lanes = 2, 235 + /* wait timings for panel enable */ 236 + .delay_ms_sleep_exit = 100, 237 + .delay_ms_display_on = 0, 238 + /* wait timings for panel disable */ 239 + .delay_ms_display_off = 20, 240 + .delay_ms_sleep_enter = 90, 241 + }; 242 + 243 + static const struct tddi_panel_data td4300_panel_data = { 244 + .lanes = 4, 245 + /* wait timings for panel enable */ 246 + .delay_ms_sleep_exit = 100, 247 + .delay_ms_display_on = 0, 248 + /* wait timings for panel disable */ 249 + .delay_ms_display_off = 0, 250 + .delay_ms_sleep_enter = 0, 251 + }; 252 + 253 + static const struct of_device_id tddi_of_device_id[] = { 254 + { 255 + .compatible = "syna,td4101-panel", 256 + .data = &td4101_panel_data, 257 + }, { 258 + .compatible = "syna,td4300-panel", 259 + .data = &td4300_panel_data, 260 + }, { } 261 + }; 262 + MODULE_DEVICE_TABLE(of, tddi_of_device_id); 263 + 264 + static struct mipi_dsi_driver tddi_dsi_driver = { 265 + .probe = tddi_probe, 266 + .remove = tddi_remove, 267 + .driver = { 268 + .name = "panel-synaptics-tddi", 269 + .of_match_table = tddi_of_device_id, 270 + }, 271 + }; 272 + module_mipi_dsi_driver(tddi_dsi_driver); 273 + 274 + MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); 275 + MODULE_DESCRIPTION("Synaptics TDDI Display Panel Driver"); 276 + MODULE_LICENSE("GPL");