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