523 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.4 *5 * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd6 *7 * Inki Dae <inki.dae@samsung.com>8 * Hoegeun Kwon <hoegeun.kwon@samsung.com>9 */10 11#include <linux/backlight.h>12#include <linux/delay.h>13#include <linux/gpio/consumer.h>14#include <linux/module.h>15#include <linux/regulator/consumer.h>16 17#include <video/mipi_display.h>18 19#include <drm/drm_mipi_dsi.h>20#include <drm/drm_modes.h>21#include <drm/drm_panel.h>22 23#define MCS_LEVEL2_KEY 0xf024#define MCS_MTP_KEY 0xf125#define MCS_MTP_SET3 0xd426 27#define MAX_BRIGHTNESS 10028#define DEFAULT_BRIGHTNESS 8029 30#define NUM_GAMMA_STEPS 931#define GAMMA_CMD_CNT 2832 33#define FIRST_COLUMN 2034 35struct s6e63j0x03 {36 struct device *dev;37 struct drm_panel panel;38 struct backlight_device *bl_dev;39 40 struct regulator_bulk_data supplies[2];41 struct gpio_desc *reset_gpio;42};43 44static const struct drm_display_mode default_mode = {45 .clock = 4649,46 .hdisplay = 320,47 .hsync_start = 320 + 1,48 .hsync_end = 320 + 1 + 1,49 .htotal = 320 + 1 + 1 + 1,50 .vdisplay = 320,51 .vsync_start = 320 + 150,52 .vsync_end = 320 + 150 + 1,53 .vtotal = 320 + 150 + 1 + 2,54 .flags = 0,55};56 57static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = {58 { /* Gamma 10 */59 MCS_MTP_SET3,60 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26,61 0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36,62 0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf63 },64 { /* gamma 30 */65 MCS_MTP_SET3,66 0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26,67 0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34,68 0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc69 },70 { /* gamma 60 */71 MCS_MTP_SET3,72 0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a,73 0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33,74 0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf575 },76 { /* gamma 90 */77 MCS_MTP_SET3,78 0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29,79 0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31,80 0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x0981 },82 { /* gamma 120 */83 MCS_MTP_SET3,84 0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28,85 0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31,86 0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b87 },88 { /* gamma 150 */89 MCS_MTP_SET3,90 0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28,91 0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31,92 0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x2993 },94 { /* gamma 200 */95 MCS_MTP_SET3,96 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29,97 0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30,98 0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b99 },100 { /* gamma 240 */101 MCS_MTP_SET3,102 0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a,103 0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30,104 0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47105 },106 { /* gamma 300 */107 MCS_MTP_SET3,108 0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28,109 0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f,110 0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57111 }112};113 114static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)115{116 return container_of(panel, struct s6e63j0x03, panel);117}118 119static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx,120 const void *seq, size_t len)121{122 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);123 124 return mipi_dsi_dcs_write_buffer(dsi, seq, len);125}126 127#define s6e63j0x03_dcs_write_seq_static(ctx, seq...) \128 ({ \129 static const u8 d[] = { seq }; \130 s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d)); \131 })132 133static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx)134{135 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a);136}137 138static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)139{140 if (on)141 return s6e63j0x03_dcs_write_seq_static(ctx,142 MCS_MTP_KEY, 0x5a, 0x5a);143 144 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5);145}146 147static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)148{149 int ret;150 151 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);152 if (ret < 0)153 return ret;154 155 msleep(30);156 157 gpiod_set_value(ctx->reset_gpio, 1);158 usleep_range(1000, 2000);159 gpiod_set_value(ctx->reset_gpio, 0);160 usleep_range(5000, 6000);161 162 return 0;163}164 165static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)166{167 return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);168}169 170static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness)171{172 unsigned int index;173 174 index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS);175 176 if (index >= NUM_GAMMA_STEPS)177 index = NUM_GAMMA_STEPS - 1;178 179 return index;180}181 182static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,183 unsigned int brightness)184{185 struct backlight_device *bl_dev = ctx->bl_dev;186 unsigned int index = s6e63j0x03_get_brightness_index(brightness);187 int ret;188 189 ret = s6e63j0x03_apply_mtp_key(ctx, true);190 if (ret < 0)191 return ret;192 193 ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT);194 if (ret < 0)195 return ret;196 197 ret = s6e63j0x03_apply_mtp_key(ctx, false);198 if (ret < 0)199 return ret;200 201 bl_dev->props.brightness = brightness;202 203 return 0;204}205 206static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)207{208 struct s6e63j0x03 *ctx = bl_get_data(bl_dev);209 unsigned int brightness = bl_dev->props.brightness;210 211 return s6e63j0x03_update_gamma(ctx, brightness);212}213 214static const struct backlight_ops s6e63j0x03_bl_ops = {215 .update_status = s6e63j0x03_set_brightness,216};217 218static int s6e63j0x03_disable(struct drm_panel *panel)219{220 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);221 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);222 int ret;223 224 ret = mipi_dsi_dcs_set_display_off(dsi);225 if (ret < 0)226 return ret;227 228 ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED;229 230 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);231 if (ret < 0)232 return ret;233 234 msleep(120);235 236 return 0;237}238 239static int s6e63j0x03_unprepare(struct drm_panel *panel)240{241 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);242 int ret;243 244 ret = s6e63j0x03_power_off(ctx);245 if (ret < 0)246 return ret;247 248 ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF;249 250 return 0;251}252 253static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx)254{255 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);256 int ret;257 258 ret = s6e63j0x03_enable_lv2_command(ctx);259 if (ret < 0)260 return ret;261 262 ret = s6e63j0x03_apply_mtp_key(ctx, true);263 if (ret < 0)264 return ret;265 266 /* set porch adjustment */267 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);268 if (ret < 0)269 return ret;270 271 /* set frame freq */272 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);273 if (ret < 0)274 return ret;275 276 /* set caset, paset */277 ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN,278 default_mode.hdisplay - 1 + FIRST_COLUMN);279 if (ret < 0)280 return ret;281 282 ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1);283 if (ret < 0)284 return ret;285 286 /* set ltps timming 0, 1 */287 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17,288 0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00);289 if (ret < 0)290 return ret;291 292 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);293 if (ret < 0)294 return ret;295 296 /* set param pos te_edge */297 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);298 if (ret < 0)299 return ret;300 301 /* set te rising edge */302 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);303 if (ret < 0)304 return ret;305 306 /* set param pos default */307 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);308 if (ret < 0)309 return ret;310 311 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);312 if (ret < 0)313 return ret;314 315 ret = s6e63j0x03_apply_mtp_key(ctx, false);316 if (ret < 0)317 return ret;318 319 return 0;320}321 322static int s6e63j0x03_prepare(struct drm_panel *panel)323{324 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);325 int ret;326 327 ret = s6e63j0x03_power_on(ctx);328 if (ret < 0)329 return ret;330 331 ret = s6e63j0x03_panel_init(ctx);332 if (ret < 0)333 goto err;334 335 ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED;336 337 return 0;338 339err:340 s6e63j0x03_power_off(ctx);341 return ret;342}343 344static int s6e63j0x03_enable(struct drm_panel *panel)345{346 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);347 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);348 int ret;349 350 msleep(120);351 352 ret = s6e63j0x03_apply_mtp_key(ctx, true);353 if (ret < 0)354 return ret;355 356 /* set elvss_cond */357 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);358 if (ret < 0)359 return ret;360 361 /* set pos */362 ret = s6e63j0x03_dcs_write_seq_static(ctx,363 MIPI_DCS_SET_ADDRESS_MODE, 0x40);364 if (ret < 0)365 return ret;366 367 /* set default white brightness */368 ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);369 if (ret < 0)370 return ret;371 372 /* set white ctrl */373 ret = s6e63j0x03_dcs_write_seq_static(ctx,374 MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);375 if (ret < 0)376 return ret;377 378 /* set acl off */379 ret = s6e63j0x03_dcs_write_seq_static(ctx,380 MIPI_DCS_WRITE_POWER_SAVE, 0x00);381 if (ret < 0)382 return ret;383 384 ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);385 if (ret < 0)386 return ret;387 388 ret = s6e63j0x03_apply_mtp_key(ctx, false);389 if (ret < 0)390 return ret;391 392 ret = mipi_dsi_dcs_set_display_on(dsi);393 if (ret < 0)394 return ret;395 396 ctx->bl_dev->props.power = BACKLIGHT_POWER_ON;397 398 return 0;399}400 401static int s6e63j0x03_get_modes(struct drm_panel *panel,402 struct drm_connector *connector)403{404 struct drm_display_mode *mode;405 406 mode = drm_mode_duplicate(connector->dev, &default_mode);407 if (!mode) {408 dev_err(panel->dev, "failed to add mode %ux%u@%u\n",409 default_mode.hdisplay, default_mode.vdisplay,410 drm_mode_vrefresh(&default_mode));411 return -ENOMEM;412 }413 414 drm_mode_set_name(mode);415 416 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;417 drm_mode_probed_add(connector, mode);418 419 connector->display_info.width_mm = 29;420 connector->display_info.height_mm = 29;421 422 return 1;423}424 425static const struct drm_panel_funcs s6e63j0x03_funcs = {426 .disable = s6e63j0x03_disable,427 .unprepare = s6e63j0x03_unprepare,428 .prepare = s6e63j0x03_prepare,429 .enable = s6e63j0x03_enable,430 .get_modes = s6e63j0x03_get_modes,431};432 433static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)434{435 struct device *dev = &dsi->dev;436 struct s6e63j0x03 *ctx;437 int ret;438 439 ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);440 if (!ctx)441 return -ENOMEM;442 443 mipi_dsi_set_drvdata(dsi, ctx);444 445 ctx->dev = dev;446 447 dsi->lanes = 1;448 dsi->format = MIPI_DSI_FMT_RGB888;449 dsi->mode_flags = MIPI_DSI_MODE_VIDEO_NO_HFP |450 MIPI_DSI_MODE_VIDEO_NO_HBP | MIPI_DSI_MODE_VIDEO_NO_HSA;451 452 ctx->supplies[0].supply = "vdd3";453 ctx->supplies[1].supply = "vci";454 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),455 ctx->supplies);456 if (ret < 0)457 return dev_err_probe(dev, ret, "failed to get regulators\n");458 459 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);460 if (IS_ERR(ctx->reset_gpio))461 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),462 "cannot get reset-gpio\n");463 464 drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs,465 DRM_MODE_CONNECTOR_DSI);466 ctx->panel.prepare_prev_first = true;467 468 ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,469 &s6e63j0x03_bl_ops, NULL);470 if (IS_ERR(ctx->bl_dev))471 return dev_err_probe(dev, PTR_ERR(ctx->bl_dev),472 "failed to register backlight device\n");473 474 ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;475 ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;476 ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF;477 478 drm_panel_add(&ctx->panel);479 480 ret = mipi_dsi_attach(dsi);481 if (ret < 0)482 goto remove_panel;483 484 return ret;485 486remove_panel:487 drm_panel_remove(&ctx->panel);488 backlight_device_unregister(ctx->bl_dev);489 490 return ret;491}492 493static void s6e63j0x03_remove(struct mipi_dsi_device *dsi)494{495 struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);496 497 mipi_dsi_detach(dsi);498 drm_panel_remove(&ctx->panel);499 500 backlight_device_unregister(ctx->bl_dev);501}502 503static const struct of_device_id s6e63j0x03_of_match[] = {504 { .compatible = "samsung,s6e63j0x03" },505 { }506};507MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);508 509static struct mipi_dsi_driver s6e63j0x03_driver = {510 .probe = s6e63j0x03_probe,511 .remove = s6e63j0x03_remove,512 .driver = {513 .name = "panel_samsung_s6e63j0x03",514 .of_match_table = s6e63j0x03_of_match,515 },516};517module_mipi_dsi_driver(s6e63j0x03_driver);518 519MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");520MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");521MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");522MODULE_LICENSE("GPL v2");523