.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (C) 2015 Free Electrons |
---|
3 | 4 | * Copyright (C) 2015 NextThing Co |
---|
4 | 5 | * |
---|
5 | 6 | * 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 | 7 | */ |
---|
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_encoder.h> |
---|
18 | | -#include <drm/drm_modes.h> |
---|
19 | | -#include <drm/drm_of.h> |
---|
20 | | - |
---|
21 | | -#include <uapi/drm/drm_mode.h> |
---|
22 | 8 | |
---|
23 | 9 | #include <linux/component.h> |
---|
24 | 10 | #include <linux/ioport.h> |
---|
| 11 | +#include <linux/module.h> |
---|
25 | 12 | #include <linux/of_address.h> |
---|
26 | 13 | #include <linux/of_device.h> |
---|
27 | 14 | #include <linux/of_irq.h> |
---|
28 | 15 | #include <linux/regmap.h> |
---|
29 | 16 | #include <linux/reset.h> |
---|
| 17 | + |
---|
| 18 | +#include <drm/drm_atomic_helper.h> |
---|
| 19 | +#include <drm/drm_bridge.h> |
---|
| 20 | +#include <drm/drm_connector.h> |
---|
| 21 | +#include <drm/drm_crtc.h> |
---|
| 22 | +#include <drm/drm_encoder.h> |
---|
| 23 | +#include <drm/drm_modes.h> |
---|
| 24 | +#include <drm/drm_of.h> |
---|
| 25 | +#include <drm/drm_panel.h> |
---|
| 26 | +#include <drm/drm_print.h> |
---|
| 27 | +#include <drm/drm_probe_helper.h> |
---|
| 28 | +#include <drm/drm_vblank.h> |
---|
| 29 | + |
---|
| 30 | +#include <uapi/drm/drm_mode.h> |
---|
30 | 31 | |
---|
31 | 32 | #include "sun4i_crtc.h" |
---|
32 | 33 | #include "sun4i_dotclock.h" |
---|
.. | .. |
---|
35 | 36 | #include "sun4i_rgb.h" |
---|
36 | 37 | #include "sun4i_tcon.h" |
---|
37 | 38 | #include "sun6i_mipi_dsi.h" |
---|
| 39 | +#include "sun8i_tcon_top.h" |
---|
38 | 40 | #include "sunxi_engine.h" |
---|
39 | 41 | |
---|
40 | 42 | static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) |
---|
.. | .. |
---|
112 | 114 | } |
---|
113 | 115 | } |
---|
114 | 116 | |
---|
| 117 | +static void sun4i_tcon_setup_lvds_phy(struct sun4i_tcon *tcon, |
---|
| 118 | + const struct drm_encoder *encoder) |
---|
| 119 | +{ |
---|
| 120 | + regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
| 121 | + SUN4I_TCON0_LVDS_ANA0_CK_EN | |
---|
| 122 | + SUN4I_TCON0_LVDS_ANA0_REG_V | |
---|
| 123 | + SUN4I_TCON0_LVDS_ANA0_REG_C | |
---|
| 124 | + SUN4I_TCON0_LVDS_ANA0_EN_MB | |
---|
| 125 | + SUN4I_TCON0_LVDS_ANA0_PD | |
---|
| 126 | + SUN4I_TCON0_LVDS_ANA0_DCHS); |
---|
| 127 | + |
---|
| 128 | + udelay(2); /* delay at least 1200 ns */ |
---|
| 129 | + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG, |
---|
| 130 | + SUN4I_TCON0_LVDS_ANA1_INIT, |
---|
| 131 | + SUN4I_TCON0_LVDS_ANA1_INIT); |
---|
| 132 | + udelay(1); /* delay at least 120 ns */ |
---|
| 133 | + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG, |
---|
| 134 | + SUN4I_TCON0_LVDS_ANA1_UPDATE, |
---|
| 135 | + SUN4I_TCON0_LVDS_ANA1_UPDATE); |
---|
| 136 | + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
| 137 | + SUN4I_TCON0_LVDS_ANA0_EN_MB, |
---|
| 138 | + SUN4I_TCON0_LVDS_ANA0_EN_MB); |
---|
| 139 | +} |
---|
| 140 | + |
---|
| 141 | +static void sun6i_tcon_setup_lvds_phy(struct sun4i_tcon *tcon, |
---|
| 142 | + const struct drm_encoder *encoder) |
---|
| 143 | +{ |
---|
| 144 | + u8 val; |
---|
| 145 | + |
---|
| 146 | + regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
| 147 | + SUN6I_TCON0_LVDS_ANA0_C(2) | |
---|
| 148 | + SUN6I_TCON0_LVDS_ANA0_V(3) | |
---|
| 149 | + SUN6I_TCON0_LVDS_ANA0_PD(2) | |
---|
| 150 | + SUN6I_TCON0_LVDS_ANA0_EN_LDO); |
---|
| 151 | + udelay(2); |
---|
| 152 | + |
---|
| 153 | + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
| 154 | + SUN6I_TCON0_LVDS_ANA0_EN_MB, |
---|
| 155 | + SUN6I_TCON0_LVDS_ANA0_EN_MB); |
---|
| 156 | + udelay(2); |
---|
| 157 | + |
---|
| 158 | + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
| 159 | + SUN6I_TCON0_LVDS_ANA0_EN_DRVC, |
---|
| 160 | + SUN6I_TCON0_LVDS_ANA0_EN_DRVC); |
---|
| 161 | + |
---|
| 162 | + if (sun4i_tcon_get_pixel_depth(encoder) == 18) |
---|
| 163 | + val = 7; |
---|
| 164 | + else |
---|
| 165 | + val = 0xf; |
---|
| 166 | + |
---|
| 167 | + regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
| 168 | + SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf), |
---|
| 169 | + SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val)); |
---|
| 170 | +} |
---|
| 171 | + |
---|
115 | 172 | static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon, |
---|
116 | 173 | const struct drm_encoder *encoder, |
---|
117 | 174 | bool enabled) |
---|
118 | 175 | { |
---|
119 | 176 | if (enabled) { |
---|
120 | | - u8 val; |
---|
121 | | - |
---|
122 | 177 | regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, |
---|
123 | 178 | SUN4I_TCON0_LVDS_IF_EN, |
---|
124 | 179 | SUN4I_TCON0_LVDS_IF_EN); |
---|
125 | | - |
---|
126 | | - /* |
---|
127 | | - * As their name suggest, these values only apply to the A31 |
---|
128 | | - * and later SoCs. We'll have to rework this when merging |
---|
129 | | - * support for the older SoCs. |
---|
130 | | - */ |
---|
131 | | - regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
132 | | - SUN6I_TCON0_LVDS_ANA0_C(2) | |
---|
133 | | - SUN6I_TCON0_LVDS_ANA0_V(3) | |
---|
134 | | - SUN6I_TCON0_LVDS_ANA0_PD(2) | |
---|
135 | | - SUN6I_TCON0_LVDS_ANA0_EN_LDO); |
---|
136 | | - udelay(2); |
---|
137 | | - |
---|
138 | | - regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
139 | | - SUN6I_TCON0_LVDS_ANA0_EN_MB, |
---|
140 | | - SUN6I_TCON0_LVDS_ANA0_EN_MB); |
---|
141 | | - udelay(2); |
---|
142 | | - |
---|
143 | | - regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
144 | | - SUN6I_TCON0_LVDS_ANA0_EN_DRVC, |
---|
145 | | - SUN6I_TCON0_LVDS_ANA0_EN_DRVC); |
---|
146 | | - |
---|
147 | | - if (sun4i_tcon_get_pixel_depth(encoder) == 18) |
---|
148 | | - val = 7; |
---|
149 | | - else |
---|
150 | | - val = 0xf; |
---|
151 | | - |
---|
152 | | - regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, |
---|
153 | | - SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf), |
---|
154 | | - SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val)); |
---|
| 180 | + if (tcon->quirks->setup_lvds_phy) |
---|
| 181 | + tcon->quirks->setup_lvds_phy(tcon, encoder); |
---|
155 | 182 | } else { |
---|
156 | 183 | regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, |
---|
157 | 184 | SUN4I_TCON0_LVDS_IF_EN, 0); |
---|
.. | .. |
---|
168 | 195 | switch (encoder->encoder_type) { |
---|
169 | 196 | case DRM_MODE_ENCODER_LVDS: |
---|
170 | 197 | is_lvds = true; |
---|
171 | | - /* Fallthrough */ |
---|
| 198 | + fallthrough; |
---|
172 | 199 | case DRM_MODE_ENCODER_DSI: |
---|
173 | 200 | case DRM_MODE_ENCODER_NONE: |
---|
174 | 201 | channel = 0; |
---|
.. | .. |
---|
233 | 260 | return NULL; |
---|
234 | 261 | } |
---|
235 | 262 | |
---|
236 | | -void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, |
---|
237 | | - const struct drm_encoder *encoder) |
---|
| 263 | +static void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, |
---|
| 264 | + const struct drm_encoder *encoder) |
---|
238 | 265 | { |
---|
239 | 266 | int ret = -ENOTSUPP; |
---|
240 | 267 | |
---|
.. | .. |
---|
275 | 302 | SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); |
---|
276 | 303 | } |
---|
277 | 304 | |
---|
| 305 | +static void sun4i_tcon0_mode_set_dithering(struct sun4i_tcon *tcon, |
---|
| 306 | + const struct drm_connector *connector) |
---|
| 307 | +{ |
---|
| 308 | + u32 bus_format = 0; |
---|
| 309 | + u32 val = 0; |
---|
| 310 | + |
---|
| 311 | + /* XXX Would this ever happen? */ |
---|
| 312 | + if (!connector) |
---|
| 313 | + return; |
---|
| 314 | + |
---|
| 315 | + /* |
---|
| 316 | + * FIXME: Undocumented bits |
---|
| 317 | + * |
---|
| 318 | + * The whole dithering process and these parameters are not |
---|
| 319 | + * explained in the vendor documents or BSP kernel code. |
---|
| 320 | + */ |
---|
| 321 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_PR_REG, 0x11111111); |
---|
| 322 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_PG_REG, 0x11111111); |
---|
| 323 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_PB_REG, 0x11111111); |
---|
| 324 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_LR_REG, 0x11111111); |
---|
| 325 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_LG_REG, 0x11111111); |
---|
| 326 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_LB_REG, 0x11111111); |
---|
| 327 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL0_REG, 0x01010000); |
---|
| 328 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL1_REG, 0x15151111); |
---|
| 329 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL2_REG, 0x57575555); |
---|
| 330 | + regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL3_REG, 0x7f7f7777); |
---|
| 331 | + |
---|
| 332 | + /* Do dithering if panel only supports 6 bits per color */ |
---|
| 333 | + if (connector->display_info.bpc == 6) |
---|
| 334 | + val |= SUN4I_TCON0_FRM_CTL_EN; |
---|
| 335 | + |
---|
| 336 | + if (connector->display_info.num_bus_formats == 1) |
---|
| 337 | + bus_format = connector->display_info.bus_formats[0]; |
---|
| 338 | + |
---|
| 339 | + /* Check the connection format */ |
---|
| 340 | + switch (bus_format) { |
---|
| 341 | + case MEDIA_BUS_FMT_RGB565_1X16: |
---|
| 342 | + /* R and B components are only 5 bits deep */ |
---|
| 343 | + val |= SUN4I_TCON0_FRM_CTL_MODE_R; |
---|
| 344 | + val |= SUN4I_TCON0_FRM_CTL_MODE_B; |
---|
| 345 | + fallthrough; |
---|
| 346 | + case MEDIA_BUS_FMT_RGB666_1X18: |
---|
| 347 | + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: |
---|
| 348 | + /* Fall through: enable dithering */ |
---|
| 349 | + val |= SUN4I_TCON0_FRM_CTL_EN; |
---|
| 350 | + break; |
---|
| 351 | + } |
---|
| 352 | + |
---|
| 353 | + /* Write dithering settings */ |
---|
| 354 | + regmap_write(tcon->regs, SUN4I_TCON_FRM_CTL_REG, val); |
---|
| 355 | +} |
---|
| 356 | + |
---|
278 | 357 | static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon *tcon, |
---|
279 | | - struct mipi_dsi_device *device, |
---|
| 358 | + const struct drm_encoder *encoder, |
---|
280 | 359 | const struct drm_display_mode *mode) |
---|
281 | 360 | { |
---|
| 361 | + /* TODO support normal CPU interface modes */ |
---|
| 362 | + struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder); |
---|
| 363 | + struct mipi_dsi_device *device = dsi->device; |
---|
282 | 364 | u8 bpp = mipi_dsi_pixel_format_to_bpp(device->format); |
---|
283 | 365 | u8 lanes = device->lanes; |
---|
284 | 366 | u32 block_space, start_delay; |
---|
285 | 367 | u32 tcon_div; |
---|
286 | 368 | |
---|
287 | | - tcon->dclk_min_div = 4; |
---|
288 | | - tcon->dclk_max_div = 127; |
---|
| 369 | + tcon->dclk_min_div = SUN6I_DSI_TCON_DIV; |
---|
| 370 | + tcon->dclk_max_div = SUN6I_DSI_TCON_DIV; |
---|
289 | 371 | |
---|
290 | 372 | sun4i_tcon0_mode_set_common(tcon, mode); |
---|
| 373 | + |
---|
| 374 | + /* Set dithering if needed */ |
---|
| 375 | + sun4i_tcon0_mode_set_dithering(tcon, sun4i_tcon_get_connector(encoder)); |
---|
291 | 376 | |
---|
292 | 377 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, |
---|
293 | 378 | SUN4I_TCON0_CTL_IF_MASK, |
---|
.. | .. |
---|
354 | 439 | tcon->dclk_max_div = 7; |
---|
355 | 440 | sun4i_tcon0_mode_set_common(tcon, mode); |
---|
356 | 441 | |
---|
| 442 | + /* Set dithering if needed */ |
---|
| 443 | + sun4i_tcon0_mode_set_dithering(tcon, sun4i_tcon_get_connector(encoder)); |
---|
| 444 | + |
---|
357 | 445 | /* Adjust clock delay */ |
---|
358 | 446 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); |
---|
359 | 447 | regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, |
---|
.. | .. |
---|
386 | 474 | SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | |
---|
387 | 475 | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); |
---|
388 | 476 | |
---|
389 | | - reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 | |
---|
390 | | - SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL | |
---|
391 | | - SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL; |
---|
| 477 | + reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0; |
---|
392 | 478 | if (sun4i_tcon_get_pixel_depth(encoder) == 24) |
---|
393 | 479 | reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS; |
---|
394 | 480 | else |
---|
.. | .. |
---|
415 | 501 | } |
---|
416 | 502 | |
---|
417 | 503 | static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, |
---|
| 504 | + const struct drm_encoder *encoder, |
---|
418 | 505 | const struct drm_display_mode *mode) |
---|
419 | 506 | { |
---|
| 507 | + struct drm_connector *connector = sun4i_tcon_get_connector(encoder); |
---|
| 508 | + const struct drm_display_info *info = &connector->display_info; |
---|
420 | 509 | unsigned int bp, hsync, vsync; |
---|
421 | 510 | u8 clk_delay; |
---|
422 | 511 | u32 val = 0; |
---|
.. | .. |
---|
426 | 515 | tcon->dclk_min_div = tcon->quirks->dclk_min_div; |
---|
427 | 516 | tcon->dclk_max_div = 127; |
---|
428 | 517 | sun4i_tcon0_mode_set_common(tcon, mode); |
---|
| 518 | + |
---|
| 519 | + /* Set dithering if needed */ |
---|
| 520 | + sun4i_tcon0_mode_set_dithering(tcon, connector); |
---|
429 | 521 | |
---|
430 | 522 | /* Adjust clock delay */ |
---|
431 | 523 | clk_delay = sun4i_tcon_get_clk_delay(mode, 0); |
---|
.. | .. |
---|
474 | 566 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
---|
475 | 567 | val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; |
---|
476 | 568 | |
---|
| 569 | + if (info->bus_flags & DRM_BUS_FLAG_DE_LOW) |
---|
| 570 | + val |= SUN4I_TCON0_IO_POL_DE_NEGATIVE; |
---|
| 571 | + |
---|
| 572 | + if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) |
---|
| 573 | + val |= SUN4I_TCON0_IO_POL_DCLK_DRIVE_NEGEDGE; |
---|
| 574 | + |
---|
477 | 575 | regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, |
---|
478 | | - SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, |
---|
| 576 | + SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | |
---|
| 577 | + SUN4I_TCON0_IO_POL_VSYNC_POSITIVE | |
---|
| 578 | + SUN4I_TCON0_IO_POL_DCLK_DRIVE_NEGEDGE | |
---|
| 579 | + SUN4I_TCON0_IO_POL_DE_NEGATIVE, |
---|
479 | 580 | val); |
---|
480 | 581 | |
---|
481 | 582 | /* Map output pins to channel 0 */ |
---|
.. | .. |
---|
571 | 672 | SUN4I_TCON1_BASIC5_V_SYNC(vsync) | |
---|
572 | 673 | SUN4I_TCON1_BASIC5_H_SYNC(hsync)); |
---|
573 | 674 | |
---|
| 675 | + /* Setup the polarity of multiple signals */ |
---|
| 676 | + if (tcon->quirks->polarity_in_ch0) { |
---|
| 677 | + val = 0; |
---|
| 678 | + |
---|
| 679 | + if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
---|
| 680 | + val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; |
---|
| 681 | + |
---|
| 682 | + if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
---|
| 683 | + val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; |
---|
| 684 | + |
---|
| 685 | + regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val); |
---|
| 686 | + } else { |
---|
| 687 | + /* according to vendor driver, this bit must be always set */ |
---|
| 688 | + val = SUN4I_TCON1_IO_POL_UNKNOWN; |
---|
| 689 | + |
---|
| 690 | + if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
---|
| 691 | + val |= SUN4I_TCON1_IO_POL_HSYNC_POSITIVE; |
---|
| 692 | + |
---|
| 693 | + if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
---|
| 694 | + val |= SUN4I_TCON1_IO_POL_VSYNC_POSITIVE; |
---|
| 695 | + |
---|
| 696 | + regmap_write(tcon->regs, SUN4I_TCON1_IO_POL_REG, val); |
---|
| 697 | + } |
---|
| 698 | + |
---|
574 | 699 | /* Map output pins to channel 1 */ |
---|
575 | 700 | regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, |
---|
576 | 701 | SUN4I_TCON_GCTL_IOMAP_MASK, |
---|
.. | .. |
---|
581 | 706 | const struct drm_encoder *encoder, |
---|
582 | 707 | const struct drm_display_mode *mode) |
---|
583 | 708 | { |
---|
584 | | - struct sun6i_dsi *dsi; |
---|
585 | | - |
---|
586 | 709 | switch (encoder->encoder_type) { |
---|
587 | 710 | case DRM_MODE_ENCODER_DSI: |
---|
588 | | - /* |
---|
589 | | - * This is not really elegant, but it's the "cleaner" |
---|
590 | | - * way I could think of... |
---|
591 | | - */ |
---|
592 | | - dsi = encoder_to_sun6i_dsi(encoder); |
---|
593 | | - sun4i_tcon0_mode_set_cpu(tcon, dsi->device, mode); |
---|
| 711 | + /* DSI is tied to special case of CPU interface */ |
---|
| 712 | + sun4i_tcon0_mode_set_cpu(tcon, encoder, mode); |
---|
594 | 713 | break; |
---|
595 | 714 | case DRM_MODE_ENCODER_LVDS: |
---|
596 | 715 | sun4i_tcon0_mode_set_lvds(tcon, encoder, mode); |
---|
597 | 716 | break; |
---|
598 | 717 | case DRM_MODE_ENCODER_NONE: |
---|
599 | | - sun4i_tcon0_mode_set_rgb(tcon, mode); |
---|
| 718 | + sun4i_tcon0_mode_set_rgb(tcon, encoder, mode); |
---|
600 | 719 | sun4i_tcon_set_mux(tcon, 0, encoder); |
---|
601 | 720 | break; |
---|
602 | 721 | case DRM_MODE_ENCODER_TVDAC: |
---|
.. | .. |
---|
698 | 817 | int irq, ret; |
---|
699 | 818 | |
---|
700 | 819 | irq = platform_get_irq(pdev, 0); |
---|
701 | | - if (irq < 0) { |
---|
702 | | - dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); |
---|
| 820 | + if (irq < 0) |
---|
703 | 821 | return irq; |
---|
704 | | - } |
---|
705 | 822 | |
---|
706 | 823 | ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, |
---|
707 | 824 | dev_name(dev), tcon); |
---|
.. | .. |
---|
713 | 830 | return 0; |
---|
714 | 831 | } |
---|
715 | 832 | |
---|
716 | | -static struct regmap_config sun4i_tcon_regmap_config = { |
---|
| 833 | +static const struct regmap_config sun4i_tcon_regmap_config = { |
---|
717 | 834 | .reg_bits = 32, |
---|
718 | 835 | .val_bits = 32, |
---|
719 | 836 | .reg_stride = 4, |
---|
.. | .. |
---|
882 | 999 | return ERR_PTR(-EINVAL); |
---|
883 | 1000 | } |
---|
884 | 1001 | |
---|
| 1002 | +static bool sun4i_tcon_connected_to_tcon_top(struct device_node *node) |
---|
| 1003 | +{ |
---|
| 1004 | + struct device_node *remote; |
---|
| 1005 | + bool ret = false; |
---|
| 1006 | + |
---|
| 1007 | + remote = of_graph_get_remote_node(node, 0, -1); |
---|
| 1008 | + if (remote) { |
---|
| 1009 | + ret = !!(IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
---|
| 1010 | + of_match_node(sun8i_tcon_top_of_table, remote)); |
---|
| 1011 | + of_node_put(remote); |
---|
| 1012 | + } |
---|
| 1013 | + |
---|
| 1014 | + return ret; |
---|
| 1015 | +} |
---|
| 1016 | + |
---|
| 1017 | +static int sun4i_tcon_get_index(struct sun4i_drv *drv) |
---|
| 1018 | +{ |
---|
| 1019 | + struct list_head *pos; |
---|
| 1020 | + int size = 0; |
---|
| 1021 | + |
---|
| 1022 | + /* |
---|
| 1023 | + * Because TCON is added to the list at the end of the probe |
---|
| 1024 | + * (after this function is called), index of the current TCON |
---|
| 1025 | + * will be same as current TCON list size. |
---|
| 1026 | + */ |
---|
| 1027 | + list_for_each(pos, &drv->tcon_list) |
---|
| 1028 | + ++size; |
---|
| 1029 | + |
---|
| 1030 | + return size; |
---|
| 1031 | +} |
---|
| 1032 | + |
---|
885 | 1033 | /* |
---|
886 | 1034 | * On SoCs with the old display pipeline design (Display Engine 1.0), |
---|
887 | 1035 | * we assumed the TCON was always tied to just one backend. However |
---|
.. | .. |
---|
930 | 1078 | * connections between the backend and TCON? |
---|
931 | 1079 | */ |
---|
932 | 1080 | if (of_get_child_count(port) > 1) { |
---|
933 | | - /* Get our ID directly from an upstream endpoint */ |
---|
934 | | - int id = sun4i_tcon_of_get_id_from_port(port); |
---|
| 1081 | + int id; |
---|
| 1082 | + |
---|
| 1083 | + /* |
---|
| 1084 | + * When pipeline has the same number of TCONs and engines which |
---|
| 1085 | + * are represented by frontends/backends (DE1) or mixers (DE2), |
---|
| 1086 | + * we match them by their respective IDs. However, if pipeline |
---|
| 1087 | + * contains TCON TOP, chances are that there are either more |
---|
| 1088 | + * TCONs than engines (R40) or TCONs with non-consecutive ids. |
---|
| 1089 | + * (H6). In that case it's easier just use TCON index in list |
---|
| 1090 | + * as an id. That means that on R40, any 2 TCONs can be enabled |
---|
| 1091 | + * in DT out of 4 (there are 2 mixers). Due to the design of |
---|
| 1092 | + * TCON TOP, remaining 2 TCONs can't be connected to anything |
---|
| 1093 | + * anyway. |
---|
| 1094 | + */ |
---|
| 1095 | + if (sun4i_tcon_connected_to_tcon_top(node)) |
---|
| 1096 | + id = sun4i_tcon_get_index(drv); |
---|
| 1097 | + else |
---|
| 1098 | + id = sun4i_tcon_of_get_id_from_port(port); |
---|
935 | 1099 | |
---|
936 | 1100 | /* Get our engine by matching our ID */ |
---|
937 | 1101 | engine = sun4i_tcon_get_engine_by_id(drv, id); |
---|
.. | .. |
---|
1246 | 1410 | return 0; |
---|
1247 | 1411 | } |
---|
1248 | 1412 | |
---|
| 1413 | +static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon, |
---|
| 1414 | + const struct drm_encoder *encoder) |
---|
| 1415 | +{ |
---|
| 1416 | + struct device_node *port, *remote; |
---|
| 1417 | + struct platform_device *pdev; |
---|
| 1418 | + int id, ret; |
---|
| 1419 | + |
---|
| 1420 | + /* find TCON TOP platform device and TCON id */ |
---|
| 1421 | + |
---|
| 1422 | + port = of_graph_get_port_by_id(tcon->dev->of_node, 0); |
---|
| 1423 | + if (!port) |
---|
| 1424 | + return -EINVAL; |
---|
| 1425 | + |
---|
| 1426 | + id = sun4i_tcon_of_get_id_from_port(port); |
---|
| 1427 | + of_node_put(port); |
---|
| 1428 | + |
---|
| 1429 | + remote = of_graph_get_remote_node(tcon->dev->of_node, 0, -1); |
---|
| 1430 | + if (!remote) |
---|
| 1431 | + return -EINVAL; |
---|
| 1432 | + |
---|
| 1433 | + pdev = of_find_device_by_node(remote); |
---|
| 1434 | + of_node_put(remote); |
---|
| 1435 | + if (!pdev) |
---|
| 1436 | + return -EINVAL; |
---|
| 1437 | + |
---|
| 1438 | + if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
---|
| 1439 | + encoder->encoder_type == DRM_MODE_ENCODER_TMDS) { |
---|
| 1440 | + ret = sun8i_tcon_top_set_hdmi_src(&pdev->dev, id); |
---|
| 1441 | + if (ret) { |
---|
| 1442 | + put_device(&pdev->dev); |
---|
| 1443 | + return ret; |
---|
| 1444 | + } |
---|
| 1445 | + } |
---|
| 1446 | + |
---|
| 1447 | + if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP)) { |
---|
| 1448 | + ret = sun8i_tcon_top_de_config(&pdev->dev, tcon->id, id); |
---|
| 1449 | + if (ret) { |
---|
| 1450 | + put_device(&pdev->dev); |
---|
| 1451 | + return ret; |
---|
| 1452 | + } |
---|
| 1453 | + } |
---|
| 1454 | + |
---|
| 1455 | + return 0; |
---|
| 1456 | +} |
---|
| 1457 | + |
---|
1249 | 1458 | static const struct sun4i_tcon_quirks sun4i_a10_quirks = { |
---|
1250 | 1459 | .has_channel_0 = true, |
---|
1251 | 1460 | .has_channel_1 = true, |
---|
.. | .. |
---|
1276 | 1485 | .dclk_min_div = 1, |
---|
1277 | 1486 | }; |
---|
1278 | 1487 | |
---|
| 1488 | +static const struct sun4i_tcon_quirks sun7i_a20_tcon0_quirks = { |
---|
| 1489 | + .supports_lvds = true, |
---|
| 1490 | + .has_channel_0 = true, |
---|
| 1491 | + .has_channel_1 = true, |
---|
| 1492 | + .dclk_min_div = 4, |
---|
| 1493 | + /* Same display pipeline structure as A10 */ |
---|
| 1494 | + .set_mux = sun4i_a10_tcon_set_mux, |
---|
| 1495 | + .setup_lvds_phy = sun4i_tcon_setup_lvds_phy, |
---|
| 1496 | +}; |
---|
| 1497 | + |
---|
1279 | 1498 | static const struct sun4i_tcon_quirks sun7i_a20_quirks = { |
---|
1280 | 1499 | .has_channel_0 = true, |
---|
1281 | 1500 | .has_channel_1 = true, |
---|
.. | .. |
---|
1288 | 1507 | .has_channel_0 = true, |
---|
1289 | 1508 | .has_lvds_alt = true, |
---|
1290 | 1509 | .dclk_min_div = 1, |
---|
| 1510 | + .setup_lvds_phy = sun6i_tcon_setup_lvds_phy, |
---|
| 1511 | + .supports_lvds = true, |
---|
1291 | 1512 | }; |
---|
1292 | 1513 | |
---|
1293 | 1514 | static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { |
---|
1294 | 1515 | .supports_lvds = true, |
---|
1295 | 1516 | .has_channel_0 = true, |
---|
1296 | 1517 | .dclk_min_div = 1, |
---|
| 1518 | + .setup_lvds_phy = sun6i_tcon_setup_lvds_phy, |
---|
1297 | 1519 | }; |
---|
1298 | 1520 | |
---|
1299 | 1521 | static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { |
---|
1300 | 1522 | .has_channel_1 = true, |
---|
| 1523 | +}; |
---|
| 1524 | + |
---|
| 1525 | +static const struct sun4i_tcon_quirks sun8i_r40_tv_quirks = { |
---|
| 1526 | + .has_channel_1 = true, |
---|
| 1527 | + .polarity_in_ch0 = true, |
---|
| 1528 | + .set_mux = sun8i_r40_tcon_tv_set_mux, |
---|
1301 | 1529 | }; |
---|
1302 | 1530 | |
---|
1303 | 1531 | static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { |
---|
.. | .. |
---|
1323 | 1551 | { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, |
---|
1324 | 1552 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, |
---|
1325 | 1553 | { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, |
---|
| 1554 | + { .compatible = "allwinner,sun7i-a20-tcon0", .data = &sun7i_a20_tcon0_quirks }, |
---|
| 1555 | + { .compatible = "allwinner,sun7i-a20-tcon1", .data = &sun7i_a20_quirks }, |
---|
| 1556 | + { .compatible = "allwinner,sun8i-a23-tcon", .data = &sun8i_a33_quirks }, |
---|
1326 | 1557 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, |
---|
1327 | 1558 | { .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks }, |
---|
1328 | 1559 | { .compatible = "allwinner,sun8i-a83t-tcon-tv", .data = &sun8i_a83t_tv_quirks }, |
---|
| 1560 | + { .compatible = "allwinner,sun8i-r40-tcon-tv", .data = &sun8i_r40_tv_quirks }, |
---|
1329 | 1561 | { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, |
---|
1330 | 1562 | { .compatible = "allwinner,sun9i-a80-tcon-lcd", .data = &sun9i_a80_tcon_lcd_quirks }, |
---|
1331 | 1563 | { .compatible = "allwinner,sun9i-a80-tcon-tv", .data = &sun9i_a80_tcon_tv_quirks }, |
---|