1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Samsung S6D7AA0 MIPI-DSI TFT LCD controller drm_panel driver.
4  *
5  * Copyright (C) 2022 Artur Weber <aweber.kernel@gmail.com>
6  */
7 
8 #include <linux/backlight.h>
9 #include <linux/delay.h>
10 #include <linux/gpio/consumer.h>
11 #include <linux/module.h>
12 #include <linux/regulator/consumer.h>
13 #include <linux/of.h>
14 #include <linux/of_device.h>
15 
16 #include <video/mipi_display.h>
17 #include <drm/drm_mipi_dsi.h>
18 #include <drm/drm_modes.h>
19 #include <drm/drm_panel.h>
20 
21 /* Manufacturer command set */
22 #define MCS_BL_CTL		0xc3
23 #define MCS_OTP_RELOAD		0xd0
24 #define MCS_PASSWD1		0xf0
25 #define MCS_PASSWD2		0xf1
26 #define MCS_PASSWD3		0xfc
27 
28 struct s6d7aa0 {
29 	struct drm_panel panel;
30 	struct mipi_dsi_device *dsi;
31 	struct gpio_desc *reset_gpio;
32 	struct regulator_bulk_data supplies[2];
33 	const struct s6d7aa0_panel_desc *desc;
34 };
35 
36 struct s6d7aa0_panel_desc {
37 	unsigned int panel_type;
38 	int (*init_func)(struct s6d7aa0 *ctx);
39 	int (*off_func)(struct s6d7aa0 *ctx);
40 	const struct drm_display_mode *drm_mode;
41 	unsigned long mode_flags;
42 	u32 bus_flags;
43 	bool has_backlight;
44 	bool use_passwd3;
45 };
46 
47 enum s6d7aa0_panels {
48 	S6D7AA0_PANEL_LSL080AL02,
49 	S6D7AA0_PANEL_LSL080AL03,
50 	S6D7AA0_PANEL_LTL101AT01,
51 };
52 
53 static inline struct s6d7aa0 *panel_to_s6d7aa0(struct drm_panel *panel)
54 {
55 	return container_of(panel, struct s6d7aa0, panel);
56 }
57 
58 static void s6d7aa0_reset(struct s6d7aa0 *ctx)
59 {
60 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
61 	msleep(50);
62 	gpiod_set_value_cansleep(ctx->reset_gpio, 0);
63 	msleep(50);
64 }
65 
66 static int s6d7aa0_lock(struct s6d7aa0 *ctx, bool lock)
67 {
68 	struct mipi_dsi_device *dsi = ctx->dsi;
69 	int ret = 0;
70 
71 	if (lock) {
72 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0xa5, 0xa5);
73 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0xa5, 0xa5);
74 		if (ctx->desc->use_passwd3)
75 			mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0x5a, 0x5a);
76 	} else {
77 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD1, 0x5a, 0x5a);
78 		mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD2, 0x5a, 0x5a);
79 		if (ctx->desc->use_passwd3)
80 			mipi_dsi_dcs_write_seq(dsi, MCS_PASSWD3, 0xa5, 0xa5);
81 	}
82 
83 	return ret;
84 }
85 
86 static int s6d7aa0_on(struct s6d7aa0 *ctx)
87 {
88 	struct mipi_dsi_device *dsi = ctx->dsi;
89 	struct device *dev = &dsi->dev;
90 	int ret;
91 
92 	ret = ctx->desc->init_func(ctx);
93 	if (ret < 0) {
94 		dev_err(dev, "Failed to initialize panel: %d\n", ret);
95 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
96 		return ret;
97 	}
98 
99 	ret = mipi_dsi_dcs_set_display_on(dsi);
100 	if (ret < 0) {
101 		dev_err(dev, "Failed to set display on: %d\n", ret);
102 		return ret;
103 	}
104 
105 	return 0;
106 }
107 
108 static int s6d7aa0_off(struct s6d7aa0 *ctx)
109 {
110 	struct mipi_dsi_device *dsi = ctx->dsi;
111 	struct device *dev = &dsi->dev;
112 	int ret;
113 
114 	ret = ctx->desc->off_func(ctx);
115 	if (ret < 0) {
116 		dev_err(dev, "Panel-specific off function failed: %d\n", ret);
117 		return ret;
118 	}
119 
120 	ret = mipi_dsi_dcs_set_display_off(dsi);
121 	if (ret < 0) {
122 		dev_err(dev, "Failed to set display off: %d\n", ret);
123 		return ret;
124 	}
125 	msleep(64);
126 
127 	ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
128 	if (ret < 0) {
129 		dev_err(dev, "Failed to enter sleep mode: %d\n", ret);
130 		return ret;
131 	}
132 	msleep(120);
133 
134 	return 0;
135 }
136 
137 static int s6d7aa0_prepare(struct drm_panel *panel)
138 {
139 	struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);
140 	struct device *dev = &ctx->dsi->dev;
141 	int ret;
142 
143 	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
144 	if (ret < 0) {
145 		dev_err(dev, "Failed to enable regulators: %d\n", ret);
146 		return ret;
147 	}
148 
149 	s6d7aa0_reset(ctx);
150 
151 	ret = s6d7aa0_on(ctx);
152 	if (ret < 0) {
153 		dev_err(dev, "Failed to initialize panel: %d\n", ret);
154 		gpiod_set_value_cansleep(ctx->reset_gpio, 1);
155 		return ret;
156 	}
157 
158 	return 0;
159 }
160 
161 static int s6d7aa0_disable(struct drm_panel *panel)
162 {
163 	struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);
164 	struct device *dev = &ctx->dsi->dev;
165 	int ret;
166 
167 	ret = s6d7aa0_off(ctx);
168 	if (ret < 0)
169 		dev_err(dev, "Failed to un-initialize panel: %d\n", ret);
170 
171 	return 0;
172 }
173 
174 static int s6d7aa0_unprepare(struct drm_panel *panel)
175 {
176 	struct s6d7aa0 *ctx = panel_to_s6d7aa0(panel);
177 
178 	gpiod_set_value_cansleep(ctx->reset_gpio, 1);
179 	regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
180 
181 	return 0;
182 }
183 
184 /* Backlight control code */
185 
186 static int s6d7aa0_bl_update_status(struct backlight_device *bl)
187 {
188 	struct mipi_dsi_device *dsi = bl_get_data(bl);
189 	u16 brightness = backlight_get_brightness(bl);
190 	int ret;
191 
192 	ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness);
193 	if (ret < 0)
194 		return ret;
195 
196 	return 0;
197 }
198 
199 static int s6d7aa0_bl_get_brightness(struct backlight_device *bl)
200 {
201 	struct mipi_dsi_device *dsi = bl_get_data(bl);
202 	u16 brightness;
203 	int ret;
204 
205 	ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);
206 	if (ret < 0)
207 		return ret;
208 
209 	return brightness & 0xff;
210 }
211 
212 static const struct backlight_ops s6d7aa0_bl_ops = {
213 	.update_status = s6d7aa0_bl_update_status,
214 	.get_brightness = s6d7aa0_bl_get_brightness,
215 };
216 
217 static struct backlight_device *
218 s6d7aa0_create_backlight(struct mipi_dsi_device *dsi)
219 {
220 	struct device *dev = &dsi->dev;
221 	const struct backlight_properties props = {
222 		.type = BACKLIGHT_RAW,
223 		.brightness = 255,
224 		.max_brightness = 255,
225 	};
226 
227 	return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
228 					      &s6d7aa0_bl_ops, &props);
229 }
230 
231 /* Initialization code and structures for LSL080AL02 panel */
232 
233 static int s6d7aa0_lsl080al02_init(struct s6d7aa0 *ctx)
234 {
235 	struct mipi_dsi_device *dsi = ctx->dsi;
236 	struct device *dev = &dsi->dev;
237 	int ret;
238 
239 	usleep_range(20000, 25000);
240 
241 	ret = s6d7aa0_lock(ctx, false);
242 	if (ret < 0) {
243 		dev_err(dev, "Failed to unlock registers: %d\n", ret);
244 		return ret;
245 	}
246 
247 	mipi_dsi_dcs_write_seq(dsi, MCS_OTP_RELOAD, 0x00, 0x10);
248 	usleep_range(1000, 1500);
249 
250 	/* SEQ_B6_PARAM_8_R01 */
251 	mipi_dsi_dcs_write_seq(dsi, 0xb6, 0x10);
252 
253 	/* BL_CTL_ON */
254 	mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x28);
255 
256 	usleep_range(5000, 6000);
257 
258 	mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x04);
259 
260 	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
261 	if (ret < 0) {
262 		dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
263 		return ret;
264 	}
265 
266 	msleep(120);
267 	mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
268 
269 	ret = s6d7aa0_lock(ctx, true);
270 	if (ret < 0) {
271 		dev_err(dev, "Failed to lock registers: %d\n", ret);
272 		return ret;
273 	}
274 
275 	ret = mipi_dsi_dcs_set_display_on(dsi);
276 	if (ret < 0) {
277 		dev_err(dev, "Failed to set display on: %d\n", ret);
278 		return ret;
279 	}
280 
281 	return 0;
282 }
283 
284 static int s6d7aa0_lsl080al02_off(struct s6d7aa0 *ctx)
285 {
286 	struct mipi_dsi_device *dsi = ctx->dsi;
287 
288 	/* BL_CTL_OFF */
289 	mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x20);
290 
291 	return 0;
292 }
293 
294 static const struct drm_display_mode s6d7aa0_lsl080al02_mode = {
295 	.clock = (800 + 16 + 4 + 140) * (1280 + 8 + 4 + 4) * 60 / 1000,
296 	.hdisplay = 800,
297 	.hsync_start = 800 + 16,
298 	.hsync_end = 800 + 16 + 4,
299 	.htotal = 800 + 16 + 4 + 140,
300 	.vdisplay = 1280,
301 	.vsync_start = 1280 + 8,
302 	.vsync_end = 1280 + 8 + 4,
303 	.vtotal = 1280 + 8 + 4 + 4,
304 	.width_mm = 108,
305 	.height_mm = 173,
306 };
307 
308 static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al02_desc = {
309 	.panel_type = S6D7AA0_PANEL_LSL080AL02,
310 	.init_func = s6d7aa0_lsl080al02_init,
311 	.off_func = s6d7aa0_lsl080al02_off,
312 	.drm_mode = &s6d7aa0_lsl080al02_mode,
313 	.mode_flags = MIPI_DSI_MODE_VSYNC_FLUSH | MIPI_DSI_MODE_VIDEO_NO_HFP,
314 	.bus_flags = DRM_BUS_FLAG_DE_HIGH,
315 
316 	.has_backlight = false,
317 	.use_passwd3 = false,
318 };
319 
320 /* Initialization code and structures for LSL080AL03 panel */
321 
322 static int s6d7aa0_lsl080al03_init(struct s6d7aa0 *ctx)
323 {
324 	struct mipi_dsi_device *dsi = ctx->dsi;
325 	struct device *dev = &dsi->dev;
326 	int ret;
327 
328 	usleep_range(20000, 25000);
329 
330 	ret = s6d7aa0_lock(ctx, false);
331 	if (ret < 0) {
332 		dev_err(dev, "Failed to unlock registers: %d\n", ret);
333 		return ret;
334 	}
335 
336 	if (ctx->desc->panel_type == S6D7AA0_PANEL_LSL080AL03) {
337 		mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0xc7, 0x00, 0x29);
338 		mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0xa0);
339 		mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23,
340 				       0x09);
341 		mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21,
342 				       0x80, 0x78);
343 	} else if (ctx->desc->panel_type == S6D7AA0_PANEL_LTL101AT01) {
344 		mipi_dsi_dcs_write_seq(dsi, MCS_BL_CTL, 0x40, 0x00, 0x08);
345 		mipi_dsi_dcs_write_seq(dsi, 0xbc, 0x01, 0x4e, 0x0b);
346 		mipi_dsi_dcs_write_seq(dsi, 0xfd, 0x16, 0x10, 0x11, 0x23,
347 				       0x09);
348 		mipi_dsi_dcs_write_seq(dsi, 0xfe, 0x00, 0x02, 0x03, 0x21,
349 				       0x80, 0x68);
350 	}
351 
352 	mipi_dsi_dcs_write_seq(dsi, 0xb3, 0x51);
353 	mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);
354 	mipi_dsi_dcs_write_seq(dsi, 0xf2, 0x02, 0x08, 0x08);
355 
356 	usleep_range(10000, 11000);
357 
358 	mipi_dsi_dcs_write_seq(dsi, 0xc0, 0x80, 0x80, 0x30);
359 	mipi_dsi_dcs_write_seq(dsi, 0xcd,
360 			       0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
361 			       0x2e, 0x2e, 0x2e, 0x2e, 0x2e);
362 	mipi_dsi_dcs_write_seq(dsi, 0xce,
363 			       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
364 			       0x00, 0x00, 0x00, 0x00, 0x00);
365 	mipi_dsi_dcs_write_seq(dsi, 0xc1, 0x03);
366 
367 	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
368 	if (ret < 0) {
369 		dev_err(dev, "Failed to exit sleep mode: %d\n", ret);
370 		return ret;
371 	}
372 
373 	ret = s6d7aa0_lock(ctx, true);
374 	if (ret < 0) {
375 		dev_err(dev, "Failed to lock registers: %d\n", ret);
376 		return ret;
377 	}
378 
379 	ret = mipi_dsi_dcs_set_display_on(dsi);
380 	if (ret < 0) {
381 		dev_err(dev, "Failed to set display on: %d\n", ret);
382 		return ret;
383 	}
384 
385 	return 0;
386 }
387 
388 static int s6d7aa0_lsl080al03_off(struct s6d7aa0 *ctx)
389 {
390 	struct mipi_dsi_device *dsi = ctx->dsi;
391 
392 	mipi_dsi_dcs_write_seq(dsi, 0x22, 0x00);
393 
394 	return 0;
395 }
396 
397 static const struct drm_display_mode s6d7aa0_lsl080al03_mode = {
398 	.clock = (768 + 18 + 16 + 126) * (1024 + 8 + 2 + 6) * 60 / 1000,
399 	.hdisplay = 768,
400 	.hsync_start = 768 + 18,
401 	.hsync_end = 768 + 18 + 16,
402 	.htotal = 768 + 18 + 16 + 126,
403 	.vdisplay = 1024,
404 	.vsync_start = 1024 + 8,
405 	.vsync_end = 1024 + 8 + 2,
406 	.vtotal = 1024 + 8 + 2 + 6,
407 	.width_mm = 122,
408 	.height_mm = 163,
409 };
410 
411 static const struct s6d7aa0_panel_desc s6d7aa0_lsl080al03_desc = {
412 	.panel_type = S6D7AA0_PANEL_LSL080AL03,
413 	.init_func = s6d7aa0_lsl080al03_init,
414 	.off_func = s6d7aa0_lsl080al03_off,
415 	.drm_mode = &s6d7aa0_lsl080al03_mode,
416 	.mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET,
417 	.bus_flags = 0,
418 
419 	.has_backlight = true,
420 	.use_passwd3 = true,
421 };
422 
423 /* Initialization structures for LTL101AT01 panel */
424 
425 static const struct drm_display_mode s6d7aa0_ltl101at01_mode = {
426 	.clock = (768 + 96 + 16 + 184) * (1024 + 8 + 2 + 6) * 60 / 1000,
427 	.hdisplay = 768,
428 	.hsync_start = 768 + 96,
429 	.hsync_end = 768 + 96 + 16,
430 	.htotal = 768 + 96 + 16 + 184,
431 	.vdisplay = 1024,
432 	.vsync_start = 1024 + 8,
433 	.vsync_end = 1024 + 8 + 2,
434 	.vtotal = 1024 + 8 + 2 + 6,
435 	.width_mm = 148,
436 	.height_mm = 197,
437 };
438 
439 static const struct s6d7aa0_panel_desc s6d7aa0_ltl101at01_desc = {
440 	.panel_type = S6D7AA0_PANEL_LTL101AT01,
441 	.init_func = s6d7aa0_lsl080al03_init, /* Similar init to LSL080AL03 */
442 	.off_func = s6d7aa0_lsl080al03_off,
443 	.drm_mode = &s6d7aa0_ltl101at01_mode,
444 	.mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET,
445 	.bus_flags = 0,
446 
447 	.has_backlight = true,
448 	.use_passwd3 = true,
449 };
450 
451 static int s6d7aa0_get_modes(struct drm_panel *panel,
452 					struct drm_connector *connector)
453 {
454 	struct drm_display_mode *mode;
455 	struct s6d7aa0 *ctx;
456 
457 	ctx = container_of(panel, struct s6d7aa0, panel);
458 	if (!ctx)
459 		return -EINVAL;
460 
461 	mode = drm_mode_duplicate(connector->dev, ctx->desc->drm_mode);
462 	if (!mode)
463 		return -ENOMEM;
464 
465 	drm_mode_set_name(mode);
466 
467 	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
468 	connector->display_info.width_mm = mode->width_mm;
469 	connector->display_info.height_mm = mode->height_mm;
470 	connector->display_info.bus_flags = ctx->desc->bus_flags;
471 	drm_mode_probed_add(connector, mode);
472 
473 	return 1;
474 }
475 
476 static const struct drm_panel_funcs s6d7aa0_panel_funcs = {
477 	.disable = s6d7aa0_disable,
478 	.prepare = s6d7aa0_prepare,
479 	.unprepare = s6d7aa0_unprepare,
480 	.get_modes = s6d7aa0_get_modes,
481 };
482 
483 static int s6d7aa0_probe(struct mipi_dsi_device *dsi)
484 {
485 	struct device *dev = &dsi->dev;
486 	struct s6d7aa0 *ctx;
487 	int ret;
488 
489 	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
490 	if (!ctx)
491 		return -ENOMEM;
492 
493 	ctx->desc = of_device_get_match_data(dev);
494 	if (!ctx->desc)
495 		return -ENODEV;
496 
497 	ctx->supplies[0].supply = "power";
498 	ctx->supplies[1].supply = "vmipi";
499 	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
500 					      ctx->supplies);
501 	if (ret < 0)
502 		return dev_err_probe(dev, ret, "Failed to get regulators\n");
503 
504 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
505 	if (IS_ERR(ctx->reset_gpio))
506 		return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
507 				     "Failed to get reset-gpios\n");
508 
509 	ctx->dsi = dsi;
510 	mipi_dsi_set_drvdata(dsi, ctx);
511 
512 	dsi->lanes = 4;
513 	dsi->format = MIPI_DSI_FMT_RGB888;
514 	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST
515 		| ctx->desc->mode_flags;
516 
517 	drm_panel_init(&ctx->panel, dev, &s6d7aa0_panel_funcs,
518 		       DRM_MODE_CONNECTOR_DSI);
519 	ctx->panel.prepare_prev_first = true;
520 
521 	ret = drm_panel_of_backlight(&ctx->panel);
522 	if (ret)
523 		return dev_err_probe(dev, ret, "Failed to get backlight\n");
524 
525 	/* Use DSI-based backlight as fallback if available */
526 	if (ctx->desc->has_backlight && !ctx->panel.backlight) {
527 		ctx->panel.backlight = s6d7aa0_create_backlight(dsi);
528 		if (IS_ERR(ctx->panel.backlight))
529 			return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight),
530 					     "Failed to create backlight\n");
531 	}
532 
533 	drm_panel_add(&ctx->panel);
534 
535 	ret = mipi_dsi_attach(dsi);
536 	if (ret < 0) {
537 		dev_err(dev, "Failed to attach to DSI host: %d\n", ret);
538 		drm_panel_remove(&ctx->panel);
539 		return ret;
540 	}
541 
542 	return 0;
543 }
544 
545 static void s6d7aa0_remove(struct mipi_dsi_device *dsi)
546 {
547 	struct s6d7aa0 *ctx = mipi_dsi_get_drvdata(dsi);
548 	int ret;
549 
550 	ret = mipi_dsi_detach(dsi);
551 	if (ret < 0)
552 		dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);
553 
554 	drm_panel_remove(&ctx->panel);
555 }
556 
557 static const struct of_device_id s6d7aa0_of_match[] = {
558 	{
559 		.compatible = "samsung,lsl080al02",
560 		.data = &s6d7aa0_lsl080al02_desc
561 	},
562 	{
563 		.compatible = "samsung,lsl080al03",
564 		.data = &s6d7aa0_lsl080al03_desc
565 	},
566 	{
567 		.compatible = "samsung,ltl101at01",
568 		.data = &s6d7aa0_ltl101at01_desc
569 	},
570 	{ /* sentinel */ }
571 };
572 MODULE_DEVICE_TABLE(of, s6d7aa0_of_match);
573 
574 static struct mipi_dsi_driver s6d7aa0_driver = {
575 	.probe = s6d7aa0_probe,
576 	.remove = s6d7aa0_remove,
577 	.driver = {
578 		.name = "panel-samsung-s6d7aa0",
579 		.of_match_table = s6d7aa0_of_match,
580 	},
581 };
582 module_mipi_dsi_driver(s6d7aa0_driver);
583 
584 MODULE_AUTHOR("Artur Weber <aweber.kernel@gmail.com>");
585 MODULE_DESCRIPTION("Samsung S6D7AA0 MIPI-DSI LCD controller driver");
586 MODULE_LICENSE("GPL");
587