drm/sun4i: Add LVDS support
authorMaxime Ripard <maxime.ripard@free-electrons.com>
Thu, 21 Dec 2017 11:02:33 +0000 (12:02 +0100)
committerMaxime Ripard <maxime.ripard@free-electrons.com>
Thu, 4 Jan 2018 19:37:17 +0000 (20:37 +0100)
The TCON supports the LVDS interface to output to a panel or a bridge.
Let's add support for it.

Reviewed-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/7fbb85f33ee1d5009fde4f0d7d236e11ca58b114.1513854122.git-series.maxime.ripard@free-electrons.com
drivers/gpu/drm/sun4i/Makefile
drivers/gpu/drm/sun4i/sun4i_lvds.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_lvds.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_tcon.c
drivers/gpu/drm/sun4i/sun4i_tcon.h

index 82a6ac57fbe337dfaf897f4b5d8c754e0a48eec6..2b37a6abbb1d7d7e073c05684634a03e8e73c14e 100644 (file)
@@ -15,6 +15,7 @@ sun8i-mixer-y                 += sun8i_mixer.o sun8i_ui_layer.o \
 
 sun4i-tcon-y                   += sun4i_crtc.o
 sun4i-tcon-y                   += sun4i_dotclock.o
+sun4i-tcon-y                   += sun4i_lvds.o
 sun4i-tcon-y                   += sun4i_tcon.o
 sun4i-tcon-y                   += sun4i_rgb.o
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c
new file mode 100644 (file)
index 0000000..be3f14d
--- /dev/null
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Free Electrons
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_tcon.h"
+#include "sun4i_lvds.h"
+
+struct sun4i_lvds {
+       struct drm_connector    connector;
+       struct drm_encoder      encoder;
+
+       struct sun4i_tcon       *tcon;
+};
+
+static inline struct sun4i_lvds *
+drm_connector_to_sun4i_lvds(struct drm_connector *connector)
+{
+       return container_of(connector, struct sun4i_lvds,
+                           connector);
+}
+
+static inline struct sun4i_lvds *
+drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder)
+{
+       return container_of(encoder, struct sun4i_lvds,
+                           encoder);
+}
+
+static int sun4i_lvds_get_modes(struct drm_connector *connector)
+{
+       struct sun4i_lvds *lvds =
+               drm_connector_to_sun4i_lvds(connector);
+       struct sun4i_tcon *tcon = lvds->tcon;
+
+       return drm_panel_get_modes(tcon->panel);
+}
+
+static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = {
+       .get_modes      = sun4i_lvds_get_modes,
+};
+
+static void
+sun4i_lvds_connector_destroy(struct drm_connector *connector)
+{
+       struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector);
+       struct sun4i_tcon *tcon = lvds->tcon;
+
+       drm_panel_detach(tcon->panel);
+       drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs sun4i_lvds_con_funcs = {
+       .fill_modes             = drm_helper_probe_single_connector_modes,
+       .destroy                = sun4i_lvds_connector_destroy,
+       .reset                  = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
+};
+
+static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder)
+{
+       struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
+       struct sun4i_tcon *tcon = lvds->tcon;
+
+       DRM_DEBUG_DRIVER("Enabling LVDS output\n");
+
+       if (!IS_ERR(tcon->panel)) {
+               drm_panel_prepare(tcon->panel);
+               drm_panel_enable(tcon->panel);
+       }
+}
+
+static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder)
+{
+       struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
+       struct sun4i_tcon *tcon = lvds->tcon;
+
+       DRM_DEBUG_DRIVER("Disabling LVDS output\n");
+
+       if (!IS_ERR(tcon->panel)) {
+               drm_panel_disable(tcon->panel);
+               drm_panel_unprepare(tcon->panel);
+       }
+}
+
+static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = {
+       .disable        = sun4i_lvds_encoder_disable,
+       .enable         = sun4i_lvds_encoder_enable,
+};
+
+static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = {
+       .destroy        = drm_encoder_cleanup,
+};
+
+int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
+{
+       struct drm_encoder *encoder;
+       struct drm_bridge *bridge;
+       struct sun4i_lvds *lvds;
+       int ret;
+
+       lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL);
+       if (!lvds)
+               return -ENOMEM;
+       lvds->tcon = tcon;
+       encoder = &lvds->encoder;
+
+       ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0,
+                                         &tcon->panel, &bridge);
+       if (ret) {
+               dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n");
+               return 0;
+       }
+
+       drm_encoder_helper_add(&lvds->encoder,
+                              &sun4i_lvds_enc_helper_funcs);
+       ret = drm_encoder_init(drm,
+                              &lvds->encoder,
+                              &sun4i_lvds_enc_funcs,
+                              DRM_MODE_ENCODER_LVDS,
+                              NULL);
+       if (ret) {
+               dev_err(drm->dev, "Couldn't initialise the lvds encoder\n");
+               goto err_out;
+       }
+
+       /* The LVDS encoder can only work with the TCON channel 0 */
+       lvds->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc));
+
+       if (tcon->panel) {
+               drm_connector_helper_add(&lvds->connector,
+                                        &sun4i_lvds_con_helper_funcs);
+               ret = drm_connector_init(drm, &lvds->connector,
+                                        &sun4i_lvds_con_funcs,
+                                        DRM_MODE_CONNECTOR_LVDS);
+               if (ret) {
+                       dev_err(drm->dev, "Couldn't initialise the lvds connector\n");
+                       goto err_cleanup_connector;
+               }
+
+               drm_mode_connector_attach_encoder(&lvds->connector,
+                                                 &lvds->encoder);
+
+               ret = drm_panel_attach(tcon->panel, &lvds->connector);
+               if (ret) {
+                       dev_err(drm->dev, "Couldn't attach our panel\n");
+                       goto err_cleanup_connector;
+               }
+       }
+
+       if (bridge) {
+               ret = drm_bridge_attach(encoder, bridge, NULL);
+               if (ret) {
+                       dev_err(drm->dev, "Couldn't attach our bridge\n");
+                       goto err_cleanup_connector;
+               }
+       }
+
+       return 0;
+
+err_cleanup_connector:
+       drm_encoder_cleanup(&lvds->encoder);
+err_out:
+       return ret;
+}
+EXPORT_SYMBOL(sun4i_lvds_init);
diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.h b/drivers/gpu/drm/sun4i/sun4i_lvds.h
new file mode 100644 (file)
index 0000000..f3e90fa
--- /dev/null
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Free Electrons
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#ifndef _SUN4I_LVDS_H_
+#define _SUN4I_LVDS_H_
+
+int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon);
+
+#endif /* _SUN4I_LVDS_H_ */
index 41d325c0420fa234c546034ac40c5484afbf9feb..65c18f6ef693d1767fa792fafef4b660ee8a5eb3 100644 (file)
 #include "sun4i_crtc.h"
 #include "sun4i_dotclock.h"
 #include "sun4i_drv.h"
