panel-innolux-p079zca.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
  4. */
  5. #include <linux/delay.h>
  6. #include <linux/gpio/consumer.h>
  7. #include <linux/module.h>
  8. #include <linux/of.h>
  9. #include <linux/regulator/consumer.h>
  10. #include <video/mipi_display.h>
  11. #include <drm/drm_crtc.h>
  12. #include <drm/drm_device.h>
  13. #include <drm/drm_mipi_dsi.h>
  14. #include <drm/drm_modes.h>
  15. #include <drm/drm_panel.h>
  16. struct innolux_panel;
  17. struct panel_desc {
  18. const struct drm_display_mode *mode;
  19. unsigned int bpc;
  20. struct {
  21. unsigned int width;
  22. unsigned int height;
  23. } size;
  24. unsigned long flags;
  25. enum mipi_dsi_pixel_format format;
  26. int (*init)(struct innolux_panel *innolux);
  27. unsigned int lanes;
  28. const char * const *supply_names;
  29. unsigned int num_supplies;
  30. unsigned int sleep_mode_delay;
  31. unsigned int power_down_delay;
  32. };
  33. struct innolux_panel {
  34. struct drm_panel base;
  35. struct mipi_dsi_device *link;
  36. const struct panel_desc *desc;
  37. struct regulator_bulk_data *supplies;
  38. struct gpio_desc *enable_gpio;
  39. };
  40. static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel)
  41. {
  42. return container_of(panel, struct innolux_panel, base);
  43. }
  44. static int innolux_panel_unprepare(struct drm_panel *panel)
  45. {
  46. struct innolux_panel *innolux = to_innolux_panel(panel);
  47. int err;
  48. err = mipi_dsi_dcs_set_display_off(innolux->link);
  49. if (err < 0)
  50. dev_err(panel->dev, "failed to set display off: %d\n", err);
  51. err = mipi_dsi_dcs_enter_sleep_mode(innolux->link);
  52. if (err < 0) {
  53. dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
  54. return err;
  55. }
  56. if (innolux->desc->sleep_mode_delay)
  57. msleep(innolux->desc->sleep_mode_delay);
  58. gpiod_set_value_cansleep(innolux->enable_gpio, 0);
  59. if (innolux->desc->power_down_delay)
  60. msleep(innolux->desc->power_down_delay);
  61. err = regulator_bulk_disable(innolux->desc->num_supplies,
  62. innolux->supplies);
  63. if (err < 0)
  64. return err;
  65. return 0;
  66. }
  67. static int innolux_panel_prepare(struct drm_panel *panel)
  68. {
  69. struct innolux_panel *innolux = to_innolux_panel(panel);
  70. int err;
  71. gpiod_set_value_cansleep(innolux->enable_gpio, 0);
  72. err = regulator_bulk_enable(innolux->desc->num_supplies,
  73. innolux->supplies);
  74. if (err < 0)
  75. return err;
  76. /* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
  77. usleep_range(20000, 21000);
  78. gpiod_set_value_cansleep(innolux->enable_gpio, 1);
  79. /* p079zca: t4, p097pfg: t5 */
  80. usleep_range(20000, 21000);
  81. if (innolux->desc->init) {
  82. err = innolux->desc->init(innolux);
  83. if (err < 0)
  84. goto poweroff;
  85. }
  86. err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
  87. if (err < 0) {
  88. dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
  89. goto poweroff;
  90. }
  91. /* T6: 120ms - 1000ms*/
  92. msleep(120);
  93. err = mipi_dsi_dcs_set_display_on(innolux->link);
  94. if (err < 0) {
  95. dev_err(panel->dev, "failed to set display on: %d\n", err);
  96. goto poweroff;
  97. }
  98. /* T7: 5ms */
  99. usleep_range(5000, 6000);
  100. return 0;
  101. poweroff:
  102. gpiod_set_value_cansleep(innolux->enable_gpio, 0);
  103. regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
  104. return err;
  105. }
  106. static const char * const innolux_p079zca_supply_names[] = {
  107. "power",
  108. };
  109. static const struct drm_display_mode innolux_p079zca_mode = {
  110. .clock = 56900,
  111. .hdisplay = 768,
  112. .hsync_start = 768 + 40,
  113. .hsync_end = 768 + 40 + 40,
  114. .htotal = 768 + 40 + 40 + 40,
  115. .vdisplay = 1024,
  116. .vsync_start = 1024 + 20,
  117. .vsync_end = 1024 + 20 + 4,
  118. .vtotal = 1024 + 20 + 4 + 20,
  119. };
  120. static const struct panel_desc innolux_p079zca_panel_desc = {
  121. .mode = &innolux_p079zca_mode,
  122. .bpc = 8,
  123. .size = {
  124. .width = 120,
  125. .height = 160,
  126. },
  127. .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
  128. MIPI_DSI_MODE_LPM,
  129. .format = MIPI_DSI_FMT_RGB888,
  130. .lanes = 4,
  131. .supply_names = innolux_p079zca_supply_names,
  132. .num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
  133. .power_down_delay = 80, /* T8: 80ms - 1000ms */
  134. };
  135. static const char * const innolux_p097pfg_supply_names[] = {
  136. "avdd",
  137. "avee",
  138. };
  139. static const struct drm_display_mode innolux_p097pfg_mode = {
  140. .clock = 229000,
  141. .hdisplay = 1536,
  142. .hsync_start = 1536 + 100,
  143. .hsync_end = 1536 + 100 + 24,
  144. .htotal = 1536 + 100 + 24 + 100,
  145. .vdisplay = 2048,
  146. .vsync_start = 2048 + 100,
  147. .vsync_end = 2048 + 100 + 2,
  148. .vtotal = 2048 + 100 + 2 + 18,
  149. };
  150. static void innolux_panel_write_multi(struct mipi_dsi_multi_context *ctx,
  151. const void *payload, size_t size)
  152. {
  153. mipi_dsi_generic_write_multi(ctx, payload, size);
  154. /*
  155. * Included by random guessing, because without this
  156. * (or at least, some delay), the panel sometimes
  157. * didn't appear to pick up the command sequence.
  158. */
  159. mipi_dsi_dcs_nop_multi(ctx);
  160. }
  161. #define innolux_panel_init_cmd_multi(ctx, seq...) \
  162. do { \
  163. static const u8 d[] = { seq }; \
  164. innolux_panel_write_multi(ctx, d, ARRAY_SIZE(d)); \
  165. } while (0)
  166. #define innolux_panel_switch_page(ctx, page) \
  167. innolux_panel_init_cmd_multi(ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, (page))
  168. /*
  169. * Display manufacturer failed to provide init sequencing according to
  170. * https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/
  171. * so the init sequence stems from a register dump of a working panel.
  172. */
  173. static int innolux_p097pfg_init(struct innolux_panel *innolux)
  174. {
  175. struct mipi_dsi_multi_context ctx = { .dsi = innolux->link };
  176. innolux_panel_switch_page(&ctx, 0x00);
  177. innolux_panel_init_cmd_multi(&ctx, 0xb1, 0xe8, 0x11);
  178. innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x25, 0x02);
  179. innolux_panel_init_cmd_multi(&ctx, 0xb5, 0x08, 0x00);
  180. innolux_panel_init_cmd_multi(&ctx, 0xbc, 0x0f, 0x00);
  181. innolux_panel_init_cmd_multi(&ctx, 0xb8, 0x03, 0x06, 0x00, 0x00);
  182. innolux_panel_init_cmd_multi(&ctx, 0xbd, 0x01, 0x90, 0x14, 0x14);
  183. innolux_panel_init_cmd_multi(&ctx, 0x6f, 0x01);
  184. innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x03);
  185. innolux_panel_init_cmd_multi(&ctx, 0x6f, 0x02);
  186. innolux_panel_init_cmd_multi(&ctx, 0xc1, 0x0d);
  187. innolux_panel_init_cmd_multi(&ctx, 0xd9, 0x01, 0x09, 0x70);
  188. innolux_panel_init_cmd_multi(&ctx, 0xc5, 0x12, 0x21, 0x00);
  189. innolux_panel_init_cmd_multi(&ctx, 0xbb, 0x93, 0x93);
  190. innolux_panel_switch_page(&ctx, 0x01);
  191. innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x3c, 0x3c);
  192. innolux_panel_init_cmd_multi(&ctx, 0xb4, 0x0f, 0x0f);
  193. innolux_panel_init_cmd_multi(&ctx, 0xb9, 0x45, 0x45);
  194. innolux_panel_init_cmd_multi(&ctx, 0xba, 0x14, 0x14);
  195. innolux_panel_init_cmd_multi(&ctx, 0xca, 0x02);
  196. innolux_panel_init_cmd_multi(&ctx, 0xce, 0x04);
  197. innolux_panel_init_cmd_multi(&ctx, 0xc3, 0x9b, 0x9b);
  198. innolux_panel_init_cmd_multi(&ctx, 0xd8, 0xc0, 0x03);
  199. innolux_panel_init_cmd_multi(&ctx, 0xbc, 0x82, 0x01);
  200. innolux_panel_init_cmd_multi(&ctx, 0xbd, 0x9e, 0x01);
  201. innolux_panel_switch_page(&ctx, 0x02);
  202. innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x82);
  203. innolux_panel_init_cmd_multi(&ctx, 0xd1, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x82, 0x00, 0xa5,
  204. 0x00, 0xc1, 0x00, 0xea, 0x01, 0x0d, 0x01, 0x40);
  205. innolux_panel_init_cmd_multi(&ctx, 0xd2, 0x01, 0x6a, 0x01, 0xa8, 0x01, 0xdc, 0x02, 0x29,
  206. 0x02, 0x67, 0x02, 0x68, 0x02, 0xa8, 0x02, 0xf0);
  207. innolux_panel_init_cmd_multi(&ctx, 0xd3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8c,
  208. 0x03, 0xa6, 0x03, 0xc7, 0x03, 0xde, 0x03, 0xec);
  209. innolux_panel_init_cmd_multi(&ctx, 0xd4, 0x03, 0xff, 0x03, 0xff);
  210. innolux_panel_init_cmd_multi(&ctx, 0xe0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xc5, 0x00, 0xe5,
  211. 0x00, 0xff, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75);
  212. innolux_panel_init_cmd_multi(&ctx, 0xe1, 0x01, 0x9c, 0x01, 0xd5, 0x02, 0x05, 0x02, 0x4d,
  213. 0x02, 0x86, 0x02, 0x87, 0x02, 0xc3, 0x03, 0x03);
  214. innolux_panel_init_cmd_multi(&ctx, 0xe2, 0x03, 0x2a, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94,
  215. 0x03, 0xac, 0x03, 0xcb, 0x03, 0xe0, 0x03, 0xed);
  216. innolux_panel_init_cmd_multi(&ctx, 0xe3, 0x03, 0xff, 0x03, 0xff);
  217. innolux_panel_switch_page(&ctx, 0x03);
  218. innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x00, 0x00, 0x00, 0x00);
  219. innolux_panel_init_cmd_multi(&ctx, 0xb1, 0x00, 0x00, 0x00, 0x00);
  220. innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85);
  221. innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x10, 0x07, 0xfc, 0x04, 0x01, 0x40, 0x80);
  222. innolux_panel_init_cmd_multi(&ctx, 0xb6, 0xf0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
  223. 0x40, 0x80);
  224. innolux_panel_init_cmd_multi(&ctx, 0xba, 0xc5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8c);
  225. innolux_panel_init_cmd_multi(&ctx, 0xbb, 0xc5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8c);
  226. innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x80, 0x80);
  227. innolux_panel_init_cmd_multi(&ctx, 0xc1, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x80, 0x80);
  228. innolux_panel_init_cmd_multi(&ctx, 0xc4, 0x00, 0x00);
  229. innolux_panel_init_cmd_multi(&ctx, 0xef, 0x41);
  230. innolux_panel_switch_page(&ctx, 0x04);
  231. innolux_panel_init_cmd_multi(&ctx, 0xec, 0x4c);
  232. innolux_panel_switch_page(&ctx, 0x05);
  233. innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x13, 0x03, 0x03, 0x01);
  234. innolux_panel_init_cmd_multi(&ctx, 0xb1, 0x30, 0x00);
  235. innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x02, 0x02, 0x00);
  236. innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x82, 0x23, 0x82, 0x9d);
  237. innolux_panel_init_cmd_multi(&ctx, 0xb4, 0xc5, 0x75, 0x24, 0x57);
  238. innolux_panel_init_cmd_multi(&ctx, 0xb5, 0x00, 0xd4, 0x72, 0x11, 0x11, 0xab, 0x0a);
  239. innolux_panel_init_cmd_multi(&ctx, 0xb6, 0x00, 0x00, 0xd5, 0x72, 0x24, 0x56);
  240. innolux_panel_init_cmd_multi(&ctx, 0xb7, 0x5c, 0xdc, 0x5c, 0x5c);
  241. innolux_panel_init_cmd_multi(&ctx, 0xb9, 0x0c, 0x00, 0x00, 0x01, 0x00);
  242. innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x75, 0x11, 0x11, 0x54, 0x05);
  243. innolux_panel_init_cmd_multi(&ctx, 0xc6, 0x00, 0x00, 0x00, 0x00);
  244. innolux_panel_init_cmd_multi(&ctx, 0xd0, 0x00, 0x48, 0x08, 0x00, 0x00);
  245. innolux_panel_init_cmd_multi(&ctx, 0xd1, 0x00, 0x48, 0x09, 0x00, 0x00);
  246. innolux_panel_switch_page(&ctx, 0x06);
  247. innolux_panel_init_cmd_multi(&ctx, 0xb0, 0x02, 0x32, 0x32, 0x08, 0x2f);
  248. innolux_panel_init_cmd_multi(&ctx, 0xb1, 0x2e, 0x15, 0x14, 0x13, 0x12);
  249. innolux_panel_init_cmd_multi(&ctx, 0xb2, 0x11, 0x10, 0x00, 0x3d, 0x3d);
  250. innolux_panel_init_cmd_multi(&ctx, 0xb3, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d);
  251. innolux_panel_init_cmd_multi(&ctx, 0xb4, 0x3d, 0x32);
  252. innolux_panel_init_cmd_multi(&ctx, 0xb5, 0x03, 0x32, 0x32, 0x09, 0x2f);
  253. innolux_panel_init_cmd_multi(&ctx, 0xb6, 0x2e, 0x1b, 0x1a, 0x19, 0x18);
  254. innolux_panel_init_cmd_multi(&ctx, 0xb7, 0x17, 0x16, 0x01, 0x3d, 0x3d);
  255. innolux_panel_init_cmd_multi(&ctx, 0xb8, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d);
  256. innolux_panel_init_cmd_multi(&ctx, 0xb9, 0x3d, 0x32);
  257. innolux_panel_init_cmd_multi(&ctx, 0xc0, 0x01, 0x32, 0x32, 0x09, 0x2f);
  258. innolux_panel_init_cmd_multi(&ctx, 0xc1, 0x2e, 0x1a, 0x1b, 0x16, 0x17);
  259. innolux_panel_init_cmd_multi(&ctx, 0xc2, 0x18, 0x19, 0x03, 0x3d, 0x3d);
  260. innolux_panel_init_cmd_multi(&ctx, 0xc3, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d);
  261. innolux_panel_init_cmd_multi(&ctx, 0xc4, 0x3d, 0x32);
  262. innolux_panel_init_cmd_multi(&ctx, 0xc5, 0x00, 0x32, 0x32, 0x08, 0x2f);
  263. innolux_panel_init_cmd_multi(&ctx, 0xc6, 0x2e, 0x14, 0x15, 0x10, 0x11);
  264. innolux_panel_init_cmd_multi(&ctx, 0xc7, 0x12, 0x13, 0x02, 0x3d, 0x3d);
  265. innolux_panel_init_cmd_multi(&ctx, 0xc8, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d);
  266. innolux_panel_init_cmd_multi(&ctx, 0xc9, 0x3d, 0x32);
  267. return ctx.accum_err;
  268. }
  269. static const struct panel_desc innolux_p097pfg_panel_desc = {
  270. .mode = &innolux_p097pfg_mode,
  271. .bpc = 8,
  272. .size = {
  273. .width = 147,
  274. .height = 196,
  275. },
  276. .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
  277. MIPI_DSI_MODE_LPM,
  278. .format = MIPI_DSI_FMT_RGB888,
  279. .init = innolux_p097pfg_init,
  280. .lanes = 4,
  281. .supply_names = innolux_p097pfg_supply_names,
  282. .num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names),
  283. .sleep_mode_delay = 100, /* T15 */
  284. };
  285. static int innolux_panel_get_modes(struct drm_panel *panel,
  286. struct drm_connector *connector)
  287. {
  288. struct innolux_panel *innolux = to_innolux_panel(panel);
  289. const struct drm_display_mode *m = innolux->desc->mode;
  290. struct drm_display_mode *mode;
  291. mode = drm_mode_duplicate(connector->dev, m);
  292. if (!mode) {
  293. dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
  294. m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
  295. return -ENOMEM;
  296. }
  297. drm_mode_set_name(mode);
  298. drm_mode_probed_add(connector, mode);
  299. connector->display_info.width_mm = innolux->desc->size.width;
  300. connector->display_info.height_mm = innolux->desc->size.height;
  301. connector->display_info.bpc = innolux->desc->bpc;
  302. return 1;
  303. }
  304. static const struct drm_panel_funcs innolux_panel_funcs = {
  305. .unprepare = innolux_panel_unprepare,
  306. .prepare = innolux_panel_prepare,
  307. .get_modes = innolux_panel_get_modes,
  308. };
  309. static const struct of_device_id innolux_of_match[] = {
  310. { .compatible = "innolux,p079zca",
  311. .data = &innolux_p079zca_panel_desc
  312. },
  313. { .compatible = "innolux,p097pfg",
  314. .data = &innolux_p097pfg_panel_desc
  315. },
  316. { }
  317. };
  318. MODULE_DEVICE_TABLE(of, innolux_of_match);
  319. static int innolux_panel_add(struct mipi_dsi_device *dsi,
  320. const struct panel_desc *desc)
  321. {
  322. struct innolux_panel *innolux;
  323. struct device *dev = &dsi->dev;
  324. int err, i;
  325. innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
  326. if (!innolux)
  327. return -ENOMEM;
  328. innolux->desc = desc;
  329. innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
  330. sizeof(*innolux->supplies),
  331. GFP_KERNEL);
  332. if (!innolux->supplies)
  333. return -ENOMEM;
  334. for (i = 0; i < desc->num_supplies; i++)
  335. innolux->supplies[i].supply = desc->supply_names[i];
  336. err = devm_regulator_bulk_get(dev, desc->num_supplies,
  337. innolux->supplies);
  338. if (err < 0)
  339. return err;
  340. innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
  341. GPIOD_OUT_HIGH);
  342. if (IS_ERR(innolux->enable_gpio)) {
  343. err = PTR_ERR(innolux->enable_gpio);
  344. dev_dbg(dev, "failed to get enable gpio: %d\n", err);
  345. innolux->enable_gpio = NULL;
  346. }
  347. drm_panel_init(&innolux->base, dev, &innolux_panel_funcs,
  348. DRM_MODE_CONNECTOR_DSI);
  349. err = drm_panel_of_backlight(&innolux->base);
  350. if (err)
  351. return err;
  352. drm_panel_add(&innolux->base);
  353. mipi_dsi_set_drvdata(dsi, innolux);
  354. innolux->link = dsi;
  355. return 0;
  356. }
  357. static void innolux_panel_del(struct innolux_panel *innolux)
  358. {
  359. drm_panel_remove(&innolux->base);
  360. }
  361. static int innolux_panel_probe(struct mipi_dsi_device *dsi)
  362. {
  363. const struct panel_desc *desc;
  364. struct innolux_panel *innolux;
  365. int err;
  366. desc = of_device_get_match_data(&dsi->dev);
  367. dsi->mode_flags = desc->flags;
  368. dsi->format = desc->format;
  369. dsi->lanes = desc->lanes;
  370. err = innolux_panel_add(dsi, desc);
  371. if (err < 0)
  372. return err;
  373. err = mipi_dsi_attach(dsi);
  374. if (err < 0) {
  375. innolux = mipi_dsi_get_drvdata(dsi);
  376. innolux_panel_del(innolux);
  377. return err;
  378. }
  379. return 0;
  380. }
  381. static void innolux_panel_remove(struct mipi_dsi_device *dsi)
  382. {
  383. struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
  384. int err;
  385. err = mipi_dsi_detach(dsi);
  386. if (err < 0)
  387. dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
  388. innolux_panel_del(innolux);
  389. }
  390. static struct mipi_dsi_driver innolux_panel_driver = {
  391. .driver = {
  392. .name = "panel-innolux-p079zca",
  393. .of_match_table = innolux_of_match,
  394. },
  395. .probe = innolux_panel_probe,
  396. .remove = innolux_panel_remove,
  397. };
  398. module_mipi_dsi_driver(innolux_panel_driver);
  399. MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
  400. MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
  401. MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
  402. MODULE_LICENSE("GPL v2");