]>
Commit | Line | Data |
---|---|---|
9026e0d1 MR |
1 | /* |
2 | * Copyright (C) 2015 Free Electrons | |
3 | * Copyright (C) 2015 NextThing Co | |
4 | * | |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License as | |
9 | * published by the Free Software Foundation; either version 2 of | |
10 | * the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <drm/drmP.h> | |
14 | #include <drm/drm_atomic_helper.h> | |
15 | #include <drm/drm_crtc.h> | |
16 | #include <drm/drm_crtc_helper.h> | |
17 | #include <drm/drm_modes.h> | |
ebc94461 | 18 | #include <drm/drm_of.h> |
9026e0d1 MR |
19 | |
20 | #include <linux/component.h> | |
21 | #include <linux/ioport.h> | |
22 | #include <linux/of_address.h> | |
91ea2f29 | 23 | #include <linux/of_device.h> |
9026e0d1 MR |
24 | #include <linux/of_irq.h> |
25 | #include <linux/regmap.h> | |
26 | #include <linux/reset.h> | |
27 | ||
28 | #include "sun4i_crtc.h" | |
29 | #include "sun4i_dotclock.h" | |
30 | #include "sun4i_drv.h" | |
29e57fab | 31 | #include "sun4i_rgb.h" |
9026e0d1 | 32 | #include "sun4i_tcon.h" |
87969338 | 33 | #include "sunxi_engine.h" |
9026e0d1 MR |
34 | |
35 | void sun4i_tcon_disable(struct sun4i_tcon *tcon) | |
36 | { | |
37 | DRM_DEBUG_DRIVER("Disabling TCON\n"); | |
38 | ||
39 | /* Disable the TCON */ | |
40 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
41 | SUN4I_TCON_GCTL_TCON_ENABLE, 0); | |
42 | } | |
43 | EXPORT_SYMBOL(sun4i_tcon_disable); | |
44 | ||
45 | void sun4i_tcon_enable(struct sun4i_tcon *tcon) | |
46 | { | |
47 | DRM_DEBUG_DRIVER("Enabling TCON\n"); | |
48 | ||
49 | /* Enable the TCON */ | |
50 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
51 | SUN4I_TCON_GCTL_TCON_ENABLE, | |
52 | SUN4I_TCON_GCTL_TCON_ENABLE); | |
53 | } | |
54 | EXPORT_SYMBOL(sun4i_tcon_enable); | |
55 | ||
56 | void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) | |
57 | { | |
1a075426 MR |
58 | DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel); |
59 | ||
9026e0d1 MR |
60 | /* Disable the TCON's channel */ |
61 | if (channel == 0) { | |
62 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | |
63 | SUN4I_TCON0_CTL_TCON_ENABLE, 0); | |
64 | clk_disable_unprepare(tcon->dclk); | |
8e924047 | 65 | return; |
9026e0d1 | 66 | } |
8e924047 | 67 | |
91ea2f29 | 68 | WARN_ON(!tcon->quirks->has_channel_1); |
8e924047 MR |
69 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, |
70 | SUN4I_TCON1_CTL_TCON_ENABLE, 0); | |
71 | clk_disable_unprepare(tcon->sclk1); | |
9026e0d1 MR |
72 | } |
73 | EXPORT_SYMBOL(sun4i_tcon_channel_disable); | |
74 | ||
75 | void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) | |
76 | { | |
1a075426 MR |
77 | DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel); |
78 | ||
9026e0d1 MR |
79 | /* Enable the TCON's channel */ |
80 | if (channel == 0) { | |
81 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | |
82 | SUN4I_TCON0_CTL_TCON_ENABLE, | |
83 | SUN4I_TCON0_CTL_TCON_ENABLE); | |
84 | clk_prepare_enable(tcon->dclk); | |
8e924047 | 85 | return; |
9026e0d1 | 86 | } |
8e924047 | 87 | |
91ea2f29 | 88 | WARN_ON(!tcon->quirks->has_channel_1); |
8e924047 MR |
89 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, |
90 | SUN4I_TCON1_CTL_TCON_ENABLE, | |
91 | SUN4I_TCON1_CTL_TCON_ENABLE); | |
92 | clk_prepare_enable(tcon->sclk1); | |
9026e0d1 MR |
93 | } |
94 | EXPORT_SYMBOL(sun4i_tcon_channel_enable); | |
95 | ||
96 | void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) | |
97 | { | |
98 | u32 mask, val = 0; | |
99 | ||
100 | DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis"); | |
101 | ||
102 | mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | | |
103 | SUN4I_TCON_GINT0_VBLANK_ENABLE(1); | |
104 | ||
105 | if (enable) | |
106 | val = mask; | |
107 | ||
108 | regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); | |
109 | } | |
110 | EXPORT_SYMBOL(sun4i_tcon_enable_vblank); | |
111 | ||
f8c73f4f MR |
112 | void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, |
113 | struct drm_encoder *encoder) | |
114 | { | |
b7cb9b91 MR |
115 | u32 val; |
116 | ||
f8c73f4f MR |
117 | if (!tcon->quirks->has_unknown_mux) |
118 | return; | |
119 | ||
120 | if (channel != 1) | |
121 | return; | |
122 | ||
b7cb9b91 MR |
123 | if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) |
124 | val = 1; | |
125 | else | |
126 | val = 0; | |
127 | ||
f8c73f4f MR |
128 | /* |
129 | * FIXME: Undocumented bits | |
130 | */ | |
b7cb9b91 | 131 | regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); |
f8c73f4f MR |
132 | } |
133 | EXPORT_SYMBOL(sun4i_tcon_set_mux); | |
134 | ||
9026e0d1 MR |
135 | static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, |
136 | int channel) | |
137 | { | |
138 | int delay = mode->vtotal - mode->vdisplay; | |
139 | ||
140 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) | |
141 | delay /= 2; | |
142 | ||
143 | if (channel == 1) | |
144 | delay -= 2; | |
145 | ||
146 | delay = min(delay, 30); | |
147 | ||
148 | DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); | |
149 | ||
150 | return delay; | |
151 | } | |
152 | ||
153 | void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, | |
154 | struct drm_display_mode *mode) | |
155 | { | |
156 | unsigned int bp, hsync, vsync; | |
157 | u8 clk_delay; | |
158 | u32 val = 0; | |
159 | ||
86cf6788 CYT |
160 | /* Configure the dot clock */ |
161 | clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); | |
162 | ||
9026e0d1 MR |
163 | /* Adjust clock delay */ |
164 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); | |
165 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | |
166 | SUN4I_TCON0_CTL_CLK_DELAY_MASK, | |
167 | SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); | |
168 | ||
169 | /* Set the resolution */ | |
170 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, | |
171 | SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | | |
172 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); | |
173 | ||
174 | /* | |
175 | * This is called a backporch in the register documentation, | |
23a1cb11 | 176 | * but it really is the back porch + hsync |
9026e0d1 MR |
177 | */ |
178 | bp = mode->crtc_htotal - mode->crtc_hsync_start; | |
179 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", | |
180 | mode->crtc_htotal, bp); | |
181 | ||
182 | /* Set horizontal display timings */ | |
183 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, | |
184 | SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | | |
185 | SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); | |
186 | ||
187 | /* | |
188 | * This is called a backporch in the register documentation, | |
23a1cb11 | 189 | * but it really is the back porch + hsync |
9026e0d1 MR |
190 | */ |
191 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; | |
192 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", | |
193 | mode->crtc_vtotal, bp); | |
194 | ||
195 | /* Set vertical display timings */ | |
196 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, | |
a88cbbd4 | 197 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | |
9026e0d1 MR |
198 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); |
199 | ||
200 | /* Set Hsync and Vsync length */ | |
201 | hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; | |
202 | vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; | |
203 | DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); | |
204 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, | |
205 | SUN4I_TCON0_BASIC3_V_SYNC(vsync) | | |
206 | SUN4I_TCON0_BASIC3_H_SYNC(hsync)); | |
207 | ||
208 | /* Setup the polarity of the various signals */ | |
209 | if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) | |
210 | val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; | |
211 | ||
212 | if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) | |
213 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; | |
214 | ||
215 | regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, | |
216 | SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, | |
217 | val); | |
218 | ||
219 | /* Map output pins to channel 0 */ | |
220 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
221 | SUN4I_TCON_GCTL_IOMAP_MASK, | |
222 | SUN4I_TCON_GCTL_IOMAP_TCON0); | |
223 | ||
224 | /* Enable the output on the pins */ | |
225 | regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); | |
226 | } | |
227 | EXPORT_SYMBOL(sun4i_tcon0_mode_set); | |
228 | ||
229 | void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, | |
230 | struct drm_display_mode *mode) | |
231 | { | |
b8317a3d | 232 | unsigned int bp, hsync, vsync, vtotal; |
9026e0d1 MR |
233 | u8 clk_delay; |
234 | u32 val; | |
235 | ||
91ea2f29 | 236 | WARN_ON(!tcon->quirks->has_channel_1); |
8e924047 | 237 | |
86cf6788 CYT |
238 | /* Configure the dot clock */ |
239 | clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); | |
240 | ||
9026e0d1 MR |
241 | /* Adjust clock delay */ |
242 | clk_delay = sun4i_tcon_get_clk_delay(mode, 1); | |
243 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | |
244 | SUN4I_TCON1_CTL_CLK_DELAY_MASK, | |
245 | SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); | |
246 | ||
247 | /* Set interlaced mode */ | |
248 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) | |
249 | val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; | |
250 | else | |
251 | val = 0; | |
252 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | |
253 | SUN4I_TCON1_CTL_INTERLACE_ENABLE, | |
254 | val); | |
255 | ||
256 | /* Set the input resolution */ | |
257 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, | |
258 | SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | | |
259 | SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); | |
260 | ||
261 | /* Set the upscaling resolution */ | |
262 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, | |
263 | SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | | |
264 | SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); | |
265 | ||
266 | /* Set the output resolution */ | |
267 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, | |
268 | SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | | |
269 | SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); | |
270 | ||
271 | /* Set horizontal display timings */ | |
3cb2f46b | 272 | bp = mode->crtc_htotal - mode->crtc_hsync_start; |
9026e0d1 MR |
273 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", |
274 | mode->htotal, bp); | |
275 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, | |
276 | SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | | |
277 | SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); | |
278 | ||
3cb2f46b | 279 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; |
9026e0d1 | 280 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", |
b8317a3d MR |
281 | mode->crtc_vtotal, bp); |
282 | ||
283 | /* | |
284 | * The vertical resolution needs to be doubled in all | |
285 | * cases. We could use crtc_vtotal and always multiply by two, | |
286 | * but that leads to a rounding error in interlace when vtotal | |
287 | * is odd. | |
288 | * | |
289 | * This happens with TV's PAL for example, where vtotal will | |
290 | * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be | |
291 | * 624, which apparently confuses the hardware. | |
292 | * | |
293 | * To work around this, we will always use vtotal, and | |
294 | * multiply by two only if we're not in interlace. | |
295 | */ | |
296 | vtotal = mode->vtotal; | |
297 | if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) | |
298 | vtotal = vtotal * 2; | |
299 | ||
300 | /* Set vertical display timings */ | |
9026e0d1 | 301 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, |
b8317a3d | 302 | SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | |
9026e0d1 MR |
303 | SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); |
304 | ||
305 | /* Set Hsync and Vsync length */ | |
306 | hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; | |
307 | vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; | |
308 | DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); | |
309 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, | |
310 | SUN4I_TCON1_BASIC5_V_SYNC(vsync) | | |
311 | SUN4I_TCON1_BASIC5_H_SYNC(hsync)); | |
312 | ||
313 | /* Map output pins to channel 1 */ | |
314 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
315 | SUN4I_TCON_GCTL_IOMAP_MASK, | |
316 | SUN4I_TCON_GCTL_IOMAP_TCON1); | |
9026e0d1 MR |
317 | } |
318 | EXPORT_SYMBOL(sun4i_tcon1_mode_set); | |
319 | ||
320 | static void sun4i_tcon_finish_page_flip(struct drm_device *dev, | |
321 | struct sun4i_crtc *scrtc) | |
322 | { | |
323 | unsigned long flags; | |
324 | ||
325 | spin_lock_irqsave(&dev->event_lock, flags); | |
326 | if (scrtc->event) { | |
327 | drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event); | |
328 | drm_crtc_vblank_put(&scrtc->crtc); | |
329 | scrtc->event = NULL; | |
330 | } | |
331 | spin_unlock_irqrestore(&dev->event_lock, flags); | |
332 | } | |
333 | ||
334 | static irqreturn_t sun4i_tcon_handler(int irq, void *private) | |
335 | { | |
336 | struct sun4i_tcon *tcon = private; | |
337 | struct drm_device *drm = tcon->drm; | |
46cce6da | 338 | struct sun4i_crtc *scrtc = tcon->crtc; |
9026e0d1 MR |
339 | unsigned int status; |
340 | ||
341 | regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); | |
342 | ||
343 | if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | | |
344 | SUN4I_TCON_GINT0_VBLANK_INT(1)))) | |
345 | return IRQ_NONE; | |
346 | ||
347 | drm_crtc_handle_vblank(&scrtc->crtc); | |
348 | sun4i_tcon_finish_page_flip(drm, scrtc); | |
349 | ||
350 | /* Acknowledge the interrupt */ | |
351 | regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, | |
352 | SUN4I_TCON_GINT0_VBLANK_INT(0) | | |
353 | SUN4I_TCON_GINT0_VBLANK_INT(1), | |
354 | 0); | |
355 | ||
356 | return IRQ_HANDLED; | |
357 | } | |
358 | ||
359 | static int sun4i_tcon_init_clocks(struct device *dev, | |
360 | struct sun4i_tcon *tcon) | |
361 | { | |
362 | tcon->clk = devm_clk_get(dev, "ahb"); | |
363 | if (IS_ERR(tcon->clk)) { | |
364 | dev_err(dev, "Couldn't get the TCON bus clock\n"); | |
365 | return PTR_ERR(tcon->clk); | |
366 | } | |
367 | clk_prepare_enable(tcon->clk); | |
368 | ||
369 | tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); | |
370 | if (IS_ERR(tcon->sclk0)) { | |
371 | dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); | |
372 | return PTR_ERR(tcon->sclk0); | |
373 | } | |
374 | ||
91ea2f29 | 375 | if (tcon->quirks->has_channel_1) { |
8e924047 MR |
376 | tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); |
377 | if (IS_ERR(tcon->sclk1)) { | |
378 | dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); | |
379 | return PTR_ERR(tcon->sclk1); | |
380 | } | |
9026e0d1 MR |
381 | } |
382 | ||
4c7f16d1 | 383 | return 0; |
9026e0d1 MR |
384 | } |
385 | ||
386 | static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) | |
387 | { | |
9026e0d1 MR |
388 | clk_disable_unprepare(tcon->clk); |
389 | } | |
390 | ||
391 | static int sun4i_tcon_init_irq(struct device *dev, | |
392 | struct sun4i_tcon *tcon) | |
393 | { | |
394 | struct platform_device *pdev = to_platform_device(dev); | |
395 | int irq, ret; | |
396 | ||
397 | irq = platform_get_irq(pdev, 0); | |
398 | if (irq < 0) { | |
399 | dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); | |
400 | return irq; | |
401 | } | |
402 | ||
403 | ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, | |
404 | dev_name(dev), tcon); | |
405 | if (ret) { | |
406 | dev_err(dev, "Couldn't request the IRQ\n"); | |
407 | return ret; | |
408 | } | |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
413 | static struct regmap_config sun4i_tcon_regmap_config = { | |
414 | .reg_bits = 32, | |
415 | .val_bits = 32, | |
416 | .reg_stride = 4, | |
417 | .max_register = 0x800, | |
418 | }; | |
419 | ||
420 | static int sun4i_tcon_init_regmap(struct device *dev, | |
421 | struct sun4i_tcon *tcon) | |
422 | { | |
423 | struct platform_device *pdev = to_platform_device(dev); | |
424 | struct resource *res; | |
425 | void __iomem *regs; | |
426 | ||
427 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
428 | regs = devm_ioremap_resource(dev, res); | |
af346f55 | 429 | if (IS_ERR(regs)) |
9026e0d1 | 430 | return PTR_ERR(regs); |
9026e0d1 MR |
431 | |
432 | tcon->regs = devm_regmap_init_mmio(dev, regs, | |
433 | &sun4i_tcon_regmap_config); | |
434 | if (IS_ERR(tcon->regs)) { | |
435 | dev_err(dev, "Couldn't create the TCON regmap\n"); | |
436 | return PTR_ERR(tcon->regs); | |
437 | } | |
438 | ||
439 | /* Make sure the TCON is disabled and all IRQs are off */ | |
440 | regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); | |
441 | regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); | |
442 | regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); | |
443 | ||
444 | /* Disable IO lines and set them to tristate */ | |
445 | regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); | |
446 | regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); | |
447 | ||
448 | return 0; | |
449 | } | |
450 | ||
b317fa3b CYT |
451 | /* |
452 | * On SoCs with the old display pipeline design (Display Engine 1.0), | |
453 | * the TCON is always tied to just one backend. Hence we can traverse | |
454 | * the of_graph upwards to find the backend our tcon is connected to, | |
455 | * and take its ID as our own. | |
456 | * | |
457 | * We can either identify backends from their compatible strings, which | |
458 | * means maintaining a large list of them. Or, since the backend is | |
459 | * registered and binded before the TCON, we can just go through the | |
460 | * list of registered backends and compare the device node. | |
87969338 IZ |
461 | * |
462 | * As the structures now store engines instead of backends, here this | |
463 | * function in fact searches the corresponding engine, and the ID is | |
464 | * requested via the get_id function of the engine. | |
b317fa3b | 465 | */ |
87969338 IZ |
466 | static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, |
467 | struct device_node *node) | |
b317fa3b CYT |
468 | { |
469 | struct device_node *port, *ep, *remote; | |
87969338 | 470 | struct sunxi_engine *engine; |
b317fa3b CYT |
471 | |
472 | port = of_graph_get_port_by_id(node, 0); | |
473 | if (!port) | |
474 | return ERR_PTR(-EINVAL); | |
475 | ||
476 | for_each_available_child_of_node(port, ep) { | |
477 | remote = of_graph_get_remote_port_parent(ep); | |
478 | if (!remote) | |
479 | continue; | |
480 | ||
87969338 IZ |
481 | /* does this node match any registered engines? */ |
482 | list_for_each_entry(engine, &drv->engine_list, list) { | |
483 | if (remote == engine->node) { | |
b317fa3b CYT |
484 | of_node_put(remote); |
485 | of_node_put(port); | |
87969338 | 486 | return engine; |
b317fa3b CYT |
487 | } |
488 | } | |
489 | ||
490 | /* keep looking through upstream ports */ | |
87969338 IZ |
491 | engine = sun4i_tcon_find_engine(drv, remote); |
492 | if (!IS_ERR(engine)) { | |
b317fa3b CYT |
493 | of_node_put(remote); |
494 | of_node_put(port); | |
87969338 | 495 | return engine; |
b317fa3b CYT |
496 | } |
497 | } | |
498 | ||
499 | return ERR_PTR(-EINVAL); | |
500 | } | |
501 | ||
9026e0d1 MR |
502 | static int sun4i_tcon_bind(struct device *dev, struct device *master, |
503 | void *data) | |
504 | { | |
505 | struct drm_device *drm = data; | |
506 | struct sun4i_drv *drv = drm->dev_private; | |
87969338 | 507 | struct sunxi_engine *engine; |
9026e0d1 MR |
508 | struct sun4i_tcon *tcon; |
509 | int ret; | |
510 | ||
87969338 IZ |
511 | engine = sun4i_tcon_find_engine(drv, dev->of_node); |
512 | if (IS_ERR(engine)) { | |
513 | dev_err(dev, "Couldn't find matching engine\n"); | |
80a58240 | 514 | return -EPROBE_DEFER; |
b317fa3b | 515 | } |
80a58240 | 516 | |
9026e0d1 MR |
517 | tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); |
518 | if (!tcon) | |
519 | return -ENOMEM; | |
520 | dev_set_drvdata(dev, tcon); | |
9026e0d1 | 521 | tcon->drm = drm; |
ae558110 | 522 | tcon->dev = dev; |
87969338 | 523 | tcon->id = engine->id; |
91ea2f29 | 524 | tcon->quirks = of_device_get_match_data(dev); |
9026e0d1 MR |
525 | |
526 | tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); | |
527 | if (IS_ERR(tcon->lcd_rst)) { | |
528 | dev_err(dev, "Couldn't get our reset line\n"); | |
529 | return PTR_ERR(tcon->lcd_rst); | |
530 | } | |
531 | ||
532 | /* Make sure our TCON is reset */ | |
533 | if (!reset_control_status(tcon->lcd_rst)) | |
534 | reset_control_assert(tcon->lcd_rst); | |
535 | ||
536 | ret = reset_control_deassert(tcon->lcd_rst); | |
537 | if (ret) { | |
538 | dev_err(dev, "Couldn't deassert our reset line\n"); | |
539 | return ret; | |
540 | } | |
541 | ||
4c7f16d1 CYT |
542 | ret = sun4i_tcon_init_clocks(dev, tcon); |
543 | if (ret) { | |
544 | dev_err(dev, "Couldn't init our TCON clocks\n"); | |
545 | goto err_assert_reset; | |
546 | } | |
547 | ||
9026e0d1 MR |
548 | ret = sun4i_tcon_init_regmap(dev, tcon); |
549 | if (ret) { | |
550 | dev_err(dev, "Couldn't init our TCON regmap\n"); | |
4c7f16d1 | 551 | goto err_free_clocks; |
9026e0d1 MR |
552 | } |
553 | ||
4c7f16d1 | 554 | ret = sun4i_dclk_create(dev, tcon); |
9026e0d1 | 555 | if (ret) { |
4c7f16d1 CYT |
556 | dev_err(dev, "Couldn't create our TCON dot clock\n"); |
557 | goto err_free_clocks; | |
9026e0d1 MR |
558 | } |
559 | ||
560 | ret = sun4i_tcon_init_irq(dev, tcon); | |
561 | if (ret) { | |
562 | dev_err(dev, "Couldn't init our TCON interrupts\n"); | |
4c7f16d1 | 563 | goto err_free_dotclock; |
9026e0d1 MR |
564 | } |
565 | ||
87969338 | 566 | tcon->crtc = sun4i_crtc_init(drm, engine, tcon); |
46cce6da CYT |
567 | if (IS_ERR(tcon->crtc)) { |
568 | dev_err(dev, "Couldn't create our CRTC\n"); | |
569 | ret = PTR_ERR(tcon->crtc); | |
9026e0d1 MR |
570 | goto err_free_clocks; |
571 | } | |
572 | ||
b9c8506c | 573 | ret = sun4i_rgb_init(drm, tcon); |
13fef095 CYT |
574 | if (ret < 0) |
575 | goto err_free_clocks; | |
576 | ||
80a58240 CYT |
577 | list_add_tail(&tcon->list, &drv->tcon_list); |
578 | ||
13fef095 | 579 | return 0; |
9026e0d1 | 580 | |
4c7f16d1 CYT |
581 | err_free_dotclock: |
582 | sun4i_dclk_free(tcon); | |
9026e0d1 MR |
583 | err_free_clocks: |
584 | sun4i_tcon_free_clocks(tcon); | |
585 | err_assert_reset: | |
586 | reset_control_assert(tcon->lcd_rst); | |
587 | return ret; | |
588 | } | |
589 | ||
590 | static void sun4i_tcon_unbind(struct device *dev, struct device *master, | |
591 | void *data) | |
592 | { | |
593 | struct sun4i_tcon *tcon = dev_get_drvdata(dev); | |
594 | ||
80a58240 | 595 | list_del(&tcon->list); |
4c7f16d1 | 596 | sun4i_dclk_free(tcon); |
9026e0d1 MR |
597 | sun4i_tcon_free_clocks(tcon); |
598 | } | |
599 | ||
dfeb693d | 600 | static const struct component_ops sun4i_tcon_ops = { |
9026e0d1 MR |
601 | .bind = sun4i_tcon_bind, |
602 | .unbind = sun4i_tcon_unbind, | |
603 | }; | |
604 | ||
605 | static int sun4i_tcon_probe(struct platform_device *pdev) | |
606 | { | |
29e57fab | 607 | struct device_node *node = pdev->dev.of_node; |
894f5a9f | 608 | struct drm_bridge *bridge; |
29e57fab | 609 | struct drm_panel *panel; |
ebc94461 | 610 | int ret; |
29e57fab | 611 | |
ebc94461 RH |
612 | ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); |
613 | if (ret == -EPROBE_DEFER) | |
614 | return ret; | |
29e57fab | 615 | |
9026e0d1 MR |
616 | return component_add(&pdev->dev, &sun4i_tcon_ops); |
617 | } | |
618 | ||
619 | static int sun4i_tcon_remove(struct platform_device *pdev) | |
620 | { | |
621 | component_del(&pdev->dev, &sun4i_tcon_ops); | |
622 | ||
623 | return 0; | |
624 | } | |
625 | ||
91ea2f29 CYT |
626 | static const struct sun4i_tcon_quirks sun5i_a13_quirks = { |
627 | .has_unknown_mux = true, | |
628 | .has_channel_1 = true, | |
629 | }; | |
630 | ||
93a5ec14 CYT |
631 | static const struct sun4i_tcon_quirks sun6i_a31_quirks = { |
632 | .has_channel_1 = true, | |
633 | }; | |
634 | ||
635 | static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { | |
636 | .has_channel_1 = true, | |
637 | }; | |
638 | ||
91ea2f29 CYT |
639 | static const struct sun4i_tcon_quirks sun8i_a33_quirks = { |
640 | /* nothing is supported */ | |
641 | }; | |
642 | ||
1a0edb3f IZ |
643 | static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { |
644 | /* nothing is supported */ | |
645 | }; | |
646 | ||
9026e0d1 | 647 | static const struct of_device_id sun4i_tcon_of_table[] = { |
91ea2f29 | 648 | { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, |
93a5ec14 CYT |
649 | { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, |
650 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, | |
91ea2f29 | 651 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, |
1a0edb3f | 652 | { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, |
9026e0d1 MR |
653 | { } |
654 | }; | |
655 | MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); | |
656 | ||
657 | static struct platform_driver sun4i_tcon_platform_driver = { | |
658 | .probe = sun4i_tcon_probe, | |
659 | .remove = sun4i_tcon_remove, | |
660 | .driver = { | |
661 | .name = "sun4i-tcon", | |
662 | .of_match_table = sun4i_tcon_of_table, | |
663 | }, | |
664 | }; | |
665 | module_platform_driver(sun4i_tcon_platform_driver); | |
666 | ||
667 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | |
668 | MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); | |
669 | MODULE_LICENSE("GPL"); |