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> | |
29e57fab | 18 | #include <drm/drm_panel.h> |
9026e0d1 MR |
19 | |
20 | #include <linux/component.h> | |
21 | #include <linux/ioport.h> | |
22 | #include <linux/of_address.h> | |
29e57fab | 23 | #include <linux/of_graph.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 MR |
32 | #include "sun4i_tcon.h" |
33 | ||
34 | void sun4i_tcon_disable(struct sun4i_tcon *tcon) | |
35 | { | |
36 | DRM_DEBUG_DRIVER("Disabling TCON\n"); | |
37 | ||
38 | /* Disable the TCON */ | |
39 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
40 | SUN4I_TCON_GCTL_TCON_ENABLE, 0); | |
41 | } | |
42 | EXPORT_SYMBOL(sun4i_tcon_disable); | |
43 | ||
44 | void sun4i_tcon_enable(struct sun4i_tcon *tcon) | |
45 | { | |
46 | DRM_DEBUG_DRIVER("Enabling TCON\n"); | |
47 | ||
48 | /* Enable the TCON */ | |
49 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
50 | SUN4I_TCON_GCTL_TCON_ENABLE, | |
51 | SUN4I_TCON_GCTL_TCON_ENABLE); | |
52 | } | |
53 | EXPORT_SYMBOL(sun4i_tcon_enable); | |
54 | ||
55 | void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) | |
56 | { | |
57 | /* Disable the TCON's channel */ | |
58 | if (channel == 0) { | |
59 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | |
60 | SUN4I_TCON0_CTL_TCON_ENABLE, 0); | |
61 | clk_disable_unprepare(tcon->dclk); | |
62 | } else if (channel == 1) { | |
63 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | |
64 | SUN4I_TCON1_CTL_TCON_ENABLE, 0); | |
65 | clk_disable_unprepare(tcon->sclk1); | |
66 | } | |
67 | } | |
68 | EXPORT_SYMBOL(sun4i_tcon_channel_disable); | |
69 | ||
70 | void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) | |
71 | { | |
72 | /* Enable the TCON's channel */ | |
73 | if (channel == 0) { | |
74 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | |
75 | SUN4I_TCON0_CTL_TCON_ENABLE, | |
76 | SUN4I_TCON0_CTL_TCON_ENABLE); | |
77 | clk_prepare_enable(tcon->dclk); | |
78 | } else if (channel == 1) { | |
79 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | |
80 | SUN4I_TCON1_CTL_TCON_ENABLE, | |
81 | SUN4I_TCON1_CTL_TCON_ENABLE); | |
82 | clk_prepare_enable(tcon->sclk1); | |
83 | } | |
84 | } | |
85 | EXPORT_SYMBOL(sun4i_tcon_channel_enable); | |
86 | ||
87 | void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) | |
88 | { | |
89 | u32 mask, val = 0; | |
90 | ||
91 | DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis"); | |
92 | ||
93 | mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | | |
94 | SUN4I_TCON_GINT0_VBLANK_ENABLE(1); | |
95 | ||
96 | if (enable) | |
97 | val = mask; | |
98 | ||
99 | regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); | |
100 | } | |
101 | EXPORT_SYMBOL(sun4i_tcon_enable_vblank); | |
102 | ||
103 | static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, | |
104 | int channel) | |
105 | { | |
106 | int delay = mode->vtotal - mode->vdisplay; | |
107 | ||
108 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) | |
109 | delay /= 2; | |
110 | ||
111 | if (channel == 1) | |
112 | delay -= 2; | |
113 | ||
114 | delay = min(delay, 30); | |
115 | ||
116 | DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); | |
117 | ||
118 | return delay; | |
119 | } | |
120 | ||
121 | void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, | |
122 | struct drm_display_mode *mode) | |
123 | { | |
124 | unsigned int bp, hsync, vsync; | |
125 | u8 clk_delay; | |
126 | u32 val = 0; | |
127 | ||
128 | /* Adjust clock delay */ | |
129 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); | |
130 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, | |
131 | SUN4I_TCON0_CTL_CLK_DELAY_MASK, | |
132 | SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); | |
133 | ||
134 | /* Set the resolution */ | |
135 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, | |
136 | SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | | |
137 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); | |
138 | ||
139 | /* | |
140 | * This is called a backporch in the register documentation, | |
141 | * but it really is the front porch + hsync | |
142 | */ | |
143 | bp = mode->crtc_htotal - mode->crtc_hsync_start; | |
144 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", | |
145 | mode->crtc_htotal, bp); | |
146 | ||
147 | /* Set horizontal display timings */ | |
148 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, | |
149 | SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | | |
150 | SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); | |
151 | ||
152 | /* | |
153 | * This is called a backporch in the register documentation, | |
154 | * but it really is the front porch + hsync | |
155 | */ | |
156 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; | |
157 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", | |
158 | mode->crtc_vtotal, bp); | |
159 | ||
160 | /* Set vertical display timings */ | |
161 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, | |
162 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) | | |
163 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); | |
164 | ||
165 | /* Set Hsync and Vsync length */ | |
166 | hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; | |
167 | vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; | |
168 | DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); | |
169 | regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, | |
170 | SUN4I_TCON0_BASIC3_V_SYNC(vsync) | | |
171 | SUN4I_TCON0_BASIC3_H_SYNC(hsync)); | |
172 | ||
173 | /* Setup the polarity of the various signals */ | |
174 | if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) | |
175 | val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; | |
176 | ||
177 | if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) | |
178 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; | |
179 | ||
180 | regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, | |
181 | SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, | |
182 | val); | |
183 | ||
184 | /* Map output pins to channel 0 */ | |
185 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
186 | SUN4I_TCON_GCTL_IOMAP_MASK, | |
187 | SUN4I_TCON_GCTL_IOMAP_TCON0); | |
188 | ||
189 | /* Enable the output on the pins */ | |
190 | regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); | |
191 | } | |
192 | EXPORT_SYMBOL(sun4i_tcon0_mode_set); | |
193 | ||
194 | void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, | |
195 | struct drm_display_mode *mode) | |
196 | { | |
197 | unsigned int bp, hsync, vsync; | |
198 | u8 clk_delay; | |
199 | u32 val; | |
200 | ||
201 | /* Adjust clock delay */ | |
202 | clk_delay = sun4i_tcon_get_clk_delay(mode, 1); | |
203 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | |
204 | SUN4I_TCON1_CTL_CLK_DELAY_MASK, | |
205 | SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); | |
206 | ||
207 | /* Set interlaced mode */ | |
208 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) | |
209 | val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; | |
210 | else | |
211 | val = 0; | |
212 | regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, | |
213 | SUN4I_TCON1_CTL_INTERLACE_ENABLE, | |
214 | val); | |
215 | ||
216 | /* Set the input resolution */ | |
217 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, | |
218 | SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | | |
219 | SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); | |
220 | ||
221 | /* Set the upscaling resolution */ | |
222 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, | |
223 | SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | | |
224 | SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); | |
225 | ||
226 | /* Set the output resolution */ | |
227 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, | |
228 | SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | | |
229 | SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); | |
230 | ||
231 | /* Set horizontal display timings */ | |
232 | bp = mode->crtc_htotal - mode->crtc_hsync_end; | |
233 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", | |
234 | mode->htotal, bp); | |
235 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, | |
236 | SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | | |
237 | SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); | |
238 | ||
239 | /* Set vertical display timings */ | |
240 | bp = mode->crtc_vtotal - mode->crtc_vsync_end; | |
241 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", | |
242 | mode->vtotal, bp); | |
243 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, | |
244 | SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) | | |
245 | SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); | |
246 | ||
247 | /* Set Hsync and Vsync length */ | |
248 | hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; | |
249 | vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; | |
250 | DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); | |
251 | regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, | |
252 | SUN4I_TCON1_BASIC5_V_SYNC(vsync) | | |
253 | SUN4I_TCON1_BASIC5_H_SYNC(hsync)); | |
254 | ||
255 | /* Map output pins to channel 1 */ | |
256 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, | |
257 | SUN4I_TCON_GCTL_IOMAP_MASK, | |
258 | SUN4I_TCON_GCTL_IOMAP_TCON1); | |
259 | ||
260 | /* | |
261 | * FIXME: Undocumented bits | |
262 | */ | |
263 | if (tcon->has_mux) | |
264 | regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1); | |
265 | } | |
266 | EXPORT_SYMBOL(sun4i_tcon1_mode_set); | |
267 | ||
268 | static void sun4i_tcon_finish_page_flip(struct drm_device *dev, | |
269 | struct sun4i_crtc *scrtc) | |
270 | { | |
271 | unsigned long flags; | |
272 | ||
273 | spin_lock_irqsave(&dev->event_lock, flags); | |
274 | if (scrtc->event) { | |
275 | drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event); | |
276 | drm_crtc_vblank_put(&scrtc->crtc); | |
277 | scrtc->event = NULL; | |
278 | } | |
279 | spin_unlock_irqrestore(&dev->event_lock, flags); | |
280 | } | |
281 | ||
282 | static irqreturn_t sun4i_tcon_handler(int irq, void *private) | |
283 | { | |
284 | struct sun4i_tcon *tcon = private; | |
285 | struct drm_device *drm = tcon->drm; | |
286 | struct sun4i_drv *drv = drm->dev_private; | |
287 | struct sun4i_crtc *scrtc = drv->crtc; | |
288 | unsigned int status; | |
289 | ||
290 | regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); | |
291 | ||
292 | if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | | |
293 | SUN4I_TCON_GINT0_VBLANK_INT(1)))) | |
294 | return IRQ_NONE; | |
295 | ||
296 | drm_crtc_handle_vblank(&scrtc->crtc); | |
297 | sun4i_tcon_finish_page_flip(drm, scrtc); | |
298 | ||
299 | /* Acknowledge the interrupt */ | |
300 | regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, | |
301 | SUN4I_TCON_GINT0_VBLANK_INT(0) | | |
302 | SUN4I_TCON_GINT0_VBLANK_INT(1), | |
303 | 0); | |
304 | ||
305 | return IRQ_HANDLED; | |
306 | } | |
307 | ||
308 | static int sun4i_tcon_init_clocks(struct device *dev, | |
309 | struct sun4i_tcon *tcon) | |
310 | { | |
311 | tcon->clk = devm_clk_get(dev, "ahb"); | |
312 | if (IS_ERR(tcon->clk)) { | |
313 | dev_err(dev, "Couldn't get the TCON bus clock\n"); | |
314 | return PTR_ERR(tcon->clk); | |
315 | } | |
316 | clk_prepare_enable(tcon->clk); | |
317 | ||
318 | tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); | |
319 | if (IS_ERR(tcon->sclk0)) { | |
320 | dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); | |
321 | return PTR_ERR(tcon->sclk0); | |
322 | } | |
323 | ||
324 | tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); | |
325 | if (IS_ERR(tcon->sclk1)) { | |
326 | dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); | |
327 | return PTR_ERR(tcon->sclk1); | |
328 | } | |
329 | ||
330 | return sun4i_dclk_create(dev, tcon); | |
331 | } | |
332 | ||
333 | static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) | |
334 | { | |
335 | sun4i_dclk_free(tcon); | |
336 | clk_disable_unprepare(tcon->clk); | |
337 | } | |
338 | ||
339 | static int sun4i_tcon_init_irq(struct device *dev, | |
340 | struct sun4i_tcon *tcon) | |
341 | { | |
342 | struct platform_device *pdev = to_platform_device(dev); | |
343 | int irq, ret; | |
344 | ||
345 | irq = platform_get_irq(pdev, 0); | |
346 | if (irq < 0) { | |
347 | dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); | |
348 | return irq; | |
349 | } | |
350 | ||
351 | ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, | |
352 | dev_name(dev), tcon); | |
353 | if (ret) { | |
354 | dev_err(dev, "Couldn't request the IRQ\n"); | |
355 | return ret; | |
356 | } | |
357 | ||
358 | return 0; | |
359 | } | |
360 | ||
361 | static struct regmap_config sun4i_tcon_regmap_config = { | |
362 | .reg_bits = 32, | |
363 | .val_bits = 32, | |
364 | .reg_stride = 4, | |
365 | .max_register = 0x800, | |
366 | }; | |
367 | ||
368 | static int sun4i_tcon_init_regmap(struct device *dev, | |
369 | struct sun4i_tcon *tcon) | |
370 | { | |
371 | struct platform_device *pdev = to_platform_device(dev); | |
372 | struct resource *res; | |
373 | void __iomem *regs; | |
374 | ||
375 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
376 | regs = devm_ioremap_resource(dev, res); | |
377 | if (IS_ERR(regs)) { | |
378 | dev_err(dev, "Couldn't map the TCON registers\n"); | |
379 | return PTR_ERR(regs); | |
380 | } | |
381 | ||
382 | tcon->regs = devm_regmap_init_mmio(dev, regs, | |
383 | &sun4i_tcon_regmap_config); | |
384 | if (IS_ERR(tcon->regs)) { | |
385 | dev_err(dev, "Couldn't create the TCON regmap\n"); | |
386 | return PTR_ERR(tcon->regs); | |
387 | } | |
388 | ||
389 | /* Make sure the TCON is disabled and all IRQs are off */ | |
390 | regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); | |
391 | regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); | |
392 | regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); | |
393 | ||
394 | /* Disable IO lines and set them to tristate */ | |
395 | regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); | |
396 | regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); | |
397 | ||
398 | return 0; | |
399 | } | |
400 | ||
29e57fab MR |
401 | static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) |
402 | { | |
403 | struct device_node *port, *remote, *child; | |
404 | struct device_node *end_node = NULL; | |
405 | ||
406 | /* Inputs are listed first, then outputs */ | |
407 | port = of_graph_get_port_by_id(node, 1); | |
408 | ||
409 | /* | |
410 | * Our first output is the RGB interface where the panel will | |
411 | * be connected. | |
412 | */ | |
413 | for_each_child_of_node(port, child) { | |
414 | u32 reg; | |
415 | ||
416 | of_property_read_u32(child, "reg", ®); | |
417 | if (reg == 0) | |
418 | end_node = child; | |
419 | } | |
420 | ||
421 | if (!end_node) { | |
422 | DRM_DEBUG_DRIVER("Missing panel endpoint\n"); | |
423 | return ERR_PTR(-ENODEV); | |
424 | } | |
425 | ||
426 | remote = of_graph_get_remote_port_parent(end_node); | |
427 | if (!remote) { | |
428 | DRM_DEBUG_DRIVER("Enable to parse remote node\n"); | |
429 | return ERR_PTR(-EINVAL); | |
430 | } | |
431 | ||
432 | return of_drm_find_panel(remote); | |
433 | } | |
434 | ||
9026e0d1 MR |
435 | static int sun4i_tcon_bind(struct device *dev, struct device *master, |
436 | void *data) | |
437 | { | |
438 | struct drm_device *drm = data; | |
439 | struct sun4i_drv *drv = drm->dev_private; | |
440 | struct sun4i_tcon *tcon; | |
441 | int ret; | |
442 | ||
443 | tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); | |
444 | if (!tcon) | |
445 | return -ENOMEM; | |
446 | dev_set_drvdata(dev, tcon); | |
447 | drv->tcon = tcon; | |
448 | tcon->drm = drm; | |
449 | ||
450 | if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon")) | |
451 | tcon->has_mux = true; | |
452 | ||
453 | tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); | |
454 | if (IS_ERR(tcon->lcd_rst)) { | |
455 | dev_err(dev, "Couldn't get our reset line\n"); | |
456 | return PTR_ERR(tcon->lcd_rst); | |
457 | } | |
458 | ||
459 | /* Make sure our TCON is reset */ | |
460 | if (!reset_control_status(tcon->lcd_rst)) | |
461 | reset_control_assert(tcon->lcd_rst); | |
462 | ||
463 | ret = reset_control_deassert(tcon->lcd_rst); | |
464 | if (ret) { | |
465 | dev_err(dev, "Couldn't deassert our reset line\n"); | |
466 | return ret; | |
467 | } | |
468 | ||
469 | ret = sun4i_tcon_init_regmap(dev, tcon); | |
470 | if (ret) { | |
471 | dev_err(dev, "Couldn't init our TCON regmap\n"); | |
472 | goto err_assert_reset; | |
473 | } | |
474 | ||
475 | ret = sun4i_tcon_init_clocks(dev, tcon); | |
476 | if (ret) { | |
477 | dev_err(dev, "Couldn't init our TCON clocks\n"); | |
478 | goto err_assert_reset; | |
479 | } | |
480 | ||
481 | ret = sun4i_tcon_init_irq(dev, tcon); | |
482 | if (ret) { | |
483 | dev_err(dev, "Couldn't init our TCON interrupts\n"); | |
484 | goto err_free_clocks; | |
485 | } | |
486 | ||
29e57fab MR |
487 | tcon->panel = sun4i_tcon_find_panel(dev->of_node); |
488 | if (IS_ERR(tcon->panel)) { | |
489 | dev_info(dev, "No panel found... RGB output disabled\n"); | |
490 | return 0; | |
491 | } | |
492 | ||
493 | return sun4i_rgb_init(drm); | |
9026e0d1 MR |
494 | |
495 | err_free_clocks: | |
496 | sun4i_tcon_free_clocks(tcon); | |
497 | err_assert_reset: | |
498 | reset_control_assert(tcon->lcd_rst); | |
499 | return ret; | |
500 | } | |
501 | ||
502 | static void sun4i_tcon_unbind(struct device *dev, struct device *master, | |
503 | void *data) | |
504 | { | |
505 | struct sun4i_tcon *tcon = dev_get_drvdata(dev); | |
506 | ||
507 | sun4i_tcon_free_clocks(tcon); | |
508 | } | |
509 | ||
510 | static struct component_ops sun4i_tcon_ops = { | |
511 | .bind = sun4i_tcon_bind, | |
512 | .unbind = sun4i_tcon_unbind, | |
513 | }; | |
514 | ||
515 | static int sun4i_tcon_probe(struct platform_device *pdev) | |
516 | { | |
29e57fab MR |
517 | struct device_node *node = pdev->dev.of_node; |
518 | struct drm_panel *panel; | |
519 | ||
520 | /* | |
521 | * The panel is not ready. | |
522 | * Defer the probe. | |
523 | */ | |
524 | panel = sun4i_tcon_find_panel(node); | |
525 | if (IS_ERR(panel)) { | |
526 | /* | |
527 | * If we don't have a panel endpoint, just go on | |
528 | */ | |
529 | if (PTR_ERR(panel) != -ENODEV) | |
530 | return -EPROBE_DEFER; | |
531 | } | |
532 | ||
9026e0d1 MR |
533 | return component_add(&pdev->dev, &sun4i_tcon_ops); |
534 | } | |
535 | ||
536 | static int sun4i_tcon_remove(struct platform_device *pdev) | |
537 | { | |
538 | component_del(&pdev->dev, &sun4i_tcon_ops); | |
539 | ||
540 | return 0; | |
541 | } | |
542 | ||
543 | static const struct of_device_id sun4i_tcon_of_table[] = { | |
544 | { .compatible = "allwinner,sun5i-a13-tcon" }, | |
545 | { } | |
546 | }; | |
547 | MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); | |
548 | ||
549 | static struct platform_driver sun4i_tcon_platform_driver = { | |
550 | .probe = sun4i_tcon_probe, | |
551 | .remove = sun4i_tcon_remove, | |
552 | .driver = { | |
553 | .name = "sun4i-tcon", | |
554 | .of_match_table = sun4i_tcon_of_table, | |
555 | }, | |
556 | }; | |
557 | module_platform_driver(sun4i_tcon_platform_driver); | |
558 | ||
559 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); | |
560 | MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); | |
561 | MODULE_LICENSE("GPL"); |