+#include "sun4i_lvds.h"
 #include "sun4i_rgb.h"
 #include "sun4i_tcon.h"
 #include "sunxi_engine.h"
 
+static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder)
+{
+       struct drm_connector *connector;
+       struct drm_connector_list_iter iter;
+
+       drm_connector_list_iter_begin(encoder->dev, &iter);
+       drm_for_each_connector_iter(connector, &iter)
+               if (connector->encoder == encoder) {
+                       drm_connector_list_iter_end(&iter);
+                       return connector;
+               }
+       drm_connector_list_iter_end(&iter);
+
+       return NULL;
+}
+
+static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder)
+{
+       struct drm_connector *connector;
+       struct drm_display_info *info;
+
+       connector = sun4i_tcon_get_connector(encoder);
+       if (!connector)
+               return -EINVAL;
+
+       info = &connector->display_info;
+       if (info->num_bus_formats != 1)
+               return -EINVAL;
+
+       switch (info->bus_formats[0]) {
+       case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+               return 18;
+
+       case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+       case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+               return 24;
+       }
+
+       return -EINVAL;
+}
+
 static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
                                          bool enabled)
 {
@@ -65,13 +107,63 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
                clk_disable_unprepare(clk);
 }
 
+static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon,
+                                      const struct drm_encoder *encoder,
+                                      bool enabled)
+{
+       if (enabled) {
+               u8 val;
+
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
+                                  SUN4I_TCON0_LVDS_IF_EN,
+                                  SUN4I_TCON0_LVDS_IF_EN);
+
+               /*
+                * As their name suggest, these values only apply to the A31
+                * and later SoCs. We'll have to rework this when merging
+                * support for the older SoCs.
+                */
+               regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+                            SUN6I_TCON0_LVDS_ANA0_C(2) |
+                            SUN6I_TCON0_LVDS_ANA0_V(3) |
+                            SUN6I_TCON0_LVDS_ANA0_PD(2) |
+                            SUN6I_TCON0_LVDS_ANA0_EN_LDO);
+               udelay(2);
+
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+                                  SUN6I_TCON0_LVDS_ANA0_EN_MB,
+                                  SUN6I_TCON0_LVDS_ANA0_EN_MB);
+               udelay(2);
+
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+                                  SUN6I_TCON0_LVDS_ANA0_EN_DRVC,
+                                  SUN6I_TCON0_LVDS_ANA0_EN_DRVC);
+
+               if (sun4i_tcon_get_pixel_depth(encoder) == 18)
+                       val = 7;
+               else
+                       val = 0xf;
+
+               regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
+                                 SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf),
+                                 SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val));
+       } else {
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
+                                  SUN4I_TCON0_LVDS_IF_EN, 0);
+       }
+}
+
 void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
                           const struct drm_encoder *encoder,
                           bool enabled)
 {
+       bool is_lvds = false;
        int channel;
 
        switch (encoder->encoder_type) {
+       case DRM_MODE_ENCODER_LVDS:
+               is_lvds = true;
+               /* Fallthrough */
        case DRM_MODE_ENCODER_NONE:
                channel = 0;
                break;
@@ -84,10 +176,16 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
                return;
        }
 
+       if (is_lvds && !enabled)
+               sun4i_tcon_lvds_set_status(tcon, encoder, false);
+
        regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
                           SUN4I_TCON_GCTL_TCON_ENABLE,
                           enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0);
 
