diff options
Diffstat (limited to 'drivers/gpu/drm/panel/panel-simple.c')
-rw-r--r-- | drivers/gpu/drm/panel/panel-simple.c | 142 |
1 files changed, 96 insertions, 46 deletions
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index be312b5c04dd..21939d4352cf 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -27,6 +27,7 @@ #include <linux/module.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <video/display_timing.h> @@ -175,6 +176,8 @@ struct panel_simple { bool enabled; bool no_hpd; + bool prepared; + ktime_t prepared_time; ktime_t unprepared_time; @@ -186,6 +189,8 @@ struct panel_simple { struct gpio_desc *enable_gpio; struct gpio_desc *hpd_gpio; + struct edid *edid; + struct drm_display_mode override_mode; enum drm_panel_orientation orientation; @@ -334,25 +339,39 @@ static int panel_simple_disable(struct drm_panel *panel) return 0; } +static int panel_simple_suspend(struct device *dev) +{ + struct panel_simple *p = dev_get_drvdata(dev); + + gpiod_set_value_cansleep(p->enable_gpio, 0); + regulator_disable(p->supply); + p->unprepared_time = ktime_get(); + + kfree(p->edid); + p->edid = NULL; + + return 0; +} + static int panel_simple_unprepare(struct drm_panel *panel) { struct panel_simple *p = to_panel_simple(panel); + int ret; - if (p->prepared_time == 0) + /* Unpreparing when already unprepared is a no-op */ + if (!p->prepared) return 0; - gpiod_set_value_cansleep(p->enable_gpio, 0); - - regulator_disable(p->supply); - - p->prepared_time = 0; - p->unprepared_time = ktime_get(); + pm_runtime_mark_last_busy(panel->dev); + ret = pm_runtime_put_autosuspend(panel->dev); + if (ret < 0) + return ret; + p->prepared = false; return 0; } -static int panel_simple_get_hpd_gpio(struct device *dev, - struct panel_simple *p, bool from_probe) +static int panel_simple_get_hpd_gpio(struct device *dev, struct panel_simple *p) { int err; @@ -360,38 +379,28 @@ static int panel_simple_get_hpd_gpio(struct device *dev, if (IS_ERR(p->hpd_gpio)) { err = PTR_ERR(p->hpd_gpio); - /* - * If we're called from probe we won't consider '-EPROBE_DEFER' - * to be an error--we'll leave the error code in "hpd_gpio". - * When we try to use it we'll try again. This allows for - * circular dependencies where the component providing the - * hpd gpio needs the panel to init before probing. - */ - if (err != -EPROBE_DEFER || !from_probe) { + if (err != -EPROBE_DEFER) dev_err(dev, "failed to get 'hpd' GPIO: %d\n", err); - return err; - } + + return err; } return 0; } -static int panel_simple_prepare_once(struct drm_panel *panel) +static int panel_simple_prepare_once(struct panel_simple *p) { - struct panel_simple *p = to_panel_simple(panel); + struct device *dev = p->base.dev; unsigned int delay; int err; int hpd_asserted; unsigned long hpd_wait_us; - if (p->prepared_time != 0) - return 0; - panel_simple_wait(p->unprepared_time, p->desc->delay.unprepare); err = regulator_enable(p->supply); if (err < 0) { - dev_err(panel->dev, "failed to enable supply: %d\n", err); + dev_err(dev, "failed to enable supply: %d\n", err); return err; } @@ -404,12 +413,6 @@ static int panel_simple_prepare_once(struct drm_panel *panel) msleep(delay); if (p->hpd_gpio) { - if (IS_ERR(p->hpd_gpio)) { - err = panel_simple_get_hpd_gpio(panel->dev, p, false); - if (err) - goto error; - } - if (p->desc->delay.hpd_absent_delay) hpd_wait_us = p->desc->delay.hpd_absent_delay * 1000UL; else @@ -423,7 +426,7 @@ static int panel_simple_prepare_once(struct drm_panel *panel) if (err) { if (err != -ETIMEDOUT) - dev_err(panel->dev, + dev_err(dev, "error waiting for hpd GPIO: %d\n", err); goto error; } @@ -447,25 +450,46 @@ error: */ #define MAX_PANEL_PREPARE_TRIES 5 -static int panel_simple_prepare(struct drm_panel *panel) +static int panel_simple_resume(struct device *dev) { + struct panel_simple *p = dev_get_drvdata(dev); int ret; int try; for (try = 0; try < MAX_PANEL_PREPARE_TRIES; try++) { - ret = panel_simple_prepare_once(panel); + ret = panel_simple_prepare_once(p); if (ret != -ETIMEDOUT) break; } if (ret == -ETIMEDOUT) - dev_err(panel->dev, "Prepare timeout after %d tries\n", try); + dev_err(dev, "Prepare timeout after %d tries\n", try); else if (try) - dev_warn(panel->dev, "Prepare needed %d retries\n", try); + dev_warn(dev, "Prepare needed %d retries\n", try); return ret; } +static int panel_simple_prepare(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + int ret; + + /* Preparing when already prepared is a no-op */ + if (p->prepared) + return 0; + + ret = pm_runtime_get_sync(panel->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(panel->dev); + return ret; + } + + p->prepared = true; + + return 0; +} + static int panel_simple_enable(struct drm_panel *panel) { struct panel_simple *p = to_panel_simple(panel); @@ -491,13 +515,16 @@ static int panel_simple_get_modes(struct drm_panel *panel, /* probe EDID if a DDC bus is available */ if (p->ddc) { - struct edid *edid = drm_get_edid(connector, p->ddc); + pm_runtime_get_sync(panel->dev); - drm_connector_update_edid_property(connector, edid); - if (edid) { - num += drm_add_edid_modes(connector, edid); - kfree(edid); - } + if (!p->edid) + p->edid = drm_get_edid(connector, p->ddc); + + if (p->edid) + num += drm_add_edid_modes(connector, p->edid); + + pm_runtime_mark_last_busy(panel->dev); + pm_runtime_put_autosuspend(panel->dev); } /* add hard-coded panel modes */ @@ -649,7 +676,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); if (!panel->no_hpd) { - err = panel_simple_get_hpd_gpio(dev, panel, true); + err = panel_simple_get_hpd_gpio(dev, panel); if (err) return err; } @@ -748,18 +775,31 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) break; } + dev_set_drvdata(dev, panel); + + /* + * We use runtime PM for prepare / unprepare since those power the panel + * on and off and those can be very slow operations. This is important + * to optimize powering the panel on briefly to read the EDID before + * fully enabling the panel. + */ + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + drm_panel_init(&panel->base, dev, &panel_simple_funcs, connector_type); err = drm_panel_of_backlight(&panel->base); if (err) - goto free_ddc; + goto disable_pm_runtime; drm_panel_add(&panel->base); - dev_set_drvdata(dev, panel); - return 0; +disable_pm_runtime: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); free_ddc: if (panel->ddc) put_device(&panel->ddc->dev); @@ -775,6 +815,8 @@ static int panel_simple_remove(struct device *dev) drm_panel_disable(&panel->base); drm_panel_unprepare(&panel->base); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); if (panel->ddc) put_device(&panel->ddc->dev); @@ -4603,10 +4645,17 @@ static void panel_simple_platform_shutdown(struct platform_device *pdev) panel_simple_shutdown(&pdev->dev); } +static const struct dev_pm_ops panel_simple_pm_ops = { + SET_RUNTIME_PM_OPS(panel_simple_suspend, panel_simple_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + static struct platform_driver panel_simple_platform_driver = { .driver = { .name = "panel-simple", .of_match_table = platform_of_match, + .pm = &panel_simple_pm_ops, }, .probe = panel_simple_platform_probe, .remove = panel_simple_platform_remove, @@ -4901,6 +4950,7 @@ static struct mipi_dsi_driver panel_simple_dsi_driver = { .driver = { .name = "panel-simple-dsi", .of_match_table = dsi_of_match, + .pm = &panel_simple_pm_ops, }, .probe = panel_simple_dsi_probe, .remove = panel_simple_dsi_remove, |