]>
Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
23c0a7a6 | 2 | /* |
23c0a7a6 | 3 | * Copyright (C) 2009 Nokia Corporation |
6505d75c | 4 | * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> |
23c0a7a6 TV |
5 | */ |
6 | ||
7 | #define DSS_SUBSYS_NAME "SDI" | |
8 | ||
9 | #include <linux/kernel.h> | |
23c0a7a6 TV |
10 | #include <linux/delay.h> |
11 | #include <linux/err.h> | |
508886cf | 12 | #include <linux/regulator/consumer.h> |
a8a35931 | 13 | #include <linux/export.h> |
a57dd4fe | 14 | #include <linux/platform_device.h> |
13b1ba7d | 15 | #include <linux/string.h> |
2ecef246 | 16 | #include <linux/of.h> |
23c0a7a6 | 17 | |
32043da7 | 18 | #include "omapdss.h" |
23c0a7a6 TV |
19 | #include "dss.h" |
20 | ||
24aac601 | 21 | struct sdi_device { |
46c4b645 | 22 | struct platform_device *pdev; |
d7157dfe | 23 | struct dss_device *dss; |
46c4b645 | 24 | |
23c0a7a6 | 25 | bool update_enabled; |
508886cf | 26 | struct regulator *vdds_sdi_reg; |
23c0a7a6 | 27 | |
37a57990 | 28 | struct dss_lcd_mgr_config mgr_config; |
e5906f76 | 29 | unsigned long pixelclock; |
889b4fd7 | 30 | int datapairs; |
81b87f51 | 31 | |
1f68d9c4 | 32 | struct omap_dss_device output; |
24aac601 | 33 | }; |
2ecef246 | 34 | |
24aac601 | 35 | #define dssdev_to_sdi(dssdev) container_of(dssdev, struct sdi_device, output) |
64ba4f74 | 36 | |
36816faa | 37 | struct sdi_clk_calc_ctx { |
24aac601 | 38 | struct sdi_device *sdi; |
36816faa TV |
39 | unsigned long pck_min, pck_max; |
40 | ||
c56812fc | 41 | unsigned long fck; |
36816faa TV |
42 | struct dispc_clock_info dispc_cinfo; |
43 | }; | |
44 | ||
45 | static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, | |
46 | unsigned long pck, void *data) | |
47 | { | |
48 | struct sdi_clk_calc_ctx *ctx = data; | |
49 | ||
50 | ctx->dispc_cinfo.lck_div = lckd; | |
51 | ctx->dispc_cinfo.pck_div = pckd; | |
52 | ctx->dispc_cinfo.lck = lck; | |
53 | ctx->dispc_cinfo.pck = pck; | |
54 | ||
55 | return true; | |
56 | } | |
57 | ||
d0f58bd3 | 58 | static bool dpi_calc_dss_cb(unsigned long fck, void *data) |
36816faa TV |
59 | { |
60 | struct sdi_clk_calc_ctx *ctx = data; | |
61 | ||
d0f58bd3 | 62 | ctx->fck = fck; |
36816faa | 63 | |
24aac601 | 64 | return dispc_div_calc(ctx->sdi->dss->dispc, fck, |
8a7eda76 LP |
65 | ctx->pck_min, ctx->pck_max, |
66 | dpi_calc_dispc_cb, ctx); | |
36816faa TV |
67 | } |
68 | ||
24aac601 LP |
69 | static int sdi_calc_clock_div(struct sdi_device *sdi, unsigned long pclk, |
70 | unsigned long *fck, | |
71 | struct dispc_clock_info *dispc_cinfo) | |
36816faa TV |
72 | { |
73 | int i; | |
2bc5ff0b | 74 | struct sdi_clk_calc_ctx ctx; |
36816faa TV |
75 | |
76 | /* | |
77 | * DSS fclk gives us very few possibilities, so finding a good pixel | |
78 | * clock may not be possible. We try multiple times to find the clock, | |
79 | * each time widening the pixel clock range we look for, up to | |
80 | * +/- 1MHz. | |
81 | */ | |
82 | ||
83 | for (i = 0; i < 10; ++i) { | |
84 | bool ok; | |
85 | ||
86 | memset(&ctx, 0, sizeof(ctx)); | |
2bc5ff0b TV |
87 | |
88 | ctx.sdi = sdi; | |
89 | ||
36816faa TV |
90 | if (pclk > 1000 * i * i * i) |
91 | ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu); | |
92 | else | |
93 | ctx.pck_min = 0; | |
94 | ctx.pck_max = pclk + 1000 * i * i * i; | |
95 | ||
24aac601 | 96 | ok = dss_div_calc(sdi->dss, pclk, ctx.pck_min, |
60f9c59f | 97 | dpi_calc_dss_cb, &ctx); |
36816faa | 98 | if (ok) { |
d0f58bd3 | 99 | *fck = ctx.fck; |
36816faa TV |
100 | *dispc_cinfo = ctx.dispc_cinfo; |
101 | return 0; | |
102 | } | |
103 | } | |
104 | ||
105 | return -EINVAL; | |
106 | } | |
107 | ||
24aac601 | 108 | static void sdi_config_lcd_manager(struct sdi_device *sdi) |
23c0a7a6 | 109 | { |
24aac601 | 110 | sdi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; |
64ba4f74 | 111 | |
24aac601 LP |
112 | sdi->mgr_config.stallmode = false; |
113 | sdi->mgr_config.fifohandcheck = false; | |
37a57990 | 114 | |
24aac601 LP |
115 | sdi->mgr_config.video_port_width = 24; |
116 | sdi->mgr_config.lcden_sig_polarity = 1; | |
37a57990 | 117 | |
24aac601 | 118 | dss_mgr_set_lcd_config(&sdi->output, &sdi->mgr_config); |
23c0a7a6 TV |
119 | } |
120 | ||
19b4200d | 121 | static void sdi_display_enable(struct omap_dss_device *dssdev) |
23c0a7a6 | 122 | { |
24aac601 | 123 | struct sdi_device *sdi = dssdev_to_sdi(dssdev); |
23c0a7a6 | 124 | struct dispc_clock_info dispc_cinfo; |
96fc64c7 | 125 | unsigned long fck; |
23c0a7a6 TV |
126 | int r; |
127 | ||
24aac601 | 128 | r = regulator_enable(sdi->vdds_sdi_reg); |
508886cf | 129 | if (r) |
19b4200d | 130 | return; |
508886cf | 131 | |
24aac601 | 132 | r = dispc_runtime_get(sdi->dss->dispc); |
4fbafaf3 TV |
133 | if (r) |
134 | goto err_get_dispc; | |
23c0a7a6 | 135 | |
e5906f76 | 136 | r = sdi_calc_clock_div(sdi, sdi->pixelclock, &fck, &dispc_cinfo); |
23c0a7a6 | 137 | if (r) |
4fbafaf3 | 138 | goto err_calc_clock_div; |
23c0a7a6 | 139 | |
24aac601 | 140 | sdi->mgr_config.clock_info = dispc_cinfo; |
23c0a7a6 | 141 | |
24aac601 | 142 | r = dss_set_fck_rate(sdi->dss, fck); |
23c0a7a6 | 143 | if (r) |
4fbafaf3 | 144 | goto err_set_dss_clock_div; |
23c0a7a6 | 145 | |
24aac601 | 146 | sdi_config_lcd_manager(sdi); |
23c0a7a6 | 147 | |
35d67866 TV |
148 | /* |
149 | * LCLK and PCLK divisors are located in shadow registers, and we | |
150 | * normally write them to DISPC registers when enabling the output. | |
151 | * However, SDI uses pck-free as source clock for its PLL, and pck-free | |
152 | * is affected by the divisors. And as we need the PLL before enabling | |
153 | * the output, we need to write the divisors early. | |
154 | * | |
155 | * It seems just writing to the DISPC register is enough, and we don't | |
156 | * need to care about the shadow register mechanism for pck-free. The | |
157 | * exact reason for this is unknown. | |
158 | */ | |
24aac601 LP |
159 | dispc_mgr_set_clock_div(sdi->dss->dispc, sdi->output.dispc_channel, |
160 | &sdi->mgr_config.clock_info); | |
889b4fd7 | 161 | |
24aac601 LP |
162 | dss_sdi_init(sdi->dss, sdi->datapairs); |
163 | r = dss_sdi_enable(sdi->dss); | |
42c9dee8 | 164 | if (r) |
4fbafaf3 | 165 | goto err_sdi_enable; |
42c9dee8 | 166 | mdelay(2); |
23c0a7a6 | 167 | |
24aac601 | 168 | r = dss_mgr_enable(&sdi->output); |
33ca237f TV |
169 | if (r) |
170 | goto err_mgr_enable; | |
23c0a7a6 | 171 | |
19b4200d | 172 | return; |
4fbafaf3 | 173 | |
33ca237f | 174 | err_mgr_enable: |
24aac601 | 175 | dss_sdi_disable(sdi->dss); |
4fbafaf3 | 176 | err_sdi_enable: |
4fbafaf3 TV |
177 | err_set_dss_clock_div: |
178 | err_calc_clock_div: | |
24aac601 | 179 | dispc_runtime_put(sdi->dss->dispc); |
4fbafaf3 | 180 | err_get_dispc: |
24aac601 | 181 | regulator_disable(sdi->vdds_sdi_reg); |
23c0a7a6 TV |
182 | } |
183 | ||
cd6e915b | 184 | static void sdi_display_disable(struct omap_dss_device *dssdev) |
23c0a7a6 | 185 | { |
24aac601 LP |
186 | struct sdi_device *sdi = dssdev_to_sdi(dssdev); |
187 | ||
188 | dss_mgr_disable(&sdi->output); | |
23c0a7a6 | 189 | |
24aac601 | 190 | dss_sdi_disable(sdi->dss); |
23c0a7a6 | 191 | |
24aac601 | 192 | dispc_runtime_put(sdi->dss->dispc); |
23c0a7a6 | 193 | |
24aac601 | 194 | regulator_disable(sdi->vdds_sdi_reg); |
23c0a7a6 | 195 | } |
23c0a7a6 | 196 | |
cd6e915b | 197 | static void sdi_set_timings(struct omap_dss_device *dssdev, |
41322aa6 | 198 | const struct drm_display_mode *mode) |
c7833f7b | 199 | { |
24aac601 LP |
200 | struct sdi_device *sdi = dssdev_to_sdi(dssdev); |
201 | ||
e5906f76 | 202 | sdi->pixelclock = mode->clock * 1000; |
c7833f7b | 203 | } |
c7833f7b | 204 | |
b1082dfd | 205 | static int sdi_check_timings(struct omap_dss_device *dssdev, |
41322aa6 | 206 | struct drm_display_mode *mode) |
b1082dfd | 207 | { |
96fc64c7 LP |
208 | struct sdi_device *sdi = dssdev_to_sdi(dssdev); |
209 | struct dispc_clock_info dispc_cinfo; | |
41322aa6 | 210 | unsigned long pixelclock = mode->clock * 1000; |
96fc64c7 LP |
211 | unsigned long fck; |
212 | unsigned long pck; | |
213 | int r; | |
214 | ||
41322aa6 | 215 | if (pixelclock == 0) |
b1082dfd TV |
216 | return -EINVAL; |
217 | ||
41322aa6 | 218 | r = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo); |
96fc64c7 LP |
219 | if (r) |
220 | return r; | |
221 | ||
222 | pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div; | |
223 | ||
41322aa6 | 224 | if (pck != pixelclock) { |
96fc64c7 | 225 | DSSWARN("Pixel clock adjusted from %lu Hz to %lu Hz\n", |
41322aa6 | 226 | pixelclock, pck); |
96fc64c7 | 227 | |
41322aa6 | 228 | mode->clock = pck / 1000; |
96fc64c7 LP |
229 | } |
230 | ||
b1082dfd TV |
231 | return 0; |
232 | } | |
233 | ||
511afb44 LP |
234 | static int sdi_connect(struct omap_dss_device *src, |
235 | struct omap_dss_device *dst) | |
b1082dfd | 236 | { |
f8a8eabb | 237 | return omapdss_device_connect(dst->dss, dst, dst->next); |
b1082dfd TV |
238 | } |
239 | ||
511afb44 LP |
240 | static void sdi_disconnect(struct omap_dss_device *src, |
241 | struct omap_dss_device *dst) | |
b1082dfd | 242 | { |
511afb44 | 243 | omapdss_device_disconnect(dst, dst->next); |
b1082dfd TV |
244 | } |
245 | ||
b93109d7 | 246 | static const struct omap_dss_device_ops sdi_ops = { |
b1082dfd TV |
247 | .connect = sdi_connect, |
248 | .disconnect = sdi_disconnect, | |
249 | ||
cd6e915b TV |
250 | .enable = sdi_display_enable, |
251 | .disable = sdi_display_disable, | |
b1082dfd TV |
252 | |
253 | .check_timings = sdi_check_timings, | |
cd6e915b | 254 | .set_timings = sdi_set_timings, |
b1082dfd TV |
255 | }; |
256 | ||
27d62452 | 257 | static int sdi_init_output(struct sdi_device *sdi) |
81b87f51 | 258 | { |
24aac601 | 259 | struct omap_dss_device *out = &sdi->output; |
71316556 | 260 | int r; |
81b87f51 | 261 | |
24aac601 | 262 | out->dev = &sdi->pdev->dev; |
81b87f51 | 263 | out->id = OMAP_DSS_OUTPUT_SDI; |
0dbfc396 | 264 | out->type = OMAP_DISPLAY_TYPE_SDI; |
7286a08f | 265 | out->name = "sdi.0"; |
2eea5ae6 | 266 | out->dispc_channel = OMAP_DSS_CHANNEL_LCD; |
a32442d4 | 267 | /* We have SDI only on OMAP3, where it's on port 1 */ |
4e20bda6 | 268 | out->of_ports = BIT(1); |
b93109d7 | 269 | out->ops = &sdi_ops; |
b7328e14 | 270 | out->owner = THIS_MODULE; |
88bc4178 LP |
271 | out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE /* 15.5.9.1.2 */ |
272 | | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE; | |
81b87f51 | 273 | |
d17eb453 LP |
274 | r = omapdss_device_init_output(out); |
275 | if (r < 0) | |
71316556 | 276 | return r; |
71316556 | 277 | |
de57e9db | 278 | omapdss_device_register(out); |
27d62452 LP |
279 | |
280 | return 0; | |
81b87f51 AT |
281 | } |
282 | ||
24aac601 | 283 | static void sdi_uninit_output(struct sdi_device *sdi) |
81b87f51 | 284 | { |
de57e9db | 285 | omapdss_device_unregister(&sdi->output); |
d17eb453 | 286 | omapdss_device_cleanup_output(&sdi->output); |
81b87f51 AT |
287 | } |
288 | ||
d7157dfe LP |
289 | int sdi_init_port(struct dss_device *dss, struct platform_device *pdev, |
290 | struct device_node *port) | |
2ecef246 | 291 | { |
24aac601 | 292 | struct sdi_device *sdi; |
2ecef246 TV |
293 | struct device_node *ep; |
294 | u32 datapairs; | |
295 | int r; | |
296 | ||
24aac601 LP |
297 | sdi = kzalloc(sizeof(*sdi), GFP_KERNEL); |
298 | if (!sdi) | |
299 | return -ENOMEM; | |
300 | ||
09bffa6e | 301 | ep = of_get_next_child(port, NULL); |
24aac601 LP |
302 | if (!ep) { |
303 | r = 0; | |
304 | goto err_free; | |
305 | } | |
2ecef246 TV |
306 | |
307 | r = of_property_read_u32(ep, "datapairs", &datapairs); | |
66aacfe2 | 308 | of_node_put(ep); |
2ecef246 TV |
309 | if (r) { |
310 | DSSERR("failed to parse datapairs\n"); | |
66aacfe2 | 311 | goto err_free; |
2ecef246 TV |
312 | } |
313 | ||
24aac601 LP |
314 | sdi->datapairs = datapairs; |
315 | sdi->dss = dss; | |
2ecef246 | 316 | |
24aac601 LP |
317 | sdi->pdev = pdev; |
318 | port->data = sdi; | |
2ecef246 | 319 | |
8a36357a LP |
320 | sdi->vdds_sdi_reg = devm_regulator_get(&pdev->dev, "vdds_sdi"); |
321 | if (IS_ERR(sdi->vdds_sdi_reg)) { | |
322 | r = PTR_ERR(sdi->vdds_sdi_reg); | |
323 | if (r != -EPROBE_DEFER) | |
324 | DSSERR("can't get VDDS_SDI regulator\n"); | |
325 | goto err_free; | |
326 | } | |
327 | ||
27d62452 LP |
328 | r = sdi_init_output(sdi); |
329 | if (r) | |
330 | goto err_free; | |
2ecef246 TV |
331 | |
332 | return 0; | |
333 | ||
24aac601 LP |
334 | err_free: |
335 | kfree(sdi); | |
2ecef246 TV |
336 | |
337 | return r; | |
338 | } | |
339 | ||
ede92695 | 340 | void sdi_uninit_port(struct device_node *port) |
2ecef246 | 341 | { |
24aac601 LP |
342 | struct sdi_device *sdi = port->data; |
343 | ||
344 | if (!sdi) | |
2ecef246 TV |
345 | return; |
346 | ||
24aac601 LP |
347 | sdi_uninit_output(sdi); |
348 | kfree(sdi); | |
2ecef246 | 349 | } |