diff options
Diffstat (limited to 'drivers/video/fbdev/omap2/omapfb/dss/dpi.c')
-rw-r--r-- | drivers/video/fbdev/omap2/omapfb/dss/dpi.c | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/drivers/video/fbdev/omap2/omapfb/dss/dpi.c b/drivers/video/fbdev/omap2/omapfb/dss/dpi.c new file mode 100644 index 000000000000..7953e6a52346 --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/dss/dpi.c | |||
@@ -0,0 +1,899 @@ | |||
1 | /* | ||
2 | * linux/drivers/video/omap2/dss/dpi.c | ||
3 | * | ||
4 | * Copyright (C) 2009 Nokia Corporation | ||
5 | * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> | ||
6 | * | ||
7 | * Some code and ideas taken from drivers/video/omap/ driver | ||
8 | * by Imre Deak. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify it | ||
11 | * under the terms of the GNU General Public License version 2 as published by | ||
12 | * the Free Software Foundation. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
17 | * more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License along with | ||
20 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
21 | */ | ||
22 | |||
23 | #define DSS_SUBSYS_NAME "DPI" | ||
24 | |||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/export.h> | ||
28 | #include <linux/err.h> | ||
29 | #include <linux/errno.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/regulator/consumer.h> | ||
32 | #include <linux/string.h> | ||
33 | #include <linux/of.h> | ||
34 | #include <linux/clk.h> | ||
35 | #include <linux/component.h> | ||
36 | |||
37 | #include <video/omapdss.h> | ||
38 | |||
39 | #include "dss.h" | ||
40 | #include "dss_features.h" | ||
41 | |||
42 | #define HSDIV_DISPC 0 | ||
43 | |||
44 | struct dpi_data { | ||
45 | struct platform_device *pdev; | ||
46 | |||
47 | struct regulator *vdds_dsi_reg; | ||
48 | struct dss_pll *pll; | ||
49 | |||
50 | struct mutex lock; | ||
51 | |||
52 | struct omap_video_timings timings; | ||
53 | struct dss_lcd_mgr_config mgr_config; | ||
54 | int data_lines; | ||
55 | |||
56 | struct omap_dss_device output; | ||
57 | |||
58 | bool port_initialized; | ||
59 | }; | ||
60 | |||
61 | static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev) | ||
62 | { | ||
63 | return container_of(dssdev, struct dpi_data, output); | ||
64 | } | ||
65 | |||
66 | /* only used in non-DT mode */ | ||
67 | static struct dpi_data *dpi_get_data_from_pdev(struct platform_device *pdev) | ||
68 | { | ||
69 | return dev_get_drvdata(&pdev->dev); | ||
70 | } | ||
71 | |||
72 | static struct dss_pll *dpi_get_pll(enum omap_channel channel) | ||
73 | { | ||
74 | /* | ||
75 | * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL | ||
76 | * would also be used for DISPC fclk. Meaning, when the DPI output is | ||
77 | * disabled, DISPC clock will be disabled, and TV out will stop. | ||
78 | */ | ||
79 | switch (omapdss_get_version()) { | ||
80 | case OMAPDSS_VER_OMAP24xx: | ||
81 | case OMAPDSS_VER_OMAP34xx_ES1: | ||
82 | case OMAPDSS_VER_OMAP34xx_ES3: | ||
83 | case OMAPDSS_VER_OMAP3630: | ||
84 | case OMAPDSS_VER_AM35xx: | ||
85 | case OMAPDSS_VER_AM43xx: | ||
86 | return NULL; | ||
87 | |||
88 | case OMAPDSS_VER_OMAP4430_ES1: | ||
89 | case OMAPDSS_VER_OMAP4430_ES2: | ||
90 | case OMAPDSS_VER_OMAP4: | ||
91 | switch (channel) { | ||
92 | case OMAP_DSS_CHANNEL_LCD: | ||
93 | return dss_pll_find("dsi0"); | ||
94 | case OMAP_DSS_CHANNEL_LCD2: | ||
95 | return dss_pll_find("dsi1"); | ||
96 | default: | ||
97 | return NULL; | ||
98 | } | ||
99 | |||
100 | case OMAPDSS_VER_OMAP5: | ||
101 | switch (channel) { | ||
102 | case OMAP_DSS_CHANNEL_LCD: | ||
103 | return dss_pll_find("dsi0"); | ||
104 | case OMAP_DSS_CHANNEL_LCD3: | ||
105 | return dss_pll_find("dsi1"); | ||
106 | default: | ||
107 | return NULL; | ||
108 | } | ||
109 | |||
110 | case OMAPDSS_VER_DRA7xx: | ||
111 | switch (channel) { | ||
112 | case OMAP_DSS_CHANNEL_LCD: | ||
113 | case OMAP_DSS_CHANNEL_LCD2: | ||
114 | return dss_pll_find("video0"); | ||
115 | case OMAP_DSS_CHANNEL_LCD3: | ||
116 | return dss_pll_find("video1"); | ||
117 | default: | ||
118 | return NULL; | ||
119 | } | ||
120 | |||
121 | default: | ||
122 | return NULL; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | static enum omap_dss_clk_source dpi_get_alt_clk_src(enum omap_channel channel) | ||
127 | { | ||
128 | switch (channel) { | ||
129 | case OMAP_DSS_CHANNEL_LCD: | ||
130 | return OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC; | ||
131 | case OMAP_DSS_CHANNEL_LCD2: | ||
132 | return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC; | ||
133 | case OMAP_DSS_CHANNEL_LCD3: | ||
134 | return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC; | ||
135 | default: | ||
136 | /* this shouldn't happen */ | ||
137 | WARN_ON(1); | ||
138 | return OMAP_DSS_CLK_SRC_FCK; | ||
139 | } | ||
140 | } | ||
141 | |||
142 | struct dpi_clk_calc_ctx { | ||
143 | struct dss_pll *pll; | ||
144 | |||
145 | /* inputs */ | ||
146 | |||
147 | unsigned long pck_min, pck_max; | ||
148 | |||
149 | /* outputs */ | ||
150 | |||
151 | struct dss_pll_clock_info dsi_cinfo; | ||
152 | unsigned long fck; | ||
153 | struct dispc_clock_info dispc_cinfo; | ||
154 | }; | ||
155 | |||
156 | static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, | ||
157 | unsigned long pck, void *data) | ||
158 | { | ||
159 | struct dpi_clk_calc_ctx *ctx = data; | ||
160 | |||
161 | /* | ||
162 | * Odd dividers give us uneven duty cycle, causing problem when level | ||
163 | * shifted. So skip all odd dividers when the pixel clock is on the | ||
164 | * higher side. | ||
165 | */ | ||
166 | if (ctx->pck_min >= 100000000) { | ||
167 | if (lckd > 1 && lckd % 2 != 0) | ||
168 | return false; | ||
169 | |||
170 | if (pckd > 1 && pckd % 2 != 0) | ||
171 | return false; | ||
172 | } | ||
173 | |||
174 | ctx->dispc_cinfo.lck_div = lckd; | ||
175 | ctx->dispc_cinfo.pck_div = pckd; | ||
176 | ctx->dispc_cinfo.lck = lck; | ||
177 | ctx->dispc_cinfo.pck = pck; | ||
178 | |||
179 | return true; | ||
180 | } | ||
181 | |||
182 | |||
183 | static bool dpi_calc_hsdiv_cb(int m_dispc, unsigned long dispc, | ||
184 | void *data) | ||
185 | { | ||
186 | struct dpi_clk_calc_ctx *ctx = data; | ||
187 | |||
188 | /* | ||
189 | * Odd dividers give us uneven duty cycle, causing problem when level | ||
190 | * shifted. So skip all odd dividers when the pixel clock is on the | ||
191 | * higher side. | ||
192 | */ | ||
193 | if (m_dispc > 1 && m_dispc % 2 != 0 && ctx->pck_min >= 100000000) | ||
194 | return false; | ||
195 | |||
196 | ctx->dsi_cinfo.mX[HSDIV_DISPC] = m_dispc; | ||
197 | ctx->dsi_cinfo.clkout[HSDIV_DISPC] = dispc; | ||
198 | |||
199 | return dispc_div_calc(dispc, ctx->pck_min, ctx->pck_max, | ||
200 | dpi_calc_dispc_cb, ctx); | ||
201 | } | ||
202 | |||
203 | |||
204 | static bool dpi_calc_pll_cb(int n, int m, unsigned long fint, | ||
205 | unsigned long clkdco, | ||
206 | void *data) | ||
207 | { | ||
208 | struct dpi_clk_calc_ctx *ctx = data; | ||
209 | |||
210 | ctx->dsi_cinfo.n = n; | ||
211 | ctx->dsi_cinfo.m = m; | ||
212 | ctx->dsi_cinfo.fint = fint; | ||
213 | ctx->dsi_cinfo.clkdco = clkdco; | ||
214 | |||
215 | return dss_pll_hsdiv_calc(ctx->pll, clkdco, | ||
216 | ctx->pck_min, dss_feat_get_param_max(FEAT_PARAM_DSS_FCK), | ||
217 | dpi_calc_hsdiv_cb, ctx); | ||
218 | } | ||
219 | |||
220 | static bool dpi_calc_dss_cb(unsigned long fck, void *data) | ||
221 | { | ||
222 | struct dpi_clk_calc_ctx *ctx = data; | ||
223 | |||
224 | ctx->fck = fck; | ||
225 | |||
226 | return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, | ||
227 | dpi_calc_dispc_cb, ctx); | ||
228 | } | ||
229 | |||
230 | static bool dpi_dsi_clk_calc(struct dpi_data *dpi, unsigned long pck, | ||
231 | struct dpi_clk_calc_ctx *ctx) | ||
232 | { | ||
233 | unsigned long clkin; | ||
234 | unsigned long pll_min, pll_max; | ||
235 | |||
236 | memset(ctx, 0, sizeof(*ctx)); | ||
237 | ctx->pll = dpi->pll; | ||
238 | ctx->pck_min = pck - 1000; | ||
239 | ctx->pck_max = pck + 1000; | ||
240 | |||
241 | pll_min = 0; | ||
242 | pll_max = 0; | ||
243 | |||
244 | clkin = clk_get_rate(ctx->pll->clkin); | ||
245 | |||
246 | return dss_pll_calc(ctx->pll, clkin, | ||
247 | pll_min, pll_max, | ||
248 | dpi_calc_pll_cb, ctx); | ||
249 | } | ||
250 | |||
251 | static bool dpi_dss_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) | ||
252 | { | ||
253 | int i; | ||
254 | |||
255 | /* | ||
256 | * DSS fck gives us very few possibilities, so finding a good pixel | ||
257 | * clock may not be possible. We try multiple times to find the clock, | ||
258 | * each time widening the pixel clock range we look for, up to | ||
259 | * +/- ~15MHz. | ||
260 | */ | ||
261 | |||
262 | for (i = 0; i < 25; ++i) { | ||
263 | bool ok; | ||
264 | |||
265 | memset(ctx, 0, sizeof(*ctx)); | ||
266 | if (pck > 1000 * i * i * i) | ||
267 | ctx->pck_min = max(pck - 1000 * i * i * i, 0lu); | ||
268 | else | ||
269 | ctx->pck_min = 0; | ||
270 | ctx->pck_max = pck + 1000 * i * i * i; | ||
271 | |||
272 | ok = dss_div_calc(pck, ctx->pck_min, dpi_calc_dss_cb, ctx); | ||
273 | if (ok) | ||
274 | return ok; | ||
275 | } | ||
276 | |||
277 | return false; | ||
278 | } | ||
279 | |||
280 | |||
281 | |||
282 | static int dpi_set_dsi_clk(struct dpi_data *dpi, enum omap_channel channel, | ||
283 | unsigned long pck_req, unsigned long *fck, int *lck_div, | ||
284 | int *pck_div) | ||
285 | { | ||
286 | struct dpi_clk_calc_ctx ctx; | ||
287 | int r; | ||
288 | bool ok; | ||
289 | |||
290 | ok = dpi_dsi_clk_calc(dpi, pck_req, &ctx); | ||
291 | if (!ok) | ||
292 | return -EINVAL; | ||
293 | |||
294 | r = dss_pll_set_config(dpi->pll, &ctx.dsi_cinfo); | ||
295 | if (r) | ||
296 | return r; | ||
297 | |||
298 | dss_select_lcd_clk_source(channel, | ||
299 | dpi_get_alt_clk_src(channel)); | ||
300 | |||
301 | dpi->mgr_config.clock_info = ctx.dispc_cinfo; | ||
302 | |||
303 | *fck = ctx.dsi_cinfo.clkout[HSDIV_DISPC]; | ||
304 | *lck_div = ctx.dispc_cinfo.lck_div; | ||
305 | *pck_div = ctx.dispc_cinfo.pck_div; | ||
306 | |||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req, | ||
311 | unsigned long *fck, int *lck_div, int *pck_div) | ||
312 | { | ||
313 | struct dpi_clk_calc_ctx ctx; | ||
314 | int r; | ||
315 | bool ok; | ||
316 | |||
317 | ok = dpi_dss_clk_calc(pck_req, &ctx); | ||
318 | if (!ok) | ||
319 | return -EINVAL; | ||
320 | |||
321 | r = dss_set_fck_rate(ctx.fck); | ||
322 | if (r) | ||
323 | return r; | ||
324 | |||
325 | dpi->mgr_config.clock_info = ctx.dispc_cinfo; | ||
326 | |||
327 | *fck = ctx.fck; | ||
328 | *lck_div = ctx.dispc_cinfo.lck_div; | ||
329 | *pck_div = ctx.dispc_cinfo.pck_div; | ||
330 | |||
331 | return 0; | ||
332 | } | ||
333 | |||
334 | static int dpi_set_mode(struct dpi_data *dpi) | ||
335 | { | ||
336 | struct omap_dss_device *out = &dpi->output; | ||
337 | struct omap_overlay_manager *mgr = out->manager; | ||
338 | struct omap_video_timings *t = &dpi->timings; | ||
339 | int lck_div = 0, pck_div = 0; | ||
340 | unsigned long fck = 0; | ||
341 | unsigned long pck; | ||
342 | int r = 0; | ||
343 | |||
344 | if (dpi->pll) | ||
345 | r = dpi_set_dsi_clk(dpi, mgr->id, t->pixelclock, &fck, | ||
346 | &lck_div, &pck_div); | ||
347 | else | ||
348 | r = dpi_set_dispc_clk(dpi, t->pixelclock, &fck, | ||
349 | &lck_div, &pck_div); | ||
350 | if (r) | ||
351 | return r; | ||
352 | |||
353 | pck = fck / lck_div / pck_div; | ||
354 | |||
355 | if (pck != t->pixelclock) { | ||
356 | DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n", | ||
357 | t->pixelclock, pck); | ||
358 | |||
359 | t->pixelclock = pck; | ||
360 | } | ||
361 | |||
362 | dss_mgr_set_timings(mgr, t); | ||
363 | |||
364 | return 0; | ||
365 | } | ||
366 | |||
367 | static void dpi_config_lcd_manager(struct dpi_data *dpi) | ||
368 | { | ||
369 | struct omap_dss_device *out = &dpi->output; | ||
370 | struct omap_overlay_manager *mgr = out->manager; | ||
371 | |||
372 | dpi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; | ||
373 | |||
374 | dpi->mgr_config.stallmode = false; | ||
375 | dpi->mgr_config.fifohandcheck = false; | ||
376 | |||
377 | dpi->mgr_config.video_port_width = dpi->data_lines; | ||
378 | |||
379 | dpi->mgr_config.lcden_sig_polarity = 0; | ||
380 | |||
381 | dss_mgr_set_lcd_config(mgr, &dpi->mgr_config); | ||
382 | } | ||
383 | |||
384 | static int dpi_display_enable(struct omap_dss_device *dssdev) | ||
385 | { | ||
386 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
387 | struct omap_dss_device *out = &dpi->output; | ||
388 | int r; | ||
389 | |||
390 | mutex_lock(&dpi->lock); | ||
391 | |||
392 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI) && !dpi->vdds_dsi_reg) { | ||
393 | DSSERR("no VDSS_DSI regulator\n"); | ||
394 | r = -ENODEV; | ||
395 | goto err_no_reg; | ||
396 | } | ||
397 | |||
398 | if (out->manager == NULL) { | ||
399 | DSSERR("failed to enable display: no output/manager\n"); | ||
400 | r = -ENODEV; | ||
401 | goto err_no_out_mgr; | ||
402 | } | ||
403 | |||
404 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) { | ||
405 | r = regulator_enable(dpi->vdds_dsi_reg); | ||
406 | if (r) | ||
407 | goto err_reg_enable; | ||
408 | } | ||
409 | |||
410 | r = dispc_runtime_get(); | ||
411 | if (r) | ||
412 | goto err_get_dispc; | ||
413 | |||
414 | r = dss_dpi_select_source(out->port_num, out->manager->id); | ||
415 | if (r) | ||
416 | goto err_src_sel; | ||
417 | |||
418 | if (dpi->pll) { | ||
419 | r = dss_pll_enable(dpi->pll); | ||
420 | if (r) | ||
421 | goto err_dsi_pll_init; | ||
422 | } | ||
423 | |||
424 | r = dpi_set_mode(dpi); | ||
425 | if (r) | ||
426 | goto err_set_mode; | ||
427 | |||
428 | dpi_config_lcd_manager(dpi); | ||
429 | |||
430 | mdelay(2); | ||
431 | |||
432 | r = dss_mgr_enable(out->manager); | ||
433 | if (r) | ||
434 | goto err_mgr_enable; | ||
435 | |||
436 | mutex_unlock(&dpi->lock); | ||
437 | |||
438 | return 0; | ||
439 | |||
440 | err_mgr_enable: | ||
441 | err_set_mode: | ||
442 | if (dpi->pll) | ||
443 | dss_pll_disable(dpi->pll); | ||
444 | err_dsi_pll_init: | ||
445 | err_src_sel: | ||
446 | dispc_runtime_put(); | ||
447 | err_get_dispc: | ||
448 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) | ||
449 | regulator_disable(dpi->vdds_dsi_reg); | ||
450 | err_reg_enable: | ||
451 | err_no_out_mgr: | ||
452 | err_no_reg: | ||
453 | mutex_unlock(&dpi->lock); | ||
454 | return r; | ||
455 | } | ||
456 | |||
457 | static void dpi_display_disable(struct omap_dss_device *dssdev) | ||
458 | { | ||
459 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
460 | struct omap_overlay_manager *mgr = dpi->output.manager; | ||
461 | |||
462 | mutex_lock(&dpi->lock); | ||
463 | |||
464 | dss_mgr_disable(mgr); | ||
465 | |||
466 | if (dpi->pll) { | ||
467 | dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); | ||
468 | dss_pll_disable(dpi->pll); | ||
469 | } | ||
470 | |||
471 | dispc_runtime_put(); | ||
472 | |||
473 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) | ||
474 | regulator_disable(dpi->vdds_dsi_reg); | ||
475 | |||
476 | mutex_unlock(&dpi->lock); | ||
477 | } | ||
478 | |||
479 | static void dpi_set_timings(struct omap_dss_device *dssdev, | ||
480 | struct omap_video_timings *timings) | ||
481 | { | ||
482 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
483 | |||
484 | DSSDBG("dpi_set_timings\n"); | ||
485 | |||
486 | mutex_lock(&dpi->lock); | ||
487 | |||
488 | dpi->timings = *timings; | ||
489 | |||
490 | mutex_unlock(&dpi->lock); | ||
491 | } | ||
492 | |||
493 | static void dpi_get_timings(struct omap_dss_device *dssdev, | ||
494 | struct omap_video_timings *timings) | ||
495 | { | ||
496 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
497 | |||
498 | mutex_lock(&dpi->lock); | ||
499 | |||
500 | *timings = dpi->timings; | ||
501 | |||
502 | mutex_unlock(&dpi->lock); | ||
503 | } | ||
504 | |||
505 | static int dpi_check_timings(struct omap_dss_device *dssdev, | ||
506 | struct omap_video_timings *timings) | ||
507 | { | ||
508 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
509 | struct omap_overlay_manager *mgr = dpi->output.manager; | ||
510 | int lck_div, pck_div; | ||
511 | unsigned long fck; | ||
512 | unsigned long pck; | ||
513 | struct dpi_clk_calc_ctx ctx; | ||
514 | bool ok; | ||
515 | |||
516 | if (mgr && !dispc_mgr_timings_ok(mgr->id, timings)) | ||
517 | return -EINVAL; | ||
518 | |||
519 | if (timings->pixelclock == 0) | ||
520 | return -EINVAL; | ||
521 | |||
522 | if (dpi->pll) { | ||
523 | ok = dpi_dsi_clk_calc(dpi, timings->pixelclock, &ctx); | ||
524 | if (!ok) | ||
525 | return -EINVAL; | ||
526 | |||
527 | fck = ctx.dsi_cinfo.clkout[HSDIV_DISPC]; | ||
528 | } else { | ||
529 | ok = dpi_dss_clk_calc(timings->pixelclock, &ctx); | ||
530 | if (!ok) | ||
531 | return -EINVAL; | ||
532 | |||
533 | fck = ctx.fck; | ||
534 | } | ||
535 | |||
536 | lck_div = ctx.dispc_cinfo.lck_div; | ||
537 | pck_div = ctx.dispc_cinfo.pck_div; | ||
538 | |||
539 | pck = fck / lck_div / pck_div; | ||
540 | |||
541 | timings->pixelclock = pck; | ||
542 | |||
543 | return 0; | ||
544 | } | ||
545 | |||
546 | static void dpi_set_data_lines(struct omap_dss_device *dssdev, int data_lines) | ||
547 | { | ||
548 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
549 | |||
550 | mutex_lock(&dpi->lock); | ||
551 | |||
552 | dpi->data_lines = data_lines; | ||
553 | |||
554 | mutex_unlock(&dpi->lock); | ||
555 | } | ||
556 | |||
557 | static int dpi_verify_dsi_pll(struct dss_pll *pll) | ||
558 | { | ||
559 | int r; | ||
560 | |||
561 | /* do initial setup with the PLL to see if it is operational */ | ||
562 | |||
563 | r = dss_pll_enable(pll); | ||
564 | if (r) | ||
565 | return r; | ||
566 | |||
567 | dss_pll_disable(pll); | ||
568 | |||
569 | return 0; | ||
570 | } | ||
571 | |||
572 | static int dpi_init_regulator(struct dpi_data *dpi) | ||
573 | { | ||
574 | struct regulator *vdds_dsi; | ||
575 | |||
576 | if (!dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) | ||
577 | return 0; | ||
578 | |||
579 | if (dpi->vdds_dsi_reg) | ||
580 | return 0; | ||
581 | |||
582 | vdds_dsi = devm_regulator_get(&dpi->pdev->dev, "vdds_dsi"); | ||
583 | if (IS_ERR(vdds_dsi)) { | ||
584 | if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) | ||
585 | DSSERR("can't get VDDS_DSI regulator\n"); | ||
586 | return PTR_ERR(vdds_dsi); | ||
587 | } | ||
588 | |||
589 | dpi->vdds_dsi_reg = vdds_dsi; | ||
590 | |||
591 | return 0; | ||
592 | } | ||
593 | |||
594 | static void dpi_init_pll(struct dpi_data *dpi) | ||
595 | { | ||
596 | struct dss_pll *pll; | ||
597 | |||
598 | if (dpi->pll) | ||
599 | return; | ||
600 | |||
601 | pll = dpi_get_pll(dpi->output.dispc_channel); | ||
602 | if (!pll) | ||
603 | return; | ||
604 | |||
605 | /* On DRA7 we need to set a mux to use the PLL */ | ||
606 | if (omapdss_get_version() == OMAPDSS_VER_DRA7xx) | ||
607 | dss_ctrl_pll_set_control_mux(pll->id, dpi->output.dispc_channel); | ||
608 | |||
609 | if (dpi_verify_dsi_pll(pll)) { | ||
610 | DSSWARN("DSI PLL not operational\n"); | ||
611 | return; | ||
612 | } | ||
613 | |||
614 | dpi->pll = pll; | ||
615 | } | ||
616 | |||
617 | /* | ||
618 | * Return a hardcoded channel for the DPI output. This should work for | ||
619 | * current use cases, but this can be later expanded to either resolve | ||
620 | * the channel in some more dynamic manner, or get the channel as a user | ||
621 | * parameter. | ||
622 | */ | ||
623 | static enum omap_channel dpi_get_channel(int port_num) | ||
624 | { | ||
625 | switch (omapdss_get_version()) { | ||
626 | case OMAPDSS_VER_OMAP24xx: | ||
627 | case OMAPDSS_VER_OMAP34xx_ES1: | ||
628 | case OMAPDSS_VER_OMAP34xx_ES3: | ||
629 | case OMAPDSS_VER_OMAP3630: | ||
630 | case OMAPDSS_VER_AM35xx: | ||
631 | case OMAPDSS_VER_AM43xx: | ||
632 | return OMAP_DSS_CHANNEL_LCD; | ||
633 | |||
634 | case OMAPDSS_VER_DRA7xx: | ||
635 | switch (port_num) { | ||
636 | case 2: | ||
637 | return OMAP_DSS_CHANNEL_LCD3; | ||
638 | case 1: | ||
639 | return OMAP_DSS_CHANNEL_LCD2; | ||
640 | case 0: | ||
641 | default: | ||
642 | return OMAP_DSS_CHANNEL_LCD; | ||
643 | } | ||
644 | |||
645 | case OMAPDSS_VER_OMAP4430_ES1: | ||
646 | case OMAPDSS_VER_OMAP4430_ES2: | ||
647 | case OMAPDSS_VER_OMAP4: | ||
648 | return OMAP_DSS_CHANNEL_LCD2; | ||
649 | |||
650 | case OMAPDSS_VER_OMAP5: | ||
651 | return OMAP_DSS_CHANNEL_LCD3; | ||
652 | |||
653 | default: | ||
654 | DSSWARN("unsupported DSS version\n"); | ||
655 | return OMAP_DSS_CHANNEL_LCD; | ||
656 | } | ||
657 | } | ||
658 | |||
659 | static int dpi_connect(struct omap_dss_device *dssdev, | ||
660 | struct omap_dss_device *dst) | ||
661 | { | ||
662 | struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); | ||
663 | struct omap_overlay_manager *mgr; | ||
664 | int r; | ||
665 | |||
666 | r = dpi_init_regulator(dpi); | ||
667 | if (r) | ||
668 | return r; | ||
669 | |||
670 | dpi_init_pll(dpi); | ||
671 | |||
672 | mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel); | ||
673 | if (!mgr) | ||
674 | return -ENODEV; | ||
675 | |||
676 | r = dss_mgr_connect(mgr, dssdev); | ||
677 | if (r) | ||
678 | return r; | ||
679 | |||
680 | r = omapdss_output_set_device(dssdev, dst); | ||
681 | if (r) { | ||
682 | DSSERR("failed to connect output to new device: %s\n", | ||
683 | dst->name); | ||
684 | dss_mgr_disconnect(mgr, dssdev); | ||
685 | return r; | ||
686 | } | ||
687 | |||
688 | return 0; | ||
689 | } | ||
690 | |||
691 | static void dpi_disconnect(struct omap_dss_device *dssdev, | ||
692 | struct omap_dss_device *dst) | ||
693 | { | ||
694 | WARN_ON(dst != dssdev->dst); | ||
695 | |||
696 | if (dst != dssdev->dst) | ||
697 | return; | ||
698 | |||
699 | omapdss_output_unset_device(dssdev); | ||
700 | |||
701 | if (dssdev->manager) | ||
702 | dss_mgr_disconnect(dssdev->manager, dssdev); | ||
703 | } | ||
704 | |||
705 | static const struct omapdss_dpi_ops dpi_ops = { | ||
706 | .connect = dpi_connect, | ||
707 | .disconnect = dpi_disconnect, | ||
708 | |||
709 | .enable = dpi_display_enable, | ||
710 | .disable = dpi_display_disable, | ||
711 | |||
712 | .check_timings = dpi_check_timings, | ||
713 | .set_timings = dpi_set_timings, | ||
714 | .get_timings = dpi_get_timings, | ||
715 | |||
716 | .set_data_lines = dpi_set_data_lines, | ||
717 | }; | ||
718 | |||
719 | static void dpi_init_output(struct platform_device *pdev) | ||
720 | { | ||
721 | struct dpi_data *dpi = dpi_get_data_from_pdev(pdev); | ||
722 | struct omap_dss_device *out = &dpi->output; | ||
723 | |||
724 | out->dev = &pdev->dev; | ||
725 | out->id = OMAP_DSS_OUTPUT_DPI; | ||
726 | out->output_type = OMAP_DISPLAY_TYPE_DPI; | ||
727 | out->name = "dpi.0"; | ||
728 | out->dispc_channel = dpi_get_channel(0); | ||
729 | out->ops.dpi = &dpi_ops; | ||
730 | out->owner = THIS_MODULE; | ||
731 | |||
732 | omapdss_register_output(out); | ||
733 | } | ||
734 | |||
735 | static void dpi_uninit_output(struct platform_device *pdev) | ||
736 | { | ||
737 | struct dpi_data *dpi = dpi_get_data_from_pdev(pdev); | ||
738 | struct omap_dss_device *out = &dpi->output; | ||
739 | |||
740 | omapdss_unregister_output(out); | ||
741 | } | ||
742 | |||
743 | static void dpi_init_output_port(struct platform_device *pdev, | ||
744 | struct device_node *port) | ||
745 | { | ||
746 | struct dpi_data *dpi = port->data; | ||
747 | struct omap_dss_device *out = &dpi->output; | ||
748 | int r; | ||
749 | u32 port_num; | ||
750 | |||
751 | r = of_property_read_u32(port, "reg", &port_num); | ||
752 | if (r) | ||
753 | port_num = 0; | ||
754 | |||
755 | switch (port_num) { | ||
756 | case 2: | ||
757 | out->name = "dpi.2"; | ||
758 | break; | ||
759 | case 1: | ||
760 | out->name = "dpi.1"; | ||
761 | break; | ||
762 | case 0: | ||
763 | default: | ||
764 | out->name = "dpi.0"; | ||
765 | break; | ||
766 | } | ||
767 | |||
768 | out->dev = &pdev->dev; | ||
769 | out->id = OMAP_DSS_OUTPUT_DPI; | ||
770 | out->output_type = OMAP_DISPLAY_TYPE_DPI; | ||
771 | out->dispc_channel = dpi_get_channel(port_num); | ||
772 | out->port_num = port_num; | ||
773 | out->ops.dpi = &dpi_ops; | ||
774 | out->owner = THIS_MODULE; | ||
775 | |||
776 | omapdss_register_output(out); | ||
777 | } | ||
778 | |||
779 | static void dpi_uninit_output_port(struct device_node *port) | ||
780 | { | ||
781 | struct dpi_data *dpi = port->data; | ||
782 | struct omap_dss_device *out = &dpi->output; | ||
783 | |||
784 | omapdss_unregister_output(out); | ||
785 | } | ||
786 | |||
787 | static int dpi_bind(struct device *dev, struct device *master, void *data) | ||
788 | { | ||
789 | struct platform_device *pdev = to_platform_device(dev); | ||
790 | struct dpi_data *dpi; | ||
791 | |||
792 | dpi = devm_kzalloc(&pdev->dev, sizeof(*dpi), GFP_KERNEL); | ||
793 | if (!dpi) | ||
794 | return -ENOMEM; | ||
795 | |||
796 | dpi->pdev = pdev; | ||
797 | |||
798 | dev_set_drvdata(&pdev->dev, dpi); | ||
799 | |||
800 | mutex_init(&dpi->lock); | ||
801 | |||
802 | dpi_init_output(pdev); | ||
803 | |||
804 | return 0; | ||
805 | } | ||
806 | |||
807 | static void dpi_unbind(struct device *dev, struct device *master, void *data) | ||
808 | { | ||
809 | struct platform_device *pdev = to_platform_device(dev); | ||
810 | |||
811 | dpi_uninit_output(pdev); | ||
812 | } | ||
813 | |||
814 | static const struct component_ops dpi_component_ops = { | ||
815 | .bind = dpi_bind, | ||
816 | .unbind = dpi_unbind, | ||
817 | }; | ||
818 | |||
819 | static int dpi_probe(struct platform_device *pdev) | ||
820 | { | ||
821 | return component_add(&pdev->dev, &dpi_component_ops); | ||
822 | } | ||
823 | |||
824 | static int dpi_remove(struct platform_device *pdev) | ||
825 | { | ||
826 | component_del(&pdev->dev, &dpi_component_ops); | ||
827 | return 0; | ||
828 | } | ||
829 | |||
830 | static struct platform_driver omap_dpi_driver = { | ||
831 | .probe = dpi_probe, | ||
832 | .remove = dpi_remove, | ||
833 | .driver = { | ||
834 | .name = "omapdss_dpi", | ||
835 | .suppress_bind_attrs = true, | ||
836 | }, | ||
837 | }; | ||
838 | |||
839 | int __init dpi_init_platform_driver(void) | ||
840 | { | ||
841 | return platform_driver_register(&omap_dpi_driver); | ||
842 | } | ||
843 | |||
844 | void dpi_uninit_platform_driver(void) | ||
845 | { | ||
846 | platform_driver_unregister(&omap_dpi_driver); | ||
847 | } | ||
848 | |||
849 | int dpi_init_port(struct platform_device *pdev, struct device_node *port) | ||
850 | { | ||
851 | struct dpi_data *dpi; | ||
852 | struct device_node *ep; | ||
853 | u32 datalines; | ||
854 | int r; | ||
855 | |||
856 | dpi = devm_kzalloc(&pdev->dev, sizeof(*dpi), GFP_KERNEL); | ||
857 | if (!dpi) | ||
858 | return -ENOMEM; | ||
859 | |||
860 | ep = omapdss_of_get_next_endpoint(port, NULL); | ||
861 | if (!ep) | ||
862 | return 0; | ||
863 | |||
864 | r = of_property_read_u32(ep, "data-lines", &datalines); | ||
865 | if (r) { | ||
866 | DSSERR("failed to parse datalines\n"); | ||
867 | goto err_datalines; | ||
868 | } | ||
869 | |||
870 | dpi->data_lines = datalines; | ||
871 | |||
872 | of_node_put(ep); | ||
873 | |||
874 | dpi->pdev = pdev; | ||
875 | port->data = dpi; | ||
876 | |||
877 | mutex_init(&dpi->lock); | ||
878 | |||
879 | dpi_init_output_port(pdev, port); | ||
880 | |||
881 | dpi->port_initialized = true; | ||
882 | |||
883 | return 0; | ||
884 | |||
885 | err_datalines: | ||
886 | of_node_put(ep); | ||
887 | |||
888 | return r; | ||
889 | } | ||
890 | |||
891 | void dpi_uninit_port(struct device_node *port) | ||
892 | { | ||
893 | struct dpi_data *dpi = port->data; | ||
894 | |||
895 | if (!dpi->port_initialized) | ||
896 | return; | ||
897 | |||
898 | dpi_uninit_output_port(port); | ||
899 | } | ||