1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Novatek NT35950 DriverIC panels driver |
4 | * |
5 | * Copyright (c) 2021 AngeloGioacchino Del Regno |
6 | * <angelogioacchino.delregno@somainline.org> |
7 | */ |
8 | #include <linux/delay.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_graph.h> |
13 | #include <linux/regulator/consumer.h> |
14 | |
15 | #include <drm/drm_connector.h> |
16 | #include <drm/drm_crtc.h> |
17 | #include <drm/drm_mipi_dsi.h> |
18 | #include <drm/drm_modes.h> |
19 | #include <drm/drm_panel.h> |
20 | |
21 | #define MCS_CMD_MAUCCTR 0xf0 /* Manufacturer command enable */ |
22 | #define MCS_PARAM_SCALER_FUNCTION 0x58 /* Scale-up function */ |
23 | #define MCS_PARAM_SCALEUP_MODE 0xc9 |
24 | #define MCS_SCALEUP_SIMPLE 0x0 |
25 | #define MCS_SCALEUP_BILINEAR BIT(0) |
26 | #define MCS_SCALEUP_DUPLICATE (BIT(0) | BIT(4)) |
27 | |
28 | /* VESA Display Stream Compression param */ |
29 | #define MCS_PARAM_VESA_DSC_ON 0x03 |
30 | |
31 | /* Data Compression mode */ |
32 | #define MCS_PARAM_DATA_COMPRESSION 0x90 |
33 | #define MCS_DATA_COMPRESSION_NONE 0x00 |
34 | #define MCS_DATA_COMPRESSION_FBC 0x02 |
35 | #define MCS_DATA_COMPRESSION_DSC 0x03 |
36 | |
37 | /* Display Output control */ |
38 | #define MCS_PARAM_DISP_OUTPUT_CTRL 0xb4 |
39 | #define MCS_DISP_OUT_SRAM_EN BIT(0) |
40 | #define MCS_DISP_OUT_VIDEO_MODE BIT(4) |
41 | |
42 | /* VESA Display Stream Compression setting */ |
43 | #define MCS_PARAM_VESA_DSC_SETTING 0xc0 |
44 | |
45 | /* SubPixel Rendering (SPR) */ |
46 | #define MCS_PARAM_SPR_EN 0xe3 |
47 | #define MCS_PARAM_SPR_MODE 0xef |
48 | #define MCS_SPR_MODE_YYG_RAINBOW_RGB 0x01 |
49 | |
50 | #define NT35950_VREG_MAX 4 |
51 | |
52 | struct nt35950 { |
53 | struct drm_panel panel; |
54 | struct drm_connector *connector; |
55 | struct mipi_dsi_device *dsi[2]; |
56 | struct regulator_bulk_data vregs[NT35950_VREG_MAX]; |
57 | struct gpio_desc *reset_gpio; |
58 | const struct nt35950_panel_desc *desc; |
59 | |
60 | int cur_mode; |
61 | u8 last_page; |
62 | }; |
63 | |
64 | struct nt35950_panel_mode { |
65 | const struct drm_display_mode mode; |
66 | |
67 | bool enable_sram; |
68 | bool is_video_mode; |
69 | u8 scaler_on; |
70 | u8 scaler_mode; |
71 | u8 compression; |
72 | u8 spr_en; |
73 | u8 spr_mode; |
74 | }; |
75 | |
76 | struct nt35950_panel_desc { |
77 | const char *model_name; |
78 | const struct mipi_dsi_device_info dsi_info; |
79 | const struct nt35950_panel_mode *mode_data; |
80 | |
81 | bool is_dual_dsi; |
82 | u8 num_lanes; |
83 | u8 num_modes; |
84 | }; |
85 | |
86 | static inline struct nt35950 *to_nt35950(struct drm_panel *panel) |
87 | { |
88 | return container_of(panel, struct nt35950, panel); |
89 | } |
90 | |
91 | static void nt35950_reset(struct nt35950 *nt) |
92 | { |
93 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 1); |
94 | usleep_range(min: 12000, max: 13000); |
95 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 0); |
96 | usleep_range(min: 300, max: 400); |
97 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 1); |
98 | usleep_range(min: 12000, max: 13000); |
99 | } |
100 | |
101 | /* |
102 | * nt35950_set_cmd2_page - Select manufacturer control (CMD2) page |
103 | * @nt: Main driver structure |
104 | * @page: Page number (0-7) |
105 | * |
106 | * Return: Number of transferred bytes or negative number on error |
107 | */ |
108 | static int nt35950_set_cmd2_page(struct nt35950 *nt, u8 page) |
109 | { |
110 | const u8 mauc_cmd2_page[] = { MCS_CMD_MAUCCTR, 0x55, 0xaa, 0x52, |
111 | 0x08, page }; |
112 | int ret; |
113 | |
114 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: mauc_cmd2_page, |
115 | ARRAY_SIZE(mauc_cmd2_page)); |
116 | if (ret < 0) |
117 | return ret; |
118 | |
119 | nt->last_page = page; |
120 | return 0; |
121 | } |
122 | |
123 | /* |
124 | * nt35950_set_data_compression - Set data compression mode |
125 | * @nt: Main driver structure |
126 | * @comp_mode: Compression mode |
127 | * |
128 | * Return: Number of transferred bytes or negative number on error |
129 | */ |
130 | static int nt35950_set_data_compression(struct nt35950 *nt, u8 comp_mode) |
131 | { |
132 | u8 cmd_data_compression[] = { MCS_PARAM_DATA_COMPRESSION, comp_mode }; |
133 | u8 cmd_vesa_dsc_on[] = { MCS_PARAM_VESA_DSC_ON, !!comp_mode }; |
134 | u8 cmd_vesa_dsc_setting[] = { MCS_PARAM_VESA_DSC_SETTING, 0x03 }; |
135 | u8 last_page = nt->last_page; |
136 | int ret; |
137 | |
138 | /* Set CMD2 Page 0 if we're not there yet */ |
139 | if (last_page != 0) { |
140 | ret = nt35950_set_cmd2_page(nt, page: 0); |
141 | if (ret < 0) |
142 | return ret; |
143 | } |
144 | |
145 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_data_compression, |
146 | ARRAY_SIZE(cmd_data_compression)); |
147 | if (ret < 0) |
148 | return ret; |
149 | |
150 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_vesa_dsc_on, |
151 | ARRAY_SIZE(cmd_vesa_dsc_on)); |
152 | if (ret < 0) |
153 | return ret; |
154 | |
155 | /* Set the vesa dsc setting on Page 4 */ |
156 | ret = nt35950_set_cmd2_page(nt, page: 4); |
157 | if (ret < 0) |
158 | return ret; |
159 | |
160 | /* Display Stream Compression setting, always 0x03 */ |
161 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_vesa_dsc_setting, |
162 | ARRAY_SIZE(cmd_vesa_dsc_setting)); |
163 | if (ret < 0) |
164 | return ret; |
165 | |
166 | /* Get back to the previously set page */ |
167 | return nt35950_set_cmd2_page(nt, page: last_page); |
168 | } |
169 | |
170 | /* |
171 | * nt35950_set_scaler - Enable/disable resolution upscaling |
172 | * @nt: Main driver structure |
173 | * @scale_up: Scale up function control |
174 | * |
175 | * Return: Number of transferred bytes or negative number on error |
176 | */ |
177 | static int nt35950_set_scaler(struct nt35950 *nt, u8 scale_up) |
178 | { |
179 | u8 cmd_scaler[] = { MCS_PARAM_SCALER_FUNCTION, scale_up }; |
180 | |
181 | return mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_scaler, |
182 | ARRAY_SIZE(cmd_scaler)); |
183 | } |
184 | |
185 | /* |
186 | * nt35950_set_scale_mode - Resolution upscaling mode |
187 | * @nt: Main driver structure |
188 | * @mode: Scaler mode (MCS_DATA_COMPRESSION_*) |
189 | * |
190 | * Return: Number of transferred bytes or negative number on error |
191 | */ |
192 | static int nt35950_set_scale_mode(struct nt35950 *nt, u8 mode) |
193 | { |
194 | u8 cmd_scaler[] = { MCS_PARAM_SCALEUP_MODE, mode }; |
195 | |
196 | return mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_scaler, |
197 | ARRAY_SIZE(cmd_scaler)); |
198 | } |
199 | |
200 | /* |
201 | * nt35950_inject_black_image - Display a completely black image |
202 | * @nt: Main driver structure |
203 | * |
204 | * After IC setup, the attached panel may show random data |
205 | * due to driveric behavior changes (resolution, compression, |
206 | * scaling, etc). This function, called after parameters setup, |
207 | * makes the driver ic to output a completely black image to |
208 | * the display. |
209 | * It makes sense to push a black image before sending the sleep-out |
210 | * and display-on commands. |
211 | * |
212 | * Return: Number of transferred bytes or negative number on error |
213 | */ |
214 | static int nt35950_inject_black_image(struct nt35950 *nt) |
215 | { |
216 | const u8 cmd0_black_img[] = { 0x6f, 0x01 }; |
217 | const u8 cmd1_black_img[] = { 0xf3, 0x10 }; |
218 | u8 cmd_test[] = { 0xff, 0xaa, 0x55, 0xa5, 0x80 }; |
219 | int ret; |
220 | |
221 | /* Enable test command */ |
222 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_test, ARRAY_SIZE(cmd_test)); |
223 | if (ret < 0) |
224 | return ret; |
225 | |
226 | /* Send a black image */ |
227 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd0_black_img, |
228 | ARRAY_SIZE(cmd0_black_img)); |
229 | if (ret < 0) |
230 | return ret; |
231 | ret = mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd1_black_img, |
232 | ARRAY_SIZE(cmd1_black_img)); |
233 | if (ret < 0) |
234 | return ret; |
235 | |
236 | /* Disable test command */ |
237 | cmd_test[ARRAY_SIZE(cmd_test) - 1] = 0x00; |
238 | return mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_test, ARRAY_SIZE(cmd_test)); |
239 | } |
240 | |
241 | /* |
242 | * nt35950_set_dispout - Set Display Output register parameters |
243 | * @nt: Main driver structure |
244 | * |
245 | * Return: Number of transferred bytes or negative number on error |
246 | */ |
247 | static int nt35950_set_dispout(struct nt35950 *nt) |
248 | { |
249 | u8 cmd_dispout[] = { MCS_PARAM_DISP_OUTPUT_CTRL, 0x00 }; |
250 | const struct nt35950_panel_mode *mode_data = nt->desc->mode_data; |
251 | |
252 | if (mode_data[nt->cur_mode].is_video_mode) |
253 | cmd_dispout[1] |= MCS_DISP_OUT_VIDEO_MODE; |
254 | if (mode_data[nt->cur_mode].enable_sram) |
255 | cmd_dispout[1] |= MCS_DISP_OUT_SRAM_EN; |
256 | |
257 | return mipi_dsi_dcs_write_buffer(dsi: nt->dsi[0], data: cmd_dispout, |
258 | ARRAY_SIZE(cmd_dispout)); |
259 | } |
260 | |
261 | static int nt35950_get_current_mode(struct nt35950 *nt) |
262 | { |
263 | struct drm_connector *connector = nt->connector; |
264 | struct drm_crtc_state *crtc_state; |
265 | int i; |
266 | |
267 | /* Return the default (first) mode if no info available yet */ |
268 | if (!connector->state || !connector->state->crtc) |
269 | return 0; |
270 | |
271 | crtc_state = connector->state->crtc->state; |
272 | |
273 | for (i = 0; i < nt->desc->num_modes; i++) { |
274 | if (drm_mode_match(mode1: &crtc_state->mode, |
275 | mode2: &nt->desc->mode_data[i].mode, |
276 | DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK)) |
277 | return i; |
278 | } |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | static int nt35950_on(struct nt35950 *nt) |
284 | { |
285 | const struct nt35950_panel_mode *mode_data = nt->desc->mode_data; |
286 | struct mipi_dsi_device *dsi = nt->dsi[0]; |
287 | struct device *dev = &dsi->dev; |
288 | int ret; |
289 | |
290 | nt->cur_mode = nt35950_get_current_mode(nt); |
291 | nt->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM; |
292 | nt->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; |
293 | |
294 | ret = nt35950_set_cmd2_page(nt, page: 0); |
295 | if (ret < 0) |
296 | return ret; |
297 | |
298 | ret = nt35950_set_data_compression(nt, comp_mode: mode_data[nt->cur_mode].compression); |
299 | if (ret < 0) |
300 | return ret; |
301 | |
302 | ret = nt35950_set_scale_mode(nt, mode: mode_data[nt->cur_mode].scaler_mode); |
303 | if (ret < 0) |
304 | return ret; |
305 | |
306 | ret = nt35950_set_scaler(nt, scale_up: mode_data[nt->cur_mode].scaler_on); |
307 | if (ret < 0) |
308 | return ret; |
309 | |
310 | ret = nt35950_set_dispout(nt); |
311 | if (ret < 0) |
312 | return ret; |
313 | |
314 | ret = mipi_dsi_dcs_set_tear_on(dsi, mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
315 | if (ret < 0) { |
316 | dev_err(dev, "Failed to set tear on: %d\n" , ret); |
317 | return ret; |
318 | } |
319 | |
320 | ret = mipi_dsi_dcs_set_tear_scanline(dsi, scanline: 0); |
321 | if (ret < 0) { |
322 | dev_err(dev, "Failed to set tear scanline: %d\n" , ret); |
323 | return ret; |
324 | } |
325 | |
326 | /* CMD2 Page 1 */ |
327 | ret = nt35950_set_cmd2_page(nt, page: 1); |
328 | if (ret < 0) |
329 | return ret; |
330 | |
331 | /* Unknown command */ |
332 | mipi_dsi_dcs_write_seq(dsi, 0xd4, 0x88, 0x88); |
333 | |
334 | /* CMD2 Page 7 */ |
335 | ret = nt35950_set_cmd2_page(nt, page: 7); |
336 | if (ret < 0) |
337 | return ret; |
338 | |
339 | /* Enable SubPixel Rendering */ |
340 | mipi_dsi_dcs_write_seq(dsi, MCS_PARAM_SPR_EN, 0x01); |
341 | |
342 | /* SPR Mode: YYG Rainbow-RGB */ |
343 | mipi_dsi_dcs_write_seq(dsi, MCS_PARAM_SPR_MODE, MCS_SPR_MODE_YYG_RAINBOW_RGB); |
344 | |
345 | /* CMD3 */ |
346 | ret = nt35950_inject_black_image(nt); |
347 | if (ret < 0) |
348 | return ret; |
349 | |
350 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
351 | if (ret < 0) |
352 | return ret; |
353 | msleep(msecs: 120); |
354 | |
355 | ret = mipi_dsi_dcs_set_display_on(dsi); |
356 | if (ret < 0) |
357 | return ret; |
358 | msleep(msecs: 120); |
359 | |
360 | nt->dsi[0]->mode_flags &= ~MIPI_DSI_MODE_LPM; |
361 | nt->dsi[1]->mode_flags &= ~MIPI_DSI_MODE_LPM; |
362 | |
363 | return 0; |
364 | } |
365 | |
366 | static int nt35950_off(struct nt35950 *nt) |
367 | { |
368 | struct device *dev = &nt->dsi[0]->dev; |
369 | int ret; |
370 | |
371 | ret = mipi_dsi_dcs_set_display_off(dsi: nt->dsi[0]); |
372 | if (ret < 0) { |
373 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
374 | goto set_lpm; |
375 | } |
376 | usleep_range(min: 10000, max: 11000); |
377 | |
378 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi: nt->dsi[0]); |
379 | if (ret < 0) { |
380 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
381 | goto set_lpm; |
382 | } |
383 | msleep(msecs: 150); |
384 | |
385 | set_lpm: |
386 | nt->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM; |
387 | nt->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM; |
388 | |
389 | return 0; |
390 | } |
391 | |
392 | static int nt35950_sharp_init_vregs(struct nt35950 *nt, struct device *dev) |
393 | { |
394 | int ret; |
395 | |
396 | nt->vregs[0].supply = "vddio" ; |
397 | nt->vregs[1].supply = "avdd" ; |
398 | nt->vregs[2].supply = "avee" ; |
399 | nt->vregs[3].supply = "dvdd" ; |
400 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(nt->vregs), |
401 | consumers: nt->vregs); |
402 | if (ret < 0) |
403 | return ret; |
404 | |
405 | ret = regulator_is_supported_voltage(regulator: nt->vregs[0].consumer, |
406 | min_uV: 1750000, max_uV: 1950000); |
407 | if (!ret) |
408 | return -EINVAL; |
409 | ret = regulator_is_supported_voltage(regulator: nt->vregs[1].consumer, |
410 | min_uV: 5200000, max_uV: 5900000); |
411 | if (!ret) |
412 | return -EINVAL; |
413 | /* AVEE is negative: -5.90V to -5.20V */ |
414 | ret = regulator_is_supported_voltage(regulator: nt->vregs[2].consumer, |
415 | min_uV: 5200000, max_uV: 5900000); |
416 | if (!ret) |
417 | return -EINVAL; |
418 | |
419 | ret = regulator_is_supported_voltage(regulator: nt->vregs[3].consumer, |
420 | min_uV: 1300000, max_uV: 1400000); |
421 | if (!ret) |
422 | return -EINVAL; |
423 | |
424 | return 0; |
425 | } |
426 | |
427 | static int nt35950_prepare(struct drm_panel *panel) |
428 | { |
429 | struct nt35950 *nt = to_nt35950(panel); |
430 | struct device *dev = &nt->dsi[0]->dev; |
431 | int ret; |
432 | |
433 | ret = regulator_enable(regulator: nt->vregs[0].consumer); |
434 | if (ret) |
435 | return ret; |
436 | usleep_range(min: 2000, max: 5000); |
437 | |
438 | ret = regulator_enable(regulator: nt->vregs[3].consumer); |
439 | if (ret) |
440 | goto end; |
441 | usleep_range(min: 15000, max: 18000); |
442 | |
443 | ret = regulator_enable(regulator: nt->vregs[1].consumer); |
444 | if (ret) |
445 | goto end; |
446 | |
447 | ret = regulator_enable(regulator: nt->vregs[2].consumer); |
448 | if (ret) |
449 | goto end; |
450 | usleep_range(min: 12000, max: 13000); |
451 | |
452 | nt35950_reset(nt); |
453 | |
454 | ret = nt35950_on(nt); |
455 | if (ret < 0) { |
456 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
457 | goto end; |
458 | } |
459 | |
460 | end: |
461 | if (ret < 0) { |
462 | regulator_bulk_disable(ARRAY_SIZE(nt->vregs), consumers: nt->vregs); |
463 | return ret; |
464 | } |
465 | |
466 | return 0; |
467 | } |
468 | |
469 | static int nt35950_unprepare(struct drm_panel *panel) |
470 | { |
471 | struct nt35950 *nt = to_nt35950(panel); |
472 | struct device *dev = &nt->dsi[0]->dev; |
473 | int ret; |
474 | |
475 | ret = nt35950_off(nt); |
476 | if (ret < 0) |
477 | dev_err(dev, "Failed to deinitialize panel: %d\n" , ret); |
478 | |
479 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 0); |
480 | regulator_bulk_disable(ARRAY_SIZE(nt->vregs), consumers: nt->vregs); |
481 | |
482 | return 0; |
483 | } |
484 | |
485 | static int nt35950_get_modes(struct drm_panel *panel, |
486 | struct drm_connector *connector) |
487 | { |
488 | struct nt35950 *nt = to_nt35950(panel); |
489 | int i; |
490 | |
491 | for (i = 0; i < nt->desc->num_modes; i++) { |
492 | struct drm_display_mode *mode; |
493 | |
494 | mode = drm_mode_duplicate(dev: connector->dev, |
495 | mode: &nt->desc->mode_data[i].mode); |
496 | if (!mode) |
497 | return -ENOMEM; |
498 | |
499 | drm_mode_set_name(mode); |
500 | |
501 | mode->type |= DRM_MODE_TYPE_DRIVER; |
502 | if (nt->desc->num_modes == 1) |
503 | mode->type |= DRM_MODE_TYPE_PREFERRED; |
504 | |
505 | drm_mode_probed_add(connector, mode); |
506 | } |
507 | |
508 | connector->display_info.bpc = 8; |
509 | connector->display_info.height_mm = nt->desc->mode_data[0].mode.height_mm; |
510 | connector->display_info.width_mm = nt->desc->mode_data[0].mode.width_mm; |
511 | nt->connector = connector; |
512 | |
513 | return nt->desc->num_modes; |
514 | } |
515 | |
516 | static const struct drm_panel_funcs nt35950_panel_funcs = { |
517 | .prepare = nt35950_prepare, |
518 | .unprepare = nt35950_unprepare, |
519 | .get_modes = nt35950_get_modes, |
520 | }; |
521 | |
522 | static int nt35950_probe(struct mipi_dsi_device *dsi) |
523 | { |
524 | struct device *dev = &dsi->dev; |
525 | struct device_node *dsi_r; |
526 | struct mipi_dsi_host *dsi_r_host; |
527 | struct nt35950 *nt; |
528 | const struct mipi_dsi_device_info *info; |
529 | int i, num_dsis = 1, ret; |
530 | |
531 | nt = devm_kzalloc(dev, size: sizeof(*nt), GFP_KERNEL); |
532 | if (!nt) |
533 | return -ENOMEM; |
534 | |
535 | ret = nt35950_sharp_init_vregs(nt, dev); |
536 | if (ret) |
537 | return dev_err_probe(dev, err: ret, fmt: "Regulator init failure.\n" ); |
538 | |
539 | nt->desc = of_device_get_match_data(dev); |
540 | if (!nt->desc) |
541 | return -ENODEV; |
542 | |
543 | nt->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_ASIS); |
544 | if (IS_ERR(ptr: nt->reset_gpio)) { |
545 | return dev_err_probe(dev, err: PTR_ERR(ptr: nt->reset_gpio), |
546 | fmt: "Failed to get reset gpio\n" ); |
547 | } |
548 | |
549 | /* If the panel is connected on two DSIs then DSI0 left, DSI1 right */ |
550 | if (nt->desc->is_dual_dsi) { |
551 | info = &nt->desc->dsi_info; |
552 | dsi_r = of_graph_get_remote_node(node: dsi->dev.of_node, port: 1, endpoint: -1); |
553 | if (!dsi_r) { |
554 | dev_err(dev, "Cannot get secondary DSI node.\n" ); |
555 | return -ENODEV; |
556 | } |
557 | dsi_r_host = of_find_mipi_dsi_host_by_node(node: dsi_r); |
558 | of_node_put(node: dsi_r); |
559 | if (!dsi_r_host) { |
560 | dev_err(dev, "Cannot get secondary DSI host\n" ); |
561 | return -EPROBE_DEFER; |
562 | } |
563 | |
564 | nt->dsi[1] = mipi_dsi_device_register_full(host: dsi_r_host, info); |
565 | if (!nt->dsi[1]) { |
566 | dev_err(dev, "Cannot get secondary DSI node\n" ); |
567 | return -ENODEV; |
568 | } |
569 | num_dsis++; |
570 | } |
571 | |
572 | nt->dsi[0] = dsi; |
573 | mipi_dsi_set_drvdata(dsi, data: nt); |
574 | |
575 | drm_panel_init(panel: &nt->panel, dev, funcs: &nt35950_panel_funcs, |
576 | DRM_MODE_CONNECTOR_DSI); |
577 | |
578 | ret = drm_panel_of_backlight(panel: &nt->panel); |
579 | if (ret) { |
580 | if (num_dsis == 2) |
581 | mipi_dsi_device_unregister(dsi: nt->dsi[1]); |
582 | |
583 | return dev_err_probe(dev, err: ret, fmt: "Failed to get backlight\n" ); |
584 | } |
585 | |
586 | drm_panel_add(panel: &nt->panel); |
587 | |
588 | for (i = 0; i < num_dsis; i++) { |
589 | nt->dsi[i]->lanes = nt->desc->num_lanes; |
590 | nt->dsi[i]->format = MIPI_DSI_FMT_RGB888; |
591 | |
592 | nt->dsi[i]->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | |
593 | MIPI_DSI_MODE_LPM; |
594 | |
595 | if (nt->desc->mode_data[0].is_video_mode) |
596 | nt->dsi[i]->mode_flags |= MIPI_DSI_MODE_VIDEO; |
597 | |
598 | ret = mipi_dsi_attach(dsi: nt->dsi[i]); |
599 | if (ret < 0) { |
600 | /* If we fail to attach to either host, we're done */ |
601 | if (num_dsis == 2) |
602 | mipi_dsi_device_unregister(dsi: nt->dsi[1]); |
603 | |
604 | return dev_err_probe(dev, err: ret, |
605 | fmt: "Cannot attach to DSI%d host.\n" , i); |
606 | } |
607 | } |
608 | |
609 | /* Make sure to set RESX LOW before starting the power-on sequence */ |
610 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 0); |
611 | return 0; |
612 | } |
613 | |
614 | static void nt35950_remove(struct mipi_dsi_device *dsi) |
615 | { |
616 | struct nt35950 *nt = mipi_dsi_get_drvdata(dsi); |
617 | int ret; |
618 | |
619 | ret = mipi_dsi_detach(dsi: nt->dsi[0]); |
620 | if (ret < 0) |
621 | dev_err(&dsi->dev, |
622 | "Failed to detach from DSI0 host: %d\n" , ret); |
623 | |
624 | if (nt->dsi[1]) { |
625 | ret = mipi_dsi_detach(dsi: nt->dsi[1]); |
626 | if (ret < 0) |
627 | dev_err(&dsi->dev, |
628 | "Failed to detach from DSI1 host: %d\n" , ret); |
629 | mipi_dsi_device_unregister(dsi: nt->dsi[1]); |
630 | } |
631 | |
632 | drm_panel_remove(panel: &nt->panel); |
633 | } |
634 | |
635 | static const struct nt35950_panel_mode sharp_ls055d1sx04_modes[] = { |
636 | { |
637 | /* 1920x1080 60Hz no compression */ |
638 | .mode = { |
639 | .clock = 214537, |
640 | .hdisplay = 1080, |
641 | .hsync_start = 1080 + 400, |
642 | .hsync_end = 1080 + 400 + 40, |
643 | .htotal = 1080 + 400 + 40 + 300, |
644 | .vdisplay = 1920, |
645 | .vsync_start = 1920 + 12, |
646 | .vsync_end = 1920 + 12 + 2, |
647 | .vtotal = 1920 + 12 + 2 + 10, |
648 | .width_mm = 68, |
649 | .height_mm = 121, |
650 | }, |
651 | .compression = MCS_DATA_COMPRESSION_NONE, |
652 | .enable_sram = true, |
653 | .is_video_mode = false, |
654 | .scaler_on = 1, |
655 | .scaler_mode = MCS_SCALEUP_DUPLICATE, |
656 | }, |
657 | /* TODO: Add 2160x3840 60Hz when DSC is supported */ |
658 | }; |
659 | |
660 | static const struct nt35950_panel_desc sharp_ls055d1sx04 = { |
661 | .model_name = "Sharp LS055D1SX04" , |
662 | .dsi_info = { |
663 | .type = "LS055D1SX04" , |
664 | .channel = 0, |
665 | .node = NULL, |
666 | }, |
667 | .mode_data = sharp_ls055d1sx04_modes, |
668 | .num_modes = ARRAY_SIZE(sharp_ls055d1sx04_modes), |
669 | .is_dual_dsi = true, |
670 | .num_lanes = 4, |
671 | }; |
672 | |
673 | static const struct of_device_id nt35950_of_match[] = { |
674 | { .compatible = "sharp,ls055d1sx04" , .data = &sharp_ls055d1sx04 }, |
675 | { } |
676 | }; |
677 | MODULE_DEVICE_TABLE(of, nt35950_of_match); |
678 | |
679 | static struct mipi_dsi_driver nt35950_driver = { |
680 | .probe = nt35950_probe, |
681 | .remove = nt35950_remove, |
682 | .driver = { |
683 | .name = "panel-novatek-nt35950" , |
684 | .of_match_table = nt35950_of_match, |
685 | }, |
686 | }; |
687 | module_mipi_dsi_driver(nt35950_driver); |
688 | |
689 | MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>" ); |
690 | MODULE_DESCRIPTION("Novatek NT35950 DriverIC panels driver" ); |
691 | MODULE_LICENSE("GPL v2" ); |
692 | |