1
2
3
4
5
6
7
8
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
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
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
102
103
104
105
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
401 s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
402 ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
403
404
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 { }
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