]>
Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
c96f5662 VSB |
2 | /* |
3 | * Copyright (C) 2016 InforceComputing | |
4 | * Author: Vinay Simha BN <simhavcs@gmail.com> | |
5 | * | |
6 | * Copyright (C) 2016 Linaro Ltd | |
7 | * Author: Sumit Semwal <sumit.semwal@linaro.org> | |
8 | * | |
9 | * From internet archives, the panel for Nexus 7 2nd Gen, 2013 model is a | |
10 | * JDI model LT070ME05000, and its data sheet is at: | |
11 | * http://panelone.net/en/7-0-inch/JDI_LT070ME05000_7.0_inch-datasheet | |
c96f5662 VSB |
12 | */ |
13 | #include <linux/backlight.h> | |
14 | #include <linux/gpio/consumer.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/regulator/consumer.h> | |
18 | ||
19 | #include <drm/drmP.h> | |
20 | #include <drm/drm_crtc.h> | |
21 | #include <drm/drm_mipi_dsi.h> | |
22 | #include <drm/drm_panel.h> | |
23 | ||
24 | #include <video/mipi_display.h> | |
25 | ||
26 | static const char * const regulator_names[] = { | |
27 | "vddp", | |
28 | "iovcc" | |
29 | }; | |
30 | ||
31 | struct jdi_panel { | |
32 | struct drm_panel base; | |
33 | struct mipi_dsi_device *dsi; | |
34 | ||
35 | struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)]; | |
36 | ||
37 | struct gpio_desc *enable_gpio; | |
38 | struct gpio_desc *reset_gpio; | |
39 | struct gpio_desc *dcdc_en_gpio; | |
40 | struct backlight_device *backlight; | |
41 | ||
42 | bool prepared; | |
43 | bool enabled; | |
44 | ||
45 | const struct drm_display_mode *mode; | |
46 | }; | |
47 | ||
48 | static inline struct jdi_panel *to_jdi_panel(struct drm_panel *panel) | |
49 | { | |
50 | return container_of(panel, struct jdi_panel, base); | |
51 | } | |
52 | ||
53 | static int jdi_panel_init(struct jdi_panel *jdi) | |
54 | { | |
55 | struct mipi_dsi_device *dsi = jdi->dsi; | |
56 | struct device *dev = &jdi->dsi->dev; | |
57 | int ret; | |
58 | ||
59 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; | |
60 | ||
61 | ret = mipi_dsi_dcs_soft_reset(dsi); | |
62 | if (ret < 0) | |
63 | return ret; | |
64 | ||
65 | usleep_range(10000, 20000); | |
66 | ||
67 | ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT << 4); | |
68 | if (ret < 0) { | |
69 | dev_err(dev, "failed to set pixel format: %d\n", ret); | |
70 | return ret; | |
71 | } | |
72 | ||
73 | ret = mipi_dsi_dcs_set_column_address(dsi, 0, jdi->mode->hdisplay - 1); | |
74 | if (ret < 0) { | |
75 | dev_err(dev, "failed to set column address: %d\n", ret); | |
76 | return ret; | |
77 | } | |
78 | ||
79 | ret = mipi_dsi_dcs_set_page_address(dsi, 0, jdi->mode->vdisplay - 1); | |
80 | if (ret < 0) { | |
81 | dev_err(dev, "failed to set page address: %d\n", ret); | |
82 | return ret; | |
83 | } | |
84 | ||
85 | /* | |
86 | * BIT(5) BCTRL = 1 Backlight Control Block On, Brightness registers | |
87 | * are active | |
88 | * BIT(3) BL = 1 Backlight Control On | |
89 | * BIT(2) DD = 0 Display Dimming is Off | |
90 | */ | |
91 | ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, | |
92 | (u8[]){ 0x24 }, 1); | |
93 | if (ret < 0) { | |
94 | dev_err(dev, "failed to write control display: %d\n", ret); | |
95 | return ret; | |
96 | } | |
97 | ||
98 | /* CABC off */ | |
99 | ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_POWER_SAVE, | |
100 | (u8[]){ 0x00 }, 1); | |
101 | if (ret < 0) { | |
102 | dev_err(dev, "failed to set cabc off: %d\n", ret); | |
103 | return ret; | |
104 | } | |
105 | ||
106 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); | |
107 | if (ret < 0) { | |
108 | dev_err(dev, "failed to set exit sleep mode: %d\n", ret); | |
109 | return ret; | |
110 | } | |
111 | ||
112 | msleep(120); | |
113 | ||
114 | ret = mipi_dsi_generic_write(dsi, (u8[]){0xB0, 0x00}, 2); | |
115 | if (ret < 0) { | |
116 | dev_err(dev, "failed to set mcap: %d\n", ret); | |
117 | return ret; | |
118 | } | |
119 | ||
120 | mdelay(10); | |
121 | ||
122 | /* Interface setting, video mode */ | |
123 | ret = mipi_dsi_generic_write(dsi, (u8[]) | |
124 | {0xB3, 0x26, 0x08, 0x00, 0x20, 0x00}, 6); | |
125 | if (ret < 0) { | |
126 | dev_err(dev, "failed to set display interface setting: %d\n" | |
127 | , ret); | |
128 | return ret; | |
129 | } | |
130 | ||
131 | mdelay(20); | |
132 | ||
133 | ret = mipi_dsi_generic_write(dsi, (u8[]){0xB0, 0x03}, 2); | |
134 | if (ret < 0) { | |
135 | dev_err(dev, "failed to set default values for mcap: %d\n" | |
136 | , ret); | |
137 | return ret; | |
138 | } | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | static int jdi_panel_on(struct jdi_panel *jdi) | |
144 | { | |
145 | struct mipi_dsi_device *dsi = jdi->dsi; | |
146 | struct device *dev = &jdi->dsi->dev; | |
147 | int ret; | |
148 | ||
149 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; | |
150 | ||
151 | ret = mipi_dsi_dcs_set_display_on(dsi); | |
152 | if (ret < 0) | |
153 | dev_err(dev, "failed to set display on: %d\n", ret); | |
154 | ||
155 | return ret; | |
156 | } | |
157 | ||
158 | static void jdi_panel_off(struct jdi_panel *jdi) | |
159 | { | |
160 | struct mipi_dsi_device *dsi = jdi->dsi; | |
161 | struct device *dev = &jdi->dsi->dev; | |
162 | int ret; | |
163 | ||
164 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; | |
165 | ||
166 | ret = mipi_dsi_dcs_set_display_off(dsi); | |
167 | if (ret < 0) | |
168 | dev_err(dev, "failed to set display off: %d\n", ret); | |
169 | ||
170 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); | |
171 | if (ret < 0) | |
172 | dev_err(dev, "failed to enter sleep mode: %d\n", ret); | |
173 | ||
174 | msleep(100); | |
175 | } | |
176 | ||
177 | static int jdi_panel_disable(struct drm_panel *panel) | |
178 | { | |
179 | struct jdi_panel *jdi = to_jdi_panel(panel); | |
180 | ||
181 | if (!jdi->enabled) | |
182 | return 0; | |
183 | ||
d593bfdb | 184 | backlight_disable(jdi->backlight); |
c96f5662 VSB |
185 | |
186 | jdi->enabled = false; | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | static int jdi_panel_unprepare(struct drm_panel *panel) | |
192 | { | |
193 | struct jdi_panel *jdi = to_jdi_panel(panel); | |
194 | struct device *dev = &jdi->dsi->dev; | |
195 | int ret; | |
196 | ||
197 | if (!jdi->prepared) | |
198 | return 0; | |
199 | ||
200 | jdi_panel_off(jdi); | |
201 | ||
202 | ret = regulator_bulk_disable(ARRAY_SIZE(jdi->supplies), jdi->supplies); | |
203 | if (ret < 0) | |
204 | dev_err(dev, "regulator disable failed, %d\n", ret); | |
205 | ||
206 | gpiod_set_value(jdi->enable_gpio, 0); | |
207 | ||
208 | gpiod_set_value(jdi->reset_gpio, 1); | |
209 | ||
210 | gpiod_set_value(jdi->dcdc_en_gpio, 0); | |
211 | ||
212 | jdi->prepared = false; | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | static int jdi_panel_prepare(struct drm_panel *panel) | |
218 | { | |
219 | struct jdi_panel *jdi = to_jdi_panel(panel); | |
220 | struct device *dev = &jdi->dsi->dev; | |
221 | int ret; | |
222 | ||
223 | if (jdi->prepared) | |
224 | return 0; | |
225 | ||
226 | ret = regulator_bulk_enable(ARRAY_SIZE(jdi->supplies), jdi->supplies); | |
227 | if (ret < 0) { | |
228 | dev_err(dev, "regulator enable failed, %d\n", ret); | |
229 | return ret; | |
230 | } | |
231 | ||
232 | msleep(20); | |
233 | ||
234 | gpiod_set_value(jdi->dcdc_en_gpio, 1); | |
235 | usleep_range(10, 20); | |
236 | ||
237 | gpiod_set_value(jdi->reset_gpio, 0); | |
238 | usleep_range(10, 20); | |
239 | ||
240 | gpiod_set_value(jdi->enable_gpio, 1); | |
241 | usleep_range(10, 20); | |
242 | ||
243 | ret = jdi_panel_init(jdi); | |
244 | if (ret < 0) { | |
245 | dev_err(dev, "failed to init panel: %d\n", ret); | |
246 | goto poweroff; | |
247 | } | |
248 | ||
249 | ret = jdi_panel_on(jdi); | |
250 | if (ret < 0) { | |
251 | dev_err(dev, "failed to set panel on: %d\n", ret); | |
252 | goto poweroff; | |
253 | } | |
254 | ||
255 | jdi->prepared = true; | |
256 | ||
257 | return 0; | |
258 | ||
259 | poweroff: | |
260 | ret = regulator_bulk_disable(ARRAY_SIZE(jdi->supplies), jdi->supplies); | |
261 | if (ret < 0) | |
262 | dev_err(dev, "regulator disable failed, %d\n", ret); | |
263 | ||
264 | gpiod_set_value(jdi->enable_gpio, 0); | |
265 | ||
266 | gpiod_set_value(jdi->reset_gpio, 1); | |
267 | ||
268 | gpiod_set_value(jdi->dcdc_en_gpio, 0); | |
269 | ||
270 | return ret; | |
271 | } | |
272 | ||
273 | static int jdi_panel_enable(struct drm_panel *panel) | |
274 | { | |
275 | struct jdi_panel *jdi = to_jdi_panel(panel); | |
276 | ||
277 | if (jdi->enabled) | |
278 | return 0; | |
279 | ||
d593bfdb | 280 | backlight_enable(jdi->backlight); |
c96f5662 VSB |
281 | |
282 | jdi->enabled = true; | |
283 | ||
284 | return 0; | |
285 | } | |
286 | ||
287 | static const struct drm_display_mode default_mode = { | |
288 | .clock = 155493, | |
289 | .hdisplay = 1200, | |
290 | .hsync_start = 1200 + 48, | |
291 | .hsync_end = 1200 + 48 + 32, | |
292 | .htotal = 1200 + 48 + 32 + 60, | |
293 | .vdisplay = 1920, | |
294 | .vsync_start = 1920 + 3, | |
295 | .vsync_end = 1920 + 3 + 5, | |
296 | .vtotal = 1920 + 3 + 5 + 6, | |
297 | .vrefresh = 60, | |
298 | .flags = 0, | |
299 | }; | |
300 | ||
301 | static int jdi_panel_get_modes(struct drm_panel *panel) | |
302 | { | |
303 | struct drm_display_mode *mode; | |
304 | struct jdi_panel *jdi = to_jdi_panel(panel); | |
305 | struct device *dev = &jdi->dsi->dev; | |
306 | ||
307 | mode = drm_mode_duplicate(panel->drm, &default_mode); | |
308 | if (!mode) { | |
309 | dev_err(dev, "failed to add mode %ux%ux@%u\n", | |
310 | default_mode.hdisplay, default_mode.vdisplay, | |
311 | default_mode.vrefresh); | |
312 | return -ENOMEM; | |
313 | } | |
314 | ||
315 | drm_mode_set_name(mode); | |
316 | ||
317 | drm_mode_probed_add(panel->connector, mode); | |
318 | ||
319 | panel->connector->display_info.width_mm = 95; | |
320 | panel->connector->display_info.height_mm = 151; | |
321 | ||
322 | return 1; | |
323 | } | |
324 | ||
325 | static int dsi_dcs_bl_get_brightness(struct backlight_device *bl) | |
326 | { | |
327 | struct mipi_dsi_device *dsi = bl_get_data(bl); | |
328 | int ret; | |
329 | u16 brightness = bl->props.brightness; | |
330 | ||
331 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; | |
332 | ||
333 | ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); | |
334 | if (ret < 0) | |
335 | return ret; | |
336 | ||
337 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; | |
338 | ||
339 | return brightness & 0xff; | |
340 | } | |
341 | ||
342 | static int dsi_dcs_bl_update_status(struct backlight_device *bl) | |
343 | { | |
344 | struct mipi_dsi_device *dsi = bl_get_data(bl); | |
345 | int ret; | |
346 | ||
347 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; | |
348 | ||
349 | ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness); | |
350 | if (ret < 0) | |
351 | return ret; | |
352 | ||
353 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; | |
354 | ||
355 | return 0; | |
356 | } | |
357 | ||
358 | static const struct backlight_ops dsi_bl_ops = { | |
359 | .update_status = dsi_dcs_bl_update_status, | |
360 | .get_brightness = dsi_dcs_bl_get_brightness, | |
361 | }; | |
362 | ||
363 | static struct backlight_device * | |
364 | drm_panel_create_dsi_backlight(struct mipi_dsi_device *dsi) | |
365 | { | |
366 | struct device *dev = &dsi->dev; | |
367 | struct backlight_properties props; | |
368 | ||
369 | memset(&props, 0, sizeof(props)); | |
370 | props.type = BACKLIGHT_RAW; | |
371 | props.brightness = 255; | |
372 | props.max_brightness = 255; | |
373 | ||
374 | return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, | |
375 | &dsi_bl_ops, &props); | |
376 | } | |
377 | ||
378 | static const struct drm_panel_funcs jdi_panel_funcs = { | |
379 | .disable = jdi_panel_disable, | |
380 | .unprepare = jdi_panel_unprepare, | |
381 | .prepare = jdi_panel_prepare, | |
382 | .enable = jdi_panel_enable, | |
383 | .get_modes = jdi_panel_get_modes, | |
384 | }; | |
385 | ||
386 | static const struct of_device_id jdi_of_match[] = { | |
387 | { .compatible = "jdi,lt070me05000", }, | |
388 | { } | |
389 | }; | |
390 | MODULE_DEVICE_TABLE(of, jdi_of_match); | |
391 | ||
392 | static int jdi_panel_add(struct jdi_panel *jdi) | |
393 | { | |
394 | struct device *dev = &jdi->dsi->dev; | |
395 | int ret; | |
396 | unsigned int i; | |
397 | ||
398 | jdi->mode = &default_mode; | |
399 | ||
400 | for (i = 0; i < ARRAY_SIZE(jdi->supplies); i++) | |
401 | jdi->supplies[i].supply = regulator_names[i]; | |
402 | ||
403 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(jdi->supplies), | |
404 | jdi->supplies); | |
405 | if (ret < 0) { | |
406 | dev_err(dev, "failed to init regulator, ret=%d\n", ret); | |
407 | return ret; | |
408 | } | |
409 | ||
410 | jdi->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); | |
411 | if (IS_ERR(jdi->enable_gpio)) { | |
412 | ret = PTR_ERR(jdi->enable_gpio); | |
413 | dev_err(dev, "cannot get enable-gpio %d\n", ret); | |
414 | return ret; | |
415 | } | |
416 | ||
417 | jdi->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); | |
418 | if (IS_ERR(jdi->reset_gpio)) { | |
419 | ret = PTR_ERR(jdi->reset_gpio); | |
420 | dev_err(dev, "cannot get reset-gpios %d\n", ret); | |
421 | return ret; | |
422 | } | |
423 | ||
424 | jdi->dcdc_en_gpio = devm_gpiod_get(dev, "dcdc-en", GPIOD_OUT_LOW); | |
425 | if (IS_ERR(jdi->dcdc_en_gpio)) { | |
426 | ret = PTR_ERR(jdi->dcdc_en_gpio); | |
427 | dev_err(dev, "cannot get dcdc-en-gpio %d\n", ret); | |
428 | return ret; | |
429 | } | |
430 | ||
431 | jdi->backlight = drm_panel_create_dsi_backlight(jdi->dsi); | |
432 | if (IS_ERR(jdi->backlight)) { | |
433 | ret = PTR_ERR(jdi->backlight); | |
434 | dev_err(dev, "failed to register backlight %d\n", ret); | |
435 | return ret; | |
436 | } | |
437 | ||
438 | drm_panel_init(&jdi->base); | |
439 | jdi->base.funcs = &jdi_panel_funcs; | |
440 | jdi->base.dev = &jdi->dsi->dev; | |
441 | ||
442 | ret = drm_panel_add(&jdi->base); | |
443 | ||
444 | return ret; | |
445 | } | |
446 | ||
447 | static void jdi_panel_del(struct jdi_panel *jdi) | |
448 | { | |
449 | if (jdi->base.dev) | |
450 | drm_panel_remove(&jdi->base); | |
451 | } | |
452 | ||
453 | static int jdi_panel_probe(struct mipi_dsi_device *dsi) | |
454 | { | |
455 | struct jdi_panel *jdi; | |
456 | int ret; | |
457 | ||
458 | dsi->lanes = 4; | |
459 | dsi->format = MIPI_DSI_FMT_RGB888; | |
460 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | | |
461 | MIPI_DSI_CLOCK_NON_CONTINUOUS; | |
462 | ||
463 | jdi = devm_kzalloc(&dsi->dev, sizeof(*jdi), GFP_KERNEL); | |
464 | if (!jdi) | |
465 | return -ENOMEM; | |
466 | ||
467 | mipi_dsi_set_drvdata(dsi, jdi); | |
468 | ||
469 | jdi->dsi = dsi; | |
470 | ||
471 | ret = jdi_panel_add(jdi); | |
472 | if (ret < 0) | |
473 | return ret; | |
474 | ||
475 | return mipi_dsi_attach(dsi); | |
476 | } | |
477 | ||
478 | static int jdi_panel_remove(struct mipi_dsi_device *dsi) | |
479 | { | |
480 | struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); | |
481 | int ret; | |
482 | ||
483 | ret = jdi_panel_disable(&jdi->base); | |
484 | if (ret < 0) | |
485 | dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); | |
486 | ||
487 | ret = mipi_dsi_detach(dsi); | |
488 | if (ret < 0) | |
489 | dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", | |
490 | ret); | |
491 | ||
c96f5662 VSB |
492 | jdi_panel_del(jdi); |
493 | ||
494 | return 0; | |
495 | } | |
496 | ||
497 | static void jdi_panel_shutdown(struct mipi_dsi_device *dsi) | |
498 | { | |
499 | struct jdi_panel *jdi = mipi_dsi_get_drvdata(dsi); | |
500 | ||
501 | jdi_panel_disable(&jdi->base); | |
502 | } | |
503 | ||
504 | static struct mipi_dsi_driver jdi_panel_driver = { | |
505 | .driver = { | |
506 | .name = "panel-jdi-lt070me05000", | |
507 | .of_match_table = jdi_of_match, | |
508 | }, | |
509 | .probe = jdi_panel_probe, | |
510 | .remove = jdi_panel_remove, | |
511 | .shutdown = jdi_panel_shutdown, | |
512 | }; | |
513 | module_mipi_dsi_driver(jdi_panel_driver); | |
514 | ||
515 | MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>"); | |
516 | MODULE_AUTHOR("Vinay Simha BN <simhavcs@gmail.com>"); | |
517 | MODULE_DESCRIPTION("JDI LT070ME05000 WUXGA"); | |
518 | MODULE_LICENSE("GPL v2"); |