[PATCH 15/23] drm/sun4i: Add LVDS support
From: Priit Laes <hidden>
Date: 2017-10-23 20:17:09
Also in:
dri-devel, linux-clk, lkml
On Tue, Oct 17, 2017 at 11:06:22AM +0200, Maxime Ripard wrote:
quoted hunk ↗ jump to hunk
The TCON supports the LVDS interface to output to a panel or a bridge. Let's add support for it. Signed-off-by: Maxime Ripard <redacted> --- drivers/gpu/drm/sun4i/Makefile | 1 +- drivers/gpu/drm/sun4i/sun4i_lvds.c | 183 ++++++++++++++++++++++++++++- drivers/gpu/drm/sun4i/sun4i_lvds.h | 18 +++- drivers/gpu/drm/sun4i/sun4i_tcon.c | 193 +++++++++++++++++++++++++++++- drivers/gpu/drm/sun4i/sun4i_tcon.h | 25 ++++- 5 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sun4i/sun4i_lvds.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_lvds.hdiff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index cfba2c07519c..6fee15d016ef 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile@@ -10,6 +10,7 @@ sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o sun4i-tcon-y += sun4i_tcon.o sun4i-tcon-y += sun4i_rgb.o +sun4i-tcon-y += sun4i_lvds.o sun4i-tcon-y += sun4i_dotclock.o sun4i-tcon-y += sun4i_crtc.o
[...]
quoted hunk ↗ jump to hunk
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 3efa1ab045cd..6a20a467ee6d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c@@ -31,10 +31,52 @@ #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"
[...]
quoted hunk ↗ jump to hunk
static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, bool enabled) {@@ -69,9 +111,13 @@ 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 +130,47 @@ void sun4i_tcon_set_status(struct sun4i_tcon *tcon, return; } + if (is_lvds && !enabled) + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, + SUN4I_TCON0_LVDS_IF_EN, 0); + 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) { + u8 val; + + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, + SUN4I_TCON0_LVDS_IF_EN, + SUN4I_TCON0_LVDS_IF_EN); +
OK, this part below seems to be quite A83T specific, as A10/A20
has different register bits.
For example ANA0 bits:
A10:
#define SUN4I_TCON0_LVDS_ANA0_EN_MB BIT(22)
But here it is:
#define SUN4I_TCON0_LVDS_ANA0_EN_MB BIT(31)
Also, the programming sequence for A10/A20 (from u-boot),
and my initial patchset. (I haven't yet had time to get output working
on top of your stuff).
1. Set ANA0 (0x220)
2. Set ANA1
3. Update ANA1
regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN4I_TCON0_LVDS_ANA0_CK_EN |
SUN4I_TCON0_LVDS_ANA0_REG_V |
SUN4I_TCON0_LVDS_ANA0_REG_C |
SUN4I_TCON0_LVDS_ANA0_EN_MB |
SUN4I_TCON0_LVDS_ANA0_PD |
SUN4I_TCON0_LVDS_ANA0_DCHS);
udelay(2000);
regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG,
SUN4I_TCON0_LVDS_ANA1_INIT);
udelay(1000);
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG,
SUN4I_TCON0_LVDS_ANA1_UPDATE,
SUN4I_TCON0_LVDS_ANA1_UPDATE);
quoted hunk ↗ jump to hunk
+ regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN4I_TCON0_LVDS_ANA0_C(2) | + SUN4I_TCON0_LVDS_ANA0_V(3) | + SUN4I_TCON0_LVDS_ANA0_PD(2) | + SUN4I_TCON0_LVDS_ANA0_EN_LDO); + udelay(2); + + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN4I_TCON0_LVDS_ANA0_EN_MB, + SUN4I_TCON0_LVDS_ANA0_EN_MB); + udelay(2); + + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN4I_TCON0_LVDS_ANA0_EN_DRVC, + SUN4I_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, + SUN4I_TCON0_LVDS_ANA0_EN_DRVD(0xf), + SUN4I_TCON0_LVDS_ANA0_EN_DRVD(val)); + } + sun4i_tcon_channel_set_status(tcon, channel, enabled); }@@ -170,6 +253,78 @@ 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(0x554) | + SUN4I_TCON0_BASIC1_H_BACKPORCH(0xa0)); + + /* + * 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(0x17)); + + 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, 0); + + /* 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); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0xe0000000); +} + static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, const struct drm_display_mode *mode) {@@ -336,6 +491,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 +825,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; int ret; engine = sun4i_tcon_find_engine(drv, dev->of_node);@@ -698,6 +858,26 @@ 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 = true; + reset_control_reset(tcon->lvds_rst); + } else { + has_lvds = false; + dev_warn(dev, + "Missing LVDS reset property, you should consider upgrading your DT\n"); + } + ret = sun4i_tcon_init_clocks(dev, tcon); if (ret) { dev_err(dev, "Couldn't init our TCON clocks\n");@@ -729,7 +909,18 @@ 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 (has_lvds && of_device_is_compatible(remote, "panel-lvds")) + ret = sun4i_lvds_init(drm, tcon); + else + ret = sun4i_rgb_init(drm, tcon); + of_node_put(remote); + if (ret < 0) goto err_free_clocks;diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 4141fbd97ddf..382689e5396e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h@@ -70,7 +70,21 @@ #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)@@ -131,6 +145,16 @@ #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
Although register address is same as A10/A20, its contents seem to be different here...
quoted hunk ↗ jump to hunk
+#define SUN4I_TCON0_LVDS_ANA0_EN_MB BIT(31) +#define SUN4I_TCON0_LVDS_ANA0_EN_LDO BIT(30) +#define SUN4I_TCON0_LVDS_ANA0_EN_DRVC BIT(24) +#define SUN4I_TCON0_LVDS_ANA0_EN_DRVD(x) (((x) & 0xf) << 20) +#define SUN4I_TCON0_LVDS_ANA0_C(x) (((x) & 3) << 17) +#define SUN4I_TCON0_LVDS_ANA0_V(x) (((x) & 3) << 8) +#define SUN4I_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@@ -174,6 +198,7 @@ struct sun4i_tcon { /* Reset control */ struct reset_control *lcd_rst; + struct reset_control *lvds_rst; struct drm_panel *panel;-- git-series 0.9.1