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 
35 struct 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 
44 static 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 
57 static 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 
114 static inline struct s6e63j0x03 *panel_to_s6e63j0x03(struct drm_panel *panel)
115 {
116 	return container_of(panel, struct s6e63j0x03, panel);
117 }
118 
119 static 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 
133 static 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 
138 static 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 
147 static 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 
165 static int s6e63j0x03_power_off(struct s6e63j0x03 *ctx)
166 {
167 	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
168 }
169 
170 static 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 
182 static 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 
206 static 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 
214 static const struct backlight_ops s6e63j0x03_bl_ops = {
215 	.update_status = s6e63j0x03_set_brightness,
216 };
217 
218 static 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 = FB_BLANK_NORMAL;
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 
239 static 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 = FB_BLANK_POWERDOWN;
249 
250 	return 0;
251 }
252 
253 static 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 
322 static 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 = FB_BLANK_NORMAL;
336 
337 	return 0;
338 
339 err:
340 	s6e63j0x03_power_off(ctx);
341 	return ret;
342 }
343 
344 static 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 = FB_BLANK_UNBLANK;
397 
398 	return 0;
399 }
400 
401 static 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 
425 static 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 
433 static 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_NO_EOT_PACKET;
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 		dev_err(dev, "failed to get regulators: %d\n", ret);
457 		return ret;
458 	}
459 
460 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
461 	if (IS_ERR(ctx->reset_gpio)) {
462 		dev_err(dev, "cannot get reset-gpio: %ld\n",
463 				PTR_ERR(ctx->reset_gpio));
464 		return PTR_ERR(ctx->reset_gpio);
465 	}
466 
467 	drm_panel_init(&ctx->panel, dev, &s6e63j0x03_funcs,
468 		       DRM_MODE_CONNECTOR_DSI);
469 
470 	ctx->bl_dev = backlight_device_register("s6e63j0x03", dev, ctx,
471 						&s6e63j0x03_bl_ops, NULL);
472 	if (IS_ERR(ctx->bl_dev)) {
473 		dev_err(dev, "failed to register backlight device\n");
474 		return PTR_ERR(ctx->bl_dev);
475 	}
476 
477 	ctx->bl_dev->props.max_brightness = MAX_BRIGHTNESS;
478 	ctx->bl_dev->props.brightness = DEFAULT_BRIGHTNESS;
479 	ctx->bl_dev->props.power = FB_BLANK_POWERDOWN;
480 
481 	drm_panel_add(&ctx->panel);
482 
483 	ret = mipi_dsi_attach(dsi);
484 	if (ret < 0)
485 		goto remove_panel;
486 
487 	return ret;
488 
489 remove_panel:
490 	drm_panel_remove(&ctx->panel);
491 	backlight_device_unregister(ctx->bl_dev);
492 
493 	return ret;
494 }
495 
496 static int s6e63j0x03_remove(struct mipi_dsi_device *dsi)
497 {
498 	struct s6e63j0x03 *ctx = mipi_dsi_get_drvdata(dsi);
499 
500 	mipi_dsi_detach(dsi);
501 	drm_panel_remove(&ctx->panel);
502 
503 	backlight_device_unregister(ctx->bl_dev);
504 
505 	return 0;
506 }
507 
508 static const struct of_device_id s6e63j0x03_of_match[] = {
509 	{ .compatible = "samsung,s6e63j0x03" },
510 	{ }
511 };
512 MODULE_DEVICE_TABLE(of, s6e63j0x03_of_match);
513 
514 static struct mipi_dsi_driver s6e63j0x03_driver = {
515 	.probe = s6e63j0x03_probe,
516 	.remove = s6e63j0x03_remove,
517 	.driver = {
518 		.name = "panel_samsung_s6e63j0x03",
519 		.of_match_table = s6e63j0x03_of_match,
520 	},
521 };
522 module_mipi_dsi_driver(s6e63j0x03_driver);
523 
524 MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
525 MODULE_AUTHOR("Hoegeun Kwon <hoegeun.kwon@samsung.com>");
526 MODULE_DESCRIPTION("MIPI-DSI based s6e63j0x03 AMOLED LCD Panel Driver");
527 MODULE_LICENSE("GPL v2");
528