diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c')
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.c | 201 |
1 files changed, 145 insertions, 56 deletions
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 3c15cf24b503..c3d92d537240 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> @@ -84,6 +85,7 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, switch (channel) { case 0: + WARN_ON(!tcon->quirks->has_channel_0); regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0); @@ -101,10 +103,13 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, return; } - if (enabled) + if (enabled) { clk_prepare_enable(clk); - else + clk_rate_exclusive_get(clk); + } else { + clk_rate_exclusive_put(clk); clk_disable_unprepare(clk); + } } static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon, @@ -276,6 +281,8 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, u8 clk_delay; u32 reg, val = 0; + WARN_ON(!tcon->quirks->has_channel_0); + tcon->dclk_min_div = 7; tcon->dclk_max_div = 7; sun4i_tcon0_mode_set_common(tcon, mode); @@ -335,15 +342,23 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, 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) { + 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; + WARN_ON(!tcon->quirks->has_channel_0); + tcon->dclk_min_div = 6; tcon->dclk_max_div = 127; sun4i_tcon0_mode_set_common(tcon, mode); @@ -389,12 +404,33 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, SUN4I_TCON0_BASIC3_H_SYNC(hsync)); /* Setup the polarity of the various signals */ - if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + if (mode->flags & DRM_MODE_FLAG_PHSYNC) val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; - if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + 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); @@ -540,6 +576,7 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) struct sun4i_tcon *tcon = private; struct drm_device *drm = tcon->drm; struct sun4i_crtc *scrtc = tcon->crtc; + struct sunxi_engine *engine = scrtc->engine; unsigned int status; regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); @@ -557,6 +594,9 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) SUN4I_TCON_GINT0_VBLANK_INT(1), 0); + if (engine->ops->vblank_quirk) + engine->ops->vblank_quirk(engine); + return IRQ_HANDLED; } @@ -570,10 +610,12 @@ static int sun4i_tcon_init_clocks(struct device *dev, } clk_prepare_enable(tcon->clk); - tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); - if (IS_ERR(tcon->sclk0)) { - dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); - return PTR_ERR(tcon->sclk0); + if (tcon->quirks->has_channel_0) { + tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); + if (IS_ERR(tcon->sclk0)) { + dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); + return PTR_ERR(tcon->sclk0); + } } if (tcon->quirks->has_channel_1) { @@ -839,6 +881,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; @@ -863,6 +906,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) { @@ -870,52 +927,56 @@ 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; - } + if (tcon->quirks->supports_lvds) { + /* + * 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; + /* + * 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_pll); + } } else { - dev_err(dev, "Couldn't get the LVDS PLL\n"); - return PTR_ERR(tcon->lvds_pll); + has_lvds_alt = true; } - } 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; + 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; + } } else { - can_lvds = true; + can_lvds = false; } ret = sun4i_tcon_init_clocks(dev, tcon); @@ -930,10 +991,12 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, goto err_free_clocks; } - ret = sun4i_dclk_create(dev, tcon); - if (ret) { - dev_err(dev, "Couldn't create our TCON dot clock\n"); - goto err_free_clocks; + if (tcon->quirks->has_channel_0) { + ret = sun4i_dclk_create(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't create our TCON dot clock\n"); + goto err_free_clocks; + } } ret = sun4i_tcon_init_irq(dev, tcon); @@ -991,7 +1054,8 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, return 0; err_free_dotclock: - sun4i_dclk_free(tcon); + if (tcon->quirks->has_channel_0) + sun4i_dclk_free(tcon); err_free_clocks: sun4i_tcon_free_clocks(tcon); err_assert_reset: @@ -1005,7 +1069,8 @@ static void sun4i_tcon_unbind(struct device *dev, struct device *master, struct sun4i_tcon *tcon = dev_get_drvdata(dev); list_del(&tcon->list); - sun4i_dclk_free(tcon); + if (tcon->quirks->has_channel_0) + sun4i_dclk_free(tcon); sun4i_tcon_free_clocks(tcon); } @@ -1102,16 +1167,19 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, } static const struct sun4i_tcon_quirks sun4i_a10_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .set_mux = sun4i_a10_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun5i_a13_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .set_mux = sun5i_a13_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun6i_a31_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .has_lvds_alt = true, .needs_de_be_mux = true, @@ -1119,26 +1187,44 @@ static const struct sun4i_tcon_quirks sun6i_a31_quirks = { }; static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .needs_de_be_mux = true, }; static const struct sun4i_tcon_quirks sun7i_a20_quirks = { + .has_channel_0 = true, .has_channel_1 = true, /* Same display pipeline structure as A10 */ .set_mux = sun4i_a10_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun8i_a33_quirks = { + .has_channel_0 = true, .has_lvds_alt = true, }; static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { - /* nothing is supported */ + .supports_lvds = true, + .has_channel_0 = true, +}; + +static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { + .has_channel_1 = true, }; static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { - /* nothing is supported */ + .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 */ @@ -1150,7 +1236,10 @@ const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, { .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); |