Commit | Line | Data |
---|---|---|
23c0a7a6 TV |
1 | /* |
2 | * linux/drivers/video/omap2/dss/sdi.c | |
3 | * | |
4 | * Copyright (C) 2009 Nokia Corporation | |
5 | * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 as published by | |
9 | * the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along with | |
17 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #define DSS_SUBSYS_NAME "SDI" | |
21 | ||
22 | #include <linux/kernel.h> | |
23c0a7a6 TV |
23 | #include <linux/delay.h> |
24 | #include <linux/err.h> | |
508886cf | 25 | #include <linux/regulator/consumer.h> |
a8a35931 | 26 | #include <linux/export.h> |
a57dd4fe | 27 | #include <linux/platform_device.h> |
13b1ba7d | 28 | #include <linux/string.h> |
2ecef246 | 29 | #include <linux/of.h> |
736e60dd | 30 | #include <linux/component.h> |
23c0a7a6 | 31 | |
32043da7 | 32 | #include "omapdss.h" |
23c0a7a6 TV |
33 | #include "dss.h" |
34 | ||
35 | static struct { | |
46c4b645 TV |
36 | struct platform_device *pdev; |
37 | ||
23c0a7a6 | 38 | bool update_enabled; |
508886cf | 39 | struct regulator *vdds_sdi_reg; |
23c0a7a6 | 40 | |
37a57990 | 41 | struct dss_lcd_mgr_config mgr_config; |
9b4a5716 | 42 | struct omap_video_timings timings; |
889b4fd7 | 43 | int datapairs; |
81b87f51 | 44 | |
1f68d9c4 | 45 | struct omap_dss_device output; |
2ecef246 TV |
46 | |
47 | bool port_initialized; | |
37a57990 | 48 | } sdi; |
64ba4f74 | 49 | |
36816faa TV |
50 | struct sdi_clk_calc_ctx { |
51 | unsigned long pck_min, pck_max; | |
52 | ||
c56812fc | 53 | unsigned long fck; |
36816faa TV |
54 | struct dispc_clock_info dispc_cinfo; |
55 | }; | |
56 | ||
57 | static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, | |
58 | unsigned long pck, void *data) | |
59 | { | |
60 | struct sdi_clk_calc_ctx *ctx = data; | |
61 | ||
62 | ctx->dispc_cinfo.lck_div = lckd; | |
63 | ctx->dispc_cinfo.pck_div = pckd; | |
64 | ctx->dispc_cinfo.lck = lck; | |
65 | ctx->dispc_cinfo.pck = pck; | |
66 | ||
67 | return true; | |
68 | } | |
69 | ||
d0f58bd3 | 70 | static bool dpi_calc_dss_cb(unsigned long fck, void *data) |
36816faa TV |
71 | { |
72 | struct sdi_clk_calc_ctx *ctx = data; | |
73 | ||
d0f58bd3 | 74 | ctx->fck = fck; |
36816faa TV |
75 | |
76 | return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max, | |
77 | dpi_calc_dispc_cb, ctx); | |
78 | } | |
79 | ||
80 | static int sdi_calc_clock_div(unsigned long pclk, | |
d0f58bd3 | 81 | unsigned long *fck, |
36816faa TV |
82 | struct dispc_clock_info *dispc_cinfo) |
83 | { | |
84 | int i; | |
85 | struct sdi_clk_calc_ctx ctx; | |
86 | ||
87 | /* | |
88 | * DSS fclk gives us very few possibilities, so finding a good pixel | |
89 | * clock may not be possible. We try multiple times to find the clock, | |
90 | * each time widening the pixel clock range we look for, up to | |
91 | * +/- 1MHz. | |
92 | */ | |
93 | ||
94 | for (i = 0; i < 10; ++i) { | |
95 | bool ok; | |
96 | ||
97 | memset(&ctx, 0, sizeof(ctx)); | |
98 | if (pclk > 1000 * i * i * i) | |
99 | ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu); | |
100 | else | |
101 | ctx.pck_min = 0; | |
102 | ctx.pck_max = pclk + 1000 * i * i * i; | |
103 | ||
688af02d | 104 | ok = dss_div_calc(pclk, ctx.pck_min, dpi_calc_dss_cb, &ctx); |
36816faa | 105 | if (ok) { |
d0f58bd3 | 106 | *fck = ctx.fck; |
36816faa TV |
107 | *dispc_cinfo = ctx.dispc_cinfo; |
108 | return 0; | |
109 | } | |
110 | } | |
111 | ||
112 | return -EINVAL; | |
113 | } | |
114 | ||
37a57990 | 115 | static void sdi_config_lcd_manager(struct omap_dss_device *dssdev) |
23c0a7a6 | 116 | { |
c64b79c8 | 117 | enum omap_channel channel = dssdev->dispc_channel; |
7d6069e5 | 118 | |
37a57990 | 119 | sdi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; |
64ba4f74 | 120 | |
37a57990 AT |
121 | sdi.mgr_config.stallmode = false; |
122 | sdi.mgr_config.fifohandcheck = false; | |
123 | ||
124 | sdi.mgr_config.video_port_width = 24; | |
125 | sdi.mgr_config.lcden_sig_polarity = 1; | |
126 | ||
c64b79c8 | 127 | dss_mgr_set_lcd_config(channel, &sdi.mgr_config); |
23c0a7a6 TV |
128 | } |
129 | ||
cd6e915b | 130 | static int sdi_display_enable(struct omap_dss_device *dssdev) |
23c0a7a6 | 131 | { |
1f68d9c4 | 132 | struct omap_dss_device *out = &sdi.output; |
c64b79c8 | 133 | enum omap_channel channel = dssdev->dispc_channel; |
9b4a5716 | 134 | struct omap_video_timings *t = &sdi.timings; |
d0f58bd3 | 135 | unsigned long fck; |
23c0a7a6 | 136 | struct dispc_clock_info dispc_cinfo; |
23c0a7a6 TV |
137 | unsigned long pck; |
138 | int r; | |
139 | ||
f1504ad0 | 140 | if (!out->dispc_channel_connected) { |
7d6069e5 | 141 | DSSERR("failed to enable display: no output/manager\n"); |
05e1d606 TV |
142 | return -ENODEV; |
143 | } | |
144 | ||
508886cf RQ |
145 | r = regulator_enable(sdi.vdds_sdi_reg); |
146 | if (r) | |
4fbafaf3 | 147 | goto err_reg_enable; |
508886cf | 148 | |
4fbafaf3 TV |
149 | r = dispc_runtime_get(); |
150 | if (r) | |
151 | goto err_get_dispc; | |
23c0a7a6 | 152 | |
23c0a7a6 | 153 | /* 15.5.9.1.2 */ |
9b4a5716 AT |
154 | t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; |
155 | t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; | |
a8d5e41c | 156 | |
d8d78941 | 157 | r = sdi_calc_clock_div(t->pixelclock, &fck, &dispc_cinfo); |
23c0a7a6 | 158 | if (r) |
4fbafaf3 | 159 | goto err_calc_clock_div; |
23c0a7a6 | 160 | |
37a57990 | 161 | sdi.mgr_config.clock_info = dispc_cinfo; |
23c0a7a6 | 162 | |
d8d78941 | 163 | pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div; |
23c0a7a6 | 164 | |
d8d78941 TV |
165 | if (pck != t->pixelclock) { |
166 | DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n", | |
167 | t->pixelclock, pck); | |
23c0a7a6 | 168 | |
d8d78941 | 169 | t->pixelclock = pck; |
23c0a7a6 TV |
170 | } |
171 | ||
172 | ||
c64b79c8 | 173 | dss_mgr_set_timings(channel, t); |
23c0a7a6 | 174 | |
d0f58bd3 | 175 | r = dss_set_fck_rate(fck); |
23c0a7a6 | 176 | if (r) |
4fbafaf3 | 177 | goto err_set_dss_clock_div; |
23c0a7a6 | 178 | |
37a57990 | 179 | sdi_config_lcd_manager(dssdev); |
23c0a7a6 | 180 | |
35d67866 TV |
181 | /* |
182 | * LCLK and PCLK divisors are located in shadow registers, and we | |
183 | * normally write them to DISPC registers when enabling the output. | |
184 | * However, SDI uses pck-free as source clock for its PLL, and pck-free | |
185 | * is affected by the divisors. And as we need the PLL before enabling | |
186 | * the output, we need to write the divisors early. | |
187 | * | |
188 | * It seems just writing to the DISPC register is enough, and we don't | |
189 | * need to care about the shadow register mechanism for pck-free. The | |
190 | * exact reason for this is unknown. | |
191 | */ | |
c64b79c8 | 192 | dispc_mgr_set_clock_div(channel, &sdi.mgr_config.clock_info); |
889b4fd7 | 193 | |
66591457 | 194 | dss_sdi_init(sdi.datapairs); |
42c9dee8 TV |
195 | r = dss_sdi_enable(); |
196 | if (r) | |
4fbafaf3 | 197 | goto err_sdi_enable; |
42c9dee8 | 198 | mdelay(2); |
23c0a7a6 | 199 | |
c64b79c8 | 200 | r = dss_mgr_enable(channel); |
33ca237f TV |
201 | if (r) |
202 | goto err_mgr_enable; | |
23c0a7a6 | 203 | |
23c0a7a6 | 204 | return 0; |
4fbafaf3 | 205 | |
33ca237f TV |
206 | err_mgr_enable: |
207 | dss_sdi_disable(); | |
4fbafaf3 | 208 | err_sdi_enable: |
4fbafaf3 TV |
209 | err_set_dss_clock_div: |
210 | err_calc_clock_div: | |
211 | dispc_runtime_put(); | |
212 | err_get_dispc: | |
508886cf | 213 | regulator_disable(sdi.vdds_sdi_reg); |
4fbafaf3 | 214 | err_reg_enable: |
23c0a7a6 TV |
215 | return r; |
216 | } | |
217 | ||
cd6e915b | 218 | static void sdi_display_disable(struct omap_dss_device *dssdev) |
23c0a7a6 | 219 | { |
c64b79c8 | 220 | enum omap_channel channel = dssdev->dispc_channel; |
7d6069e5 | 221 | |
c64b79c8 | 222 | dss_mgr_disable(channel); |
23c0a7a6 TV |
223 | |
224 | dss_sdi_disable(); | |
225 | ||
4fbafaf3 | 226 | dispc_runtime_put(); |
23c0a7a6 | 227 | |
508886cf | 228 | regulator_disable(sdi.vdds_sdi_reg); |
23c0a7a6 | 229 | } |
23c0a7a6 | 230 | |
cd6e915b | 231 | static void sdi_set_timings(struct omap_dss_device *dssdev, |
c7833f7b AT |
232 | struct omap_video_timings *timings) |
233 | { | |
9b4a5716 | 234 | sdi.timings = *timings; |
c7833f7b | 235 | } |
c7833f7b | 236 | |
b1082dfd TV |
237 | static void sdi_get_timings(struct omap_dss_device *dssdev, |
238 | struct omap_video_timings *timings) | |
239 | { | |
240 | *timings = sdi.timings; | |
241 | } | |
242 | ||
243 | static int sdi_check_timings(struct omap_dss_device *dssdev, | |
244 | struct omap_video_timings *timings) | |
245 | { | |
c64b79c8 | 246 | enum omap_channel channel = dssdev->dispc_channel; |
b1082dfd | 247 | |
c64b79c8 | 248 | if (!dispc_mgr_timings_ok(channel, timings)) |
b1082dfd TV |
249 | return -EINVAL; |
250 | ||
d8d78941 | 251 | if (timings->pixelclock == 0) |
b1082dfd TV |
252 | return -EINVAL; |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
cd6e915b | 257 | static void sdi_set_datapairs(struct omap_dss_device *dssdev, int datapairs) |
889b4fd7 AT |
258 | { |
259 | sdi.datapairs = datapairs; | |
260 | } | |
889b4fd7 | 261 | |
d37801b3 | 262 | static int sdi_init_regulator(void) |
23c0a7a6 | 263 | { |
d37801b3 | 264 | struct regulator *vdds_sdi; |
23c0a7a6 | 265 | |
d37801b3 TV |
266 | if (sdi.vdds_sdi_reg) |
267 | return 0; | |
5f42f2ce | 268 | |
349c3d95 | 269 | vdds_sdi = devm_regulator_get(&sdi.pdev->dev, "vdds_sdi"); |
d37801b3 | 270 | if (IS_ERR(vdds_sdi)) { |
40359a9b TV |
271 | if (PTR_ERR(vdds_sdi) != -EPROBE_DEFER) |
272 | DSSERR("can't get VDDS_SDI regulator\n"); | |
349c3d95 | 273 | return PTR_ERR(vdds_sdi); |
5f42f2ce TV |
274 | } |
275 | ||
d37801b3 TV |
276 | sdi.vdds_sdi_reg = vdds_sdi; |
277 | ||
23c0a7a6 TV |
278 | return 0; |
279 | } | |
280 | ||
b1082dfd TV |
281 | static int sdi_connect(struct omap_dss_device *dssdev, |
282 | struct omap_dss_device *dst) | |
283 | { | |
c64b79c8 | 284 | enum omap_channel channel = dssdev->dispc_channel; |
b1082dfd TV |
285 | int r; |
286 | ||
287 | r = sdi_init_regulator(); | |
288 | if (r) | |
289 | return r; | |
290 | ||
c64b79c8 | 291 | r = dss_mgr_connect(channel, dssdev); |
b1082dfd TV |
292 | if (r) |
293 | return r; | |
294 | ||
295 | r = omapdss_output_set_device(dssdev, dst); | |
296 | if (r) { | |
297 | DSSERR("failed to connect output to new device: %s\n", | |
298 | dst->name); | |
c64b79c8 | 299 | dss_mgr_disconnect(channel, dssdev); |
b1082dfd TV |
300 | return r; |
301 | } | |
302 | ||
303 | return 0; | |
304 | } | |
305 | ||
306 | static void sdi_disconnect(struct omap_dss_device *dssdev, | |
307 | struct omap_dss_device *dst) | |
308 | { | |
c64b79c8 TV |
309 | enum omap_channel channel = dssdev->dispc_channel; |
310 | ||
9560dc10 | 311 | WARN_ON(dst != dssdev->dst); |
b1082dfd | 312 | |
9560dc10 | 313 | if (dst != dssdev->dst) |
b1082dfd TV |
314 | return; |
315 | ||
316 | omapdss_output_unset_device(dssdev); | |
317 | ||
c64b79c8 | 318 | dss_mgr_disconnect(channel, dssdev); |
b1082dfd TV |
319 | } |
320 | ||
321 | static const struct omapdss_sdi_ops sdi_ops = { | |
322 | .connect = sdi_connect, | |
323 | .disconnect = sdi_disconnect, | |
324 | ||
cd6e915b TV |
325 | .enable = sdi_display_enable, |
326 | .disable = sdi_display_disable, | |
b1082dfd TV |
327 | |
328 | .check_timings = sdi_check_timings, | |
cd6e915b | 329 | .set_timings = sdi_set_timings, |
b1082dfd TV |
330 | .get_timings = sdi_get_timings, |
331 | ||
cd6e915b | 332 | .set_datapairs = sdi_set_datapairs, |
b1082dfd TV |
333 | }; |
334 | ||
d23b3357 | 335 | static void sdi_init_output(struct platform_device *pdev) |
81b87f51 | 336 | { |
1f68d9c4 | 337 | struct omap_dss_device *out = &sdi.output; |
81b87f51 | 338 | |
1f68d9c4 | 339 | out->dev = &pdev->dev; |
81b87f51 | 340 | out->id = OMAP_DSS_OUTPUT_SDI; |
1f68d9c4 | 341 | out->output_type = OMAP_DISPLAY_TYPE_SDI; |
7286a08f | 342 | out->name = "sdi.0"; |
2eea5ae6 | 343 | out->dispc_channel = OMAP_DSS_CHANNEL_LCD; |
a32442d4 TV |
344 | /* We have SDI only on OMAP3, where it's on port 1 */ |
345 | out->port_num = 1; | |
b1082dfd | 346 | out->ops.sdi = &sdi_ops; |
b7328e14 | 347 | out->owner = THIS_MODULE; |
81b87f51 | 348 | |
5d47dbc8 | 349 | omapdss_register_output(out); |
81b87f51 AT |
350 | } |
351 | ||
ede92695 | 352 | static void sdi_uninit_output(struct platform_device *pdev) |
81b87f51 | 353 | { |
1f68d9c4 | 354 | struct omap_dss_device *out = &sdi.output; |
81b87f51 | 355 | |
5d47dbc8 | 356 | omapdss_unregister_output(out); |
81b87f51 AT |
357 | } |
358 | ||
736e60dd | 359 | static int sdi_bind(struct device *dev, struct device *master, void *data) |
38f3daf6 | 360 | { |
736e60dd TV |
361 | struct platform_device *pdev = to_platform_device(dev); |
362 | ||
46c4b645 TV |
363 | sdi.pdev = pdev; |
364 | ||
81b87f51 AT |
365 | sdi_init_output(pdev); |
366 | ||
23c0a7a6 TV |
367 | return 0; |
368 | } | |
369 | ||
736e60dd | 370 | static void sdi_unbind(struct device *dev, struct device *master, void *data) |
23c0a7a6 | 371 | { |
736e60dd TV |
372 | struct platform_device *pdev = to_platform_device(dev); |
373 | ||
81b87f51 | 374 | sdi_uninit_output(pdev); |
736e60dd TV |
375 | } |
376 | ||
377 | static const struct component_ops sdi_component_ops = { | |
378 | .bind = sdi_bind, | |
379 | .unbind = sdi_unbind, | |
380 | }; | |
81b87f51 | 381 | |
736e60dd TV |
382 | static int sdi_probe(struct platform_device *pdev) |
383 | { | |
384 | return component_add(&pdev->dev, &sdi_component_ops); | |
385 | } | |
386 | ||
387 | static int sdi_remove(struct platform_device *pdev) | |
388 | { | |
389 | component_del(&pdev->dev, &sdi_component_ops); | |
a57dd4fe TV |
390 | return 0; |
391 | } | |
392 | ||
393 | static struct platform_driver omap_sdi_driver = { | |
736e60dd TV |
394 | .probe = sdi_probe, |
395 | .remove = sdi_remove, | |
a57dd4fe TV |
396 | .driver = { |
397 | .name = "omapdss_sdi", | |
422ccbd5 | 398 | .suppress_bind_attrs = true, |
a57dd4fe TV |
399 | }, |
400 | }; | |
401 | ||
6e7e8f06 | 402 | int __init sdi_init_platform_driver(void) |
a57dd4fe | 403 | { |
d23b3357 | 404 | return platform_driver_register(&omap_sdi_driver); |
a57dd4fe TV |
405 | } |
406 | ||
ede92695 | 407 | void sdi_uninit_platform_driver(void) |
a57dd4fe TV |
408 | { |
409 | platform_driver_unregister(&omap_sdi_driver); | |
23c0a7a6 | 410 | } |
2ecef246 | 411 | |
ede92695 | 412 | int sdi_init_port(struct platform_device *pdev, struct device_node *port) |
2ecef246 TV |
413 | { |
414 | struct device_node *ep; | |
415 | u32 datapairs; | |
416 | int r; | |
417 | ||
418 | ep = omapdss_of_get_next_endpoint(port, NULL); | |
419 | if (!ep) | |
420 | return 0; | |
421 | ||
422 | r = of_property_read_u32(ep, "datapairs", &datapairs); | |
423 | if (r) { | |
424 | DSSERR("failed to parse datapairs\n"); | |
425 | goto err_datapairs; | |
426 | } | |
427 | ||
428 | sdi.datapairs = datapairs; | |
429 | ||
430 | of_node_put(ep); | |
431 | ||
432 | sdi.pdev = pdev; | |
433 | ||
434 | sdi_init_output(pdev); | |
435 | ||
436 | sdi.port_initialized = true; | |
437 | ||
438 | return 0; | |
439 | ||
440 | err_datapairs: | |
441 | of_node_put(ep); | |
442 | ||
443 | return r; | |
444 | } | |
445 | ||
ede92695 | 446 | void sdi_uninit_port(struct device_node *port) |
2ecef246 TV |
447 | { |
448 | if (!sdi.port_initialized) | |
449 | return; | |
450 | ||
451 | sdi_uninit_output(sdi.pdev); | |
452 | } |