1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2015 Free Electrons |
4 | * Copyright (C) 2015 NextThing Co |
5 | * |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
7 | */ |
8 | |
9 | #include <linux/component.h> |
10 | #include <linux/ioport.h> |
11 | #include <linux/media-bus-format.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_platform.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/reset.h> |
18 | |
19 | #include <drm/drm_atomic_helper.h> |
20 | #include <drm/drm_bridge.h> |
21 | #include <drm/drm_connector.h> |
22 | #include <drm/drm_crtc.h> |
23 | #include <drm/drm_encoder.h> |
24 | #include <drm/drm_modes.h> |
25 | #include <drm/drm_of.h> |
26 | #include <drm/drm_panel.h> |
27 | #include <drm/drm_print.h> |
28 | #include <drm/drm_probe_helper.h> |
29 | #include <drm/drm_vblank.h> |
30 | |
31 | #include <uapi/drm/drm_mode.h> |
32 | |
33 | #include "sun4i_crtc.h" |
34 | #include "sun4i_drv.h" |
35 | #include "sun4i_lvds.h" |
36 | #include "sun4i_rgb.h" |
37 | #include "sun4i_tcon.h" |
38 | #include "sun6i_mipi_dsi.h" |
39 | #include "sun4i_tcon_dclk.h" |
40 | #include "sun8i_tcon_top.h" |
41 | #include "sunxi_engine.h" |
42 | |
43 | static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) |
44 | { |
45 | struct drm_connector *connector; |
46 | struct drm_connector_list_iter iter; |
47 | |
48 | drm_connector_list_iter_begin(dev: encoder->dev, iter: &iter); |
49 | drm_for_each_connector_iter(connector, &iter) |
50 | if (connector->encoder == encoder) { |
51 | drm_connector_list_iter_end(iter: &iter); |
52 | return connector; |
53 | } |
54 | drm_connector_list_iter_end(iter: &iter); |
55 | |
56 | return NULL; |
57 | } |
58 | |
59 | static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder) |
60 | { |
61 | struct drm_connector *connector; |
62 | struct drm_display_info *info; |
63 | |
64 | connector = sun4i_tcon_get_connector(encoder); |
65 | if (!connector) |
66 | return -EINVAL; |
67 | |
68 | info = &connector->display_info; |
69 | if (info->num_bus_formats != 1) |
70 | return -EINVAL; |
71 | |
72 | switch (info->bus_formats[0]) { |
73 | case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: |
74 | return 18; |
75 | |
76 | case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: |
77 | case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: |
78 | return 24; |
79 | } |
80 | |
81 | return -EINVAL; |
82 | } |
83 | |
84 | static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, |
85 | bool enabled) |
86 | { |
87 | struct clk *clk; |
88 | |
89 | switch (channel) { |
90 | case 0: |
91 | WARN_ON(!tcon->quirks->has_channel_0); |
92 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_CTL_REG, |
93 | SUN4I_TCON0_CTL_TCON_ENABLE, |
94 | val: enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0); |
95 | clk = tcon->dclk; |
96 | break; |
97 | case 1: |
98 | WARN_ON(!tcon->quirks->has_channel_1); |
99 | regmap_update_bits(map: tcon->regs, SUN4I_TCON1_CTL_REG, |
100 | SUN4I_TCON1_CTL_TCON_ENABLE, |
101 | val: enabled ? SUN4I_TCON1_CTL_TCON_ENABLE : 0); |
102 | clk = tcon->sclk1; |
103 | break; |
104 | default: |
105 | DRM_WARN("Unknown channel... doing nothing\n" ); |
106 | return; |
107 | } |
108 | |
109 | if (enabled) { |
110 | clk_prepare_enable(clk); |
111 | clk_rate_exclusive_get(clk); |
112 | } else { |
113 | clk_rate_exclusive_put(clk); |
114 | clk_disable_unprepare(clk); |
115 | } |
116 | } |
117 | |
118 | static void sun4i_tcon_setup_lvds_phy(struct sun4i_tcon *tcon, |
119 | const struct drm_encoder *encoder) |
120 | { |
121 | regmap_write(map: tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
122 | SUN4I_TCON0_LVDS_ANA0_CK_EN | |
123 | SUN4I_TCON0_LVDS_ANA0_REG_V | |
124 | SUN4I_TCON0_LVDS_ANA0_REG_C | |
125 | SUN4I_TCON0_LVDS_ANA0_EN_MB | |
126 | SUN4I_TCON0_LVDS_ANA0_PD | |
127 | SUN4I_TCON0_LVDS_ANA0_DCHS); |
128 | |
129 | udelay(2); /* delay at least 1200 ns */ |
130 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG, |
131 | SUN4I_TCON0_LVDS_ANA1_INIT, |
132 | SUN4I_TCON0_LVDS_ANA1_INIT); |
133 | udelay(1); /* delay at least 120 ns */ |
134 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG, |
135 | SUN4I_TCON0_LVDS_ANA1_UPDATE, |
136 | SUN4I_TCON0_LVDS_ANA1_UPDATE); |
137 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
138 | SUN4I_TCON0_LVDS_ANA0_EN_MB, |
139 | SUN4I_TCON0_LVDS_ANA0_EN_MB); |
140 | } |
141 | |
142 | static void sun6i_tcon_setup_lvds_phy(struct sun4i_tcon *tcon, |
143 | const struct drm_encoder *encoder) |
144 | { |
145 | u8 val; |
146 | |
147 | regmap_write(map: tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
148 | SUN6I_TCON0_LVDS_ANA0_C(2) | |
149 | SUN6I_TCON0_LVDS_ANA0_V(3) | |
150 | SUN6I_TCON0_LVDS_ANA0_PD(2) | |
151 | SUN6I_TCON0_LVDS_ANA0_EN_LDO); |
152 | udelay(2); |
153 | |
154 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
155 | SUN6I_TCON0_LVDS_ANA0_EN_MB, |
156 | SUN6I_TCON0_LVDS_ANA0_EN_MB); |
157 | udelay(2); |
158 | |
159 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
160 | SUN6I_TCON0_LVDS_ANA0_EN_DRVC, |
161 | SUN6I_TCON0_LVDS_ANA0_EN_DRVC); |
162 | |
163 | if (sun4i_tcon_get_pixel_depth(encoder) == 18) |
164 | val = 7; |
165 | else |
166 | val = 0xf; |
167 | |
168 | regmap_write_bits(map: tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
169 | SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf), |
170 | SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val)); |
171 | } |
172 | |
173 | static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon, |
174 | const struct drm_encoder *encoder, |
175 | bool enabled) |
176 | { |
177 | if (enabled) { |
178 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_IF_REG, |
179 | SUN4I_TCON0_LVDS_IF_EN, |
180 | SUN4I_TCON0_LVDS_IF_EN); |
181 | if (tcon->quirks->setup_lvds_phy) |
182 | tcon->quirks->setup_lvds_phy(tcon, encoder); |
183 | } else { |
184 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_LVDS_IF_REG, |
185 | SUN4I_TCON0_LVDS_IF_EN, val: 0); |
186 | } |
187 | } |
188 | |
189 | void sun4i_tcon_set_status(struct sun4i_tcon *tcon, |
190 | const struct drm_encoder *encoder, |
191 | bool enabled) |
192 | { |
193 | bool is_lvds = false; |
194 | int channel; |
195 | |
196 | switch (encoder->encoder_type) { |
197 | case DRM_MODE_ENCODER_LVDS: |
198 | is_lvds = true; |
199 | fallthrough; |
200 | case DRM_MODE_ENCODER_DSI: |
201 | case DRM_MODE_ENCODER_NONE: |
202 | channel = 0; |
203 | break; |
204 | case DRM_MODE_ENCODER_TMDS: |
205 | case DRM_MODE_ENCODER_TVDAC: |
206 | channel = 1; |
207 | break; |
208 | default: |
209 | DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n" ); |
210 | return; |
211 | } |
212 | |
213 | if (is_lvds && !enabled) |
214 | sun4i_tcon_lvds_set_status(tcon, encoder, enabled: false); |
215 | |
216 | regmap_update_bits(map: tcon->regs, SUN4I_TCON_GCTL_REG, |
217 | SUN4I_TCON_GCTL_TCON_ENABLE, |
218 | val: enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0); |
219 | |
220 | if (is_lvds && enabled) |
221 | sun4i_tcon_lvds_set_status(tcon, encoder, enabled: true); |
222 | |
223 | sun4i_tcon_channel_set_status(tcon, channel, enabled); |
224 | } |
225 | |
226 | void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) |
227 | { |
228 | u32 mask, val = 0; |
229 | |
230 | DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n" , enable ? "En" : "Dis" ); |
231 | |
232 | mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | |
233 | SUN4I_TCON_GINT0_VBLANK_ENABLE(1) | |
234 | SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE; |
235 | |
236 | if (enable) |
237 | val = mask; |
238 | |
239 | regmap_update_bits(map: tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); |
240 | } |
241 | EXPORT_SYMBOL(sun4i_tcon_enable_vblank); |
242 | |
243 | /* |
244 | * This function is a helper for TCON output muxing. The TCON output |
245 | * muxing control register in earlier SoCs (without the TCON TOP block) |
246 | * are located in TCON0. This helper returns a pointer to TCON0's |
247 | * sun4i_tcon structure, or NULL if not found. |
248 | */ |
249 | static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm) |
250 | { |
251 | struct sun4i_drv *drv = drm->dev_private; |
252 | struct sun4i_tcon *tcon; |
253 | |
254 | list_for_each_entry(tcon, &drv->tcon_list, list) |
255 | if (tcon->id == 0) |
256 | return tcon; |
257 | |
258 | dev_warn(drm->dev, |
259 | "TCON0 not found, display output muxing may not work\n" ); |
260 | |
261 | return NULL; |
262 | } |
263 | |
264 | static void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, |
265 | const struct drm_encoder *encoder) |
266 | { |
267 | int ret = -ENOTSUPP; |
268 | |
269 | if (tcon->quirks->set_mux) |
270 | ret = tcon->quirks->set_mux(tcon, encoder); |
271 | |
272 | DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n" , |
273 | encoder->name, encoder->crtc->name, ret); |
274 | } |
275 | |
276 | static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, |
277 | int channel) |
278 | { |
279 | int delay = mode->vtotal - mode->vdisplay; |
280 | |
281 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
282 | delay /= 2; |
283 | |
284 | if (channel == 1) |
285 | delay -= 2; |
286 | |
287 | delay = min(delay, 30); |
288 | |
289 | DRM_DEBUG_DRIVER("TCON %d clock delay %u\n" , channel, delay); |
290 | |
291 | return delay; |
292 | } |
293 | |
294 | static void sun4i_tcon0_mode_set_dithering(struct sun4i_tcon *tcon, |
295 | const struct drm_connector *connector) |
296 | { |
297 | u32 bus_format = 0; |
298 | u32 val = 0; |
299 | |
300 | /* XXX Would this ever happen? */ |
301 | if (!connector) |
302 | return; |
303 | |
304 | /* |
305 | * FIXME: Undocumented bits |
306 | * |
307 | * The whole dithering process and these parameters are not |
308 | * explained in the vendor documents or BSP kernel code. |
309 | */ |
310 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_SEED_PR_REG, val: 0x11111111); |
311 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_SEED_PG_REG, val: 0x11111111); |
312 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_SEED_PB_REG, val: 0x11111111); |
313 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_SEED_LR_REG, val: 0x11111111); |
314 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_SEED_LG_REG, val: 0x11111111); |
315 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_SEED_LB_REG, val: 0x11111111); |
316 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_TBL0_REG, val: 0x01010000); |
317 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_TBL1_REG, val: 0x15151111); |
318 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_TBL2_REG, val: 0x57575555); |
319 | regmap_write(map: tcon->regs, SUN4I_TCON0_FRM_TBL3_REG, val: 0x7f7f7777); |
320 | |
321 | /* Do dithering if panel only supports 6 bits per color */ |
322 | if (connector->display_info.bpc == 6) |
323 | val |= SUN4I_TCON0_FRM_CTL_EN; |
324 | |
325 | if (connector->display_info.num_bus_formats == 1) |
326 | bus_format = connector->display_info.bus_formats[0]; |
327 | |
328 | /* Check the connection format */ |
329 | switch (bus_format) { |
330 | case MEDIA_BUS_FMT_RGB565_1X16: |
331 | /* R and B components are only 5 bits deep */ |
332 | val |= SUN4I_TCON0_FRM_CTL_MODE_R; |
333 | val |= SUN4I_TCON0_FRM_CTL_MODE_B; |
334 | fallthrough; |
335 | case MEDIA_BUS_FMT_RGB666_1X18: |
336 | case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: |
337 | /* Fall through: enable dithering */ |
338 | val |= SUN4I_TCON0_FRM_CTL_EN; |
339 | break; |
340 | } |
341 | |
342 | /* Write dithering settings */ |
343 | regmap_write(map: tcon->regs, SUN4I_TCON_FRM_CTL_REG, val); |
344 | } |
345 | |
346 | static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon *tcon, |
347 | const struct drm_encoder *encoder, |
348 | const struct drm_display_mode *mode) |
349 | { |
350 | /* TODO support normal CPU interface modes */ |
351 | struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder); |
352 | struct mipi_dsi_device *device = dsi->device; |
353 | u8 bpp = mipi_dsi_pixel_format_to_bpp(fmt: device->format); |
354 | u8 lanes = device->lanes; |
355 | u32 block_space, start_delay; |
356 | u32 tcon_div; |
357 | |
358 | /* |
359 | * dclk is required to run at 1/4 the DSI per-lane bit rate. |
360 | */ |
361 | tcon->dclk_min_div = SUN6I_DSI_TCON_DIV; |
362 | tcon->dclk_max_div = SUN6I_DSI_TCON_DIV; |
363 | clk_set_rate(clk: tcon->dclk, rate: mode->crtc_clock * 1000 * (bpp / lanes) |
364 | / SUN6I_DSI_TCON_DIV); |
365 | |
366 | /* Set the resolution */ |
367 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC0_REG, |
368 | SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | |
369 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); |
370 | |
371 | /* Set dithering if needed */ |
372 | sun4i_tcon0_mode_set_dithering(tcon, connector: sun4i_tcon_get_connector(encoder)); |
373 | |
374 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_CTL_REG, |
375 | SUN4I_TCON0_CTL_IF_MASK, |
376 | SUN4I_TCON0_CTL_IF_8080); |
377 | |
378 | regmap_write(map: tcon->regs, SUN4I_TCON_ECC_FIFO_REG, |
379 | SUN4I_TCON_ECC_FIFO_EN); |
380 | |
381 | regmap_write(map: tcon->regs, SUN4I_TCON0_CPU_IF_REG, |
382 | SUN4I_TCON0_CPU_IF_MODE_DSI | |
383 | SUN4I_TCON0_CPU_IF_TRI_FIFO_FLUSH | |
384 | SUN4I_TCON0_CPU_IF_TRI_FIFO_EN | |
385 | SUN4I_TCON0_CPU_IF_TRI_EN); |
386 | |
387 | /* |
388 | * This looks suspicious, but it works... |
389 | * |
390 | * The datasheet says that this should be set higher than 20 * |
391 | * pixel cycle, but it's not clear what a pixel cycle is. |
392 | */ |
393 | regmap_read(map: tcon->regs, SUN4I_TCON0_DCLK_REG, val: &tcon_div); |
394 | tcon_div &= GENMASK(6, 0); |
395 | block_space = mode->htotal * bpp / (tcon_div * lanes); |
396 | block_space -= mode->hdisplay + 40; |
397 | |
398 | regmap_write(map: tcon->regs, SUN4I_TCON0_CPU_TRI0_REG, |
399 | SUN4I_TCON0_CPU_TRI0_BLOCK_SPACE(block_space) | |
400 | SUN4I_TCON0_CPU_TRI0_BLOCK_SIZE(mode->hdisplay)); |
401 | |
402 | regmap_write(map: tcon->regs, SUN4I_TCON0_CPU_TRI1_REG, |
403 | SUN4I_TCON0_CPU_TRI1_BLOCK_NUM(mode->vdisplay)); |
404 | |
405 | start_delay = (mode->crtc_vtotal - mode->crtc_vdisplay - 10 - 1); |
406 | start_delay = start_delay * mode->crtc_htotal * 149; |
407 | start_delay = start_delay / (mode->crtc_clock / 1000) / 8; |
408 | regmap_write(map: tcon->regs, SUN4I_TCON0_CPU_TRI2_REG, |
409 | SUN4I_TCON0_CPU_TRI2_TRANS_START_SET(10) | |
410 | SUN4I_TCON0_CPU_TRI2_START_DELAY(start_delay)); |
411 | |
412 | /* |
413 | * The Allwinner BSP has a comment that the period should be |
414 | * the display clock * 15, but uses an hardcoded 3000... |
415 | */ |
416 | regmap_write(map: tcon->regs, SUN4I_TCON_SAFE_PERIOD_REG, |
417 | SUN4I_TCON_SAFE_PERIOD_NUM(3000) | |
418 | SUN4I_TCON_SAFE_PERIOD_MODE(3)); |
419 | |
420 | /* Enable the output on the pins */ |
421 | regmap_write(map: tcon->regs, SUN4I_TCON0_IO_TRI_REG, |
422 | val: 0xe0000000); |
423 | } |
424 | |
425 | static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, |
426 | const struct drm_encoder *encoder, |
427 | const struct drm_display_mode *mode) |
428 | { |
429 | unsigned int bp; |
430 | u8 clk_delay; |
431 | u32 reg, val = 0; |
432 | |
433 | WARN_ON(!tcon->quirks->has_channel_0); |
434 | |
435 | tcon->dclk_min_div = 7; |
436 | tcon->dclk_max_div = 7; |
437 | clk_set_rate(clk: tcon->dclk, rate: mode->crtc_clock * 1000); |
438 | |
439 | /* Set the resolution */ |
440 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC0_REG, |
441 | SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | |
442 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); |
443 | |
444 | /* Set dithering if needed */ |
445 | sun4i_tcon0_mode_set_dithering(tcon, connector: sun4i_tcon_get_connector(encoder)); |
446 | |
447 | /* Adjust clock delay */ |
448 | clk_delay = sun4i_tcon_get_clk_delay(mode, channel: 0); |
449 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_CTL_REG, |
450 | SUN4I_TCON0_CTL_CLK_DELAY_MASK, |
451 | SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); |
452 | |
453 | /* |
454 | * This is called a backporch in the register documentation, |
455 | * but it really is the back porch + hsync |
456 | */ |
457 | bp = mode->crtc_htotal - mode->crtc_hsync_start; |
458 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n" , |
459 | mode->crtc_htotal, bp); |
460 | |
461 | /* Set horizontal display timings */ |
462 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC1_REG, |
463 | SUN4I_TCON0_BASIC1_H_TOTAL(mode->htotal) | |
464 | SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); |
465 | |
466 | /* |
467 | * This is called a backporch in the register documentation, |
468 | * but it really is the back porch + hsync |
469 | */ |
470 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; |
471 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n" , |
472 | mode->crtc_vtotal, bp); |
473 | |
474 | /* Set vertical display timings */ |
475 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC2_REG, |
476 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | |
477 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); |
478 | |
479 | reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0; |
480 | if (sun4i_tcon_get_pixel_depth(encoder) == 24) |
481 | reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS; |
482 | else |
483 | reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS; |
484 | |
485 | regmap_write(map: tcon->regs, SUN4I_TCON0_LVDS_IF_REG, val: reg); |
486 | |
487 | /* Setup the polarity of the various signals */ |
488 | if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) |
489 | val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; |
490 | |
491 | if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) |
492 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; |
493 | |
494 | regmap_write(map: tcon->regs, SUN4I_TCON0_IO_POL_REG, val); |
495 | |
496 | /* Map output pins to channel 0 */ |
497 | regmap_update_bits(map: tcon->regs, SUN4I_TCON_GCTL_REG, |
498 | SUN4I_TCON_GCTL_IOMAP_MASK, |
499 | SUN4I_TCON_GCTL_IOMAP_TCON0); |
500 | |
501 | /* Enable the output on the pins */ |
502 | regmap_write(map: tcon->regs, SUN4I_TCON0_IO_TRI_REG, val: 0xe0000000); |
503 | } |
504 | |
505 | static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, |
506 | const struct drm_encoder *encoder, |
507 | const struct drm_display_mode *mode) |
508 | { |
509 | struct drm_connector *connector = sun4i_tcon_get_connector(encoder); |
510 | const struct drm_display_info *info = &connector->display_info; |
511 | unsigned int bp, hsync, vsync; |
512 | u8 clk_delay; |
513 | u32 val = 0; |
514 | |
515 | WARN_ON(!tcon->quirks->has_channel_0); |
516 | |
517 | tcon->dclk_min_div = tcon->quirks->dclk_min_div; |
518 | tcon->dclk_max_div = 127; |
519 | clk_set_rate(clk: tcon->dclk, rate: mode->crtc_clock * 1000); |
520 | |
521 | /* Set the resolution */ |
522 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC0_REG, |
523 | SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | |
524 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); |
525 | |
526 | /* Set dithering if needed */ |
527 | sun4i_tcon0_mode_set_dithering(tcon, connector); |
528 | |
529 | /* Adjust clock delay */ |
530 | clk_delay = sun4i_tcon_get_clk_delay(mode, channel: 0); |
531 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_CTL_REG, |
532 | SUN4I_TCON0_CTL_CLK_DELAY_MASK, |
533 | SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); |
534 | |
535 | /* |
536 | * This is called a backporch in the register documentation, |
537 | * but it really is the back porch + hsync |
538 | */ |
539 | bp = mode->crtc_htotal - mode->crtc_hsync_start; |
540 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n" , |
541 | mode->crtc_htotal, bp); |
542 | |
543 | /* Set horizontal display timings */ |
544 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC1_REG, |
545 | SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | |
546 | SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); |
547 | |
548 | /* |
549 | * This is called a backporch in the register documentation, |
550 | * but it really is the back porch + hsync |
551 | */ |
552 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; |
553 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n" , |
554 | mode->crtc_vtotal, bp); |
555 | |
556 | /* Set vertical display timings */ |
557 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC2_REG, |
558 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | |
559 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); |
560 | |
561 | /* Set Hsync and Vsync length */ |
562 | hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; |
563 | vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; |
564 | DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n" , hsync, vsync); |
565 | regmap_write(map: tcon->regs, SUN4I_TCON0_BASIC3_REG, |
566 | SUN4I_TCON0_BASIC3_V_SYNC(vsync) | |
567 | SUN4I_TCON0_BASIC3_H_SYNC(hsync)); |
568 | |
569 | /* Setup the polarity of the various signals */ |
570 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
571 | val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; |
572 | |
573 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
574 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; |
575 | |
576 | if (info->bus_flags & DRM_BUS_FLAG_DE_LOW) |
577 | val |= SUN4I_TCON0_IO_POL_DE_NEGATIVE; |
578 | |
579 | if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) |
580 | val |= SUN4I_TCON0_IO_POL_DCLK_DRIVE_NEGEDGE; |
581 | |
582 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_IO_POL_REG, |
583 | SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | |
584 | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE | |
585 | SUN4I_TCON0_IO_POL_DCLK_DRIVE_NEGEDGE | |
586 | SUN4I_TCON0_IO_POL_DE_NEGATIVE, |
587 | val); |
588 | |
589 | /* Map output pins to channel 0 */ |
590 | regmap_update_bits(map: tcon->regs, SUN4I_TCON_GCTL_REG, |
591 | SUN4I_TCON_GCTL_IOMAP_MASK, |
592 | SUN4I_TCON_GCTL_IOMAP_TCON0); |
593 | |
594 | /* Enable the output on the pins */ |
595 | regmap_write(map: tcon->regs, SUN4I_TCON0_IO_TRI_REG, val: 0); |
596 | } |
597 | |
598 | static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, |
599 | const struct drm_display_mode *mode) |
600 | { |
601 | unsigned int bp, hsync, vsync, vtotal; |
602 | u8 clk_delay; |
603 | u32 val; |
604 | |
605 | WARN_ON(!tcon->quirks->has_channel_1); |
606 | |
607 | /* Configure the dot clock */ |
608 | clk_set_rate(clk: tcon->sclk1, rate: mode->crtc_clock * 1000); |
609 | |
610 | /* Adjust clock delay */ |
611 | clk_delay = sun4i_tcon_get_clk_delay(mode, channel: 1); |
612 | regmap_update_bits(map: tcon->regs, SUN4I_TCON1_CTL_REG, |
613 | SUN4I_TCON1_CTL_CLK_DELAY_MASK, |
614 | SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); |
615 | |
616 | /* Set interlaced mode */ |
617 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
618 | val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; |
619 | else |
620 | val = 0; |
621 | regmap_update_bits(map: tcon->regs, SUN4I_TCON1_CTL_REG, |
622 | SUN4I_TCON1_CTL_INTERLACE_ENABLE, |
623 | val); |
624 | |
625 | /* Set the input resolution */ |
626 | regmap_write(map: tcon->regs, SUN4I_TCON1_BASIC0_REG, |
627 | SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | |
628 | SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); |
629 | |
630 | /* Set the upscaling resolution */ |
631 | regmap_write(map: tcon->regs, SUN4I_TCON1_BASIC1_REG, |
632 | SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | |
633 | SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); |
634 | |
635 | /* Set the output resolution */ |
636 | regmap_write(map: tcon->regs, SUN4I_TCON1_BASIC2_REG, |
637 | SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | |
638 | SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); |
639 | |
640 | /* Set horizontal display timings */ |
641 | bp = mode->crtc_htotal - mode->crtc_hsync_start; |
642 | DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n" , |
643 | mode->htotal, bp); |
644 | regmap_write(map: tcon->regs, SUN4I_TCON1_BASIC3_REG, |
645 | SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | |
646 | SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); |
647 | |
648 | bp = mode->crtc_vtotal - mode->crtc_vsync_start; |
649 | DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n" , |
650 | mode->crtc_vtotal, bp); |
651 | |
652 | /* |
653 | * The vertical resolution needs to be doubled in all |
654 | * cases. We could use crtc_vtotal and always multiply by two, |
655 | * but that leads to a rounding error in interlace when vtotal |
656 | * is odd. |
657 | * |
658 | * This happens with TV's PAL for example, where vtotal will |
659 | * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be |
660 | * 624, which apparently confuses the hardware. |
661 | * |
662 | * To work around this, we will always use vtotal, and |
663 | * multiply by two only if we're not in interlace. |
664 | */ |
665 | vtotal = mode->vtotal; |
666 | if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) |
667 | vtotal = vtotal * 2; |
668 | |
669 | /* Set vertical display timings */ |
670 | regmap_write(map: tcon->regs, SUN4I_TCON1_BASIC4_REG, |
671 | SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | |
672 | SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); |
673 | |
674 | /* Set Hsync and Vsync length */ |
675 | hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; |
676 | vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; |
677 | DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n" , hsync, vsync); |
678 | regmap_write(map: tcon->regs, SUN4I_TCON1_BASIC5_REG, |
679 | SUN4I_TCON1_BASIC5_V_SYNC(vsync) | |
680 | SUN4I_TCON1_BASIC5_H_SYNC(hsync)); |
681 | |
682 | /* Setup the polarity of multiple signals */ |
683 | if (tcon->quirks->polarity_in_ch0) { |
684 | val = 0; |
685 | |
686 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
687 | val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; |
688 | |
689 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
690 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; |
691 | |
692 | regmap_write(map: tcon->regs, SUN4I_TCON0_IO_POL_REG, val); |
693 | } else { |
694 | /* according to vendor driver, this bit must be always set */ |
695 | val = SUN4I_TCON1_IO_POL_UNKNOWN; |
696 | |
697 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
698 | val |= SUN4I_TCON1_IO_POL_HSYNC_POSITIVE; |
699 | |
700 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
701 | val |= SUN4I_TCON1_IO_POL_VSYNC_POSITIVE; |
702 | |
703 | regmap_write(map: tcon->regs, SUN4I_TCON1_IO_POL_REG, val); |
704 | } |
705 | |
706 | /* Map output pins to channel 1 */ |
707 | regmap_update_bits(map: tcon->regs, SUN4I_TCON_GCTL_REG, |
708 | SUN4I_TCON_GCTL_IOMAP_MASK, |
709 | SUN4I_TCON_GCTL_IOMAP_TCON1); |
710 | } |
711 | |
712 | void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, |
713 | const struct drm_encoder *encoder, |
714 | const struct drm_display_mode *mode) |
715 | { |
716 | switch (encoder->encoder_type) { |
717 | case DRM_MODE_ENCODER_DSI: |
718 | /* DSI is tied to special case of CPU interface */ |
719 | sun4i_tcon0_mode_set_cpu(tcon, encoder, mode); |
720 | break; |
721 | case DRM_MODE_ENCODER_LVDS: |
722 | sun4i_tcon0_mode_set_lvds(tcon, encoder, mode); |
723 | break; |
724 | case DRM_MODE_ENCODER_NONE: |
725 | sun4i_tcon0_mode_set_rgb(tcon, encoder, mode); |
726 | sun4i_tcon_set_mux(tcon, channel: 0, encoder); |
727 | break; |
728 | case DRM_MODE_ENCODER_TVDAC: |
729 | case DRM_MODE_ENCODER_TMDS: |
730 | sun4i_tcon1_mode_set(tcon, mode); |
731 | sun4i_tcon_set_mux(tcon, channel: 1, encoder); |
732 | break; |
733 | default: |
734 | DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n" ); |
735 | } |
736 | } |
737 | EXPORT_SYMBOL(sun4i_tcon_mode_set); |
738 | |
739 | static void sun4i_tcon_finish_page_flip(struct drm_device *dev, |
740 | struct sun4i_crtc *scrtc) |
741 | { |
742 | unsigned long flags; |
743 | |
744 | spin_lock_irqsave(&dev->event_lock, flags); |
745 | if (scrtc->event) { |
746 | drm_crtc_send_vblank_event(crtc: &scrtc->crtc, e: scrtc->event); |
747 | drm_crtc_vblank_put(crtc: &scrtc->crtc); |
748 | scrtc->event = NULL; |
749 | } |
750 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
751 | } |
752 | |
753 | static irqreturn_t sun4i_tcon_handler(int irq, void *private) |
754 | { |
755 | struct sun4i_tcon *tcon = private; |
756 | struct drm_device *drm = tcon->drm; |
757 | struct sun4i_crtc *scrtc = tcon->crtc; |
758 | struct sunxi_engine *engine = scrtc->engine; |
759 | unsigned int status; |
760 | |
761 | regmap_read(map: tcon->regs, SUN4I_TCON_GINT0_REG, val: &status); |
762 | |
763 | if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | |
764 | SUN4I_TCON_GINT0_VBLANK_INT(1) | |
765 | SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT))) |
766 | return IRQ_NONE; |
767 | |
768 | drm_crtc_handle_vblank(crtc: &scrtc->crtc); |
769 | sun4i_tcon_finish_page_flip(dev: drm, scrtc); |
770 | |
771 | /* Acknowledge the interrupt */ |
772 | regmap_update_bits(map: tcon->regs, SUN4I_TCON_GINT0_REG, |
773 | SUN4I_TCON_GINT0_VBLANK_INT(0) | |
774 | SUN4I_TCON_GINT0_VBLANK_INT(1) | |
775 | SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT, |
776 | val: 0); |
777 | |
778 | if (engine->ops->vblank_quirk) |
779 | engine->ops->vblank_quirk(engine); |
780 | |
781 | return IRQ_HANDLED; |
782 | } |
783 | |
784 | static int sun4i_tcon_init_clocks(struct device *dev, |
785 | struct sun4i_tcon *tcon) |
786 | { |
787 | tcon->clk = devm_clk_get_enabled(dev, id: "ahb" ); |
788 | if (IS_ERR(ptr: tcon->clk)) { |
789 | dev_err(dev, "Couldn't get the TCON bus clock\n" ); |
790 | return PTR_ERR(ptr: tcon->clk); |
791 | } |
792 | |
793 | if (tcon->quirks->has_channel_0) { |
794 | tcon->sclk0 = devm_clk_get_enabled(dev, id: "tcon-ch0" ); |
795 | if (IS_ERR(ptr: tcon->sclk0)) { |
796 | dev_err(dev, "Couldn't get the TCON channel 0 clock\n" ); |
797 | return PTR_ERR(ptr: tcon->sclk0); |
798 | } |
799 | } |
800 | |
801 | if (tcon->quirks->has_channel_1) { |
802 | tcon->sclk1 = devm_clk_get(dev, id: "tcon-ch1" ); |
803 | if (IS_ERR(ptr: tcon->sclk1)) { |
804 | dev_err(dev, "Couldn't get the TCON channel 1 clock\n" ); |
805 | return PTR_ERR(ptr: tcon->sclk1); |
806 | } |
807 | } |
808 | |
809 | return 0; |
810 | } |
811 | |
812 | static int sun4i_tcon_init_irq(struct device *dev, |
813 | struct sun4i_tcon *tcon) |
814 | { |
815 | struct platform_device *pdev = to_platform_device(dev); |
816 | int irq, ret; |
817 | |
818 | irq = platform_get_irq(pdev, 0); |
819 | if (irq < 0) |
820 | return irq; |
821 | |
822 | ret = devm_request_irq(dev, irq, handler: sun4i_tcon_handler, irqflags: 0, |
823 | devname: dev_name(dev), dev_id: tcon); |
824 | if (ret) { |
825 | dev_err(dev, "Couldn't request the IRQ\n" ); |
826 | return ret; |
827 | } |
828 | |
829 | return 0; |
830 | } |
831 | |
832 | static const struct regmap_config sun4i_tcon_regmap_config = { |
833 | .reg_bits = 32, |
834 | .val_bits = 32, |
835 | .reg_stride = 4, |
836 | .max_register = 0x800, |
837 | }; |
838 | |
839 | static int sun4i_tcon_init_regmap(struct device *dev, |
840 | struct sun4i_tcon *tcon) |
841 | { |
842 | struct platform_device *pdev = to_platform_device(dev); |
843 | void __iomem *regs; |
844 | |
845 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
846 | if (IS_ERR(ptr: regs)) |
847 | return PTR_ERR(ptr: regs); |
848 | |
849 | tcon->regs = devm_regmap_init_mmio(dev, regs, |
850 | &sun4i_tcon_regmap_config); |
851 | if (IS_ERR(ptr: tcon->regs)) { |
852 | dev_err(dev, "Couldn't create the TCON regmap\n" ); |
853 | return PTR_ERR(ptr: tcon->regs); |
854 | } |
855 | |
856 | /* Make sure the TCON is disabled and all IRQs are off */ |
857 | regmap_write(map: tcon->regs, SUN4I_TCON_GCTL_REG, val: 0); |
858 | regmap_write(map: tcon->regs, SUN4I_TCON_GINT0_REG, val: 0); |
859 | regmap_write(map: tcon->regs, SUN4I_TCON_GINT1_REG, val: 0); |
860 | |
861 | /* Disable IO lines and set them to tristate */ |
862 | regmap_write(map: tcon->regs, SUN4I_TCON0_IO_TRI_REG, val: ~0); |
863 | regmap_write(map: tcon->regs, SUN4I_TCON1_IO_TRI_REG, val: ~0); |
864 | |
865 | return 0; |
866 | } |
867 | |
868 | /* |
869 | * On SoCs with the old display pipeline design (Display Engine 1.0), |
870 | * the TCON is always tied to just one backend. Hence we can traverse |
871 | * the of_graph upwards to find the backend our tcon is connected to, |
872 | * and take its ID as our own. |
873 | * |
874 | * We can either identify backends from their compatible strings, which |
875 | * means maintaining a large list of them. Or, since the backend is |
876 | * registered and binded before the TCON, we can just go through the |
877 | * list of registered backends and compare the device node. |
878 | * |
879 | * As the structures now store engines instead of backends, here this |
880 | * function in fact searches the corresponding engine, and the ID is |
881 | * requested via the get_id function of the engine. |
882 | */ |
883 | static struct sunxi_engine * |
884 | sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, |
885 | struct device_node *node, |
886 | u32 port_id) |
887 | { |
888 | struct device_node *port, *ep, *remote; |
889 | struct sunxi_engine *engine = ERR_PTR(error: -EINVAL); |
890 | u32 reg = 0; |
891 | |
892 | port = of_graph_get_port_by_id(node, id: port_id); |
893 | if (!port) |
894 | return ERR_PTR(error: -EINVAL); |
895 | |
896 | /* |
897 | * This only works if there is only one path from the TCON |
898 | * to any display engine. Otherwise the probe order of the |
899 | * TCONs and display engines is not guaranteed. They may |
900 | * either bind to the wrong one, or worse, bind to the same |
901 | * one if additional checks are not done. |
902 | * |
903 | * Bail out if there are multiple input connections. |
904 | */ |
905 | if (of_get_available_child_count(np: port) != 1) |
906 | goto out_put_port; |
907 | |
908 | /* Get the first connection without specifying an ID */ |
909 | ep = of_get_next_available_child(node: port, NULL); |
910 | if (!ep) |
911 | goto out_put_port; |
912 | |
913 | remote = of_graph_get_remote_port_parent(node: ep); |
914 | if (!remote) |
915 | goto out_put_ep; |
916 | |
917 | /* does this node match any registered engines? */ |
918 | list_for_each_entry(engine, &drv->engine_list, list) |
919 | if (remote == engine->node) |
920 | goto out_put_remote; |
921 | |
922 | /* |
923 | * According to device tree binding input ports have even id |
924 | * number and output ports have odd id. Since component with |
925 | * more than one input and one output (TCON TOP) exits, correct |
926 | * remote input id has to be calculated by subtracting 1 from |
927 | * remote output id. If this for some reason can't be done, 0 |
928 | * is used as input port id. |
929 | */ |
930 | of_node_put(node: port); |
931 | port = of_graph_get_remote_port(node: ep); |
932 | if (!of_property_read_u32(np: port, propname: "reg" , out_value: ®) && reg > 0) |
933 | reg -= 1; |
934 | |
935 | /* keep looking through upstream ports */ |
936 | engine = sun4i_tcon_find_engine_traverse(drv, node: remote, port_id: reg); |
937 | |
938 | out_put_remote: |
939 | of_node_put(node: remote); |
940 | out_put_ep: |
941 | of_node_put(node: ep); |
942 | out_put_port: |
943 | of_node_put(node: port); |
944 | |
945 | return engine; |
946 | } |
947 | |
948 | /* |
949 | * The device tree binding says that the remote endpoint ID of any |
950 | * connection between components, up to and including the TCON, of |
951 | * the display pipeline should be equal to the actual ID of the local |
952 | * component. Thus we can look at any one of the input connections of |
953 | * the TCONs, and use that connection's remote endpoint ID as our own. |
954 | * |
955 | * Since the user of this function already finds the input port, |
956 | * the port is passed in directly without further checks. |
957 | */ |
958 | static int sun4i_tcon_of_get_id_from_port(struct device_node *port) |
959 | { |
960 | struct device_node *ep; |
961 | int ret = -EINVAL; |
962 | |
963 | /* try finding an upstream endpoint */ |
964 | for_each_available_child_of_node(port, ep) { |
965 | struct device_node *remote; |
966 | u32 reg; |
967 | |
968 | remote = of_graph_get_remote_endpoint(node: ep); |
969 | if (!remote) |
970 | continue; |
971 | |
972 | ret = of_property_read_u32(np: remote, propname: "reg" , out_value: ®); |
973 | if (ret) |
974 | continue; |
975 | |
976 | ret = reg; |
977 | } |
978 | |
979 | return ret; |
980 | } |
981 | |
982 | /* |
983 | * Once we know the TCON's id, we can look through the list of |
984 | * engines to find a matching one. We assume all engines have |
985 | * been probed and added to the list. |
986 | */ |
987 | static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, |
988 | int id) |
989 | { |
990 | struct sunxi_engine *engine; |
991 | |
992 | list_for_each_entry(engine, &drv->engine_list, list) |
993 | if (engine->id == id) |
994 | return engine; |
995 | |
996 | return ERR_PTR(error: -EINVAL); |
997 | } |
998 | |
999 | static bool sun4i_tcon_connected_to_tcon_top(struct device_node *node) |
1000 | { |
1001 | struct device_node *remote; |
1002 | bool ret = false; |
1003 | |
1004 | remote = of_graph_get_remote_node(node, port: 0, endpoint: -1); |
1005 | if (remote) { |
1006 | ret = !!(IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
1007 | of_match_node(matches: sun8i_tcon_top_of_table, node: remote)); |
1008 | of_node_put(node: remote); |
1009 | } |
1010 | |
1011 | return ret; |
1012 | } |
1013 | |
1014 | static int sun4i_tcon_get_index(struct sun4i_drv *drv) |
1015 | { |
1016 | struct list_head *pos; |
1017 | int size = 0; |
1018 | |
1019 | /* |
1020 | * Because TCON is added to the list at the end of the probe |
1021 | * (after this function is called), index of the current TCON |
1022 | * will be same as current TCON list size. |
1023 | */ |
1024 | list_for_each(pos, &drv->tcon_list) |
1025 | ++size; |
1026 | |
1027 | return size; |
1028 | } |
1029 | |
1030 | /* |
1031 | * On SoCs with the old display pipeline design (Display Engine 1.0), |
1032 | * we assumed the TCON was always tied to just one backend. However |
1033 | * this proved not to be the case. On the A31, the TCON can select |
1034 | * either backend as its source. On the A20 (and likely on the A10), |
1035 | * the backend can choose which TCON to output to. |
1036 | * |
1037 | * The device tree binding says that the remote endpoint ID of any |
1038 | * connection between components, up to and including the TCON, of |
1039 | * the display pipeline should be equal to the actual ID of the local |
1040 | * component. Thus we should be able to look at any one of the input |
1041 | * connections of the TCONs, and use that connection's remote endpoint |
1042 | * ID as our own. |
1043 | * |
1044 | * However the connections between the backend and TCON were assumed |
1045 | * to be always singular, and their endpoit IDs were all incorrectly |
1046 | * set to 0. This means for these old device trees, we cannot just look |
1047 | * up the remote endpoint ID of a TCON input endpoint. TCON1 would be |
1048 | * incorrectly identified as TCON0. |
1049 | * |
1050 | * This function first checks if the TCON node has 2 input endpoints. |
1051 | * If so, then the device tree is a corrected version, and it will use |
1052 | * sun4i_tcon_of_get_id() and sun4i_tcon_get_engine_by_id() from above |
1053 | * to fetch the ID and engine directly. If not, then it is likely an |
1054 | * old device trees, where the endpoint IDs were incorrect, but did not |
1055 | * have endpoint connections between the backend and TCON across |
1056 | * different display pipelines. It will fall back to the old method of |
1057 | * traversing the of_graph to try and find a matching engine by device |
1058 | * node. |
1059 | * |
1060 | * In the case of single display pipeline device trees, either method |
1061 | * works. |
1062 | */ |
1063 | static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, |
1064 | struct device_node *node) |
1065 | { |
1066 | struct device_node *port; |
1067 | struct sunxi_engine *engine; |
1068 | |
1069 | port = of_graph_get_port_by_id(node, id: 0); |
1070 | if (!port) |
1071 | return ERR_PTR(error: -EINVAL); |
1072 | |
1073 | /* |
1074 | * Is this a corrected device tree with cross pipeline |
1075 | * connections between the backend and TCON? |
1076 | */ |
1077 | if (of_get_child_count(np: port) > 1) { |
1078 | int id; |
1079 | |
1080 | /* |
1081 | * When pipeline has the same number of TCONs and engines which |
1082 | * are represented by frontends/backends (DE1) or mixers (DE2), |
1083 | * we match them by their respective IDs. However, if pipeline |
1084 | * contains TCON TOP, chances are that there are either more |
1085 | * TCONs than engines (R40) or TCONs with non-consecutive ids. |
1086 | * (H6). In that case it's easier just use TCON index in list |
1087 | * as an id. That means that on R40, any 2 TCONs can be enabled |
1088 | * in DT out of 4 (there are 2 mixers). Due to the design of |
1089 | * TCON TOP, remaining 2 TCONs can't be connected to anything |
1090 | * anyway. |
1091 | */ |
1092 | if (sun4i_tcon_connected_to_tcon_top(node)) |
1093 | id = sun4i_tcon_get_index(drv); |
1094 | else |
1095 | id = sun4i_tcon_of_get_id_from_port(port); |
1096 | |
1097 | /* Get our engine by matching our ID */ |
1098 | engine = sun4i_tcon_get_engine_by_id(drv, id); |
1099 | |
1100 | of_node_put(node: port); |
1101 | return engine; |
1102 | } |
1103 | |
1104 | /* Fallback to old method by traversing input endpoints */ |
1105 | of_node_put(node: port); |
1106 | return sun4i_tcon_find_engine_traverse(drv, node, port_id: 0); |
1107 | } |
1108 | |
1109 | static int sun4i_tcon_bind(struct device *dev, struct device *master, |
1110 | void *data) |
1111 | { |
1112 | struct drm_device *drm = data; |
1113 | struct sun4i_drv *drv = drm->dev_private; |
1114 | struct sunxi_engine *engine; |
1115 | struct device_node *remote; |
1116 | struct sun4i_tcon *tcon; |
1117 | struct reset_control *edp_rstc; |
1118 | bool has_lvds_rst, has_lvds_alt, can_lvds; |
1119 | int ret; |
1120 | |
1121 | engine = sun4i_tcon_find_engine(drv, node: dev->of_node); |
1122 | if (IS_ERR(ptr: engine)) { |
1123 | dev_err(dev, "Couldn't find matching engine\n" ); |
1124 | return -EPROBE_DEFER; |
1125 | } |
1126 | |
1127 | tcon = devm_kzalloc(dev, size: sizeof(*tcon), GFP_KERNEL); |
1128 | if (!tcon) |
1129 | return -ENOMEM; |
1130 | dev_set_drvdata(dev, data: tcon); |
1131 | tcon->drm = drm; |
1132 | tcon->dev = dev; |
1133 | tcon->id = engine->id; |
1134 | tcon->quirks = of_device_get_match_data(dev); |
1135 | |
1136 | tcon->lcd_rst = devm_reset_control_get(dev, id: "lcd" ); |
1137 | if (IS_ERR(ptr: tcon->lcd_rst)) { |
1138 | dev_err(dev, "Couldn't get our reset line\n" ); |
1139 | return PTR_ERR(ptr: tcon->lcd_rst); |
1140 | } |
1141 | |
1142 | if (tcon->quirks->needs_edp_reset) { |
1143 | edp_rstc = devm_reset_control_get_shared(dev, id: "edp" ); |
1144 | if (IS_ERR(ptr: edp_rstc)) { |
1145 | dev_err(dev, "Couldn't get edp reset line\n" ); |
1146 | return PTR_ERR(ptr: edp_rstc); |
1147 | } |
1148 | |
1149 | ret = reset_control_deassert(rstc: edp_rstc); |
1150 | if (ret) { |
1151 | dev_err(dev, "Couldn't deassert edp reset line\n" ); |
1152 | return ret; |
1153 | } |
1154 | } |
1155 | |
1156 | /* Make sure our TCON is reset */ |
1157 | ret = reset_control_reset(rstc: tcon->lcd_rst); |
1158 | if (ret) { |
1159 | dev_err(dev, "Couldn't deassert our reset line\n" ); |
1160 | return ret; |
1161 | } |
1162 | |
1163 | if (tcon->quirks->supports_lvds) { |
1164 | /* |
1165 | * This can only be made optional since we've had DT |
1166 | * nodes without the LVDS reset properties. |
1167 | * |
1168 | * If the property is missing, just disable LVDS, and |
1169 | * print a warning. |
1170 | */ |
1171 | tcon->lvds_rst = devm_reset_control_get_optional(dev, id: "lvds" ); |
1172 | if (IS_ERR(ptr: tcon->lvds_rst)) { |
1173 | dev_err(dev, "Couldn't get our reset line\n" ); |
1174 | return PTR_ERR(ptr: tcon->lvds_rst); |
1175 | } else if (tcon->lvds_rst) { |
1176 | has_lvds_rst = true; |
1177 | reset_control_reset(rstc: tcon->lvds_rst); |
1178 | } else { |
1179 | has_lvds_rst = false; |
1180 | } |
1181 | |
1182 | /* |
1183 | * This can only be made optional since we've had DT |
1184 | * nodes without the LVDS reset properties. |
1185 | * |
1186 | * If the property is missing, just disable LVDS, and |
1187 | * print a warning. |
1188 | */ |
1189 | if (tcon->quirks->has_lvds_alt) { |
1190 | tcon->lvds_pll = devm_clk_get(dev, id: "lvds-alt" ); |
1191 | if (IS_ERR(ptr: tcon->lvds_pll)) { |
1192 | if (PTR_ERR(ptr: tcon->lvds_pll) == -ENOENT) { |
1193 | has_lvds_alt = false; |
1194 | } else { |
1195 | dev_err(dev, "Couldn't get the LVDS PLL\n" ); |
1196 | return PTR_ERR(ptr: tcon->lvds_pll); |
1197 | } |
1198 | } else { |
1199 | has_lvds_alt = true; |
1200 | } |
1201 | } |
1202 | |
1203 | if (!has_lvds_rst || |
1204 | (tcon->quirks->has_lvds_alt && !has_lvds_alt)) { |
1205 | dev_warn(dev, "Missing LVDS properties, Please upgrade your DT\n" ); |
1206 | dev_warn(dev, "LVDS output disabled\n" ); |
1207 | can_lvds = false; |
1208 | } else { |
1209 | can_lvds = true; |
1210 | } |
1211 | } else { |
1212 | can_lvds = false; |
1213 | } |
1214 | |
1215 | ret = sun4i_tcon_init_clocks(dev, tcon); |
1216 | if (ret) { |
1217 | dev_err(dev, "Couldn't init our TCON clocks\n" ); |
1218 | goto err_assert_reset; |
1219 | } |
1220 | |
1221 | ret = sun4i_tcon_init_regmap(dev, tcon); |
1222 | if (ret) { |
1223 | dev_err(dev, "Couldn't init our TCON regmap\n" ); |
1224 | goto err_assert_reset; |
1225 | } |
1226 | |
1227 | if (tcon->quirks->has_channel_0) { |
1228 | ret = sun4i_dclk_create(dev, tcon); |
1229 | if (ret) { |
1230 | dev_err(dev, "Couldn't create our TCON dot clock\n" ); |
1231 | goto err_assert_reset; |
1232 | } |
1233 | } |
1234 | |
1235 | ret = sun4i_tcon_init_irq(dev, tcon); |
1236 | if (ret) { |
1237 | dev_err(dev, "Couldn't init our TCON interrupts\n" ); |
1238 | goto err_free_dclk; |
1239 | } |
1240 | |
1241 | tcon->crtc = sun4i_crtc_init(drm, engine, tcon); |
1242 | if (IS_ERR(ptr: tcon->crtc)) { |
1243 | dev_err(dev, "Couldn't create our CRTC\n" ); |
1244 | ret = PTR_ERR(ptr: tcon->crtc); |
1245 | goto err_free_dclk; |
1246 | } |
1247 | |
1248 | if (tcon->quirks->has_channel_0) { |
1249 | /* |
1250 | * If we have an LVDS panel connected to the TCON, we should |
1251 | * just probe the LVDS connector. Otherwise, just probe RGB as |
1252 | * we used to. |
1253 | */ |
1254 | remote = of_graph_get_remote_node(node: dev->of_node, port: 1, endpoint: 0); |
1255 | if (of_device_is_compatible(device: remote, "panel-lvds" )) |
1256 | if (can_lvds) |
1257 | ret = sun4i_lvds_init(drm, tcon); |
1258 | else |
1259 | ret = -EINVAL; |
1260 | else |
1261 | ret = sun4i_rgb_init(drm, tcon); |
1262 | of_node_put(node: remote); |
1263 | |
1264 | if (ret < 0) |
1265 | goto err_free_dclk; |
1266 | } |
1267 | |
1268 | if (tcon->quirks->needs_de_be_mux) { |
1269 | /* |
1270 | * We assume there is no dynamic muxing of backends |
1271 | * and TCONs, so we select the backend with same ID. |
1272 | * |
1273 | * While dynamic selection might be interesting, since |
1274 | * the CRTC is tied to the TCON, while the layers are |
1275 | * tied to the backends, this means, we will need to |
1276 | * switch between groups of layers. There might not be |
1277 | * a way to represent this constraint in DRM. |
1278 | */ |
1279 | regmap_update_bits(map: tcon->regs, SUN4I_TCON0_CTL_REG, |
1280 | SUN4I_TCON0_CTL_SRC_SEL_MASK, |
1281 | val: tcon->id); |
1282 | regmap_update_bits(map: tcon->regs, SUN4I_TCON1_CTL_REG, |
1283 | SUN4I_TCON1_CTL_SRC_SEL_MASK, |
1284 | val: tcon->id); |
1285 | } |
1286 | |
1287 | list_add_tail(new: &tcon->list, head: &drv->tcon_list); |
1288 | |
1289 | return 0; |
1290 | |
1291 | err_free_dclk: |
1292 | if (tcon->quirks->has_channel_0) |
1293 | sun4i_dclk_free(tcon); |
1294 | err_assert_reset: |
1295 | reset_control_assert(rstc: tcon->lcd_rst); |
1296 | return ret; |
1297 | } |
1298 | |
1299 | static void sun4i_tcon_unbind(struct device *dev, struct device *master, |
1300 | void *data) |
1301 | { |
1302 | struct sun4i_tcon *tcon = dev_get_drvdata(dev); |
1303 | |
1304 | list_del(entry: &tcon->list); |
1305 | if (tcon->quirks->has_channel_0) |
1306 | sun4i_dclk_free(tcon); |
1307 | } |
1308 | |
1309 | static const struct component_ops sun4i_tcon_ops = { |
1310 | .bind = sun4i_tcon_bind, |
1311 | .unbind = sun4i_tcon_unbind, |
1312 | }; |
1313 | |
1314 | static int sun4i_tcon_probe(struct platform_device *pdev) |
1315 | { |
1316 | struct device_node *node = pdev->dev.of_node; |
1317 | const struct sun4i_tcon_quirks *quirks; |
1318 | struct drm_bridge *bridge; |
1319 | struct drm_panel *panel; |
1320 | int ret; |
1321 | |
1322 | quirks = of_device_get_match_data(dev: &pdev->dev); |
1323 | |
1324 | /* panels and bridges are present only on TCONs with channel 0 */ |
1325 | if (quirks->has_channel_0) { |
1326 | ret = drm_of_find_panel_or_bridge(np: node, port: 1, endpoint: 0, panel: &panel, bridge: &bridge); |
1327 | if (ret == -EPROBE_DEFER) |
1328 | return ret; |
1329 | } |
1330 | |
1331 | return component_add(&pdev->dev, &sun4i_tcon_ops); |
1332 | } |
1333 | |
1334 | static void sun4i_tcon_remove(struct platform_device *pdev) |
1335 | { |
1336 | component_del(&pdev->dev, &sun4i_tcon_ops); |
1337 | } |
1338 | |
1339 | /* platform specific TCON muxing callbacks */ |
1340 | static int sun4i_a10_tcon_set_mux(struct sun4i_tcon *tcon, |
1341 | const struct drm_encoder *encoder) |
1342 | { |
1343 | struct sun4i_tcon *tcon0 = sun4i_get_tcon0(drm: encoder->dev); |
1344 | u32 shift; |
1345 | |
1346 | if (!tcon0) |
1347 | return -EINVAL; |
1348 | |
1349 | switch (encoder->encoder_type) { |
1350 | case DRM_MODE_ENCODER_TMDS: |
1351 | /* HDMI */ |
1352 | shift = 8; |
1353 | break; |
1354 | default: |
1355 | return -EINVAL; |
1356 | } |
1357 | |
1358 | regmap_update_bits(map: tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, |
1359 | mask: 0x3 << shift, val: tcon->id << shift); |
1360 | |
1361 | return 0; |
1362 | } |
1363 | |
1364 | static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, |
1365 | const struct drm_encoder *encoder) |
1366 | { |
1367 | u32 val; |
1368 | |
1369 | if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) |
1370 | val = 1; |
1371 | else |
1372 | val = 0; |
1373 | |
1374 | /* |
1375 | * FIXME: Undocumented bits |
1376 | */ |
1377 | return regmap_write(map: tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); |
1378 | } |
1379 | |
1380 | static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, |
1381 | const struct drm_encoder *encoder) |
1382 | { |
1383 | struct sun4i_tcon *tcon0 = sun4i_get_tcon0(drm: encoder->dev); |
1384 | u32 shift; |
1385 | |
1386 | if (!tcon0) |
1387 | return -EINVAL; |
1388 | |
1389 | switch (encoder->encoder_type) { |
1390 | case DRM_MODE_ENCODER_TMDS: |
1391 | /* HDMI */ |
1392 | shift = 8; |
1393 | break; |
1394 | default: |
1395 | /* TODO A31 has MIPI DSI but A31s does not */ |
1396 | return -EINVAL; |
1397 | } |
1398 | |
1399 | regmap_update_bits(map: tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, |
1400 | mask: 0x3 << shift, val: tcon->id << shift); |
1401 | |
1402 | return 0; |
1403 | } |
1404 | |
1405 | static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon, |
1406 | const struct drm_encoder *encoder) |
1407 | { |
1408 | struct device_node *port, *remote; |
1409 | struct platform_device *pdev; |
1410 | int id, ret; |
1411 | |
1412 | /* find TCON TOP platform device and TCON id */ |
1413 | |
1414 | port = of_graph_get_port_by_id(node: tcon->dev->of_node, id: 0); |
1415 | if (!port) |
1416 | return -EINVAL; |
1417 | |
1418 | id = sun4i_tcon_of_get_id_from_port(port); |
1419 | of_node_put(node: port); |
1420 | |
1421 | remote = of_graph_get_remote_node(node: tcon->dev->of_node, port: 0, endpoint: -1); |
1422 | if (!remote) |
1423 | return -EINVAL; |
1424 | |
1425 | pdev = of_find_device_by_node(np: remote); |
1426 | of_node_put(node: remote); |
1427 | if (!pdev) |
1428 | return -EINVAL; |
1429 | |
1430 | if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
1431 | encoder->encoder_type == DRM_MODE_ENCODER_TMDS) { |
1432 | ret = sun8i_tcon_top_set_hdmi_src(dev: &pdev->dev, tcon: id); |
1433 | if (ret) { |
1434 | put_device(dev: &pdev->dev); |
1435 | return ret; |
1436 | } |
1437 | } |
1438 | |
1439 | if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP)) { |
1440 | ret = sun8i_tcon_top_de_config(dev: &pdev->dev, mixer: tcon->id, tcon: id); |
1441 | if (ret) { |
1442 | put_device(dev: &pdev->dev); |
1443 | return ret; |
1444 | } |
1445 | } |
1446 | |
1447 | return 0; |
1448 | } |
1449 | |
1450 | static const struct sun4i_tcon_quirks sun4i_a10_quirks = { |
1451 | .has_channel_0 = true, |
1452 | .has_channel_1 = true, |
1453 | .dclk_min_div = 4, |
1454 | .set_mux = sun4i_a10_tcon_set_mux, |
1455 | }; |
1456 | |
1457 | static const struct sun4i_tcon_quirks sun5i_a13_quirks = { |
1458 | .has_channel_0 = true, |
1459 | .has_channel_1 = true, |
1460 | .dclk_min_div = 4, |
1461 | .set_mux = sun5i_a13_tcon_set_mux, |
1462 | }; |
1463 | |
1464 | static const struct sun4i_tcon_quirks sun6i_a31_quirks = { |
1465 | .has_channel_0 = true, |
1466 | .has_channel_1 = true, |
1467 | .has_lvds_alt = true, |
1468 | .needs_de_be_mux = true, |
1469 | .dclk_min_div = 1, |
1470 | .set_mux = sun6i_tcon_set_mux, |
1471 | }; |
1472 | |
1473 | static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { |
1474 | .has_channel_0 = true, |
1475 | .has_channel_1 = true, |
1476 | .needs_de_be_mux = true, |
1477 | .dclk_min_div = 1, |
1478 | }; |
1479 | |
1480 | static const struct sun4i_tcon_quirks sun7i_a20_tcon0_quirks = { |
1481 | .supports_lvds = true, |
1482 | .has_channel_0 = true, |
1483 | .has_channel_1 = true, |
1484 | .dclk_min_div = 4, |
1485 | /* Same display pipeline structure as A10 */ |
1486 | .set_mux = sun4i_a10_tcon_set_mux, |
1487 | .setup_lvds_phy = sun4i_tcon_setup_lvds_phy, |
1488 | }; |
1489 | |
1490 | static const struct sun4i_tcon_quirks sun7i_a20_quirks = { |
1491 | .has_channel_0 = true, |
1492 | .has_channel_1 = true, |
1493 | .dclk_min_div = 4, |
1494 | /* Same display pipeline structure as A10 */ |
1495 | .set_mux = sun4i_a10_tcon_set_mux, |
1496 | }; |
1497 | |
1498 | static const struct sun4i_tcon_quirks sun8i_a33_quirks = { |
1499 | .has_channel_0 = true, |
1500 | .has_lvds_alt = true, |
1501 | .dclk_min_div = 1, |
1502 | .setup_lvds_phy = sun6i_tcon_setup_lvds_phy, |
1503 | .supports_lvds = true, |
1504 | }; |
1505 | |
1506 | static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { |
1507 | .supports_lvds = true, |
1508 | .has_channel_0 = true, |
1509 | .dclk_min_div = 1, |
1510 | .setup_lvds_phy = sun6i_tcon_setup_lvds_phy, |
1511 | }; |
1512 | |
1513 | static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { |
1514 | .has_channel_1 = true, |
1515 | }; |
1516 | |
1517 | static const struct sun4i_tcon_quirks sun8i_r40_tv_quirks = { |
1518 | .has_channel_1 = true, |
1519 | .polarity_in_ch0 = true, |
1520 | .set_mux = sun8i_r40_tcon_tv_set_mux, |
1521 | }; |
1522 | |
1523 | static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { |
1524 | .has_channel_0 = true, |
1525 | .dclk_min_div = 1, |
1526 | }; |
1527 | |
1528 | static const struct sun4i_tcon_quirks sun9i_a80_tcon_lcd_quirks = { |
1529 | .has_channel_0 = true, |
1530 | .needs_edp_reset = true, |
1531 | .dclk_min_div = 1, |
1532 | }; |
1533 | |
1534 | static const struct sun4i_tcon_quirks sun9i_a80_tcon_tv_quirks = { |
1535 | .has_channel_1 = true, |
1536 | .needs_edp_reset = true, |
1537 | }; |
1538 | |
1539 | static const struct sun4i_tcon_quirks sun20i_d1_lcd_quirks = { |
1540 | .has_channel_0 = true, |
1541 | .dclk_min_div = 1, |
1542 | .set_mux = sun8i_r40_tcon_tv_set_mux, |
1543 | }; |
1544 | |
1545 | /* sun4i_drv uses this list to check if a device node is a TCON */ |
1546 | const struct of_device_id sun4i_tcon_of_table[] = { |
1547 | { .compatible = "allwinner,sun4i-a10-tcon" , .data = &sun4i_a10_quirks }, |
1548 | { .compatible = "allwinner,sun5i-a13-tcon" , .data = &sun5i_a13_quirks }, |
1549 | { .compatible = "allwinner,sun6i-a31-tcon" , .data = &sun6i_a31_quirks }, |
1550 | { .compatible = "allwinner,sun6i-a31s-tcon" , .data = &sun6i_a31s_quirks }, |
1551 | { .compatible = "allwinner,sun7i-a20-tcon" , .data = &sun7i_a20_quirks }, |
1552 | { .compatible = "allwinner,sun7i-a20-tcon0" , .data = &sun7i_a20_tcon0_quirks }, |
1553 | { .compatible = "allwinner,sun7i-a20-tcon1" , .data = &sun7i_a20_quirks }, |
1554 | { .compatible = "allwinner,sun8i-a23-tcon" , .data = &sun8i_a33_quirks }, |
1555 | { .compatible = "allwinner,sun8i-a33-tcon" , .data = &sun8i_a33_quirks }, |
1556 | { .compatible = "allwinner,sun8i-a83t-tcon-lcd" , .data = &sun8i_a83t_lcd_quirks }, |
1557 | { .compatible = "allwinner,sun8i-a83t-tcon-tv" , .data = &sun8i_a83t_tv_quirks }, |
1558 | { .compatible = "allwinner,sun8i-r40-tcon-tv" , .data = &sun8i_r40_tv_quirks }, |
1559 | { .compatible = "allwinner,sun8i-v3s-tcon" , .data = &sun8i_v3s_quirks }, |
1560 | { .compatible = "allwinner,sun9i-a80-tcon-lcd" , .data = &sun9i_a80_tcon_lcd_quirks }, |
1561 | { .compatible = "allwinner,sun9i-a80-tcon-tv" , .data = &sun9i_a80_tcon_tv_quirks }, |
1562 | { .compatible = "allwinner,sun20i-d1-tcon-lcd" , .data = &sun20i_d1_lcd_quirks }, |
1563 | { .compatible = "allwinner,sun20i-d1-tcon-tv" , .data = &sun8i_r40_tv_quirks }, |
1564 | { } |
1565 | }; |
1566 | MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); |
1567 | EXPORT_SYMBOL(sun4i_tcon_of_table); |
1568 | |
1569 | static struct platform_driver sun4i_tcon_platform_driver = { |
1570 | .probe = sun4i_tcon_probe, |
1571 | .remove_new = sun4i_tcon_remove, |
1572 | .driver = { |
1573 | .name = "sun4i-tcon" , |
1574 | .of_match_table = sun4i_tcon_of_table, |
1575 | }, |
1576 | }; |
1577 | module_platform_driver(sun4i_tcon_platform_driver); |
1578 | |
1579 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>" ); |
1580 | MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver" ); |
1581 | MODULE_LICENSE("GPL" ); |
1582 | |