+       if (is_lvds && enabled)
+               sun4i_tcon_lvds_set_status(tcon, encoder, true);
+
        sun4i_tcon_channel_set_status(tcon, channel, enabled);
 }
 
@@ -170,6 +268,75 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon,
                     SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
 }
 
+static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
+                                     const struct drm_encoder *encoder,
+                                     const struct drm_display_mode *mode)
+{
+       unsigned int bp;
+       u8 clk_delay;
+       u32 reg, val = 0;
+
+       tcon->dclk_min_div = 7;
+       tcon->dclk_max_div = 7;
+       sun4i_tcon0_mode_set_common(tcon, mode);
+
+       /* Adjust clock delay */
+       clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
+       regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
+                          SUN4I_TCON0_CTL_CLK_DELAY_MASK,
+                          SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
+
+       /*
+        * This is called a backporch in the register documentation,
+        * but it really is the back porch + hsync
+        */
+       bp = mode->crtc_htotal - mode->crtc_hsync_start;
+       DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
+                        mode->crtc_htotal, bp);
+
+       /* Set horizontal display timings */
+       regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG,
+                    SUN4I_TCON0_BASIC1_H_TOTAL(mode->htotal) |
+                    SUN4I_TCON0_BASIC1_H_BACKPORCH(bp));
+
+       /*
+        * This is called a backporch in the register documentation,
+        * but it really is the back porch + hsync
+        */
+       bp = mode->crtc_vtotal - mode->crtc_vsync_start;
+       DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
+                        mode->crtc_vtotal, bp);
+
+       /* Set vertical display timings */
+       regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
+                    SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
+                    SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
+
+       reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 |
+               SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL |
+               SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL;
+       if (sun4i_tcon_get_pixel_depth(encoder) == 24)
+               reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS;
+       else
+               reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS;
+
+       regmap_write(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, reg);
+
+       /* Setup the polarity of the various signals */
+       if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+               val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE;
+
+       if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+               val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
+
+       regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val);
+
+       /* Map output pins to channel 0 */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+                          SUN4I_TCON_GCTL_IOMAP_MASK,
+                          SUN4I_TCON_GCTL_IOMAP_TCON0);
+}
+
 static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
                                     const struct drm_display_mode *mode)
 {
@@ -336,6 +503,9 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
                         const struct drm_display_mode *mode)
 {
        switch (encoder->encoder_type) {
+       case DRM_MODE_ENCODER_LVDS:
+               sun4i_tcon0_mode_set_lvds(tcon, encoder, mode);
+               break;
        case DRM_MODE_ENCODER_NONE:
                sun4i_tcon0_mode_set_rgb(tcon, mode);
                sun4i_tcon_set_mux(tcon, 0, encoder);
@@ -667,7 +837,9 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
        struct drm_device *drm = data;
        struct sun4i_drv *drv = drm->dev_private;
        struct sunxi_engine *engine;
+       struct device_node *remote;
        struct sun4i_tcon *tcon;
+       bool has_lvds_rst, has_lvds_alt, can_lvds;
        int ret;
 
        engine = sun4i_tcon_find_engine(drv, dev->of_node);
@@ -698,6 +870,54 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
                return ret;
        }
 
+       /*
+        * This can only be made optional since we've had DT nodes
+        * without the LVDS reset properties.
+        *
+        * If the property is missing, just disable LVDS, and print a
+        * warning.
+        */
+       tcon->lvds_rst = devm_reset_control_get_optional(dev, "lvds");
+       if (IS_ERR(tcon->lvds_rst)) {
+               dev_err(dev, "Couldn't get our reset line\n");
+               return PTR_ERR(tcon->lvds_rst);
+       } else if (tcon->lvds_rst) {
+               has_lvds_rst = true;
+               reset_control_reset(tcon->lvds_rst);
+       } else {
+               has_lvds_rst = false;
+       }
+
+       /*
+        * This can only be made optional since we've had DT nodes
+        * without the LVDS reset properties.
+        *
+        * If the property is missing, just disable LVDS, and print a
+        * warning.
+        */
+       if (tcon->quirks->has_lvds_alt) {
+               tcon->lvds_pll = devm_clk_get(dev, "lvds-alt");
+               if (IS_ERR(tcon->lvds_pll)) {
+                       if (PTR_ERR(tcon->lvds_pll) == -ENOENT) {
+                               has_lvds_alt = false;
+                       } else {
+                               dev_err(dev, "Couldn't get the LVDS PLL\n");
+                               return PTR_ERR(tcon->lvds_rst);
+                       }
+               } else {
+                       has_lvds_alt = true;
+               }
+       }
+
+       if (!has_lvds_rst || (tcon->quirks->has_lvds_alt && !has_lvds_alt)) {
+               dev_warn(dev,
+                        "Missing LVDS properties, Please upgrade your DT\n");
+               dev_warn(dev, "LVDS output disabled\n");
+               can_lvds = false;
+       } else {
+               can_lvds = true;
+       }
+
        ret = sun4i_tcon_init_clocks(dev, tcon);
        if (ret) {
                dev_err(dev, "Couldn't init our TCON clocks\n");
@@ -729,7 +949,21 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
                goto err_free_clocks;
        }
 
-       ret = sun4i_rgb_init(drm, tcon);
+       /*
+        * If we have an LVDS panel connected to the TCON, we should
+        * just probe the LVDS connector. Otherwise, just probe RGB as
+        * we used to.
+        */
+       remote = of_graph_get_remote_node(dev->of_node, 1, 0);
+       if (of_device_is_compatible(remote, "panel-lvds"))
+               if (can_lvds)
+                       ret = sun4i_lvds_init(drm, tcon);
+               else
+                       ret = -EINVAL;
+       else
+               ret = sun4i_rgb_init(drm, tcon);
+       of_node_put(remote);
+
        if (ret < 0)
                goto err_free_clocks;
 
@@ -879,6 +1113,7 @@ static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
 
 static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
        .has_channel_1          = true,
+       .has_lvds_alt           = true,
        .needs_de_be_mux        = true,
        .set_mux                = sun6i_tcon_set_mux,
 };
