linux/drivers/gpu/drm/panel/panel-samsung-s6e63m0.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * S6E63M0 AMOLED LCD drm_panel driver.
   4 *
   5 * Copyright (C) 2019 Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
   6 * Derived from drivers/gpu/drm/panel-samsung-ld9040.c
   7 *
   8 * Andrzej Hajda <a.hajda@samsung.com>
   9 */
  10
  11#include <drm/drm_modes.h>
  12#include <drm/drm_panel.h>
  13#include <drm/drm_print.h>
  14
  15#include <linux/backlight.h>
  16#include <linux/delay.h>
  17#include <linux/gpio/consumer.h>
  18#include <linux/module.h>
  19#include <linux/regulator/consumer.h>
  20#include <linux/spi/spi.h>
  21
  22#include <video/mipi_display.h>
  23
  24/* Manufacturer Command Set */
  25#define MCS_ELVSS_ON                0xb1
  26#define MCS_MIECTL1                0xc0
  27#define MCS_BCMODE                              0xc1
  28#define MCS_DISCTL   0xf2
  29#define MCS_SRCCTL           0xf6
  30#define MCS_IFCTL                       0xf7
  31#define MCS_PANELCTL         0xF8
  32#define MCS_PGAMMACTL                   0xfa
  33
  34#define NUM_GAMMA_LEVELS             11
  35#define GAMMA_TABLE_COUNT           23
  36
  37#define DATA_MASK                                       0x100
  38
  39#define MAX_BRIGHTNESS              (NUM_GAMMA_LEVELS - 1)
  40
  41/* array of gamma tables for gamma value 2.2 */
  42static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
  43        { MCS_PGAMMACTL, 0x00,
  44          0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
  45          0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
  46          0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51 },
  47        { MCS_PGAMMACTL, 0x00,
  48          0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
  49          0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
  50          0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82 },
  51        { MCS_PGAMMACTL, 0x00,
  52          0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
  53          0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
  54          0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D },
  55        { MCS_PGAMMACTL, 0x00,
  56          0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
  57          0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
  58          0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E },
  59        { MCS_PGAMMACTL, 0x00,
  60          0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
  61          0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
  62          0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB },
  63        { MCS_PGAMMACTL, 0x00,
  64          0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
  65          0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
  66          0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA },
  67        { MCS_PGAMMACTL, 0x00,
  68          0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
  69          0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
  70          0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2 },
  71        { MCS_PGAMMACTL, 0x00,
  72          0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
  73          0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
  74          0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA },
  75        { MCS_PGAMMACTL, 0x00,
  76          0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
  77          0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
  78          0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1 },
  79        { MCS_PGAMMACTL, 0x00,
  80          0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
  81          0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
  82          0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6 },
  83        { MCS_PGAMMACTL, 0x00,
  84          0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
  85          0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
  86          0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb },
  87};
  88
  89struct s6e63m0 {
  90        struct device *dev;
  91        struct drm_panel panel;
  92        struct backlight_device *bl_dev;
  93
  94        struct regulator_bulk_data supplies[2];
  95        struct gpio_desc *reset_gpio;
  96
  97        bool prepared;
  98        bool enabled;
  99
 100        /*
 101         * This field is tested by functions directly accessing bus before
 102         * transfer, transfer is skipped if it is set. In case of transfer
 103         * failure or unexpected response the field is set to error value.
 104         * Such construct allows to eliminate many checks in higher level
 105         * functions.
 106         */
 107        int error;
 108};
 109
 110static const struct drm_display_mode default_mode = {
 111        .clock          = 25628,
 112        .hdisplay       = 480,
 113        .hsync_start    = 480 + 16,
 114        .hsync_end      = 480 + 16 + 2,
 115        .htotal         = 480 + 16 + 2 + 16,
 116        .vdisplay       = 800,
 117        .vsync_start    = 800 + 28,
 118        .vsync_end      = 800 + 28 + 2,
 119        .vtotal         = 800 + 28 + 2 + 1,
 120        .vrefresh       = 60,
 121        .width_mm       = 53,
 122        .height_mm      = 89,
 123        .flags          = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
 124};
 125
 126static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
 127{
 128        return container_of(panel, struct s6e63m0, panel);
 129}
 130
 131static int s6e63m0_clear_error(struct s6e63m0 *ctx)
 132{
 133        int ret = ctx->error;
 134
 135        ctx->error = 0;
 136        return ret;
 137}
 138
 139static int s6e63m0_spi_write_word(struct s6e63m0 *ctx, u16 data)
 140{
 141        struct spi_device *spi = to_spi_device(ctx->dev);
 142        struct spi_transfer xfer = {
 143                .len    = 2,
 144                .tx_buf = &data,
 145        };
 146        struct spi_message msg;
 147
 148        spi_message_init(&msg);
 149        spi_message_add_tail(&xfer, &msg);
 150
 151        return spi_sync(spi, &msg);
 152}
 153
 154static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
 155{
 156        int ret = 0;
 157
 158        if (ctx->error < 0 || len == 0)
 159                return;
 160
 161        DRM_DEV_DEBUG(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);
 162        ret = s6e63m0_spi_write_word(ctx, *data);
 163
 164        while (!ret && --len) {
 165                ++data;
 166                ret = s6e63m0_spi_write_word(ctx, *data | DATA_MASK);
 167        }
 168
 169        if (ret) {
 170                DRM_DEV_ERROR(ctx->dev, "error %d writing dcs seq: %*ph\n", ret,
 171                              (int)len, data);
 172                ctx->error = ret;
 173        }
 174
 175        usleep_range(300, 310);
 176}
 177
 178#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
 179        ({ \
 180                static const u8 d[] = { seq }; \
 181                s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
 182        })
 183
 184static void s6e63m0_init(struct s6e63m0 *ctx)
 185{
 186        s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
 187                                     0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
 188                                     0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
 189
 190        s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
 191                                     0x02, 0x03, 0x1c, 0x10, 0x10);
 192        s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
 193                                     0x03, 0x00, 0x00);
 194
 195        s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
 196                                     0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
 197                                     0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
 198                                     0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
 199                                     0xd6);
 200        s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
 201                                     0x01);
 202
 203        s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
 204                                     0x00, 0x8c, 0x07);
 205        s6e63m0_dcs_write_seq_static(ctx, 0xb3,
 206                                     0xc);
 207
 208        s6e63m0_dcs_write_seq_static(ctx, 0xb5,
 209                                     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
 210                                     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
 211                                     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
 212                                     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
 213                                     0x21, 0x20, 0x1e, 0x1e);
 214
 215        s6e63m0_dcs_write_seq_static(ctx, 0xb6,
 216                                     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
 217                                     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
 218                                     0x66, 0x66);
 219
 220        s6e63m0_dcs_write_seq_static(ctx, 0xb7,
 221                                     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
 222                                     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
 223                                     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
 224                                     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
 225                                     0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
 226                                     0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
 227                                     0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
 228
 229        s6e63m0_dcs_write_seq_static(ctx, 0xb9,
 230                                     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
 231                                     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
 232                                     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
 233                                     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
 234                                     0x21, 0x20, 0x1e, 0x1e);
 235
 236        s6e63m0_dcs_write_seq_static(ctx, 0xba,
 237                                     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
 238                                     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
 239                                     0x66, 0x66);
 240
 241        s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
 242                                     0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
 243                                     0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
 244                                     0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
 245                                     0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
 246
 247        s6e63m0_dcs_write_seq_static(ctx, 0xb2,
 248                                     0x10, 0x10, 0x0b, 0x05);
 249
 250        s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
 251                                     0x01);
 252
 253        s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
 254                                     0x0b);
 255
 256        s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
 257}
 258
 259static int s6e63m0_power_on(struct s6e63m0 *ctx)
 260{
 261        int ret;
 262
 263        ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
 264        if (ret < 0)
 265                return ret;
 266
 267        msleep(25);
 268
 269        gpiod_set_value(ctx->reset_gpio, 0);
 270        msleep(120);
 271
 272        return 0;
 273}
 274
 275static int s6e63m0_power_off(struct s6e63m0 *ctx)
 276{
 277        int ret;
 278
 279        gpiod_set_value(ctx->reset_gpio, 1);
 280        msleep(120);
 281
 282        ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
 283        if (ret < 0)
 284                return ret;
 285
 286        return 0;
 287}
 288
 289static int s6e63m0_disable(struct drm_panel *panel)
 290{
 291        struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
 292
 293        if (!ctx->enabled)
 294                return 0;
 295
 296        backlight_disable(ctx->bl_dev);
 297
 298        s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
 299        msleep(200);
 300
 301        ctx->enabled = false;
 302
 303        return 0;
 304}
 305
 306static int s6e63m0_unprepare(struct drm_panel *panel)
 307{
 308        struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
 309        int ret;
 310
 311        if (!ctx->prepared)
 312                return 0;
 313
 314        s6e63m0_clear_error(ctx);
 315
 316        ret = s6e63m0_power_off(ctx);
 317        if (ret < 0)
 318                return ret;
 319
 320        ctx->prepared = false;
 321
 322        return 0;
 323}
 324
 325static int s6e63m0_prepare(struct drm_panel *panel)
 326{
 327        struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
 328        int ret;
 329
 330        if (ctx->prepared)
 331                return 0;
 332
 333        ret = s6e63m0_power_on(ctx);
 334        if (ret < 0)
 335                return ret;
 336
 337        s6e63m0_init(ctx);
 338
 339        ret = s6e63m0_clear_error(ctx);
 340
 341        if (ret < 0)
 342                s6e63m0_unprepare(panel);
 343
 344        ctx->prepared = true;
 345
 346        return ret;
 347}
 348
 349static int s6e63m0_enable(struct drm_panel *panel)
 350{
 351        struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
 352
 353        if (ctx->enabled)
 354                return 0;
 355
 356        s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
 357
 358        backlight_enable(ctx->bl_dev);
 359
 360        ctx->enabled = true;
 361
 362        return 0;
 363}
 364
 365static int s6e63m0_get_modes(struct drm_panel *panel)
 366{
 367        struct drm_connector *connector = panel->connector;
 368        struct drm_display_mode *mode;
 369
 370        mode = drm_mode_duplicate(panel->drm, &default_mode);
 371        if (!mode) {
 372                DRM_ERROR("failed to add mode %ux%ux@%u\n",
 373                          default_mode.hdisplay, default_mode.vdisplay,
 374                          default_mode.vrefresh);
 375                return -ENOMEM;
 376        }
 377
 378        drm_mode_set_name(mode);
 379
 380        mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
 381        drm_mode_probed_add(connector, mode);
 382
 383        return 1;
 384}
 385
 386static const struct drm_panel_funcs s6e63m0_drm_funcs = {
 387        .disable        = s6e63m0_disable,
 388        .unprepare      = s6e63m0_unprepare,
 389        .prepare        = s6e63m0_prepare,
 390        .enable         = s6e63m0_enable,
 391        .get_modes      = s6e63m0_get_modes,
 392};
 393
 394static int s6e63m0_set_brightness(struct backlight_device *bd)
 395{
 396        struct s6e63m0 *ctx = bl_get_data(bd);
 397
 398        int brightness = bd->props.brightness;
 399
 400        /* disable and set new gamma */
 401        s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
 402                          ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
 403
 404        /* update gamma table. */
 405        s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
 406
 407        return s6e63m0_clear_error(ctx);
 408}
 409
 410static const struct backlight_ops s6e63m0_backlight_ops = {
 411        .update_status  = s6e63m0_set_brightness,
 412};
 413
 414static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
 415{
 416        struct backlight_properties props = {
 417                .type           = BACKLIGHT_RAW,
 418                .brightness     = MAX_BRIGHTNESS,
 419                .max_brightness = MAX_BRIGHTNESS
 420        };
 421        struct device *dev = ctx->dev;
 422        int ret = 0;
 423
 424        ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
 425                                                     &s6e63m0_backlight_ops,
 426                                                     &props);
 427        if (IS_ERR(ctx->bl_dev)) {
 428                ret = PTR_ERR(ctx->bl_dev);
 429                DRM_DEV_ERROR(dev, "error registering backlight device (%d)\n",
 430                              ret);
 431        }
 432
 433        return ret;
 434}
 435
 436static int s6e63m0_probe(struct spi_device *spi)
 437{
 438        struct device *dev = &spi->dev;
 439        struct s6e63m0 *ctx;
 440        int ret;
 441
 442        ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
 443        if (!ctx)
 444                return -ENOMEM;
 445
 446        spi_set_drvdata(spi, ctx);
 447
 448        ctx->dev = dev;
 449        ctx->enabled = false;
 450        ctx->prepared = false;
 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                DRM_DEV_ERROR(dev, "failed to get regulators: %d\n", ret);
 458                return ret;
 459        }
 460
 461        ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
 462        if (IS_ERR(ctx->reset_gpio)) {
 463                DRM_DEV_ERROR(dev, "cannot get reset-gpios %ld\n",
 464                              PTR_ERR(ctx->reset_gpio));
 465                return PTR_ERR(ctx->reset_gpio);
 466        }
 467
 468        spi->bits_per_word = 9;
 469        spi->mode = SPI_MODE_3;
 470        ret = spi_setup(spi);
 471        if (ret < 0) {
 472                DRM_DEV_ERROR(dev, "spi setup failed.\n");
 473                return ret;
 474        }
 475
 476        drm_panel_init(&ctx->panel);
 477        ctx->panel.dev = dev;
 478        ctx->panel.funcs = &s6e63m0_drm_funcs;
 479
 480        ret = s6e63m0_backlight_register(ctx);
 481        if (ret < 0)
 482                return ret;
 483
 484        return drm_panel_add(&ctx->panel);
 485}
 486
 487static int s6e63m0_remove(struct spi_device *spi)
 488{
 489        struct s6e63m0 *ctx = spi_get_drvdata(spi);
 490
 491        drm_panel_remove(&ctx->panel);
 492
 493        return 0;
 494}
 495
 496static const struct of_device_id s6e63m0_of_match[] = {
 497        { .compatible = "samsung,s6e63m0" },
 498        { /* sentinel */ }
 499};
 500MODULE_DEVICE_TABLE(of, s6e63m0_of_match);
 501
 502static struct spi_driver s6e63m0_driver = {
 503        .probe                  = s6e63m0_probe,
 504        .remove                 = s6e63m0_remove,
 505        .driver                 = {
 506                .name           = "panel-samsung-s6e63m0",
 507                .of_match_table = s6e63m0_of_match,
 508        },
 509};
 510module_spi_driver(s6e63m0_driver);
 511
 512MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
 513MODULE_DESCRIPTION("s6e63m0 LCD Driver");
 514MODULE_LICENSE("GPL v2");
 515