1 // SPDX-License-Identifier: GPL-2.0
3 * S6E63M0 AMOLED LCD drm_panel driver.
5 * Copyright (C) 2019 Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
6 * Derived from drivers/gpu/drm/panel-samsung-ld9040.c
8 * Andrzej Hajda <a.hajda@samsung.com>
11 #include <drm/drm_modes.h>
12 #include <drm/drm_panel.h>
14 #include <linux/backlight.h>
15 #include <linux/delay.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/module.h>
18 #include <linux/regulator/consumer.h>
20 #include <video/mipi_display.h>
22 #include "panel-samsung-s6e63m0.h"
24 /* Manufacturer Command Set */
25 #define MCS_ELVSS_ON 0xb1
26 #define MCS_MIECTL1 0xc0
27 #define MCS_BCMODE 0xc1
28 #define MCS_ERROR_CHECK 0xd5
29 #define MCS_READ_ID1 0xda
30 #define MCS_READ_ID2 0xdb
31 #define MCS_READ_ID3 0xdc
32 #define MCS_LEVEL_2_KEY 0xf0
33 #define MCS_MTP_KEY 0xf1
34 #define MCS_DISCTL 0xf2
35 #define MCS_SRCCTL 0xf6
36 #define MCS_IFCTL 0xf7
37 #define MCS_PANELCTL 0xF8
38 #define MCS_PGAMMACTL 0xfa
40 #define S6E63M0_LCD_ID_VALUE_M2 0xA4
41 #define S6E63M0_LCD_ID_VALUE_SM2 0xB4
42 #define S6E63M0_LCD_ID_VALUE_SM2_1 0xB6
44 #define NUM_GAMMA_LEVELS 11
45 #define GAMMA_TABLE_COUNT 23
47 #define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1)
49 /* array of gamma tables for gamma value 2.2 */
50 static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
51 { MCS_PGAMMACTL, 0x00,
52 0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
53 0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
54 0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51 },
55 { MCS_PGAMMACTL, 0x00,
56 0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
57 0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
58 0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82 },
59 { MCS_PGAMMACTL, 0x00,
60 0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
61 0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
62 0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D },
63 { MCS_PGAMMACTL, 0x00,
64 0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
65 0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
66 0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E },
67 { MCS_PGAMMACTL, 0x00,
68 0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
69 0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
70 0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB },
71 { MCS_PGAMMACTL, 0x00,
72 0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
73 0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
74 0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA },
75 { MCS_PGAMMACTL, 0x00,
76 0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
77 0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
78 0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2 },
79 { MCS_PGAMMACTL, 0x00,
80 0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
81 0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
82 0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA },
83 { MCS_PGAMMACTL, 0x00,
84 0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
85 0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
86 0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1 },
87 { MCS_PGAMMACTL, 0x00,
88 0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
89 0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
90 0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6 },
91 { MCS_PGAMMACTL, 0x00,
92 0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
93 0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
94 0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb },
99 int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val);
100 int (*dcs_write)(struct device *dev, const u8 *data, size_t len);
101 struct drm_panel panel;
102 struct backlight_device *bl_dev;
105 struct regulator_bulk_data supplies[2];
106 struct gpio_desc *reset_gpio;
112 * This field is tested by functions directly accessing bus before
113 * transfer, transfer is skipped if it is set. In case of transfer
114 * failure or unexpected response the field is set to error value.
115 * Such construct allows to eliminate many checks in higher level
121 static const struct drm_display_mode default_mode = {
124 .hsync_start = 480 + 16,
125 .hsync_end = 480 + 16 + 2,
126 .htotal = 480 + 16 + 2 + 16,
128 .vsync_start = 800 + 28,
129 .vsync_end = 800 + 28 + 2,
130 .vtotal = 800 + 28 + 2 + 1,
133 .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
136 static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
138 return container_of(panel, struct s6e63m0, panel);
141 static int s6e63m0_clear_error(struct s6e63m0 *ctx)
143 int ret = ctx->error;
149 static void s6e63m0_dcs_read(struct s6e63m0 *ctx, const u8 cmd, u8 *data)
154 ctx->error = ctx->dcs_read(ctx->dev, cmd, data);
157 static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
159 if (ctx->error < 0 || len == 0)
162 ctx->error = ctx->dcs_write(ctx->dev, data, len);
165 #define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
167 static const u8 d[] = { seq }; \
168 s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
171 static int s6e63m0_check_lcd_type(struct s6e63m0 *ctx)
176 s6e63m0_dcs_read(ctx, MCS_READ_ID1, &id1);
177 s6e63m0_dcs_read(ctx, MCS_READ_ID2, &id2);
178 s6e63m0_dcs_read(ctx, MCS_READ_ID3, &id3);
180 ret = s6e63m0_clear_error(ctx);
182 dev_err(ctx->dev, "error checking LCD type (%d)\n", ret);
183 ctx->lcd_type = 0x00;
187 dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
189 /* We attempt to detect what panel is mounted on the controller */
191 case S6E63M0_LCD_ID_VALUE_M2:
192 dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI M2\n");
194 case S6E63M0_LCD_ID_VALUE_SM2:
195 case S6E63M0_LCD_ID_VALUE_SM2_1:
196 dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI SM2\n");
199 dev_info(ctx->dev, "unknown LCD panel type %02x\n", id2);
208 static void s6e63m0_init(struct s6e63m0 *ctx)
210 s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
211 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
212 0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
214 s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
215 0x02, 0x03, 0x1c, 0x10, 0x10);
216 s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
219 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
220 0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
221 0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
222 0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
224 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
227 s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
229 s6e63m0_dcs_write_seq_static(ctx, 0xb3,
232 s6e63m0_dcs_write_seq_static(ctx, 0xb5,
233 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
234 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
235 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
236 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
237 0x21, 0x20, 0x1e, 0x1e);
239 s6e63m0_dcs_write_seq_static(ctx, 0xb6,
240 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
241 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
244 s6e63m0_dcs_write_seq_static(ctx, 0xb7,
245 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
246 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
247 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
248 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
249 0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
250 0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
251 0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
253 s6e63m0_dcs_write_seq_static(ctx, 0xb9,
254 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
255 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
256 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
257 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
258 0x21, 0x20, 0x1e, 0x1e);
260 s6e63m0_dcs_write_seq_static(ctx, 0xba,
261 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
262 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
265 s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
266 0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
267 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
268 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
269 0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
271 s6e63m0_dcs_write_seq_static(ctx, 0xb2,
272 0x10, 0x10, 0x0b, 0x05);
274 s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
277 s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
281 static int s6e63m0_power_on(struct s6e63m0 *ctx)
285 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
291 /* Be sure to send a reset pulse */
292 gpiod_set_value(ctx->reset_gpio, 1);
294 gpiod_set_value(ctx->reset_gpio, 0);
300 static int s6e63m0_power_off(struct s6e63m0 *ctx)
304 gpiod_set_value(ctx->reset_gpio, 1);
307 ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
314 static int s6e63m0_disable(struct drm_panel *panel)
316 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
321 backlight_disable(ctx->bl_dev);
323 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
325 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
328 ctx->enabled = false;
333 static int s6e63m0_unprepare(struct drm_panel *panel)
335 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
341 s6e63m0_clear_error(ctx);
343 ret = s6e63m0_power_off(ctx);
347 ctx->prepared = false;
352 static int s6e63m0_prepare(struct drm_panel *panel)
354 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
360 ret = s6e63m0_power_on(ctx);
364 /* Magic to unlock level 2 control of the display */
365 s6e63m0_dcs_write_seq_static(ctx, MCS_LEVEL_2_KEY, 0x5a, 0x5a);
366 /* Magic to unlock MTP reading */
367 s6e63m0_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0x5a, 0x5a);
369 ret = s6e63m0_check_lcd_type(ctx);
375 ret = s6e63m0_clear_error(ctx);
378 s6e63m0_unprepare(panel);
380 ctx->prepared = true;
385 static int s6e63m0_enable(struct drm_panel *panel)
387 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
392 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
394 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
397 s6e63m0_dcs_write_seq_static(ctx, MCS_ERROR_CHECK,
398 0xE7, 0x14, 0x60, 0x17, 0x0A, 0x49, 0xC3,
399 0x8F, 0x19, 0x64, 0x91, 0x84, 0x76, 0x20,
402 backlight_enable(ctx->bl_dev);
409 static int s6e63m0_get_modes(struct drm_panel *panel,
410 struct drm_connector *connector)
412 struct drm_display_mode *mode;
414 mode = drm_mode_duplicate(connector->dev, &default_mode);
416 dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
417 default_mode.hdisplay, default_mode.vdisplay,
418 drm_mode_vrefresh(&default_mode));
422 drm_mode_set_name(mode);
424 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
425 drm_mode_probed_add(connector, mode);
430 static const struct drm_panel_funcs s6e63m0_drm_funcs = {
431 .disable = s6e63m0_disable,
432 .unprepare = s6e63m0_unprepare,
433 .prepare = s6e63m0_prepare,
434 .enable = s6e63m0_enable,
435 .get_modes = s6e63m0_get_modes,
438 static int s6e63m0_set_brightness(struct backlight_device *bd)
440 struct s6e63m0 *ctx = bl_get_data(bd);
442 int brightness = bd->props.brightness;
444 /* disable and set new gamma */
445 s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
446 ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
448 /* update gamma table. */
449 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
451 return s6e63m0_clear_error(ctx);
454 static const struct backlight_ops s6e63m0_backlight_ops = {
455 .update_status = s6e63m0_set_brightness,
458 static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
460 struct backlight_properties props = {
461 .type = BACKLIGHT_RAW,
462 .brightness = MAX_BRIGHTNESS,
463 .max_brightness = MAX_BRIGHTNESS
465 struct device *dev = ctx->dev;
468 ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
469 &s6e63m0_backlight_ops,
471 if (IS_ERR(ctx->bl_dev)) {
472 ret = PTR_ERR(ctx->bl_dev);
473 dev_err(dev, "error registering backlight device (%d)\n", ret);
479 int s6e63m0_probe(struct device *dev,
480 int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val),
481 int (*dcs_write)(struct device *dev, const u8 *data, size_t len),
487 ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
491 ctx->dcs_read = dcs_read;
492 ctx->dcs_write = dcs_write;
493 dev_set_drvdata(dev, ctx);
496 ctx->enabled = false;
497 ctx->prepared = false;
499 ctx->supplies[0].supply = "vdd3";
500 ctx->supplies[1].supply = "vci";
501 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
504 dev_err(dev, "failed to get regulators: %d\n", ret);
508 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
509 if (IS_ERR(ctx->reset_gpio)) {
510 dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio));
511 return PTR_ERR(ctx->reset_gpio);
514 drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
515 dsi_mode ? DRM_MODE_CONNECTOR_DSI :
516 DRM_MODE_CONNECTOR_DPI);
518 ret = s6e63m0_backlight_register(ctx);
522 drm_panel_add(&ctx->panel);
526 EXPORT_SYMBOL_GPL(s6e63m0_probe);
528 int s6e63m0_remove(struct device *dev)
530 struct s6e63m0 *ctx = dev_get_drvdata(dev);
532 drm_panel_remove(&ctx->panel);
536 EXPORT_SYMBOL_GPL(s6e63m0_remove);
538 MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
539 MODULE_DESCRIPTION("s6e63m0 LCD Driver");
540 MODULE_LICENSE("GPL v2");