]>
Commit | Line | Data |
---|---|---|
559d6701 TV |
1 | /* |
2 | * linux/drivers/video/omap2/dss/dss.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 "DSS" | |
24 | ||
25 | #include <linux/kernel.h> | |
26 | #include <linux/io.h> | |
a8a35931 | 27 | #include <linux/export.h> |
559d6701 TV |
28 | #include <linux/err.h> |
29 | #include <linux/delay.h> | |
559d6701 TV |
30 | #include <linux/seq_file.h> |
31 | #include <linux/clk.h> | |
24e6289c | 32 | #include <linux/platform_device.h> |
4fbafaf3 | 33 | #include <linux/pm_runtime.h> |
185bae10 | 34 | #include <linux/gfp.h> |
33366d0e | 35 | #include <linux/sizes.h> |
559d6701 | 36 | |
a0b38cc4 | 37 | #include <video/omapdss.h> |
2c799cef | 38 | |
559d6701 | 39 | #include "dss.h" |
6ec549e5 | 40 | #include "dss_features.h" |
559d6701 | 41 | |
559d6701 TV |
42 | #define DSS_SZ_REGS SZ_512 |
43 | ||
44 | struct dss_reg { | |
45 | u16 idx; | |
46 | }; | |
47 | ||
48 | #define DSS_REG(idx) ((const struct dss_reg) { idx }) | |
49 | ||
50 | #define DSS_REVISION DSS_REG(0x0000) | |
51 | #define DSS_SYSCONFIG DSS_REG(0x0010) | |
52 | #define DSS_SYSSTATUS DSS_REG(0x0014) | |
559d6701 TV |
53 | #define DSS_CONTROL DSS_REG(0x0040) |
54 | #define DSS_SDI_CONTROL DSS_REG(0x0044) | |
55 | #define DSS_PLL_CONTROL DSS_REG(0x0048) | |
56 | #define DSS_SDI_STATUS DSS_REG(0x005C) | |
57 | ||
58 | #define REG_GET(idx, start, end) \ | |
59 | FLD_GET(dss_read_reg(idx), start, end) | |
60 | ||
61 | #define REG_FLD_MOD(idx, val, start, end) \ | |
62 | dss_write_reg(idx, FLD_MOD(dss_read_reg(idx), val, start, end)) | |
63 | ||
852f0838 TV |
64 | static int dss_runtime_get(void); |
65 | static void dss_runtime_put(void); | |
66 | ||
185bae10 CM |
67 | struct dss_features { |
68 | u8 fck_div_max; | |
69 | u8 dss_fck_multiplier; | |
70 | const char *clk_name; | |
de09e455 | 71 | int (*dpi_select_source)(enum omap_channel channel); |
185bae10 CM |
72 | }; |
73 | ||
559d6701 | 74 | static struct { |
96c401bc | 75 | struct platform_device *pdev; |
559d6701 | 76 | void __iomem *base; |
4fbafaf3 | 77 | |
559d6701 | 78 | struct clk *dpll4_m4_ck; |
4fbafaf3 | 79 | struct clk *dss_clk; |
5aaee69d | 80 | unsigned long dss_clk_rate; |
559d6701 TV |
81 | |
82 | unsigned long cache_req_pck; | |
83 | unsigned long cache_prate; | |
84 | struct dss_clock_info cache_dss_cinfo; | |
85 | struct dispc_clock_info cache_dispc_cinfo; | |
86 | ||
5a8b572d | 87 | enum omap_dss_clk_source dsi_clk_source[MAX_NUM_DSI]; |
89a35e51 AT |
88 | enum omap_dss_clk_source dispc_clk_source; |
89 | enum omap_dss_clk_source lcd_clk_source[MAX_DSS_LCD_MANAGERS]; | |
2f18c4d8 | 90 | |
69f06054 | 91 | bool ctx_valid; |
559d6701 | 92 | u32 ctx[DSS_SZ_REGS / sizeof(u32)]; |
185bae10 CM |
93 | |
94 | const struct dss_features *feat; | |
559d6701 TV |
95 | } dss; |
96 | ||
235e7dba | 97 | static const char * const dss_generic_clk_source_names[] = { |
89a35e51 AT |
98 | [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC] = "DSI_PLL_HSDIV_DISPC", |
99 | [OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI] = "DSI_PLL_HSDIV_DSI", | |
100 | [OMAP_DSS_CLK_SRC_FCK] = "DSS_FCK", | |
901e5fe5 TV |
101 | [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC] = "DSI_PLL2_HSDIV_DISPC", |
102 | [OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI] = "DSI_PLL2_HSDIV_DSI", | |
067a57e4 AT |
103 | }; |
104 | ||
559d6701 TV |
105 | static inline void dss_write_reg(const struct dss_reg idx, u32 val) |
106 | { | |
107 | __raw_writel(val, dss.base + idx.idx); | |
108 | } | |
109 | ||
110 | static inline u32 dss_read_reg(const struct dss_reg idx) | |
111 | { | |
112 | return __raw_readl(dss.base + idx.idx); | |
113 | } | |
114 | ||
115 | #define SR(reg) \ | |
116 | dss.ctx[(DSS_##reg).idx / sizeof(u32)] = dss_read_reg(DSS_##reg) | |
117 | #define RR(reg) \ | |
118 | dss_write_reg(DSS_##reg, dss.ctx[(DSS_##reg).idx / sizeof(u32)]) | |
119 | ||
4fbafaf3 | 120 | static void dss_save_context(void) |
559d6701 | 121 | { |
4fbafaf3 | 122 | DSSDBG("dss_save_context\n"); |
559d6701 | 123 | |
559d6701 TV |
124 | SR(CONTROL); |
125 | ||
6ec549e5 TV |
126 | if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & |
127 | OMAP_DISPLAY_TYPE_SDI) { | |
128 | SR(SDI_CONTROL); | |
129 | SR(PLL_CONTROL); | |
130 | } | |
69f06054 TV |
131 | |
132 | dss.ctx_valid = true; | |
133 | ||
134 | DSSDBG("context saved\n"); | |
559d6701 TV |
135 | } |
136 | ||
4fbafaf3 | 137 | static void dss_restore_context(void) |
559d6701 | 138 | { |
4fbafaf3 | 139 | DSSDBG("dss_restore_context\n"); |
559d6701 | 140 | |
69f06054 TV |
141 | if (!dss.ctx_valid) |
142 | return; | |
143 | ||
559d6701 TV |
144 | RR(CONTROL); |
145 | ||
6ec549e5 TV |
146 | if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & |
147 | OMAP_DISPLAY_TYPE_SDI) { | |
148 | RR(SDI_CONTROL); | |
149 | RR(PLL_CONTROL); | |
150 | } | |
69f06054 TV |
151 | |
152 | DSSDBG("context restored\n"); | |
559d6701 TV |
153 | } |
154 | ||
155 | #undef SR | |
156 | #undef RR | |
157 | ||
bdb736ab AT |
158 | int dss_get_ctx_loss_count(void) |
159 | { | |
160 | struct omap_dss_board_info *board_data = dss.pdev->dev.platform_data; | |
161 | int cnt; | |
162 | ||
163 | if (!board_data->get_context_loss_count) | |
164 | return -ENOENT; | |
165 | ||
166 | cnt = board_data->get_context_loss_count(&dss.pdev->dev); | |
167 | ||
168 | WARN_ONCE(cnt < 0, "get_context_loss_count failed: %d\n", cnt); | |
169 | ||
170 | return cnt; | |
171 | } | |
172 | ||
889b4fd7 | 173 | void dss_sdi_init(int datapairs) |
559d6701 TV |
174 | { |
175 | u32 l; | |
176 | ||
177 | BUG_ON(datapairs > 3 || datapairs < 1); | |
178 | ||
179 | l = dss_read_reg(DSS_SDI_CONTROL); | |
180 | l = FLD_MOD(l, 0xf, 19, 15); /* SDI_PDIV */ | |
181 | l = FLD_MOD(l, datapairs-1, 3, 2); /* SDI_PRSEL */ | |
182 | l = FLD_MOD(l, 2, 1, 0); /* SDI_BWSEL */ | |
183 | dss_write_reg(DSS_SDI_CONTROL, l); | |
184 | ||
185 | l = dss_read_reg(DSS_PLL_CONTROL); | |
186 | l = FLD_MOD(l, 0x7, 25, 22); /* SDI_PLL_FREQSEL */ | |
187 | l = FLD_MOD(l, 0xb, 16, 11); /* SDI_PLL_REGN */ | |
188 | l = FLD_MOD(l, 0xb4, 10, 1); /* SDI_PLL_REGM */ | |
189 | dss_write_reg(DSS_PLL_CONTROL, l); | |
190 | } | |
191 | ||
192 | int dss_sdi_enable(void) | |
193 | { | |
194 | unsigned long timeout; | |
195 | ||
196 | dispc_pck_free_enable(1); | |
197 | ||
198 | /* Reset SDI PLL */ | |
199 | REG_FLD_MOD(DSS_PLL_CONTROL, 1, 18, 18); /* SDI_PLL_SYSRESET */ | |
200 | udelay(1); /* wait 2x PCLK */ | |
201 | ||
202 | /* Lock SDI PLL */ | |
203 | REG_FLD_MOD(DSS_PLL_CONTROL, 1, 28, 28); /* SDI_PLL_GOBIT */ | |
204 | ||
205 | /* Waiting for PLL lock request to complete */ | |
206 | timeout = jiffies + msecs_to_jiffies(500); | |
207 | while (dss_read_reg(DSS_SDI_STATUS) & (1 << 6)) { | |
208 | if (time_after_eq(jiffies, timeout)) { | |
209 | DSSERR("PLL lock request timed out\n"); | |
210 | goto err1; | |
211 | } | |
212 | } | |
213 | ||
214 | /* Clearing PLL_GO bit */ | |
215 | REG_FLD_MOD(DSS_PLL_CONTROL, 0, 28, 28); | |
216 | ||
217 | /* Waiting for PLL to lock */ | |
218 | timeout = jiffies + msecs_to_jiffies(500); | |
219 | while (!(dss_read_reg(DSS_SDI_STATUS) & (1 << 5))) { | |
220 | if (time_after_eq(jiffies, timeout)) { | |
221 | DSSERR("PLL lock timed out\n"); | |
222 | goto err1; | |
223 | } | |
224 | } | |
225 | ||
226 | dispc_lcd_enable_signal(1); | |
227 | ||
228 | /* Waiting for SDI reset to complete */ | |
229 | timeout = jiffies + msecs_to_jiffies(500); | |
230 | while (!(dss_read_reg(DSS_SDI_STATUS) & (1 << 2))) { | |
231 | if (time_after_eq(jiffies, timeout)) { | |
232 | DSSERR("SDI reset timed out\n"); | |
233 | goto err2; | |
234 | } | |
235 | } | |
236 | ||
237 | return 0; | |
238 | ||
239 | err2: | |
240 | dispc_lcd_enable_signal(0); | |
241 | err1: | |
242 | /* Reset SDI PLL */ | |
243 | REG_FLD_MOD(DSS_PLL_CONTROL, 0, 18, 18); /* SDI_PLL_SYSRESET */ | |
244 | ||
245 | dispc_pck_free_enable(0); | |
246 | ||
247 | return -ETIMEDOUT; | |
248 | } | |
249 | ||
250 | void dss_sdi_disable(void) | |
251 | { | |
252 | dispc_lcd_enable_signal(0); | |
253 | ||
254 | dispc_pck_free_enable(0); | |
255 | ||
256 | /* Reset SDI PLL */ | |
257 | REG_FLD_MOD(DSS_PLL_CONTROL, 0, 18, 18); /* SDI_PLL_SYSRESET */ | |
258 | } | |
259 | ||
89a35e51 | 260 | const char *dss_get_generic_clk_source_name(enum omap_dss_clk_source clk_src) |
067a57e4 | 261 | { |
235e7dba | 262 | return dss_generic_clk_source_names[clk_src]; |
067a57e4 AT |
263 | } |
264 | ||
559d6701 TV |
265 | void dss_dump_clocks(struct seq_file *s) |
266 | { | |
267 | unsigned long dpll4_ck_rate; | |
268 | unsigned long dpll4_m4_ck_rate; | |
0acf659f TV |
269 | const char *fclk_name, *fclk_real_name; |
270 | unsigned long fclk_rate; | |
559d6701 | 271 | |
4fbafaf3 TV |
272 | if (dss_runtime_get()) |
273 | return; | |
559d6701 | 274 | |
559d6701 TV |
275 | seq_printf(s, "- DSS -\n"); |
276 | ||
89a35e51 AT |
277 | fclk_name = dss_get_generic_clk_source_name(OMAP_DSS_CLK_SRC_FCK); |
278 | fclk_real_name = dss_feat_get_clk_source_name(OMAP_DSS_CLK_SRC_FCK); | |
4fbafaf3 | 279 | fclk_rate = clk_get_rate(dss.dss_clk); |
559d6701 | 280 | |
0acf659f TV |
281 | if (dss.dpll4_m4_ck) { |
282 | dpll4_ck_rate = clk_get_rate(clk_get_parent(dss.dpll4_m4_ck)); | |
283 | dpll4_m4_ck_rate = clk_get_rate(dss.dpll4_m4_ck); | |
284 | ||
285 | seq_printf(s, "dpll4_ck %lu\n", dpll4_ck_rate); | |
286 | ||
185bae10 CM |
287 | seq_printf(s, "%s (%s) = %lu / %lu * %d = %lu\n", |
288 | fclk_name, fclk_real_name, dpll4_ck_rate, | |
289 | dpll4_ck_rate / dpll4_m4_ck_rate, | |
290 | dss.feat->dss_fck_multiplier, fclk_rate); | |
0acf659f TV |
291 | } else { |
292 | seq_printf(s, "%s (%s) = %lu\n", | |
293 | fclk_name, fclk_real_name, | |
294 | fclk_rate); | |
295 | } | |
559d6701 | 296 | |
4fbafaf3 | 297 | dss_runtime_put(); |
559d6701 TV |
298 | } |
299 | ||
e40402cf | 300 | static void dss_dump_regs(struct seq_file *s) |
559d6701 TV |
301 | { |
302 | #define DUMPREG(r) seq_printf(s, "%-35s %08x\n", #r, dss_read_reg(r)) | |
303 | ||
4fbafaf3 TV |
304 | if (dss_runtime_get()) |
305 | return; | |
559d6701 TV |
306 | |
307 | DUMPREG(DSS_REVISION); | |
308 | DUMPREG(DSS_SYSCONFIG); | |
309 | DUMPREG(DSS_SYSSTATUS); | |
559d6701 | 310 | DUMPREG(DSS_CONTROL); |
6ec549e5 TV |
311 | |
312 | if (dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_LCD) & | |
313 | OMAP_DISPLAY_TYPE_SDI) { | |
314 | DUMPREG(DSS_SDI_CONTROL); | |
315 | DUMPREG(DSS_PLL_CONTROL); | |
316 | DUMPREG(DSS_SDI_STATUS); | |
317 | } | |
559d6701 | 318 | |
4fbafaf3 | 319 | dss_runtime_put(); |
559d6701 TV |
320 | #undef DUMPREG |
321 | } | |
322 | ||
a5b8399f | 323 | static void dss_select_dispc_clk_source(enum omap_dss_clk_source clk_src) |
2f18c4d8 | 324 | { |
a72b64b9 | 325 | struct platform_device *dsidev; |
2f18c4d8 | 326 | int b; |
ea75159e | 327 | u8 start, end; |
2f18c4d8 | 328 | |
66534e8e | 329 | switch (clk_src) { |
89a35e51 | 330 | case OMAP_DSS_CLK_SRC_FCK: |
66534e8e AT |
331 | b = 0; |
332 | break; | |
89a35e51 | 333 | case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: |
66534e8e | 334 | b = 1; |
a72b64b9 AT |
335 | dsidev = dsi_get_dsidev_from_id(0); |
336 | dsi_wait_pll_hsdiv_dispc_active(dsidev); | |
66534e8e | 337 | break; |
5a8b572d AT |
338 | case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: |
339 | b = 2; | |
340 | dsidev = dsi_get_dsidev_from_id(1); | |
341 | dsi_wait_pll_hsdiv_dispc_active(dsidev); | |
342 | break; | |
66534e8e AT |
343 | default: |
344 | BUG(); | |
c6eee968 | 345 | return; |
66534e8e | 346 | } |
e406f907 | 347 | |
ea75159e AT |
348 | dss_feat_get_reg_field(FEAT_REG_DISPC_CLK_SWITCH, &start, &end); |
349 | ||
350 | REG_FLD_MOD(DSS_CONTROL, b, start, end); /* DISPC_CLK_SWITCH */ | |
2f18c4d8 TV |
351 | |
352 | dss.dispc_clk_source = clk_src; | |
353 | } | |
354 | ||
5a8b572d AT |
355 | void dss_select_dsi_clk_source(int dsi_module, |
356 | enum omap_dss_clk_source clk_src) | |
559d6701 | 357 | { |
a72b64b9 | 358 | struct platform_device *dsidev; |
a2e5d827 | 359 | int b, pos; |
2f18c4d8 | 360 | |
66534e8e | 361 | switch (clk_src) { |
89a35e51 | 362 | case OMAP_DSS_CLK_SRC_FCK: |
66534e8e AT |
363 | b = 0; |
364 | break; | |
89a35e51 | 365 | case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DSI: |
5a8b572d | 366 | BUG_ON(dsi_module != 0); |
66534e8e | 367 | b = 1; |
a72b64b9 AT |
368 | dsidev = dsi_get_dsidev_from_id(0); |
369 | dsi_wait_pll_hsdiv_dsi_active(dsidev); | |
66534e8e | 370 | break; |
5a8b572d AT |
371 | case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DSI: |
372 | BUG_ON(dsi_module != 1); | |
373 | b = 1; | |
374 | dsidev = dsi_get_dsidev_from_id(1); | |
375 | dsi_wait_pll_hsdiv_dsi_active(dsidev); | |
376 | break; | |
66534e8e AT |
377 | default: |
378 | BUG(); | |
c6eee968 | 379 | return; |
66534e8e | 380 | } |
e406f907 | 381 | |
a2e5d827 AT |
382 | pos = dsi_module == 0 ? 1 : 10; |
383 | REG_FLD_MOD(DSS_CONTROL, b, pos, pos); /* DSIx_CLK_SWITCH */ | |
2f18c4d8 | 384 | |
5a8b572d | 385 | dss.dsi_clk_source[dsi_module] = clk_src; |
559d6701 TV |
386 | } |
387 | ||
ea75159e | 388 | void dss_select_lcd_clk_source(enum omap_channel channel, |
89a35e51 | 389 | enum omap_dss_clk_source clk_src) |
ea75159e | 390 | { |
a72b64b9 | 391 | struct platform_device *dsidev; |
ea75159e AT |
392 | int b, ix, pos; |
393 | ||
a5b8399f TV |
394 | if (!dss_has_feature(FEAT_LCD_CLK_SRC)) { |
395 | dss_select_dispc_clk_source(clk_src); | |
ea75159e | 396 | return; |
a5b8399f | 397 | } |
ea75159e AT |
398 | |
399 | switch (clk_src) { | |
89a35e51 | 400 | case OMAP_DSS_CLK_SRC_FCK: |
ea75159e AT |
401 | b = 0; |
402 | break; | |
89a35e51 | 403 | case OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC: |
ea75159e AT |
404 | BUG_ON(channel != OMAP_DSS_CHANNEL_LCD); |
405 | b = 1; | |
a72b64b9 AT |
406 | dsidev = dsi_get_dsidev_from_id(0); |
407 | dsi_wait_pll_hsdiv_dispc_active(dsidev); | |
ea75159e | 408 | break; |
5a8b572d | 409 | case OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC: |
e86d456a CM |
410 | BUG_ON(channel != OMAP_DSS_CHANNEL_LCD2 && |
411 | channel != OMAP_DSS_CHANNEL_LCD3); | |
5a8b572d AT |
412 | b = 1; |
413 | dsidev = dsi_get_dsidev_from_id(1); | |
414 | dsi_wait_pll_hsdiv_dispc_active(dsidev); | |
415 | break; | |
ea75159e AT |
416 | default: |
417 | BUG(); | |
c6eee968 | 418 | return; |
ea75159e AT |
419 | } |
420 | ||
e86d456a CM |
421 | pos = channel == OMAP_DSS_CHANNEL_LCD ? 0 : |
422 | (channel == OMAP_DSS_CHANNEL_LCD2 ? 12 : 19); | |
ea75159e AT |
423 | REG_FLD_MOD(DSS_CONTROL, b, pos, pos); /* LCDx_CLK_SWITCH */ |
424 | ||
e86d456a CM |
425 | ix = channel == OMAP_DSS_CHANNEL_LCD ? 0 : |
426 | (channel == OMAP_DSS_CHANNEL_LCD2 ? 1 : 2); | |
ea75159e AT |
427 | dss.lcd_clk_source[ix] = clk_src; |
428 | } | |
429 | ||
89a35e51 | 430 | enum omap_dss_clk_source dss_get_dispc_clk_source(void) |
559d6701 | 431 | { |
2f18c4d8 | 432 | return dss.dispc_clk_source; |
559d6701 TV |
433 | } |
434 | ||
5a8b572d | 435 | enum omap_dss_clk_source dss_get_dsi_clk_source(int dsi_module) |
559d6701 | 436 | { |
5a8b572d | 437 | return dss.dsi_clk_source[dsi_module]; |
559d6701 TV |
438 | } |
439 | ||
89a35e51 | 440 | enum omap_dss_clk_source dss_get_lcd_clk_source(enum omap_channel channel) |
ea75159e | 441 | { |
89976f29 | 442 | if (dss_has_feature(FEAT_LCD_CLK_SRC)) { |
e86d456a CM |
443 | int ix = channel == OMAP_DSS_CHANNEL_LCD ? 0 : |
444 | (channel == OMAP_DSS_CHANNEL_LCD2 ? 1 : 2); | |
89976f29 AT |
445 | return dss.lcd_clk_source[ix]; |
446 | } else { | |
447 | /* LCD_CLK source is the same as DISPC_FCLK source for | |
448 | * OMAP2 and OMAP3 */ | |
449 | return dss.dispc_clk_source; | |
450 | } | |
ea75159e AT |
451 | } |
452 | ||
930b027e TV |
453 | /* calculate clock rates using dividers in cinfo */ |
454 | int dss_calc_clock_rates(struct dss_clock_info *cinfo) | |
455 | { | |
456 | if (dss.dpll4_m4_ck) { | |
457 | unsigned long prate; | |
458 | ||
459 | if (cinfo->fck_div > dss.feat->fck_div_max || | |
460 | cinfo->fck_div == 0) | |
461 | return -EINVAL; | |
462 | ||
463 | prate = clk_get_rate(clk_get_parent(dss.dpll4_m4_ck)); | |
464 | ||
465 | cinfo->fck = prate / cinfo->fck_div * | |
466 | dss.feat->dss_fck_multiplier; | |
467 | } else { | |
468 | if (cinfo->fck_div != 0) | |
469 | return -EINVAL; | |
470 | cinfo->fck = clk_get_rate(dss.dss_clk); | |
471 | } | |
472 | ||
473 | return 0; | |
474 | } | |
475 | ||
559d6701 TV |
476 | int dss_set_clock_div(struct dss_clock_info *cinfo) |
477 | { | |
0acf659f TV |
478 | if (dss.dpll4_m4_ck) { |
479 | unsigned long prate; | |
480 | int r; | |
559d6701 | 481 | |
559d6701 TV |
482 | prate = clk_get_rate(clk_get_parent(dss.dpll4_m4_ck)); |
483 | DSSDBG("dpll4_m4 = %ld\n", prate); | |
484 | ||
485 | r = clk_set_rate(dss.dpll4_m4_ck, prate / cinfo->fck_div); | |
486 | if (r) | |
487 | return r; | |
0acf659f TV |
488 | } else { |
489 | if (cinfo->fck_div != 0) | |
490 | return -EINVAL; | |
559d6701 TV |
491 | } |
492 | ||
5aaee69d TV |
493 | dss.dss_clk_rate = clk_get_rate(dss.dss_clk); |
494 | ||
495 | WARN_ONCE(dss.dss_clk_rate != cinfo->fck, "clk rate mismatch"); | |
496 | ||
559d6701 TV |
497 | DSSDBG("fck = %ld (%d)\n", cinfo->fck, cinfo->fck_div); |
498 | ||
499 | return 0; | |
500 | } | |
501 | ||
559d6701 TV |
502 | unsigned long dss_get_dpll4_rate(void) |
503 | { | |
0acf659f | 504 | if (dss.dpll4_m4_ck) |
559d6701 TV |
505 | return clk_get_rate(clk_get_parent(dss.dpll4_m4_ck)); |
506 | else | |
507 | return 0; | |
508 | } | |
509 | ||
5aaee69d TV |
510 | unsigned long dss_get_dispc_clk_rate(void) |
511 | { | |
512 | return dss.dss_clk_rate; | |
513 | } | |
514 | ||
13a1a2b2 TV |
515 | static int dss_setup_default_clock(void) |
516 | { | |
517 | unsigned long max_dss_fck, prate; | |
518 | unsigned fck_div; | |
519 | struct dss_clock_info dss_cinfo = { 0 }; | |
520 | int r; | |
521 | ||
522 | if (dss.dpll4_m4_ck == NULL) | |
523 | return 0; | |
524 | ||
525 | max_dss_fck = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); | |
526 | ||
527 | prate = dss_get_dpll4_rate(); | |
528 | ||
529 | fck_div = DIV_ROUND_UP(prate * dss.feat->dss_fck_multiplier, | |
530 | max_dss_fck); | |
531 | ||
532 | dss_cinfo.fck_div = fck_div; | |
533 | ||
534 | r = dss_calc_clock_rates(&dss_cinfo); | |
535 | if (r) | |
536 | return r; | |
537 | ||
538 | r = dss_set_clock_div(&dss_cinfo); | |
539 | if (r) | |
540 | return r; | |
541 | ||
542 | return 0; | |
543 | } | |
544 | ||
6d523e7b | 545 | int dss_calc_clock_div(unsigned long req_pck, struct dss_clock_info *dss_cinfo, |
559d6701 TV |
546 | struct dispc_clock_info *dispc_cinfo) |
547 | { | |
548 | unsigned long prate; | |
549 | struct dss_clock_info best_dss; | |
550 | struct dispc_clock_info best_dispc; | |
551 | ||
819d807c | 552 | unsigned long fck, max_dss_fck; |
559d6701 | 553 | |
185bae10 | 554 | u16 fck_div; |
559d6701 TV |
555 | |
556 | int match = 0; | |
557 | int min_fck_per_pck; | |
558 | ||
559 | prate = dss_get_dpll4_rate(); | |
560 | ||
31ef8237 | 561 | max_dss_fck = dss_feat_get_param_max(FEAT_PARAM_DSS_FCK); |
819d807c | 562 | |
4fbafaf3 | 563 | fck = clk_get_rate(dss.dss_clk); |
185bae10 CM |
564 | if (req_pck == dss.cache_req_pck && prate == dss.cache_prate && |
565 | dss.cache_dss_cinfo.fck == fck) { | |
559d6701 TV |
566 | DSSDBG("dispc clock info found from cache.\n"); |
567 | *dss_cinfo = dss.cache_dss_cinfo; | |
568 | *dispc_cinfo = dss.cache_dispc_cinfo; | |
569 | return 0; | |
570 | } | |
571 | ||
572 | min_fck_per_pck = CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK; | |
573 | ||
574 | if (min_fck_per_pck && | |
819d807c | 575 | req_pck * min_fck_per_pck > max_dss_fck) { |
559d6701 TV |
576 | DSSERR("Requested pixel clock not possible with the current " |
577 | "OMAP2_DSS_MIN_FCK_PER_PCK setting. Turning " | |
578 | "the constraint off.\n"); | |
579 | min_fck_per_pck = 0; | |
580 | } | |
581 | ||
582 | retry: | |
583 | memset(&best_dss, 0, sizeof(best_dss)); | |
584 | memset(&best_dispc, 0, sizeof(best_dispc)); | |
585 | ||
2de11086 | 586 | if (dss.dpll4_m4_ck == NULL) { |
559d6701 TV |
587 | struct dispc_clock_info cur_dispc; |
588 | /* XXX can we change the clock on omap2? */ | |
4fbafaf3 | 589 | fck = clk_get_rate(dss.dss_clk); |
559d6701 TV |
590 | fck_div = 1; |
591 | ||
6d523e7b | 592 | dispc_find_clk_divs(req_pck, fck, &cur_dispc); |
559d6701 TV |
593 | match = 1; |
594 | ||
595 | best_dss.fck = fck; | |
596 | best_dss.fck_div = fck_div; | |
597 | ||
598 | best_dispc = cur_dispc; | |
599 | ||
600 | goto found; | |
2de11086 | 601 | } else { |
185bae10 | 602 | for (fck_div = dss.feat->fck_div_max; fck_div > 0; --fck_div) { |
559d6701 TV |
603 | struct dispc_clock_info cur_dispc; |
604 | ||
185bae10 | 605 | fck = prate / fck_div * dss.feat->dss_fck_multiplier; |
559d6701 | 606 | |
819d807c | 607 | if (fck > max_dss_fck) |
559d6701 TV |
608 | continue; |
609 | ||
610 | if (min_fck_per_pck && | |
611 | fck < req_pck * min_fck_per_pck) | |
612 | continue; | |
613 | ||
614 | match = 1; | |
615 | ||
6d523e7b | 616 | dispc_find_clk_divs(req_pck, fck, &cur_dispc); |
559d6701 TV |
617 | |
618 | if (abs(cur_dispc.pck - req_pck) < | |
619 | abs(best_dispc.pck - req_pck)) { | |
620 | ||
621 | best_dss.fck = fck; | |
622 | best_dss.fck_div = fck_div; | |
623 | ||
624 | best_dispc = cur_dispc; | |
625 | ||
626 | if (cur_dispc.pck == req_pck) | |
627 | goto found; | |
628 | } | |
629 | } | |
559d6701 TV |
630 | } |
631 | ||
632 | found: | |
633 | if (!match) { | |
634 | if (min_fck_per_pck) { | |
635 | DSSERR("Could not find suitable clock settings.\n" | |
636 | "Turning FCK/PCK constraint off and" | |
637 | "trying again.\n"); | |
638 | min_fck_per_pck = 0; | |
639 | goto retry; | |
640 | } | |
641 | ||
642 | DSSERR("Could not find suitable clock settings.\n"); | |
643 | ||
644 | return -EINVAL; | |
645 | } | |
646 | ||
647 | if (dss_cinfo) | |
648 | *dss_cinfo = best_dss; | |
649 | if (dispc_cinfo) | |
650 | *dispc_cinfo = best_dispc; | |
651 | ||
652 | dss.cache_req_pck = req_pck; | |
653 | dss.cache_prate = prate; | |
654 | dss.cache_dss_cinfo = best_dss; | |
655 | dss.cache_dispc_cinfo = best_dispc; | |
656 | ||
657 | return 0; | |
658 | } | |
659 | ||
559d6701 TV |
660 | void dss_set_venc_output(enum omap_dss_venc_type type) |
661 | { | |
662 | int l = 0; | |
663 | ||
664 | if (type == OMAP_DSS_VENC_TYPE_COMPOSITE) | |
665 | l = 0; | |
666 | else if (type == OMAP_DSS_VENC_TYPE_SVIDEO) | |
667 | l = 1; | |
668 | else | |
669 | BUG(); | |
670 | ||
671 | /* venc out selection. 0 = comp, 1 = svideo */ | |
672 | REG_FLD_MOD(DSS_CONTROL, l, 6, 6); | |
673 | } | |
674 | ||
675 | void dss_set_dac_pwrdn_bgz(bool enable) | |
676 | { | |
677 | REG_FLD_MOD(DSS_CONTROL, enable, 5, 5); /* DAC Power-Down Control */ | |
678 | } | |
679 | ||
8aa2eed1 | 680 | void dss_select_hdmi_venc_clk_source(enum dss_hdmi_venc_clk_source_select src) |
7ed024aa | 681 | { |
8aa2eed1 RN |
682 | enum omap_display_type dp; |
683 | dp = dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_DIGIT); | |
684 | ||
685 | /* Complain about invalid selections */ | |
686 | WARN_ON((src == DSS_VENC_TV_CLK) && !(dp & OMAP_DISPLAY_TYPE_VENC)); | |
687 | WARN_ON((src == DSS_HDMI_M_PCLK) && !(dp & OMAP_DISPLAY_TYPE_HDMI)); | |
688 | ||
689 | /* Select only if we have options */ | |
690 | if ((dp & OMAP_DISPLAY_TYPE_VENC) && (dp & OMAP_DISPLAY_TYPE_HDMI)) | |
691 | REG_FLD_MOD(DSS_CONTROL, src, 15, 15); /* VENC_HDMI_SWITCH */ | |
7ed024aa M |
692 | } |
693 | ||
4a61e267 TV |
694 | enum dss_hdmi_venc_clk_source_select dss_get_hdmi_venc_clk_source(void) |
695 | { | |
696 | enum omap_display_type displays; | |
697 | ||
698 | displays = dss_feat_get_supported_displays(OMAP_DSS_CHANNEL_DIGIT); | |
699 | if ((displays & OMAP_DISPLAY_TYPE_HDMI) == 0) | |
700 | return DSS_VENC_TV_CLK; | |
701 | ||
8aa2eed1 RN |
702 | if ((displays & OMAP_DISPLAY_TYPE_VENC) == 0) |
703 | return DSS_HDMI_M_PCLK; | |
704 | ||
4a61e267 TV |
705 | return REG_GET(DSS_CONTROL, 15, 15); |
706 | } | |
707 | ||
de09e455 TV |
708 | static int dss_dpi_select_source_omap2_omap3(enum omap_channel channel) |
709 | { | |
710 | if (channel != OMAP_DSS_CHANNEL_LCD) | |
711 | return -EINVAL; | |
712 | ||
713 | return 0; | |
714 | } | |
715 | ||
716 | static int dss_dpi_select_source_omap4(enum omap_channel channel) | |
717 | { | |
718 | int val; | |
719 | ||
720 | switch (channel) { | |
721 | case OMAP_DSS_CHANNEL_LCD2: | |
722 | val = 0; | |
723 | break; | |
724 | case OMAP_DSS_CHANNEL_DIGIT: | |
725 | val = 1; | |
726 | break; | |
727 | default: | |
728 | return -EINVAL; | |
729 | } | |
730 | ||
731 | REG_FLD_MOD(DSS_CONTROL, val, 17, 17); | |
732 | ||
733 | return 0; | |
734 | } | |
735 | ||
736 | static int dss_dpi_select_source_omap5(enum omap_channel channel) | |
737 | { | |
738 | int val; | |
739 | ||
740 | switch (channel) { | |
741 | case OMAP_DSS_CHANNEL_LCD: | |
742 | val = 1; | |
743 | break; | |
744 | case OMAP_DSS_CHANNEL_LCD2: | |
745 | val = 2; | |
746 | break; | |
747 | case OMAP_DSS_CHANNEL_LCD3: | |
748 | val = 3; | |
749 | break; | |
750 | case OMAP_DSS_CHANNEL_DIGIT: | |
751 | val = 0; | |
752 | break; | |
753 | default: | |
754 | return -EINVAL; | |
755 | } | |
756 | ||
757 | REG_FLD_MOD(DSS_CONTROL, val, 17, 16); | |
758 | ||
759 | return 0; | |
760 | } | |
761 | ||
762 | int dss_dpi_select_source(enum omap_channel channel) | |
763 | { | |
764 | return dss.feat->dpi_select_source(channel); | |
765 | } | |
766 | ||
8b9cb3a8 SG |
767 | static int dss_get_clocks(void) |
768 | { | |
4fbafaf3 | 769 | struct clk *clk; |
8b9cb3a8 | 770 | int r; |
8b9cb3a8 | 771 | |
4fbafaf3 TV |
772 | clk = clk_get(&dss.pdev->dev, "fck"); |
773 | if (IS_ERR(clk)) { | |
774 | DSSERR("can't get clock fck\n"); | |
775 | r = PTR_ERR(clk); | |
8b9cb3a8 | 776 | goto err; |
a1a0dcca | 777 | } |
8b9cb3a8 | 778 | |
4fbafaf3 | 779 | dss.dss_clk = clk; |
8b9cb3a8 | 780 | |
8ad9375f AK |
781 | if (dss.feat->clk_name) { |
782 | clk = clk_get(NULL, dss.feat->clk_name); | |
783 | if (IS_ERR(clk)) { | |
784 | DSSERR("Failed to get %s\n", dss.feat->clk_name); | |
785 | r = PTR_ERR(clk); | |
786 | goto err; | |
787 | } | |
788 | } else { | |
789 | clk = NULL; | |
94c042ce TV |
790 | } |
791 | ||
4fbafaf3 | 792 | dss.dpll4_m4_ck = clk; |
94c042ce | 793 | |
8b9cb3a8 SG |
794 | return 0; |
795 | ||
796 | err: | |
4fbafaf3 TV |
797 | if (dss.dss_clk) |
798 | clk_put(dss.dss_clk); | |
94c042ce TV |
799 | if (dss.dpll4_m4_ck) |
800 | clk_put(dss.dpll4_m4_ck); | |
8b9cb3a8 SG |
801 | |
802 | return r; | |
803 | } | |
804 | ||
805 | static void dss_put_clocks(void) | |
806 | { | |
94c042ce TV |
807 | if (dss.dpll4_m4_ck) |
808 | clk_put(dss.dpll4_m4_ck); | |
4fbafaf3 | 809 | clk_put(dss.dss_clk); |
8b9cb3a8 SG |
810 | } |
811 | ||
852f0838 | 812 | static int dss_runtime_get(void) |
8b9cb3a8 | 813 | { |
4fbafaf3 | 814 | int r; |
8b9cb3a8 | 815 | |
4fbafaf3 | 816 | DSSDBG("dss_runtime_get\n"); |
8b9cb3a8 | 817 | |
4fbafaf3 TV |
818 | r = pm_runtime_get_sync(&dss.pdev->dev); |
819 | WARN_ON(r < 0); | |
820 | return r < 0 ? r : 0; | |
8b9cb3a8 SG |
821 | } |
822 | ||
852f0838 | 823 | static void dss_runtime_put(void) |
8b9cb3a8 | 824 | { |
4fbafaf3 | 825 | int r; |
8b9cb3a8 | 826 | |
4fbafaf3 | 827 | DSSDBG("dss_runtime_put\n"); |
8b9cb3a8 | 828 | |
0eaf9f52 | 829 | r = pm_runtime_put_sync(&dss.pdev->dev); |
5be3aebd | 830 | WARN_ON(r < 0 && r != -ENOSYS && r != -EBUSY); |
8b9cb3a8 SG |
831 | } |
832 | ||
8b9cb3a8 | 833 | /* DEBUGFS */ |
1b3bcb33 | 834 | #if defined(CONFIG_OMAP2_DSS_DEBUGFS) |
8b9cb3a8 SG |
835 | void dss_debug_dump_clocks(struct seq_file *s) |
836 | { | |
8b9cb3a8 SG |
837 | dss_dump_clocks(s); |
838 | dispc_dump_clocks(s); | |
839 | #ifdef CONFIG_OMAP2_DSS_DSI | |
840 | dsi_dump_clocks(s); | |
841 | #endif | |
842 | } | |
843 | #endif | |
844 | ||
84273a95 TV |
845 | static const struct dss_features omap24xx_dss_feats __initconst = { |
846 | .fck_div_max = 16, | |
847 | .dss_fck_multiplier = 2, | |
848 | .clk_name = NULL, | |
de09e455 | 849 | .dpi_select_source = &dss_dpi_select_source_omap2_omap3, |
84273a95 TV |
850 | }; |
851 | ||
852 | static const struct dss_features omap34xx_dss_feats __initconst = { | |
853 | .fck_div_max = 16, | |
854 | .dss_fck_multiplier = 2, | |
855 | .clk_name = "dpll4_m4_ck", | |
de09e455 | 856 | .dpi_select_source = &dss_dpi_select_source_omap2_omap3, |
84273a95 TV |
857 | }; |
858 | ||
859 | static const struct dss_features omap3630_dss_feats __initconst = { | |
860 | .fck_div_max = 32, | |
861 | .dss_fck_multiplier = 1, | |
862 | .clk_name = "dpll4_m4_ck", | |
de09e455 | 863 | .dpi_select_source = &dss_dpi_select_source_omap2_omap3, |
84273a95 TV |
864 | }; |
865 | ||
866 | static const struct dss_features omap44xx_dss_feats __initconst = { | |
867 | .fck_div_max = 32, | |
868 | .dss_fck_multiplier = 1, | |
869 | .clk_name = "dpll_per_m5x2_ck", | |
de09e455 | 870 | .dpi_select_source = &dss_dpi_select_source_omap4, |
84273a95 TV |
871 | }; |
872 | ||
873 | static const struct dss_features omap54xx_dss_feats __initconst = { | |
874 | .fck_div_max = 64, | |
875 | .dss_fck_multiplier = 1, | |
876 | .clk_name = "dpll_per_h12x2_ck", | |
de09e455 | 877 | .dpi_select_source = &dss_dpi_select_source_omap5, |
84273a95 TV |
878 | }; |
879 | ||
bd81ed08 | 880 | static int __init dss_init_features(struct platform_device *pdev) |
185bae10 CM |
881 | { |
882 | const struct dss_features *src; | |
883 | struct dss_features *dst; | |
884 | ||
bd81ed08 | 885 | dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL); |
185bae10 | 886 | if (!dst) { |
bd81ed08 | 887 | dev_err(&pdev->dev, "Failed to allocate local DSS Features\n"); |
185bae10 CM |
888 | return -ENOMEM; |
889 | } | |
890 | ||
b2c7d54f | 891 | switch (omapdss_get_version()) { |
bd81ed08 | 892 | case OMAPDSS_VER_OMAP24xx: |
185bae10 | 893 | src = &omap24xx_dss_feats; |
bd81ed08 TV |
894 | break; |
895 | ||
896 | case OMAPDSS_VER_OMAP34xx_ES1: | |
897 | case OMAPDSS_VER_OMAP34xx_ES3: | |
898 | case OMAPDSS_VER_AM35xx: | |
185bae10 | 899 | src = &omap34xx_dss_feats; |
bd81ed08 TV |
900 | break; |
901 | ||
902 | case OMAPDSS_VER_OMAP3630: | |
185bae10 | 903 | src = &omap3630_dss_feats; |
bd81ed08 TV |
904 | break; |
905 | ||
906 | case OMAPDSS_VER_OMAP4430_ES1: | |
907 | case OMAPDSS_VER_OMAP4430_ES2: | |
908 | case OMAPDSS_VER_OMAP4: | |
185bae10 | 909 | src = &omap44xx_dss_feats; |
bd81ed08 TV |
910 | break; |
911 | ||
912 | case OMAPDSS_VER_OMAP5: | |
23362832 | 913 | src = &omap54xx_dss_feats; |
bd81ed08 TV |
914 | break; |
915 | ||
916 | default: | |
185bae10 | 917 | return -ENODEV; |
bd81ed08 | 918 | } |
185bae10 CM |
919 | |
920 | memcpy(dst, src, sizeof(*dst)); | |
921 | dss.feat = dst; | |
922 | ||
923 | return 0; | |
924 | } | |
925 | ||
96c401bc | 926 | /* DSS HW IP initialisation */ |
6e7e8f06 | 927 | static int __init omap_dsshw_probe(struct platform_device *pdev) |
96c401bc | 928 | { |
b98482ed TV |
929 | struct resource *dss_mem; |
930 | u32 rev; | |
96c401bc | 931 | int r; |
96c401bc SG |
932 | |
933 | dss.pdev = pdev; | |
934 | ||
bd81ed08 | 935 | r = dss_init_features(dss.pdev); |
185bae10 CM |
936 | if (r) |
937 | return r; | |
938 | ||
b98482ed TV |
939 | dss_mem = platform_get_resource(dss.pdev, IORESOURCE_MEM, 0); |
940 | if (!dss_mem) { | |
941 | DSSERR("can't get IORESOURCE_MEM DSS\n"); | |
cd3b3449 | 942 | return -EINVAL; |
b98482ed | 943 | } |
cd3b3449 | 944 | |
6e2a14d2 JL |
945 | dss.base = devm_ioremap(&pdev->dev, dss_mem->start, |
946 | resource_size(dss_mem)); | |
b98482ed TV |
947 | if (!dss.base) { |
948 | DSSERR("can't ioremap DSS\n"); | |
cd3b3449 | 949 | return -ENOMEM; |
b98482ed TV |
950 | } |
951 | ||
8b9cb3a8 SG |
952 | r = dss_get_clocks(); |
953 | if (r) | |
cd3b3449 | 954 | return r; |
8b9cb3a8 | 955 | |
13a1a2b2 TV |
956 | r = dss_setup_default_clock(); |
957 | if (r) | |
958 | goto err_setup_clocks; | |
959 | ||
4fbafaf3 | 960 | pm_runtime_enable(&pdev->dev); |
b98482ed | 961 | |
4fbafaf3 TV |
962 | r = dss_runtime_get(); |
963 | if (r) | |
964 | goto err_runtime_get; | |
b98482ed | 965 | |
5aaee69d TV |
966 | dss.dss_clk_rate = clk_get_rate(dss.dss_clk); |
967 | ||
b98482ed TV |
968 | /* Select DPLL */ |
969 | REG_FLD_MOD(DSS_CONTROL, 0, 0, 0); | |
970 | ||
a5b8399f TV |
971 | dss_select_dispc_clk_source(OMAP_DSS_CLK_SRC_FCK); |
972 | ||
b98482ed TV |
973 | #ifdef CONFIG_OMAP2_DSS_VENC |
974 | REG_FLD_MOD(DSS_CONTROL, 1, 4, 4); /* venc dac demen */ | |
975 | REG_FLD_MOD(DSS_CONTROL, 1, 3, 3); /* venc clock 4x enable */ | |
976 | REG_FLD_MOD(DSS_CONTROL, 0, 2, 2); /* venc clock mode = normal */ | |
977 | #endif | |
978 | dss.dsi_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; | |
979 | dss.dsi_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; | |
980 | dss.dispc_clk_source = OMAP_DSS_CLK_SRC_FCK; | |
981 | dss.lcd_clk_source[0] = OMAP_DSS_CLK_SRC_FCK; | |
982 | dss.lcd_clk_source[1] = OMAP_DSS_CLK_SRC_FCK; | |
96c401bc | 983 | |
b98482ed TV |
984 | rev = dss_read_reg(DSS_REVISION); |
985 | printk(KERN_INFO "OMAP DSS rev %d.%d\n", | |
986 | FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); | |
987 | ||
4fbafaf3 | 988 | dss_runtime_put(); |
b98482ed | 989 | |
e40402cf TV |
990 | dss_debugfs_create_file("dss", dss_dump_regs); |
991 | ||
8b9cb3a8 | 992 | return 0; |
a57dd4fe | 993 | |
4fbafaf3 TV |
994 | err_runtime_get: |
995 | pm_runtime_disable(&pdev->dev); | |
13a1a2b2 | 996 | err_setup_clocks: |
8b9cb3a8 | 997 | dss_put_clocks(); |
96c401bc SG |
998 | return r; |
999 | } | |
1000 | ||
6e7e8f06 | 1001 | static int __exit omap_dsshw_remove(struct platform_device *pdev) |
96c401bc | 1002 | { |
4fbafaf3 | 1003 | pm_runtime_disable(&pdev->dev); |
8b9cb3a8 SG |
1004 | |
1005 | dss_put_clocks(); | |
b98482ed | 1006 | |
96c401bc SG |
1007 | return 0; |
1008 | } | |
1009 | ||
4fbafaf3 TV |
1010 | static int dss_runtime_suspend(struct device *dev) |
1011 | { | |
1012 | dss_save_context(); | |
a8081d31 | 1013 | dss_set_min_bus_tput(dev, 0); |
4fbafaf3 TV |
1014 | return 0; |
1015 | } | |
1016 | ||
1017 | static int dss_runtime_resume(struct device *dev) | |
1018 | { | |
a8081d31 TV |
1019 | int r; |
1020 | /* | |
1021 | * Set an arbitrarily high tput request to ensure OPP100. | |
1022 | * What we should really do is to make a request to stay in OPP100, | |
1023 | * without any tput requirements, but that is not currently possible | |
1024 | * via the PM layer. | |
1025 | */ | |
1026 | ||
1027 | r = dss_set_min_bus_tput(dev, 1000000000); | |
1028 | if (r) | |
1029 | return r; | |
1030 | ||
39020710 | 1031 | dss_restore_context(); |
4fbafaf3 TV |
1032 | return 0; |
1033 | } | |
1034 | ||
1035 | static const struct dev_pm_ops dss_pm_ops = { | |
1036 | .runtime_suspend = dss_runtime_suspend, | |
1037 | .runtime_resume = dss_runtime_resume, | |
1038 | }; | |
1039 | ||
96c401bc | 1040 | static struct platform_driver omap_dsshw_driver = { |
6e7e8f06 | 1041 | .remove = __exit_p(omap_dsshw_remove), |
96c401bc SG |
1042 | .driver = { |
1043 | .name = "omapdss_dss", | |
1044 | .owner = THIS_MODULE, | |
4fbafaf3 | 1045 | .pm = &dss_pm_ops, |
96c401bc SG |
1046 | }, |
1047 | }; | |
1048 | ||
6e7e8f06 | 1049 | int __init dss_init_platform_driver(void) |
96c401bc | 1050 | { |
11436e1d | 1051 | return platform_driver_probe(&omap_dsshw_driver, omap_dsshw_probe); |
96c401bc SG |
1052 | } |
1053 | ||
1054 | void dss_uninit_platform_driver(void) | |
1055 | { | |
04c742c3 | 1056 | platform_driver_unregister(&omap_dsshw_driver); |
96c401bc | 1057 | } |