585 lines · c
1// SPDX-License-Identifier: GPL-2.02/*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 0xc322#define MCS_OTP_RELOAD 0xd023#define MCS_PASSWD1 0xf024#define MCS_PASSWD2 0xf125#define MCS_PASSWD3 0xfc26 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 int (*init_func)(struct s6d7aa0 *ctx);38 int (*off_func)(struct s6d7aa0 *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 int s6d7aa0_lock(struct s6d7aa0 *ctx, bool lock)66{67 struct mipi_dsi_device *dsi = ctx->dsi;68 69 if (lock) {70 mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0xa5, 0xa5);71 mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0xa5, 0xa5);72 if (ctx->desc->use_passwd3)73 mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0x5a, 0x5a);74 } else {75 mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0x5a, 0x5a);76 mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0x5a, 0x5a);77 if (ctx->desc->use_passwd3)78 mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0xa5, 0xa5);79 }80 81 return 0;82}83 84static int s6d7aa0_on(struct s6d7aa0 *ctx)85{86 struct mipi_dsi_device *dsi = ctx->dsi;87 struct device *dev = &dsi->dev;88 int ret;89 90 ret = ctx->desc->init_func(ctx);91 if (ret < 0) {92 dev_err(dev, "Failed to initialize panel: %d\n", ret);93 gpiod_set_value_cansleep(ctx->reset_gpio, 1);94 return ret;95 }96 97 ret = mipi_dsi_dcs_set_display_on(dsi);98 if (ret < 0) {99 dev_err(dev, "Failed to set display on: %d\n", ret);100 return ret;101 }102 103 return 0;104}105 106static int s6d7aa0_off(struct s6d7aa0 *ctx)107{108 struct mipi_dsi_device *dsi = ctx->dsi;109 struct device *dev = &dsi->dev;110 int ret;111 112 ret = ctx->desc->off_func(ctx);113 if (ret < 0) {114 dev_err(dev, "Panel-specific off function failed: %d\n", ret);115 return ret;116 }117 118 ret = mipi_dsi_dcs_set_display_off(dsi);119 if (ret < 0) {120 dev_err(dev, "Failed to set display off: %d\n", ret);121 return ret;122 }123 msleep(64);124 125 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);126 if (ret < 0) {127 dev_err(dev, "Failed to enter sleep mode: %d\n", ret);128 return ret;129 }130 msleep(120);131 132 return 0;133}134 135static int s6d7aa0_prepare(struct drm_panel *panel)136{137 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);138 struct device *dev = &ctx->dsi->dev;139 int ret;140 141 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);142 if (ret < 0) {143 dev_err(dev, "Failed to enable regulators: %d\n", ret);144 return ret;145 }146 147 s6d7aa0_reset(ctx);148 149 ret = s6d7aa0_on(ctx);150 if (ret < 0) {151 dev_err(dev, "Failed to initialize panel: %d\n", ret);152 gpiod_set_value_cansleep(ctx->reset_gpio, 1);153 return ret;154 }155 156 return 0;157}158 159static int s6d7aa0_disable(struct drm_panel *panel)160{161 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);162 struct device *dev = &ctx->dsi->dev;163 int ret;164 165 ret = s6d7aa0_off(ctx);166 if (ret < 0)167 dev_err(dev, "Failed to un-initialize panel: %d\n", ret);168 169 return 0;170}171 172static int s6d7aa0_unprepare(struct drm_panel *panel)173{174 struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);175 176 gpiod_set_value_cansleep(ctx->reset_gpio, 1);177 regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);178 179 return 0;180}181 182/* Backlight control code */183 184static int s6d7aa0_bl_update_status(struct backlight_device *bl)185{186 struct mipi_dsi_device *dsi = bl_get_data(bl);187 u16 brightness = backlight_get_brightness(bl);188 int ret;189 190 ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness);191 if (ret < 0)192 return ret;193 194 return 0;195}196 197static int s6d7aa0_bl_get_brightness(struct backlight_device *bl)198{199 struct mipi_dsi_device *dsi = bl_get_data(bl);200 u16 brightness;201 int ret;202 203 ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);204 if (ret < 0)205 return ret;206 207 return brightness & 0xff;208}209 210static const struct backlight_ops s6d7aa0_bl_ops = {211 .update_status = s6d7aa0_bl_update_status,212 .get_brightness = s6d7aa0_bl_get_brightness,213};214 215static struct backlight_device *216s6d7aa0_create_backlight(struct mipi_dsi_device *dsi)217{218 struct device *dev = &dsi->dev;219 const struct backlight_properties props = {220 .type = BACKLIGHT_RAW,221 .brightness = 255,222 .max_brightness = 255,223 };224 225 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,226 &s6d7aa0_bl_ops, &props);227}228 229/* Initialization code and structures for LSL080AL02 panel */230 231static int s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx)232{233 struct mipi_dsi_device *dsi = ctx->dsi;234 struct device *dev = &dsi->dev;235 int ret;236 237 usleep_range(20000, 25000);238 239 ret = s6d7aa0_lock(ctx, false);240 if (ret < 0) {241 dev_err(dev, "Failed to unlock registers: %d\n", ret);242 return ret;243 }244 245 mipi_dsi_dcs_write_seq(dsi, MCS_OTP_RELOAD, 0x00, 0x10);246 usleep_range(1000, 1500);247 248 /* SEQ_B6_PARAM_8_R01 */249 mipi_dsi_dcs_write_seq(dsi, 0xb6, 0x10);250 251 /* BL_CTL_ON */252 mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x28);253 254 usleep_range(5000, 6000);255 256 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x04);257 258 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);259 if (ret < 0) {260 dev_err(dev, "Failed to exit sleep mode: %d\n", ret);261 return ret;262 }263 264 msleep(120);265 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x00);266 267 ret = s6d7aa0_lock(ctx, true);268 if (ret < 0) {269 dev_err(dev, "Failed to lock registers: %d\n", ret);270 return ret;271 }272 273 ret = mipi_dsi_dcs_set_display_on(dsi);274 if (ret < 0) {275 dev_err(dev, "Failed to set display on: %d\n", ret);276 return ret;277 }278 279 return 0;280}281 282static int s6d7aa0_lsl080al02_off(struct s6d7aa0 *ctx)283{284 struct mipi_dsi_device *dsi = ctx->dsi;285 286 /* BL_CTL_OFF */287 mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x20);288 289 return 0;290}291 292static const struct drm_display_mode s6d7aa0_lsl080al02_mode = {293 .clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000,294 .hdisplay = 800,295 .hsync_start = 800 + 16,296 .hsync_end = 800 + 16 + 4,297 .htotal = 800 + 16 + 4 + 140,298 .vdisplay = 1280,299 .vsync_start = 1280 + 8,300 .vsync_end = 1280 + 8 + 4,301 .vtotal = 1280 + 8 + 4 + 4,302 .width_mm = 108,303 .height_mm = 173,304};305 306static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = {307 .panel_type = S6D7AA0_PANEL_LSL080AL02,308 .init_func = s6d7aa0_lsl080al02_init,309 .off_func = s6d7aa0_lsl080al02_off,310 .drm_mode = &s6d7aa0_lsl080al02_mode,311 .mode_flags = MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_NO_HFP,312 .bus_flags = 0,313 314 .has_backlight = false,315 .use_passwd3 = false,316};317 318/* Initialization code and structures for LSL080AL03 panel */319 320static int s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx)321{322 struct mipi_dsi_device *dsi = ctx->dsi;323 struct device *dev = &dsi->dev;324 int ret;325 326 usleep_range(20000, 25000);327 328 ret = s6d7aa0_lock(ctx, false);329 if (ret < 0) {330 dev_err(dev, "Failed to unlock registers: %d\n", ret);331 return ret;332 }333 334 if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) {335 mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0xc7, 0x00, 0x29);336 mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0xa0);337 mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23,338 0x09);339 mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21,340 0x80, 0x78);341 } else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) {342 mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x08);343 mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0x0b);344 mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23,345 0x09);346 mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21,347 0x80, 0x68);348 }349 350 mipi_dsi_dcs_write_seq(dsi, 0xb3, 0x51);351 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);352 mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x02, 0x08, 0x08);353 354 usleep_range(10000, 11000);355 356 mipi_dsi_dcs_write_seq(dsi, 0xc0, 0x80, 0x80, 0x30);357 mipi_dsi_dcs_write_seq(dsi, 0xcd,358 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,359 0x2e, 0x2e, 0x2e, 0x2e, 0x2e);360 mipi_dsi_dcs_write_seq(dsi, 0xce,361 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,362 0x00, 0x00, 0x00, 0x00, 0x00);363 mipi_dsi_dcs_write_seq(dsi, 0xc1, 0x03);364 365 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);366 if (ret < 0) {367 dev_err(dev, "Failed to exit sleep mode: %d\n", ret);368 return ret;369 }370 371 ret = s6d7aa0_lock(ctx, true);372 if (ret < 0) {373 dev_err(dev, "Failed to lock registers: %d\n", ret);374 return ret;375 }376 377 ret = mipi_dsi_dcs_set_display_on(dsi);378 if (ret < 0) {379 dev_err(dev, "Failed to set display on: %d\n", ret);380 return ret;381 }382 383 return 0;384}385 386static int s6d7aa0_lsl080al03_off(struct s6d7aa0 *ctx)387{388 struct mipi_dsi_device *dsi = ctx->dsi;389 390 mipi_dsi_dcs_write_seq(dsi, 0x22, 0x00);391 392 return 0;393}394 395static const struct drm_display_mode s6d7aa0_lsl080al03_mode = {396 .clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000,397 .hdisplay = 768,398 .hsync_start = 768 + 18,399 .hsync_end = 768 + 18 + 16,400 .htotal = 768 + 18 + 16 + 126,401 .vdisplay = 1024,402 .vsync_start = 1024 + 8,403 .vsync_end = 1024 + 8 + 2,404 .vtotal = 1024 + 8 + 2 + 6,405 .width_mm = 122,406 .height_mm = 163,407};408 409static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = {410 .panel_type = S6D7AA0_PANEL_LSL080AL03,411 .init_func = s6d7aa0_lsl080al03_init,412 .off_func = s6d7aa0_lsl080al03_off,413 .drm_mode = &s6d7aa0_lsl080al03_mode,414 .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET,415 .bus_flags = 0,416 417 .has_backlight = true,418 .use_passwd3 = true,419};420 421/* Initialization structures for LTL101AT01 panel */422 423static const struct drm_display_mode s6d7aa0_ltl101at01_mode = {424 .clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000,425 .hdisplay = 768,426 .hsync_start = 768 + 96,427 .hsync_end = 768 + 96 + 16,428 .htotal = 768 + 96 + 16 + 184,429 .vdisplay = 1024,430 .vsync_start = 1024 + 8,431 .vsync_end = 1024 + 8 + 2,432 .vtotal = 1024 + 8 + 2 + 6,433 .width_mm = 148,434 .height_mm = 197,435};436 437static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = {438 .panel_type = S6D7AA0_PANEL_LTL101AT01,439 .init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */440 .off_func = s6d7aa0_lsl080al03_off,441 .drm_mode = &s6d7aa0_ltl101at01_mode,442 .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET,443 .bus_flags = 0,444 445 .has_backlight = true,446 .use_passwd3 = true,447};448 449static int s6d7aa0_get_modes(struct drm_panel *panel,450 struct drm_connector *connector)451{452 struct drm_display_mode *mode;453 struct s6d7aa0 *ctx;454 455 ctx = container_of(panel, struct s6d7aa0, panel);456 if (!ctx)457 return -EINVAL;458 459 mode = drm_mode_duplicate(connector->dev, ctx->desc->drm_mode);460 if (!mode)461 return -ENOMEM;462 463 drm_mode_set_name(mode);464 465 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;466 connector->display_info.width_mm = mode->width_mm;467 connector->display_info.height_mm = mode->height_mm;468 connector->display_info.bus_flags = ctx->desc->bus_flags;469 drm_mode_probed_add(connector, mode);470 471 return 1;472}473 474static const struct drm_panel_funcs s6d7aa0_panel_funcs = {475 .disable = s6d7aa0_disable,476 .prepare = s6d7aa0_prepare,477 .unprepare = s6d7aa0_unprepare,478 .get_modes = s6d7aa0_get_modes,479};480 481static int s6d7aa0_probe(struct mipi_dsi_device *dsi)482{483 struct device *dev = &dsi->dev;484 struct s6d7aa0 *ctx;485 int ret;486 487 ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);488 if (!ctx)489 return -ENOMEM;490 491 ctx->desc = of_device_get_match_data(dev);492 if (!ctx->desc)493 return -ENODEV;494 495 ctx->supplies[0].supply = "power";496 ctx->supplies[1].supply = "vmipi";497 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),498 ctx->supplies);499 if (ret < 0)500 return dev_err_probe(dev, ret, "Failed to get regulators\n");501 502 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);503 if (IS_ERR(ctx->reset_gpio))504 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),505 "Failed to get reset-gpios\n");506 507 ctx->dsi = dsi;508 mipi_dsi_set_drvdata(dsi, ctx);509 510 dsi->lanes = 4;511 dsi->format = MIPI_DSI_FMT_RGB888;512 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST513 | ctx->desc->mode_flags;514 515 drm_panel_init(&ctx->panel, dev, &s6d7aa0_panel_funcs,516 DRM_MODE_CONNECTOR_DSI);517 ctx->panel.prepare_prev_first = true;518 519 ret = drm_panel_of_backlight(&ctx->panel);520 if (ret)521 return dev_err_probe(dev, ret, "Failed to get backlight\n");522 523 /* Use DSI-based backlight as fallback if available */524 if (ctx->desc->has_backlight && !ctx->panel.backlight) {525 ctx->panel.backlight = s6d7aa0_create_backlight(dsi);526 if (IS_ERR(ctx->panel.backlight))527 return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),528 "Failed to create backlight\n");529 }530 531 drm_panel_add(&ctx->panel);532 533 ret = mipi_dsi_attach(dsi);534 if (ret < 0) {535 dev_err(dev, "Failed to attach to DSI host: %d\n", ret);536 drm_panel_remove(&ctx->panel);537 return ret;538 }539 540 return 0;541}542 543static void s6d7aa0_remove(struct mipi_dsi_device *dsi)544{545 struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi);546 int ret;547 548 ret = mipi_dsi_detach(dsi);549 if (ret < 0)550 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);551 552 drm_panel_remove(&ctx->panel);553}554 555static const struct of_device_id s6d7aa0_of_match[] = {556 {557 .compatible = "samsung,lsl080al02",558 .data = &s6d7aa0_lsl080al02_desc559 },560 {561 .compatible = "samsung,lsl080al03",562 .data = &s6d7aa0_lsl080al03_desc563 },564 {565 .compatible = "samsung,ltl101at01",566 .data = &s6d7aa0_ltl101at01_desc567 },568 { /* sentinel */ }569};570MODULE_DEVICE_TABLE(of, s6d7aa0_of_match);571 572static struct mipi_dsi_driver s6d7aa0_driver = {573 .probe = s6d7aa0_probe,574 .remove = s6d7aa0_remove,575 .driver = {576 .name = "panel-samsung-s6d7aa0",577 .of_match_table = s6d7aa0_of_match,578 },579};580module_mipi_dsi_driver(s6d7aa0_driver);581 582MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>");583MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver");584MODULE_LICENSE("GPL");585