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