Commit | Line | Data |
---|---|---|
553c48cf TV |
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> | |
553c48cf | 26 | #include <linux/delay.h> |
a8a35931 | 27 | #include <linux/export.h> |
8a2cfea8 | 28 | #include <linux/err.h> |
553c48cf | 29 | #include <linux/errno.h> |
8a2cfea8 TV |
30 | #include <linux/platform_device.h> |
31 | #include <linux/regulator/consumer.h> | |
13b1ba7d | 32 | #include <linux/string.h> |
553c48cf | 33 | |
a0b38cc4 | 34 | #include <video/omapdss.h> |
553c48cf TV |
35 | |
36 | #include "dss.h" | |
195e672a | 37 | #include "dss_features.h" |
553c48cf TV |
38 | |
39 | static struct { | |
8a2cfea8 | 40 | struct regulator *vdds_dsi_reg; |
a72b64b9 | 41 | struct platform_device *dsidev; |
5cf9a264 | 42 | |
c8a5e4e8 AT |
43 | struct mutex lock; |
44 | ||
c499144c | 45 | struct omap_video_timings timings; |
5cf9a264 | 46 | struct dss_lcd_mgr_config mgr_config; |
c6b393d4 | 47 | int data_lines; |
81b87f51 AT |
48 | |
49 | struct omap_dss_output output; | |
553c48cf TV |
50 | } dpi; |
51 | ||
0e8276ef | 52 | static struct platform_device *dpi_get_dsidev(enum omap_channel channel) |
a72b64b9 | 53 | { |
bd0f5cc3 TV |
54 | /* |
55 | * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL | |
56 | * would also be used for DISPC fclk. Meaning, when the DPI output is | |
57 | * disabled, DISPC clock will be disabled, and TV out will stop. | |
58 | */ | |
59 | switch (omapdss_get_version()) { | |
60 | case OMAPDSS_VER_OMAP24xx: | |
61 | case OMAPDSS_VER_OMAP34xx_ES1: | |
62 | case OMAPDSS_VER_OMAP34xx_ES3: | |
63 | case OMAPDSS_VER_OMAP3630: | |
64 | case OMAPDSS_VER_AM35xx: | |
65 | return NULL; | |
bd0f5cc3 | 66 | |
f8ad984c TV |
67 | case OMAPDSS_VER_OMAP4430_ES1: |
68 | case OMAPDSS_VER_OMAP4430_ES2: | |
69 | case OMAPDSS_VER_OMAP4: | |
70 | switch (channel) { | |
71 | case OMAP_DSS_CHANNEL_LCD: | |
72 | return dsi_get_dsidev_from_id(0); | |
73 | case OMAP_DSS_CHANNEL_LCD2: | |
74 | return dsi_get_dsidev_from_id(1); | |
75 | default: | |
76 | return NULL; | |
77 | } | |
78 | ||
79 | case OMAPDSS_VER_OMAP5: | |
80 | switch (channel) { | |
81 | case OMAP_DSS_CHANNEL_LCD: | |
82 | return dsi_get_dsidev_from_id(0); | |
83 | case OMAP_DSS_CHANNEL_LCD3: | |
84 | return dsi_get_dsidev_from_id(1); | |
85 | default: | |
86 | return NULL; | |
87 | } | |
88 | ||
0e8276ef TV |
89 | default: |
90 | return NULL; | |
91 | } | |
a72b64b9 AT |
92 | } |
93 | ||
0e8276ef | 94 | static enum omap_dss_clk_source dpi_get_alt_clk_src(enum omap_channel channel) |
7636b3b4 | 95 | { |
0e8276ef TV |
96 | switch (channel) { |
97 | case OMAP_DSS_CHANNEL_LCD: | |
98 | return OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC; | |
99 | case OMAP_DSS_CHANNEL_LCD2: | |
100 | return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC; | |
101 | default: | |
102 | /* this shouldn't happen */ | |
103 | WARN_ON(1); | |
104 | return OMAP_DSS_CLK_SRC_FCK; | |
105 | } | |
7636b3b4 AT |
106 | } |
107 | ||
100c8262 TV |
108 | struct dpi_clk_calc_ctx { |
109 | struct platform_device *dsidev; | |
110 | ||
111 | /* inputs */ | |
112 | ||
113 | unsigned long pck_min, pck_max; | |
114 | ||
115 | /* outputs */ | |
116 | ||
117 | struct dsi_clock_info dsi_cinfo; | |
118 | struct dss_clock_info dss_cinfo; | |
119 | struct dispc_clock_info dispc_cinfo; | |
120 | }; | |
121 | ||
122 | static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, | |
123 | unsigned long pck, void *data) | |
124 | { | |
125 | struct dpi_clk_calc_ctx *ctx = data; | |
126 | ||
127 | /* | |
128 | * Odd dividers give us uneven duty cycle, causing problem when level | |
129 | * shifted. So skip all odd dividers when the pixel clock is on the | |
130 | * higher side. | |
131 | */ | |
132 | if (ctx->pck_min >= 1000000) { | |
133 | if (lckd > 1 && lckd % 2 != 0) | |
134 | return false; | |
135 | ||
136 | if (pckd > 1 && pckd % 2 != 0) | |
137 | return false; | |
138 | } | |
139 | ||
140 | ctx->dispc_cinfo.lck_div = lckd; | |
141 | ctx->dispc_cinfo.pck_div = pckd; | |
142 | ctx->dispc_cinfo.lck = lck; | |
143 | ctx->dispc_cinfo.pck = pck; | |
144 | ||
145 | return true; | |
146 | } | |
147 | ||
148 | ||
149 | static bool dpi_calc_hsdiv_cb(int regm_dispc, unsigned long dispc, | |
150 | void *data) | |
151 | { | |
152 | struct dpi_clk_calc_ctx *ctx = data; | |
153 | ||
154 | /* | |
155 | * Odd dividers give us uneven duty cycle, causing problem when level | |
156 | * shifted. So skip all odd dividers when the pixel clock is on the | |
157 | * higher side. | |
158 | */ | |
159 | if (regm_dispc > 1 && regm_dispc % 2 != 0 && ctx->pck_min >= 1000000) | |
160 | return false; | |
161 | ||
162 | ctx->dsi_cinfo.regm_dispc = regm_dispc; | |
163 | ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc; | |
164 | ||
165 | return dispc_div_calc(dispc, ctx->pck_min, ctx->pck_max, | |
166 | dpi_calc_dispc_cb, ctx); | |
167 | } | |
168 | ||
169 | ||
170 | static bool dpi_calc_pll_cb(int regn, int regm, unsigned long fint, | |
171 | unsigned long pll, | |
172 | void *data) | |
173 | { | |
174 | struct dpi_clk_calc_ctx *ctx = data; | |
175 | ||
176 | ctx->dsi_cinfo.regn = regn; | |
177 | ctx->dsi_cinfo.regm = regm; | |
178 | ctx->dsi_cinfo.fint = fint; | |
179 | ctx->dsi_cinfo.clkin4ddr = pll; | |
180 | ||
181 | return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->pck_min, | |
182 | dpi_calc_hsdiv_cb, ctx); | |
183 | } | |
184 | ||
185 | static bool dpi_calc_dss_cb(int fckd, unsigned long fck, void *data) | |
186 | { | |
187 | struct dpi_clk_calc_ctx *ctx = data; | |
188 | ||
189 | ctx->dss_cinfo.fck = fck; | |
190 | ctx->dss_cinfo.fck_div = fckd; | |
191 | ||
192 | return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, | |
193 | dpi_calc_dispc_cb, ctx); | |
194 | } | |
195 | ||
196 | static bool dpi_dsi_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) | |
197 | { | |
198 | unsigned long clkin; | |
199 | unsigned long pll_min, pll_max; | |
200 | ||
201 | clkin = dsi_get_pll_clkin(dpi.dsidev); | |
202 | ||
203 | memset(ctx, 0, sizeof(*ctx)); | |
204 | ctx->dsidev = dpi.dsidev; | |
205 | ctx->pck_min = pck - 1000; | |
206 | ctx->pck_max = pck + 1000; | |
207 | ctx->dsi_cinfo.clkin = clkin; | |
208 | ||
209 | pll_min = 0; | |
210 | pll_max = 0; | |
211 | ||
212 | return dsi_pll_calc(dpi.dsidev, clkin, | |
213 | pll_min, pll_max, | |
214 | dpi_calc_pll_cb, ctx); | |
215 | } | |
216 | ||
217 | static bool dpi_dss_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx) | |
218 | { | |
219 | int i; | |
220 | ||
221 | /* | |
222 | * DSS fck gives us very few possibilities, so finding a good pixel | |
223 | * clock may not be possible. We try multiple times to find the clock, | |
224 | * each time widening the pixel clock range we look for, up to | |
2c6360fb | 225 | * +/- ~15MHz. |
100c8262 TV |
226 | */ |
227 | ||
2c6360fb | 228 | for (i = 0; i < 25; ++i) { |
100c8262 TV |
229 | bool ok; |
230 | ||
231 | memset(ctx, 0, sizeof(*ctx)); | |
232 | if (pck > 1000 * i * i * i) | |
233 | ctx->pck_min = max(pck - 1000 * i * i * i, 0lu); | |
234 | else | |
235 | ctx->pck_min = 0; | |
236 | ctx->pck_max = pck + 1000 * i * i * i; | |
237 | ||
238 | ok = dss_div_calc(ctx->pck_min, dpi_calc_dss_cb, ctx); | |
239 | if (ok) | |
240 | return ok; | |
241 | } | |
242 | ||
243 | return false; | |
244 | } | |
245 | ||
246 | ||
247 | ||
03a0d1e8 | 248 | static int dpi_set_dsi_clk(enum omap_channel channel, |
ff1b2cde SS |
249 | unsigned long pck_req, unsigned long *fck, int *lck_div, |
250 | int *pck_div) | |
553c48cf | 251 | { |
100c8262 | 252 | struct dpi_clk_calc_ctx ctx; |
553c48cf | 253 | int r; |
100c8262 | 254 | bool ok; |
553c48cf | 255 | |
100c8262 TV |
256 | ok = dpi_dsi_clk_calc(pck_req, &ctx); |
257 | if (!ok) | |
258 | return -EINVAL; | |
553c48cf | 259 | |
100c8262 | 260 | r = dsi_pll_set_clock_div(dpi.dsidev, &ctx.dsi_cinfo); |
553c48cf TV |
261 | if (r) |
262 | return r; | |
263 | ||
03a0d1e8 TV |
264 | dss_select_lcd_clk_source(channel, |
265 | dpi_get_alt_clk_src(channel)); | |
553c48cf | 266 | |
100c8262 | 267 | dpi.mgr_config.clock_info = ctx.dispc_cinfo; |
553c48cf | 268 | |
100c8262 TV |
269 | *fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk; |
270 | *lck_div = ctx.dispc_cinfo.lck_div; | |
271 | *pck_div = ctx.dispc_cinfo.pck_div; | |
553c48cf TV |
272 | |
273 | return 0; | |
274 | } | |
7636b3b4 | 275 | |
03a0d1e8 TV |
276 | static int dpi_set_dispc_clk(unsigned long pck_req, unsigned long *fck, |
277 | int *lck_div, int *pck_div) | |
553c48cf | 278 | { |
100c8262 | 279 | struct dpi_clk_calc_ctx ctx; |
553c48cf | 280 | int r; |
100c8262 | 281 | bool ok; |
553c48cf | 282 | |
100c8262 TV |
283 | ok = dpi_dss_clk_calc(pck_req, &ctx); |
284 | if (!ok) | |
285 | return -EINVAL; | |
553c48cf | 286 | |
100c8262 | 287 | r = dss_set_clock_div(&ctx.dss_cinfo); |
553c48cf TV |
288 | if (r) |
289 | return r; | |
290 | ||
100c8262 | 291 | dpi.mgr_config.clock_info = ctx.dispc_cinfo; |
553c48cf | 292 | |
100c8262 TV |
293 | *fck = ctx.dss_cinfo.fck; |
294 | *lck_div = ctx.dispc_cinfo.lck_div; | |
295 | *pck_div = ctx.dispc_cinfo.pck_div; | |
553c48cf TV |
296 | |
297 | return 0; | |
298 | } | |
553c48cf | 299 | |
03a0d1e8 | 300 | static int dpi_set_mode(struct omap_overlay_manager *mgr) |
553c48cf | 301 | { |
c499144c | 302 | struct omap_video_timings *t = &dpi.timings; |
7636b3b4 AT |
303 | int lck_div = 0, pck_div = 0; |
304 | unsigned long fck = 0; | |
553c48cf | 305 | unsigned long pck; |
553c48cf TV |
306 | int r = 0; |
307 | ||
8a3db406 | 308 | if (dpi.dsidev) |
03a0d1e8 | 309 | r = dpi_set_dsi_clk(mgr->id, t->pixel_clock * 1000, &fck, |
6d523e7b | 310 | &lck_div, &pck_div); |
7636b3b4 | 311 | else |
03a0d1e8 | 312 | r = dpi_set_dispc_clk(t->pixel_clock * 1000, &fck, |
6d523e7b | 313 | &lck_div, &pck_div); |
553c48cf | 314 | if (r) |
4fbafaf3 | 315 | return r; |
553c48cf TV |
316 | |
317 | pck = fck / lck_div / pck_div / 1000; | |
318 | ||
319 | if (pck != t->pixel_clock) { | |
320 | DSSWARN("Could not find exact pixel clock. " | |
321 | "Requested %d kHz, got %lu kHz\n", | |
322 | t->pixel_clock, pck); | |
323 | ||
324 | t->pixel_clock = pck; | |
325 | } | |
326 | ||
5d512fcd | 327 | dss_mgr_set_timings(mgr, t); |
553c48cf | 328 | |
4fbafaf3 | 329 | return 0; |
553c48cf TV |
330 | } |
331 | ||
03a0d1e8 | 332 | static void dpi_config_lcd_manager(struct omap_overlay_manager *mgr) |
553c48cf | 333 | { |
5cf9a264 | 334 | dpi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; |
569969d6 | 335 | |
5cf9a264 AT |
336 | dpi.mgr_config.stallmode = false; |
337 | dpi.mgr_config.fifohandcheck = false; | |
338 | ||
c6b393d4 | 339 | dpi.mgr_config.video_port_width = dpi.data_lines; |
5cf9a264 AT |
340 | |
341 | dpi.mgr_config.lcden_sig_polarity = 0; | |
342 | ||
5d512fcd | 343 | dss_mgr_set_lcd_config(mgr, &dpi.mgr_config); |
553c48cf TV |
344 | } |
345 | ||
37ac60e4 | 346 | int omapdss_dpi_display_enable(struct omap_dss_device *dssdev) |
553c48cf | 347 | { |
03a0d1e8 | 348 | struct omap_dss_output *out = &dpi.output; |
553c48cf TV |
349 | int r; |
350 | ||
c8a5e4e8 AT |
351 | mutex_lock(&dpi.lock); |
352 | ||
195e672a | 353 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI) && !dpi.vdds_dsi_reg) { |
40410715 | 354 | DSSERR("no VDSS_DSI regulator\n"); |
c8a5e4e8 AT |
355 | r = -ENODEV; |
356 | goto err_no_reg; | |
40410715 RK |
357 | } |
358 | ||
5d512fcd AT |
359 | if (out == NULL || out->manager == NULL) { |
360 | DSSERR("failed to enable display: no output/manager\n"); | |
c8a5e4e8 | 361 | r = -ENODEV; |
5d512fcd | 362 | goto err_no_out_mgr; |
05e1d606 TV |
363 | } |
364 | ||
553c48cf TV |
365 | r = omap_dss_start_device(dssdev); |
366 | if (r) { | |
367 | DSSERR("failed to start device\n"); | |
4fbafaf3 | 368 | goto err_start_dev; |
553c48cf TV |
369 | } |
370 | ||
195e672a | 371 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) { |
8a2cfea8 TV |
372 | r = regulator_enable(dpi.vdds_dsi_reg); |
373 | if (r) | |
4fbafaf3 | 374 | goto err_reg_enable; |
8a2cfea8 TV |
375 | } |
376 | ||
4fbafaf3 | 377 | r = dispc_runtime_get(); |
553c48cf | 378 | if (r) |
4fbafaf3 TV |
379 | goto err_get_dispc; |
380 | ||
03a0d1e8 | 381 | r = dss_dpi_select_source(out->manager->id); |
de09e455 TV |
382 | if (r) |
383 | goto err_src_sel; | |
384 | ||
8a3db406 | 385 | if (dpi.dsidev) { |
4fbafaf3 TV |
386 | r = dsi_runtime_get(dpi.dsidev); |
387 | if (r) | |
388 | goto err_get_dsi; | |
389 | ||
a72b64b9 | 390 | r = dsi_pll_init(dpi.dsidev, 0, 1); |
7636b3b4 | 391 | if (r) |
4fbafaf3 | 392 | goto err_dsi_pll_init; |
7636b3b4 AT |
393 | } |
394 | ||
03a0d1e8 | 395 | r = dpi_set_mode(out->manager); |
553c48cf | 396 | if (r) |
4fbafaf3 | 397 | goto err_set_mode; |
553c48cf | 398 | |
03a0d1e8 | 399 | dpi_config_lcd_manager(out->manager); |
5cf9a264 | 400 | |
553c48cf TV |
401 | mdelay(2); |
402 | ||
5d512fcd | 403 | r = dss_mgr_enable(out->manager); |
33ca237f TV |
404 | if (r) |
405 | goto err_mgr_enable; | |
553c48cf | 406 | |
c8a5e4e8 AT |
407 | mutex_unlock(&dpi.lock); |
408 | ||
553c48cf TV |
409 | return 0; |
410 | ||
33ca237f | 411 | err_mgr_enable: |
4fbafaf3 | 412 | err_set_mode: |
8a3db406 | 413 | if (dpi.dsidev) |
19077a73 | 414 | dsi_pll_uninit(dpi.dsidev, true); |
4fbafaf3 | 415 | err_dsi_pll_init: |
8a3db406 | 416 | if (dpi.dsidev) |
4fbafaf3 TV |
417 | dsi_runtime_put(dpi.dsidev); |
418 | err_get_dsi: | |
de09e455 | 419 | err_src_sel: |
4fbafaf3 TV |
420 | dispc_runtime_put(); |
421 | err_get_dispc: | |
195e672a | 422 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) |
8a2cfea8 | 423 | regulator_disable(dpi.vdds_dsi_reg); |
4fbafaf3 | 424 | err_reg_enable: |
553c48cf | 425 | omap_dss_stop_device(dssdev); |
4fbafaf3 | 426 | err_start_dev: |
5d512fcd | 427 | err_no_out_mgr: |
c8a5e4e8 AT |
428 | err_no_reg: |
429 | mutex_unlock(&dpi.lock); | |
553c48cf TV |
430 | return r; |
431 | } | |
37ac60e4 | 432 | EXPORT_SYMBOL(omapdss_dpi_display_enable); |
553c48cf | 433 | |
37ac60e4 | 434 | void omapdss_dpi_display_disable(struct omap_dss_device *dssdev) |
553c48cf | 435 | { |
03a0d1e8 | 436 | struct omap_overlay_manager *mgr = dpi.output.manager; |
5d512fcd | 437 | |
c8a5e4e8 AT |
438 | mutex_lock(&dpi.lock); |
439 | ||
5d512fcd | 440 | dss_mgr_disable(mgr); |
553c48cf | 441 | |
8a3db406 | 442 | if (dpi.dsidev) { |
a5b8399f | 443 | dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK); |
a72b64b9 | 444 | dsi_pll_uninit(dpi.dsidev, true); |
4fbafaf3 | 445 | dsi_runtime_put(dpi.dsidev); |
7636b3b4 | 446 | } |
553c48cf | 447 | |
4fbafaf3 | 448 | dispc_runtime_put(); |
553c48cf | 449 | |
195e672a | 450 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) |
8a2cfea8 TV |
451 | regulator_disable(dpi.vdds_dsi_reg); |
452 | ||
553c48cf | 453 | omap_dss_stop_device(dssdev); |
c8a5e4e8 AT |
454 | |
455 | mutex_unlock(&dpi.lock); | |
553c48cf | 456 | } |
37ac60e4 | 457 | EXPORT_SYMBOL(omapdss_dpi_display_disable); |
553c48cf | 458 | |
c499144c AT |
459 | void omapdss_dpi_set_timings(struct omap_dss_device *dssdev, |
460 | struct omap_video_timings *timings) | |
553c48cf TV |
461 | { |
462 | DSSDBG("dpi_set_timings\n"); | |
c8a5e4e8 AT |
463 | |
464 | mutex_lock(&dpi.lock); | |
465 | ||
c499144c | 466 | dpi.timings = *timings; |
c499144c | 467 | |
c8a5e4e8 | 468 | mutex_unlock(&dpi.lock); |
553c48cf | 469 | } |
c499144c | 470 | EXPORT_SYMBOL(omapdss_dpi_set_timings); |
553c48cf | 471 | |
69b2048f | 472 | int dpi_check_timings(struct omap_dss_device *dssdev, |
553c48cf TV |
473 | struct omap_video_timings *timings) |
474 | { | |
03a0d1e8 | 475 | struct omap_overlay_manager *mgr = dpi.output.manager; |
553c48cf TV |
476 | int lck_div, pck_div; |
477 | unsigned long fck; | |
478 | unsigned long pck; | |
100c8262 TV |
479 | struct dpi_clk_calc_ctx ctx; |
480 | bool ok; | |
553c48cf | 481 | |
8b095513 | 482 | if (mgr && !dispc_mgr_timings_ok(mgr->id, timings)) |
553c48cf TV |
483 | return -EINVAL; |
484 | ||
485 | if (timings->pixel_clock == 0) | |
486 | return -EINVAL; | |
487 | ||
8a3db406 | 488 | if (dpi.dsidev) { |
100c8262 TV |
489 | ok = dpi_dsi_clk_calc(timings->pixel_clock * 1000, &ctx); |
490 | if (!ok) | |
491 | return -EINVAL; | |
553c48cf | 492 | |
100c8262 | 493 | fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk; |
7636b3b4 | 494 | } else { |
100c8262 TV |
495 | ok = dpi_dss_clk_calc(timings->pixel_clock * 1000, &ctx); |
496 | if (!ok) | |
497 | return -EINVAL; | |
553c48cf | 498 | |
100c8262 | 499 | fck = ctx.dss_cinfo.fck; |
553c48cf | 500 | } |
7636b3b4 | 501 | |
100c8262 TV |
502 | lck_div = ctx.dispc_cinfo.lck_div; |
503 | pck_div = ctx.dispc_cinfo.pck_div; | |
553c48cf TV |
504 | |
505 | pck = fck / lck_div / pck_div / 1000; | |
506 | ||
507 | timings->pixel_clock = pck; | |
508 | ||
509 | return 0; | |
510 | } | |
69b2048f | 511 | EXPORT_SYMBOL(dpi_check_timings); |
553c48cf | 512 | |
c6b393d4 AT |
513 | void omapdss_dpi_set_data_lines(struct omap_dss_device *dssdev, int data_lines) |
514 | { | |
515 | mutex_lock(&dpi.lock); | |
516 | ||
517 | dpi.data_lines = data_lines; | |
518 | ||
519 | mutex_unlock(&dpi.lock); | |
520 | } | |
521 | EXPORT_SYMBOL(omapdss_dpi_set_data_lines); | |
522 | ||
94cf394b | 523 | static int dpi_verify_dsi_pll(struct platform_device *dsidev) |
6061675b TV |
524 | { |
525 | int r; | |
526 | ||
527 | /* do initial setup with the PLL to see if it is operational */ | |
528 | ||
529 | r = dsi_runtime_get(dsidev); | |
530 | if (r) | |
531 | return r; | |
532 | ||
533 | r = dsi_pll_init(dsidev, 0, 1); | |
534 | if (r) { | |
535 | dsi_runtime_put(dsidev); | |
536 | return r; | |
537 | } | |
538 | ||
539 | dsi_pll_uninit(dsidev, true); | |
540 | dsi_runtime_put(dsidev); | |
541 | ||
542 | return 0; | |
543 | } | |
544 | ||
2eea5ae6 TV |
545 | /* |
546 | * Return a hardcoded channel for the DPI output. This should work for | |
547 | * current use cases, but this can be later expanded to either resolve | |
548 | * the channel in some more dynamic manner, or get the channel as a user | |
549 | * parameter. | |
550 | */ | |
551 | static enum omap_channel dpi_get_channel(void) | |
552 | { | |
553 | switch (omapdss_get_version()) { | |
554 | case OMAPDSS_VER_OMAP24xx: | |
555 | case OMAPDSS_VER_OMAP34xx_ES1: | |
556 | case OMAPDSS_VER_OMAP34xx_ES3: | |
557 | case OMAPDSS_VER_OMAP3630: | |
558 | case OMAPDSS_VER_AM35xx: | |
559 | return OMAP_DSS_CHANNEL_LCD; | |
560 | ||
561 | case OMAPDSS_VER_OMAP4430_ES1: | |
562 | case OMAPDSS_VER_OMAP4430_ES2: | |
563 | case OMAPDSS_VER_OMAP4: | |
564 | return OMAP_DSS_CHANNEL_LCD2; | |
565 | ||
566 | case OMAPDSS_VER_OMAP5: | |
567 | return OMAP_DSS_CHANNEL_LCD3; | |
568 | ||
569 | default: | |
570 | DSSWARN("unsupported DSS version\n"); | |
571 | return OMAP_DSS_CHANNEL_LCD; | |
572 | } | |
573 | } | |
574 | ||
94cf394b | 575 | static int dpi_init_display(struct omap_dss_device *dssdev) |
553c48cf | 576 | { |
0e8276ef TV |
577 | struct platform_device *dsidev; |
578 | ||
553c48cf TV |
579 | DSSDBG("init_display\n"); |
580 | ||
195e672a CM |
581 | if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI) && |
582 | dpi.vdds_dsi_reg == NULL) { | |
5f42f2ce | 583 | struct regulator *vdds_dsi; |
553c48cf | 584 | |
5f42f2ce TV |
585 | vdds_dsi = dss_get_vdds_dsi(); |
586 | ||
587 | if (IS_ERR(vdds_dsi)) { | |
8a2cfea8 | 588 | DSSERR("can't get VDDS_DSI regulator\n"); |
5f42f2ce | 589 | return PTR_ERR(vdds_dsi); |
8a2cfea8 | 590 | } |
5f42f2ce TV |
591 | |
592 | dpi.vdds_dsi_reg = vdds_dsi; | |
8a2cfea8 TV |
593 | } |
594 | ||
2eea5ae6 | 595 | dsidev = dpi_get_dsidev(dpi.output.dispc_channel); |
6061675b | 596 | |
1de8e129 | 597 | if (dsidev && dpi_verify_dsi_pll(dsidev)) { |
0e8276ef TV |
598 | dsidev = NULL; |
599 | DSSWARN("DSI PLL not operational\n"); | |
a72b64b9 AT |
600 | } |
601 | ||
0e8276ef TV |
602 | if (dsidev) |
603 | DSSDBG("using DSI PLL for DPI clock\n"); | |
604 | ||
605 | dpi.dsidev = dsidev; | |
606 | ||
553c48cf TV |
607 | return 0; |
608 | } | |
609 | ||
94cf394b | 610 | static struct omap_dss_device *dpi_find_dssdev(struct platform_device *pdev) |
5f42f2ce | 611 | { |
35deca3d | 612 | struct omap_dss_board_info *pdata = pdev->dev.platform_data; |
2bbcce5e | 613 | const char *def_disp_name = omapdss_get_default_display_name(); |
1521653c TV |
614 | struct omap_dss_device *def_dssdev; |
615 | int i; | |
616 | ||
617 | def_dssdev = NULL; | |
35deca3d TV |
618 | |
619 | for (i = 0; i < pdata->num_devices; ++i) { | |
620 | struct omap_dss_device *dssdev = pdata->devices[i]; | |
621 | ||
622 | if (dssdev->type != OMAP_DISPLAY_TYPE_DPI) | |
623 | continue; | |
624 | ||
1521653c TV |
625 | if (def_dssdev == NULL) |
626 | def_dssdev = dssdev; | |
627 | ||
628 | if (def_disp_name != NULL && | |
629 | strcmp(dssdev->name, def_disp_name) == 0) { | |
630 | def_dssdev = dssdev; | |
631 | break; | |
9d8232a7 | 632 | } |
1521653c | 633 | } |
9d8232a7 | 634 | |
1521653c TV |
635 | return def_dssdev; |
636 | } | |
637 | ||
bcb734d2 | 638 | static int dpi_probe_pdata(struct platform_device *dpidev) |
1521653c | 639 | { |
5274484b | 640 | struct omap_dss_device *plat_dssdev; |
1521653c TV |
641 | struct omap_dss_device *dssdev; |
642 | int r; | |
643 | ||
5274484b | 644 | plat_dssdev = dpi_find_dssdev(dpidev); |
1521653c | 645 | |
5274484b | 646 | if (!plat_dssdev) |
bcb734d2 | 647 | return 0; |
5274484b TV |
648 | |
649 | dssdev = dss_alloc_and_init_device(&dpidev->dev); | |
1521653c | 650 | if (!dssdev) |
bcb734d2 | 651 | return -ENOMEM; |
1521653c | 652 | |
5274484b TV |
653 | dss_copy_device_pdata(dssdev, plat_dssdev); |
654 | ||
1521653c TV |
655 | r = dpi_init_display(dssdev); |
656 | if (r) { | |
657 | DSSERR("device %s init failed: %d\n", dssdev->name, r); | |
5274484b | 658 | dss_put_device(dssdev); |
bcb734d2 | 659 | return r; |
1521653c TV |
660 | } |
661 | ||
486c0e17 TV |
662 | r = omapdss_output_set_device(&dpi.output, dssdev); |
663 | if (r) { | |
664 | DSSERR("failed to connect output to new device: %s\n", | |
665 | dssdev->name); | |
666 | dss_put_device(dssdev); | |
bcb734d2 | 667 | return r; |
486c0e17 TV |
668 | } |
669 | ||
5274484b | 670 | r = dss_add_device(dssdev); |
1521653c TV |
671 | if (r) { |
672 | DSSERR("device %s register failed: %d\n", dssdev->name, r); | |
486c0e17 | 673 | omapdss_output_unset_device(&dpi.output); |
5274484b | 674 | dss_put_device(dssdev); |
bcb734d2 | 675 | return r; |
35deca3d | 676 | } |
bcb734d2 TV |
677 | |
678 | return 0; | |
38f3daf6 TV |
679 | } |
680 | ||
94cf394b | 681 | static void dpi_init_output(struct platform_device *pdev) |
81b87f51 AT |
682 | { |
683 | struct omap_dss_output *out = &dpi.output; | |
684 | ||
685 | out->pdev = pdev; | |
686 | out->id = OMAP_DSS_OUTPUT_DPI; | |
687 | out->type = OMAP_DISPLAY_TYPE_DPI; | |
7286a08f | 688 | out->name = "dpi.0"; |
2eea5ae6 | 689 | out->dispc_channel = dpi_get_channel(); |
81b87f51 AT |
690 | |
691 | dss_register_output(out); | |
692 | } | |
693 | ||
694 | static void __exit dpi_uninit_output(struct platform_device *pdev) | |
695 | { | |
696 | struct omap_dss_output *out = &dpi.output; | |
697 | ||
698 | dss_unregister_output(out); | |
699 | } | |
700 | ||
94cf394b | 701 | static int omap_dpi_probe(struct platform_device *pdev) |
38f3daf6 | 702 | { |
bcb734d2 TV |
703 | int r; |
704 | ||
c8a5e4e8 AT |
705 | mutex_init(&dpi.lock); |
706 | ||
81b87f51 AT |
707 | dpi_init_output(pdev); |
708 | ||
bcb734d2 TV |
709 | r = dpi_probe_pdata(pdev); |
710 | if (r) { | |
711 | dpi_uninit_output(pdev); | |
712 | return r; | |
713 | } | |
35deca3d | 714 | |
5f42f2ce TV |
715 | return 0; |
716 | } | |
717 | ||
6e7e8f06 | 718 | static int __exit omap_dpi_remove(struct platform_device *pdev) |
553c48cf | 719 | { |
5274484b | 720 | dss_unregister_child_devices(&pdev->dev); |
35deca3d | 721 | |
81b87f51 AT |
722 | dpi_uninit_output(pdev); |
723 | ||
a57dd4fe | 724 | return 0; |
553c48cf TV |
725 | } |
726 | ||
a57dd4fe | 727 | static struct platform_driver omap_dpi_driver = { |
94cf394b | 728 | .probe = omap_dpi_probe, |
6e7e8f06 | 729 | .remove = __exit_p(omap_dpi_remove), |
a57dd4fe TV |
730 | .driver = { |
731 | .name = "omapdss_dpi", | |
732 | .owner = THIS_MODULE, | |
733 | }, | |
734 | }; | |
735 | ||
6e7e8f06 | 736 | int __init dpi_init_platform_driver(void) |
a57dd4fe | 737 | { |
94cf394b | 738 | return platform_driver_register(&omap_dpi_driver); |
a57dd4fe TV |
739 | } |
740 | ||
6e7e8f06 | 741 | void __exit dpi_uninit_platform_driver(void) |
a57dd4fe TV |
742 | { |
743 | platform_driver_unregister(&omap_dpi_driver); | |
744 | } |