| .. | .. |
|---|
| 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: |
|---|
| .. | .. |
|---|
| 658 | 777 | static int sun4i_tcon_init_clocks(struct device *dev, |
|---|
| 659 | 778 | struct sun4i_tcon *tcon) |
|---|
| 660 | 779 | { |
|---|
| 661 | | - tcon->clk = devm_clk_get(dev, "ahb"); |
|---|
| 780 | + tcon->clk = devm_clk_get_enabled(dev, "ahb"); |
|---|
| 662 | 781 | if (IS_ERR(tcon->clk)) { |
|---|
| 663 | 782 | dev_err(dev, "Couldn't get the TCON bus clock\n"); |
|---|
| 664 | 783 | return PTR_ERR(tcon->clk); |
|---|
| 665 | 784 | } |
|---|
| 666 | | - clk_prepare_enable(tcon->clk); |
|---|
| 667 | 785 | |
|---|
| 668 | 786 | if (tcon->quirks->has_channel_0) { |
|---|
| 669 | | - tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); |
|---|
| 787 | + tcon->sclk0 = devm_clk_get_enabled(dev, "tcon-ch0"); |
|---|
| 670 | 788 | if (IS_ERR(tcon->sclk0)) { |
|---|
| 671 | 789 | dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); |
|---|
| 672 | 790 | return PTR_ERR(tcon->sclk0); |
|---|
| 673 | 791 | } |
|---|
| 674 | 792 | } |
|---|
| 675 | | - clk_prepare_enable(tcon->sclk0); |
|---|
| 676 | 793 | |
|---|
| 677 | 794 | if (tcon->quirks->has_channel_1) { |
|---|
| 678 | 795 | tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); |
|---|
| .. | .. |
|---|
| 685 | 802 | return 0; |
|---|
| 686 | 803 | } |
|---|
| 687 | 804 | |
|---|
| 688 | | -static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) |
|---|
| 689 | | -{ |
|---|
| 690 | | - clk_disable_unprepare(tcon->sclk0); |
|---|
| 691 | | - clk_disable_unprepare(tcon->clk); |
|---|
| 692 | | -} |
|---|
| 693 | | - |
|---|
| 694 | 805 | static int sun4i_tcon_init_irq(struct device *dev, |
|---|
| 695 | 806 | struct sun4i_tcon *tcon) |
|---|
| 696 | 807 | { |
|---|
| .. | .. |
|---|
| 698 | 809 | int irq, ret; |
|---|
| 699 | 810 | |
|---|
| 700 | 811 | irq = platform_get_irq(pdev, 0); |
|---|
| 701 | | - if (irq < 0) { |
|---|
| 702 | | - dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); |
|---|
| 812 | + if (irq < 0) |
|---|
| 703 | 813 | return irq; |
|---|
| 704 | | - } |
|---|
| 705 | 814 | |
|---|
| 706 | 815 | ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, |
|---|
| 707 | 816 | dev_name(dev), tcon); |
|---|
| .. | .. |
|---|
| 713 | 822 | return 0; |
|---|
| 714 | 823 | } |
|---|
| 715 | 824 | |
|---|
| 716 | | -static struct regmap_config sun4i_tcon_regmap_config = { |
|---|
| 825 | +static const struct regmap_config sun4i_tcon_regmap_config = { |
|---|
| 717 | 826 | .reg_bits = 32, |
|---|
| 718 | 827 | .val_bits = 32, |
|---|
| 719 | 828 | .reg_stride = 4, |
|---|
| .. | .. |
|---|
| 882 | 991 | return ERR_PTR(-EINVAL); |
|---|
| 883 | 992 | } |
|---|
| 884 | 993 | |
|---|
| 994 | +static bool sun4i_tcon_connected_to_tcon_top(struct device_node *node) |
|---|
| 995 | +{ |
|---|
| 996 | + struct device_node *remote; |
|---|
| 997 | + bool ret = false; |
|---|
| 998 | + |
|---|
| 999 | + remote = of_graph_get_remote_node(node, 0, -1); |
|---|
| 1000 | + if (remote) { |
|---|
| 1001 | + ret = !!(IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
|---|
| 1002 | + of_match_node(sun8i_tcon_top_of_table, remote)); |
|---|
| 1003 | + of_node_put(remote); |
|---|
| 1004 | + } |
|---|
| 1005 | + |
|---|
| 1006 | + return ret; |
|---|
| 1007 | +} |
|---|
| 1008 | + |
|---|
| 1009 | +static int sun4i_tcon_get_index(struct sun4i_drv *drv) |
|---|
| 1010 | +{ |
|---|
| 1011 | + struct list_head *pos; |
|---|
| 1012 | + int size = 0; |
|---|
| 1013 | + |
|---|
| 1014 | + /* |
|---|
| 1015 | + * Because TCON is added to the list at the end of the probe |
|---|
| 1016 | + * (after this function is called), index of the current TCON |
|---|
| 1017 | + * will be same as current TCON list size. |
|---|
| 1018 | + */ |
|---|
| 1019 | + list_for_each(pos, &drv->tcon_list) |
|---|
| 1020 | + ++size; |
|---|
| 1021 | + |
|---|
| 1022 | + return size; |
|---|
| 1023 | +} |
|---|
| 1024 | + |
|---|
| 885 | 1025 | /* |
|---|
| 886 | 1026 | * On SoCs with the old display pipeline design (Display Engine 1.0), |
|---|
| 887 | 1027 | * we assumed the TCON was always tied to just one backend. However |
|---|
| .. | .. |
|---|
| 930 | 1070 | * connections between the backend and TCON? |
|---|
| 931 | 1071 | */ |
|---|
| 932 | 1072 | 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); |
|---|
| 1073 | + int id; |
|---|
| 1074 | + |
|---|
| 1075 | + /* |
|---|
| 1076 | + * When pipeline has the same number of TCONs and engines which |
|---|
| 1077 | + * are represented by frontends/backends (DE1) or mixers (DE2), |
|---|
| 1078 | + * we match them by their respective IDs. However, if pipeline |
|---|
| 1079 | + * contains TCON TOP, chances are that there are either more |
|---|
| 1080 | + * TCONs than engines (R40) or TCONs with non-consecutive ids. |
|---|
| 1081 | + * (H6). In that case it's easier just use TCON index in list |
|---|
| 1082 | + * as an id. That means that on R40, any 2 TCONs can be enabled |
|---|
| 1083 | + * in DT out of 4 (there are 2 mixers). Due to the design of |
|---|
| 1084 | + * TCON TOP, remaining 2 TCONs can't be connected to anything |
|---|
| 1085 | + * anyway. |
|---|
| 1086 | + */ |
|---|
| 1087 | + if (sun4i_tcon_connected_to_tcon_top(node)) |
|---|
| 1088 | + id = sun4i_tcon_get_index(drv); |
|---|
| 1089 | + else |
|---|
| 1090 | + id = sun4i_tcon_of_get_id_from_port(port); |
|---|
| 935 | 1091 | |
|---|
| 936 | 1092 | /* Get our engine by matching our ID */ |
|---|
| 937 | 1093 | engine = sun4i_tcon_get_engine_by_id(drv, id); |
|---|
| .. | .. |
|---|
| 1060 | 1216 | ret = sun4i_tcon_init_regmap(dev, tcon); |
|---|
| 1061 | 1217 | if (ret) { |
|---|
| 1062 | 1218 | dev_err(dev, "Couldn't init our TCON regmap\n"); |
|---|
| 1063 | | - goto err_free_clocks; |
|---|
| 1219 | + goto err_assert_reset; |
|---|
| 1064 | 1220 | } |
|---|
| 1065 | 1221 | |
|---|
| 1066 | 1222 | if (tcon->quirks->has_channel_0) { |
|---|
| 1067 | 1223 | ret = sun4i_dclk_create(dev, tcon); |
|---|
| 1068 | 1224 | if (ret) { |
|---|
| 1069 | 1225 | dev_err(dev, "Couldn't create our TCON dot clock\n"); |
|---|
| 1070 | | - goto err_free_clocks; |
|---|
| 1226 | + goto err_assert_reset; |
|---|
| 1071 | 1227 | } |
|---|
| 1072 | 1228 | } |
|---|
| 1073 | 1229 | |
|---|
| .. | .. |
|---|
| 1130 | 1286 | err_free_dotclock: |
|---|
| 1131 | 1287 | if (tcon->quirks->has_channel_0) |
|---|
| 1132 | 1288 | sun4i_dclk_free(tcon); |
|---|
| 1133 | | -err_free_clocks: |
|---|
| 1134 | | - sun4i_tcon_free_clocks(tcon); |
|---|
| 1135 | 1289 | err_assert_reset: |
|---|
| 1136 | 1290 | reset_control_assert(tcon->lcd_rst); |
|---|
| 1137 | 1291 | return ret; |
|---|
| .. | .. |
|---|
| 1145 | 1299 | list_del(&tcon->list); |
|---|
| 1146 | 1300 | if (tcon->quirks->has_channel_0) |
|---|
| 1147 | 1301 | sun4i_dclk_free(tcon); |
|---|
| 1148 | | - sun4i_tcon_free_clocks(tcon); |
|---|
| 1149 | 1302 | } |
|---|
| 1150 | 1303 | |
|---|
| 1151 | 1304 | static const struct component_ops sun4i_tcon_ops = { |
|---|
| .. | .. |
|---|
| 1246 | 1399 | return 0; |
|---|
| 1247 | 1400 | } |
|---|
| 1248 | 1401 | |
|---|
| 1402 | +static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon, |
|---|
| 1403 | + const struct drm_encoder *encoder) |
|---|
| 1404 | +{ |
|---|
| 1405 | + struct device_node *port, *remote; |
|---|
| 1406 | + struct platform_device *pdev; |
|---|
| 1407 | + int id, ret; |
|---|
| 1408 | + |
|---|
| 1409 | + /* find TCON TOP platform device and TCON id */ |
|---|
| 1410 | + |
|---|
| 1411 | + port = of_graph_get_port_by_id(tcon->dev->of_node, 0); |
|---|
| 1412 | + if (!port) |
|---|
| 1413 | + return -EINVAL; |
|---|
| 1414 | + |
|---|
| 1415 | + id = sun4i_tcon_of_get_id_from_port(port); |
|---|
| 1416 | + of_node_put(port); |
|---|
| 1417 | + |
|---|
| 1418 | + remote = of_graph_get_remote_node(tcon->dev->of_node, 0, -1); |
|---|
| 1419 | + if (!remote) |
|---|
| 1420 | + return -EINVAL; |
|---|
| 1421 | + |
|---|
| 1422 | + pdev = of_find_device_by_node(remote); |
|---|
| 1423 | + of_node_put(remote); |
|---|
| 1424 | + if (!pdev) |
|---|
| 1425 | + return -EINVAL; |
|---|
| 1426 | + |
|---|
| 1427 | + if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && |
|---|
| 1428 | + encoder->encoder_type == DRM_MODE_ENCODER_TMDS) { |
|---|
| 1429 | + ret = sun8i_tcon_top_set_hdmi_src(&pdev->dev, id); |
|---|
| 1430 | + if (ret) { |
|---|
| 1431 | + put_device(&pdev->dev); |
|---|
| 1432 | + return ret; |
|---|
| 1433 | + } |
|---|
| 1434 | + } |
|---|
| 1435 | + |
|---|
| 1436 | + if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP)) { |
|---|
| 1437 | + ret = sun8i_tcon_top_de_config(&pdev->dev, tcon->id, id); |
|---|
| 1438 | + if (ret) { |
|---|
| 1439 | + put_device(&pdev->dev); |
|---|
| 1440 | + return ret; |
|---|
| 1441 | + } |
|---|
| 1442 | + } |
|---|
| 1443 | + |
|---|
| 1444 | + return 0; |
|---|
| 1445 | +} |
|---|
| 1446 | + |
|---|
| 1249 | 1447 | static const struct sun4i_tcon_quirks sun4i_a10_quirks = { |
|---|
| 1250 | 1448 | .has_channel_0 = true, |
|---|
| 1251 | 1449 | .has_channel_1 = true, |
|---|
| .. | .. |
|---|
| 1276 | 1474 | .dclk_min_div = 1, |
|---|
| 1277 | 1475 | }; |
|---|
| 1278 | 1476 | |
|---|
| 1477 | +static const struct sun4i_tcon_quirks sun7i_a20_tcon0_quirks = { |
|---|
| 1478 | + .supports_lvds = true, |
|---|
| 1479 | + .has_channel_0 = true, |
|---|
| 1480 | + .has_channel_1 = true, |
|---|
| 1481 | + .dclk_min_div = 4, |
|---|
| 1482 | + /* Same display pipeline structure as A10 */ |
|---|
| 1483 | + .set_mux = sun4i_a10_tcon_set_mux, |
|---|
| 1484 | + .setup_lvds_phy = sun4i_tcon_setup_lvds_phy, |
|---|
| 1485 | +}; |
|---|
| 1486 | + |
|---|
| 1279 | 1487 | static const struct sun4i_tcon_quirks sun7i_a20_quirks = { |
|---|
| 1280 | 1488 | .has_channel_0 = true, |
|---|
| 1281 | 1489 | .has_channel_1 = true, |
|---|
| .. | .. |
|---|
| 1288 | 1496 | .has_channel_0 = true, |
|---|
| 1289 | 1497 | .has_lvds_alt = true, |
|---|
| 1290 | 1498 | .dclk_min_div = 1, |
|---|
| 1499 | + .setup_lvds_phy = sun6i_tcon_setup_lvds_phy, |
|---|
| 1500 | + .supports_lvds = true, |
|---|
| 1291 | 1501 | }; |
|---|
| 1292 | 1502 | |
|---|
| 1293 | 1503 | static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { |
|---|
| 1294 | 1504 | .supports_lvds = true, |
|---|
| 1295 | 1505 | .has_channel_0 = true, |
|---|
| 1296 | 1506 | .dclk_min_div = 1, |
|---|
| 1507 | + .setup_lvds_phy = sun6i_tcon_setup_lvds_phy, |
|---|
| 1297 | 1508 | }; |
|---|
| 1298 | 1509 | |
|---|
| 1299 | 1510 | static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { |
|---|
| 1300 | 1511 | .has_channel_1 = true, |
|---|
| 1512 | +}; |
|---|
| 1513 | + |
|---|
| 1514 | +static const struct sun4i_tcon_quirks sun8i_r40_tv_quirks = { |
|---|
| 1515 | + .has_channel_1 = true, |
|---|
| 1516 | + .polarity_in_ch0 = true, |
|---|
| 1517 | + .set_mux = sun8i_r40_tcon_tv_set_mux, |
|---|
| 1301 | 1518 | }; |
|---|
| 1302 | 1519 | |
|---|
| 1303 | 1520 | static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { |
|---|
| .. | .. |
|---|
| 1323 | 1540 | { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, |
|---|
| 1324 | 1541 | { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, |
|---|
| 1325 | 1542 | { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, |
|---|
| 1543 | + { .compatible = "allwinner,sun7i-a20-tcon0", .data = &sun7i_a20_tcon0_quirks }, |
|---|
| 1544 | + { .compatible = "allwinner,sun7i-a20-tcon1", .data = &sun7i_a20_quirks }, |
|---|
| 1545 | + { .compatible = "allwinner,sun8i-a23-tcon", .data = &sun8i_a33_quirks }, |
|---|
| 1326 | 1546 | { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, |
|---|
| 1327 | 1547 | { .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks }, |
|---|
| 1328 | 1548 | { .compatible = "allwinner,sun8i-a83t-tcon-tv", .data = &sun8i_a83t_tv_quirks }, |
|---|
| 1549 | + { .compatible = "allwinner,sun8i-r40-tcon-tv", .data = &sun8i_r40_tv_quirks }, |
|---|
| 1329 | 1550 | { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, |
|---|
| 1330 | 1551 | { .compatible = "allwinner,sun9i-a80-tcon-lcd", .data = &sun9i_a80_tcon_lcd_quirks }, |
|---|
| 1331 | 1552 | { .compatible = "allwinner,sun9i-a80-tcon-tv", .data = &sun9i_a80_tcon_tv_quirks }, |
|---|