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

drm/panel: Add Samsung S6E3FC2X01 DDIC with AMS641RW panel

Add panel driver used in the OnePlus 6T.

No datasheet, based mostly on EDK2 init sequence and the downstream driver.

Note: This driver doesn't use previously mentioned "samsung,s6e3fc2x01"
by OnePlus 6T device-tree.
The reason is because DDIC itself without knowing the panel type used
with it will not give the driver enough information about the panel used,
as the panel cannot be autodetected.
While would be more practical to support the original compatible,
I would like to avoid it, to prevent confusing devs upstreaming DDICs.

Based on work of:
Casey Connolly <casey@connolly.tech>
Joel Selvaraj <foss@joelselvaraj.com>
Nia Espera <a5b6@riseup.net>

Signed-off-by: David Heidelberg <david@ixit.cz>
Reviewed-by: Jessica Zhang <jesszhan0024@gmail.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://patch.msgid.link/20251023-s6e3fc2x01-v5-2-8f8852e67417@ixit.cz

authored by

David Heidelberg and committed by
Neil Armstrong
88148c30 986f28f3

+400
+1
MAINTAINERS
··· 8072 8072 M: David Heidelberg <david@ixit.cz> 8073 8073 S: Maintained 8074 8074 F: Documentation/devicetree/bindings/display/panel/samsung,s6e3fc2x01.yaml 8075 + F: drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c 8075 8076 8076 8077 DRM DRIVER FOR SAMSUNG S6E3HA8 PANELS 8077 8078 M: Dzmitry Sankouski <dsankouski@gmail.com>
+13
drivers/gpu/drm/panel/Kconfig
··· 801 801 select DRM_MIPI_DSI 802 802 select VIDEOMODE_HELPERS 803 803 804 + config DRM_PANEL_SAMSUNG_S6E3FC2X01 805 + tristate "Samsung S6E3FC2X01 DSI panel controller" 806 + depends on OF 807 + depends on DRM_MIPI_DSI 808 + depends on BACKLIGHT_CLASS_DEVICE 809 + select VIDEOMODE_HELPERS 810 + help 811 + Say Y or M here if you want to enable support for the 812 + Samsung S6E3FC2 DDIC and connected MIPI DSI panel. 813 + Currently supported panels: 814 + 815 + Samsung AMS641RW (found in the OnePlus 6T smartphone) 816 + 804 817 config DRM_PANEL_SAMSUNG_S6E3HA2 805 818 tristate "Samsung S6E3HA2 DSI video mode panel" 806 819 depends on OF
+1
drivers/gpu/drm/panel/Makefile
··· 79 79 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D27A1) += panel-samsung-s6d27a1.o 80 80 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0) += panel-samsung-s6d7aa0.o 81 81 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3FA7) += panel-samsung-s6e3fa7.o 82 + obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3FC2X01) += panel-samsung-s6e3fc2x01.o 82 83 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o 83 84 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA8) += panel-samsung-s6e3ha8.o 84 85 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03) += panel-samsung-s6e63j0x03.o
+385
drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2022 Nia Espera <a5b6@riseup.net> 4 + * Copyright (c) 2025 David Heidelberg <david@ixit.cz> 5 + */ 6 + 7 + #include <linux/delay.h> 8 + #include <linux/gpio/consumer.h> 9 + #include <linux/module.h> 10 + #include <linux/of.h> 11 + #include <linux/of_device.h> 12 + #include <linux/regulator/consumer.h> 13 + #include <linux/swab.h> 14 + #include <linux/backlight.h> 15 + 16 + #include <video/mipi_display.h> 17 + 18 + #include <drm/drm_mipi_dsi.h> 19 + #include <drm/drm_modes.h> 20 + #include <drm/drm_panel.h> 21 + #include <drm/drm_probe_helper.h> 22 + 23 + #define MCS_ELVSS_ON 0xb1 24 + 25 + struct samsung_s6e3fc2x01 { 26 + struct drm_panel panel; 27 + struct mipi_dsi_device *dsi; 28 + struct regulator_bulk_data *supplies; 29 + struct gpio_desc *reset_gpio; 30 + }; 31 + 32 + static const struct regulator_bulk_data s6e3fc2x01_supplies[] = { 33 + { .supply = "vddio" }, 34 + { .supply = "vci" }, 35 + { .supply = "poc" }, 36 + }; 37 + 38 + static inline 39 + struct samsung_s6e3fc2x01 *to_samsung_s6e3fc2x01(struct drm_panel *panel) 40 + { 41 + return container_of(panel, struct samsung_s6e3fc2x01, panel); 42 + } 43 + 44 + #define s6e3fc2x01_test_key_on_lvl1(ctx) \ 45 + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5) 46 + #define s6e3fc2x01_test_key_off_lvl1(ctx) \ 47 + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a) 48 + #define s6e3fc2x01_test_key_on_lvl2(ctx) \ 49 + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) 50 + #define s6e3fc2x01_test_key_off_lvl2(ctx) \ 51 + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) 52 + #define s6e3fc2x01_test_key_on_lvl3(ctx) \ 53 + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) 54 + #define s6e3fc2x01_test_key_off_lvl3(ctx) \ 55 + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) 56 + 57 + static void s6e3fc2x01_reset(struct samsung_s6e3fc2x01 *ctx) 58 + { 59 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 60 + usleep_range(5000, 6000); 61 + gpiod_set_value_cansleep(ctx->reset_gpio, 0); 62 + usleep_range(5000, 6000); 63 + } 64 + 65 + static int s6e3fc2x01_on(struct samsung_s6e3fc2x01 *ctx) 66 + { 67 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; 68 + 69 + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); 70 + 71 + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 72 + 73 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 74 + 75 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x0a); 76 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 77 + 78 + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); 79 + 80 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 81 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x01); 82 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x01); 83 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 84 + 85 + mipi_dsi_usleep_range(&dsi_ctx, 15000, 16000); 86 + 87 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x0f); 88 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 89 + 90 + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); 91 + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 92 + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); 93 + 94 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 95 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xeb, 0x17, 96 + 0x41, 0x92, 97 + 0x0e, 0x10, 98 + 0x82, 0x5a); 99 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 100 + 101 + /* Column & Page Address Setting */ 102 + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437); 103 + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923); 104 + 105 + /* Horizontal & Vertical sync Setting */ 106 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 107 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x09); 108 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x10, 0x30); 109 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 110 + 111 + s6e3fc2x01_test_key_on_lvl3(&dsi_ctx); 112 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x01); 113 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, 0x88); 114 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); 115 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xed, 0x67); 116 + s6e3fc2x01_test_key_off_lvl3(&dsi_ctx); 117 + 118 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 119 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); 120 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x01); 121 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x08); 122 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x12); 123 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 124 + 125 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); 126 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); 127 + mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); 128 + 129 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 130 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0x00, 0x01); 131 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 132 + 133 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 134 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x00, 0xc1); 135 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 136 + 137 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x78); 138 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 139 + 140 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x90); 141 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 142 + 143 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 144 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x02); 145 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0xc6, 0x00, 0x00, 146 + 0x21, 0xed, 0x02, 0x08, 0x06, 0xc1, 0x27, 147 + 0xfc, 0xdc, 0xe4, 0x00, 0xd9, 0xe6, 0xe7, 148 + 0x00, 0xfc, 0xff, 0xea); 149 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0x00, 0x00); 150 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 151 + 152 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 153 + 154 + return dsi_ctx.accum_err; 155 + } 156 + 157 + static int s6e3fc2x01_enable(struct drm_panel *panel) 158 + { 159 + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); 160 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; 161 + 162 + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); 163 + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 164 + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); 165 + 166 + return dsi_ctx.accum_err; 167 + } 168 + 169 + static int s6e3fc2x01_off(struct samsung_s6e3fc2x01 *ctx) 170 + { 171 + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; 172 + 173 + s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); 174 + 175 + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 176 + 177 + mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); 178 + 179 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 180 + mipi_dsi_usleep_range(&dsi_ctx, 16000, 17000); 181 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x50); 182 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x82); 183 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 184 + mipi_dsi_usleep_range(&dsi_ctx, 16000, 17000); 185 + 186 + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 187 + 188 + s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); 189 + 190 + s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); 191 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x05); 192 + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x01); 193 + s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); 194 + mipi_dsi_msleep(&dsi_ctx, 160); 195 + 196 + return dsi_ctx.accum_err; 197 + } 198 + 199 + static int s6e3fc2x01_disable(struct drm_panel *panel) 200 + { 201 + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); 202 + 203 + s6e3fc2x01_off(ctx); 204 + 205 + return 0; 206 + } 207 + 208 + static int s6e3fc2x01_prepare(struct drm_panel *panel) 209 + { 210 + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); 211 + int ret; 212 + 213 + ret = regulator_bulk_enable(ARRAY_SIZE(s6e3fc2x01_supplies), ctx->supplies); 214 + if (ret < 0) 215 + return ret; 216 + 217 + s6e3fc2x01_reset(ctx); 218 + 219 + ret = s6e3fc2x01_on(ctx); 220 + if (ret < 0) { 221 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 222 + regulator_bulk_disable(ARRAY_SIZE(s6e3fc2x01_supplies), ctx->supplies); 223 + return ret; 224 + } 225 + 226 + return 0; 227 + } 228 + 229 + static int s6e3fc2x01_unprepare(struct drm_panel *panel) 230 + { 231 + struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); 232 + 233 + gpiod_set_value_cansleep(ctx->reset_gpio, 1); 234 + regulator_bulk_disable(ARRAY_SIZE(s6e3fc2x01_supplies), ctx->supplies); 235 + 236 + return 0; 237 + } 238 + 239 + static const struct drm_display_mode ams641rw_mode = { 240 + .clock = (1080 + 72 + 16 + 36) * (2340 + 32 + 4 + 18) * 60 / 1000, 241 + .hdisplay = 1080, 242 + .hsync_start = 1080 + 72, 243 + .hsync_end = 1080 + 72 + 16, 244 + .htotal = 1080 + 72 + 16 + 36, 245 + .vdisplay = 2340, 246 + .vsync_start = 2340 + 32, 247 + .vsync_end = 2340 + 32 + 4, 248 + .vtotal = 2340 + 32 + 4 + 18, 249 + .width_mm = 68, 250 + .height_mm = 145, 251 + }; 252 + 253 + static int s6e3fc2x01_get_modes(struct drm_panel *panel, 254 + struct drm_connector *connector) 255 + { 256 + return drm_connector_helper_get_modes_fixed(connector, &ams641rw_mode); 257 + } 258 + 259 + static const struct drm_panel_funcs samsung_s6e3fc2x01_panel_funcs = { 260 + .prepare = s6e3fc2x01_prepare, 261 + .enable = s6e3fc2x01_enable, 262 + .disable = s6e3fc2x01_disable, 263 + .unprepare = s6e3fc2x01_unprepare, 264 + .get_modes = s6e3fc2x01_get_modes, 265 + }; 266 + 267 + static int s6e3fc2x01_panel_bl_update_status(struct backlight_device *bl) 268 + { 269 + struct mipi_dsi_device *dsi = bl_get_data(bl); 270 + u16 brightness = backlight_get_brightness(bl); 271 + int err; 272 + 273 + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 274 + 275 + err = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); 276 + if (err < 0) 277 + return err; 278 + 279 + dsi->mode_flags |= MIPI_DSI_MODE_LPM; 280 + 281 + return 0; 282 + } 283 + 284 + static const struct backlight_ops s6e3fc2x01_panel_bl_ops = { 285 + .update_status = s6e3fc2x01_panel_bl_update_status, 286 + }; 287 + 288 + static struct backlight_device * 289 + s6e3fc2x01_create_backlight(struct mipi_dsi_device *dsi) 290 + { 291 + struct device *dev = &dsi->dev; 292 + const struct backlight_properties props = { 293 + .type = BACKLIGHT_PLATFORM, 294 + .brightness = 512, 295 + .max_brightness = 1023, 296 + }; 297 + 298 + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 299 + &s6e3fc2x01_panel_bl_ops, &props); 300 + } 301 + 302 + static int s6e3fc2x01_probe(struct mipi_dsi_device *dsi) 303 + { 304 + struct device *dev = &dsi->dev; 305 + struct samsung_s6e3fc2x01 *ctx; 306 + int ret; 307 + 308 + ctx = devm_drm_panel_alloc(dev, struct samsung_s6e3fc2x01, panel, 309 + &samsung_s6e3fc2x01_panel_funcs, 310 + DRM_MODE_CONNECTOR_DSI); 311 + if (IS_ERR(ctx)) 312 + return PTR_ERR(ctx); 313 + 314 + ret = devm_regulator_bulk_get_const(dev, 315 + ARRAY_SIZE(s6e3fc2x01_supplies), 316 + s6e3fc2x01_supplies, 317 + &ctx->supplies); 318 + if (ret) 319 + return dev_err_probe(dev, ret, "Failed to get regulators\n"); 320 + 321 + 322 + /* keep the display on for flicker-free experience */ 323 + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 324 + if (IS_ERR(ctx->reset_gpio)) 325 + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 326 + "Failed to get reset-gpios\n"); 327 + 328 + ctx->dsi = dsi; 329 + mipi_dsi_set_drvdata(dsi, ctx); 330 + 331 + dsi->lanes = 4; 332 + dsi->format = MIPI_DSI_FMT_RGB888; 333 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST | 334 + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; 335 + 336 + ctx->panel.prepare_prev_first = true; 337 + 338 + ctx->panel.backlight = s6e3fc2x01_create_backlight(dsi); 339 + if (IS_ERR(ctx->panel.backlight)) 340 + return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 341 + "Failed to create backlight\n"); 342 + 343 + drm_panel_add(&ctx->panel); 344 + 345 + ret = mipi_dsi_attach(dsi); 346 + if (ret < 0) { 347 + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 348 + drm_panel_remove(&ctx->panel); 349 + return ret; 350 + } 351 + 352 + return 0; 353 + } 354 + 355 + static void s6e3fc2x01_remove(struct mipi_dsi_device *dsi) 356 + { 357 + struct samsung_s6e3fc2x01 *ctx = mipi_dsi_get_drvdata(dsi); 358 + int ret; 359 + 360 + ret = mipi_dsi_detach(dsi); 361 + if (ret < 0) 362 + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 363 + 364 + drm_panel_remove(&ctx->panel); 365 + } 366 + 367 + static const struct of_device_id s6e3fc2x01_of_match[] = { 368 + { .compatible = "samsung,s6e3fc2x01-ams641rw", .data = &ams641rw_mode }, 369 + { /* sentinel */ } 370 + }; 371 + MODULE_DEVICE_TABLE(of, s6e3fc2x01_of_match); 372 + 373 + static struct mipi_dsi_driver s6e3fc2x01_driver = { 374 + .probe = s6e3fc2x01_probe, 375 + .remove = s6e3fc2x01_remove, 376 + .driver = { 377 + .name = "panel-samsung-s6e3fc2x01", 378 + .of_match_table = s6e3fc2x01_of_match, 379 + }, 380 + }; 381 + module_mipi_dsi_driver(s6e3fc2x01_driver); 382 + 383 + MODULE_AUTHOR("David Heidelberg <david@ixit.cz>"); 384 + MODULE_DESCRIPTION("DRM driver for Samsung S6E3FC2X01 DDIC"); 385 + MODULE_LICENSE("GPL");