1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2014 Google, Inc. |
4 | * |
5 | * Copyright (C) 2022 Diogo Ivo <diogo.ivo@tecnico.ulisboa.pt> |
6 | * |
7 | * Adapted from the downstream Pixel C driver written by Sean Paul |
8 | */ |
9 | |
10 | #include <linux/backlight.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/regulator/consumer.h> |
16 | |
17 | #include <video/mipi_display.h> |
18 | |
19 | #include <drm/drm_crtc.h> |
20 | #include <drm/drm_mipi_dsi.h> |
21 | #include <drm/drm_panel.h> |
22 | |
23 | #define MCS_CMD_ACS_PROT 0xB0 |
24 | #define MCS_CMD_ACS_PROT_OFF (0 << 0) |
25 | |
26 | #define MCS_PWR_CTRL_FUNC 0xD0 |
27 | #define MCS_PWR_CTRL_PARAM1_DEFAULT (2 << 0) |
28 | #define MCS_PWR_CTRL_PARAM1_VGH_210_DIV (1 << 4) |
29 | #define MCS_PWR_CTRL_PARAM1_VGH_240_DIV (2 << 4) |
30 | #define MCS_PWR_CTRL_PARAM1_VGH_280_DIV (3 << 4) |
31 | #define MCS_PWR_CTRL_PARAM1_VGH_330_DIV (4 << 4) |
32 | #define MCS_PWR_CTRL_PARAM1_VGH_410_DIV (5 << 4) |
33 | #define MCS_PWR_CTRL_PARAM2_DEFAULT (9 << 4) |
34 | #define MCS_PWR_CTRL_PARAM2_VGL_210_DIV (1 << 0) |
35 | #define MCS_PWR_CTRL_PARAM2_VGL_240_DIV (2 << 0) |
36 | #define MCS_PWR_CTRL_PARAM2_VGL_280_DIV (3 << 0) |
37 | #define MCS_PWR_CTRL_PARAM2_VGL_330_DIV (4 << 0) |
38 | #define MCS_PWR_CTRL_PARAM2_VGL_410_DIV (5 << 0) |
39 | |
40 | struct jdi_panel { |
41 | struct drm_panel base; |
42 | struct mipi_dsi_device *link1; |
43 | struct mipi_dsi_device *link2; |
44 | |
45 | struct regulator *supply; |
46 | struct regulator *ddi_supply; |
47 | struct backlight_device *backlight; |
48 | |
49 | struct gpio_desc *enable_gpio; |
50 | struct gpio_desc *reset_gpio; |
51 | |
52 | const struct drm_display_mode *mode; |
53 | }; |
54 | |
55 | static inline struct jdi_panel *to_panel_jdi(struct drm_panel *panel) |
56 | { |
57 | return container_of(panel, struct jdi_panel, base); |
58 | } |
59 | |
60 | static void jdi_wait_frames(struct jdi_panel *jdi, unsigned int frames) |
61 | { |
62 | unsigned int refresh = drm_mode_vrefresh(mode: jdi->mode); |
63 | |
64 | if (WARN_ON(frames > refresh)) |
65 | return; |
66 | |
67 | msleep(msecs: 1000 / (refresh / frames)); |
68 | } |
69 | |
70 | static int jdi_panel_disable(struct drm_panel *panel) |
71 | { |
72 | struct jdi_panel *jdi = to_panel_jdi(panel); |
73 | |
74 | backlight_disable(bd: jdi->backlight); |
75 | |
76 | jdi_wait_frames(jdi, frames: 2); |
77 | |
78 | return 0; |
79 | } |
80 | |
81 | static int jdi_panel_unprepare(struct drm_panel *panel) |
82 | { |
83 | struct jdi_panel *jdi = to_panel_jdi(panel); |
84 | int ret; |
85 | |
86 | ret = mipi_dsi_dcs_set_display_off(dsi: jdi->link1); |
87 | if (ret < 0) |
88 | dev_err(panel->dev, "failed to set display off: %d\n" , ret); |
89 | |
90 | ret = mipi_dsi_dcs_set_display_off(dsi: jdi->link2); |
91 | if (ret < 0) |
92 | dev_err(panel->dev, "failed to set display off: %d\n" , ret); |
93 | |
94 | /* Specified by JDI @ 50ms, subject to change */ |
95 | msleep(msecs: 50); |
96 | |
97 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi: jdi->link1); |
98 | if (ret < 0) |
99 | dev_err(panel->dev, "failed to enter sleep mode: %d\n" , ret); |
100 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi: jdi->link2); |
101 | if (ret < 0) |
102 | dev_err(panel->dev, "failed to enter sleep mode: %d\n" , ret); |
103 | |
104 | /* Specified by JDI @ 150ms, subject to change */ |
105 | msleep(msecs: 150); |
106 | |
107 | gpiod_set_value(desc: jdi->reset_gpio, value: 1); |
108 | |
109 | /* T4 = 1ms */ |
110 | usleep_range(min: 1000, max: 3000); |
111 | |
112 | gpiod_set_value(desc: jdi->enable_gpio, value: 0); |
113 | |
114 | /* T5 = 2ms */ |
115 | usleep_range(min: 2000, max: 4000); |
116 | |
117 | regulator_disable(regulator: jdi->ddi_supply); |
118 | |
119 | /* T6 = 2ms plus some time to discharge capacitors */ |
120 | usleep_range(min: 7000, max: 9000); |
121 | |
122 | regulator_disable(regulator: jdi->supply); |
123 | /* Specified by JDI @ 20ms, subject to change */ |
124 | msleep(msecs: 20); |
125 | |
126 | return ret; |
127 | } |
128 | |
129 | static int jdi_setup_symmetrical_split(struct mipi_dsi_device *left, |
130 | struct mipi_dsi_device *right, |
131 | const struct drm_display_mode *mode) |
132 | { |
133 | int err; |
134 | |
135 | err = mipi_dsi_dcs_set_column_address(dsi: left, start: 0, end: mode->hdisplay / 2 - 1); |
136 | if (err < 0) { |
137 | dev_err(&left->dev, "failed to set column address: %d\n" , err); |
138 | return err; |
139 | } |
140 | |
141 | err = mipi_dsi_dcs_set_column_address(dsi: right, start: 0, end: mode->hdisplay / 2 - 1); |
142 | if (err < 0) { |
143 | dev_err(&right->dev, "failed to set column address: %d\n" , err); |
144 | return err; |
145 | } |
146 | |
147 | err = mipi_dsi_dcs_set_page_address(dsi: left, start: 0, end: mode->vdisplay - 1); |
148 | if (err < 0) { |
149 | dev_err(&left->dev, "failed to set page address: %d\n" , err); |
150 | return err; |
151 | } |
152 | |
153 | err = mipi_dsi_dcs_set_page_address(dsi: right, start: 0, end: mode->vdisplay - 1); |
154 | if (err < 0) { |
155 | dev_err(&right->dev, "failed to set page address: %d\n" , err); |
156 | return err; |
157 | } |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | static int jdi_write_dcdc_registers(struct jdi_panel *jdi) |
163 | { |
164 | /* Clear the manufacturer command access protection */ |
165 | mipi_dsi_generic_write_seq(jdi->link1, MCS_CMD_ACS_PROT, |
166 | MCS_CMD_ACS_PROT_OFF); |
167 | mipi_dsi_generic_write_seq(jdi->link2, MCS_CMD_ACS_PROT, |
168 | MCS_CMD_ACS_PROT_OFF); |
169 | /* |
170 | * Change the VGH/VGL divide rations to move the noise generated by the |
171 | * TCONN. This should hopefully avoid interaction with the backlight |
172 | * controller. |
173 | */ |
174 | mipi_dsi_generic_write_seq(jdi->link1, MCS_PWR_CTRL_FUNC, |
175 | MCS_PWR_CTRL_PARAM1_VGH_330_DIV | |
176 | MCS_PWR_CTRL_PARAM1_DEFAULT, |
177 | MCS_PWR_CTRL_PARAM2_VGL_410_DIV | |
178 | MCS_PWR_CTRL_PARAM2_DEFAULT); |
179 | |
180 | mipi_dsi_generic_write_seq(jdi->link2, MCS_PWR_CTRL_FUNC, |
181 | MCS_PWR_CTRL_PARAM1_VGH_330_DIV | |
182 | MCS_PWR_CTRL_PARAM1_DEFAULT, |
183 | MCS_PWR_CTRL_PARAM2_VGL_410_DIV | |
184 | MCS_PWR_CTRL_PARAM2_DEFAULT); |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static int jdi_panel_prepare(struct drm_panel *panel) |
190 | { |
191 | struct jdi_panel *jdi = to_panel_jdi(panel); |
192 | int err; |
193 | |
194 | /* Disable backlight to avoid showing random pixels |
195 | * with a conservative delay for it to take effect. |
196 | */ |
197 | backlight_disable(bd: jdi->backlight); |
198 | jdi_wait_frames(jdi, frames: 3); |
199 | |
200 | jdi->link1->mode_flags |= MIPI_DSI_MODE_LPM; |
201 | jdi->link2->mode_flags |= MIPI_DSI_MODE_LPM; |
202 | |
203 | err = regulator_enable(regulator: jdi->supply); |
204 | if (err < 0) { |
205 | dev_err(panel->dev, "failed to enable supply: %d\n" , err); |
206 | return err; |
207 | } |
208 | /* T1 = 2ms */ |
209 | usleep_range(min: 2000, max: 4000); |
210 | |
211 | err = regulator_enable(regulator: jdi->ddi_supply); |
212 | if (err < 0) { |
213 | dev_err(panel->dev, "failed to enable ddi_supply: %d\n" , err); |
214 | goto supply_off; |
215 | } |
216 | /* T2 = 1ms */ |
217 | usleep_range(min: 1000, max: 3000); |
218 | |
219 | gpiod_set_value(desc: jdi->enable_gpio, value: 1); |
220 | /* T3 = 10ms */ |
221 | usleep_range(min: 10000, max: 15000); |
222 | |
223 | gpiod_set_value(desc: jdi->reset_gpio, value: 0); |
224 | /* Specified by JDI @ 3ms, subject to change */ |
225 | usleep_range(min: 3000, max: 5000); |
226 | |
227 | /* |
228 | * TODO: The device supports both left-right and even-odd split |
229 | * configurations, but this driver currently supports only the left- |
230 | * right split. To support a different mode a mechanism needs to be |
231 | * put in place to communicate the configuration back to the DSI host |
232 | * controller. |
233 | */ |
234 | err = jdi_setup_symmetrical_split(left: jdi->link1, right: jdi->link2, |
235 | mode: jdi->mode); |
236 | if (err < 0) { |
237 | dev_err(panel->dev, "failed to set up symmetrical split: %d\n" , |
238 | err); |
239 | goto poweroff; |
240 | } |
241 | |
242 | err = mipi_dsi_dcs_set_tear_scanline(dsi: jdi->link1, |
243 | scanline: jdi->mode->vdisplay - 16); |
244 | if (err < 0) { |
245 | dev_err(panel->dev, "failed to set tear scanline: %d\n" , err); |
246 | goto poweroff; |
247 | } |
248 | |
249 | err = mipi_dsi_dcs_set_tear_scanline(dsi: jdi->link2, |
250 | scanline: jdi->mode->vdisplay - 16); |
251 | if (err < 0) { |
252 | dev_err(panel->dev, "failed to set tear scanline: %d\n" , err); |
253 | goto poweroff; |
254 | } |
255 | |
256 | err = mipi_dsi_dcs_set_tear_on(dsi: jdi->link1, |
257 | mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
258 | if (err < 0) { |
259 | dev_err(panel->dev, "failed to set tear on: %d\n" , err); |
260 | goto poweroff; |
261 | } |
262 | |
263 | err = mipi_dsi_dcs_set_tear_on(dsi: jdi->link2, |
264 | mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
265 | if (err < 0) { |
266 | dev_err(panel->dev, "failed to set tear on: %d\n" , err); |
267 | goto poweroff; |
268 | } |
269 | |
270 | err = mipi_dsi_dcs_set_pixel_format(dsi: jdi->link1, MIPI_DCS_PIXEL_FMT_24BIT); |
271 | if (err < 0) { |
272 | dev_err(panel->dev, "failed to set pixel format: %d\n" , err); |
273 | goto poweroff; |
274 | } |
275 | |
276 | err = mipi_dsi_dcs_set_pixel_format(dsi: jdi->link2, MIPI_DCS_PIXEL_FMT_24BIT); |
277 | if (err < 0) { |
278 | dev_err(panel->dev, "failed to set pixel format: %d\n" , err); |
279 | goto poweroff; |
280 | } |
281 | |
282 | err = mipi_dsi_dcs_exit_sleep_mode(dsi: jdi->link1); |
283 | if (err < 0) { |
284 | dev_err(panel->dev, "failed to exit sleep mode: %d\n" , err); |
285 | goto poweroff; |
286 | } |
287 | |
288 | err = mipi_dsi_dcs_exit_sleep_mode(dsi: jdi->link2); |
289 | if (err < 0) { |
290 | dev_err(panel->dev, "failed to exit sleep mode: %d\n" , err); |
291 | goto poweroff; |
292 | } |
293 | |
294 | err = jdi_write_dcdc_registers(jdi); |
295 | if (err < 0) { |
296 | dev_err(panel->dev, "failed to write dcdc registers: %d\n" , err); |
297 | goto poweroff; |
298 | } |
299 | /* |
300 | * We need to wait 150ms between mipi_dsi_dcs_exit_sleep_mode() and |
301 | * mipi_dsi_dcs_set_display_on(). |
302 | */ |
303 | msleep(msecs: 150); |
304 | |
305 | err = mipi_dsi_dcs_set_display_on(dsi: jdi->link1); |
306 | if (err < 0) { |
307 | dev_err(panel->dev, "failed to set display on: %d\n" , err); |
308 | goto poweroff; |
309 | } |
310 | |
311 | err = mipi_dsi_dcs_set_display_on(dsi: jdi->link2); |
312 | if (err < 0) { |
313 | dev_err(panel->dev, "failed to set display on: %d\n" , err); |
314 | goto poweroff; |
315 | } |
316 | |
317 | jdi->link1->mode_flags &= ~MIPI_DSI_MODE_LPM; |
318 | jdi->link2->mode_flags &= ~MIPI_DSI_MODE_LPM; |
319 | |
320 | return 0; |
321 | |
322 | poweroff: |
323 | regulator_disable(regulator: jdi->ddi_supply); |
324 | |
325 | /* T6 = 2ms plus some time to discharge capacitors */ |
326 | usleep_range(min: 7000, max: 9000); |
327 | supply_off: |
328 | regulator_disable(regulator: jdi->supply); |
329 | /* Specified by JDI @ 20ms, subject to change */ |
330 | msleep(msecs: 20); |
331 | |
332 | return err; |
333 | } |
334 | |
335 | static int jdi_panel_enable(struct drm_panel *panel) |
336 | { |
337 | struct jdi_panel *jdi = to_panel_jdi(panel); |
338 | |
339 | /* |
340 | * Ensure we send image data before turning the backlight |
341 | * on, to avoid the display showing random pixels. |
342 | */ |
343 | jdi_wait_frames(jdi, frames: 3); |
344 | |
345 | backlight_enable(bd: jdi->backlight); |
346 | |
347 | return 0; |
348 | } |
349 | |
350 | static const struct drm_display_mode default_mode = { |
351 | .clock = (2560 + 80 + 80 + 80) * (1800 + 4 + 4 + 4) * 60 / 1000, |
352 | .hdisplay = 2560, |
353 | .hsync_start = 2560 + 80, |
354 | .hsync_end = 2560 + 80 + 80, |
355 | .htotal = 2560 + 80 + 80 + 80, |
356 | .vdisplay = 1800, |
357 | .vsync_start = 1800 + 4, |
358 | .vsync_end = 1800 + 4 + 4, |
359 | .vtotal = 1800 + 4 + 4 + 4, |
360 | .flags = 0, |
361 | }; |
362 | |
363 | static int jdi_panel_get_modes(struct drm_panel *panel, |
364 | struct drm_connector *connector) |
365 | { |
366 | struct drm_display_mode *mode; |
367 | struct jdi_panel *jdi = to_panel_jdi(panel); |
368 | struct device *dev = &jdi->link1->dev; |
369 | |
370 | mode = drm_mode_duplicate(dev: connector->dev, mode: &default_mode); |
371 | if (!mode) { |
372 | dev_err(dev, "failed to add mode %ux%ux@%u\n" , |
373 | default_mode.hdisplay, default_mode.vdisplay, |
374 | drm_mode_vrefresh(&default_mode)); |
375 | return -ENOMEM; |
376 | } |
377 | |
378 | drm_mode_set_name(mode); |
379 | |
380 | drm_mode_probed_add(connector, mode); |
381 | |
382 | connector->display_info.width_mm = 211; |
383 | connector->display_info.height_mm = 148; |
384 | connector->display_info.bpc = 8; |
385 | |
386 | return 1; |
387 | } |
388 | |
389 | static const struct drm_panel_funcs jdi_panel_funcs = { |
390 | .prepare = jdi_panel_prepare, |
391 | .enable = jdi_panel_enable, |
392 | .disable = jdi_panel_disable, |
393 | .unprepare = jdi_panel_unprepare, |
394 | .get_modes = jdi_panel_get_modes, |
395 | }; |
396 | |
397 | static const struct of_device_id jdi_of_match[] = { |
398 | { .compatible = "jdi,lpm102a188a" , }, |
399 | { } |
400 | }; |
401 | MODULE_DEVICE_TABLE(of, jdi_of_match); |
402 | |
403 | static int jdi_panel_add(struct jdi_panel *jdi) |
404 | { |
405 | struct device *dev = &jdi->link1->dev; |
406 | |
407 | jdi->mode = &default_mode; |
408 | |
409 | jdi->supply = devm_regulator_get(dev, id: "power" ); |
410 | if (IS_ERR(ptr: jdi->supply)) |
411 | return dev_err_probe(dev, err: PTR_ERR(ptr: jdi->supply), |
412 | fmt: "failed to get power regulator\n" ); |
413 | |
414 | jdi->ddi_supply = devm_regulator_get(dev, id: "ddi" ); |
415 | if (IS_ERR(ptr: jdi->ddi_supply)) |
416 | return dev_err_probe(dev, err: PTR_ERR(ptr: jdi->ddi_supply), |
417 | fmt: "failed to get ddi regulator\n" ); |
418 | |
419 | jdi->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
420 | if (IS_ERR(ptr: jdi->reset_gpio)) |
421 | return dev_err_probe(dev, err: PTR_ERR(ptr: jdi->reset_gpio), |
422 | fmt: "failed to get reset gpio\n" ); |
423 | /* T4 = 1ms */ |
424 | usleep_range(min: 1000, max: 3000); |
425 | |
426 | jdi->enable_gpio = devm_gpiod_get(dev, con_id: "enable" , flags: GPIOD_OUT_LOW); |
427 | if (IS_ERR(ptr: jdi->enable_gpio)) |
428 | return dev_err_probe(dev, err: PTR_ERR(ptr: jdi->enable_gpio), |
429 | fmt: "failed to get enable gpio\n" ); |
430 | /* T5 = 2ms */ |
431 | usleep_range(min: 2000, max: 4000); |
432 | |
433 | jdi->backlight = devm_of_find_backlight(dev); |
434 | if (IS_ERR(ptr: jdi->backlight)) |
435 | return dev_err_probe(dev, err: PTR_ERR(ptr: jdi->backlight), |
436 | fmt: "failed to create backlight\n" ); |
437 | |
438 | drm_panel_init(panel: &jdi->base, dev: &jdi->link1->dev, funcs: &jdi_panel_funcs, |
439 | DRM_MODE_CONNECTOR_DSI); |
440 | |
441 | drm_panel_add(panel: &jdi->base); |
442 | |
443 | return 0; |
444 | } |
445 | |
446 | static void jdi_panel_del(struct jdi_panel *jdi) |
447 | { |
448 | if (jdi->base.dev) |
449 | drm_panel_remove(panel: &jdi->base); |
450 | |
451 | if (jdi->link2) |
452 | put_device(dev: &jdi->link2->dev); |
453 | } |
454 | |
455 | static int jdi_panel_dsi_probe(struct mipi_dsi_device *dsi) |
456 | { |
457 | struct mipi_dsi_device *secondary = NULL; |
458 | struct jdi_panel *jdi; |
459 | struct device_node *np; |
460 | int err; |
461 | |
462 | dsi->lanes = 4; |
463 | dsi->format = MIPI_DSI_FMT_RGB888; |
464 | dsi->mode_flags = 0; |
465 | |
466 | /* Find DSI-LINK1 */ |
467 | np = of_parse_phandle(np: dsi->dev.of_node, phandle_name: "link2" , index: 0); |
468 | if (np) { |
469 | secondary = of_find_mipi_dsi_device_by_node(np); |
470 | of_node_put(node: np); |
471 | |
472 | if (!secondary) |
473 | return -EPROBE_DEFER; |
474 | } |
475 | |
476 | /* register a panel for only the DSI-LINK1 interface */ |
477 | if (secondary) { |
478 | jdi = devm_kzalloc(dev: &dsi->dev, size: sizeof(*jdi), GFP_KERNEL); |
479 | if (!jdi) { |
480 | put_device(dev: &secondary->dev); |
481 | return -ENOMEM; |
482 | } |
483 | |
484 | mipi_dsi_set_drvdata(dsi, data: jdi); |
485 | |
486 | jdi->link1 = dsi; |
487 | jdi->link2 = secondary; |
488 | |
489 | err = jdi_panel_add(jdi); |
490 | if (err < 0) { |
491 | put_device(dev: &secondary->dev); |
492 | return err; |
493 | } |
494 | } |
495 | |
496 | err = mipi_dsi_attach(dsi); |
497 | if (err < 0) { |
498 | if (secondary) |
499 | jdi_panel_del(jdi); |
500 | |
501 | return err; |
502 | } |
503 | |
504 | return 0; |
505 | } |
506 | |
507 | static void jdi_panel_dsi_remove(struct mipi_dsi_device *dsi) |
508 | { |
509 | struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); |
510 | int err; |
511 | |
512 | /* only detach from host for the DSI-LINK2 interface */ |
513 | if (!jdi) |
514 | mipi_dsi_detach(dsi); |
515 | |
516 | err = jdi_panel_disable(panel: &jdi->base); |
517 | if (err < 0) |
518 | dev_err(&dsi->dev, "failed to disable panel: %d\n" , err); |
519 | |
520 | err = mipi_dsi_detach(dsi); |
521 | if (err < 0) |
522 | dev_err(&dsi->dev, "failed to detach from DSI host: %d\n" , err); |
523 | |
524 | jdi_panel_del(jdi); |
525 | } |
526 | |
527 | static void jdi_panel_dsi_shutdown(struct mipi_dsi_device *dsi) |
528 | { |
529 | struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); |
530 | |
531 | if (!jdi) |
532 | return; |
533 | |
534 | jdi_panel_disable(panel: &jdi->base); |
535 | } |
536 | |
537 | static struct mipi_dsi_driver jdi_panel_dsi_driver = { |
538 | .driver = { |
539 | .name = "panel-jdi-lpm102a188a" , |
540 | .of_match_table = jdi_of_match, |
541 | }, |
542 | .probe = jdi_panel_dsi_probe, |
543 | .remove = jdi_panel_dsi_remove, |
544 | .shutdown = jdi_panel_dsi_shutdown, |
545 | }; |
546 | module_mipi_dsi_driver(jdi_panel_dsi_driver); |
547 | |
548 | MODULE_AUTHOR("Sean Paul <seanpaul@chromium.org>" ); |
549 | MODULE_AUTHOR("Diogo Ivo <diogo.ivo@tecnico.ulisboa.pt>" ); |
550 | MODULE_DESCRIPTION("DRM Driver for JDI LPM102A188A DSI panel, command mode" ); |
551 | MODULE_LICENSE("GPL" ); |
552 | |