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.

at v6.16-rc1 492 lines 13 kB view raw
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Samsung S6D7AA0 MIPI-DSI TFT LCD controller drm_panel driver. 4 * 5 * Copyright (C) 2022 Artur Weber <aweber.kernel@gmail.com> 6 */ 7 8#include <linux/backlight.h> 9#include <linux/delay.h> 10#include <linux/gpio/consumer.h> 11#include <linux/module.h> 12#include <linux/regulator/consumer.h> 13#include <linux/of.h> 14 15#include <video/mipi_display.h> 16#include <drm/drm_mipi_dsi.h> 17#include <drm/drm_modes.h> 18#include <drm/drm_panel.h> 19 20/* Manufacturer command set */ 21#define MCS_BL_CTL 0xc3 22#define MCS_OTP_RELOAD 0xd0 23#define MCS_PASSWD1 0xf0 24#define MCS_PASSWD2 0xf1 25#define MCS_PASSWD3 0xfc 26 27struct s6d7aa0 { 28 struct drm_panel panel; 29 struct mipi_dsi_device *dsi; 30 struct gpio_desc *reset_gpio; 31 struct regulator_bulk_data supplies[2]; 32 const struct s6d7aa0_panel_desc *desc; 33}; 34 35struct s6d7aa0_panel_desc { 36 unsigned int panel_type; 37 void (*init_func)(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx); 38 void (*off_func)(struct mipi_dsi_multi_context *dsi_ctx); 39 const struct drm_display_mode *drm_mode; 40 unsigned long mode_flags; 41 u32 bus_flags; 42 bool has_backlight; 43 bool use_passwd3; 44}; 45 46enum s6d7aa0_panels { 47 S6D7AA0_PANEL_LSL080AL02, 48 S6D7AA0_PANEL_LSL080AL03, 49 S6D7AA0_PANEL_LTL101AT01, 50}; 51 52static inline struct s6d7aa0 *panel_to_s6d7aa0(struct drm_panel *panel) 53{ 54 return container_of(panel, struct s6d7aa0, panel); 55} 56 57static void s6d7aa0_reset(struct s6d7aa0 *ctx) 58{ 59 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 60 msleep(50); 61 gpiod_set_value_cansleep(ctx->reset_gpio, 0); 62 msleep(50); 63} 64 65static void s6d7aa0_lock(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx, bool lock) 66{ 67 if (lock) { 68 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD1, 0xa5, 0xa5); 69 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD2, 0xa5, 0xa5); 70 if (ctx->desc->use_passwd3) 71 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD3, 0x5a, 0x5a); 72 } else { 73 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD1, 0x5a, 0x5a); 74 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD2, 0x5a, 0x5a); 75 if (ctx->desc->use_passwd3) 76 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_PASSWD3, 0xa5, 0xa5); 77 } 78} 79 80static int s6d7aa0_on(struct s6d7aa0 *ctx) 81{ 82 struct mipi_dsi_device *dsi = ctx->dsi; 83 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 84 85 ctx->desc->init_func(ctx, &dsi_ctx); 86 87 mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 88 89 return dsi_ctx.accum_err; 90} 91 92static void s6d7aa0_off(struct s6d7aa0 *ctx) 93{ 94 struct mipi_dsi_device *dsi = ctx->dsi; 95 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 96 97 ctx->desc->off_func(&dsi_ctx); 98 99 mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 100 mipi_dsi_msleep(&dsi_ctx, 64); 101 102 mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 103 104 mipi_dsi_msleep(&dsi_ctx, 120); 105} 106 107static int s6d7aa0_prepare(struct drm_panel *panel) 108{ 109 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); 110 int ret; 111 112 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 113 if (ret < 0) 114 return ret; 115 116 s6d7aa0_reset(ctx); 117 118 ret = s6d7aa0_on(ctx); 119 if (ret < 0) { 120 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 121 return ret; 122 } 123 124 return 0; 125} 126 127static int s6d7aa0_disable(struct drm_panel *panel) 128{ 129 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); 130 131 s6d7aa0_off(ctx); 132 133 return 0; 134} 135 136static int s6d7aa0_unprepare(struct drm_panel *panel) 137{ 138 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel); 139 140 gpiod_set_value_cansleep(ctx->reset_gpio, 1); 141 regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 142 143 return 0; 144} 145 146/* Backlight control code */ 147 148static int s6d7aa0_bl_update_status(struct backlight_device *bl) 149{ 150 struct mipi_dsi_device *dsi = bl_get_data(bl); 151 u16 brightness = backlight_get_brightness(bl); 152 struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; 153 154 mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, brightness); 155 156 return dsi_ctx.accum_err; 157} 158 159static int s6d7aa0_bl_get_brightness(struct backlight_device *bl) 160{ 161 struct mipi_dsi_device *dsi = bl_get_data(bl); 162 u16 brightness; 163 int ret; 164 165 ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); 166 if (ret < 0) 167 return ret; 168 169 return brightness & 0xff; 170} 171 172static const struct backlight_ops s6d7aa0_bl_ops = { 173 .update_status = s6d7aa0_bl_update_status, 174 .get_brightness = s6d7aa0_bl_get_brightness, 175}; 176 177static struct backlight_device * 178s6d7aa0_create_backlight(struct mipi_dsi_device *dsi) 179{ 180 struct device *dev = &dsi->dev; 181 const struct backlight_properties props = { 182 .type = BACKLIGHT_RAW, 183 .brightness = 255, 184 .max_brightness = 255, 185 }; 186 187 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 188 &s6d7aa0_bl_ops, &props); 189} 190 191/* Initialization code and structures for LSL080AL02 panel */ 192 193static void s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx) 194{ 195 mipi_dsi_usleep_range(dsi_ctx, 20000, 25000); 196 197 s6d7aa0_lock(ctx, dsi_ctx, false); 198 199 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_OTP_RELOAD, 0x00, 0x10); 200 mipi_dsi_usleep_range(dsi_ctx, 1000, 1500); 201 202 /* SEQ_B6_PARAM_8_R01 */ 203 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb6, 0x10); 204 205 /* BL_CTL_ON */ 206 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x28); 207 208 mipi_dsi_usleep_range(dsi_ctx, 5000, 6000); 209 210 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x04); 211 212 mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); 213 214 mipi_dsi_msleep(dsi_ctx, 120); 215 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); 216 217 s6d7aa0_lock(ctx, dsi_ctx, true); 218 219 mipi_dsi_dcs_set_display_on_multi(dsi_ctx); 220} 221 222static void s6d7aa0_lsl080al02_off(struct mipi_dsi_multi_context *dsi_ctx) 223{ 224 /* BL_CTL_OFF */ 225 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x20); 226} 227 228static const struct drm_display_mode s6d7aa0_lsl080al02_mode = { 229 .clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000, 230 .hdisplay = 800, 231 .hsync_start = 800 + 16, 232 .hsync_end = 800 + 16 + 4, 233 .htotal = 800 + 16 + 4 + 140, 234 .vdisplay = 1280, 235 .vsync_start = 1280 + 8, 236 .vsync_end = 1280 + 8 + 4, 237 .vtotal = 1280 + 8 + 4 + 4, 238 .width_mm = 108, 239 .height_mm = 173, 240}; 241 242static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = { 243 .panel_type = S6D7AA0_PANEL_LSL080AL02, 244 .init_func = s6d7aa0_lsl080al02_init, 245 .off_func = s6d7aa0_lsl080al02_off, 246 .drm_mode = &s6d7aa0_lsl080al02_mode, 247 .mode_flags = MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_NO_HFP, 248 .bus_flags = 0, 249 250 .has_backlight = false, 251 .use_passwd3 = false, 252}; 253 254/* Initialization code and structures for LSL080AL03 panel */ 255 256static void s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx, struct mipi_dsi_multi_context *dsi_ctx) 257{ 258 mipi_dsi_usleep_range(dsi_ctx, 20000, 25000); 259 260 s6d7aa0_lock(ctx, dsi_ctx, false); 261 262 if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) { 263 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0xc7, 0x00, 0x29); 264 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x01, 0x4e, 0xa0); 265 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfd, 0x16, 0x10, 0x11, 0x23, 266 0x09); 267 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfe, 0x00, 0x02, 0x03, 0x21, 268 0x80, 0x78); 269 } else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) { 270 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MCS_BL_CTL, 0x40, 0x00, 0x08); 271 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x01, 0x4e, 0x0b); 272 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfd, 0x16, 0x10, 0x11, 0x23, 273 0x09); 274 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfe, 0x00, 0x02, 0x03, 0x21, 275 0x80, 0x68); 276 } 277 278 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb3, 0x51); 279 mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); 280 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf2, 0x02, 0x08, 0x08); 281 282 mipi_dsi_usleep_range(dsi_ctx, 10000, 11000); 283 284 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x80, 0x80, 0x30); 285 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcd, 286 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 287 0x2e, 0x2e, 0x2e, 0x2e, 0x2e); 288 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xce, 289 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 290 0x00, 0x00, 0x00, 0x00, 0x00); 291 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x03); 292 293 mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); 294 s6d7aa0_lock(ctx, dsi_ctx, true); 295 mipi_dsi_dcs_set_display_on_multi(dsi_ctx); 296} 297 298static void s6d7aa0_lsl080al03_off(struct mipi_dsi_multi_context *dsi_ctx) 299{ 300 mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x22, 0x00); 301} 302 303static const struct drm_display_mode s6d7aa0_lsl080al03_mode = { 304 .clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000, 305 .hdisplay = 768, 306 .hsync_start = 768 + 18, 307 .hsync_end = 768 + 18 + 16, 308 .htotal = 768 + 18 + 16 + 126, 309 .vdisplay = 1024, 310 .vsync_start = 1024 + 8, 311 .vsync_end = 1024 + 8 + 2, 312 .vtotal = 1024 + 8 + 2 + 6, 313 .width_mm = 122, 314 .height_mm = 163, 315}; 316 317static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = { 318 .panel_type = S6D7AA0_PANEL_LSL080AL03, 319 .init_func = s6d7aa0_lsl080al03_init, 320 .off_func = s6d7aa0_lsl080al03_off, 321 .drm_mode = &s6d7aa0_lsl080al03_mode, 322 .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, 323 .bus_flags = 0, 324 325 .has_backlight = true, 326 .use_passwd3 = true, 327}; 328 329/* Initialization structures for LTL101AT01 panel */ 330 331static const struct drm_display_mode s6d7aa0_ltl101at01_mode = { 332 .clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000, 333 .hdisplay = 768, 334 .hsync_start = 768 + 96, 335 .hsync_end = 768 + 96 + 16, 336 .htotal = 768 + 96 + 16 + 184, 337 .vdisplay = 1024, 338 .vsync_start = 1024 + 8, 339 .vsync_end = 1024 + 8 + 2, 340 .vtotal = 1024 + 8 + 2 + 6, 341 .width_mm = 148, 342 .height_mm = 197, 343}; 344 345static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = { 346 .panel_type = S6D7AA0_PANEL_LTL101AT01, 347 .init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */ 348 .off_func = s6d7aa0_lsl080al03_off, 349 .drm_mode = &s6d7aa0_ltl101at01_mode, 350 .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET, 351 .bus_flags = 0, 352 353 .has_backlight = true, 354 .use_passwd3 = true, 355}; 356 357static int s6d7aa0_get_modes(struct drm_panel *panel, 358 struct drm_connector *connector) 359{ 360 struct drm_display_mode *mode; 361 struct s6d7aa0 *ctx; 362 363 ctx = container_of(panel, struct s6d7aa0, panel); 364 if (!ctx) 365 return -EINVAL; 366 367 mode = drm_mode_duplicate(connector->dev, ctx->desc->drm_mode); 368 if (!mode) 369 return -ENOMEM; 370 371 drm_mode_set_name(mode); 372 373 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 374 connector->display_info.width_mm = mode->width_mm; 375 connector->display_info.height_mm = mode->height_mm; 376 connector->display_info.bus_flags = ctx->desc->bus_flags; 377 drm_mode_probed_add(connector, mode); 378 379 return 1; 380} 381 382static const struct drm_panel_funcs s6d7aa0_panel_funcs = { 383 .disable = s6d7aa0_disable, 384 .prepare = s6d7aa0_prepare, 385 .unprepare = s6d7aa0_unprepare, 386 .get_modes = s6d7aa0_get_modes, 387}; 388 389static int s6d7aa0_probe(struct mipi_dsi_device *dsi) 390{ 391 struct device *dev = &dsi->dev; 392 struct s6d7aa0 *ctx; 393 int ret; 394 395 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 396 if (!ctx) 397 return -ENOMEM; 398 399 ctx->desc = of_device_get_match_data(dev); 400 if (!ctx->desc) 401 return -ENODEV; 402 403 ctx->supplies[0].supply = "power"; 404 ctx->supplies[1].supply = "vmipi"; 405 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), 406 ctx->supplies); 407 if (ret < 0) 408 return dev_err_probe(dev, ret, "Failed to get regulators\n"); 409 410 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 411 if (IS_ERR(ctx->reset_gpio)) 412 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), 413 "Failed to get reset-gpios\n"); 414 415 ctx->dsi = dsi; 416 mipi_dsi_set_drvdata(dsi, ctx); 417 418 dsi->lanes = 4; 419 dsi->format = MIPI_DSI_FMT_RGB888; 420 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST 421 | ctx->desc->mode_flags; 422 423 drm_panel_init(&ctx->panel, dev, &s6d7aa0_panel_funcs, 424 DRM_MODE_CONNECTOR_DSI); 425 ctx->panel.prepare_prev_first = true; 426 427 ret = drm_panel_of_backlight(&ctx->panel); 428 if (ret) 429 return dev_err_probe(dev, ret, "Failed to get backlight\n"); 430 431 /* Use DSI-based backlight as fallback if available */ 432 if (ctx->desc->has_backlight && !ctx->panel.backlight) { 433 ctx->panel.backlight = s6d7aa0_create_backlight(dsi); 434 if (IS_ERR(ctx->panel.backlight)) 435 return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 436 "Failed to create backlight\n"); 437 } 438 439 drm_panel_add(&ctx->panel); 440 441 ret = mipi_dsi_attach(dsi); 442 if (ret < 0) { 443 dev_err(dev, "Failed to attach to DSI host: %d\n", ret); 444 drm_panel_remove(&ctx->panel); 445 return ret; 446 } 447 448 return 0; 449} 450 451static void s6d7aa0_remove(struct mipi_dsi_device *dsi) 452{ 453 struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi); 454 int ret; 455 456 ret = mipi_dsi_detach(dsi); 457 if (ret < 0) 458 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 459 460 drm_panel_remove(&ctx->panel); 461} 462 463static const struct of_device_id s6d7aa0_of_match[] = { 464 { 465 .compatible = "samsung,lsl080al02", 466 .data = &s6d7aa0_lsl080al02_desc 467 }, 468 { 469 .compatible = "samsung,lsl080al03", 470 .data = &s6d7aa0_lsl080al03_desc 471 }, 472 { 473 .compatible = "samsung,ltl101at01", 474 .data = &s6d7aa0_ltl101at01_desc 475 }, 476 { /* sentinel */ } 477}; 478MODULE_DEVICE_TABLE(of, s6d7aa0_of_match); 479 480static struct mipi_dsi_driver s6d7aa0_driver = { 481 .probe = s6d7aa0_probe, 482 .remove = s6d7aa0_remove, 483 .driver = { 484 .name = "panel-samsung-s6d7aa0", 485 .of_match_table = s6d7aa0_of_match, 486 }, 487}; 488module_mipi_dsi_driver(s6d7aa0_driver); 489 490MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>"); 491MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver"); 492MODULE_LICENSE("GPL");