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