summaryrefslogtreecommitdiff
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.c52
1 files changed, 52 insertions, 0 deletions
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0d6c5ed44795..1a114e380f13 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -17,6 +17,7 @@
#include <drm/drm_encoder.h>
#include <drm/drm_modes.h>
#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
#include <uapi/drm/drm_mode.h>
@@ -343,6 +344,9 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
const struct drm_display_mode *mode)
{
+ struct drm_panel *panel = tcon->panel;
+ struct drm_connector *connector = panel->connector;
+ struct drm_display_info display_info = connector->display_info;
unsigned int bp, hsync, vsync;
u8 clk_delay;
u32 val = 0;
@@ -400,6 +404,27 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
+ /*
+ * On A20 and similar SoCs, the only way to achieve Positive Edge
+ * (Rising Edge), is setting dclk clock phase to 2/3(240°).
+ * By default TCON works in Negative Edge(Falling Edge),
+ * this is why phase is set to 0 in that case.
+ * Unfortunately there's no way to logically invert dclk through
+ * IO_POL register.
+ * The only acceptable way to work, triple checked with scope,
+ * is using clock phase set to 0° for Negative Edge and set to 240°
+ * for Positive Edge.
+ * On A33 and similar SoCs there would be a 90° phase option,
+ * but it divides also dclk by 2.
+ * Following code is a way to avoid quirks all around TCON
+ * and DOTCLOCK drivers.
+ */
+ if (display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_POSEDGE)
+ clk_set_phase(tcon->dclk, 240);
+
+ if (display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+ clk_set_phase(tcon->dclk, 0);
+
regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG,
SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE,
val);
@@ -850,6 +875,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
struct sunxi_engine *engine;
struct device_node *remote;
struct sun4i_tcon *tcon;
+ struct reset_control *edp_rstc;
bool has_lvds_rst, has_lvds_alt, can_lvds;
int ret;
@@ -874,6 +900,20 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
return PTR_ERR(tcon->lcd_rst);
}
+ if (tcon->quirks->needs_edp_reset) {
+ edp_rstc = devm_reset_control_get_shared(dev, "edp");
+ if (IS_ERR(edp_rstc)) {
+ dev_err(dev, "Couldn't get edp reset line\n");
+ return PTR_ERR(edp_rstc);
+ }
+
+ ret = reset_control_deassert(edp_rstc);
+ if (ret) {
+ dev_err(dev, "Couldn't deassert edp reset line\n");
+ return ret;
+ }
+ }
+
/* Make sure our TCON is reset */
ret = reset_control_reset(tcon->lcd_rst);
if (ret) {
@@ -1166,6 +1206,16 @@ static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
.has_channel_0 = true,
};
+static const struct sun4i_tcon_quirks sun9i_a80_tcon_lcd_quirks = {
+ .has_channel_0 = true,
+ .needs_edp_reset = true,
+};
+
+static const struct sun4i_tcon_quirks sun9i_a80_tcon_tv_quirks = {
+ .has_channel_1 = true,
+ .needs_edp_reset = true,
+};
+
/* sun4i_drv uses this list to check if a device node is a TCON */
const struct of_device_id sun4i_tcon_of_table[] = {
{ .compatible = "allwinner,sun4i-a10-tcon", .data = &sun4i_a10_quirks },
@@ -1177,6 +1227,8 @@ const struct of_device_id sun4i_tcon_of_table[] = {
{ .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks },
{ .compatible = "allwinner,sun8i-a83t-tcon-tv", .data = &sun8i_a83t_tv_quirks },
{ .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks },
+ { .compatible = "allwinner,sun9i-a80-tcon-lcd", .data = &sun9i_a80_tcon_lcd_quirks },
+ { .compatible = "allwinner,sun9i-a80-tcon-tv", .data = &sun9i_a80_tcon_tv_quirks },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table);