Loading...
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");
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
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>
19#include <linux/media-bus-format.h>
20
21#include <video/mipi_display.h>
22
23#include "panel-samsung-s6e63m0.h"
24
25/* Manufacturer Command Set */
26#define MCS_ELVSS_ON 0xb1
27#define MCS_TEMP_SWIRE 0xb2
28#define MCS_PENTILE_1 0xb3
29#define MCS_PENTILE_2 0xb4
30#define MCS_GAMMA_DELTA_Y_RED 0xb5
31#define MCS_GAMMA_DELTA_X_RED 0xb6
32#define MCS_GAMMA_DELTA_Y_GREEN 0xb7
33#define MCS_GAMMA_DELTA_X_GREEN 0xb8
34#define MCS_GAMMA_DELTA_Y_BLUE 0xb9
35#define MCS_GAMMA_DELTA_X_BLUE 0xba
36#define MCS_MIECTL1 0xc0
37#define MCS_BCMODE 0xc1
38#define MCS_ERROR_CHECK 0xd5
39#define MCS_READ_ID1 0xda
40#define MCS_READ_ID2 0xdb
41#define MCS_READ_ID3 0xdc
42#define MCS_LEVEL_2_KEY 0xf0
43#define MCS_MTP_KEY 0xf1
44#define MCS_DISCTL 0xf2
45#define MCS_SRCCTL 0xf6
46#define MCS_IFCTL 0xf7
47#define MCS_PANELCTL 0xf8
48#define MCS_PGAMMACTL 0xfa
49
50#define S6E63M0_LCD_ID_VALUE_M2 0xA4
51#define S6E63M0_LCD_ID_VALUE_SM2 0xB4
52#define S6E63M0_LCD_ID_VALUE_SM2_1 0xB6
53
54#define NUM_GAMMA_LEVELS 28
55#define GAMMA_TABLE_COUNT 23
56
57#define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1)
58
59/* array of gamma tables for gamma value 2.2 */
60static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
61 /* 30 cd */
62 { MCS_PGAMMACTL, 0x02,
63 0x18, 0x08, 0x24, 0xA1, 0x51, 0x7B, 0xCE,
64 0xCB, 0xC2, 0xC7, 0xCB, 0xBC, 0xDA, 0xDD,
65 0xD3, 0x00, 0x53, 0x00, 0x52, 0x00, 0x6F, },
66 /* 40 cd */
67 { MCS_PGAMMACTL, 0x02,
68 0x18, 0x08, 0x24, 0x97, 0x58, 0x71, 0xCC,
69 0xCB, 0xC0, 0xC5, 0xC9, 0xBA, 0xD9, 0xDC,
70 0xD1, 0x00, 0x5B, 0x00, 0x5A, 0x00, 0x7A, },
71 /* 50 cd */
72 { MCS_PGAMMACTL, 0x02,
73 0x18, 0x08, 0x24, 0x96, 0x58, 0x72, 0xCB,
74 0xCA, 0xBF, 0xC6, 0xC9, 0xBA, 0xD6, 0xD9,
75 0xCD, 0x00, 0x61, 0x00, 0x61, 0x00, 0x83, },
76 /* 60 cd */
77 { MCS_PGAMMACTL, 0x02,
78 0x18, 0x08, 0x24, 0x91, 0x5E, 0x6E, 0xC9,
79 0xC9, 0xBD, 0xC4, 0xC9, 0xB8, 0xD3, 0xD7,
80 0xCA, 0x00, 0x69, 0x00, 0x67, 0x00, 0x8D, },
81 /* 70 cd */
82 { MCS_PGAMMACTL, 0x02,
83 0x18, 0x08, 0x24, 0x8E, 0x62, 0x6B, 0xC7,
84 0xC9, 0xBB, 0xC3, 0xC7, 0xB7, 0xD3, 0xD7,
85 0xCA, 0x00, 0x6E, 0x00, 0x6C, 0x00, 0x94, },
86 /* 80 cd */
87 { MCS_PGAMMACTL, 0x02,
88 0x18, 0x08, 0x24, 0x89, 0x68, 0x65, 0xC9,
89 0xC9, 0xBC, 0xC1, 0xC5, 0xB6, 0xD2, 0xD5,
90 0xC9, 0x00, 0x73, 0x00, 0x72, 0x00, 0x9A, },
91 /* 90 cd */
92 { MCS_PGAMMACTL, 0x02,
93 0x18, 0x08, 0x24, 0x89, 0x69, 0x64, 0xC7,
94 0xC8, 0xBB, 0xC0, 0xC5, 0xB4, 0xD2, 0xD5,
95 0xC9, 0x00, 0x77, 0x00, 0x76, 0x00, 0xA0, },
96 /* 100 cd */
97 { MCS_PGAMMACTL, 0x02,
98 0x18, 0x08, 0x24, 0x86, 0x69, 0x60, 0xC6,
99 0xC8, 0xBA, 0xBF, 0xC4, 0xB4, 0xD0, 0xD4,
100 0xC6, 0x00, 0x7C, 0x00, 0x7A, 0x00, 0xA7, },
101 /* 110 cd */
102 { MCS_PGAMMACTL, 0x02,
103 0x18, 0x08, 0x24, 0x86, 0x6A, 0x60, 0xC5,
104 0xC7, 0xBA, 0xBD, 0xC3, 0xB2, 0xD0, 0xD4,
105 0xC5, 0x00, 0x80, 0x00, 0x7E, 0x00, 0xAD, },
106 /* 120 cd */
107 { MCS_PGAMMACTL, 0x02,
108 0x18, 0x08, 0x24, 0x82, 0x6B, 0x5E, 0xC4,
109 0xC8, 0xB9, 0xBD, 0xC2, 0xB1, 0xCE, 0xD2,
110 0xC4, 0x00, 0x85, 0x00, 0x82, 0x00, 0xB3, },
111 /* 130 cd */
112 { MCS_PGAMMACTL, 0x02,
113 0x18, 0x08, 0x24, 0x8C, 0x6C, 0x60, 0xC3,
114 0xC7, 0xB9, 0xBC, 0xC1, 0xAF, 0xCE, 0xD2,
115 0xC3, 0x00, 0x88, 0x00, 0x86, 0x00, 0xB8, },
116 /* 140 cd */
117 { MCS_PGAMMACTL, 0x02,
118 0x18, 0x08, 0x24, 0x80, 0x6C, 0x5F, 0xC1,
119 0xC6, 0xB7, 0xBC, 0xC1, 0xAE, 0xCD, 0xD0,
120 0xC2, 0x00, 0x8C, 0x00, 0x8A, 0x00, 0xBE, },
121 /* 150 cd */
122 { MCS_PGAMMACTL, 0x02,
123 0x18, 0x08, 0x24, 0x80, 0x6E, 0x5F, 0xC1,
124 0xC6, 0xB6, 0xBC, 0xC0, 0xAE, 0xCC, 0xD0,
125 0xC2, 0x00, 0x8F, 0x00, 0x8D, 0x00, 0xC2, },
126 /* 160 cd */
127 { MCS_PGAMMACTL, 0x02,
128 0x18, 0x08, 0x24, 0x7F, 0x6E, 0x5F, 0xC0,
129 0xC6, 0xB5, 0xBA, 0xBF, 0xAD, 0xCB, 0xCF,
130 0xC0, 0x00, 0x94, 0x00, 0x91, 0x00, 0xC8, },
131 /* 170 cd */
132 { MCS_PGAMMACTL, 0x02,
133 0x18, 0x08, 0x24, 0x7C, 0x6D, 0x5C, 0xC0,
134 0xC6, 0xB4, 0xBB, 0xBE, 0xAD, 0xCA, 0xCF,
135 0xC0, 0x00, 0x96, 0x00, 0x94, 0x00, 0xCC, },
136 /* 180 cd */
137 { MCS_PGAMMACTL, 0x02,
138 0x18, 0x08, 0x24, 0x7B, 0x6D, 0x5B, 0xC0,
139 0xC5, 0xB3, 0xBA, 0xBE, 0xAD, 0xCA, 0xCE,
140 0xBF, 0x00, 0x99, 0x00, 0x97, 0x00, 0xD0, },
141 /* 190 cd */
142 { MCS_PGAMMACTL, 0x02,
143 0x18, 0x08, 0x24, 0x7A, 0x6D, 0x59, 0xC1,
144 0xC5, 0xB4, 0xB8, 0xBD, 0xAC, 0xC9, 0xCE,
145 0xBE, 0x00, 0x9D, 0x00, 0x9A, 0x00, 0xD5, },
146 /* 200 cd */
147 { MCS_PGAMMACTL, 0x02,
148 0x18, 0x08, 0x24, 0x79, 0x6D, 0x58, 0xC1,
149 0xC4, 0xB4, 0xB6, 0xBD, 0xAA, 0xCA, 0xCD,
150 0xBE, 0x00, 0x9F, 0x00, 0x9D, 0x00, 0xD9, },
151 /* 210 cd */
152 { MCS_PGAMMACTL, 0x02,
153 0x18, 0x08, 0x24, 0x79, 0x6D, 0x57, 0xC0,
154 0xC4, 0xB4, 0xB7, 0xBD, 0xAA, 0xC8, 0xCC,
155 0xBD, 0x00, 0xA2, 0x00, 0xA0, 0x00, 0xDD, },
156 /* 220 cd */
157 { MCS_PGAMMACTL, 0x02,
158 0x18, 0x08, 0x24, 0x78, 0x6F, 0x58, 0xBF,
159 0xC4, 0xB3, 0xB5, 0xBB, 0xA9, 0xC8, 0xCC,
160 0xBC, 0x00, 0xA6, 0x00, 0xA3, 0x00, 0xE2, },
161 /* 230 cd */
162 { MCS_PGAMMACTL, 0x02,
163 0x18, 0x08, 0x24, 0x75, 0x6F, 0x56, 0xBF,
164 0xC3, 0xB2, 0xB6, 0xBB, 0xA8, 0xC7, 0xCB,
165 0xBC, 0x00, 0xA8, 0x00, 0xA6, 0x00, 0xE6, },
166 /* 240 cd */
167 { MCS_PGAMMACTL, 0x02,
168 0x18, 0x08, 0x24, 0x76, 0x6F, 0x56, 0xC0,
169 0xC3, 0xB2, 0xB5, 0xBA, 0xA8, 0xC6, 0xCB,
170 0xBB, 0x00, 0xAA, 0x00, 0xA8, 0x00, 0xE9, },
171 /* 250 cd */
172 { MCS_PGAMMACTL, 0x02,
173 0x18, 0x08, 0x24, 0x74, 0x6D, 0x54, 0xBF,
174 0xC3, 0xB2, 0xB4, 0xBA, 0xA7, 0xC6, 0xCA,
175 0xBA, 0x00, 0xAD, 0x00, 0xAB, 0x00, 0xED, },
176 /* 260 cd */
177 { MCS_PGAMMACTL, 0x02,
178 0x18, 0x08, 0x24, 0x74, 0x6E, 0x54, 0xBD,
179 0xC2, 0xB0, 0xB5, 0xBA, 0xA7, 0xC5, 0xC9,
180 0xBA, 0x00, 0xB0, 0x00, 0xAE, 0x00, 0xF1, },
181 /* 270 cd */
182 { MCS_PGAMMACTL, 0x02,
183 0x18, 0x08, 0x24, 0x71, 0x6C, 0x50, 0xBD,
184 0xC3, 0xB0, 0xB4, 0xB8, 0xA6, 0xC6, 0xC9,
185 0xBB, 0x00, 0xB2, 0x00, 0xB1, 0x00, 0xF4, },
186 /* 280 cd */
187 { MCS_PGAMMACTL, 0x02,
188 0x18, 0x08, 0x24, 0x6E, 0x6C, 0x4D, 0xBE,
189 0xC3, 0xB1, 0xB3, 0xB8, 0xA5, 0xC6, 0xC8,
190 0xBB, 0x00, 0xB4, 0x00, 0xB3, 0x00, 0xF7, },
191 /* 290 cd */
192 { MCS_PGAMMACTL, 0x02,
193 0x18, 0x08, 0x24, 0x71, 0x70, 0x50, 0xBD,
194 0xC1, 0xB0, 0xB2, 0xB8, 0xA4, 0xC6, 0xC7,
195 0xBB, 0x00, 0xB6, 0x00, 0xB6, 0x00, 0xFA, },
196 /* 300 cd */
197 { MCS_PGAMMACTL, 0x02,
198 0x18, 0x08, 0x24, 0x70, 0x6E, 0x4E, 0xBC,
199 0xC0, 0xAF, 0xB3, 0xB8, 0xA5, 0xC5, 0xC7,
200 0xBB, 0x00, 0xB9, 0x00, 0xB8, 0x00, 0xFC, },
201};
202
203#define NUM_ACL_LEVELS 7
204#define ACL_TABLE_COUNT 28
205
206static u8 const s6e63m0_acl[NUM_ACL_LEVELS][ACL_TABLE_COUNT] = {
207 /* NULL ACL */
208 { MCS_BCMODE,
209 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
210 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
211 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
212 0x00, 0x00, 0x00 },
213 /* 40P ACL */
214 { MCS_BCMODE,
215 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
216 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
217 0x01, 0x06, 0x0C, 0x11, 0x16, 0x1C, 0x21, 0x26,
218 0x2B, 0x31, 0x36 },
219 /* 43P ACL */
220 { MCS_BCMODE,
221 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
222 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
223 0x01, 0x07, 0x0C, 0x12, 0x18, 0x1E, 0x23, 0x29,
224 0x2F, 0x34, 0x3A },
225 /* 45P ACL */
226 { MCS_BCMODE,
227 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
228 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
229 0x01, 0x07, 0x0D, 0x13, 0x19, 0x1F, 0x25, 0x2B,
230 0x31, 0x37, 0x3D },
231 /* 47P ACL */
232 { MCS_BCMODE,
233 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
234 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
235 0x01, 0x07, 0x0E, 0x14, 0x1B, 0x21, 0x27, 0x2E,
236 0x34, 0x3B, 0x41 },
237 /* 48P ACL */
238 { MCS_BCMODE,
239 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
240 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
241 0x01, 0x08, 0x0E, 0x15, 0x1B, 0x22, 0x29, 0x2F,
242 0x36, 0x3C, 0x43 },
243 /* 50P ACL */
244 { MCS_BCMODE,
245 0x4D, 0x96, 0x1D, 0x00, 0x00, 0x01, 0xDF, 0x00,
246 0x00, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
247 0x01, 0x08, 0x0F, 0x16, 0x1D, 0x24, 0x2A, 0x31,
248 0x38, 0x3F, 0x46 },
249};
250
251/* This tells us which ACL level goes with which gamma */
252static u8 const s6e63m0_acl_per_gamma[NUM_GAMMA_LEVELS] = {
253 /* 30 - 60 cd: ACL off/NULL */
254 0, 0, 0, 0,
255 /* 70 - 250 cd: 40P ACL */
256 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
257 /* 260 - 300 cd: 50P ACL */
258 6, 6, 6, 6, 6,
259};
260
261/* The ELVSS backlight regulator has 5 levels */
262#define S6E63M0_ELVSS_LEVELS 5
263
264static u8 const s6e63m0_elvss_offsets[S6E63M0_ELVSS_LEVELS] = {
265 0x00, /* not set */
266 0x0D, /* 30 cd - 100 cd */
267 0x09, /* 110 cd - 160 cd */
268 0x07, /* 170 cd - 200 cd */
269 0x00, /* 210 cd - 300 cd */
270};
271
272/* This tells us which ELVSS level goes with which gamma */
273static u8 const s6e63m0_elvss_per_gamma[NUM_GAMMA_LEVELS] = {
274 /* 30 - 100 cd */
275 1, 1, 1, 1, 1, 1, 1, 1,
276 /* 110 - 160 cd */
277 2, 2, 2, 2, 2, 2,
278 /* 170 - 200 cd */
279 3, 3, 3, 3,
280 /* 210 - 300 cd */
281 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
282};
283
284struct s6e63m0 {
285 struct device *dev;
286 int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val);
287 int (*dcs_write)(struct device *dev, const u8 *data, size_t len);
288 struct drm_panel panel;
289 struct backlight_device *bl_dev;
290 u8 lcd_type;
291 u8 elvss_pulse;
292 bool dsi_mode;
293
294 struct regulator_bulk_data supplies[2];
295 struct gpio_desc *reset_gpio;
296
297 bool prepared;
298 bool enabled;
299
300 /*
301 * This field is tested by functions directly accessing bus before
302 * transfer, transfer is skipped if it is set. In case of transfer
303 * failure or unexpected response the field is set to error value.
304 * Such construct allows to eliminate many checks in higher level
305 * functions.
306 */
307 int error;
308};
309
310static const struct drm_display_mode default_mode = {
311 .clock = 25628,
312 .hdisplay = 480,
313 .hsync_start = 480 + 16,
314 .hsync_end = 480 + 16 + 2,
315 .htotal = 480 + 16 + 2 + 16,
316 .vdisplay = 800,
317 .vsync_start = 800 + 28,
318 .vsync_end = 800 + 28 + 2,
319 .vtotal = 800 + 28 + 2 + 1,
320 .width_mm = 53,
321 .height_mm = 89,
322 .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
323};
324
325static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
326{
327 return container_of(panel, struct s6e63m0, panel);
328}
329
330static int s6e63m0_clear_error(struct s6e63m0 *ctx)
331{
332 int ret = ctx->error;
333
334 ctx->error = 0;
335 return ret;
336}
337
338static void s6e63m0_dcs_read(struct s6e63m0 *ctx, const u8 cmd, u8 *data)
339{
340 if (ctx->error < 0)
341 return;
342
343 ctx->error = ctx->dcs_read(ctx->dev, cmd, data);
344}
345
346static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
347{
348 if (ctx->error < 0 || len == 0)
349 return;
350
351 ctx->error = ctx->dcs_write(ctx->dev, data, len);
352}
353
354#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
355 ({ \
356 static const u8 d[] = { seq }; \
357 s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
358 })
359
360static int s6e63m0_check_lcd_type(struct s6e63m0 *ctx)
361{
362 u8 id1, id2, id3;
363 int ret;
364
365 s6e63m0_dcs_read(ctx, MCS_READ_ID1, &id1);
366 s6e63m0_dcs_read(ctx, MCS_READ_ID2, &id2);
367 s6e63m0_dcs_read(ctx, MCS_READ_ID3, &id3);
368
369 ret = s6e63m0_clear_error(ctx);
370 if (ret) {
371 dev_err(ctx->dev, "error checking LCD type (%d)\n", ret);
372 ctx->lcd_type = 0x00;
373 return ret;
374 }
375
376 dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
377
378 /*
379 * We attempt to detect what panel is mounted on the controller.
380 * The third ID byte represents the desired ELVSS pulse for
381 * some displays.
382 */
383 switch (id2) {
384 case S6E63M0_LCD_ID_VALUE_M2:
385 dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI M2\n");
386 ctx->elvss_pulse = id3;
387 break;
388 case S6E63M0_LCD_ID_VALUE_SM2:
389 case S6E63M0_LCD_ID_VALUE_SM2_1:
390 dev_info(ctx->dev, "detected LCD panel AMS397GE MIPI SM2\n");
391 ctx->elvss_pulse = id3;
392 break;
393 default:
394 dev_info(ctx->dev, "unknown LCD panel type %02x\n", id2);
395 /* Default ELVSS pulse level */
396 ctx->elvss_pulse = 0x16;
397 break;
398 }
399
400 ctx->lcd_type = id2;
401
402 return 0;
403}
404
405static void s6e63m0_init(struct s6e63m0 *ctx)
406{
407 /*
408 * We do not know why there is a difference in the DSI mode.
409 * (No datasheet.)
410 *
411 * In the vendor driver this sequence is called
412 * "SEQ_PANEL_CONDITION_SET" or "DCS_CMD_SEQ_PANEL_COND_SET".
413 */
414 if (ctx->dsi_mode)
415 s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
416 0x01, 0x2c, 0x2c, 0x07, 0x07, 0x5f, 0xb3,
417 0x6d, 0x97, 0x1d, 0x3a, 0x0f, 0x00, 0x00);
418 else
419 s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
420 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
421 0x63, 0x8f, 0x1a, 0x33, 0x0d, 0x00, 0x00);
422
423 s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
424 0x02, 0x03, 0x1c, 0x10, 0x10);
425 s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
426 0x03, 0x00, 0x00);
427
428 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
429 0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
430 0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
431 0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
432 0xd6);
433 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
434 0x01);
435
436 s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
437 0x00, 0x8e, 0x07);
438 s6e63m0_dcs_write_seq_static(ctx, MCS_PENTILE_1, 0x6c);
439
440 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_RED,
441 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
442 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
443 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
444 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
445 0x21, 0x20, 0x1e, 0x1e);
446
447 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_RED,
448 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
449 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
450 0x66, 0x66);
451
452 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_GREEN,
453 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
454 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
455 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
456 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
457 0x21, 0x20, 0x1e, 0x1e);
458
459 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_GREEN,
460 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
461 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
462 0x66, 0x66);
463
464 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_Y_BLUE,
465 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
466 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
467 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
468 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
469 0x21, 0x20, 0x1e, 0x1e);
470
471 s6e63m0_dcs_write_seq_static(ctx, MCS_GAMMA_DELTA_X_BLUE,
472 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
473 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
474 0x66, 0x66);
475
476 s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
477 0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
478 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
479 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
480 0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
481
482 s6e63m0_dcs_write_seq_static(ctx, MCS_TEMP_SWIRE,
483 0x10, 0x10, 0x0b, 0x05);
484
485 s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
486 0x01);
487
488 s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
489 0x0b);
490}
491
492static int s6e63m0_power_on(struct s6e63m0 *ctx)
493{
494 int ret;
495
496 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
497 if (ret < 0)
498 return ret;
499
500 msleep(25);
501
502 /* Be sure to send a reset pulse */
503 gpiod_set_value(ctx->reset_gpio, 1);
504 msleep(5);
505 gpiod_set_value(ctx->reset_gpio, 0);
506 msleep(120);
507
508 return 0;
509}
510
511static int s6e63m0_power_off(struct s6e63m0 *ctx)
512{
513 int ret;
514
515 gpiod_set_value(ctx->reset_gpio, 1);
516 msleep(120);
517
518 ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
519 if (ret < 0)
520 return ret;
521
522 return 0;
523}
524
525static int s6e63m0_disable(struct drm_panel *panel)
526{
527 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
528
529 if (!ctx->enabled)
530 return 0;
531
532 backlight_disable(ctx->bl_dev);
533
534 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
535 msleep(10);
536 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
537 msleep(120);
538
539 ctx->enabled = false;
540
541 return 0;
542}
543
544static int s6e63m0_unprepare(struct drm_panel *panel)
545{
546 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
547 int ret;
548
549 if (!ctx->prepared)
550 return 0;
551
552 s6e63m0_clear_error(ctx);
553
554 ret = s6e63m0_power_off(ctx);
555 if (ret < 0)
556 return ret;
557
558 ctx->prepared = false;
559
560 return 0;
561}
562
563static int s6e63m0_prepare(struct drm_panel *panel)
564{
565 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
566 int ret;
567
568 if (ctx->prepared)
569 return 0;
570
571 ret = s6e63m0_power_on(ctx);
572 if (ret < 0)
573 return ret;
574
575 /* Magic to unlock level 2 control of the display */
576 s6e63m0_dcs_write_seq_static(ctx, MCS_LEVEL_2_KEY, 0x5a, 0x5a);
577 /* Magic to unlock MTP reading */
578 s6e63m0_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0x5a, 0x5a);
579
580 ret = s6e63m0_check_lcd_type(ctx);
581 if (ret < 0)
582 return ret;
583
584 s6e63m0_init(ctx);
585
586 ret = s6e63m0_clear_error(ctx);
587
588 if (ret < 0)
589 s6e63m0_unprepare(panel);
590
591 ctx->prepared = true;
592
593 return ret;
594}
595
596static int s6e63m0_enable(struct drm_panel *panel)
597{
598 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
599
600 if (ctx->enabled)
601 return 0;
602
603 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
604 msleep(120);
605 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
606 msleep(10);
607
608 s6e63m0_dcs_write_seq_static(ctx, MCS_ERROR_CHECK,
609 0xE7, 0x14, 0x60, 0x17, 0x0A, 0x49, 0xC3,
610 0x8F, 0x19, 0x64, 0x91, 0x84, 0x76, 0x20,
611 0x0F, 0x00);
612
613 backlight_enable(ctx->bl_dev);
614
615 ctx->enabled = true;
616
617 return 0;
618}
619
620static int s6e63m0_get_modes(struct drm_panel *panel,
621 struct drm_connector *connector)
622{
623 struct drm_display_mode *mode;
624 static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
625
626 mode = drm_mode_duplicate(connector->dev, &default_mode);
627 if (!mode) {
628 dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
629 default_mode.hdisplay, default_mode.vdisplay,
630 drm_mode_vrefresh(&default_mode));
631 return -ENOMEM;
632 }
633
634 connector->display_info.width_mm = mode->width_mm;
635 connector->display_info.height_mm = mode->height_mm;
636 drm_display_info_set_bus_formats(&connector->display_info,
637 &bus_format, 1);
638 connector->display_info.bus_flags = DRM_BUS_FLAG_DE_LOW |
639 DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
640
641 drm_mode_set_name(mode);
642
643 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
644 drm_mode_probed_add(connector, mode);
645
646 return 1;
647}
648
649static const struct drm_panel_funcs s6e63m0_drm_funcs = {
650 .disable = s6e63m0_disable,
651 .unprepare = s6e63m0_unprepare,
652 .prepare = s6e63m0_prepare,
653 .enable = s6e63m0_enable,
654 .get_modes = s6e63m0_get_modes,
655};
656
657static int s6e63m0_set_brightness(struct backlight_device *bd)
658{
659 struct s6e63m0 *ctx = bl_get_data(bd);
660 int brightness = bd->props.brightness;
661 u8 elvss_val;
662 u8 elvss_cmd_set[5];
663 int i;
664
665 /* Adjust ELVSS to candela level */
666 i = s6e63m0_elvss_per_gamma[brightness];
667 elvss_val = ctx->elvss_pulse + s6e63m0_elvss_offsets[i];
668 if (elvss_val > 0x1f)
669 elvss_val = 0x1f;
670 elvss_cmd_set[0] = MCS_TEMP_SWIRE;
671 elvss_cmd_set[1] = elvss_val;
672 elvss_cmd_set[2] = elvss_val;
673 elvss_cmd_set[3] = elvss_val;
674 elvss_cmd_set[4] = elvss_val;
675 s6e63m0_dcs_write(ctx, elvss_cmd_set, 5);
676
677 /* Update the ACL per gamma value */
678 i = s6e63m0_acl_per_gamma[brightness];
679 s6e63m0_dcs_write(ctx, s6e63m0_acl[i],
680 ARRAY_SIZE(s6e63m0_acl[i]));
681
682 /* Update gamma table */
683 s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
684 ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
685 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x03);
686
687
688 return s6e63m0_clear_error(ctx);
689}
690
691static const struct backlight_ops s6e63m0_backlight_ops = {
692 .update_status = s6e63m0_set_brightness,
693};
694
695static int s6e63m0_backlight_register(struct s6e63m0 *ctx, u32 max_brightness)
696{
697 struct backlight_properties props = {
698 .type = BACKLIGHT_RAW,
699 .brightness = max_brightness,
700 .max_brightness = max_brightness,
701 };
702 struct device *dev = ctx->dev;
703 int ret = 0;
704
705 ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
706 &s6e63m0_backlight_ops,
707 &props);
708 if (IS_ERR(ctx->bl_dev)) {
709 ret = PTR_ERR(ctx->bl_dev);
710 dev_err(dev, "error registering backlight device (%d)\n", ret);
711 }
712
713 return ret;
714}
715
716int s6e63m0_probe(struct device *dev,
717 int (*dcs_read)(struct device *dev, const u8 cmd, u8 *val),
718 int (*dcs_write)(struct device *dev, const u8 *data, size_t len),
719 bool dsi_mode)
720{
721 struct s6e63m0 *ctx;
722 u32 max_brightness;
723 int ret;
724
725 ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
726 if (!ctx)
727 return -ENOMEM;
728
729 ctx->dsi_mode = dsi_mode;
730 ctx->dcs_read = dcs_read;
731 ctx->dcs_write = dcs_write;
732 dev_set_drvdata(dev, ctx);
733
734 ctx->dev = dev;
735 ctx->enabled = false;
736 ctx->prepared = false;
737
738 ret = device_property_read_u32(dev, "max-brightness", &max_brightness);
739 if (ret)
740 max_brightness = MAX_BRIGHTNESS;
741 if (max_brightness > MAX_BRIGHTNESS) {
742 dev_err(dev, "illegal max brightness specified\n");
743 max_brightness = MAX_BRIGHTNESS;
744 }
745
746 ctx->supplies[0].supply = "vdd3";
747 ctx->supplies[1].supply = "vci";
748 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
749 ctx->supplies);
750 if (ret < 0) {
751 dev_err(dev, "failed to get regulators: %d\n", ret);
752 return ret;
753 }
754
755 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
756 if (IS_ERR(ctx->reset_gpio)) {
757 dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio));
758 return PTR_ERR(ctx->reset_gpio);
759 }
760
761 drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
762 dsi_mode ? DRM_MODE_CONNECTOR_DSI :
763 DRM_MODE_CONNECTOR_DPI);
764
765 ret = s6e63m0_backlight_register(ctx, max_brightness);
766 if (ret < 0)
767 return ret;
768
769 drm_panel_add(&ctx->panel);
770
771 return 0;
772}
773EXPORT_SYMBOL_GPL(s6e63m0_probe);
774
775int s6e63m0_remove(struct device *dev)
776{
777 struct s6e63m0 *ctx = dev_get_drvdata(dev);
778
779 drm_panel_remove(&ctx->panel);
780
781 return 0;
782}
783EXPORT_SYMBOL_GPL(s6e63m0_remove);
784
785MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
786MODULE_DESCRIPTION("s6e63m0 LCD Driver");
787MODULE_LICENSE("GPL v2");