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