summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/sun4i/sun4i_tcon.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c')
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_tcon.c90
1 files changed, 78 insertions, 12 deletions
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0cebb2db5b99..c78cd35a1294 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -12,6 +12,7 @@
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder.h>
@@ -277,10 +278,64 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon,
SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
}
+static void sun4i_tcon0_mode_set_dithering(struct sun4i_tcon *tcon,
+ const struct drm_connector *connector)
+{
+ u32 bus_format = 0;
+ u32 val = 0;
+
+ /* XXX Would this ever happen? */
+ if (!connector)
+ return;
+
+ /*
+ * FIXME: Undocumented bits
+ *
+ * The whole dithering process and these parameters are not
+ * explained in the vendor documents or BSP kernel code.
+ */
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_PR_REG, 0x11111111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_PG_REG, 0x11111111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_PB_REG, 0x11111111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_LR_REG, 0x11111111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_LG_REG, 0x11111111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_SEED_LB_REG, 0x11111111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL0_REG, 0x01010000);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL1_REG, 0x15151111);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL2_REG, 0x57575555);
+ regmap_write(tcon->regs, SUN4I_TCON0_FRM_TBL3_REG, 0x7f7f7777);
+
+ /* Do dithering if panel only supports 6 bits per color */
+ if (connector->display_info.bpc == 6)
+ val |= SUN4I_TCON0_FRM_CTL_EN;
+
+ if (connector->display_info.num_bus_formats == 1)
+ bus_format = connector->display_info.bus_formats[0];
+
+ /* Check the connection format */
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ /* R and B components are only 5 bits deep */
+ val |= SUN4I_TCON0_FRM_CTL_MODE_R;
+ val |= SUN4I_TCON0_FRM_CTL_MODE_B;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ /* Fall through: enable dithering */
+ val |= SUN4I_TCON0_FRM_CTL_EN;
+ break;
+ }
+
+ /* Write dithering settings */
+ regmap_write(tcon->regs, SUN4I_TCON_FRM_CTL_REG, val);
+}
+
static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon *tcon,
- struct mipi_dsi_device *device,
+ const struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
+ /* TODO support normal CPU interface modes */
+ struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder);
+ struct mipi_dsi_device *device = dsi->device;
u8 bpp = mipi_dsi_pixel_format_to_bpp(device->format);
u8 lanes = device->lanes;
u32 block_space, start_delay;
@@ -291,6 +346,9 @@ static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon *tcon,
sun4i_tcon0_mode_set_common(tcon, mode);
+ /* Set dithering if needed */
+ sun4i_tcon0_mode_set_dithering(tcon, sun4i_tcon_get_connector(encoder));
+
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
SUN4I_TCON0_CTL_IF_MASK,
SUN4I_TCON0_CTL_IF_8080);
@@ -356,6 +414,9 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
tcon->dclk_max_div = 7;
sun4i_tcon0_mode_set_common(tcon, mode);
+ /* Set dithering if needed */
+ sun4i_tcon0_mode_set_dithering(tcon, sun4i_tcon_get_connector(encoder));
+
/* Adjust clock delay */
clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@@ -429,6 +490,9 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
tcon->dclk_max_div = 127;
sun4i_tcon0_mode_set_common(tcon, mode);
+ /* Set dithering if needed */
+ sun4i_tcon0_mode_set_dithering(tcon, tcon->panel->connector);
+
/* Adjust clock delay */
clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@@ -610,16 +674,10 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
const struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
- struct sun6i_dsi *dsi;
-
switch (encoder->encoder_type) {
case DRM_MODE_ENCODER_DSI:
- /*
- * This is not really elegant, but it's the "cleaner"
- * way I could think of...
- */
- dsi = encoder_to_sun6i_dsi(encoder);
- sun4i_tcon0_mode_set_cpu(tcon, dsi->device, mode);
+ /* DSI is tied to special case of CPU interface */
+ sun4i_tcon0_mode_set_cpu(tcon, encoder, mode);
break;
case DRM_MODE_ENCODER_LVDS:
sun4i_tcon0_mode_set_lvds(tcon, encoder, mode);
@@ -916,7 +974,8 @@ static bool sun4i_tcon_connected_to_tcon_top(struct device_node *node)
remote = of_graph_get_remote_node(node, 0, -1);
if (remote) {
- ret = !!of_match_node(sun8i_tcon_top_of_table, remote);
+ ret = !!(IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) &&
+ of_match_node(sun8i_tcon_top_of_table, remote));
of_node_put(remote);
}
@@ -1344,13 +1403,20 @@ static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon,
if (!pdev)
return -EINVAL;
- if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS) {
+ if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) &&
+ encoder->encoder_type == DRM_MODE_ENCODER_TMDS) {
ret = sun8i_tcon_top_set_hdmi_src(&pdev->dev, id);
if (ret)
return ret;
}
- return sun8i_tcon_top_de_config(&pdev->dev, tcon->id, id);
+ if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP)) {
+ ret = sun8i_tcon_top_de_config(&pdev->dev, tcon->id, id);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static const struct sun4i_tcon_quirks sun4i_a10_quirks = {