Commit | Line | Data |
---|---|---|
086ceb6b WJ |
1 | /* |
2 | * Copyright (C) 2015 Red Hat | |
3 | * Copyright (C) 2015 Sony Mobile Communications Inc. | |
4 | * Author: Werner Johansson <werner.johansson@sonymobile.com> | |
5 | * | |
6 | * Based on AUO panel driver by Rob Clark <robdclark@gmail.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License version 2 as published by | |
10 | * the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
15 | * more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along with | |
18 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
20 | ||
21 | #include <linux/backlight.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/of.h> | |
24 | #include <linux/regulator/consumer.h> | |
25 | ||
26 | #include <drm/drmP.h> | |
27 | #include <drm/drm_crtc.h> | |
28 | #include <drm/drm_mipi_dsi.h> | |
29 | #include <drm/drm_panel.h> | |
30 | ||
31 | #include <video/mipi_display.h> | |
32 | ||
33 | /* | |
34 | * When power is turned off to this panel a minimum off time of 500ms has to be | |
35 | * observed before powering back on as there's no external reset pin. Keep | |
36 | * track of earliest wakeup time and delay subsequent prepare call accordingly | |
37 | */ | |
38 | #define MIN_POFF_MS (500) | |
39 | ||
40 | struct wuxga_nt_panel { | |
41 | struct drm_panel base; | |
42 | struct mipi_dsi_device *dsi; | |
43 | ||
44 | struct backlight_device *backlight; | |
45 | struct regulator *supply; | |
46 | ||
47 | bool prepared; | |
48 | bool enabled; | |
49 | ||
50 | ktime_t earliest_wake; | |
51 | ||
52 | const struct drm_display_mode *mode; | |
53 | }; | |
54 | ||
55 | static inline struct wuxga_nt_panel *to_wuxga_nt_panel(struct drm_panel *panel) | |
56 | { | |
57 | return container_of(panel, struct wuxga_nt_panel, base); | |
58 | } | |
59 | ||
60 | static int wuxga_nt_panel_on(struct wuxga_nt_panel *wuxga_nt) | |
61 | { | |
62 | struct mipi_dsi_device *dsi = wuxga_nt->dsi; | |
63 | int ret; | |
64 | ||
65 | ret = mipi_dsi_turn_on_peripheral(dsi); | |
66 | if (ret < 0) | |
67 | return ret; | |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
72 | static int wuxga_nt_panel_disable(struct drm_panel *panel) | |
73 | { | |
74 | struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); | |
75 | ||
76 | if (!wuxga_nt->enabled) | |
77 | return 0; | |
78 | ||
79 | mipi_dsi_shutdown_peripheral(wuxga_nt->dsi); | |
80 | ||
81 | if (wuxga_nt->backlight) { | |
82 | wuxga_nt->backlight->props.power = FB_BLANK_POWERDOWN; | |
83 | wuxga_nt->backlight->props.state |= BL_CORE_FBBLANK; | |
84 | backlight_update_status(wuxga_nt->backlight); | |
85 | } | |
86 | ||
87 | wuxga_nt->enabled = false; | |
88 | ||
89 | return 0; | |
90 | } | |
91 | ||
92 | static int wuxga_nt_panel_unprepare(struct drm_panel *panel) | |
93 | { | |
94 | struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); | |
95 | ||
96 | if (!wuxga_nt->prepared) | |
97 | return 0; | |
98 | ||
99 | regulator_disable(wuxga_nt->supply); | |
100 | wuxga_nt->earliest_wake = ktime_add_ms(ktime_get_real(), MIN_POFF_MS); | |
101 | wuxga_nt->prepared = false; | |
102 | ||
103 | return 0; | |
104 | } | |
105 | ||
106 | static int wuxga_nt_panel_prepare(struct drm_panel *panel) | |
107 | { | |
108 | struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); | |
109 | int ret; | |
110 | s64 enablewait; | |
111 | ||
112 | if (wuxga_nt->prepared) | |
113 | return 0; | |
114 | ||
115 | /* | |
116 | * If the user re-enabled the panel before the required off-time then | |
117 | * we need to wait the remaining period before re-enabling regulator | |
118 | */ | |
119 | enablewait = ktime_ms_delta(wuxga_nt->earliest_wake, ktime_get_real()); | |
120 | ||
121 | /* Sanity check, this should never happen */ | |
122 | if (enablewait > MIN_POFF_MS) | |
123 | enablewait = MIN_POFF_MS; | |
124 | ||
125 | if (enablewait > 0) | |
126 | msleep(enablewait); | |
127 | ||
128 | ret = regulator_enable(wuxga_nt->supply); | |
129 | if (ret < 0) | |
130 | return ret; | |
131 | ||
132 | /* | |
133 | * A minimum delay of 250ms is required after power-up until commands | |
134 | * can be sent | |
135 | */ | |
136 | msleep(250); | |
137 | ||
138 | ret = wuxga_nt_panel_on(wuxga_nt); | |
139 | if (ret < 0) { | |
140 | dev_err(panel->dev, "failed to set panel on: %d\n", ret); | |
141 | goto poweroff; | |
142 | } | |
143 | ||
144 | wuxga_nt->prepared = true; | |
145 | ||
146 | return 0; | |
147 | ||
148 | poweroff: | |
149 | regulator_disable(wuxga_nt->supply); | |
150 | ||
151 | return ret; | |
152 | } | |
153 | ||
154 | static int wuxga_nt_panel_enable(struct drm_panel *panel) | |
155 | { | |
156 | struct wuxga_nt_panel *wuxga_nt = to_wuxga_nt_panel(panel); | |
157 | ||
158 | if (wuxga_nt->enabled) | |
159 | return 0; | |
160 | ||
161 | if (wuxga_nt->backlight) { | |
162 | wuxga_nt->backlight->props.power = FB_BLANK_UNBLANK; | |
163 | wuxga_nt->backlight->props.state &= ~BL_CORE_FBBLANK; | |
164 | backlight_update_status(wuxga_nt->backlight); | |
165 | } | |
166 | ||
167 | wuxga_nt->enabled = true; | |
168 | ||
169 | return 0; | |
170 | } | |
171 | ||
172 | static const struct drm_display_mode default_mode = { | |
173 | .clock = 164402, | |
174 | .hdisplay = 1920, | |
175 | .hsync_start = 1920 + 152, | |
176 | .hsync_end = 1920 + 152 + 52, | |
177 | .htotal = 1920 + 152 + 52 + 20, | |
178 | .vdisplay = 1200, | |
179 | .vsync_start = 1200 + 24, | |
180 | .vsync_end = 1200 + 24 + 6, | |
181 | .vtotal = 1200 + 24 + 6 + 48, | |
182 | .vrefresh = 60, | |
183 | }; | |
184 | ||
185 | static int wuxga_nt_panel_get_modes(struct drm_panel *panel) | |
186 | { | |
187 | struct drm_display_mode *mode; | |
188 | ||
189 | mode = drm_mode_duplicate(panel->drm, &default_mode); | |
190 | if (!mode) { | |
191 | dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n", | |
192 | default_mode.hdisplay, default_mode.vdisplay, | |
193 | default_mode.vrefresh); | |
194 | return -ENOMEM; | |
195 | } | |
196 | ||
197 | drm_mode_set_name(mode); | |
198 | ||
199 | drm_mode_probed_add(panel->connector, mode); | |
200 | ||
201 | panel->connector->display_info.width_mm = 217; | |
202 | panel->connector->display_info.height_mm = 136; | |
203 | ||
204 | return 1; | |
205 | } | |
206 | ||
207 | static const struct drm_panel_funcs wuxga_nt_panel_funcs = { | |
208 | .disable = wuxga_nt_panel_disable, | |
209 | .unprepare = wuxga_nt_panel_unprepare, | |
210 | .prepare = wuxga_nt_panel_prepare, | |
211 | .enable = wuxga_nt_panel_enable, | |
212 | .get_modes = wuxga_nt_panel_get_modes, | |
213 | }; | |
214 | ||
215 | static const struct of_device_id wuxga_nt_of_match[] = { | |
216 | { .compatible = "panasonic,vvx10f034n00", }, | |
217 | { } | |
218 | }; | |
219 | MODULE_DEVICE_TABLE(of, wuxga_nt_of_match); | |
220 | ||
221 | static int wuxga_nt_panel_add(struct wuxga_nt_panel *wuxga_nt) | |
222 | { | |
223 | struct device *dev = &wuxga_nt->dsi->dev; | |
224 | struct device_node *np; | |
225 | int ret; | |
226 | ||
227 | wuxga_nt->mode = &default_mode; | |
228 | ||
229 | wuxga_nt->supply = devm_regulator_get(dev, "power"); | |
230 | if (IS_ERR(wuxga_nt->supply)) | |
231 | return PTR_ERR(wuxga_nt->supply); | |
232 | ||
233 | np = of_parse_phandle(dev->of_node, "backlight", 0); | |
234 | if (np) { | |
235 | wuxga_nt->backlight = of_find_backlight_by_node(np); | |
236 | of_node_put(np); | |
237 | ||
238 | if (!wuxga_nt->backlight) | |
239 | return -EPROBE_DEFER; | |
240 | } | |
241 | ||
242 | drm_panel_init(&wuxga_nt->base); | |
243 | wuxga_nt->base.funcs = &wuxga_nt_panel_funcs; | |
244 | wuxga_nt->base.dev = &wuxga_nt->dsi->dev; | |
245 | ||
246 | ret = drm_panel_add(&wuxga_nt->base); | |
247 | if (ret < 0) | |
248 | goto put_backlight; | |
249 | ||
250 | return 0; | |
251 | ||
252 | put_backlight: | |
253 | if (wuxga_nt->backlight) | |
254 | put_device(&wuxga_nt->backlight->dev); | |
255 | ||
256 | return ret; | |
257 | } | |
258 | ||
259 | static void wuxga_nt_panel_del(struct wuxga_nt_panel *wuxga_nt) | |
260 | { | |
261 | if (wuxga_nt->base.dev) | |
262 | drm_panel_remove(&wuxga_nt->base); | |
263 | ||
264 | if (wuxga_nt->backlight) | |
265 | put_device(&wuxga_nt->backlight->dev); | |
266 | } | |
267 | ||
268 | static int wuxga_nt_panel_probe(struct mipi_dsi_device *dsi) | |
269 | { | |
270 | struct wuxga_nt_panel *wuxga_nt; | |
271 | int ret; | |
272 | ||
273 | dsi->lanes = 4; | |
274 | dsi->format = MIPI_DSI_FMT_RGB888; | |
275 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | | |
276 | MIPI_DSI_MODE_VIDEO_HSE | | |
277 | MIPI_DSI_CLOCK_NON_CONTINUOUS | | |
278 | MIPI_DSI_MODE_LPM; | |
279 | ||
280 | wuxga_nt = devm_kzalloc(&dsi->dev, sizeof(*wuxga_nt), GFP_KERNEL); | |
281 | if (!wuxga_nt) | |
282 | return -ENOMEM; | |
283 | ||
284 | mipi_dsi_set_drvdata(dsi, wuxga_nt); | |
285 | ||
286 | wuxga_nt->dsi = dsi; | |
287 | ||
288 | ret = wuxga_nt_panel_add(wuxga_nt); | |
289 | if (ret < 0) | |
290 | return ret; | |
291 | ||
292 | return mipi_dsi_attach(dsi); | |
293 | } | |
294 | ||
295 | static int wuxga_nt_panel_remove(struct mipi_dsi_device *dsi) | |
296 | { | |
297 | struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi); | |
298 | int ret; | |
299 | ||
300 | ret = wuxga_nt_panel_disable(&wuxga_nt->base); | |
301 | if (ret < 0) | |
302 | dev_err(&dsi->dev, "failed to disable panel: %d\n", ret); | |
303 | ||
304 | ret = mipi_dsi_detach(dsi); | |
305 | if (ret < 0) | |
306 | dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); | |
307 | ||
308 | drm_panel_detach(&wuxga_nt->base); | |
309 | wuxga_nt_panel_del(wuxga_nt); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | static void wuxga_nt_panel_shutdown(struct mipi_dsi_device *dsi) | |
315 | { | |
316 | struct wuxga_nt_panel *wuxga_nt = mipi_dsi_get_drvdata(dsi); | |
317 | ||
318 | wuxga_nt_panel_disable(&wuxga_nt->base); | |
319 | } | |
320 | ||
321 | static struct mipi_dsi_driver wuxga_nt_panel_driver = { | |
322 | .driver = { | |
323 | .name = "panel-panasonic-vvx10f034n00", | |
324 | .of_match_table = wuxga_nt_of_match, | |
325 | }, | |
326 | .probe = wuxga_nt_panel_probe, | |
327 | .remove = wuxga_nt_panel_remove, | |
328 | .shutdown = wuxga_nt_panel_shutdown, | |
329 | }; | |
330 | module_mipi_dsi_driver(wuxga_nt_panel_driver); | |
331 | ||
332 | MODULE_AUTHOR("Werner Johansson <werner.johansson@sonymobile.com>"); | |
333 | MODULE_DESCRIPTION("Panasonic VVX10F034N00 Novatek NT1397-based WUXGA (1920x1200) video mode panel driver"); | |
334 | MODULE_LICENSE("GPL v2"); |