@@ -895,7 +1130,7 @@ static const struct sun4i_tcon_quirks sun7i_a20_quirks = {
 };
 
 static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
-       /* nothing is supported */
+       .has_lvds_alt           = true,
 };
 
 static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
index bd3ad7684870d30e2b6a94f84334d96f44cddbdd..b761c7b823c560536b0f44a3c6eab6337d3e32d7 100644 (file)
 #define SUN4I_TCON0_TTL2_REG                   0x78
 #define SUN4I_TCON0_TTL3_REG                   0x7c
 #define SUN4I_TCON0_TTL4_REG                   0x80
+
 #define SUN4I_TCON0_LVDS_IF_REG                        0x84
+#define SUN4I_TCON0_LVDS_IF_EN                         BIT(31)
+#define SUN4I_TCON0_LVDS_IF_BITWIDTH_MASK              BIT(26)
+#define SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS            (1 << 26)
+#define SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS            (0 << 26)
+#define SUN4I_TCON0_LVDS_IF_CLK_SEL_MASK               BIT(20)
+#define SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0              (1 << 20)
+#define SUN4I_TCON0_LVDS_IF_CLK_POL_MASK               BIT(4)
+#define SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL             (1 << 4)
+#define SUN4I_TCON0_LVDS_IF_CLK_POL_INV                        (0 << 4)
+#define SUN4I_TCON0_LVDS_IF_DATA_POL_MASK              GENMASK(3, 0)
+#define SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL            (0xf)
+#define SUN4I_TCON0_LVDS_IF_DATA_POL_INV               (0)
+
 #define SUN4I_TCON0_IO_POL_REG                 0x88
 #define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase)           ((phase & 3) << 28)
 #define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE              BIT(25)
 #define SUN4I_TCON_CEU_RANGE_G_REG             0x144
 #define SUN4I_TCON_CEU_RANGE_B_REG             0x148
 #define SUN4I_TCON_MUX_CTRL_REG                        0x200
