1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2018, The Linux Foundation. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/backlight.h> |
7 | #include <linux/delay.h> |
8 | #include <linux/gpio/consumer.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_graph.h> |
12 | #include <linux/pinctrl/consumer.h> |
13 | #include <linux/regulator/consumer.h> |
14 | |
15 | #include <video/mipi_display.h> |
16 | |
17 | #include <drm/drm_mipi_dsi.h> |
18 | #include <drm/drm_modes.h> |
19 | #include <drm/drm_panel.h> |
20 | |
21 | static const char * const regulator_names[] = { |
22 | "vdda" , |
23 | "vdispp" , |
24 | "vdispn" , |
25 | }; |
26 | |
27 | static unsigned long const regulator_enable_loads[] = { |
28 | 62000, |
29 | 100000, |
30 | 100000, |
31 | }; |
32 | |
33 | static unsigned long const regulator_disable_loads[] = { |
34 | 80, |
35 | 100, |
36 | 100, |
37 | }; |
38 | |
39 | struct cmd_set { |
40 | u8 commands[4]; |
41 | u8 size; |
42 | }; |
43 | |
44 | struct nt35597_config { |
45 | u32 width_mm; |
46 | u32 height_mm; |
47 | const char *panel_name; |
48 | const struct cmd_set *panel_on_cmds; |
49 | u32 num_on_cmds; |
50 | const struct drm_display_mode *dm; |
51 | }; |
52 | |
53 | struct truly_nt35597 { |
54 | struct device *dev; |
55 | struct drm_panel panel; |
56 | |
57 | struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)]; |
58 | |
59 | struct gpio_desc *reset_gpio; |
60 | struct gpio_desc *mode_gpio; |
61 | |
62 | struct backlight_device *backlight; |
63 | |
64 | struct mipi_dsi_device *dsi[2]; |
65 | |
66 | const struct nt35597_config *config; |
67 | }; |
68 | |
69 | static inline struct truly_nt35597 *panel_to_ctx(struct drm_panel *panel) |
70 | { |
71 | return container_of(panel, struct truly_nt35597, panel); |
72 | } |
73 | |
74 | static const struct cmd_set qcom_2k_panel_magic_cmds[] = { |
75 | /* CMD2_P0 */ |
76 | { { 0xff, 0x20 }, 2 }, |
77 | { { 0xfb, 0x01 }, 2 }, |
78 | { { 0x00, 0x01 }, 2 }, |
79 | { { 0x01, 0x55 }, 2 }, |
80 | { { 0x02, 0x45 }, 2 }, |
81 | { { 0x05, 0x40 }, 2 }, |
82 | { { 0x06, 0x19 }, 2 }, |
83 | { { 0x07, 0x1e }, 2 }, |
84 | { { 0x0b, 0x73 }, 2 }, |
85 | { { 0x0c, 0x73 }, 2 }, |
86 | { { 0x0e, 0xb0 }, 2 }, |
87 | { { 0x0f, 0xae }, 2 }, |
88 | { { 0x11, 0xb8 }, 2 }, |
89 | { { 0x13, 0x00 }, 2 }, |
90 | { { 0x58, 0x80 }, 2 }, |
91 | { { 0x59, 0x01 }, 2 }, |
92 | { { 0x5a, 0x00 }, 2 }, |
93 | { { 0x5b, 0x01 }, 2 }, |
94 | { { 0x5c, 0x80 }, 2 }, |
95 | { { 0x5d, 0x81 }, 2 }, |
96 | { { 0x5e, 0x00 }, 2 }, |
97 | { { 0x5f, 0x01 }, 2 }, |
98 | { { 0x72, 0x11 }, 2 }, |
99 | { { 0x68, 0x03 }, 2 }, |
100 | /* CMD2_P4 */ |
101 | { { 0xFF, 0x24 }, 2 }, |
102 | { { 0xFB, 0x01 }, 2 }, |
103 | { { 0x00, 0x1C }, 2 }, |
104 | { { 0x01, 0x0B }, 2 }, |
105 | { { 0x02, 0x0C }, 2 }, |
106 | { { 0x03, 0x01 }, 2 }, |
107 | { { 0x04, 0x0F }, 2 }, |
108 | { { 0x05, 0x10 }, 2 }, |
109 | { { 0x06, 0x10 }, 2 }, |
110 | { { 0x07, 0x10 }, 2 }, |
111 | { { 0x08, 0x89 }, 2 }, |
112 | { { 0x09, 0x8A }, 2 }, |
113 | { { 0x0A, 0x13 }, 2 }, |
114 | { { 0x0B, 0x13 }, 2 }, |
115 | { { 0x0C, 0x15 }, 2 }, |
116 | { { 0x0D, 0x15 }, 2 }, |
117 | { { 0x0E, 0x17 }, 2 }, |
118 | { { 0x0F, 0x17 }, 2 }, |
119 | { { 0x10, 0x1C }, 2 }, |
120 | { { 0x11, 0x0B }, 2 }, |
121 | { { 0x12, 0x0C }, 2 }, |
122 | { { 0x13, 0x01 }, 2 }, |
123 | { { 0x14, 0x0F }, 2 }, |
124 | { { 0x15, 0x10 }, 2 }, |
125 | { { 0x16, 0x10 }, 2 }, |
126 | { { 0x17, 0x10 }, 2 }, |
127 | { { 0x18, 0x89 }, 2 }, |
128 | { { 0x19, 0x8A }, 2 }, |
129 | { { 0x1A, 0x13 }, 2 }, |
130 | { { 0x1B, 0x13 }, 2 }, |
131 | { { 0x1C, 0x15 }, 2 }, |
132 | { { 0x1D, 0x15 }, 2 }, |
133 | { { 0x1E, 0x17 }, 2 }, |
134 | { { 0x1F, 0x17 }, 2 }, |
135 | /* STV */ |
136 | { { 0x20, 0x40 }, 2 }, |
137 | { { 0x21, 0x01 }, 2 }, |
138 | { { 0x22, 0x00 }, 2 }, |
139 | { { 0x23, 0x40 }, 2 }, |
140 | { { 0x24, 0x40 }, 2 }, |
141 | { { 0x25, 0x6D }, 2 }, |
142 | { { 0x26, 0x40 }, 2 }, |
143 | { { 0x27, 0x40 }, 2 }, |
144 | /* Vend */ |
145 | { { 0xE0, 0x00 }, 2 }, |
146 | { { 0xDC, 0x21 }, 2 }, |
147 | { { 0xDD, 0x22 }, 2 }, |
148 | { { 0xDE, 0x07 }, 2 }, |
149 | { { 0xDF, 0x07 }, 2 }, |
150 | { { 0xE3, 0x6D }, 2 }, |
151 | { { 0xE1, 0x07 }, 2 }, |
152 | { { 0xE2, 0x07 }, 2 }, |
153 | /* UD */ |
154 | { { 0x29, 0xD8 }, 2 }, |
155 | { { 0x2A, 0x2A }, 2 }, |
156 | /* CLK */ |
157 | { { 0x4B, 0x03 }, 2 }, |
158 | { { 0x4C, 0x11 }, 2 }, |
159 | { { 0x4D, 0x10 }, 2 }, |
160 | { { 0x4E, 0x01 }, 2 }, |
161 | { { 0x4F, 0x01 }, 2 }, |
162 | { { 0x50, 0x10 }, 2 }, |
163 | { { 0x51, 0x00 }, 2 }, |
164 | { { 0x52, 0x80 }, 2 }, |
165 | { { 0x53, 0x00 }, 2 }, |
166 | { { 0x56, 0x00 }, 2 }, |
167 | { { 0x54, 0x07 }, 2 }, |
168 | { { 0x58, 0x07 }, 2 }, |
169 | { { 0x55, 0x25 }, 2 }, |
170 | /* Reset XDONB */ |
171 | { { 0x5B, 0x43 }, 2 }, |
172 | { { 0x5C, 0x00 }, 2 }, |
173 | { { 0x5F, 0x73 }, 2 }, |
174 | { { 0x60, 0x73 }, 2 }, |
175 | { { 0x63, 0x22 }, 2 }, |
176 | { { 0x64, 0x00 }, 2 }, |
177 | { { 0x67, 0x08 }, 2 }, |
178 | { { 0x68, 0x04 }, 2 }, |
179 | /* Resolution:1440x2560 */ |
180 | { { 0x72, 0x02 }, 2 }, |
181 | /* mux */ |
182 | { { 0x7A, 0x80 }, 2 }, |
183 | { { 0x7B, 0x91 }, 2 }, |
184 | { { 0x7C, 0xD8 }, 2 }, |
185 | { { 0x7D, 0x60 }, 2 }, |
186 | { { 0x7F, 0x15 }, 2 }, |
187 | { { 0x75, 0x15 }, 2 }, |
188 | /* ABOFF */ |
189 | { { 0xB3, 0xC0 }, 2 }, |
190 | { { 0xB4, 0x00 }, 2 }, |
191 | { { 0xB5, 0x00 }, 2 }, |
192 | /* Source EQ */ |
193 | { { 0x78, 0x00 }, 2 }, |
194 | { { 0x79, 0x00 }, 2 }, |
195 | { { 0x80, 0x00 }, 2 }, |
196 | { { 0x83, 0x00 }, 2 }, |
197 | /* FP BP */ |
198 | { { 0x93, 0x0A }, 2 }, |
199 | { { 0x94, 0x0A }, 2 }, |
200 | /* Inversion Type */ |
201 | { { 0x8A, 0x00 }, 2 }, |
202 | { { 0x9B, 0xFF }, 2 }, |
203 | /* IMGSWAP =1 @PortSwap=1 */ |
204 | { { 0x9D, 0xB0 }, 2 }, |
205 | { { 0x9F, 0x63 }, 2 }, |
206 | { { 0x98, 0x10 }, 2 }, |
207 | /* FRM */ |
208 | { { 0xEC, 0x00 }, 2 }, |
209 | /* CMD1 */ |
210 | { { 0xFF, 0x10 }, 2 }, |
211 | /* VBP+VSA=,VFP = 10H */ |
212 | { { 0x3B, 0x03, 0x0A, 0x0A }, 4 }, |
213 | /* FTE on */ |
214 | { { 0x35, 0x00 }, 2 }, |
215 | /* EN_BK =1(auto black) */ |
216 | { { 0xE5, 0x01 }, 2 }, |
217 | /* CMD mode(10) VDO mode(03) */ |
218 | { { 0xBB, 0x03 }, 2 }, |
219 | /* Non Reload MTP */ |
220 | { { 0xFB, 0x01 }, 2 }, |
221 | }; |
222 | |
223 | static int truly_dcs_write(struct drm_panel *panel, u32 command) |
224 | { |
225 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
226 | int i, ret; |
227 | |
228 | for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { |
229 | ret = mipi_dsi_dcs_write(dsi: ctx->dsi[i], cmd: command, NULL, len: 0); |
230 | if (ret < 0) { |
231 | dev_err(ctx->dev, "cmd 0x%x failed for dsi = %d\n" , command, i); |
232 | } |
233 | } |
234 | |
235 | return ret; |
236 | } |
237 | |
238 | static int truly_dcs_write_buf(struct drm_panel *panel, |
239 | u32 size, const u8 *buf) |
240 | { |
241 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
242 | int ret = 0; |
243 | int i; |
244 | |
245 | for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { |
246 | ret = mipi_dsi_dcs_write_buffer(dsi: ctx->dsi[i], data: buf, len: size); |
247 | if (ret < 0) { |
248 | dev_err(ctx->dev, "failed to tx cmd [%d], err: %d\n" , i, ret); |
249 | return ret; |
250 | } |
251 | } |
252 | |
253 | return ret; |
254 | } |
255 | |
256 | static int truly_35597_power_on(struct truly_nt35597 *ctx) |
257 | { |
258 | int ret, i; |
259 | |
260 | for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) { |
261 | ret = regulator_set_load(regulator: ctx->supplies[i].consumer, |
262 | load_uA: regulator_enable_loads[i]); |
263 | if (ret) |
264 | return ret; |
265 | } |
266 | |
267 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
268 | if (ret < 0) |
269 | return ret; |
270 | |
271 | /* |
272 | * Reset sequence of truly panel requires the panel to be |
273 | * out of reset for 10ms, followed by being held in reset |
274 | * for 10ms and then out again |
275 | */ |
276 | gpiod_set_value(desc: ctx->reset_gpio, value: 0); |
277 | usleep_range(min: 10000, max: 20000); |
278 | gpiod_set_value(desc: ctx->reset_gpio, value: 1); |
279 | usleep_range(min: 10000, max: 20000); |
280 | gpiod_set_value(desc: ctx->reset_gpio, value: 0); |
281 | usleep_range(min: 10000, max: 20000); |
282 | |
283 | return 0; |
284 | } |
285 | |
286 | static int truly_nt35597_power_off(struct truly_nt35597 *ctx) |
287 | { |
288 | int ret = 0; |
289 | int i; |
290 | |
291 | gpiod_set_value(desc: ctx->reset_gpio, value: 1); |
292 | |
293 | for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) { |
294 | ret = regulator_set_load(regulator: ctx->supplies[i].consumer, |
295 | load_uA: regulator_disable_loads[i]); |
296 | if (ret) { |
297 | dev_err(ctx->dev, "regulator_set_load failed %d\n" , ret); |
298 | return ret; |
299 | } |
300 | } |
301 | |
302 | ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
303 | if (ret) { |
304 | dev_err(ctx->dev, "regulator_bulk_disable failed %d\n" , ret); |
305 | } |
306 | return ret; |
307 | } |
308 | |
309 | static int truly_nt35597_disable(struct drm_panel *panel) |
310 | { |
311 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
312 | int ret; |
313 | |
314 | if (ctx->backlight) { |
315 | ret = backlight_disable(bd: ctx->backlight); |
316 | if (ret < 0) |
317 | dev_err(ctx->dev, "backlight disable failed %d\n" , ret); |
318 | } |
319 | |
320 | return 0; |
321 | } |
322 | |
323 | static int truly_nt35597_unprepare(struct drm_panel *panel) |
324 | { |
325 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
326 | int ret = 0; |
327 | |
328 | ctx->dsi[0]->mode_flags = 0; |
329 | ctx->dsi[1]->mode_flags = 0; |
330 | |
331 | ret = truly_dcs_write(panel, command: MIPI_DCS_SET_DISPLAY_OFF); |
332 | if (ret < 0) { |
333 | dev_err(ctx->dev, "set_display_off cmd failed ret = %d\n" , ret); |
334 | } |
335 | |
336 | /* 120ms delay required here as per DCS spec */ |
337 | msleep(msecs: 120); |
338 | |
339 | ret = truly_dcs_write(panel, command: MIPI_DCS_ENTER_SLEEP_MODE); |
340 | if (ret < 0) { |
341 | dev_err(ctx->dev, "enter_sleep cmd failed ret = %d\n" , ret); |
342 | } |
343 | |
344 | ret = truly_nt35597_power_off(ctx); |
345 | if (ret < 0) |
346 | dev_err(ctx->dev, "power_off failed ret = %d\n" , ret); |
347 | |
348 | return ret; |
349 | } |
350 | |
351 | static int truly_nt35597_prepare(struct drm_panel *panel) |
352 | { |
353 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
354 | int ret; |
355 | int i; |
356 | const struct cmd_set *panel_on_cmds; |
357 | const struct nt35597_config *config; |
358 | u32 num_cmds; |
359 | |
360 | ret = truly_35597_power_on(ctx); |
361 | if (ret < 0) |
362 | return ret; |
363 | |
364 | ctx->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM; |
365 | ctx->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; |
366 | |
367 | config = ctx->config; |
368 | panel_on_cmds = config->panel_on_cmds; |
369 | num_cmds = config->num_on_cmds; |
370 | |
371 | for (i = 0; i < num_cmds; i++) { |
372 | ret = truly_dcs_write_buf(panel, |
373 | size: panel_on_cmds[i].size, |
374 | buf: panel_on_cmds[i].commands); |
375 | if (ret < 0) { |
376 | dev_err(ctx->dev, "cmd set tx failed i = %d ret = %d\n" , i, ret); |
377 | goto power_off; |
378 | } |
379 | } |
380 | |
381 | ret = truly_dcs_write(panel, command: MIPI_DCS_EXIT_SLEEP_MODE); |
382 | if (ret < 0) { |
383 | dev_err(ctx->dev, "exit_sleep_mode cmd failed ret = %d\n" , ret); |
384 | goto power_off; |
385 | } |
386 | |
387 | /* Per DSI spec wait 120ms after sending exit sleep DCS command */ |
388 | msleep(msecs: 120); |
389 | |
390 | ret = truly_dcs_write(panel, command: MIPI_DCS_SET_DISPLAY_ON); |
391 | if (ret < 0) { |
392 | dev_err(ctx->dev, "set_display_on cmd failed ret = %d\n" , ret); |
393 | goto power_off; |
394 | } |
395 | |
396 | /* Per DSI spec wait 120ms after sending set_display_on DCS command */ |
397 | msleep(msecs: 120); |
398 | |
399 | return 0; |
400 | |
401 | power_off: |
402 | if (truly_nt35597_power_off(ctx)) |
403 | dev_err(ctx->dev, "power_off failed\n" ); |
404 | return ret; |
405 | } |
406 | |
407 | static int truly_nt35597_enable(struct drm_panel *panel) |
408 | { |
409 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
410 | int ret; |
411 | |
412 | if (ctx->backlight) { |
413 | ret = backlight_enable(bd: ctx->backlight); |
414 | if (ret < 0) |
415 | dev_err(ctx->dev, "backlight enable failed %d\n" , ret); |
416 | } |
417 | |
418 | return 0; |
419 | } |
420 | |
421 | static int truly_nt35597_get_modes(struct drm_panel *panel, |
422 | struct drm_connector *connector) |
423 | { |
424 | struct truly_nt35597 *ctx = panel_to_ctx(panel); |
425 | struct drm_display_mode *mode; |
426 | const struct nt35597_config *config; |
427 | |
428 | config = ctx->config; |
429 | mode = drm_mode_duplicate(dev: connector->dev, mode: config->dm); |
430 | if (!mode) { |
431 | dev_err(ctx->dev, "failed to create a new display mode\n" ); |
432 | return 0; |
433 | } |
434 | |
435 | connector->display_info.width_mm = config->width_mm; |
436 | connector->display_info.height_mm = config->height_mm; |
437 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
438 | drm_mode_probed_add(connector, mode); |
439 | |
440 | return 1; |
441 | } |
442 | |
443 | static const struct drm_panel_funcs truly_nt35597_drm_funcs = { |
444 | .disable = truly_nt35597_disable, |
445 | .unprepare = truly_nt35597_unprepare, |
446 | .prepare = truly_nt35597_prepare, |
447 | .enable = truly_nt35597_enable, |
448 | .get_modes = truly_nt35597_get_modes, |
449 | }; |
450 | |
451 | static int truly_nt35597_panel_add(struct truly_nt35597 *ctx) |
452 | { |
453 | struct device *dev = ctx->dev; |
454 | int ret, i; |
455 | |
456 | for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) |
457 | ctx->supplies[i].supply = regulator_names[i]; |
458 | |
459 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
460 | consumers: ctx->supplies); |
461 | if (ret < 0) |
462 | return ret; |
463 | |
464 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
465 | if (IS_ERR(ptr: ctx->reset_gpio)) { |
466 | dev_err(dev, "cannot get reset gpio %ld\n" , PTR_ERR(ctx->reset_gpio)); |
467 | return PTR_ERR(ptr: ctx->reset_gpio); |
468 | } |
469 | |
470 | ctx->mode_gpio = devm_gpiod_get(dev, con_id: "mode" , flags: GPIOD_OUT_LOW); |
471 | if (IS_ERR(ptr: ctx->mode_gpio)) { |
472 | dev_err(dev, "cannot get mode gpio %ld\n" , PTR_ERR(ctx->mode_gpio)); |
473 | return PTR_ERR(ptr: ctx->mode_gpio); |
474 | } |
475 | |
476 | /* dual port */ |
477 | gpiod_set_value(desc: ctx->mode_gpio, value: 0); |
478 | |
479 | drm_panel_init(panel: &ctx->panel, dev, funcs: &truly_nt35597_drm_funcs, |
480 | DRM_MODE_CONNECTOR_DSI); |
481 | drm_panel_add(panel: &ctx->panel); |
482 | |
483 | return 0; |
484 | } |
485 | |
486 | static const struct drm_display_mode qcom_sdm845_mtp_2k_mode = { |
487 | .name = "1440x2560" , |
488 | .clock = 268316, |
489 | .hdisplay = 1440, |
490 | .hsync_start = 1440 + 200, |
491 | .hsync_end = 1440 + 200 + 32, |
492 | .htotal = 1440 + 200 + 32 + 64, |
493 | .vdisplay = 2560, |
494 | .vsync_start = 2560 + 8, |
495 | .vsync_end = 2560 + 8 + 1, |
496 | .vtotal = 2560 + 8 + 1 + 7, |
497 | .flags = 0, |
498 | }; |
499 | |
500 | static const struct nt35597_config nt35597_dir = { |
501 | .width_mm = 74, |
502 | .height_mm = 131, |
503 | .panel_name = "qcom_sdm845_mtp_2k_panel" , |
504 | .dm = &qcom_sdm845_mtp_2k_mode, |
505 | .panel_on_cmds = qcom_2k_panel_magic_cmds, |
506 | .num_on_cmds = ARRAY_SIZE(qcom_2k_panel_magic_cmds), |
507 | }; |
508 | |
509 | static int truly_nt35597_probe(struct mipi_dsi_device *dsi) |
510 | { |
511 | struct device *dev = &dsi->dev; |
512 | struct truly_nt35597 *ctx; |
513 | struct mipi_dsi_device *dsi1_device; |
514 | struct device_node *dsi1; |
515 | struct mipi_dsi_host *dsi1_host; |
516 | struct mipi_dsi_device *dsi_dev; |
517 | int ret = 0; |
518 | int i; |
519 | |
520 | const struct mipi_dsi_device_info info = { |
521 | .type = "trulynt35597" , |
522 | .channel = 0, |
523 | .node = NULL, |
524 | }; |
525 | |
526 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
527 | |
528 | if (!ctx) |
529 | return -ENOMEM; |
530 | |
531 | /* |
532 | * This device represents itself as one with two input ports which are |
533 | * fed by the output ports of the two DSI controllers . The DSI0 is |
534 | * the master controller and has most of the panel related info in its |
535 | * child node. |
536 | */ |
537 | |
538 | ctx->config = of_device_get_match_data(dev); |
539 | |
540 | if (!ctx->config) { |
541 | dev_err(dev, "missing device configuration\n" ); |
542 | return -ENODEV; |
543 | } |
544 | |
545 | dsi1 = of_graph_get_remote_node(node: dsi->dev.of_node, port: 1, endpoint: -1); |
546 | if (!dsi1) { |
547 | dev_err(dev, "failed to get remote node for dsi1_device\n" ); |
548 | return -ENODEV; |
549 | } |
550 | |
551 | dsi1_host = of_find_mipi_dsi_host_by_node(node: dsi1); |
552 | of_node_put(node: dsi1); |
553 | if (!dsi1_host) { |
554 | dev_err(dev, "failed to find dsi host\n" ); |
555 | return -EPROBE_DEFER; |
556 | } |
557 | |
558 | /* register the second DSI device */ |
559 | dsi1_device = mipi_dsi_device_register_full(host: dsi1_host, info: &info); |
560 | if (IS_ERR(ptr: dsi1_device)) { |
561 | dev_err(dev, "failed to create dsi device\n" ); |
562 | return PTR_ERR(ptr: dsi1_device); |
563 | } |
564 | |
565 | mipi_dsi_set_drvdata(dsi, data: ctx); |
566 | |
567 | ctx->dev = dev; |
568 | ctx->dsi[0] = dsi; |
569 | ctx->dsi[1] = dsi1_device; |
570 | |
571 | ret = truly_nt35597_panel_add(ctx); |
572 | if (ret) { |
573 | dev_err(dev, "failed to add panel\n" ); |
574 | goto err_panel_add; |
575 | } |
576 | |
577 | for (i = 0; i < ARRAY_SIZE(ctx->dsi); i++) { |
578 | dsi_dev = ctx->dsi[i]; |
579 | dsi_dev->lanes = 4; |
580 | dsi_dev->format = MIPI_DSI_FMT_RGB888; |
581 | dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | |
582 | MIPI_DSI_CLOCK_NON_CONTINUOUS; |
583 | ret = mipi_dsi_attach(dsi: dsi_dev); |
584 | if (ret < 0) { |
585 | dev_err(dev, "dsi attach failed i = %d\n" , i); |
586 | goto err_dsi_attach; |
587 | } |
588 | } |
589 | |
590 | return 0; |
591 | |
592 | err_dsi_attach: |
593 | drm_panel_remove(panel: &ctx->panel); |
594 | err_panel_add: |
595 | mipi_dsi_device_unregister(dsi: dsi1_device); |
596 | return ret; |
597 | } |
598 | |
599 | static void truly_nt35597_remove(struct mipi_dsi_device *dsi) |
600 | { |
601 | struct truly_nt35597 *ctx = mipi_dsi_get_drvdata(dsi); |
602 | |
603 | if (ctx->dsi[0]) |
604 | mipi_dsi_detach(dsi: ctx->dsi[0]); |
605 | if (ctx->dsi[1]) { |
606 | mipi_dsi_detach(dsi: ctx->dsi[1]); |
607 | mipi_dsi_device_unregister(dsi: ctx->dsi[1]); |
608 | } |
609 | |
610 | drm_panel_remove(panel: &ctx->panel); |
611 | } |
612 | |
613 | static const struct of_device_id truly_nt35597_of_match[] = { |
614 | { |
615 | .compatible = "truly,nt35597-2K-display" , |
616 | .data = &nt35597_dir, |
617 | }, |
618 | { } |
619 | }; |
620 | MODULE_DEVICE_TABLE(of, truly_nt35597_of_match); |
621 | |
622 | static struct mipi_dsi_driver truly_nt35597_driver = { |
623 | .driver = { |
624 | .name = "panel-truly-nt35597" , |
625 | .of_match_table = truly_nt35597_of_match, |
626 | }, |
627 | .probe = truly_nt35597_probe, |
628 | .remove = truly_nt35597_remove, |
629 | }; |
630 | module_mipi_dsi_driver(truly_nt35597_driver); |
631 | |
632 | MODULE_DESCRIPTION("Truly NT35597 DSI Panel Driver" ); |
633 | MODULE_LICENSE("GPL v2" ); |
634 | |