]>
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 | ||
80a58240 | 28 | #include "sun4i_backend.h" |
9026e0d1 MR |
29 | #include "sun4i_crtc.h" |
30 | #include "sun4i_dotclock.h" | |
31 | #include "sun4i_drv.h" | |
29e57fab | 32 | #include "sun4i_rgb.h" |
9026e0d1 MR |
33 | #include "sun4i_tcon.h" |
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. | |
422 | */ | |
423 | static struct sun4i_backend *sun4i_tcon_find_backend(struct sun4i_drv *drv, | |
424 | struct device_node *node) | |
425 | { | |
426 | struct device_node *port, *ep, *remote; | |
427 | struct sun4i_backend *backend; | |
428 | ||
429 | port = of_graph_get_port_by_id(node, 0); | |
430 | if (!port) | |
431 | return ERR_PTR(-EINVAL); | |
432 | ||
433 | for_each_available_child_of_node(port, ep) { | |
434 | remote = of_graph_get_remote_port_parent(ep); | |
435 | if (!remote) | |
436 | continue; | |
437 | ||
438 | /* does this node match any registered backends? */ | |
439 | list_for_each_entry(backend, &drv->backend_list, list) { | |
440 | if (remote == backend->node) { | |
441 | of_node_put(remote); | |
442 | of_node_put(port); | |
443 | return backend; | |
444 | } | |
445 | } | |
446 | ||
447 | /* keep looking through upstream ports */ | |
448 | backend = sun4i_tcon_find_backend(drv, remote); | |
449 | if (!IS_ERR(backend)) { | |
450 | of_node_put(remote); | |
451 | of_node_put(port); | |
452 | return backend; | |
453 | } | |
454 | } | |
455 | ||
456 | return ERR_PTR(-EINVAL); | |
457 | } | |
458 | ||
9026e0d1 MR |
459 | static int sun4i_tcon_bind(struct device *dev, struct device *master, |
460 | void *data) | |
461 | { | |
462 | struct drm_device *drm = data; | |
463 | struct sun4i_drv *drv = drm->dev_private; | |
80a58240 | 464 | struct sun4i_backend *backend; |
9026e0d1 MR |
465 | struct sun4i_tcon *tcon; |
466 | int ret; | |
467 | ||
b317fa3b CYT |
468 | backend = sun4i_tcon_find_backend(drv, dev->of_node); |
469 | if (IS_ERR(backend)) { | |
470 | dev_err(dev, "Couldn't find matching backend\n"); | |
80a58240 | 471 | return -EPROBE_DEFER; |
b317fa3b | 472 | } |
80a58240 | 473 | |
9026e0d1 MR |
474 | tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); |
475 | if (!tcon) | |
476 | return -ENOMEM; | |
477 | dev_set_drvdata(dev, tcon); | |
9026e0d1 | 478 | tcon->drm = drm; |
ae558110 | 479 | tcon->dev = dev; |
d281c862 | 480 | tcon->id = backend->id; |
91ea2f29 | 481 | tcon->quirks = of_device_get_match_data(dev); |
9026e0d1 MR |
482 | |
483 | tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); | |
484 | if (IS_ERR(tcon->lcd_rst)) { | |
485 | dev_err(dev, "Couldn't get our reset line\n"); | |
486 | return PTR_ERR(tcon->lcd_rst); | |
487 | } | |
488 | ||
489 | /* Make sure our TCON is reset */ | |
490 | if (!reset_control_status(tcon->lcd_rst)) | |
491 | reset_control_assert(tcon->lcd_rst); | |
492 | ||
493 | ret = reset_control_deassert(tcon->lcd_rst); | |
494 | if (ret) { | |
495 | dev_err(dev, "Couldn't deassert our reset line\n"); | |
496 | return ret; | |
497 | } | |
498 | ||
4c7f16d1 CYT |
499 | ret = sun4i_tcon_init_clocks(dev, tcon); |
500 | if (ret) { | |
501 | dev_err(dev, "Couldn't init our TCON clocks\n"); | |
502 | goto err_assert_reset; | |
503 | } | |
504 | ||
9026e0d1 MR |
505 | ret = sun4i_tcon_init_regmap(dev, tcon); |
506 | if (ret) { | |
507 | dev_err(dev, "Couldn't init our TCON regmap\n"); | |
4c7f16d1 | 508 | goto err_free_clocks; |
9026e0d1 MR |
509 | } |
510 | ||
4c7f16d1 | 511 | ret = sun4i_dclk_create(dev, tcon); |
9026e0d1 | 512 | if (ret) { |
4c7f16d1 CYT |
513 | dev_err(dev, "Couldn't create our TCON dot clock\n"); |
514 | goto err_free_clocks; | |
9026e0d1 MR |
515 | } |
516 | ||
517 | ret = sun4i_tcon_init_irq(dev, tcon); | |
518 | if (ret) { | |
519 | dev_err(dev, "Couldn't init our TCON interrupts\n"); | |
4c7f16d1 | 520 | goto err_free_dotclock; |
9026e0d1 MR |
521 | } |
522 | ||
80a58240 | 523 | tcon->crtc = sun4i_crtc_init(drm, backend, tcon); |
46cce6da CYT |
524 | if (IS_ERR(tcon->crtc)) { |
525 | dev_err(dev, "Couldn't create our CRTC\n"); | |
526 | ret = PTR_ERR(tcon->crtc); | |
9026e0d1 MR |
527 | goto err_free_clocks; |
528 | } | |
529 | ||
b9c8506c | 530 | ret = sun4i_rgb_init(drm, tcon); |
13fef095 CYT |
531 | if (ret < 0) |
532 | goto err_free_clocks; | |
533 | ||
80a58240 CYT |
534 | list_add_tail(&tcon->list, &drv->tcon_list); |
535 | ||
13fef095 | 536 | return 0; |
9026e0d1 | 537 | |
4c7f16d1 CYT |
538 | err_free_dotclock: |
539 | sun4i_dclk_free(tcon); | |
9026e0d1 MR |
540 | err_free_clocks: |
541 | sun4i_tcon_free_clocks(tcon); | |
542 | err_assert_reset: | |
543 | reset_control_assert(tcon->lcd_rst); | |
544 | return ret; | |
545 | } | |
546 | ||
547 | static void sun4i_tcon_unbind(struct device *dev, struct device *master, | |
548 | void *data) | |
549 | { | |
550 | struct sun4i_tcon *tcon = dev_get_drvdata(dev); | |
551 | ||
80a58240 | 552 | list_del(&tcon->list); |
4c7f16d1 | 553 | sun4i_dclk_free(tcon); |
9026e0d1 MR |
554 | sun4i_tcon_free_clocks(tcon); |
555 | } | |
556 | ||
dfeb693d | 557 | static const struct component_ops sun4i_tcon_ops = { |
9026e0d1 MR |
558 | .bind = sun4i_tcon_bind, |
559 | .unbind = sun4i_tcon_unbind, | |
560 | }; | |
561 | ||
562 | static int sun4i_tcon_probe(struct platform_device *pdev) | |
563 | { | |
29e57fab | 564 | struct device_node *node = pdev->dev.of_node; |
894f5a9f | 565 | struct drm_bridge *bridge; |
29e57fab | 566 | struct drm_panel *panel; |
ebc94461 | 567 | int ret; |
29e57fab | 568 | |
ebc94461 RH |
569 | ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); |
570 | if (ret == -EPROBE_DEFER) | |
571 | return ret; | |
29e57fab | 572 | |
9026e0d1 MR |
573 | return component_add(&pdev->dev, &sun4i_tcon_ops); |
574 | } | |
575 | ||
576 | static int sun4i_tcon_remove(struct platform_device *pdev) | |
577 | { | |
578 | component_del(&pdev->dev, &sun4i_tcon_ops); | |
579 | ||
580 | return 0; | |
581 | } | |
582 | ||
91ea2f29 CYT |
583 | static const struct sun4i_tcon_quirks sun5i_a13_quirks = { |
584 | .has_unknown_mux = true, | |
585 | .has_channel_1 = true, | |
586 | }; | |
587 | ||
93a5ec14 CYT |
588 | static const struct sun4i_tcon_quirks sun6i_a31_quirks = { |
589 | .has_channel_1 = true, | |
590 | }; | |
591 | ||
592 | static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { | |
593 | .has_channel_1 = true, | |
594 | }; | |
595 | ||
91ea2f29 CYT |
596 | static const struct sun4i_tcon_quirks sun8i_a33_quirks = { |
597 | /* nothing is supported */ | |
598 | }; | |
599 | ||
9026e0d1 | 600 | static const struct of_device_id sun4i_tcon_of_table[] = { |
91ea2f29 | 601 | { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, |
93a5ec14 CYT |
602 | { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, |
603 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, | |
91ea2f29 | 604 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, |
9026e0d1 MR |
605 | { } |
606 | }; | |
607 | MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); | |
608 | ||
609 | static struct platform_driver sun4i_tcon_platform_driver = { | |
610 | .probe = sun4i_tcon_probe, | |
611 | .remove = sun4i_tcon_remove, | |
612 | .driver = { | |
613 | .name = "sun4i-tcon", | |
614 | .of_match_table = sun4i_tcon_of_table, | |
615 | }, | |
616 | }; | |
617 | module_platform_driver(sun4i_tcon_platform_driver); | |
618 | ||
619 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | |
620 | MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); | |
621 | MODULE_LICENSE("GPL"); |