Loading...
Note: File does not exist in v4.6.
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 .width_mm = 53,
121 .height_mm = 89,
122 .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
123};
124
125static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
126{
127 return container_of(panel, struct s6e63m0, panel);
128}
129
130static int s6e63m0_clear_error(struct s6e63m0 *ctx)
131{
132 int ret = ctx->error;
133
134 ctx->error = 0;
135 return ret;
136}
137
138static int s6e63m0_spi_write_word(struct s6e63m0 *ctx, u16 data)
139{
140 struct spi_device *spi = to_spi_device(ctx->dev);
141 struct spi_transfer xfer = {
142 .len = 2,
143 .tx_buf = &data,
144 };
145 struct spi_message msg;
146
147 spi_message_init(&msg);
148 spi_message_add_tail(&xfer, &msg);
149
150 return spi_sync(spi, &msg);
151}
152
153static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
154{
155 int ret = 0;
156
157 if (ctx->error < 0 || len == 0)
158 return;
159
160 DRM_DEV_DEBUG(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);
161 ret = s6e63m0_spi_write_word(ctx, *data);
162
163 while (!ret && --len) {
164 ++data;
165 ret = s6e63m0_spi_write_word(ctx, *data | DATA_MASK);
166 }
167
168 if (ret) {
169 DRM_DEV_ERROR(ctx->dev, "error %d writing dcs seq: %*ph\n", ret,
170 (int)len, data);
171 ctx->error = ret;
172 }
173
174 usleep_range(300, 310);
175}
176
177#define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
178 ({ \
179 static const u8 d[] = { seq }; \
180 s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
181 })
182
183static void s6e63m0_init(struct s6e63m0 *ctx)
184{
185 s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
186 0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
187 0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
188
189 s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
190 0x02, 0x03, 0x1c, 0x10, 0x10);
191 s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
192 0x03, 0x00, 0x00);
193
194 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
195 0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
196 0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
197 0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
198 0xd6);
199 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
200 0x01);
201
202 s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
203 0x00, 0x8c, 0x07);
204 s6e63m0_dcs_write_seq_static(ctx, 0xb3,
205 0xc);
206
207 s6e63m0_dcs_write_seq_static(ctx, 0xb5,
208 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
209 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
210 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
211 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
212 0x21, 0x20, 0x1e, 0x1e);
213
214 s6e63m0_dcs_write_seq_static(ctx, 0xb6,
215 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
216 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
217 0x66, 0x66);
218
219 s6e63m0_dcs_write_seq_static(ctx, 0xb7,
220 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
221 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
222 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
223 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
224 0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
225 0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
226 0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
227
228 s6e63m0_dcs_write_seq_static(ctx, 0xb9,
229 0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
230 0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
231 0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
232 0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
233 0x21, 0x20, 0x1e, 0x1e);
234
235 s6e63m0_dcs_write_seq_static(ctx, 0xba,
236 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
237 0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
238 0x66, 0x66);
239
240 s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
241 0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
242 0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
243 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
244 0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
245
246 s6e63m0_dcs_write_seq_static(ctx, 0xb2,
247 0x10, 0x10, 0x0b, 0x05);
248
249 s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
250 0x01);
251
252 s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
253 0x0b);
254
255 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
256}
257
258static int s6e63m0_power_on(struct s6e63m0 *ctx)
259{
260 int ret;
261
262 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
263 if (ret < 0)
264 return ret;
265
266 msleep(25);
267
268 gpiod_set_value(ctx->reset_gpio, 0);
269 msleep(120);
270
271 return 0;
272}
273
274static int s6e63m0_power_off(struct s6e63m0 *ctx)
275{
276 int ret;
277
278 gpiod_set_value(ctx->reset_gpio, 1);
279 msleep(120);
280
281 ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
282 if (ret < 0)
283 return ret;
284
285 return 0;
286}
287
288static int s6e63m0_disable(struct drm_panel *panel)
289{
290 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
291
292 if (!ctx->enabled)
293 return 0;
294
295 backlight_disable(ctx->bl_dev);
296
297 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
298 msleep(200);
299
300 ctx->enabled = false;
301
302 return 0;
303}
304
305static int s6e63m0_unprepare(struct drm_panel *panel)
306{
307 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
308 int ret;
309
310 if (!ctx->prepared)
311 return 0;
312
313 s6e63m0_clear_error(ctx);
314
315 ret = s6e63m0_power_off(ctx);
316 if (ret < 0)
317 return ret;
318
319 ctx->prepared = false;
320
321 return 0;
322}
323
324static int s6e63m0_prepare(struct drm_panel *panel)
325{
326 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
327 int ret;
328
329 if (ctx->prepared)
330 return 0;
331
332 ret = s6e63m0_power_on(ctx);
333 if (ret < 0)
334 return ret;
335
336 s6e63m0_init(ctx);
337
338 ret = s6e63m0_clear_error(ctx);
339
340 if (ret < 0)
341 s6e63m0_unprepare(panel);
342
343 ctx->prepared = true;
344
345 return ret;
346}
347
348static int s6e63m0_enable(struct drm_panel *panel)
349{
350 struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
351
352 if (ctx->enabled)
353 return 0;
354
355 s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
356
357 backlight_enable(ctx->bl_dev);
358
359 ctx->enabled = true;
360
361 return 0;
362}
363
364static int s6e63m0_get_modes(struct drm_panel *panel,
365 struct drm_connector *connector)
366{
367 struct drm_display_mode *mode;
368
369 mode = drm_mode_duplicate(connector->dev, &default_mode);
370 if (!mode) {
371 DRM_ERROR("failed to add mode %ux%ux@%u\n",
372 default_mode.hdisplay, default_mode.vdisplay,
373 drm_mode_vrefresh(&default_mode));
374 return -ENOMEM;
375 }
376
377 drm_mode_set_name(mode);
378
379 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
380 drm_mode_probed_add(connector, mode);
381
382 return 1;
383}
384
385static const struct drm_panel_funcs s6e63m0_drm_funcs = {
386 .disable = s6e63m0_disable,
387 .unprepare = s6e63m0_unprepare,
388 .prepare = s6e63m0_prepare,
389 .enable = s6e63m0_enable,
390 .get_modes = s6e63m0_get_modes,
391};
392
393static int s6e63m0_set_brightness(struct backlight_device *bd)
394{
395 struct s6e63m0 *ctx = bl_get_data(bd);
396
397 int brightness = bd->props.brightness;
398
399 /* disable and set new gamma */
400 s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
401 ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
402
403 /* update gamma table. */
404 s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
405
406 return s6e63m0_clear_error(ctx);
407}
408
409static const struct backlight_ops s6e63m0_backlight_ops = {
410 .update_status = s6e63m0_set_brightness,
411};
412
413static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
414{
415 struct backlight_properties props = {
416 .type = BACKLIGHT_RAW,
417 .brightness = MAX_BRIGHTNESS,
418 .max_brightness = MAX_BRIGHTNESS
419 };
420 struct device *dev = ctx->dev;
421 int ret = 0;
422
423 ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
424 &s6e63m0_backlight_ops,
425 &props);
426 if (IS_ERR(ctx->bl_dev)) {
427 ret = PTR_ERR(ctx->bl_dev);
428 DRM_DEV_ERROR(dev, "error registering backlight device (%d)\n",
429 ret);
430 }
431
432 return ret;
433}
434
435static int s6e63m0_probe(struct spi_device *spi)
436{
437 struct device *dev = &spi->dev;
438 struct s6e63m0 *ctx;
439 int ret;
440
441 ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
442 if (!ctx)
443 return -ENOMEM;
444
445 spi_set_drvdata(spi, ctx);
446
447 ctx->dev = dev;
448 ctx->enabled = false;
449 ctx->prepared = false;
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 DRM_DEV_ERROR(dev, "failed to get regulators: %d\n", ret);
457 return ret;
458 }
459
460 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
461 if (IS_ERR(ctx->reset_gpio)) {
462 DRM_DEV_ERROR(dev, "cannot get reset-gpios %ld\n",
463 PTR_ERR(ctx->reset_gpio));
464 return PTR_ERR(ctx->reset_gpio);
465 }
466
467 spi->bits_per_word = 9;
468 spi->mode = SPI_MODE_3;
469 ret = spi_setup(spi);
470 if (ret < 0) {
471 DRM_DEV_ERROR(dev, "spi setup failed.\n");
472 return ret;
473 }
474
475 drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
476 DRM_MODE_CONNECTOR_DPI);
477
478 ret = s6e63m0_backlight_register(ctx);
479 if (ret < 0)
480 return ret;
481
482 return drm_panel_add(&ctx->panel);
483}
484
485static int s6e63m0_remove(struct spi_device *spi)
486{
487 struct s6e63m0 *ctx = spi_get_drvdata(spi);
488
489 drm_panel_remove(&ctx->panel);
490
491 return 0;
492}
493
494static const struct of_device_id s6e63m0_of_match[] = {
495 { .compatible = "samsung,s6e63m0" },
496 { /* sentinel */ }
497};
498MODULE_DEVICE_TABLE(of, s6e63m0_of_match);
499
500static struct spi_driver s6e63m0_driver = {
501 .probe = s6e63m0_probe,
502 .remove = s6e63m0_remove,
503 .driver = {
504 .name = "panel-samsung-s6e63m0",
505 .of_match_table = s6e63m0_of_match,
506 },
507};
508module_spi_driver(s6e63m0_driver);
509
510MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
511MODULE_DESCRIPTION("s6e63m0 LCD Driver");
512MODULE_LICENSE("GPL v2");