Commit ef8187d7 authored by Thierry Reding's avatar Thierry Reding

drm/tegra: dsi: Implement runtime PM

Use runtime PM to clock-(un)gate, (de)assert reset and control power to
the DSI controller. This ties in nicely with atomic DPMS in that a
runtime PM reference is taken before a pipe is enabled and dropped after
it has been shut down.
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 33a8eb8d
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
...@@ -677,6 +678,45 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi) ...@@ -677,6 +678,45 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
} }
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
{
u32 value;
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
return 0;
}
static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
{
u32 value;
/*
* XXX Is this still needed? The module reset is deasserted right
* before this function is called.
*/
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
/* start calibration */
tegra_dsi_pad_enable(dsi);
value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
DSI_PAD_OUT_CLK(0x0);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
return tegra_mipi_calibrate(dsi->mipi);
}
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
unsigned int vrefresh) unsigned int vrefresh)
{ {
...@@ -837,7 +877,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder) ...@@ -837,7 +877,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
tegra_dsi_disable(dsi); tegra_dsi_disable(dsi);
return; pm_runtime_put(dsi->dev);
} }
static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
...@@ -848,6 +888,13 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) ...@@ -848,6 +888,13 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
struct tegra_dsi *dsi = to_dsi(output); struct tegra_dsi *dsi = to_dsi(output);
struct tegra_dsi_state *state; struct tegra_dsi_state *state;
u32 value; u32 value;
int err;
pm_runtime_get_sync(dsi->dev);
err = tegra_dsi_pad_calibrate(dsi);
if (err < 0)
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
state = tegra_dsi_get_state(dsi); state = tegra_dsi_get_state(dsi);
...@@ -876,8 +923,6 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) ...@@ -876,8 +923,6 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
if (output->panel) if (output->panel)
drm_panel_enable(output->panel); drm_panel_enable(output->panel);
return;
} }
static int static int
...@@ -967,55 +1012,12 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = { ...@@ -967,55 +1012,12 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
.atomic_check = tegra_dsi_encoder_atomic_check, .atomic_check = tegra_dsi_encoder_atomic_check,
}; };
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
{
u32 value;
value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
return 0;
}
static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
{
u32 value;
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
/* start calibration */
tegra_dsi_pad_enable(dsi);
value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
DSI_PAD_OUT_CLK(0x0);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
return tegra_mipi_calibrate(dsi->mipi);
}
static int tegra_dsi_init(struct host1x_client *client) static int tegra_dsi_init(struct host1x_client *client)
{ {
struct drm_device *drm = dev_get_drvdata(client->parent); struct drm_device *drm = dev_get_drvdata(client->parent);
struct tegra_dsi *dsi = host1x_client_to_dsi(client); struct tegra_dsi *dsi = host1x_client_to_dsi(client);
int err; int err;
reset_control_deassert(dsi->rst);
err = tegra_dsi_pad_calibrate(dsi);
if (err < 0) {
dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
goto reset;
}
/* Gangsters must not register their own outputs. */ /* Gangsters must not register their own outputs. */
if (!dsi->master) { if (!dsi->master) {
dsi->output.dev = client->dev; dsi->output.dev = client->dev;
...@@ -1038,12 +1040,9 @@ static int tegra_dsi_init(struct host1x_client *client) ...@@ -1038,12 +1040,9 @@ static int tegra_dsi_init(struct host1x_client *client)
drm_connector_register(&dsi->output.connector); drm_connector_register(&dsi->output.connector);
err = tegra_output_init(drm, &dsi->output); err = tegra_output_init(drm, &dsi->output);
if (err < 0) { if (err < 0)
dev_err(client->dev, dev_err(dsi->dev, "failed to initialize output: %d\n",
"failed to initialize output: %d\n",
err); err);
goto reset;
}
dsi->output.encoder.possible_crtcs = 0x3; dsi->output.encoder.possible_crtcs = 0x3;
} }
...@@ -1055,10 +1054,6 @@ static int tegra_dsi_init(struct host1x_client *client) ...@@ -1055,10 +1054,6 @@ static int tegra_dsi_init(struct host1x_client *client)
} }
return 0; return 0;
reset:
reset_control_assert(dsi->rst);
return err;
} }
static int tegra_dsi_exit(struct host1x_client *client) static int tegra_dsi_exit(struct host1x_client *client)
...@@ -1070,7 +1065,7 @@ static int tegra_dsi_exit(struct host1x_client *client) ...@@ -1070,7 +1065,7 @@ static int tegra_dsi_exit(struct host1x_client *client)
if (IS_ENABLED(CONFIG_DEBUG_FS)) if (IS_ENABLED(CONFIG_DEBUG_FS))
tegra_dsi_debugfs_exit(dsi); tegra_dsi_debugfs_exit(dsi);
reset_control_assert(dsi->rst); regulator_disable(dsi->vdd);
return 0; return 0;
} }
...@@ -1501,67 +1496,41 @@ static int tegra_dsi_probe(struct platform_device *pdev) ...@@ -1501,67 +1496,41 @@ static int tegra_dsi_probe(struct platform_device *pdev)
dsi->clk = devm_clk_get(&pdev->dev, NULL); dsi->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dsi->clk)) { if (IS_ERR(dsi->clk)) {
dev_err(&pdev->dev, "cannot get DSI clock\n"); dev_err(&pdev->dev, "cannot get DSI clock\n");
err = PTR_ERR(dsi->clk); return PTR_ERR(dsi->clk);
goto reset;
}
err = clk_prepare_enable(dsi->clk);
if (err < 0) {
dev_err(&pdev->dev, "cannot enable DSI clock\n");
goto reset;
} }
dsi->clk_lp = devm_clk_get(&pdev->dev, "lp"); dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
if (IS_ERR(dsi->clk_lp)) { if (IS_ERR(dsi->clk_lp)) {
dev_err(&pdev->dev, "cannot get low-power clock\n"); dev_err(&pdev->dev, "cannot get low-power clock\n");
err = PTR_ERR(dsi->clk_lp); return PTR_ERR(dsi->clk_lp);
goto disable_clk;
}
err = clk_prepare_enable(dsi->clk_lp);
if (err < 0) {
dev_err(&pdev->dev, "cannot enable low-power clock\n");
goto disable_clk;
} }
dsi->clk_parent = devm_clk_get(&pdev->dev, "parent"); dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
if (IS_ERR(dsi->clk_parent)) { if (IS_ERR(dsi->clk_parent)) {
dev_err(&pdev->dev, "cannot get parent clock\n"); dev_err(&pdev->dev, "cannot get parent clock\n");
err = PTR_ERR(dsi->clk_parent); return PTR_ERR(dsi->clk_parent);
goto disable_clk_lp;
} }
dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
if (IS_ERR(dsi->vdd)) { if (IS_ERR(dsi->vdd)) {
dev_err(&pdev->dev, "cannot get VDD supply\n"); dev_err(&pdev->dev, "cannot get VDD supply\n");
err = PTR_ERR(dsi->vdd); return PTR_ERR(dsi->vdd);
goto disable_clk_lp;
}
err = regulator_enable(dsi->vdd);
if (err < 0) {
dev_err(&pdev->dev, "cannot enable VDD supply\n");
goto disable_clk_lp;
} }
err = tegra_dsi_setup_clocks(dsi); err = tegra_dsi_setup_clocks(dsi);
if (err < 0) { if (err < 0) {
dev_err(&pdev->dev, "cannot setup clocks\n"); dev_err(&pdev->dev, "cannot setup clocks\n");
goto disable_vdd; return err;
} }
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dsi->regs = devm_ioremap_resource(&pdev->dev, regs); dsi->regs = devm_ioremap_resource(&pdev->dev, regs);
if (IS_ERR(dsi->regs)) { if (IS_ERR(dsi->regs))
err = PTR_ERR(dsi->regs); return PTR_ERR(dsi->regs);
goto disable_vdd;
}
dsi->mipi = tegra_mipi_request(&pdev->dev); dsi->mipi = tegra_mipi_request(&pdev->dev);
if (IS_ERR(dsi->mipi)) { if (IS_ERR(dsi->mipi))
err = PTR_ERR(dsi->mipi); return PTR_ERR(dsi->mipi);
goto disable_vdd;
}
dsi->host.ops = &tegra_dsi_host_ops; dsi->host.ops = &tegra_dsi_host_ops;
dsi->host.dev = &pdev->dev; dsi->host.dev = &pdev->dev;
...@@ -1572,6 +1541,9 @@ static int tegra_dsi_probe(struct platform_device *pdev) ...@@ -1572,6 +1541,9 @@ static int tegra_dsi_probe(struct platform_device *pdev)
goto mipi_free; goto mipi_free;
} }
platform_set_drvdata(pdev, dsi);
pm_runtime_enable(&pdev->dev);
INIT_LIST_HEAD(&dsi->client.list); INIT_LIST_HEAD(&dsi->client.list);
dsi->client.ops = &dsi_client_ops; dsi->client.ops = &dsi_client_ops;
dsi->client.dev = &pdev->dev; dsi->client.dev = &pdev->dev;
...@@ -1583,22 +1555,12 @@ static int tegra_dsi_probe(struct platform_device *pdev) ...@@ -1583,22 +1555,12 @@ static int tegra_dsi_probe(struct platform_device *pdev)
goto unregister; goto unregister;
} }
platform_set_drvdata(pdev, dsi);
return 0; return 0;
unregister: unregister:
mipi_dsi_host_unregister(&dsi->host); mipi_dsi_host_unregister(&dsi->host);
mipi_free: mipi_free:
tegra_mipi_free(dsi->mipi); tegra_mipi_free(dsi->mipi);
disable_vdd:
regulator_disable(dsi->vdd);
disable_clk_lp:
clk_disable_unprepare(dsi->clk_lp);
disable_clk:
clk_disable_unprepare(dsi->clk);
reset:
reset_control_assert(dsi->rst);
return err; return err;
} }
...@@ -1607,6 +1569,8 @@ static int tegra_dsi_remove(struct platform_device *pdev) ...@@ -1607,6 +1569,8 @@ static int tegra_dsi_remove(struct platform_device *pdev)
struct tegra_dsi *dsi = platform_get_drvdata(pdev); struct tegra_dsi *dsi = platform_get_drvdata(pdev);
int err; int err;
pm_runtime_disable(&pdev->dev);
err = host1x_client_unregister(&dsi->client); err = host1x_client_unregister(&dsi->client);
if (err < 0) { if (err < 0) {
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
...@@ -1619,13 +1583,77 @@ static int tegra_dsi_remove(struct platform_device *pdev) ...@@ -1619,13 +1583,77 @@ static int tegra_dsi_remove(struct platform_device *pdev)
mipi_dsi_host_unregister(&dsi->host); mipi_dsi_host_unregister(&dsi->host);
tegra_mipi_free(dsi->mipi); tegra_mipi_free(dsi->mipi);
regulator_disable(dsi->vdd); return 0;
}
#ifdef CONFIG_PM
static int tegra_dsi_suspend(struct device *dev)
{
struct tegra_dsi *dsi = dev_get_drvdata(dev);
int err;
err = reset_control_assert(dsi->rst);
if (err < 0) {
dev_err(dev, "failed to assert reset: %d\n", err);
return err;
}
usleep_range(1000, 2000);
clk_disable_unprepare(dsi->clk_lp); clk_disable_unprepare(dsi->clk_lp);
clk_disable_unprepare(dsi->clk); clk_disable_unprepare(dsi->clk);
reset_control_assert(dsi->rst);
regulator_disable(dsi->vdd);
return 0;
}
static int tegra_dsi_resume(struct device *dev)
{
struct tegra_dsi *dsi = dev_get_drvdata(dev);
int err;
err = regulator_enable(dsi->vdd);
if (err < 0) {
dev_err(dsi->dev, "failed to enable VDD supply: %d\n", err);
return err;
}
err = clk_prepare_enable(dsi->clk);
if (err < 0) {
dev_err(dev, "cannot enable DSI clock: %d\n", err);
goto disable_vdd;
}
err = clk_prepare_enable(dsi->clk_lp);
if (err < 0) {
dev_err(dev, "cannot enable low-power clock: %d\n", err);
goto disable_clk;
}
usleep_range(1000, 2000);
err = reset_control_deassert(dsi->rst);
if (err < 0) {
dev_err(dev, "cannot assert reset: %d\n", err);
goto disable_clk_lp;
}
return 0; return 0;
disable_clk_lp:
clk_disable_unprepare(dsi->clk_lp);
disable_clk:
clk_disable_unprepare(dsi->clk);
disable_vdd:
regulator_disable(dsi->vdd);
return err;
} }
#endif
static const struct dev_pm_ops tegra_dsi_pm_ops = {
SET_RUNTIME_PM_OPS(tegra_dsi_suspend, tegra_dsi_resume, NULL)
};
static const struct of_device_id tegra_dsi_of_match[] = { static const struct of_device_id tegra_dsi_of_match[] = {
{ .compatible = "nvidia,tegra210-dsi", }, { .compatible = "nvidia,tegra210-dsi", },
...@@ -1640,6 +1668,7 @@ struct platform_driver tegra_dsi_driver = { ...@@ -1640,6 +1668,7 @@ struct platform_driver tegra_dsi_driver = {
.driver = { .driver = {
.name = "tegra-dsi", .name = "tegra-dsi",
.of_match_table = tegra_dsi_of_match, .of_match_table = tegra_dsi_of_match,
.pm = &tegra_dsi_pm_ops,
}, },
.probe = tegra_dsi_probe, .probe = tegra_dsi_probe,
.remove = tegra_dsi_remove, .remove = tegra_dsi_remove,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment