@@ -2662,6 +2662,11 @@ void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
dispc_vp_set_color_mgmt(dispc, hw_videoport, state, newmodeset);
}
+static bool dispc_is_idle(struct dispc_device *dispc)
+{
+ return REG_GET(dispc, DSS_SYSSTATUS, 9, 9);
+}
+
int dispc_runtime_suspend(struct dispc_device *dispc)
{
dev_dbg(dispc->dev, "suspend\n");
@@ -2801,7 +2806,7 @@ static int dispc_softreset(struct dispc_device *dispc)
return 0;
}
-static int dispc_init_hw(struct dispc_device *dispc)
+int dispc_init_hw(struct dispc_device *dispc)
{
struct device *dev = dispc->dev;
int ret;
@@ -2818,15 +2823,69 @@ static int dispc_init_hw(struct dispc_device *dispc)
goto err_runtime_suspend;
}
- ret = dispc_softreset(dispc);
- if (ret)
- goto err_clk_disable;
+ dispc->tidss->boot_enabled_vp_mask = 0;
- clk_disable_unprepare(dispc->fclk);
- ret = pm_runtime_set_suspended(dev);
- if (ret) {
- dev_err(dev, "Failed to set DSS PM to suspended\n");
- return ret;
+ if (dispc_is_idle(dispc)) {
+ ret = dispc_softreset(dispc);
+ if (ret)
+ goto err_clk_disable;
+ } else {
+ for (u32 vp_idx = 0; vp_idx < dispc->feat->num_vps; vp_idx++) {
+ bool enabled = VP_REG_GET(dispc, vp_idx,
+ DISPC_VP_CONTROL, 0, 0);
+
+ if (!enabled)
+ continue;
+
+ dispc->tidss->boot_enabled_vp_mask |= BIT(vp_idx);
+
+ /* Keep the VP clk enabled */
+ ret = clk_prepare_enable(dispc->vp_clk[vp_idx]);
+ if (ret) {
+ while (vp_idx--) {
+ if (!(dispc->tidss->boot_enabled_vp_mask &
+ BIT(vp_idx)))
+ continue;
+
+ clk_disable_unprepare(dispc->vp_clk[vp_idx]);
+ }
+
+ goto err_clk_disable;
+ }
+ }
+
+ if (!dispc->tidss->boot_enabled_vp_mask) {
+ dev_warn(dev,
+ "Bad HW state: DSS not idle but no VPs are enabled. Resetting.\n");
+ ret = dispc_softreset(dispc);
+ if (ret)
+ goto err_clk_disable;
+ } else {
+ /*
+ * Do basic HW init here, which won't interrupt the
+ * splash-screen.
+ */
+
+ /* Ensure we won't get interrupts */
+ dispc_set_irqenable(dispc, 0);
+ dispc_read_and_clear_irqstatus(dispc);
+ }
+ }
+
+ if (dispc->tidss->boot_enabled_vp_mask) {
+ dev_dbg(dev,
+ "Bootloader splash-screen detected, leaving DSS active.\n");
+ pm_runtime_get_noresume(dev);
+ } else {
+ /*
+ * No splash-screen. Disable the clock and set DSS back to suspended.
+ */
+ clk_disable_unprepare(dispc->fclk);
+ ret = pm_runtime_set_suspended(dev);
+ if (ret) {
+ dev_err(dev, "Failed to set DSS PM to suspended\n");
+ return ret;
+ }
}
return 0;
@@ -2844,6 +2903,28 @@ static int dispc_init_hw(struct dispc_device *dispc)
return ret;
}
+void dispc_init_hw_cleanup(struct dispc_device *dispc)
+{
+ if (!dispc->tidss->boot_enabled_vp_mask)
+ return;
+
+ /*
+ * Call clk_disable_unprepare() to counter the clk_prepare_enable() we
+ * did in the dispc_init_hw().
+ */
+ clk_disable_unprepare(dispc->fclk);
+
+ for (u32 vp_idx = 0; vp_idx < dispc->feat->num_vps; vp_idx++) {
+ if (dispc->tidss->boot_enabled_vp_mask & BIT(vp_idx))
+ clk_disable_unprepare(dispc->vp_clk[vp_idx]);
+ }
+
+ /* counter the pm_runtime_get_noresume() */
+ pm_runtime_put_noidle(dispc->dev);
+
+ dispc->tidss->boot_enabled_vp_mask = 0;
+}
+
int dispc_init(struct tidss_device *tidss)
{
struct device *dev = tidss->dev;
@@ -2953,11 +3034,30 @@ int dispc_init(struct tidss_device *tidss)
of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth",
&dispc->memory_bandwidth_limit);
- r = dispc_init_hw(dispc);
- if (r)
- return r;
-
tidss->dispc = dispc;
return 0;
}
+
+void dispc_splash_fini(struct dispc_device *dispc)
+{
+ if (WARN_ON(!dispc->tidss->boot_enabled_vp_mask))
+ return;
+
+ /*
+ * Do a reset now, to clean up the bootloader setup, as we're about to
+ * do a modeset.
+ */
+ dispc_softreset(dispc);
+
+ /*
+ * Call resume manually. This will clk_prepare_enable() the fclk, and do
+ * the common basic HW configuration.
+ */
+ dispc_runtime_resume(dispc);
+
+ /*
+ * Revert the enables and gets we did in dispc_init_hw() at probe time.
+ */
+ dispc_init_hw_cleanup(dispc);
+}
@@ -137,4 +137,9 @@ const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len);
int dispc_init(struct tidss_device *tidss);
void dispc_remove(struct tidss_device *tidss);
+int dispc_init_hw(struct dispc_device *dispc);
+void dispc_init_hw_cleanup(struct dispc_device *dispc);
+
+void dispc_splash_fini(struct dispc_device *dispc);
+
#endif
@@ -34,8 +34,22 @@ int tidss_runtime_get(struct tidss_device *tidss)
dev_dbg(tidss->dev, "%s\n", __func__);
r = pm_runtime_resume_and_get(tidss->dev);
- WARN_ON(r < 0);
- return r;
+ if (WARN_ON(r < 0))
+ return r;
+
+ if (tidss->boot_enabled_vp_mask) {
+ /*
+ * If 'boot_enabled_vp_mask' is set, it means that the DSS is
+ * enabled and bootloader splash-screen is still on the screen,
+ * using bootloader's DSS HW config.
+ *
+ * This is the first time the driver is about to use the HW, and
+ * we need to do some cleanup and initial setup.
+ */
+ dispc_splash_fini(tidss->dispc);
+ }
+
+ return 0;
}
void tidss_runtime_put(struct tidss_device *tidss)
@@ -147,6 +161,12 @@ static int tidss_probe(struct platform_device *pdev)
return ret;
}
+ ret = dispc_init_hw(tidss->dispc);
+ if (ret) {
+ dev_err(dev, "failed to initialize dispc HW: %d\n", ret);
+ return ret;
+ }
+
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(dev, 1000);
@@ -197,12 +217,15 @@ static int tidss_probe(struct platform_device *pdev)
tidss_irq_uninstall(ddev);
err_runtime_suspend:
+
#ifndef CONFIG_PM
dispc_runtime_suspend(tidss->dispc);
#endif
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
+ dispc_init_hw_cleanup(tidss->dispc);
+
return ret;
}
@@ -227,6 +250,8 @@ static void tidss_remove(struct platform_device *pdev)
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
+ dispc_init_hw_cleanup(tidss->dispc);
+
/* devm allocated dispc goes away with the dev so mark it NULL */
dispc_remove(tidss);
@@ -32,6 +32,8 @@ struct tidss_device {
/* protects the irq masks field and irqenable/irqstatus registers */
spinlock_t irq_lock;
dispc_irq_t irq_mask; /* enabled irqs */
+
+ u32 boot_enabled_vp_mask;
};
#define to_tidss(__dev) container_of(__dev, struct tidss_device, ddev)
Currently when the driver's probe is called, we do a full DSS reset. If the bootloader has set up a splash-screen, the reset will disable the video output, and after that it may still take time until the display is usable (all the kernel modules have been loaded) and even more time until the userspace is able to use the display. In a perfect case tidss would take over the fb memory set up by the bootloader, and use that memory for tidss's fbdev, thus retaining the splash-screen. However, we're not there yet. As a partial solution, this patch changes the driver so that the driver will not reset (or change) the DSS registers until tidss_runtime_get() is called when the display is being set up (because of fbdev modesetting or modesetting from the userspace). This is achieved in two parts: 1. Probe At probe time, in dispc_check_hw_state(), we check if the DSS is idle (videoports disabled). If yes, continue as before. If not, we know that there's a splash-screen, and we set the 'tidss->boot_enabled_vp_mask' field to reflect the enabled VP ports. We then enable the corresponding VP clocks (to ensure they stay on), set the IRQENABLE to 0 to make sure we won't get any interrupts, and then exit leaving the fclk and VP clocks enabled, and the runtime PM status active. 2. Runtime get When the tidss_runtime_get() is called the first time, as indicated by the 'boot_enabled_vp_mask', we know that we have the splash-screen showing on the screen, and that the pm_runtime_resume_and_get() call in tidss_runtime_get() did not cause a runtime_resume callback to get called. We call dispc_splash_fini() which essentially returns the DSS into the state where it would be in a non-splash-screen case: dispc_splash_fini() will do a DSS reset, manually call the runtime_resume callback, and then call clk_disable_unprepare() and pm_runtime_put_noidle() to counter the actions at probe time. Finally 'boot_enabled_vp_mask' is set to zero to mark that we're no longer in the "splash-screen mode". === A few notes: If fbdev emulation is enabled in the DRM, tidss will set up an fbdev. This will cause a modeset, and the blank framebuffer from tidss's fbdev will be shown instead of the splash-screen. I see two improvements to this: either we should memcpy the pixel data from the bootloader's splash-screen to the new fbdev buffer, or the fbdev could use the splash-screen directly as its buffer. I have done some hacks for the former, but I'm not sure how to implement either of these properly. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> --- drivers/gpu/drm/tidss/tidss_dispc.c | 126 ++++++++++++++++++++++++++++++++---- drivers/gpu/drm/tidss/tidss_dispc.h | 5 ++ drivers/gpu/drm/tidss/tidss_drv.c | 29 ++++++++- drivers/gpu/drm/tidss/tidss_drv.h | 2 + 4 files changed, 147 insertions(+), 15 deletions(-)