+
+#define SUN4I_TCON0_LVDS_ANA0_REG              0x220
+#define SUN6I_TCON0_LVDS_ANA0_EN_MB                    BIT(31)
+#define SUN6I_TCON0_LVDS_ANA0_EN_LDO                   BIT(30)
+#define SUN6I_TCON0_LVDS_ANA0_EN_DRVC                  BIT(24)
+#define SUN6I_TCON0_LVDS_ANA0_EN_DRVD(x)               (((x) & 0xf) << 20)
+#define SUN6I_TCON0_LVDS_ANA0_C(x)                     (((x) & 3) << 17)
+#define SUN6I_TCON0_LVDS_ANA0_V(x)                     (((x) & 3) << 8)
+#define SUN6I_TCON0_LVDS_ANA0_PD(x)                    (((x) & 3) << 4)
+
 #define SUN4I_TCON1_FILL_CTL_REG               0x300
 #define SUN4I_TCON1_FILL_BEG0_REG              0x304
 #define SUN4I_TCON1_FILL_END0_REG              0x308
@@ -149,6 +173,7 @@ struct sun4i_tcon;
 
 struct sun4i_tcon_quirks {
        bool    has_channel_1;  /* a33 does not have channel 1 */
+       bool    has_lvds_alt;   /* Does the LVDS clock have a parent other than the TCON clock? */
        bool    needs_de_be_mux; /* sun6i needs mux to select backend */
 
        /* callback to handle tcon muxing options */
@@ -167,6 +192,9 @@ struct sun4i_tcon {
        struct clk                      *sclk0;
        struct clk                      *sclk1;
 
+       /* Possible mux for the LVDS clock */
+       struct clk                      *lvds_pll;
+
        /* Pixel clock */
        struct clk                      *dclk;
        u8                              dclk_max_div;
@@ -174,6 +202,7 @@ struct sun4i_tcon {
 
        /* Reset control */
        struct reset_control            *lcd_rst;
+       struct reset_control            *lvds_rst;
 
        struct drm_panel                *panel;