diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c')
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.c | 203 |
1 files changed, 192 insertions, 11 deletions
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 3fb084f802e2..c78cd35a1294 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -12,11 +12,13 @@ #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> #include <drm/drm_modes.h> #include <drm/drm_of.h> +#include <drm/drm_panel.h> #include <uapi/drm/drm_mode.h> @@ -35,6 +37,7 @@ #include "sun4i_rgb.h" #include "sun4i_tcon.h" #include "sun6i_mipi_dsi.h" +#include "sun8i_tcon_top.h" #include "sunxi_engine.h" static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) @@ -275,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; @@ -289,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); @@ -354,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, @@ -427,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, @@ -474,6 +540,33 @@ 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 (!IS_ERR(tcon->panel)) { + struct drm_panel *panel = tcon->panel; + struct drm_connector *connector = panel->connector; + struct drm_display_info display_info = connector->display_info; + + 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); @@ -581,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); @@ -880,6 +967,37 @@ static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, return ERR_PTR(-EINVAL); } +static bool sun4i_tcon_connected_to_tcon_top(struct device_node *node) +{ + struct device_node *remote; + bool ret = false; + + remote = of_graph_get_remote_node(node, 0, -1); + if (remote) { + ret = !!(IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && + of_match_node(sun8i_tcon_top_of_table, remote)); + of_node_put(remote); + } + + return ret; +} + +static int sun4i_tcon_get_index(struct sun4i_drv *drv) +{ + struct list_head *pos; + int size = 0; + + /* + * Because TCON is added to the list at the end of the probe + * (after this function is called), index of the current TCON + * will be same as current TCON list size. + */ + list_for_each(pos, &drv->tcon_list) + ++size; + + return size; +} + /* * On SoCs with the old display pipeline design (Display Engine 1.0), * we assumed the TCON was always tied to just one backend. However @@ -928,8 +1046,24 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, * connections between the backend and TCON? */ if (of_get_child_count(port) > 1) { - /* Get our ID directly from an upstream endpoint */ - int id = sun4i_tcon_of_get_id_from_port(port); + int id; + + /* + * When pipeline has the same number of TCONs and engines which + * are represented by frontends/backends (DE1) or mixers (DE2), + * we match them by their respective IDs. However, if pipeline + * contains TCON TOP, chances are that there are either more + * TCONs than engines (R40) or TCONs with non-consecutive ids. + * (H6). In that case it's easier just use TCON index in list + * as an id. That means that on R40, any 2 TCONs can be enabled + * in DT out of 4 (there are 2 mixers). Due to the design of + * TCON TOP, remaining 2 TCONs can't be connected to anything + * anyway. + */ + if (sun4i_tcon_connected_to_tcon_top(node)) + id = sun4i_tcon_get_index(drv); + else + id = sun4i_tcon_of_get_id_from_port(port); /* Get our engine by matching our ID */ engine = sun4i_tcon_get_engine_by_id(drv, id); @@ -1244,6 +1378,47 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, return 0; } +static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + struct device_node *port, *remote; + struct platform_device *pdev; + int id, ret; + + /* find TCON TOP platform device and TCON id */ + + port = of_graph_get_port_by_id(tcon->dev->of_node, 0); + if (!port) + return -EINVAL; + + id = sun4i_tcon_of_get_id_from_port(port); + of_node_put(port); + + remote = of_graph_get_remote_node(tcon->dev->of_node, 0, -1); + if (!remote) + return -EINVAL; + + pdev = of_find_device_by_node(remote); + of_node_put(remote); + if (!pdev) + return -EINVAL; + + 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; + } + + 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 = { .has_channel_0 = true, .has_channel_1 = true, @@ -1291,6 +1466,11 @@ static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { .has_channel_1 = true, }; +static const struct sun4i_tcon_quirks sun8i_r40_tv_quirks = { + .has_channel_1 = true, + .set_mux = sun8i_r40_tcon_tv_set_mux, +}; + static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { .has_channel_0 = true, }; @@ -1315,6 +1495,7 @@ const struct of_device_id sun4i_tcon_of_table[] = { { .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-r40-tcon-tv", .data = &sun8i_r40_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 }, |