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