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