1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * MIPI-DSI Novatek NT35560-based panel controller. |
4 | * |
5 | * Supported panels include: |
6 | * Sony ACX424AKM - a 480x854 AMOLED DSI panel |
7 | * Sony ACX424AKP - a 480x864 AMOLED DSI panel |
8 | * |
9 | * Copyright (C) Linaro Ltd. 2019-2021 |
10 | * Author: Linus Walleij |
11 | * Based on code and know-how from Marcus Lorentzon |
12 | * Copyright (C) ST-Ericsson SA 2010 |
13 | * Based on code and know-how from Johan Olson and Joakim Wesslen |
14 | * Copyright (C) Sony Ericsson Mobile Communications 2010 |
15 | */ |
16 | #include <linux/backlight.h> |
17 | #include <linux/delay.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of.h> |
21 | #include <linux/regulator/consumer.h> |
22 | |
23 | #include <video/mipi_display.h> |
24 | |
25 | #include <drm/drm_mipi_dsi.h> |
26 | #include <drm/drm_modes.h> |
27 | #include <drm/drm_panel.h> |
28 | |
29 | #define NT35560_DCS_READ_ID1 0xDA |
30 | #define NT35560_DCS_READ_ID2 0xDB |
31 | #define NT35560_DCS_READ_ID3 0xDC |
32 | #define NT35560_DCS_SET_MDDI 0xAE |
33 | |
34 | /* |
35 | * Sony seems to use vendor ID 0x81 |
36 | */ |
37 | #define DISPLAY_SONY_ACX424AKP_ID1 0x8103 |
38 | #define DISPLAY_SONY_ACX424AKP_ID2 0x811a |
39 | #define DISPLAY_SONY_ACX424AKP_ID3 0x811b |
40 | /* |
41 | * The fourth ID looks like a bug, vendor IDs begin at 0x80 |
42 | * and panel 00 ... seems like default values. |
43 | */ |
44 | #define DISPLAY_SONY_ACX424AKP_ID4 0x8000 |
45 | |
46 | struct nt35560_config { |
47 | const struct drm_display_mode *vid_mode; |
48 | const struct drm_display_mode *cmd_mode; |
49 | }; |
50 | |
51 | struct nt35560 { |
52 | const struct nt35560_config *conf; |
53 | struct drm_panel panel; |
54 | struct device *dev; |
55 | struct regulator *supply; |
56 | struct gpio_desc *reset_gpio; |
57 | bool video_mode; |
58 | }; |
59 | |
60 | static const struct drm_display_mode sony_acx424akp_vid_mode = { |
61 | .clock = 27234, |
62 | .hdisplay = 480, |
63 | .hsync_start = 480 + 15, |
64 | .hsync_end = 480 + 15 + 0, |
65 | .htotal = 480 + 15 + 0 + 15, |
66 | .vdisplay = 864, |
67 | .vsync_start = 864 + 14, |
68 | .vsync_end = 864 + 14 + 1, |
69 | .vtotal = 864 + 14 + 1 + 11, |
70 | .width_mm = 48, |
71 | .height_mm = 84, |
72 | .flags = DRM_MODE_FLAG_PVSYNC, |
73 | }; |
74 | |
75 | /* |
76 | * The timings are not very helpful as the display is used in |
77 | * command mode using the maximum HS frequency. |
78 | */ |
79 | static const struct drm_display_mode sony_acx424akp_cmd_mode = { |
80 | .clock = 35478, |
81 | .hdisplay = 480, |
82 | .hsync_start = 480 + 154, |
83 | .hsync_end = 480 + 154 + 16, |
84 | .htotal = 480 + 154 + 16 + 32, |
85 | .vdisplay = 864, |
86 | .vsync_start = 864 + 1, |
87 | .vsync_end = 864 + 1 + 1, |
88 | .vtotal = 864 + 1 + 1 + 1, |
89 | /* |
90 | * Some desired refresh rate, experiments at the maximum "pixel" |
91 | * clock speed (HS clock 420 MHz) yields around 117Hz. |
92 | */ |
93 | .width_mm = 48, |
94 | .height_mm = 84, |
95 | }; |
96 | |
97 | static const struct nt35560_config sony_acx424akp_data = { |
98 | .vid_mode = &sony_acx424akp_vid_mode, |
99 | .cmd_mode = &sony_acx424akp_cmd_mode, |
100 | }; |
101 | |
102 | static const struct drm_display_mode sony_acx424akm_vid_mode = { |
103 | .clock = 27234, |
104 | .hdisplay = 480, |
105 | .hsync_start = 480 + 15, |
106 | .hsync_end = 480 + 15 + 0, |
107 | .htotal = 480 + 15 + 0 + 15, |
108 | .vdisplay = 854, |
109 | .vsync_start = 854 + 14, |
110 | .vsync_end = 854 + 14 + 1, |
111 | .vtotal = 854 + 14 + 1 + 11, |
112 | .width_mm = 46, |
113 | .height_mm = 82, |
114 | .flags = DRM_MODE_FLAG_PVSYNC, |
115 | }; |
116 | |
117 | /* |
118 | * The timings are not very helpful as the display is used in |
119 | * command mode using the maximum HS frequency. |
120 | */ |
121 | static const struct drm_display_mode sony_acx424akm_cmd_mode = { |
122 | .clock = 35478, |
123 | .hdisplay = 480, |
124 | .hsync_start = 480 + 154, |
125 | .hsync_end = 480 + 154 + 16, |
126 | .htotal = 480 + 154 + 16 + 32, |
127 | .vdisplay = 854, |
128 | .vsync_start = 854 + 1, |
129 | .vsync_end = 854 + 1 + 1, |
130 | .vtotal = 854 + 1 + 1 + 1, |
131 | .width_mm = 46, |
132 | .height_mm = 82, |
133 | }; |
134 | |
135 | static const struct nt35560_config sony_acx424akm_data = { |
136 | .vid_mode = &sony_acx424akm_vid_mode, |
137 | .cmd_mode = &sony_acx424akm_cmd_mode, |
138 | }; |
139 | |
140 | static inline struct nt35560 *panel_to_nt35560(struct drm_panel *panel) |
141 | { |
142 | return container_of(panel, struct nt35560, panel); |
143 | } |
144 | |
145 | #define FOSC 20 /* 20Mhz */ |
146 | #define SCALE_FACTOR_NS_DIV_MHZ 1000 |
147 | |
148 | static int nt35560_set_brightness(struct backlight_device *bl) |
149 | { |
150 | struct nt35560 *nt = bl_get_data(bl_dev: bl); |
151 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); |
152 | int period_ns = 1023; |
153 | int duty_ns = bl->props.brightness; |
154 | u8 pwm_ratio; |
155 | u8 pwm_div; |
156 | u8 par; |
157 | int ret; |
158 | |
159 | if (backlight_is_blank(bd: bl)) { |
160 | /* Disable backlight */ |
161 | par = 0x00; |
162 | ret = mipi_dsi_dcs_write(dsi, cmd: MIPI_DCS_WRITE_CONTROL_DISPLAY, |
163 | data: &par, len: 1); |
164 | if (ret) { |
165 | dev_err(nt->dev, "failed to disable display backlight (%d)\n" , ret); |
166 | return ret; |
167 | } |
168 | return 0; |
169 | } |
170 | |
171 | /* Calculate the PWM duty cycle in n/256's */ |
172 | pwm_ratio = max(((duty_ns * 256) / period_ns) - 1, 1); |
173 | pwm_div = max(1, |
174 | ((FOSC * period_ns) / 256) / |
175 | SCALE_FACTOR_NS_DIV_MHZ); |
176 | |
177 | /* Set up PWM dutycycle ONE byte (differs from the standard) */ |
178 | dev_dbg(nt->dev, "calculated duty cycle %02x\n" , pwm_ratio); |
179 | ret = mipi_dsi_dcs_write(dsi, cmd: MIPI_DCS_SET_DISPLAY_BRIGHTNESS, |
180 | data: &pwm_ratio, len: 1); |
181 | if (ret < 0) { |
182 | dev_err(nt->dev, "failed to set display PWM ratio (%d)\n" , ret); |
183 | return ret; |
184 | } |
185 | |
186 | /* |
187 | * Sequence to write PWMDIV: |
188 | * address data |
189 | * 0xF3 0xAA CMD2 Unlock |
190 | * 0x00 0x01 Enter CMD2 page 0 |
191 | * 0X7D 0x01 No reload MTP of CMD2 P1 |
192 | * 0x22 PWMDIV |
193 | * 0x7F 0xAA CMD2 page 1 lock |
194 | */ |
195 | par = 0xaa; |
196 | ret = mipi_dsi_dcs_write(dsi, cmd: 0xf3, data: &par, len: 1); |
197 | if (ret < 0) { |
198 | dev_err(nt->dev, "failed to unlock CMD 2 (%d)\n" , ret); |
199 | return ret; |
200 | } |
201 | par = 0x01; |
202 | ret = mipi_dsi_dcs_write(dsi, cmd: 0x00, data: &par, len: 1); |
203 | if (ret < 0) { |
204 | dev_err(nt->dev, "failed to enter page 1 (%d)\n" , ret); |
205 | return ret; |
206 | } |
207 | par = 0x01; |
208 | ret = mipi_dsi_dcs_write(dsi, cmd: 0x7d, data: &par, len: 1); |
209 | if (ret < 0) { |
210 | dev_err(nt->dev, "failed to disable MTP reload (%d)\n" , ret); |
211 | return ret; |
212 | } |
213 | ret = mipi_dsi_dcs_write(dsi, cmd: 0x22, data: &pwm_div, len: 1); |
214 | if (ret < 0) { |
215 | dev_err(nt->dev, "failed to set PWM divisor (%d)\n" , ret); |
216 | return ret; |
217 | } |
218 | par = 0xaa; |
219 | ret = mipi_dsi_dcs_write(dsi, cmd: 0x7f, data: &par, len: 1); |
220 | if (ret < 0) { |
221 | dev_err(nt->dev, "failed to lock CMD 2 (%d)\n" , ret); |
222 | return ret; |
223 | } |
224 | |
225 | /* Enable backlight */ |
226 | par = 0x24; |
227 | ret = mipi_dsi_dcs_write(dsi, cmd: MIPI_DCS_WRITE_CONTROL_DISPLAY, |
228 | data: &par, len: 1); |
229 | if (ret < 0) { |
230 | dev_err(nt->dev, "failed to enable display backlight (%d)\n" , ret); |
231 | return ret; |
232 | } |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static const struct backlight_ops nt35560_bl_ops = { |
238 | .update_status = nt35560_set_brightness, |
239 | }; |
240 | |
241 | static const struct backlight_properties nt35560_bl_props = { |
242 | .type = BACKLIGHT_RAW, |
243 | .brightness = 512, |
244 | .max_brightness = 1023, |
245 | }; |
246 | |
247 | static int nt35560_read_id(struct nt35560 *nt) |
248 | { |
249 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); |
250 | u8 vendor, version, panel; |
251 | u16 val; |
252 | int ret; |
253 | |
254 | ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID1, data: &vendor, len: 1); |
255 | if (ret < 0) { |
256 | dev_err(nt->dev, "could not vendor ID byte\n" ); |
257 | return ret; |
258 | } |
259 | ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID2, data: &version, len: 1); |
260 | if (ret < 0) { |
261 | dev_err(nt->dev, "could not read device version byte\n" ); |
262 | return ret; |
263 | } |
264 | ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID3, data: &panel, len: 1); |
265 | if (ret < 0) { |
266 | dev_err(nt->dev, "could not read panel ID byte\n" ); |
267 | return ret; |
268 | } |
269 | |
270 | if (vendor == 0x00) { |
271 | dev_err(nt->dev, "device vendor ID is zero\n" ); |
272 | return -ENODEV; |
273 | } |
274 | |
275 | val = (vendor << 8) | panel; |
276 | switch (val) { |
277 | case DISPLAY_SONY_ACX424AKP_ID1: |
278 | case DISPLAY_SONY_ACX424AKP_ID2: |
279 | case DISPLAY_SONY_ACX424AKP_ID3: |
280 | case DISPLAY_SONY_ACX424AKP_ID4: |
281 | dev_info(nt->dev, "MTP vendor: %02x, version: %02x, panel: %02x\n" , |
282 | vendor, version, panel); |
283 | break; |
284 | default: |
285 | dev_info(nt->dev, "unknown vendor: %02x, version: %02x, panel: %02x\n" , |
286 | vendor, version, panel); |
287 | break; |
288 | } |
289 | |
290 | return 0; |
291 | } |
292 | |
293 | static int nt35560_power_on(struct nt35560 *nt) |
294 | { |
295 | int ret; |
296 | |
297 | ret = regulator_enable(regulator: nt->supply); |
298 | if (ret) { |
299 | dev_err(nt->dev, "failed to enable supply (%d)\n" , ret); |
300 | return ret; |
301 | } |
302 | |
303 | /* Assert RESET */ |
304 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 1); |
305 | udelay(20); |
306 | /* De-assert RESET */ |
307 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 0); |
308 | usleep_range(min: 11000, max: 20000); |
309 | |
310 | return 0; |
311 | } |
312 | |
313 | static void nt35560_power_off(struct nt35560 *nt) |
314 | { |
315 | /* Assert RESET */ |
316 | gpiod_set_value_cansleep(desc: nt->reset_gpio, value: 1); |
317 | usleep_range(min: 11000, max: 20000); |
318 | |
319 | regulator_disable(regulator: nt->supply); |
320 | } |
321 | |
322 | static int nt35560_prepare(struct drm_panel *panel) |
323 | { |
324 | struct nt35560 *nt = panel_to_nt35560(panel); |
325 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); |
326 | const u8 mddi = 3; |
327 | int ret; |
328 | |
329 | ret = nt35560_power_on(nt); |
330 | if (ret) |
331 | return ret; |
332 | |
333 | ret = nt35560_read_id(nt); |
334 | if (ret) { |
335 | dev_err(nt->dev, "failed to read panel ID (%d)\n" , ret); |
336 | goto err_power_off; |
337 | } |
338 | |
339 | /* Enabe tearing mode: send TE (tearing effect) at VBLANK */ |
340 | ret = mipi_dsi_dcs_set_tear_on(dsi, |
341 | mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
342 | if (ret) { |
343 | dev_err(nt->dev, "failed to enable vblank TE (%d)\n" , ret); |
344 | goto err_power_off; |
345 | } |
346 | |
347 | /* |
348 | * Set MDDI |
349 | * |
350 | * This presumably deactivates the Qualcomm MDDI interface and |
351 | * selects DSI, similar code is found in other drivers such as the |
352 | * Sharp LS043T1LE01 which makes us suspect that this panel may be |
353 | * using a Novatek NT35565 or similar display driver chip that shares |
354 | * this command. Due to the lack of documentation we cannot know for |
355 | * sure. |
356 | */ |
357 | ret = mipi_dsi_dcs_write(dsi, NT35560_DCS_SET_MDDI, |
358 | data: &mddi, len: sizeof(mddi)); |
359 | if (ret < 0) { |
360 | dev_err(nt->dev, "failed to set MDDI (%d)\n" , ret); |
361 | goto err_power_off; |
362 | } |
363 | |
364 | /* Exit sleep mode */ |
365 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
366 | if (ret) { |
367 | dev_err(nt->dev, "failed to exit sleep mode (%d)\n" , ret); |
368 | goto err_power_off; |
369 | } |
370 | msleep(msecs: 140); |
371 | |
372 | ret = mipi_dsi_dcs_set_display_on(dsi); |
373 | if (ret) { |
374 | dev_err(nt->dev, "failed to turn display on (%d)\n" , ret); |
375 | goto err_power_off; |
376 | } |
377 | if (nt->video_mode) { |
378 | /* In video mode turn peripheral on */ |
379 | ret = mipi_dsi_turn_on_peripheral(dsi); |
380 | if (ret) { |
381 | dev_err(nt->dev, "failed to turn on peripheral\n" ); |
382 | goto err_power_off; |
383 | } |
384 | } |
385 | |
386 | return 0; |
387 | |
388 | err_power_off: |
389 | nt35560_power_off(nt); |
390 | return ret; |
391 | } |
392 | |
393 | static int nt35560_unprepare(struct drm_panel *panel) |
394 | { |
395 | struct nt35560 *nt = panel_to_nt35560(panel); |
396 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); |
397 | int ret; |
398 | |
399 | ret = mipi_dsi_dcs_set_display_off(dsi); |
400 | if (ret) { |
401 | dev_err(nt->dev, "failed to turn display off (%d)\n" , ret); |
402 | return ret; |
403 | } |
404 | |
405 | /* Enter sleep mode */ |
406 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
407 | if (ret) { |
408 | dev_err(nt->dev, "failed to enter sleep mode (%d)\n" , ret); |
409 | return ret; |
410 | } |
411 | msleep(msecs: 85); |
412 | |
413 | nt35560_power_off(nt); |
414 | |
415 | return 0; |
416 | } |
417 | |
418 | |
419 | static int nt35560_get_modes(struct drm_panel *panel, |
420 | struct drm_connector *connector) |
421 | { |
422 | struct nt35560 *nt = panel_to_nt35560(panel); |
423 | const struct nt35560_config *conf = nt->conf; |
424 | struct drm_display_mode *mode; |
425 | |
426 | if (nt->video_mode) |
427 | mode = drm_mode_duplicate(dev: connector->dev, |
428 | mode: conf->vid_mode); |
429 | else |
430 | mode = drm_mode_duplicate(dev: connector->dev, |
431 | mode: conf->cmd_mode); |
432 | if (!mode) { |
433 | dev_err(panel->dev, "bad mode or failed to add mode\n" ); |
434 | return -EINVAL; |
435 | } |
436 | drm_mode_set_name(mode); |
437 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
438 | |
439 | connector->display_info.width_mm = mode->width_mm; |
440 | connector->display_info.height_mm = mode->height_mm; |
441 | |
442 | drm_mode_probed_add(connector, mode); |
443 | |
444 | return 1; /* Number of modes */ |
445 | } |
446 | |
447 | static const struct drm_panel_funcs nt35560_drm_funcs = { |
448 | .unprepare = nt35560_unprepare, |
449 | .prepare = nt35560_prepare, |
450 | .get_modes = nt35560_get_modes, |
451 | }; |
452 | |
453 | static int nt35560_probe(struct mipi_dsi_device *dsi) |
454 | { |
455 | struct device *dev = &dsi->dev; |
456 | struct nt35560 *nt; |
457 | int ret; |
458 | |
459 | nt = devm_kzalloc(dev, size: sizeof(struct nt35560), GFP_KERNEL); |
460 | if (!nt) |
461 | return -ENOMEM; |
462 | nt->video_mode = of_property_read_bool(np: dev->of_node, |
463 | propname: "enforce-video-mode" ); |
464 | |
465 | mipi_dsi_set_drvdata(dsi, data: nt); |
466 | nt->dev = dev; |
467 | |
468 | nt->conf = of_device_get_match_data(dev); |
469 | if (!nt->conf) { |
470 | dev_err(dev, "missing device configuration\n" ); |
471 | return -ENODEV; |
472 | } |
473 | |
474 | dsi->lanes = 2; |
475 | dsi->format = MIPI_DSI_FMT_RGB888; |
476 | /* |
477 | * FIXME: these come from the ST-Ericsson vendor driver for the |
478 | * HREF520 and seems to reflect limitations in the PLLs on that |
479 | * platform, if you have the datasheet, please cross-check the |
480 | * actual max rates. |
481 | */ |
482 | dsi->lp_rate = 19200000; |
483 | dsi->hs_rate = 420160000; |
484 | |
485 | if (nt->video_mode) |
486 | /* Burst mode using event for sync */ |
487 | dsi->mode_flags = |
488 | MIPI_DSI_MODE_VIDEO | |
489 | MIPI_DSI_MODE_VIDEO_BURST; |
490 | else |
491 | dsi->mode_flags = |
492 | MIPI_DSI_CLOCK_NON_CONTINUOUS; |
493 | |
494 | nt->supply = devm_regulator_get(dev, id: "vddi" ); |
495 | if (IS_ERR(ptr: nt->supply)) |
496 | return PTR_ERR(ptr: nt->supply); |
497 | |
498 | /* This asserts RESET by default */ |
499 | nt->reset_gpio = devm_gpiod_get_optional(dev, con_id: "reset" , |
500 | flags: GPIOD_OUT_HIGH); |
501 | if (IS_ERR(ptr: nt->reset_gpio)) |
502 | return dev_err_probe(dev, err: PTR_ERR(ptr: nt->reset_gpio), |
503 | fmt: "failed to request GPIO\n" ); |
504 | |
505 | drm_panel_init(panel: &nt->panel, dev, funcs: &nt35560_drm_funcs, |
506 | DRM_MODE_CONNECTOR_DSI); |
507 | |
508 | nt->panel.backlight = devm_backlight_device_register(dev, name: "nt35560" , parent: dev, devdata: nt, |
509 | ops: &nt35560_bl_ops, props: &nt35560_bl_props); |
510 | if (IS_ERR(ptr: nt->panel.backlight)) |
511 | return dev_err_probe(dev, err: PTR_ERR(ptr: nt->panel.backlight), |
512 | fmt: "failed to register backlight device\n" ); |
513 | |
514 | drm_panel_add(panel: &nt->panel); |
515 | |
516 | ret = mipi_dsi_attach(dsi); |
517 | if (ret < 0) { |
518 | drm_panel_remove(panel: &nt->panel); |
519 | return ret; |
520 | } |
521 | |
522 | return 0; |
523 | } |
524 | |
525 | static void nt35560_remove(struct mipi_dsi_device *dsi) |
526 | { |
527 | struct nt35560 *nt = mipi_dsi_get_drvdata(dsi); |
528 | |
529 | mipi_dsi_detach(dsi); |
530 | drm_panel_remove(panel: &nt->panel); |
531 | } |
532 | |
533 | static const struct of_device_id nt35560_of_match[] = { |
534 | { |
535 | .compatible = "sony,acx424akp" , |
536 | .data = &sony_acx424akp_data, |
537 | }, |
538 | { |
539 | .compatible = "sony,acx424akm" , |
540 | .data = &sony_acx424akm_data, |
541 | }, |
542 | { /* sentinel */ } |
543 | }; |
544 | MODULE_DEVICE_TABLE(of, nt35560_of_match); |
545 | |
546 | static struct mipi_dsi_driver nt35560_driver = { |
547 | .probe = nt35560_probe, |
548 | .remove = nt35560_remove, |
549 | .driver = { |
550 | .name = "panel-novatek-nt35560" , |
551 | .of_match_table = nt35560_of_match, |
552 | }, |
553 | }; |
554 | module_mipi_dsi_driver(nt35560_driver); |
555 | |
556 | MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>" ); |
557 | MODULE_DESCRIPTION("MIPI-DSI Novatek NT35560 Panel Driver" ); |
558 | MODULE_LICENSE("GPL v2" ); |
559 | |