Loading...
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
4 *
5 * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd
6 *
7 * Inki Dae <inki.dae@samsung.com>
8 * Hoegeun Kwon <hoegeun.kwon@samsung.com>
9 */
10
11#include <linux/backlight.h>
12#include <linux/delay.h>
13#include <linux/gpio/consumer.h>
14#include <linux/module.h>
15#include <linux/regulator/consumer.h>
16
17#include <video/mipi_display.h>
18
19#include <drm/drm_mipi_dsi.h>
20#include <drm/drm_modes.h>
21#include <drm/drm_panel.h>
22#include <drm/drm_print.h>
23
24#define MCS_LEVEL2_KEY 0xf0
25#define MCS_MTP_KEY 0xf1
26#define MCS_MTP_SET3 0xd4
27
28#define MAX_BRIGHTNESS 100
29#define DEFAULT_BRIGHTNESS 80
30
31#define NUM_GAMMA_STEPS 9
32#define GAMMA_CMD_CNT 28
33
34#define FIRST_COLUMN 20
35
36struct s6e63j0x03 {
37 struct device *dev;
38 struct drm_panel panel;
39 struct backlight_device *bl_dev;
40
41 struct regulator_bulk_data supplies[2];
42 struct gpio_desc *reset_gpio;
43};
44
45static const struct drm_display_mode default_mode = {
46 .clock = 4649,
47 .hdisplay = 320,
48 .hsync_start = 320 + 1,
49 .hsync_end = 320 + 1 + 1,
50 .htotal = 320 + 1 + 1 + 1,
51 .vdisplay = 320,
52 .vsync_start = 320 + 150,
53 .vsync_end = 320 + 150 + 1,
54 .vtotal = 320 + 150 + 1 + 2,
55 .vrefresh = 30,
56 .flags = 0,
57};
58
59static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = {
60 { /* Gamma 10 */
61 MCS_MTP_SET3,
62 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26,
63 0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36,
64 0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf
65 },
66 { /* gamma 30 */
67 MCS_MTP_SET3,
68 0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26,
69 0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34,
70 0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc
71 },
72 { /* gamma 60 */
73 MCS_MTP_SET3,
74 0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a,
75 0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33,
76 0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf5
77 },
78 { /* gamma 90 */
79 MCS_MTP_SET3,
80 0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29,
81 0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31,
82 0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x09
83 },
84 { /* gamma 120 */
85 MCS_MTP_SET3,
86 0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28,
87 0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31,
88 0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b
89 },
90 { /* gamma 150 */
91 MCS_MTP_SET3,
92 0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28,
93 0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31,
94 0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x29
95 },
96 { /* gamma 200 */
97 MCS_MTP_SET3,
98 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29,
99 0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30,
100 0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b
101 },
102 { /* gamma 240 */
103 MCS_MTP_SET3,
104 0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a,
105 0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30,
106 0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47
107 },
108 { /* gamma 300 */
109 MCS_MTP_SET3,
110 0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28,
111 0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f,
112 0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57
113 }
114};
115
116static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
117{
118 return container_of(panel, struct s6e63j0x03, panel);
119}
120
121static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx,
122 const void *seq, size_t len)
123{
124 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
125
126 return mipi_dsi_dcs_write_buffer(dsi, seq, len);
127}
128
129#define s6e63j0x03_dcs_write_seq_static(ctx, seq...) \
130 ({ \
131 static const u8 d[] = { seq }; \
132 s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d)); \
133 })
134
135static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx)
136{
137 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a);
138}
139
140static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)
141{
142 if (on)
143 return s6e63j0x03_dcs_write_seq_static(ctx,
144 MCS_MTP_KEY, 0x5a, 0x5a);
145
146 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5);
147}
148
149static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)
150{
151 int ret;
152
153 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
154 if (ret < 0)
155 return ret;
156
157 msleep(30);
158
159 gpiod_set_value(ctx->reset_gpio, 1);
160 usleep_range(1000, 2000);
161 gpiod_set_value(ctx->reset_gpio, 0);
162 usleep_range(5000, 6000);
163
164 return 0;
165}
166
167static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
168{
169 return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
170}
171
172static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness)
173{
174 unsigned int index;
175
176 index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS);
177
178 if (index >= NUM_GAMMA_STEPS)
179 index = NUM_GAMMA_STEPS - 1;
180
181 return index;
182}
183
184static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
185 unsigned int brightness)
186{
187 struct backlight_device *bl_dev = ctx->bl_dev;
188 unsigned int index = s6e63j0x03_get_brightness_index(brightness);
189 int ret;
190
191 ret = s6e63j0x03_apply_mtp_key(ctx, true);
192 if (ret < 0)
193 return ret;
194
195 ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT);
196 if (ret < 0)
197 return ret;
198
199 ret = s6e63j0x03_apply_mtp_key(ctx, false);
200 if (ret < 0)
201 return ret;
202
203 bl_dev->props.brightness = brightness;
204
205 return 0;
206}
207
208static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
209{
210 struct s6e63j0x03 *ctx = bl_get_data(bl_dev);
211 unsigned int brightness = bl_dev->props.brightness;
212
213 return s6e63j0x03_update_gamma(ctx, brightness);
214}
215
216static const struct backlight_ops s6e63j0x03_bl_ops = {
217 .update_status = s6e63j0x03_set_brightness,
218};
219
220static int s6e63j0x03_disable(struct drm_panel *panel)
221{
222 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
223 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
224 int ret;
225
226 ret = mipi_dsi_dcs_set_display_off(dsi);
227 if (ret < 0)
228 return ret;
229
230 ctx->bl_dev->props.power = FB_BLANK_NORMAL;
231
232 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
233 if (ret < 0)
234 return ret;
235
236 msleep(120);
237
238 return 0;
239}
240
241static int s6e63j0x03_unprepare(struct drm_panel *panel)
242{
243 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
244 int ret;
245
246 ret = s6e63j0x03_power_off(ctx);
247 if (ret < 0)
248 return ret;
249
250 ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
251
252 return 0;
253}
254
255static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx)
256{
257 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
258 int ret;
259
260 ret = s6e63j0x03_enable_lv2_command(ctx);
261 if (ret < 0)
262 return ret;
263
264 ret = s6e63j0x03_apply_mtp_key(ctx, true);
265 if (ret < 0)
266 return ret;
267
268 /* set porch adjustment */
269 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);
270 if (ret < 0)
271 return ret;
272
273 /* set frame freq */
274 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);
275 if (ret < 0)
276 return ret;
277
278 /* set caset, paset */
279 ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN,
280 default_mode.hdisplay - 1 + FIRST_COLUMN);
281 if (ret < 0)
282 return ret;
283
284 ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1);
285 if (ret < 0)
286 return ret;
287
288 /* set ltps timming 0, 1 */
289 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17,
290 0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00);
291 if (ret < 0)
292 return ret;
293
294 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);
295 if (ret < 0)
296 return ret;
297
298 /* set param pos te_edge */
299 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);
300 if (ret < 0)
301 return ret;
302
303 /* set te rising edge */
304 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);
305 if (ret < 0)
306 return ret;
307
308 /* set param pos default */
309 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);
310 if (ret < 0)
311 return ret;
312
313 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
314 if (ret < 0)
315 return ret;
316
317 ret = s6e63j0x03_apply_mtp_key(ctx, false);
318 if (ret < 0)
319 return ret;
320
321 return 0;
322}
323
324static int s6e63j0x03_prepare(struct drm_panel *panel)
325{
326 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
327 int ret;
328
329 ret = s6e63j0x03_power_on(ctx);
330 if (ret < 0)
331 return ret;
332
333 ret = s6e63j0x03_panel_init(ctx);
334 if (ret < 0)
335 goto err;
336
337 ctx->bl_dev->props.power = FB_BLANK_NORMAL;
338
339 return 0;
340
341err:
342 s6e63j0x03_power_off(ctx);
343 return ret;
344}
345
346static int s6e63j0x03_enable(struct drm_panel *panel)
347{
348 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
349 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
350 int ret;
351
352 msleep(120);
353
354 ret = s6e63j0x03_apply_mtp_key(ctx, true);
355 if (ret < 0)
356 return ret;
357
358 /* set elvss_cond */
359 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);
360 if (ret < 0)
361 return ret;
362
363 /* set pos */
364 ret = s6e63j0x03_dcs_write_seq_static(ctx,
365 MIPI_DCS_SET_ADDRESS_MODE, 0x40);
366 if (ret < 0)
367 return ret;
368
369 /* set default white brightness */
370 ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
371 if (ret < 0)
372 return ret;
373
374 /* set white ctrl */
375 ret = s6e63j0x03_dcs_write_seq_static(ctx,
376 MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
377 if (ret < 0)
378 return ret;
379
380 /* set acl off */
381 ret = s6e63j0x03_dcs_write_seq_static(ctx,
382 MIPI_DCS_WRITE_POWER_SAVE, 0x00);
383 if (ret < 0)
384 return ret;
385
386 ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
387 if (ret < 0)
388 return ret;
389
390 ret = s6e63j0x03_apply_mtp_key(ctx, false);
391 if (ret < 0)
392 return ret;
393
394 ret = mipi_dsi_dcs_set_display_on(dsi);
395 if (ret < 0)
396 return ret;
397
398 ctx->bl_dev->props.power = FB_BLANK_UNBLANK;
399
400 return 0;
401}
402
403static int s6e63j0x03_get_modes(struct drm_panel *panel)
404{
405 struct drm_connector *connector = panel->connector;
406 struct drm_display_mode *mode;
407
408 mode = drm_mode_duplicate(panel->drm, &default_mode);
409 if (!mode) {
410 DRM_ERROR("failed to add mode %ux%ux@%u\n",
411 default_mode.hdisplay, default_mode.vdisplay,
412 default_mode.vrefresh);
413 return -ENOMEM;
414 }
415
416 drm_mode_set_name(mode);
417
418 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
419 drm_mode_probed_add(connector, mode);
420
421 connector->display_info.width_mm = 29;
422 connector->display_info.height_mm = 29;
423
424 return 1;
425}
426
427static const struct drm_panel_funcs s6e63j0x03_funcs = {
428 .disable = s6e63j0x03_disable,
429 .unprepare = s6e63j0x03_unprepare,
430 .prepare = s6e63j0x03_prepare,
431 .enable = s6e63j0x03_enable,
432 .get_modes = s6e63j0x03_get_modes,
433};
434
435static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
436{
437 struct device *dev = &dsi->dev;
438 struct s6e63j0x03 *ctx;
439 int ret;
440
441 ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
442 if (!ctx)
443 return -ENOMEM;
444
445 mipi_dsi_set_drvdata(dsi, ctx);
446
447 ctx->dev = dev;
448
449 dsi->lanes = 1;
450 dsi->format = MIPI_DSI_FMT_RGB888;
451 dsi->mode_flags = MIPI_DSI_MODE_EOT_PACKET;
452
453 ctx->supplies[0].supply = "vdd3";
454 ctx->supplies[1].supply = "vci";
455 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
456 ctx->supplies);
457 if (ret < 0) {
458 dev_err(dev, "failed to get regulators: %d\n", ret);
459 return ret;
460 }
461
462 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
463 if (IS_ERR(ctx->reset_gpio)) {
464 dev_err(dev, "cannot get reset-gpio: %ld\n",
465 PTR_ERR(ctx->reset_gpio));
466 return PTR_ERR(ctx->reset_gpio);
467 }
468
469 drm_panel_init(&ctx->panel);
470 ctx->panel.dev = dev;
471 ctx->panel.funcs = &s6e63j0x03_funcs;
472
473 ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
474 &s6e63j0x03_bl_ops, NULL);
475 if (IS_ERR(ctx->bl_dev)) {
476 dev_err(dev, "failed to register backlight device\n");
477 return PTR_ERR(ctx->bl_dev);
478 }
479
480 ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
481 ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
482 ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
483
484 ret = drm_panel_add(&ctx->panel);
485 if (ret < 0)
486 goto unregister_backlight;
487
488 ret = mipi_dsi_attach(dsi);
489 if (ret < 0)
490 goto remove_panel;
491
492 return ret;
493
494remove_panel:
495 drm_panel_remove(&ctx->panel);
496
497unregister_backlight:
498 backlight_device_unregister(ctx->bl_dev);
499
500 return ret;
501}
502
503static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
504{
505 struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
506
507 mipi_dsi_detach(dsi);
508 drm_panel_remove(&ctx->panel);
509
510 backlight_device_unregister(ctx->bl_dev);
511
512 return 0;
513}
514
515static const struct of_device_id s6e63j0x03_of_match[] = {
516 { .compatible = "samsung,s6e63j0x03" },
517 { }
518};
519MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
520
521static struct mipi_dsi_driver s6e63j0x03_driver = {
522 .probe = s6e63j0x03_probe,
523 .remove = s6e63j0x03_remove,
524 .driver = {
525 .name = "panel_samsung_s6e63j0x03",
526 .of_match_table = s6e63j0x03_of_match,
527 },
528};
529module_mipi_dsi_driver(s6e63j0x03_driver);
530
531MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
532MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
533MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
534MODULE_LICENSE("GPL v2");
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * MIPI-DSI based S6E63J0X03 AMOLED lcd 1.63 inch panel driver.
4 *
5 * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd
6 *
7 * Inki Dae <inki.dae@samsung.com>
8 * Hoegeun Kwon <hoegeun.kwon@samsung.com>
9 */
10
11#include <linux/backlight.h>
12#include <linux/delay.h>
13#include <linux/gpio/consumer.h>
14#include <linux/module.h>
15#include <linux/regulator/consumer.h>
16
17#include <video/mipi_display.h>
18
19#include <drm/drm_mipi_dsi.h>
20#include <drm/drm_modes.h>
21#include <drm/drm_panel.h>
22
23#define MCS_LEVEL2_KEY 0xf0
24#define MCS_MTP_KEY 0xf1
25#define MCS_MTP_SET3 0xd4
26
27#define MAX_BRIGHTNESS 100
28#define DEFAULT_BRIGHTNESS 80
29
30#define NUM_GAMMA_STEPS 9
31#define GAMMA_CMD_CNT 28
32
33#define FIRST_COLUMN 20
34
35struct s6e63j0x03 {
36 struct device *dev;
37 struct drm_panel panel;
38 struct backlight_device *bl_dev;
39
40 struct regulator_bulk_data supplies[2];
41 struct gpio_desc *reset_gpio;
42};
43
44static const struct drm_display_mode default_mode = {
45 .clock = 4649,
46 .hdisplay = 320,
47 .hsync_start = 320 + 1,
48 .hsync_end = 320 + 1 + 1,
49 .htotal = 320 + 1 + 1 + 1,
50 .vdisplay = 320,
51 .vsync_start = 320 + 150,
52 .vsync_end = 320 + 150 + 1,
53 .vtotal = 320 + 150 + 1 + 2,
54 .flags = 0,
55};
56
57static const unsigned char gamma_tbl[NUM_GAMMA_STEPS][GAMMA_CMD_CNT] = {
58 { /* Gamma 10 */
59 MCS_MTP_SET3,
60 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x7f, 0x52, 0x6b, 0x6f, 0x26,
61 0x28, 0x2d, 0x28, 0x26, 0x27, 0x33, 0x34, 0x32, 0x36, 0x36,
62 0x35, 0x00, 0xab, 0x00, 0xae, 0x00, 0xbf
63 },
64 { /* gamma 30 */
65 MCS_MTP_SET3,
66 0x00, 0x00, 0x00, 0x70, 0x7f, 0x7f, 0x4e, 0x64, 0x69, 0x26,
67 0x27, 0x2a, 0x28, 0x29, 0x27, 0x31, 0x32, 0x31, 0x35, 0x34,
68 0x35, 0x00, 0xc4, 0x00, 0xca, 0x00, 0xdc
69 },
70 { /* gamma 60 */
71 MCS_MTP_SET3,
72 0x00, 0x00, 0x00, 0x65, 0x7b, 0x7d, 0x5f, 0x67, 0x68, 0x2a,
73 0x28, 0x29, 0x28, 0x2a, 0x27, 0x31, 0x2f, 0x30, 0x34, 0x33,
74 0x34, 0x00, 0xd9, 0x00, 0xe4, 0x00, 0xf5
75 },
76 { /* gamma 90 */
77 MCS_MTP_SET3,
78 0x00, 0x00, 0x00, 0x4d, 0x6f, 0x71, 0x67, 0x6a, 0x6c, 0x29,
79 0x28, 0x28, 0x28, 0x29, 0x27, 0x30, 0x2e, 0x30, 0x32, 0x31,
80 0x31, 0x00, 0xea, 0x00, 0xf6, 0x01, 0x09
81 },
82 { /* gamma 120 */
83 MCS_MTP_SET3,
84 0x00, 0x00, 0x00, 0x3d, 0x66, 0x68, 0x69, 0x69, 0x69, 0x28,
85 0x28, 0x27, 0x28, 0x28, 0x27, 0x30, 0x2e, 0x2f, 0x31, 0x31,
86 0x30, 0x00, 0xf9, 0x01, 0x05, 0x01, 0x1b
87 },
88 { /* gamma 150 */
89 MCS_MTP_SET3,
90 0x00, 0x00, 0x00, 0x31, 0x51, 0x53, 0x66, 0x66, 0x67, 0x28,
91 0x29, 0x27, 0x28, 0x27, 0x27, 0x2e, 0x2d, 0x2e, 0x31, 0x31,
92 0x30, 0x01, 0x04, 0x01, 0x11, 0x01, 0x29
93 },
94 { /* gamma 200 */
95 MCS_MTP_SET3,
96 0x00, 0x00, 0x00, 0x2f, 0x4f, 0x51, 0x67, 0x65, 0x65, 0x29,
97 0x2a, 0x28, 0x27, 0x25, 0x26, 0x2d, 0x2c, 0x2c, 0x30, 0x30,
98 0x30, 0x01, 0x14, 0x01, 0x23, 0x01, 0x3b
99 },
100 { /* gamma 240 */
101 MCS_MTP_SET3,
102 0x00, 0x00, 0x00, 0x2c, 0x4d, 0x50, 0x65, 0x63, 0x64, 0x2a,
103 0x2c, 0x29, 0x26, 0x24, 0x25, 0x2c, 0x2b, 0x2b, 0x30, 0x30,
104 0x30, 0x01, 0x1e, 0x01, 0x2f, 0x01, 0x47
105 },
106 { /* gamma 300 */
107 MCS_MTP_SET3,
108 0x00, 0x00, 0x00, 0x38, 0x61, 0x64, 0x65, 0x63, 0x64, 0x28,
109 0x2a, 0x27, 0x26, 0x23, 0x25, 0x2b, 0x2b, 0x2a, 0x30, 0x2f,
110 0x30, 0x01, 0x2d, 0x01, 0x3f, 0x01, 0x57
111 }
112};
113
114static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
115{
116 return container_of(panel, struct s6e63j0x03, panel);
117}
118
119static inline ssize_t s6e63j0x03_dcs_write_seq(struct s6e63j0x03 *ctx,
120 const void *seq, size_t len)
121{
122 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
123
124 return mipi_dsi_dcs_write_buffer(dsi, seq, len);
125}
126
127#define s6e63j0x03_dcs_write_seq_static(ctx, seq...) \
128 ({ \
129 static const u8 d[] = { seq }; \
130 s6e63j0x03_dcs_write_seq(ctx, d, ARRAY_SIZE(d)); \
131 })
132
133static inline int s6e63j0x03_enable_lv2_command(struct s6e63j0x03 *ctx)
134{
135 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_LEVEL2_KEY, 0x5a, 0x5a);
136}
137
138static inline int s6e63j0x03_apply_mtp_key(struct s6e63j0x03 *ctx, bool on)
139{
140 if (on)
141 return s6e63j0x03_dcs_write_seq_static(ctx,
142 MCS_MTP_KEY, 0x5a, 0x5a);
143
144 return s6e63j0x03_dcs_write_seq_static(ctx, MCS_MTP_KEY, 0xa5, 0xa5);
145}
146
147static int s6e63j0x03_power_on(struct s6e63j0x03 *ctx)
148{
149 int ret;
150
151 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
152 if (ret < 0)
153 return ret;
154
155 msleep(30);
156
157 gpiod_set_value(ctx->reset_gpio, 1);
158 usleep_range(1000, 2000);
159 gpiod_set_value(ctx->reset_gpio, 0);
160 usleep_range(5000, 6000);
161
162 return 0;
163}
164
165static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
166{
167 return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
168}
169
170static unsigned int s6e63j0x03_get_brightness_index(unsigned int brightness)
171{
172 unsigned int index;
173
174 index = brightness / (MAX_BRIGHTNESS / NUM_GAMMA_STEPS);
175
176 if (index >= NUM_GAMMA_STEPS)
177 index = NUM_GAMMA_STEPS - 1;
178
179 return index;
180}
181
182static int s6e63j0x03_update_gamma(struct s6e63j0x03 *ctx,
183 unsigned int brightness)
184{
185 struct backlight_device *bl_dev = ctx->bl_dev;
186 unsigned int index = s6e63j0x03_get_brightness_index(brightness);
187 int ret;
188
189 ret = s6e63j0x03_apply_mtp_key(ctx, true);
190 if (ret < 0)
191 return ret;
192
193 ret = s6e63j0x03_dcs_write_seq(ctx, gamma_tbl[index], GAMMA_CMD_CNT);
194 if (ret < 0)
195 return ret;
196
197 ret = s6e63j0x03_apply_mtp_key(ctx, false);
198 if (ret < 0)
199 return ret;
200
201 bl_dev->props.brightness = brightness;
202
203 return 0;
204}
205
206static int s6e63j0x03_set_brightness(struct backlight_device *bl_dev)
207{
208 struct s6e63j0x03 *ctx = bl_get_data(bl_dev);
209 unsigned int brightness = bl_dev->props.brightness;
210
211 return s6e63j0x03_update_gamma(ctx, brightness);
212}
213
214static const struct backlight_ops s6e63j0x03_bl_ops = {
215 .update_status = s6e63j0x03_set_brightness,
216};
217
218static int s6e63j0x03_disable(struct drm_panel *panel)
219{
220 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
221 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
222 int ret;
223
224 ret = mipi_dsi_dcs_set_display_off(dsi);
225 if (ret < 0)
226 return ret;
227
228 ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED;
229
230 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
231 if (ret < 0)
232 return ret;
233
234 msleep(120);
235
236 return 0;
237}
238
239static int s6e63j0x03_unprepare(struct drm_panel *panel)
240{
241 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
242 int ret;
243
244 ret = s6e63j0x03_power_off(ctx);
245 if (ret < 0)
246 return ret;
247
248 ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF;
249
250 return 0;
251}
252
253static int s6e63j0x03_panel_init(struct s6e63j0x03 *ctx)
254{
255 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
256 int ret;
257
258 ret = s6e63j0x03_enable_lv2_command(ctx);
259 if (ret < 0)
260 return ret;
261
262 ret = s6e63j0x03_apply_mtp_key(ctx, true);
263 if (ret < 0)
264 return ret;
265
266 /* set porch adjustment */
267 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf2, 0x1c, 0x28);
268 if (ret < 0)
269 return ret;
270
271 /* set frame freq */
272 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb5, 0x00, 0x02, 0x00);
273 if (ret < 0)
274 return ret;
275
276 /* set caset, paset */
277 ret = mipi_dsi_dcs_set_column_address(dsi, FIRST_COLUMN,
278 default_mode.hdisplay - 1 + FIRST_COLUMN);
279 if (ret < 0)
280 return ret;
281
282 ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1);
283 if (ret < 0)
284 return ret;
285
286 /* set ltps timming 0, 1 */
287 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf8, 0x08, 0x08, 0x08, 0x17,
288 0x00, 0x2a, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, 0x00);
289 if (ret < 0)
290 return ret;
291
292 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xf7, 0x02);
293 if (ret < 0)
294 return ret;
295
296 /* set param pos te_edge */
297 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x01);
298 if (ret < 0)
299 return ret;
300
301 /* set te rising edge */
302 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xe2, 0x0f);
303 if (ret < 0)
304 return ret;
305
306 /* set param pos default */
307 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb0, 0x00);
308 if (ret < 0)
309 return ret;
310
311 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
312 if (ret < 0)
313 return ret;
314
315 ret = s6e63j0x03_apply_mtp_key(ctx, false);
316 if (ret < 0)
317 return ret;
318
319 return 0;
320}
321
322static int s6e63j0x03_prepare(struct drm_panel *panel)
323{
324 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
325 int ret;
326
327 ret = s6e63j0x03_power_on(ctx);
328 if (ret < 0)
329 return ret;
330
331 ret = s6e63j0x03_panel_init(ctx);
332 if (ret < 0)
333 goto err;
334
335 ctx->bl_dev->props.power = BACKLIGHT_POWER_REDUCED;
336
337 return 0;
338
339err:
340 s6e63j0x03_power_off(ctx);
341 return ret;
342}
343
344static int s6e63j0x03_enable(struct drm_panel *panel)
345{
346 struct s6e63j0x03 *ctx = panel_to_s6e63j0x03(panel);
347 struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
348 int ret;
349
350 msleep(120);
351
352 ret = s6e63j0x03_apply_mtp_key(ctx, true);
353 if (ret < 0)
354 return ret;
355
356 /* set elvss_cond */
357 ret = s6e63j0x03_dcs_write_seq_static(ctx, 0xb1, 0x00, 0x09);
358 if (ret < 0)
359 return ret;
360
361 /* set pos */
362 ret = s6e63j0x03_dcs_write_seq_static(ctx,
363 MIPI_DCS_SET_ADDRESS_MODE, 0x40);
364 if (ret < 0)
365 return ret;
366
367 /* set default white brightness */
368 ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff);
369 if (ret < 0)
370 return ret;
371
372 /* set white ctrl */
373 ret = s6e63j0x03_dcs_write_seq_static(ctx,
374 MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20);
375 if (ret < 0)
376 return ret;
377
378 /* set acl off */
379 ret = s6e63j0x03_dcs_write_seq_static(ctx,
380 MIPI_DCS_WRITE_POWER_SAVE, 0x00);
381 if (ret < 0)
382 return ret;
383
384 ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
385 if (ret < 0)
386 return ret;
387
388 ret = s6e63j0x03_apply_mtp_key(ctx, false);
389 if (ret < 0)
390 return ret;
391
392 ret = mipi_dsi_dcs_set_display_on(dsi);
393 if (ret < 0)
394 return ret;
395
396 ctx->bl_dev->props.power = BACKLIGHT_POWER_ON;
397
398 return 0;
399}
400
401static int s6e63j0x03_get_modes(struct drm_panel *panel,
402 struct drm_connector *connector)
403{
404 struct drm_display_mode *mode;
405
406 mode = drm_mode_duplicate(connector->dev, &default_mode);
407 if (!mode) {
408 dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
409 default_mode.hdisplay, default_mode.vdisplay,
410 drm_mode_vrefresh(&default_mode));
411 return -ENOMEM;
412 }
413
414 drm_mode_set_name(mode);
415
416 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
417 drm_mode_probed_add(connector, mode);
418
419 connector->display_info.width_mm = 29;
420 connector->display_info.height_mm = 29;
421
422 return 1;
423}
424
425static const struct drm_panel_funcs s6e63j0x03_funcs = {
426 .disable = s6e63j0x03_disable,
427 .unprepare = s6e63j0x03_unprepare,
428 .prepare = s6e63j0x03_prepare,
429 .enable = s6e63j0x03_enable,
430 .get_modes = s6e63j0x03_get_modes,
431};
432
433static int s6e63j0x03_probe(struct mipi_dsi_device *dsi)
434{
435 struct device *dev = &dsi->dev;
436 struct s6e63j0x03 *ctx;
437 int ret;
438
439 ctx = devm_kzalloc(dev, sizeof(struct s6e63j0x03), GFP_KERNEL);
440 if (!ctx)
441 return -ENOMEM;
442
443 mipi_dsi_set_drvdata(dsi, ctx);
444
445 ctx->dev = dev;
446
447 dsi->lanes = 1;
448 dsi->format = MIPI_DSI_FMT_RGB888;
449 dsi->mode_flags = MIPI_DSI_MODE_VIDEO_NO_HFP |
450 MIPI_DSI_MODE_VIDEO_NO_HBP | MIPI_DSI_MODE_VIDEO_NO_HSA;
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 return dev_err_probe(dev, ret, "failed to get regulators\n");
458
459 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
460 if (IS_ERR(ctx->reset_gpio))
461 return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
462 "cannot get reset-gpio\n");
463
464 drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs,
465 DRM_MODE_CONNECTOR_DSI);
466 ctx->panel.prepare_prev_first = true;
467
468 ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
469 &s6e63j0x03_bl_ops, NULL);
470 if (IS_ERR(ctx->bl_dev))
471 return dev_err_probe(dev, PTR_ERR(ctx->bl_dev),
472 "failed to register backlight device\n");
473
474 ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
475 ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
476 ctx->bl_dev->props.power = BACKLIGHT_POWER_OFF;
477
478 drm_panel_add(&ctx->panel);
479
480 ret = mipi_dsi_attach(dsi);
481 if (ret < 0)
482 goto remove_panel;
483
484 return ret;
485
486remove_panel:
487 drm_panel_remove(&ctx->panel);
488 backlight_device_unregister(ctx->bl_dev);
489
490 return ret;
491}
492
493static void s6e63j0x03_remove(struct mipi_dsi_device *dsi)
494{
495 struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
496
497 mipi_dsi_detach(dsi);
498 drm_panel_remove(&ctx->panel);
499
500 backlight_device_unregister(ctx->bl_dev);
501}
502
503static const struct of_device_id s6e63j0x03_of_match[] = {
504 { .compatible = "samsung,s6e63j0x03" },
505 { }
506};
507MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
508
509static struct mipi_dsi_driver s6e63j0x03_driver = {
510 .probe = s6e63j0x03_probe,
511 .remove = s6e63j0x03_remove,
512 .driver = {
513 .name = "panel_samsung_s6e63j0x03",
514 .of_match_table = s6e63j0x03_of_match,
515 },
516};
517module_mipi_dsi_driver(s6e63j0x03_driver);
518
519MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
520MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
521MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
522MODULE_LICENSE("GPL v2");