1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
4  */
5 
6 #include <linux/backlight.h>
7 #include <linux/gpio/consumer.h>
8 #include <linux/module.h>
9 #include <linux/of.h>
10 #include <linux/of_device.h>
11 #include <linux/regulator/consumer.h>
12 
13 #include <drm/drmP.h>
14 #include <drm/drm_crtc.h>
15 #include <drm/drm_mipi_dsi.h>
16 #include <drm/drm_panel.h>
17 
18 #include <video/mipi_display.h>
19 
20 struct panel_init_cmd {
21 	size_t len;
22 	const char *data;
23 };
24 
25 #define _INIT_CMD(...) { \
26 	.len = sizeof((char[]){__VA_ARGS__}), \
27 	.data = (char[]){__VA_ARGS__} }
28 
29 struct panel_desc {
30 	const struct drm_display_mode *mode;
31 	unsigned int bpc;
32 	struct {
33 		unsigned int width;
34 		unsigned int height;
35 	} size;
36 
37 	unsigned long flags;
38 	enum mipi_dsi_pixel_format format;
39 	const struct panel_init_cmd *init_cmds;
40 	unsigned int lanes;
41 	const char * const *supply_names;
42 	unsigned int num_supplies;
43 	unsigned int sleep_mode_delay;
44 	unsigned int power_down_delay;
45 };
46 
47 struct innolux_panel {
48 	struct drm_panel base;
49 	struct mipi_dsi_device *link;
50 	const struct panel_desc *desc;
51 
52 	struct backlight_device *backlight;
53 	struct regulator_bulk_data *supplies;
54 	unsigned int num_supplies;
55 	struct gpio_desc *enable_gpio;
56 
57 	bool prepared;
58 	bool enabled;
59 };
60 
61 static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel)
62 {
63 	return container_of(panel, struct innolux_panel, base);
64 }
65 
66 static int innolux_panel_disable(struct drm_panel *panel)
67 {
68 	struct innolux_panel *innolux = to_innolux_panel(panel);
69 
70 	if (!innolux->enabled)
71 		return 0;
72 
73 	backlight_disable(innolux->backlight);
74 
75 	innolux->enabled = false;
76 
77 	return 0;
78 }
79 
80 static int innolux_panel_unprepare(struct drm_panel *panel)
81 {
82 	struct innolux_panel *innolux = to_innolux_panel(panel);
83 	int err;
84 
85 	if (!innolux->prepared)
86 		return 0;
87 
88 	err = mipi_dsi_dcs_set_display_off(innolux->link);
89 	if (err < 0)
90 		DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n",
91 			      err);
92 
93 	err = mipi_dsi_dcs_enter_sleep_mode(innolux->link);
94 	if (err < 0) {
95 		DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n",
96 			      err);
97 		return err;
98 	}
99 
100 	if (innolux->desc->sleep_mode_delay)
101 		msleep(innolux->desc->sleep_mode_delay);
102 
103 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
104 
105 	if (innolux->desc->power_down_delay)
106 		msleep(innolux->desc->power_down_delay);
107 
108 	err = regulator_bulk_disable(innolux->desc->num_supplies,
109 				     innolux->supplies);
110 	if (err < 0)
111 		return err;
112 
113 	innolux->prepared = false;
114 
115 	return 0;
116 }
117 
118 static int innolux_panel_prepare(struct drm_panel *panel)
119 {
120 	struct innolux_panel *innolux = to_innolux_panel(panel);
121 	int err;
122 
123 	if (innolux->prepared)
124 		return 0;
125 
126 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
127 
128 	err = regulator_bulk_enable(innolux->desc->num_supplies,
129 				    innolux->supplies);
130 	if (err < 0)
131 		return err;
132 
133 	/* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
134 	usleep_range(20000, 21000);
135 
136 	gpiod_set_value_cansleep(innolux->enable_gpio, 1);
137 
138 	/* p079zca: t4, p097pfg: t5 */
139 	usleep_range(20000, 21000);
140 
141 	if (innolux->desc->init_cmds) {
142 		const struct panel_init_cmd *cmds =
143 					innolux->desc->init_cmds;
144 		unsigned int i;
145 
146 		for (i = 0; cmds[i].len != 0; i++) {
147 			const struct panel_init_cmd *cmd = &cmds[i];
148 
149 			err = mipi_dsi_generic_write(innolux->link, cmd->data,
150 						     cmd->len);
151 			if (err < 0) {
152 				dev_err(panel->dev,
153 					"failed to write command %u\n", i);
154 				goto poweroff;
155 			}
156 
157 			/*
158 			 * Included by random guessing, because without this
159 			 * (or at least, some delay), the panel sometimes
160 			 * didn't appear to pick up the command sequence.
161 			 */
162 			err = mipi_dsi_dcs_nop(innolux->link);
163 			if (err < 0) {
164 				dev_err(panel->dev,
165 					"failed to send DCS nop: %d\n", err);
166 				goto poweroff;
167 			}
168 		}
169 	}
170 
171 	err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
172 	if (err < 0) {
173 		DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n",
174 			      err);
175 		goto poweroff;
176 	}
177 
178 	/* T6: 120ms - 1000ms*/
179 	msleep(120);
180 
181 	err = mipi_dsi_dcs_set_display_on(innolux->link);
182 	if (err < 0) {
183 		DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n",
184 			      err);
185 		goto poweroff;
186 	}
187 
188 	/* T7: 5ms */
189 	usleep_range(5000, 6000);
190 
191 	innolux->prepared = true;
192 
193 	return 0;
194 
195 poweroff:
196 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
197 	regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
198 
199 	return err;
200 }
201 
202 static int innolux_panel_enable(struct drm_panel *panel)
203 {
204 	struct innolux_panel *innolux = to_innolux_panel(panel);
205 	int ret;
206 
207 	if (innolux->enabled)
208 		return 0;
209 
210 	ret = backlight_enable(innolux->backlight);
211 	if (ret) {
212 		DRM_DEV_ERROR(panel->drm->dev,
213 			      "Failed to enable backlight %d\n", ret);
214 		return ret;
215 	}
216 
217 	innolux->enabled = true;
218 
219 	return 0;
220 }
221 
222 static const char * const innolux_p079zca_supply_names[] = {
223 	"power",
224 };
225 
226 static const struct drm_display_mode innolux_p079zca_mode = {
227 	.clock = 56900,
228 	.hdisplay = 768,
229 	.hsync_start = 768 + 40,
230 	.hsync_end = 768 + 40 + 40,
231 	.htotal = 768 + 40 + 40 + 40,
232 	.vdisplay = 1024,
233 	.vsync_start = 1024 + 20,
234 	.vsync_end = 1024 + 20 + 4,
235 	.vtotal = 1024 + 20 + 4 + 20,
236 	.vrefresh = 60,
237 };
238 
239 static const struct panel_desc innolux_p079zca_panel_desc = {
240 	.mode = &innolux_p079zca_mode,
241 	.bpc = 8,
242 	.size = {
243 		.width = 120,
244 		.height = 160,
245 	},
246 	.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
247 		 MIPI_DSI_MODE_LPM,
248 	.format = MIPI_DSI_FMT_RGB888,
249 	.lanes = 4,
250 	.supply_names = innolux_p079zca_supply_names,
251 	.num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
252 	.power_down_delay = 80, /* T8: 80ms - 1000ms */
253 };
254 
255 static const char * const innolux_p097pfg_supply_names[] = {
256 	"avdd",
257 	"avee",
258 };
259 
260 static const struct drm_display_mode innolux_p097pfg_mode = {
261 	.clock = 229000,
262 	.hdisplay = 1536,
263 	.hsync_start = 1536 + 100,
264 	.hsync_end = 1536 + 100 + 24,
265 	.htotal = 1536 + 100 + 24 + 100,
266 	.vdisplay = 2048,
267 	.vsync_start = 2048 + 100,
268 	.vsync_end = 2048 + 100 + 2,
269 	.vtotal = 2048 + 100 + 2 + 18,
270 	.vrefresh = 60,
271 };
272 
273 /*
274  * Display manufacturer failed to provide init sequencing according to
275  * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/
276  * so the init sequence stems from a register dump of a working panel.
277  */
278 static const struct panel_init_cmd innolux_p097pfg_init_cmds[] = {
279 	/* page 0 */
280 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
281 	_INIT_CMD(0xB1, 0xE8, 0x11),
282 	_INIT_CMD(0xB2, 0x25, 0x02),
283 	_INIT_CMD(0xB5, 0x08, 0x00),
284 	_INIT_CMD(0xBC, 0x0F, 0x00),
285 	_INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00),
286 	_INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14),
287 	_INIT_CMD(0x6F, 0x01),
288 	_INIT_CMD(0xC0, 0x03),
289 	_INIT_CMD(0x6F, 0x02),
290 	_INIT_CMD(0xC1, 0x0D),
291 	_INIT_CMD(0xD9, 0x01, 0x09, 0x70),
292 	_INIT_CMD(0xC5, 0x12, 0x21, 0x00),
293 	_INIT_CMD(0xBB, 0x93, 0x93),
294 
295 	/* page 1 */
296 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
297 	_INIT_CMD(0xB3, 0x3C, 0x3C),
298 	_INIT_CMD(0xB4, 0x0F, 0x0F),
299 	_INIT_CMD(0xB9, 0x45, 0x45),
300 	_INIT_CMD(0xBA, 0x14, 0x14),
301 	_INIT_CMD(0xCA, 0x02),
302 	_INIT_CMD(0xCE, 0x04),
303 	_INIT_CMD(0xC3, 0x9B, 0x9B),
304 	_INIT_CMD(0xD8, 0xC0, 0x03),
305 	_INIT_CMD(0xBC, 0x82, 0x01),
306 	_INIT_CMD(0xBD, 0x9E, 0x01),
307 
308 	/* page 2 */
309 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02),
310 	_INIT_CMD(0xB0, 0x82),
311 	_INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5,
312 		  0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40),
313 	_INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29,
314 		  0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0),
315 	_INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C,
316 		  0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC),
317 	_INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF),
318 	_INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5,
319 		  0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75),
320 	_INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D,
321 		  0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03),
322 	_INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94,
323 		  0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED),
324 	_INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF),
325 
326 	/* page 3 */
327 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
328 	_INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00),
329 	_INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00),
330 	_INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85),
331 	_INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80),
332 	_INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
333 		  0x40, 0x80),
334 	_INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C),
335 	_INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C),
336 	_INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
337 	_INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
338 	_INIT_CMD(0xC4, 0x00, 0x00),
339 	_INIT_CMD(0xEF, 0x41),
340 
341 	/* page 4 */
342 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
343 	_INIT_CMD(0xEC, 0x4C),
344 
345 	/* page 5 */
346 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05),
347 	_INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01),
348 	_INIT_CMD(0xB1, 0x30, 0x00),
349 	_INIT_CMD(0xB2, 0x02, 0x02, 0x00),
350 	_INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D),
351 	_INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57),
352 	_INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A),
353 	_INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56),
354 	_INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C),
355 	_INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00),
356 	_INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05),
357 	_INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00),
358 	_INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00),
359 	_INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00),
360 
361 	/* page 6 */
362 	_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06),
363 	_INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F),
364 	_INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12),
365 	_INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D),
366 	_INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
367 	_INIT_CMD(0xB4, 0x3D, 0x32),
368 	_INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F),
369 	_INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18),
370 	_INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D),
371 	_INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
372 	_INIT_CMD(0xB9, 0x3D, 0x32),
373 	_INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F),
374 	_INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17),
375 	_INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D),
376 	_INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
377 	_INIT_CMD(0xC4, 0x3D, 0x32),
378 	_INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F),
379 	_INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11),
380 	_INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D),
381 	_INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
382 	_INIT_CMD(0xC9, 0x3D, 0x32),
383 
384 	{},
385 };
386 
387 static const struct panel_desc innolux_p097pfg_panel_desc = {
388 	.mode = &innolux_p097pfg_mode,
389 	.bpc = 8,
390 	.size = {
391 		.width = 147,
392 		.height = 196,
393 	},
394 	.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
395 		 MIPI_DSI_MODE_LPM,
396 	.format = MIPI_DSI_FMT_RGB888,
397 	.init_cmds = innolux_p097pfg_init_cmds,
398 	.lanes = 4,
399 	.supply_names = innolux_p097pfg_supply_names,
400 	.num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names),
401 	.sleep_mode_delay = 100, /* T15 */
402 };
403 
404 static int innolux_panel_get_modes(struct drm_panel *panel)
405 {
406 	struct innolux_panel *innolux = to_innolux_panel(panel);
407 	const struct drm_display_mode *m = innolux->desc->mode;
408 	struct drm_display_mode *mode;
409 
410 	mode = drm_mode_duplicate(panel->drm, m);
411 	if (!mode) {
412 		DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n",
413 			      m->hdisplay, m->vdisplay, m->vrefresh);
414 		return -ENOMEM;
415 	}
416 
417 	drm_mode_set_name(mode);
418 
419 	drm_mode_probed_add(panel->connector, mode);
420 
421 	panel->connector->display_info.width_mm =
422 			innolux->desc->size.width;
423 	panel->connector->display_info.height_mm =
424 			innolux->desc->size.height;
425 	panel->connector->display_info.bpc = innolux->desc->bpc;
426 
427 	return 1;
428 }
429 
430 static const struct drm_panel_funcs innolux_panel_funcs = {
431 	.disable = innolux_panel_disable,
432 	.unprepare = innolux_panel_unprepare,
433 	.prepare = innolux_panel_prepare,
434 	.enable = innolux_panel_enable,
435 	.get_modes = innolux_panel_get_modes,
436 };
437 
438 static const struct of_device_id innolux_of_match[] = {
439 	{ .compatible = "innolux,p079zca",
440 	  .data = &innolux_p079zca_panel_desc
441 	},
442 	{ .compatible = "innolux,p097pfg",
443 	  .data = &innolux_p097pfg_panel_desc
444 	},
445 	{ }
446 };
447 MODULE_DEVICE_TABLE(of, innolux_of_match);
448 
449 static int innolux_panel_add(struct mipi_dsi_device *dsi,
450 			     const struct panel_desc *desc)
451 {
452 	struct innolux_panel *innolux;
453 	struct device *dev = &dsi->dev;
454 	int err, i;
455 
456 	innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
457 	if (!innolux)
458 		return -ENOMEM;
459 
460 	innolux->desc = desc;
461 
462 	innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
463 					 sizeof(*innolux->supplies),
464 					 GFP_KERNEL);
465 	if (!innolux->supplies)
466 		return -ENOMEM;
467 
468 	for (i = 0; i < desc->num_supplies; i++)
469 		innolux->supplies[i].supply = desc->supply_names[i];
470 
471 	err = devm_regulator_bulk_get(dev, desc->num_supplies,
472 				      innolux->supplies);
473 	if (err < 0)
474 		return err;
475 
476 	innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
477 						       GPIOD_OUT_HIGH);
478 	if (IS_ERR(innolux->enable_gpio)) {
479 		err = PTR_ERR(innolux->enable_gpio);
480 		dev_dbg(dev, "failed to get enable gpio: %d\n", err);
481 		innolux->enable_gpio = NULL;
482 	}
483 
484 	innolux->backlight = devm_of_find_backlight(dev);
485 	if (IS_ERR(innolux->backlight))
486 		return PTR_ERR(innolux->backlight);
487 
488 	drm_panel_init(&innolux->base);
489 	innolux->base.funcs = &innolux_panel_funcs;
490 	innolux->base.dev = dev;
491 
492 	err = drm_panel_add(&innolux->base);
493 	if (err < 0)
494 		return err;
495 
496 	mipi_dsi_set_drvdata(dsi, innolux);
497 	innolux->link = dsi;
498 
499 	return 0;
500 }
501 
502 static void innolux_panel_del(struct innolux_panel *innolux)
503 {
504 	drm_panel_remove(&innolux->base);
505 }
506 
507 static int innolux_panel_probe(struct mipi_dsi_device *dsi)
508 {
509 	const struct panel_desc *desc;
510 	int err;
511 
512 	desc = of_device_get_match_data(&dsi->dev);
513 	dsi->mode_flags = desc->flags;
514 	dsi->format = desc->format;
515 	dsi->lanes = desc->lanes;
516 
517 	err = innolux_panel_add(dsi, desc);
518 	if (err < 0)
519 		return err;
520 
521 	return mipi_dsi_attach(dsi);
522 }
523 
524 static int innolux_panel_remove(struct mipi_dsi_device *dsi)
525 {
526 	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
527 	int err;
528 
529 	err = innolux_panel_unprepare(&innolux->base);
530 	if (err < 0)
531 		DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n",
532 			      err);
533 
534 	err = innolux_panel_disable(&innolux->base);
535 	if (err < 0)
536 		DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err);
537 
538 	err = mipi_dsi_detach(dsi);
539 	if (err < 0)
540 		DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n",
541 			      err);
542 
543 	innolux_panel_del(innolux);
544 
545 	return 0;
546 }
547 
548 static void innolux_panel_shutdown(struct mipi_dsi_device *dsi)
549 {
550 	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
551 
552 	innolux_panel_unprepare(&innolux->base);
553 	innolux_panel_disable(&innolux->base);
554 }
555 
556 static struct mipi_dsi_driver innolux_panel_driver = {
557 	.driver = {
558 		.name = "panel-innolux-p079zca",
559 		.of_match_table = innolux_of_match,
560 	},
561 	.probe = innolux_panel_probe,
562 	.remove = innolux_panel_remove,
563 	.shutdown = innolux_panel_shutdown,
564 };
565 module_mipi_dsi_driver(innolux_panel_driver);
566 
567 MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
568 MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
569 MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
570 MODULE_LICENSE("GPL v2");
571