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 
14 #include <linux/backlight.h>
15 #include <linux/delay.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/module.h>
18 #include <linux/regulator/consumer.h>
19 #include <linux/spi/spi.h>
20 
21 #include <video/mipi_display.h>
22 
23 /* Manufacturer Command Set */
24 #define MCS_ELVSS_ON                0xb1
25 #define MCS_MIECTL1                0xc0
26 #define MCS_BCMODE                              0xc1
27 #define MCS_DISCTL   0xf2
28 #define MCS_SRCCTL           0xf6
29 #define MCS_IFCTL                       0xf7
30 #define MCS_PANELCTL         0xF8
31 #define MCS_PGAMMACTL                   0xfa
32 
33 #define NUM_GAMMA_LEVELS             11
34 #define GAMMA_TABLE_COUNT           23
35 
36 #define DATA_MASK                                       0x100
37 
38 #define MAX_BRIGHTNESS              (NUM_GAMMA_LEVELS - 1)
39 
40 /* array of gamma tables for gamma value 2.2 */
41 static u8 const s6e63m0_gamma_22[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {
42 	{ MCS_PGAMMACTL, 0x00,
43 	  0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
44 	  0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
45 	  0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51 },
46 	{ MCS_PGAMMACTL, 0x00,
47 	  0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
48 	  0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
49 	  0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82 },
50 	{ MCS_PGAMMACTL, 0x00,
51 	  0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
52 	  0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
53 	  0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D },
54 	{ MCS_PGAMMACTL, 0x00,
55 	  0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
56 	  0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
57 	  0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E },
58 	{ MCS_PGAMMACTL, 0x00,
59 	  0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
60 	  0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
61 	  0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB },
62 	{ MCS_PGAMMACTL, 0x00,
63 	  0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
64 	  0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
65 	  0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA },
66 	{ MCS_PGAMMACTL, 0x00,
67 	  0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
68 	  0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
69 	  0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2 },
70 	{ MCS_PGAMMACTL, 0x00,
71 	  0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
72 	  0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
73 	  0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA },
74 	{ MCS_PGAMMACTL, 0x00,
75 	  0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
76 	  0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
77 	  0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1 },
78 	{ MCS_PGAMMACTL, 0x00,
79 	  0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
80 	  0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
81 	  0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6 },
82 	{ MCS_PGAMMACTL, 0x00,
83 	  0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
84 	  0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
85 	  0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb },
86 };
87 
88 struct s6e63m0 {
89 	struct device *dev;
90 	struct drm_panel panel;
91 	struct backlight_device *bl_dev;
92 
93 	struct regulator_bulk_data supplies[2];
94 	struct gpio_desc *reset_gpio;
95 
96 	bool prepared;
97 	bool enabled;
98 
99 	/*
100 	 * This field is tested by functions directly accessing bus before
101 	 * transfer, transfer is skipped if it is set. In case of transfer
102 	 * failure or unexpected response the field is set to error value.
103 	 * Such construct allows to eliminate many checks in higher level
104 	 * functions.
105 	 */
106 	int error;
107 };
108 
109 static const struct drm_display_mode default_mode = {
110 	.clock		= 25628,
111 	.hdisplay	= 480,
112 	.hsync_start	= 480 + 16,
113 	.hsync_end	= 480 + 16 + 2,
114 	.htotal		= 480 + 16 + 2 + 16,
115 	.vdisplay	= 800,
116 	.vsync_start	= 800 + 28,
117 	.vsync_end	= 800 + 28 + 2,
118 	.vtotal		= 800 + 28 + 2 + 1,
119 	.width_mm	= 53,
120 	.height_mm	= 89,
121 	.flags		= DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
122 };
123 
124 static inline struct s6e63m0 *panel_to_s6e63m0(struct drm_panel *panel)
125 {
126 	return container_of(panel, struct s6e63m0, panel);
127 }
128 
129 static int s6e63m0_clear_error(struct s6e63m0 *ctx)
130 {
131 	int ret = ctx->error;
132 
133 	ctx->error = 0;
134 	return ret;
135 }
136 
137 static int s6e63m0_spi_write_word(struct s6e63m0 *ctx, u16 data)
138 {
139 	struct spi_device *spi = to_spi_device(ctx->dev);
140 	struct spi_transfer xfer = {
141 		.len	= 2,
142 		.tx_buf = &data,
143 	};
144 	struct spi_message msg;
145 
146 	spi_message_init(&msg);
147 	spi_message_add_tail(&xfer, &msg);
148 
149 	return spi_sync(spi, &msg);
150 }
151 
152 static void s6e63m0_dcs_write(struct s6e63m0 *ctx, const u8 *data, size_t len)
153 {
154 	int ret = 0;
155 
156 	if (ctx->error < 0 || len == 0)
157 		return;
158 
159 	dev_dbg(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);
160 	ret = s6e63m0_spi_write_word(ctx, *data);
161 
162 	while (!ret && --len) {
163 		++data;
164 		ret = s6e63m0_spi_write_word(ctx, *data | DATA_MASK);
165 	}
166 
167 	if (ret) {
168 		dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n", ret, (int)len, data);
169 		ctx->error = ret;
170 	}
171 
172 	usleep_range(300, 310);
173 }
174 
175 #define s6e63m0_dcs_write_seq_static(ctx, seq ...) \
176 	({ \
177 		static const u8 d[] = { seq }; \
178 		s6e63m0_dcs_write(ctx, d, ARRAY_SIZE(d)); \
179 	})
180 
181 static void s6e63m0_init(struct s6e63m0 *ctx)
182 {
183 	s6e63m0_dcs_write_seq_static(ctx, MCS_PANELCTL,
184 				     0x01, 0x27, 0x27, 0x07, 0x07, 0x54, 0x9f,
185 				     0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00);
186 
187 	s6e63m0_dcs_write_seq_static(ctx, MCS_DISCTL,
188 				     0x02, 0x03, 0x1c, 0x10, 0x10);
189 	s6e63m0_dcs_write_seq_static(ctx, MCS_IFCTL,
190 				     0x03, 0x00, 0x00);
191 
192 	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
193 				     0x00, 0x18, 0x08, 0x24, 0x64, 0x56, 0x33,
194 				     0xb6, 0xba, 0xa8, 0xac, 0xb1, 0x9d, 0xc1,
195 				     0xc1, 0xb7, 0x00, 0x9c, 0x00, 0x9f, 0x00,
196 				     0xd6);
197 	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL,
198 				     0x01);
199 
200 	s6e63m0_dcs_write_seq_static(ctx, MCS_SRCCTL,
201 				     0x00, 0x8c, 0x07);
202 	s6e63m0_dcs_write_seq_static(ctx, 0xb3,
203 				     0xc);
204 
205 	s6e63m0_dcs_write_seq_static(ctx, 0xb5,
206 				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
207 				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
208 				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
209 				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
210 				     0x21, 0x20, 0x1e, 0x1e);
211 
212 	s6e63m0_dcs_write_seq_static(ctx, 0xb6,
213 				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
214 				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
215 				     0x66, 0x66);
216 
217 	s6e63m0_dcs_write_seq_static(ctx, 0xb7,
218 				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
219 				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
220 				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
221 				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
222 				     0x21, 0x20, 0x1e, 0x1e, 0x00, 0x00, 0x11,
223 				     0x22, 0x33, 0x44, 0x44, 0x44, 0x55, 0x55,
224 				     0x66, 0x66, 0x66, 0x66, 0x66, 0x66);
225 
226 	s6e63m0_dcs_write_seq_static(ctx, 0xb9,
227 				     0x2c, 0x12, 0x0c, 0x0a, 0x10, 0x0e, 0x17,
228 				     0x13, 0x1f, 0x1a, 0x2a, 0x24, 0x1f, 0x1b,
229 				     0x1a, 0x17, 0x2b, 0x26, 0x22, 0x20, 0x3a,
230 				     0x34, 0x30, 0x2c, 0x29, 0x26, 0x25, 0x23,
231 				     0x21, 0x20, 0x1e, 0x1e);
232 
233 	s6e63m0_dcs_write_seq_static(ctx, 0xba,
234 				     0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x44,
235 				     0x44, 0x55, 0x55, 0x66, 0x66, 0x66, 0x66,
236 				     0x66, 0x66);
237 
238 	s6e63m0_dcs_write_seq_static(ctx, MCS_BCMODE,
239 				     0x4d, 0x96, 0x1d, 0x00, 0x00, 0x01, 0xdf,
240 				     0x00, 0x00, 0x03, 0x1f, 0x00, 0x00, 0x00,
241 				     0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06,
242 				     0x09, 0x0d, 0x0f, 0x12, 0x15, 0x18);
243 
244 	s6e63m0_dcs_write_seq_static(ctx, 0xb2,
245 				     0x10, 0x10, 0x0b, 0x05);
246 
247 	s6e63m0_dcs_write_seq_static(ctx, MCS_MIECTL1,
248 				     0x01);
249 
250 	s6e63m0_dcs_write_seq_static(ctx, MCS_ELVSS_ON,
251 				     0x0b);
252 
253 	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
254 }
255 
256 static int s6e63m0_power_on(struct s6e63m0 *ctx)
257 {
258 	int ret;
259 
260 	ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
261 	if (ret < 0)
262 		return ret;
263 
264 	msleep(25);
265 
266 	gpiod_set_value(ctx->reset_gpio, 0);
267 	msleep(120);
268 
269 	return 0;
270 }
271 
272 static int s6e63m0_power_off(struct s6e63m0 *ctx)
273 {
274 	int ret;
275 
276 	gpiod_set_value(ctx->reset_gpio, 1);
277 	msleep(120);
278 
279 	ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
280 	if (ret < 0)
281 		return ret;
282 
283 	return 0;
284 }
285 
286 static int s6e63m0_disable(struct drm_panel *panel)
287 {
288 	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
289 
290 	if (!ctx->enabled)
291 		return 0;
292 
293 	backlight_disable(ctx->bl_dev);
294 
295 	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
296 	msleep(200);
297 
298 	ctx->enabled = false;
299 
300 	return 0;
301 }
302 
303 static int s6e63m0_unprepare(struct drm_panel *panel)
304 {
305 	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
306 	int ret;
307 
308 	if (!ctx->prepared)
309 		return 0;
310 
311 	s6e63m0_clear_error(ctx);
312 
313 	ret = s6e63m0_power_off(ctx);
314 	if (ret < 0)
315 		return ret;
316 
317 	ctx->prepared = false;
318 
319 	return 0;
320 }
321 
322 static int s6e63m0_prepare(struct drm_panel *panel)
323 {
324 	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
325 	int ret;
326 
327 	if (ctx->prepared)
328 		return 0;
329 
330 	ret = s6e63m0_power_on(ctx);
331 	if (ret < 0)
332 		return ret;
333 
334 	s6e63m0_init(ctx);
335 
336 	ret = s6e63m0_clear_error(ctx);
337 
338 	if (ret < 0)
339 		s6e63m0_unprepare(panel);
340 
341 	ctx->prepared = true;
342 
343 	return ret;
344 }
345 
346 static int s6e63m0_enable(struct drm_panel *panel)
347 {
348 	struct s6e63m0 *ctx = panel_to_s6e63m0(panel);
349 
350 	if (ctx->enabled)
351 		return 0;
352 
353 	s6e63m0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
354 
355 	backlight_enable(ctx->bl_dev);
356 
357 	ctx->enabled = true;
358 
359 	return 0;
360 }
361 
362 static int s6e63m0_get_modes(struct drm_panel *panel,
363 			     struct drm_connector *connector)
364 {
365 	struct drm_display_mode *mode;
366 
367 	mode = drm_mode_duplicate(connector->dev, &default_mode);
368 	if (!mode) {
369 		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
370 			default_mode.hdisplay, default_mode.vdisplay,
371 			drm_mode_vrefresh(&default_mode));
372 		return -ENOMEM;
373 	}
374 
375 	drm_mode_set_name(mode);
376 
377 	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
378 	drm_mode_probed_add(connector, mode);
379 
380 	return 1;
381 }
382 
383 static const struct drm_panel_funcs s6e63m0_drm_funcs = {
384 	.disable	= s6e63m0_disable,
385 	.unprepare	= s6e63m0_unprepare,
386 	.prepare	= s6e63m0_prepare,
387 	.enable		= s6e63m0_enable,
388 	.get_modes	= s6e63m0_get_modes,
389 };
390 
391 static int s6e63m0_set_brightness(struct backlight_device *bd)
392 {
393 	struct s6e63m0 *ctx = bl_get_data(bd);
394 
395 	int brightness = bd->props.brightness;
396 
397 	/* disable and set new gamma */
398 	s6e63m0_dcs_write(ctx, s6e63m0_gamma_22[brightness],
399 			  ARRAY_SIZE(s6e63m0_gamma_22[brightness]));
400 
401 	/* update gamma table. */
402 	s6e63m0_dcs_write_seq_static(ctx, MCS_PGAMMACTL, 0x01);
403 
404 	return s6e63m0_clear_error(ctx);
405 }
406 
407 static const struct backlight_ops s6e63m0_backlight_ops = {
408 	.update_status	= s6e63m0_set_brightness,
409 };
410 
411 static int s6e63m0_backlight_register(struct s6e63m0 *ctx)
412 {
413 	struct backlight_properties props = {
414 		.type		= BACKLIGHT_RAW,
415 		.brightness	= MAX_BRIGHTNESS,
416 		.max_brightness = MAX_BRIGHTNESS
417 	};
418 	struct device *dev = ctx->dev;
419 	int ret = 0;
420 
421 	ctx->bl_dev = devm_backlight_device_register(dev, "panel", dev, ctx,
422 						     &s6e63m0_backlight_ops,
423 						     &props);
424 	if (IS_ERR(ctx->bl_dev)) {
425 		ret = PTR_ERR(ctx->bl_dev);
426 		dev_err(dev, "error registering backlight device (%d)\n", ret);
427 	}
428 
429 	return ret;
430 }
431 
432 static int s6e63m0_probe(struct spi_device *spi)
433 {
434 	struct device *dev = &spi->dev;
435 	struct s6e63m0 *ctx;
436 	int ret;
437 
438 	ctx = devm_kzalloc(dev, sizeof(struct s6e63m0), GFP_KERNEL);
439 	if (!ctx)
440 		return -ENOMEM;
441 
442 	spi_set_drvdata(spi, ctx);
443 
444 	ctx->dev = dev;
445 	ctx->enabled = false;
446 	ctx->prepared = false;
447 
448 	ctx->supplies[0].supply = "vdd3";
449 	ctx->supplies[1].supply = "vci";
450 	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
451 				      ctx->supplies);
452 	if (ret < 0) {
453 		dev_err(dev, "failed to get regulators: %d\n", ret);
454 		return ret;
455 	}
456 
457 	ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
458 	if (IS_ERR(ctx->reset_gpio)) {
459 		dev_err(dev, "cannot get reset-gpios %ld\n", PTR_ERR(ctx->reset_gpio));
460 		return PTR_ERR(ctx->reset_gpio);
461 	}
462 
463 	spi->bits_per_word = 9;
464 	spi->mode = SPI_MODE_3;
465 	ret = spi_setup(spi);
466 	if (ret < 0) {
467 		dev_err(dev, "spi setup failed.\n");
468 		return ret;
469 	}
470 
471 	drm_panel_init(&ctx->panel, dev, &s6e63m0_drm_funcs,
472 		       DRM_MODE_CONNECTOR_DPI);
473 
474 	ret = s6e63m0_backlight_register(ctx);
475 	if (ret < 0)
476 		return ret;
477 
478 	drm_panel_add(&ctx->panel);
479 
480 	return 0;
481 }
482 
483 static int s6e63m0_remove(struct spi_device *spi)
484 {
485 	struct s6e63m0 *ctx = spi_get_drvdata(spi);
486 
487 	drm_panel_remove(&ctx->panel);
488 
489 	return 0;
490 }
491 
492 static const struct of_device_id s6e63m0_of_match[] = {
493 	{ .compatible = "samsung,s6e63m0" },
494 	{ /* sentinel */ }
495 };
496 MODULE_DEVICE_TABLE(of, s6e63m0_of_match);
497 
498 static struct spi_driver s6e63m0_driver = {
499 	.probe			= s6e63m0_probe,
500 	.remove			= s6e63m0_remove,
501 	.driver			= {
502 		.name		= "panel-samsung-s6e63m0",
503 		.of_match_table = s6e63m0_of_match,
504 	},
505 };
506 module_spi_driver(s6e63m0_driver);
507 
508 MODULE_AUTHOR("Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>");
509 MODULE_DESCRIPTION("s6e63m0 LCD Driver");
510 MODULE_LICENSE("GPL v2");
511