panel-novatek-nt36672a.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * Copyright (C) 2020 Linaro Ltd
  4. * Author: Sumit Semwal <[email protected]>
  5. *
  6. * This driver is for the DSI interface to panels using the NT36672A display driver IC
  7. * from Novatek.
  8. * Currently supported are the Tianma FHD+ panels found in some Xiaomi phones, including
  9. * some variants of the Poco F1 phone.
  10. *
  11. * Panels using the Novatek NT37762A IC should add appropriate configuration per-panel and
  12. * use this driver.
  13. */
  14. #include <linux/delay.h>
  15. #include <linux/kernel.h>
  16. #include <linux/module.h>
  17. #include <linux/of.h>
  18. #include <linux/of_device.h>
  19. #include <linux/gpio/consumer.h>
  20. #include <linux/pinctrl/consumer.h>
  21. #include <linux/regulator/consumer.h>
  22. #include <drm/drm_device.h>
  23. #include <drm/drm_mipi_dsi.h>
  24. #include <drm/drm_modes.h>
  25. #include <drm/drm_panel.h>
  26. #include <video/mipi_display.h>
  27. struct nt36672a_panel_cmd {
  28. const char data[2];
  29. };
  30. static const char * const nt36672a_regulator_names[] = {
  31. "vddio",
  32. "vddpos",
  33. "vddneg",
  34. };
  35. static unsigned long const nt36672a_regulator_enable_loads[] = {
  36. 62000,
  37. 100000,
  38. 100000
  39. };
  40. struct nt36672a_panel_desc {
  41. const struct drm_display_mode *display_mode;
  42. const char *panel_name;
  43. unsigned int width_mm;
  44. unsigned int height_mm;
  45. unsigned long mode_flags;
  46. enum mipi_dsi_pixel_format format;
  47. unsigned int lanes;
  48. unsigned int num_on_cmds_1;
  49. const struct nt36672a_panel_cmd *on_cmds_1;
  50. unsigned int num_on_cmds_2;
  51. const struct nt36672a_panel_cmd *on_cmds_2;
  52. unsigned int num_off_cmds;
  53. const struct nt36672a_panel_cmd *off_cmds;
  54. };
  55. struct nt36672a_panel {
  56. struct drm_panel base;
  57. struct mipi_dsi_device *link;
  58. const struct nt36672a_panel_desc *desc;
  59. struct regulator_bulk_data supplies[ARRAY_SIZE(nt36672a_regulator_names)];
  60. struct gpio_desc *reset_gpio;
  61. bool prepared;
  62. };
  63. static inline struct nt36672a_panel *to_nt36672a_panel(struct drm_panel *panel)
  64. {
  65. return container_of(panel, struct nt36672a_panel, base);
  66. }
  67. static int nt36672a_send_cmds(struct drm_panel *panel, const struct nt36672a_panel_cmd *cmds,
  68. int num)
  69. {
  70. struct nt36672a_panel *pinfo = to_nt36672a_panel(panel);
  71. unsigned int i;
  72. int err;
  73. for (i = 0; i < num; i++) {
  74. const struct nt36672a_panel_cmd *cmd = &cmds[i];
  75. err = mipi_dsi_dcs_write(pinfo->link, cmd->data[0], cmd->data + 1, 1);
  76. if (err < 0)
  77. return err;
  78. }
  79. return 0;
  80. }
  81. static int nt36672a_panel_power_off(struct drm_panel *panel)
  82. {
  83. struct nt36672a_panel *pinfo = to_nt36672a_panel(panel);
  84. int ret = 0;
  85. gpiod_set_value(pinfo->reset_gpio, 1);
  86. ret = regulator_bulk_disable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies);
  87. if (ret)
  88. dev_err(panel->dev, "regulator_bulk_disable failed %d\n", ret);
  89. return ret;
  90. }
  91. static int nt36672a_panel_unprepare(struct drm_panel *panel)
  92. {
  93. struct nt36672a_panel *pinfo = to_nt36672a_panel(panel);
  94. int ret;
  95. if (!pinfo->prepared)
  96. return 0;
  97. /* send off cmds */
  98. ret = nt36672a_send_cmds(panel, pinfo->desc->off_cmds,
  99. pinfo->desc->num_off_cmds);
  100. if (ret < 0)
  101. dev_err(panel->dev, "failed to send DCS off cmds: %d\n", ret);
  102. ret = mipi_dsi_dcs_set_display_off(pinfo->link);
  103. if (ret < 0)
  104. dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
  105. /* 120ms delay required here as per DCS spec */
  106. msleep(120);
  107. ret = mipi_dsi_dcs_enter_sleep_mode(pinfo->link);
  108. if (ret < 0)
  109. dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
  110. /* 0x3C = 60ms delay */
  111. msleep(60);
  112. ret = nt36672a_panel_power_off(panel);
  113. if (ret < 0)
  114. dev_err(panel->dev, "power_off failed ret = %d\n", ret);
  115. pinfo->prepared = false;
  116. return ret;
  117. }
  118. static int nt36672a_panel_power_on(struct nt36672a_panel *pinfo)
  119. {
  120. int ret;
  121. ret = regulator_bulk_enable(ARRAY_SIZE(pinfo->supplies), pinfo->supplies);
  122. if (ret < 0)
  123. return ret;
  124. /*
  125. * As per downstream kernel, Reset sequence of Tianma FHD panel requires the panel to
  126. * be out of reset for 10ms, followed by being held in reset for 10ms. But for Android
  127. * AOSP, we needed to bump it upto 200ms otherwise we get white screen sometimes.
  128. * FIXME: Try to reduce this 200ms to a lesser value.
  129. */
  130. gpiod_set_value(pinfo->reset_gpio, 1);
  131. msleep(200);
  132. gpiod_set_value(pinfo->reset_gpio, 0);
  133. msleep(200);
  134. return 0;
  135. }
  136. static int nt36672a_panel_prepare(struct drm_panel *panel)
  137. {
  138. struct nt36672a_panel *pinfo = to_nt36672a_panel(panel);
  139. int err;
  140. if (pinfo->prepared)
  141. return 0;
  142. err = nt36672a_panel_power_on(pinfo);
  143. if (err < 0)
  144. goto poweroff;
  145. /* send first part of init cmds */
  146. err = nt36672a_send_cmds(panel, pinfo->desc->on_cmds_1,
  147. pinfo->desc->num_on_cmds_1);
  148. if (err < 0) {
  149. dev_err(panel->dev, "failed to send DCS Init 1st Code: %d\n", err);
  150. goto poweroff;
  151. }
  152. err = mipi_dsi_dcs_exit_sleep_mode(pinfo->link);
  153. if (err < 0) {
  154. dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
  155. goto poweroff;
  156. }
  157. /* 0x46 = 70 ms delay */
  158. msleep(70);
  159. err = mipi_dsi_dcs_set_display_on(pinfo->link);
  160. if (err < 0) {
  161. dev_err(panel->dev, "failed to Set Display ON: %d\n", err);
  162. goto poweroff;
  163. }
  164. /* Send rest of the init cmds */
  165. err = nt36672a_send_cmds(panel, pinfo->desc->on_cmds_2,
  166. pinfo->desc->num_on_cmds_2);
  167. if (err < 0) {
  168. dev_err(panel->dev, "failed to send DCS Init 2nd Code: %d\n", err);
  169. goto poweroff;
  170. }
  171. msleep(120);
  172. pinfo->prepared = true;
  173. return 0;
  174. poweroff:
  175. gpiod_set_value(pinfo->reset_gpio, 0);
  176. return err;
  177. }
  178. static int nt36672a_panel_get_modes(struct drm_panel *panel,
  179. struct drm_connector *connector)
  180. {
  181. struct nt36672a_panel *pinfo = to_nt36672a_panel(panel);
  182. const struct drm_display_mode *m = pinfo->desc->display_mode;
  183. struct drm_display_mode *mode;
  184. mode = drm_mode_duplicate(connector->dev, m);
  185. if (!mode) {
  186. dev_err(panel->dev, "failed to add mode %ux%u@%u\n", m->hdisplay,
  187. m->vdisplay, drm_mode_vrefresh(m));
  188. return -ENOMEM;
  189. }
  190. connector->display_info.width_mm = pinfo->desc->width_mm;
  191. connector->display_info.height_mm = pinfo->desc->height_mm;
  192. drm_mode_set_name(mode);
  193. drm_mode_probed_add(connector, mode);
  194. return 1;
  195. }
  196. static const struct drm_panel_funcs panel_funcs = {
  197. .unprepare = nt36672a_panel_unprepare,
  198. .prepare = nt36672a_panel_prepare,
  199. .get_modes = nt36672a_panel_get_modes,
  200. };
  201. static const struct nt36672a_panel_cmd tianma_fhd_video_on_cmds_1[] = {
  202. /* skin enhancement mode */
  203. { .data = {0xFF, 0x22} },
  204. { .data = {0x00, 0x40} },
  205. { .data = {0x01, 0xC0} },
  206. { .data = {0x02, 0x40} },
  207. { .data = {0x03, 0x40} },
  208. { .data = {0x04, 0x40} },
  209. { .data = {0x05, 0x40} },
  210. { .data = {0x06, 0x40} },
  211. { .data = {0x07, 0x40} },
  212. { .data = {0x08, 0x40} },
  213. { .data = {0x09, 0x40} },
  214. { .data = {0x0A, 0x40} },
  215. { .data = {0x0B, 0x40} },
  216. { .data = {0x0C, 0x40} },
  217. { .data = {0x0D, 0x40} },
  218. { .data = {0x0E, 0x40} },
  219. { .data = {0x0F, 0x40} },
  220. { .data = {0x10, 0x40} },
  221. { .data = {0x11, 0x50} },
  222. { .data = {0x12, 0x60} },
  223. { .data = {0x13, 0x70} },
  224. { .data = {0x14, 0x58} },
  225. { .data = {0x15, 0x68} },
  226. { .data = {0x16, 0x78} },
  227. { .data = {0x17, 0x77} },
  228. { .data = {0x18, 0x39} },
  229. { .data = {0x19, 0x2D} },
  230. { .data = {0x1A, 0x2E} },
  231. { .data = {0x1B, 0x32} },
  232. { .data = {0x1C, 0x37} },
  233. { .data = {0x1D, 0x3A} },
  234. { .data = {0x1E, 0x40} },
  235. { .data = {0x1F, 0x40} },
  236. { .data = {0x20, 0x40} },
  237. { .data = {0x21, 0x40} },
  238. { .data = {0x22, 0x40} },
  239. { .data = {0x23, 0x40} },
  240. { .data = {0x24, 0x40} },
  241. { .data = {0x25, 0x40} },
  242. { .data = {0x26, 0x40} },
  243. { .data = {0x27, 0x40} },
  244. { .data = {0x28, 0x40} },
  245. { .data = {0x2D, 0x00} },
  246. { .data = {0x2F, 0x40} },
  247. { .data = {0x30, 0x40} },
  248. { .data = {0x31, 0x40} },
  249. { .data = {0x32, 0x40} },
  250. { .data = {0x33, 0x40} },
  251. { .data = {0x34, 0x40} },
  252. { .data = {0x35, 0x40} },
  253. { .data = {0x36, 0x40} },
  254. { .data = {0x37, 0x40} },
  255. { .data = {0x38, 0x40} },
  256. { .data = {0x39, 0x40} },
  257. { .data = {0x3A, 0x40} },
  258. { .data = {0x3B, 0x40} },
  259. { .data = {0x3D, 0x40} },
  260. { .data = {0x3F, 0x40} },
  261. { .data = {0x40, 0x40} },
  262. { .data = {0x41, 0x40} },
  263. { .data = {0x42, 0x40} },
  264. { .data = {0x43, 0x40} },
  265. { .data = {0x44, 0x40} },
  266. { .data = {0x45, 0x40} },
  267. { .data = {0x46, 0x40} },
  268. { .data = {0x47, 0x40} },
  269. { .data = {0x48, 0x40} },
  270. { .data = {0x49, 0x40} },
  271. { .data = {0x4A, 0x40} },
  272. { .data = {0x4B, 0x40} },
  273. { .data = {0x4C, 0x40} },
  274. { .data = {0x4D, 0x40} },
  275. { .data = {0x4E, 0x40} },
  276. { .data = {0x4F, 0x40} },
  277. { .data = {0x50, 0x40} },
  278. { .data = {0x51, 0x40} },
  279. { .data = {0x52, 0x40} },
  280. { .data = {0x53, 0x01} },
  281. { .data = {0x54, 0x01} },
  282. { .data = {0x55, 0xFE} },
  283. { .data = {0x56, 0x77} },
  284. { .data = {0x58, 0xCD} },
  285. { .data = {0x59, 0xD0} },
  286. { .data = {0x5A, 0xD0} },
  287. { .data = {0x5B, 0x50} },
  288. { .data = {0x5C, 0x50} },
  289. { .data = {0x5D, 0x50} },
  290. { .data = {0x5E, 0x50} },
  291. { .data = {0x5F, 0x50} },
  292. { .data = {0x60, 0x50} },
  293. { .data = {0x61, 0x50} },
  294. { .data = {0x62, 0x50} },
  295. { .data = {0x63, 0x50} },
  296. { .data = {0x64, 0x50} },
  297. { .data = {0x65, 0x50} },
  298. { .data = {0x66, 0x50} },
  299. { .data = {0x67, 0x50} },
  300. { .data = {0x68, 0x50} },
  301. { .data = {0x69, 0x50} },
  302. { .data = {0x6A, 0x50} },
  303. { .data = {0x6B, 0x50} },
  304. { .data = {0x6C, 0x50} },
  305. { .data = {0x6D, 0x50} },
  306. { .data = {0x6E, 0x50} },
  307. { .data = {0x6F, 0x50} },
  308. { .data = {0x70, 0x07} },
  309. { .data = {0x71, 0x00} },
  310. { .data = {0x72, 0x00} },
  311. { .data = {0x73, 0x00} },
  312. { .data = {0x74, 0x06} },
  313. { .data = {0x75, 0x0C} },
  314. { .data = {0x76, 0x03} },
  315. { .data = {0x77, 0x09} },
  316. { .data = {0x78, 0x0F} },
  317. { .data = {0x79, 0x68} },
  318. { .data = {0x7A, 0x88} },
  319. { .data = {0x7C, 0x80} },
  320. { .data = {0x7D, 0x80} },
  321. { .data = {0x7E, 0x80} },
  322. { .data = {0x7F, 0x00} },
  323. { .data = {0x80, 0x00} },
  324. { .data = {0x81, 0x00} },
  325. { .data = {0x83, 0x01} },
  326. { .data = {0x84, 0x00} },
  327. { .data = {0x85, 0x80} },
  328. { .data = {0x86, 0x80} },
  329. { .data = {0x87, 0x80} },
  330. { .data = {0x88, 0x40} },
  331. { .data = {0x89, 0x91} },
  332. { .data = {0x8A, 0x98} },
  333. { .data = {0x8B, 0x80} },
  334. { .data = {0x8C, 0x80} },
  335. { .data = {0x8D, 0x80} },
  336. { .data = {0x8E, 0x80} },
  337. { .data = {0x8F, 0x80} },
  338. { .data = {0x90, 0x80} },
  339. { .data = {0x91, 0x80} },
  340. { .data = {0x92, 0x80} },
  341. { .data = {0x93, 0x80} },
  342. { .data = {0x94, 0x80} },
  343. { .data = {0x95, 0x80} },
  344. { .data = {0x96, 0x80} },
  345. { .data = {0x97, 0x80} },
  346. { .data = {0x98, 0x80} },
  347. { .data = {0x99, 0x80} },
  348. { .data = {0x9A, 0x80} },
  349. { .data = {0x9B, 0x80} },
  350. { .data = {0x9C, 0x80} },
  351. { .data = {0x9D, 0x80} },
  352. { .data = {0x9E, 0x80} },
  353. { .data = {0x9F, 0x80} },
  354. { .data = {0xA0, 0x8A} },
  355. { .data = {0xA2, 0x80} },
  356. { .data = {0xA6, 0x80} },
  357. { .data = {0xA7, 0x80} },
  358. { .data = {0xA9, 0x80} },
  359. { .data = {0xAA, 0x80} },
  360. { .data = {0xAB, 0x80} },
  361. { .data = {0xAC, 0x80} },
  362. { .data = {0xAD, 0x80} },
  363. { .data = {0xAE, 0x80} },
  364. { .data = {0xAF, 0x80} },
  365. { .data = {0xB7, 0x76} },
  366. { .data = {0xB8, 0x76} },
  367. { .data = {0xB9, 0x05} },
  368. { .data = {0xBA, 0x0D} },
  369. { .data = {0xBB, 0x14} },
  370. { .data = {0xBC, 0x0F} },
  371. { .data = {0xBD, 0x18} },
  372. { .data = {0xBE, 0x1F} },
  373. { .data = {0xBF, 0x05} },
  374. { .data = {0xC0, 0x0D} },
  375. { .data = {0xC1, 0x14} },
  376. { .data = {0xC2, 0x03} },
  377. { .data = {0xC3, 0x07} },
  378. { .data = {0xC4, 0x0A} },
  379. { .data = {0xC5, 0xA0} },
  380. { .data = {0xC6, 0x55} },
  381. { .data = {0xC7, 0xFF} },
  382. { .data = {0xC8, 0x39} },
  383. { .data = {0xC9, 0x44} },
  384. { .data = {0xCA, 0x12} },
  385. { .data = {0xCD, 0x80} },
  386. { .data = {0xDB, 0x80} },
  387. { .data = {0xDC, 0x80} },
  388. { .data = {0xDD, 0x80} },
  389. { .data = {0xE0, 0x80} },
  390. { .data = {0xE1, 0x80} },
  391. { .data = {0xE2, 0x80} },
  392. { .data = {0xE3, 0x80} },
  393. { .data = {0xE4, 0x80} },
  394. { .data = {0xE5, 0x40} },
  395. { .data = {0xE6, 0x40} },
  396. { .data = {0xE7, 0x40} },
  397. { .data = {0xE8, 0x40} },
  398. { .data = {0xE9, 0x40} },
  399. { .data = {0xEA, 0x40} },
  400. { .data = {0xEB, 0x40} },
  401. { .data = {0xEC, 0x40} },
  402. { .data = {0xED, 0x40} },
  403. { .data = {0xEE, 0x40} },
  404. { .data = {0xEF, 0x40} },
  405. { .data = {0xF0, 0x40} },
  406. { .data = {0xF1, 0x40} },
  407. { .data = {0xF2, 0x40} },
  408. { .data = {0xF3, 0x40} },
  409. { .data = {0xF4, 0x40} },
  410. { .data = {0xF5, 0x40} },
  411. { .data = {0xF6, 0x40} },
  412. { .data = {0xFB, 0x1} },
  413. { .data = {0xFF, 0x23} },
  414. { .data = {0xFB, 0x01} },
  415. /* dimming enable */
  416. { .data = {0x01, 0x84} },
  417. { .data = {0x05, 0x2D} },
  418. { .data = {0x06, 0x00} },
  419. /* resolution 1080*2246 */
  420. { .data = {0x11, 0x01} },
  421. { .data = {0x12, 0x7B} },
  422. { .data = {0x15, 0x6F} },
  423. { .data = {0x16, 0x0B} },
  424. /* UI mode */
  425. { .data = {0x29, 0x0A} },
  426. { .data = {0x30, 0xFF} },
  427. { .data = {0x31, 0xFF} },
  428. { .data = {0x32, 0xFF} },
  429. { .data = {0x33, 0xFF} },
  430. { .data = {0x34, 0xFF} },
  431. { .data = {0x35, 0xFF} },
  432. { .data = {0x36, 0xFF} },
  433. { .data = {0x37, 0xFF} },
  434. { .data = {0x38, 0xFC} },
  435. { .data = {0x39, 0xF8} },
  436. { .data = {0x3A, 0xF4} },
  437. { .data = {0x3B, 0xF1} },
  438. { .data = {0x3D, 0xEE} },
  439. { .data = {0x3F, 0xEB} },
  440. { .data = {0x40, 0xE8} },
  441. { .data = {0x41, 0xE5} },
  442. /* STILL mode */
  443. { .data = {0x2A, 0x13} },
  444. { .data = {0x45, 0xFF} },
  445. { .data = {0x46, 0xFF} },
  446. { .data = {0x47, 0xFF} },
  447. { .data = {0x48, 0xFF} },
  448. { .data = {0x49, 0xFF} },
  449. { .data = {0x4A, 0xFF} },
  450. { .data = {0x4B, 0xFF} },
  451. { .data = {0x4C, 0xFF} },
  452. { .data = {0x4D, 0xED} },
  453. { .data = {0x4E, 0xD5} },
  454. { .data = {0x4F, 0xBF} },
  455. { .data = {0x50, 0xA6} },
  456. { .data = {0x51, 0x96} },
  457. { .data = {0x52, 0x86} },
  458. { .data = {0x53, 0x76} },
  459. { .data = {0x54, 0x66} },
  460. /* MOVING mode */
  461. { .data = {0x2B, 0x0E} },
  462. { .data = {0x58, 0xFF} },
  463. { .data = {0x59, 0xFF} },
  464. { .data = {0x5A, 0xFF} },
  465. { .data = {0x5B, 0xFF} },
  466. { .data = {0x5C, 0xFF} },
  467. { .data = {0x5D, 0xFF} },
  468. { .data = {0x5E, 0xFF} },
  469. { .data = {0x5F, 0xFF} },
  470. { .data = {0x60, 0xF6} },
  471. { .data = {0x61, 0xEA} },
  472. { .data = {0x62, 0xE1} },
  473. { .data = {0x63, 0xD8} },
  474. { .data = {0x64, 0xCE} },
  475. { .data = {0x65, 0xC3} },
  476. { .data = {0x66, 0xBA} },
  477. { .data = {0x67, 0xB3} },
  478. { .data = {0xFF, 0x25} },
  479. { .data = {0xFB, 0x01} },
  480. { .data = {0x05, 0x04} },
  481. { .data = {0xFF, 0x26} },
  482. { .data = {0xFB, 0x01} },
  483. { .data = {0x1C, 0xAF} },
  484. { .data = {0xFF, 0x10} },
  485. { .data = {0xFB, 0x01} },
  486. { .data = {0x51, 0xFF} },
  487. { .data = {0x53, 0x24} },
  488. { .data = {0x55, 0x00} },
  489. };
  490. static const struct nt36672a_panel_cmd tianma_fhd_video_on_cmds_2[] = {
  491. { .data = {0xFF, 0x24} },
  492. { .data = {0xFB, 0x01} },
  493. { .data = {0xC3, 0x01} },
  494. { .data = {0xC4, 0x54} },
  495. { .data = {0xFF, 0x10} },
  496. };
  497. static const struct nt36672a_panel_cmd tianma_fhd_video_off_cmds[] = {
  498. { .data = {0xFF, 0x24} },
  499. { .data = {0xFB, 0x01} },
  500. { .data = {0xC3, 0x01} },
  501. { .data = {0xFF, 0x10} },
  502. };
  503. static const struct drm_display_mode tianma_fhd_video_panel_default_mode = {
  504. .clock = 161331,
  505. .hdisplay = 1080,
  506. .hsync_start = 1080 + 40,
  507. .hsync_end = 1080 + 40 + 20,
  508. .htotal = 1080 + 40 + 20 + 44,
  509. .vdisplay = 2246,
  510. .vsync_start = 2246 + 15,
  511. .vsync_end = 2246 + 15 + 2,
  512. .vtotal = 2246 + 15 + 2 + 8,
  513. .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
  514. };
  515. static const struct nt36672a_panel_desc tianma_fhd_video_panel_desc = {
  516. .display_mode = &tianma_fhd_video_panel_default_mode,
  517. .width_mm = 68,
  518. .height_mm = 136,
  519. .mode_flags = MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO
  520. | MIPI_DSI_MODE_VIDEO_HSE
  521. | MIPI_DSI_CLOCK_NON_CONTINUOUS
  522. | MIPI_DSI_MODE_VIDEO_BURST,
  523. .format = MIPI_DSI_FMT_RGB888,
  524. .lanes = 4,
  525. .on_cmds_1 = tianma_fhd_video_on_cmds_1,
  526. .num_on_cmds_1 = ARRAY_SIZE(tianma_fhd_video_on_cmds_1),
  527. .on_cmds_2 = tianma_fhd_video_on_cmds_2,
  528. .num_on_cmds_2 = ARRAY_SIZE(tianma_fhd_video_on_cmds_2),
  529. .off_cmds = tianma_fhd_video_off_cmds,
  530. .num_off_cmds = ARRAY_SIZE(tianma_fhd_video_off_cmds),
  531. };
  532. static int nt36672a_panel_add(struct nt36672a_panel *pinfo)
  533. {
  534. struct device *dev = &pinfo->link->dev;
  535. int i, ret;
  536. for (i = 0; i < ARRAY_SIZE(pinfo->supplies); i++)
  537. pinfo->supplies[i].supply = nt36672a_regulator_names[i];
  538. ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(pinfo->supplies),
  539. pinfo->supplies);
  540. if (ret < 0)
  541. return dev_err_probe(dev, ret, "failed to get regulators\n");
  542. for (i = 0; i < ARRAY_SIZE(pinfo->supplies); i++) {
  543. ret = regulator_set_load(pinfo->supplies[i].consumer,
  544. nt36672a_regulator_enable_loads[i]);
  545. if (ret)
  546. return dev_err_probe(dev, ret, "failed to set regulator enable loads\n");
  547. }
  548. pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
  549. if (IS_ERR(pinfo->reset_gpio))
  550. return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio),
  551. "failed to get reset gpio from DT\n");
  552. drm_panel_init(&pinfo->base, dev, &panel_funcs, DRM_MODE_CONNECTOR_DSI);
  553. ret = drm_panel_of_backlight(&pinfo->base);
  554. if (ret)
  555. return dev_err_probe(dev, ret, "Failed to get backlight\n");
  556. drm_panel_add(&pinfo->base);
  557. return 0;
  558. }
  559. static int nt36672a_panel_probe(struct mipi_dsi_device *dsi)
  560. {
  561. struct nt36672a_panel *pinfo;
  562. const struct nt36672a_panel_desc *desc;
  563. int err;
  564. pinfo = devm_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL);
  565. if (!pinfo)
  566. return -ENOMEM;
  567. desc = of_device_get_match_data(&dsi->dev);
  568. dsi->mode_flags = desc->mode_flags;
  569. dsi->format = desc->format;
  570. dsi->lanes = desc->lanes;
  571. pinfo->desc = desc;
  572. pinfo->link = dsi;
  573. mipi_dsi_set_drvdata(dsi, pinfo);
  574. err = nt36672a_panel_add(pinfo);
  575. if (err < 0)
  576. return err;
  577. err = mipi_dsi_attach(dsi);
  578. if (err < 0) {
  579. drm_panel_remove(&pinfo->base);
  580. return err;
  581. }
  582. return 0;
  583. }
  584. static void nt36672a_panel_remove(struct mipi_dsi_device *dsi)
  585. {
  586. struct nt36672a_panel *pinfo = mipi_dsi_get_drvdata(dsi);
  587. int err;
  588. err = drm_panel_unprepare(&pinfo->base);
  589. if (err < 0)
  590. dev_err(&dsi->dev, "failed to unprepare panel: %d\n", err);
  591. err = drm_panel_disable(&pinfo->base);
  592. if (err < 0)
  593. dev_err(&dsi->dev, "failed to disable panel: %d\n", err);
  594. err = mipi_dsi_detach(dsi);
  595. if (err < 0)
  596. dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
  597. drm_panel_remove(&pinfo->base);
  598. }
  599. static void nt36672a_panel_shutdown(struct mipi_dsi_device *dsi)
  600. {
  601. struct nt36672a_panel *pinfo = mipi_dsi_get_drvdata(dsi);
  602. drm_panel_disable(&pinfo->base);
  603. drm_panel_unprepare(&pinfo->base);
  604. }
  605. static const struct of_device_id tianma_fhd_video_of_match[] = {
  606. { .compatible = "tianma,fhd-video", .data = &tianma_fhd_video_panel_desc },
  607. { },
  608. };
  609. MODULE_DEVICE_TABLE(of, tianma_fhd_video_of_match);
  610. static struct mipi_dsi_driver nt36672a_panel_driver = {
  611. .driver = {
  612. .name = "panel-tianma-nt36672a",
  613. .of_match_table = tianma_fhd_video_of_match,
  614. },
  615. .probe = nt36672a_panel_probe,
  616. .remove = nt36672a_panel_remove,
  617. .shutdown = nt36672a_panel_shutdown,
  618. };
  619. module_mipi_dsi_driver(nt36672a_panel_driver);
  620. MODULE_AUTHOR("Sumit Semwal <[email protected]>");
  621. MODULE_DESCRIPTION("NOVATEK NT36672A based MIPI-DSI LCD panel driver");
  622. MODULE_LICENSE("GPL");