From 7e737d031df08562043c0ea349dcd9c12b0e3f0e Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Sun, 5 Jul 2020 16:02:01 +0200 Subject: [PATCH 205/323] drm: sun4i: Support taking over display pipeline state from p-boot For perfect, flickerless and fast boot. Signed-off-by: Ondrej Jirman --- drivers/clk/sunxi-ng/ccu-sun50i-a64.c | 4 +- drivers/gpu/drm/drm_fb_helper.c | 16 ++++++ drivers/gpu/drm/panel/panel-sitronix-st7703.c | 17 ++++++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 23 ++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 + drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 13 +++++ drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h | 2 + drivers/gpu/drm/sun4i/sun8i_mixer.c | 55 +++++++++++++++++++ drivers/gpu/drm/sun4i/sun8i_mixer.h | 3 + drivers/of/base.c | 1 + drivers/phy/allwinner/phy-sun6i-mipi-dphy.c | 15 +++++ drivers/video/backlight/pwm_bl.c | 23 +++++++- 12 files changed, 172 insertions(+), 2 deletions(-) diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-a64.c b/drivers/clk/sunxi-ng/ccu-sun50i-a64.c index 3e76c751e..03e8051e3 100644 --- a/drivers/clk/sunxi-ng/ccu-sun50i-a64.c +++ b/drivers/clk/sunxi-ng/ccu-sun50i-a64.c @@ -967,7 +967,9 @@ static int sun50i_a64_ccu_probe(struct platform_device *pdev) val &= ~GENMASK(19, 16); writel(val | (0 << 16), reg + SUN50I_A64_PLL_AUDIO_REG); - writel(0x515, reg + SUN50I_A64_PLL_MIPI_REG); + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &val); + if (ret) + writel(0x515, reg + SUN50I_A64_PLL_MIPI_REG); /* Force the parent of TCON0 to PLL-MIPI */ val = readl(reg + SUN50I_A64_TCON0_REG); diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 8033467db..47a86dfd9 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -2082,6 +2082,8 @@ static int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper, struct fb_info *fbi; u32 format; void *vaddr; + u32 fb_start; + int ret; drm_dbg_kms(dev, "surface width(%d), height(%d) and bpp(%d)\n", sizes->surface_width, sizes->surface_height, @@ -2128,6 +2130,20 @@ static int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper, fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer)); #endif + + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &fb_start); + if (ret == 0) { + // copy framebuffer contents from p-boot if reasonable + if (fbi->screen_size != 720 * 1440 * 4) { + pr_err("surface width(%d), height(%d) and bpp(%d) does not match p-boot requirements\n", + sizes->surface_width, sizes->surface_height, + sizes->surface_bpp); + return 0; + } + + pr_err("passing framebuffer from p-boot to fbcon\n"); + memcpy(vaddr, __va(fb_start), fbi->screen_size); + } } return 0; diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7703.c b/drivers/gpu/drm/panel/panel-sitronix-st7703.c index 1fe0e7299..948790f1e 100644 --- a/drivers/gpu/drm/panel/panel-sitronix-st7703.c +++ b/drivers/gpu/drm/panel/panel-sitronix-st7703.c @@ -58,6 +58,7 @@ struct st7703 { struct dentry *debugfs; const struct st7703_panel_desc *desc; + bool hw_preenabled; }; struct st7703_panel_desc { @@ -360,6 +361,11 @@ static int st7703_enable(struct drm_panel *panel) struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); int ret; + if (ctx->hw_preenabled) { + ctx->hw_preenabled = false; + return 0; + } + ret = ctx->desc->init_sequence(ctx); if (ret < 0) { dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret); @@ -424,8 +430,10 @@ static int st7703_prepare(struct drm_panel *panel) if (ctx->prepared) return 0; + if (!ctx->hw_preenabled) { dev_dbg(ctx->dev, "Resetting the panel\n"); gpiod_set_value_cansleep(ctx->reset_gpio, 1); + } ret = regulator_enable(ctx->iovcc); if (ret < 0) { @@ -441,10 +449,12 @@ static int st7703_prepare(struct drm_panel *panel) } /* Give power supplies time to stabilize before deasserting reset. */ + if (!ctx->hw_preenabled) { usleep_range(10000, 20000); gpiod_set_value_cansleep(ctx->reset_gpio, 0); usleep_range(15000, 20000); + } ctx->prepared = true; @@ -521,12 +531,19 @@ static int st7703_probe(struct mipi_dsi_device *dsi) { struct device *dev = &dsi->dev; struct st7703 *ctx; + u32 fb_start; int ret; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &fb_start); + if (ret == 0) { + /* the display pipeline is already initialized by p-boot */ + ctx->hw_preenabled = true; + } + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { dev_err(dev, "cannot get reset gpio\n"); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 0fc63ee45..0af3f5060 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -39,6 +39,8 @@ #include "sun8i_tcon_top.h" #include "sunxi_engine.h" +static bool hw_preconfigured; + static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) { struct drm_connector *connector; @@ -738,6 +740,13 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, const struct drm_encoder *encoder, const struct drm_display_mode *mode) { + if (tcon->hw_preconfigured) { + // avoid the first modeset + tcon->hw_preconfigured = false; + hw_preconfigured = false; + return; + } + switch (encoder->encoder_type) { case DRM_MODE_ENCODER_DSI: /* DSI is tied to special case of CPU interface */ @@ -888,6 +897,7 @@ static int sun4i_tcon_init_regmap(struct device *dev, return PTR_ERR(tcon->regs); } + if (!tcon->hw_preconfigured) { /* Make sure the TCON is disabled and all IRQs are off */ regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); @@ -896,6 +906,7 @@ static int sun4i_tcon_init_regmap(struct device *dev, /* Disable IO lines and set them to tristate */ regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); + } return 0; } @@ -1167,6 +1178,9 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, tcon->dev = dev; tcon->id = engine->id; tcon->quirks = of_device_get_match_data(dev); + + if (tcon->id == 0) + tcon->hw_preconfigured = hw_preconfigured; tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); if (IS_ERR(tcon->lcd_rst)) { @@ -1188,12 +1202,14 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, } } + if (!tcon->hw_preconfigured) { /* Make sure our TCON is reset */ ret = reset_control_reset(tcon->lcd_rst); if (ret) { dev_err(dev, "Couldn't deassert our reset line\n"); return ret; } + } if (tcon->quirks->supports_lvds) { /* @@ -1360,8 +1376,15 @@ static int sun4i_tcon_probe(struct platform_device *pdev) const struct sun4i_tcon_quirks *quirks; struct drm_bridge *bridge; struct drm_panel *panel; + u32 fb_start; int ret; + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &fb_start); + if (ret == 0) { + /* the display pipeline is already initialized by p-boot */ + hw_preconfigured = true; + } + quirks = of_device_get_match_data(&pdev->dev); /* panels and bridges are present only on TCONs with channel 0 */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index ea0e6b023..f532d87ea 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -293,6 +293,8 @@ struct sun4i_tcon { /* TCON list management */ struct list_head list; + + bool hw_preconfigured; }; struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c index d7fff121e..572d637dc 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -732,6 +732,7 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) reset_control_deassert(dsi->reset); clk_prepare_enable(dsi->mod_clk); + if (!dsi->hw_preconfigured) { /* * Enable the DSI block. */ @@ -758,6 +759,7 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) sun6i_dsi_setup_inst_loop(dsi, mode); sun6i_dsi_setup_format(dsi, mode); sun6i_dsi_setup_timings(dsi, mode); + } phy_init(dsi->dphy); @@ -787,11 +789,15 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) if (dsi->panel) drm_panel_enable(dsi->panel); + if (!dsi->hw_preconfigured) { sun6i_dsi_start(dsi, DSI_START_HSC); udelay(1000); sun6i_dsi_start(dsi, DSI_START_HSD); + } + + dsi->hw_preconfigured = false; } static void sun6i_dsi_encoder_disable(struct drm_encoder *encoder) @@ -1107,6 +1113,7 @@ static int sun6i_dsi_probe(struct platform_device *pdev) struct sun6i_dsi *dsi; struct resource *res; void __iomem *base; + u32 fb_start; int ret; dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); @@ -1117,6 +1124,12 @@ static int sun6i_dsi_probe(struct platform_device *pdev) dsi->host.ops = &sun6i_dsi_host_ops; dsi->host.dev = dev; + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &fb_start); + if (ret == 0) { + /* the display pipeline is already initialized by p-boot */ + dsi->hw_preconfigured = true; + } + if (of_device_is_compatible(dev->of_node, "allwinner,sun6i-a31-mipi-dsi")) bus_clk_name = "bus"; diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h index c863900ae..7f80ff130 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h @@ -31,6 +31,8 @@ struct sun6i_dsi { struct mipi_dsi_device *device; struct drm_device *drm; struct drm_panel *panel; + + bool hw_preconfigured; }; static inline struct sun6i_dsi *host_to_sun6i_dsi(struct mipi_dsi_host *host) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 5b42cf25c..65b24dce7 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -22,6 +22,7 @@ #include #include "sun4i_drv.h" +#include "sun4i_tcon.h" #include "sun8i_mixer.h" #include "sun8i_ui_layer.h" #include "sun8i_vi_layer.h" @@ -32,6 +33,8 @@ struct de2_fmt_info { u32 de2_fmt; }; +static bool hw_preconfigured; + static const struct de2_fmt_info de2_formats[] = { { .drm_fmt = DRM_FORMAT_ARGB8888, @@ -250,8 +253,38 @@ int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format) static void sun8i_mixer_commit(struct sunxi_engine *engine) { + struct sun8i_mixer* mixer = engine_to_sun8i_mixer(engine); + struct sun4i_tcon* tcon; + u32 val, saved, ret; + DRM_DEBUG_DRIVER("Committing changes\n"); + if (mixer->hw_preconfigured && engine->id == 0) { + /* + * This is the first commit, wait for vblank on tcon0 before continuing. + */ + list_for_each_entry(tcon, &mixer->drv->tcon_list, list) { + if (tcon->id == 0) { + regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &saved); + saved &= 0xffff0000; + + regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); + + pr_err("polling for the first vblank\n"); + ret = regmap_read_poll_timeout(tcon->regs, SUN4I_TCON_GINT0_REG, val, + val & (SUN4I_TCON_GINT0_VBLANK_INT(0) | + SUN4I_TCON_GINT0_VBLANK_INT(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT), + 100, 40000); + pr_err("polling for the first vblank done %d\n", ret); + + regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, saved); + } + } + + mixer->hw_preconfigured = false; + } + regmap_write(engine->regs, SUN8I_MIXER_GLOBAL_DBUFF, SUN8I_MIXER_GLOBAL_DBUFF_ENABLE); } @@ -362,6 +395,7 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, dev_set_drvdata(dev, mixer); mixer->engine.ops = &sun8i_engine_ops; mixer->engine.node = dev->of_node; + mixer->drv = drv; if (of_find_property(dev->of_node, "iommus", NULL)) { /* @@ -386,6 +420,11 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, */ mixer->engine.id = sun8i_mixer_of_get_id(dev->of_node); + if (mixer->engine.id == 0) { + mixer->hw_preconfigured = hw_preconfigured; + hw_preconfigured = false; + } + mixer->cfg = of_device_get_match_data(dev); if (!mixer->cfg) return -EINVAL; @@ -434,8 +473,11 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, * reason for the mixer to be functional. Make sure it's the * case. */ + + if (!mixer->hw_preconfigured) { if (mixer->cfg->mod_rate) clk_set_rate(mixer->mod_clk, mixer->cfg->mod_rate); + } clk_prepare_enable(mixer->mod_clk); @@ -443,6 +485,7 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, base = sun8i_blender_base(mixer); + if (!mixer->hw_preconfigured) { /* Reset registers and disable unused sub-engines */ if (mixer->cfg->is_de3) { for (i = 0; i < DE3_MIXER_UNIT_SIZE; i += 4) @@ -474,6 +517,7 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, /* Enable the mixer */ regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_CTL, SUN8I_MIXER_GLOBAL_CTL_RT_EN); + } /* hw_preconfigured */ /* Set background color to black */ regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_BKCOLOR(base), @@ -494,8 +538,10 @@ static int sun8i_mixer_bind(struct device *dev, struct device *master, SUN8I_MIXER_BLEND_MODE(base, i), SUN8I_MIXER_BLEND_MODE_DEF); + if (!mixer->hw_preconfigured) { regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_BLEND_PIPE_CTL(base), SUN8I_MIXER_BLEND_PIPE_CTL_EN_MSK, 0); + } return 0; @@ -525,6 +571,15 @@ static const struct component_ops sun8i_mixer_ops = { static int sun8i_mixer_probe(struct platform_device *pdev) { + int ret; + u32 fb_start; + + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &fb_start); + if (ret == 0) { + /* the display pipeline is already initialized by p-boot */ + hw_preconfigured = true; + } + return component_add(&pdev->dev, &sun8i_mixer_ops); } diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index 7576b523f..1d31fdc71 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -179,6 +179,9 @@ struct sun8i_mixer { struct clk *bus_clk; struct clk *mod_clk; + + struct sun4i_drv *drv; + bool hw_preconfigured; }; static inline struct sun8i_mixer * diff --git a/drivers/of/base.c b/drivers/of/base.c index 161a23631..bb539f7fe 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -36,6 +36,7 @@ LIST_HEAD(aliases_lookup); struct device_node *of_root; EXPORT_SYMBOL(of_root); struct device_node *of_chosen; +EXPORT_SYMBOL(of_chosen); struct device_node *of_aliases; struct device_node *of_stdout; static const char *of_stdout_options; diff --git a/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c b/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c index 1fa761ba6..9863c6a2f 100644 --- a/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c +++ b/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c @@ -92,6 +92,8 @@ struct sun6i_dphy { struct phy *phy; struct phy_configure_opts_mipi_dphy config; + + bool hw_preconfigured; }; static int sun6i_dphy_init(struct phy *phy) @@ -124,6 +126,11 @@ static int sun6i_dphy_power_on(struct phy *phy) struct sun6i_dphy *dphy = phy_get_drvdata(phy); u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); + if (dphy->hw_preconfigured) { + dphy->hw_preconfigured = false; + return 0; + } + regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); @@ -255,11 +262,19 @@ static int sun6i_dphy_probe(struct platform_device *pdev) struct sun6i_dphy *dphy; struct resource *res; void __iomem *regs; + u32 fb_start; + int ret; dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); if (!dphy) return -ENOMEM; + ret = of_property_read_u32_index(of_chosen, "p-boot,framebuffer-start", 0, &fb_start); + if (ret == 0) { + /* the display pipeline is already initialized by p-boot */ + dphy->hw_preconfigured = true; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(regs)) { diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 1cf924f3a..b0dfa5441 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -466,7 +466,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) struct backlight_device *bl; struct device_node *node = pdev->dev.of_node; struct pwm_bl_data *pb; - struct pwm_state state; + struct pwm_state state, state_real; unsigned int i; int ret; @@ -533,6 +533,11 @@ static int pwm_backlight_probe(struct platform_device *pdev) /* Sync up PWM state. */ pwm_init_state(pb->pwm, &state); + /* Read real state, but only if the PWM is enabled. */ + pwm_get_state(pb->pwm, &state_real); + if (state_real.enabled) + state = state_real; + /* * The DT case will set the pwm_period_ns field to 0 and store the * period, parsed from the DT, in the PWM device. For the non-DT case, @@ -627,6 +632,22 @@ static int pwm_backlight_probe(struct platform_device *pdev) bl->props.brightness = data->dft_brightness; bl->props.power = pwm_backlight_initial_power_state(pb); + if (bl->props.power == FB_BLANK_UNBLANK && pb->levels) { + /* If the backlight is already on, determine the default + * brightness from PWM duty cycle instead of forcing + * the brightness determined by the driver + */ + pwm_get_state(pb->pwm, &state); + u64 level = (u64)state.duty_cycle * pb->scale; + do_div(level, (u64)state.period); + + for (i = 0; i <= data->max_brightness; i++) { + if (data->levels[i] > level) { + bl->props.brightness = i; + break; + } + } + } backlight_update_status(bl); platform_set_drvdata(pdev, bl); -- 2.34.0