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> | |
26 | #include <linux/clk.h> | |
27 | #include <linux/delay.h> | |
28 | #include <linux/errno.h> | |
29 | ||
30 | #include <plat/display.h> | |
31 | #include <plat/cpu.h> | |
32 | ||
33 | #include "dss.h" | |
34 | ||
35 | static struct { | |
36 | int update_enabled; | |
37 | } dpi; | |
38 | ||
39 | #ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL | |
40 | static int dpi_set_dsi_clk(bool is_tft, unsigned long pck_req, | |
41 | unsigned long *fck, int *lck_div, int *pck_div) | |
42 | { | |
43 | struct dsi_clock_info dsi_cinfo; | |
44 | struct dispc_clock_info dispc_cinfo; | |
45 | int r; | |
46 | ||
47 | r = dsi_pll_calc_clock_div_pck(is_tft, pck_req, &dsi_cinfo, | |
48 | &dispc_cinfo); | |
49 | if (r) | |
50 | return r; | |
51 | ||
52 | r = dsi_pll_set_clock_div(&dsi_cinfo); | |
53 | if (r) | |
54 | return r; | |
55 | ||
56 | dss_select_clk_source(0, 1); | |
57 | ||
58 | r = dispc_set_clock_div(&dispc_cinfo); | |
59 | if (r) | |
60 | return r; | |
61 | ||
62 | *fck = dsi_cinfo.dsi1_pll_fclk; | |
63 | *lck_div = dispc_cinfo.lck_div; | |
64 | *pck_div = dispc_cinfo.pck_div; | |
65 | ||
66 | return 0; | |
67 | } | |
68 | #else | |
69 | static int dpi_set_dispc_clk(bool is_tft, unsigned long pck_req, | |
70 | unsigned long *fck, int *lck_div, int *pck_div) | |
71 | { | |
72 | struct dss_clock_info dss_cinfo; | |
73 | struct dispc_clock_info dispc_cinfo; | |
74 | int r; | |
75 | ||
76 | r = dss_calc_clock_div(is_tft, pck_req, &dss_cinfo, &dispc_cinfo); | |
77 | if (r) | |
78 | return r; | |
79 | ||
80 | r = dss_set_clock_div(&dss_cinfo); | |
81 | if (r) | |
82 | return r; | |
83 | ||
84 | r = dispc_set_clock_div(&dispc_cinfo); | |
85 | if (r) | |
86 | return r; | |
87 | ||
88 | *fck = dss_cinfo.fck; | |
89 | *lck_div = dispc_cinfo.lck_div; | |
90 | *pck_div = dispc_cinfo.pck_div; | |
91 | ||
92 | return 0; | |
93 | } | |
94 | #endif | |
95 | ||
96 | static int dpi_set_mode(struct omap_dss_device *dssdev) | |
97 | { | |
98 | struct omap_video_timings *t = &dssdev->panel.timings; | |
99 | int lck_div, pck_div; | |
100 | unsigned long fck; | |
101 | unsigned long pck; | |
102 | bool is_tft; | |
103 | int r = 0; | |
104 | ||
105 | dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
106 | ||
107 | dispc_set_pol_freq(dssdev->panel.config, dssdev->panel.acbi, | |
108 | dssdev->panel.acb); | |
109 | ||
110 | is_tft = (dssdev->panel.config & OMAP_DSS_LCD_TFT) != 0; | |
111 | ||
112 | #ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL | |
113 | r = dpi_set_dsi_clk(is_tft, t->pixel_clock * 1000, | |
114 | &fck, &lck_div, &pck_div); | |
115 | #else | |
116 | r = dpi_set_dispc_clk(is_tft, t->pixel_clock * 1000, | |
117 | &fck, &lck_div, &pck_div); | |
118 | #endif | |
119 | if (r) | |
120 | goto err0; | |
121 | ||
122 | pck = fck / lck_div / pck_div / 1000; | |
123 | ||
124 | if (pck != t->pixel_clock) { | |
125 | DSSWARN("Could not find exact pixel clock. " | |
126 | "Requested %d kHz, got %lu kHz\n", | |
127 | t->pixel_clock, pck); | |
128 | ||
129 | t->pixel_clock = pck; | |
130 | } | |
131 | ||
132 | dispc_set_lcd_timings(t); | |
133 | ||
134 | err0: | |
135 | dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
136 | return r; | |
137 | } | |
138 | ||
139 | static int dpi_basic_init(struct omap_dss_device *dssdev) | |
140 | { | |
141 | bool is_tft; | |
142 | ||
143 | is_tft = (dssdev->panel.config & OMAP_DSS_LCD_TFT) != 0; | |
144 | ||
145 | dispc_set_parallel_interface_mode(OMAP_DSS_PARALLELMODE_BYPASS); | |
146 | dispc_set_lcd_display_type(is_tft ? OMAP_DSS_LCD_DISPLAY_TFT : | |
147 | OMAP_DSS_LCD_DISPLAY_STN); | |
148 | dispc_set_tft_data_lines(dssdev->phy.dpi.data_lines); | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | static int dpi_display_enable(struct omap_dss_device *dssdev) | |
154 | { | |
155 | int r; | |
156 | ||
157 | r = omap_dss_start_device(dssdev); | |
158 | if (r) { | |
159 | DSSERR("failed to start device\n"); | |
160 | goto err0; | |
161 | } | |
162 | ||
163 | if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { | |
164 | DSSERR("display already enabled\n"); | |
165 | r = -EINVAL; | |
166 | goto err1; | |
167 | } | |
168 | ||
169 | dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
170 | ||
171 | r = dpi_basic_init(dssdev); | |
172 | if (r) | |
173 | goto err2; | |
174 | ||
175 | #ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL | |
176 | dss_clk_enable(DSS_CLK_FCK2); | |
177 | r = dsi_pll_init(dssdev, 0, 1); | |
178 | if (r) | |
179 | goto err3; | |
180 | #endif | |
181 | r = dpi_set_mode(dssdev); | |
182 | if (r) | |
183 | goto err4; | |
184 | ||
185 | mdelay(2); | |
186 | ||
187 | dispc_enable_lcd_out(1); | |
188 | ||
189 | r = dssdev->driver->enable(dssdev); | |
190 | if (r) | |
191 | goto err5; | |
192 | ||
193 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | |
194 | ||
195 | return 0; | |
196 | ||
197 | err5: | |
198 | dispc_enable_lcd_out(0); | |
199 | err4: | |
200 | #ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL | |
201 | dsi_pll_uninit(); | |
202 | err3: | |
203 | dss_clk_disable(DSS_CLK_FCK2); | |
204 | #endif | |
205 | err2: | |
206 | dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
207 | err1: | |
208 | omap_dss_stop_device(dssdev); | |
209 | err0: | |
210 | return r; | |
211 | } | |
212 | ||
213 | static int dpi_display_resume(struct omap_dss_device *dssdev); | |
214 | ||
215 | static void dpi_display_disable(struct omap_dss_device *dssdev) | |
216 | { | |
217 | if (dssdev->state == OMAP_DSS_DISPLAY_DISABLED) | |
218 | return; | |
219 | ||
220 | if (dssdev->state == OMAP_DSS_DISPLAY_SUSPENDED) | |
221 | dpi_display_resume(dssdev); | |
222 | ||
223 | dssdev->driver->disable(dssdev); | |
224 | ||
225 | dispc_enable_lcd_out(0); | |
226 | ||
227 | #ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL | |
228 | dss_select_clk_source(0, 0); | |
229 | dsi_pll_uninit(); | |
230 | dss_clk_disable(DSS_CLK_FCK2); | |
231 | #endif | |
232 | ||
233 | dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
234 | ||
235 | dssdev->state = OMAP_DSS_DISPLAY_DISABLED; | |
236 | ||
237 | omap_dss_stop_device(dssdev); | |
238 | } | |
239 | ||
240 | static int dpi_display_suspend(struct omap_dss_device *dssdev) | |
241 | { | |
242 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) | |
243 | return -EINVAL; | |
244 | ||
245 | DSSDBG("dpi_display_suspend\n"); | |
246 | ||
247 | if (dssdev->driver->suspend) | |
248 | dssdev->driver->suspend(dssdev); | |
249 | ||
250 | dispc_enable_lcd_out(0); | |
251 | ||
252 | dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
253 | ||
254 | dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; | |
255 | ||
256 | return 0; | |
257 | } | |
258 | ||
259 | static int dpi_display_resume(struct omap_dss_device *dssdev) | |
260 | { | |
261 | if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) | |
262 | return -EINVAL; | |
263 | ||
264 | DSSDBG("dpi_display_resume\n"); | |
265 | ||
266 | dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK1); | |
267 | ||
268 | dispc_enable_lcd_out(1); | |
269 | ||
270 | if (dssdev->driver->resume) | |
271 | dssdev->driver->resume(dssdev); | |
272 | ||
273 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | |
274 | ||
275 | return 0; | |
276 | } | |
277 | ||
278 | static void dpi_set_timings(struct omap_dss_device *dssdev, | |
279 | struct omap_video_timings *timings) | |
280 | { | |
281 | DSSDBG("dpi_set_timings\n"); | |
282 | dssdev->panel.timings = *timings; | |
283 | if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { | |
284 | dpi_set_mode(dssdev); | |
285 | dispc_go(OMAP_DSS_CHANNEL_LCD); | |
286 | } | |
287 | } | |
288 | ||
289 | static int dpi_check_timings(struct omap_dss_device *dssdev, | |
290 | struct omap_video_timings *timings) | |
291 | { | |
292 | bool is_tft; | |
293 | int r; | |
294 | int lck_div, pck_div; | |
295 | unsigned long fck; | |
296 | unsigned long pck; | |
297 | ||
298 | if (!dispc_lcd_timings_ok(timings)) | |
299 | return -EINVAL; | |
300 | ||
301 | if (timings->pixel_clock == 0) | |
302 | return -EINVAL; | |
303 | ||
304 | is_tft = (dssdev->panel.config & OMAP_DSS_LCD_TFT) != 0; | |
305 | ||
306 | #ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL | |
307 | { | |
308 | struct dsi_clock_info dsi_cinfo; | |
309 | struct dispc_clock_info dispc_cinfo; | |
310 | r = dsi_pll_calc_clock_div_pck(is_tft, | |
311 | timings->pixel_clock * 1000, | |
312 | &dsi_cinfo, &dispc_cinfo); | |
313 | ||
314 | if (r) | |
315 | return r; | |
316 | ||
317 | fck = dsi_cinfo.dsi1_pll_fclk; | |
318 | lck_div = dispc_cinfo.lck_div; | |
319 | pck_div = dispc_cinfo.pck_div; | |
320 | } | |
321 | #else | |
322 | { | |
323 | struct dss_clock_info dss_cinfo; | |
324 | struct dispc_clock_info dispc_cinfo; | |
325 | r = dss_calc_clock_div(is_tft, timings->pixel_clock * 1000, | |
326 | &dss_cinfo, &dispc_cinfo); | |
327 | ||
328 | if (r) | |
329 | return r; | |
330 | ||
331 | fck = dss_cinfo.fck; | |
332 | lck_div = dispc_cinfo.lck_div; | |
333 | pck_div = dispc_cinfo.pck_div; | |
334 | } | |
335 | #endif | |
336 | ||
337 | pck = fck / lck_div / pck_div / 1000; | |
338 | ||
339 | timings->pixel_clock = pck; | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | static void dpi_get_timings(struct omap_dss_device *dssdev, | |
345 | struct omap_video_timings *timings) | |
346 | { | |
347 | *timings = dssdev->panel.timings; | |
348 | } | |
349 | ||
350 | static int dpi_display_set_update_mode(struct omap_dss_device *dssdev, | |
351 | enum omap_dss_update_mode mode) | |
352 | { | |
353 | if (mode == OMAP_DSS_UPDATE_MANUAL) | |
354 | return -EINVAL; | |
355 | ||
356 | if (mode == OMAP_DSS_UPDATE_DISABLED) { | |
357 | dispc_enable_lcd_out(0); | |
358 | dpi.update_enabled = 0; | |
359 | } else { | |
360 | dispc_enable_lcd_out(1); | |
361 | dpi.update_enabled = 1; | |
362 | } | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
367 | static enum omap_dss_update_mode dpi_display_get_update_mode( | |
368 | struct omap_dss_device *dssdev) | |
369 | { | |
370 | return dpi.update_enabled ? OMAP_DSS_UPDATE_AUTO : | |
371 | OMAP_DSS_UPDATE_DISABLED; | |
372 | } | |
373 | ||
374 | int dpi_init_display(struct omap_dss_device *dssdev) | |
375 | { | |
376 | DSSDBG("init_display\n"); | |
377 | ||
378 | dssdev->enable = dpi_display_enable; | |
379 | dssdev->disable = dpi_display_disable; | |
380 | dssdev->suspend = dpi_display_suspend; | |
381 | dssdev->resume = dpi_display_resume; | |
382 | dssdev->set_timings = dpi_set_timings; | |
383 | dssdev->check_timings = dpi_check_timings; | |
384 | dssdev->get_timings = dpi_get_timings; | |
385 | dssdev->set_update_mode = dpi_display_set_update_mode; | |
386 | dssdev->get_update_mode = dpi_display_get_update_mode; | |
387 | ||
388 | return 0; | |
389 | } | |
390 | ||
391 | int dpi_init(void) | |
392 | { | |
393 | return 0; | |
394 | } | |
395 | ||
396 | void dpi_exit(void) | |
397 | { | |
398 | } | |
399 |