linux/drivers/gpu/drm/panel/panel-samsung-s6e63j0x03.c
<<
>>
Prefs
   1/*
   2 * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
   3 *
   4 * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd
   5 *
   6 * Inki Dae <inki.dae@samsung.com>
   7 * Hoegeun Kwon <hoegeun.kwon@samsung.com>
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License version 2 as
  11 * published by the Free Software Foundation.
  12 */
  13
  14#include <drm/drmP.h>
  15#include <drm/drm_mipi_dsi.h>
  16#include <drm/drm_panel.h>
  17#include <linux/backlight.h>
  18#include <linux/gpio/consumer.h>
  19#include <linux/regulator/consumer.h>
  20#include <video/mipi_display.h>
  21
  22#define MCS_LEVEL2_KEY          0xf0
  23#define MCS_MTP_KEY             0xf1
  24#define MCS_MTP_SET3            0xd4
  25
  26#define MAX_BRIGHTNESS          100
  27#define DEFAULT_BRIGHTNESS      80
  28
  29#define NUM_GAMMA_STEPS         9
  30#define GAMMA_CMD_CNT           28
  31
  32#define FIRST_COLUMN 20
  33
  34struct s6e63j0x03 {
  35        struct device *dev;
  36        struct drm_panel panel;
  37        struct backlight_device *bl_dev;
  38
  39        struct regulator_bulk_data supplies[2];
  40        struct gpio_desc *reset_gpio;
  41};
  42
  43static const struct drm_display_mode default_mode = {
  44        .clock = 4649,
  45        .hdisplay = 320,
  46        .hsync_start = 320 + 1,
  47        .hsync_end = 320 + 1 + 1,
  48        .htotal = 320 + 1 + 1 + 1,
  49        .vdisplay = 320,
  50        .vsync_start = 320 + 150,
  51        .vsync_end = 320 + 150 + 1,
  52        .vtotal = 320 + 150 + 1 + 2,
  53        .vrefresh = 30,
  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, 0xbf
  63        },
  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, 0xdc
  69        },
  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, 0xf5
  75        },
  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, 0x09
  81        },
  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, 0x1b
  87        },
  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, 0x29
  93        },
  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, 0x3b
  99        },
 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, 0x47
 105        },
 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, 0x57
 111        }
 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 = FB_BLANK_NORMAL;
 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 = FB_BLANK_POWERDOWN;
 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 = FB_BLANK_NORMAL;
 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 = FB_BLANK_UNBLANK;
 397
 398        return 0;
 399}
 400
 401static int s6e63j0x03_get_modes(struct drm_panel *panel)
 402{
 403        struct drm_connector *connector = panel->connector;
 404        struct drm_display_mode *mode;
 405
 406        mode = drm_mode_duplicate(panel->drm, &default_mode);
 407        if (!mode) {
 408                DRM_ERROR("failed to add mode %ux%ux@%u\n",
 409                        default_mode.hdisplay, default_mode.vdisplay,
 410                        default_mode.vrefresh);
 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_EOT_PACKET;
 450
 451        ctx->supplies[0].supply = "vdd3";
 452        ctx->supplies[1].supply = "vci";
 453        ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
 454                                      ctx->supplies);
 455        if (ret < 0) {
 456                dev_err(dev, "failed to get regulators: %d\n", ret);
 457                return ret;
 458        }
 459
 460        ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
 461        if (IS_ERR(ctx->reset_gpio)) {
 462                dev_err(dev, "cannot get reset-gpio: %ld\n",
 463                                PTR_ERR(ctx->reset_gpio));
 464                return PTR_ERR(ctx->reset_gpio);
 465        }
 466
 467        drm_panel_init(&ctx->panel);
 468        ctx->panel.dev = dev;
 469        ctx->panel.funcs = &s6e63j0x03_funcs;
 470
 471        ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
 472                                                &s6e63j0x03_bl_ops, NULL);
 473        if (IS_ERR(ctx->bl_dev)) {
 474                dev_err(dev, "failed to register backlight device\n");
 475                return PTR_ERR(ctx->bl_dev);
 476        }
 477
 478        ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
 479        ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
 480        ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
 481
 482        ret = drm_panel_add(&ctx->panel);
 483        if (ret < 0)
 484                goto unregister_backlight;
 485
 486        ret = mipi_dsi_attach(dsi);
 487        if (ret < 0)
 488                goto remove_panel;
 489
 490        return ret;
 491
 492remove_panel:
 493        drm_panel_remove(&ctx->panel);
 494
 495unregister_backlight:
 496        backlight_device_unregister(ctx->bl_dev);
 497
 498        return ret;
 499}
 500
 501static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
 502{
 503        struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
 504
 505        mipi_dsi_detach(dsi);
 506        drm_panel_remove(&ctx->panel);
 507
 508        backlight_device_unregister(ctx->bl_dev);
 509
 510        return 0;
 511}
 512
 513static const struct of_device_id s6e63j0x03_of_match[] = {
 514        { .compatible = "samsung,s6e63j0x03" },
 515        { }
 516};
 517MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
 518
 519static struct mipi_dsi_driver s6e63j0x03_driver = {
 520        .probe = s6e63j0x03_probe,
 521        .remove = s6e63j0x03_remove,
 522        .driver = {
 523                .name = "panel_samsung_s6e63j0x03",
 524                .of_match_table = s6e63j0x03_of_match,
 525        },
 526};
 527module_mipi_dsi_driver(s6e63j0x03_driver);
 528
 529MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
 530MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
 531MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
 532MODULE_LICENSE("GPL v2");
 533