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>
13 #include <drm/drm_print.h>
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>
22 #include <video/mipi_display.h>
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
34 #define NUM_GAMMA_LEVELS 11
35 #define GAMMA_TABLE_COUNT 23
37 #define DATA_MASK 0x100
39 #define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1)
41 /* array of gamma tables for gamma value 2.2 */
42 static 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 },
91 struct drm_panel panel
;
92 struct backlight_device
*bl_dev
;
94 struct regulator_bulk_data supplies
[2];
95 struct gpio_desc
*reset_gpio
;
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
110 static const struct drm_display_mode default_mode
= {
113 .hsync_start
= 480 + 16,
114 .hsync_end
= 480 + 16 + 2,
115 .htotal
= 480 + 16 + 2 + 16,
117 .vsync_start
= 800 + 28,
118 .vsync_end
= 800 + 28 + 2,
119 .vtotal
= 800 + 28 + 2 + 1,
123 .flags
= DRM_MODE_FLAG_NVSYNC
| DRM_MODE_FLAG_NHSYNC
,
126 static inline struct s6e63m0
*panel_to_s6e63m0(struct drm_panel
*panel
)
128 return container_of(panel
, struct s6e63m0
, panel
);
131 static int s6e63m0_clear_error(struct s6e63m0
*ctx
)
133 int ret
= ctx
->error
;
139 static int s6e63m0_spi_write_word(struct s6e63m0
*ctx
, u16 data
)
141 struct spi_device
*spi
= to_spi_device(ctx
->dev
);
142 struct spi_transfer xfer
= {
146 struct spi_message msg
;
148 spi_message_init(&msg
);
149 spi_message_add_tail(&xfer
, &msg
);
151 return spi_sync(spi
, &msg
);
154 static void s6e63m0_dcs_write(struct s6e63m0
*ctx
, const u8
*data
, size_t len
)
158 if (ctx
->error
< 0 || len
== 0)
161 DRM_DEV_DEBUG(ctx
->dev
, "writing dcs seq: %*ph\n", (int)len
, data
);
162 ret
= s6e63m0_spi_write_word(ctx
, *data
);
164 while (!ret
&& --len
) {
166 ret
= s6e63m0_spi_write_word(ctx
, *data
| DATA_MASK
);
170 DRM_DEV_ERROR(ctx
->dev
, "error %d writing dcs seq: %*ph\n", ret
,
175 usleep_range(300, 310);
178 #define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
180 static const u8 d[] = { seq }; \
181 s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
184 static void s6e63m0_init(struct s6e63m0
*ctx
)
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);
190 s6e63m0_dcs_write_seq_static(ctx
, MCS_DISCTL
,
191 0x02, 0x03, 0x1c, 0x10, 0x10);
192 s6e63m0_dcs_write_seq_static(ctx
, MCS_IFCTL
,
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,
200 s6e63m0_dcs_write_seq_static(ctx
, MCS_PGAMMACTL
,
203 s6e63m0_dcs_write_seq_static(ctx
, MCS_SRCCTL
,
205 s6e63m0_dcs_write_seq_static(ctx
, 0xb3,
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);
215 s6e63m0_dcs_write_seq_static(ctx
, 0xb6,
216 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
217 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
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);
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);
236 s6e63m0_dcs_write_seq_static(ctx
, 0xba,
237 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
238 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
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);
247 s6e63m0_dcs_write_seq_static(ctx
, 0xb2,
248 0x10, 0x10, 0x0b, 0x05);
250 s6e63m0_dcs_write_seq_static(ctx
, MCS_MIECTL1
,
253 s6e63m0_dcs_write_seq_static(ctx
, MCS_ELVSS_ON
,
256 s6e63m0_dcs_write_seq_static(ctx
, MIPI_DCS_EXIT_SLEEP_MODE
);
259 static int s6e63m0_power_on(struct s6e63m0
*ctx
)
263 ret
= regulator_bulk_enable(ARRAY_SIZE(ctx
->supplies
), ctx
->supplies
);
269 gpiod_set_value(ctx
->reset_gpio
, 0);
275 static int s6e63m0_power_off(struct s6e63m0
*ctx
)
279 gpiod_set_value(ctx
->reset_gpio
, 1);
282 ret
= regulator_bulk_disable(ARRAY_SIZE(ctx
->supplies
), ctx
->supplies
);
289 static int s6e63m0_disable(struct drm_panel
*panel
)
291 struct s6e63m0
*ctx
= panel_to_s6e63m0(panel
);
296 backlight_disable(ctx
->bl_dev
);
298 s6e63m0_dcs_write_seq_static(ctx
, MIPI_DCS_ENTER_SLEEP_MODE
);
301 ctx
->enabled
= false;
306 static int s6e63m0_unprepare(struct drm_panel
*panel
)
308 struct s6e63m0
*ctx
= panel_to_s6e63m0(panel
);
314 s6e63m0_clear_error(ctx
);
316 ret
= s6e63m0_power_off(ctx
);
320 ctx
->prepared
= false;
325 static int s6e63m0_prepare(struct drm_panel
*panel
)
327 struct s6e63m0
*ctx
= panel_to_s6e63m0(panel
);
333 ret
= s6e63m0_power_on(ctx
);
339 ret
= s6e63m0_clear_error(ctx
);
342 s6e63m0_unprepare(panel
);
344 ctx
->prepared
= true;
349 static int s6e63m0_enable(struct drm_panel
*panel
)
351 struct s6e63m0
*ctx
= panel_to_s6e63m0(panel
);
356 s6e63m0_dcs_write_seq_static(ctx
, MIPI_DCS_SET_DISPLAY_ON
);
358 backlight_enable(ctx
->bl_dev
);
365 static int s6e63m0_get_modes(struct drm_panel
*panel
,
366 struct drm_connector
*connector
)
368 struct drm_display_mode
*mode
;
370 mode
= drm_mode_duplicate(connector
->dev
, &default_mode
);
372 DRM_ERROR("failed to add mode %ux%ux@%u\n",
373 default_mode
.hdisplay
, default_mode
.vdisplay
,
374 default_mode
.vrefresh
);
378 drm_mode_set_name(mode
);
380 mode
->type
= DRM_MODE_TYPE_DRIVER
| DRM_MODE_TYPE_PREFERRED
;
381 drm_mode_probed_add(connector
, mode
);
386 static 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
,
394 static int s6e63m0_set_brightness(struct backlight_device
*bd
)
396 struct s6e63m0
*ctx
= bl_get_data(bd
);
398 int brightness
= bd
->props
.brightness
;
400 /* disable and set new gamma */
401 s6e63m0_dcs_write(ctx
, s6e63m0_gamma_22
[brightness
],
402 ARRAY_SIZE(s6e63m0_gamma_22
[brightness
]));
404 /* update gamma table. */
405 s6e63m0_dcs_write_seq_static(ctx
, MCS_PGAMMACTL
, 0x01);
407 return s6e63m0_clear_error(ctx
);
410 static const struct backlight_ops s6e63m0_backlight_ops
= {
411 .update_status
= s6e63m0_set_brightness
,
414 static int s6e63m0_backlight_register(struct s6e63m0
*ctx
)
416 struct backlight_properties props
= {
417 .type
= BACKLIGHT_RAW
,
418 .brightness
= MAX_BRIGHTNESS
,
419 .max_brightness
= MAX_BRIGHTNESS
421 struct device
*dev
= ctx
->dev
;
424 ctx
->bl_dev
= devm_backlight_device_register(dev
, "panel", dev
, ctx
,
425 &s6e63m0_backlight_ops
,
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",
436 static int s6e63m0_probe(struct spi_device
*spi
)
438 struct device
*dev
= &spi
->dev
;
442 ctx
= devm_kzalloc(dev
, sizeof(struct s6e63m0
), GFP_KERNEL
);
446 spi_set_drvdata(spi
, ctx
);
449 ctx
->enabled
= false;
450 ctx
->prepared
= false;
452 ctx
->supplies
[0].supply
= "vdd3";
453 ctx
->supplies
[1].supply
= "vci";
454 ret
= devm_regulator_bulk_get(dev
, ARRAY_SIZE(ctx
->supplies
),
457 DRM_DEV_ERROR(dev
, "failed to get regulators: %d\n", ret
);
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
);
468 spi
->bits_per_word
= 9;
469 spi
->mode
= SPI_MODE_3
;
470 ret
= spi_setup(spi
);
472 DRM_DEV_ERROR(dev
, "spi setup failed.\n");
476 drm_panel_init(&ctx
->panel
, dev
, &s6e63m0_drm_funcs
,
477 DRM_MODE_CONNECTOR_DPI
);
479 ret
= s6e63m0_backlight_register(ctx
);
483 return drm_panel_add(&ctx
->panel
);
486 static int s6e63m0_remove(struct spi_device
*spi
)
488 struct s6e63m0
*ctx
= spi_get_drvdata(spi
);
490 drm_panel_remove(&ctx
->panel
);
495 static const struct of_device_id s6e63m0_of_match
[] = {
496 { .compatible
= "samsung,s6e63m0" },
499 MODULE_DEVICE_TABLE(of
, s6e63m0_of_match
);
501 static struct spi_driver s6e63m0_driver
= {
502 .probe
= s6e63m0_probe
,
503 .remove
= s6e63m0_remove
,
505 .name
= "panel-samsung-s6e63m0",
506 .of_match_table
= s6e63m0_of_match
,
509 module_spi_driver(s6e63m0_driver
);
511 MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
512 MODULE_DESCRIPTION("s6e63m0 LCD Driver");
513 MODULE_LICENSE("GPL v2");