mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
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: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
33a8eb8d40
commit
ef8187d752
@ -13,6 +13,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <linux/regulator/consumer.h>
|
||||
@ -677,6 +678,45 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
|
||||
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,
|
||||
unsigned int vrefresh)
|
||||
{
|
||||
@ -837,7 +877,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
|
||||
|
||||
tegra_dsi_disable(dsi);
|
||||
|
||||
return;
|
||||
pm_runtime_put(dsi->dev);
|
||||
}
|
||||
|
||||
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_state *state;
|
||||
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);
|
||||
|
||||
@ -876,8 +923,6 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_enable(output->panel);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -967,55 +1012,12 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
|
||||
.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)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(client->parent);
|
||||
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
|
||||
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. */
|
||||
if (!dsi->master) {
|
||||
dsi->output.dev = client->dev;
|
||||
@ -1038,12 +1040,9 @@ static int tegra_dsi_init(struct host1x_client *client)
|
||||
drm_connector_register(&dsi->output.connector);
|
||||
|
||||
err = tegra_output_init(drm, &dsi->output);
|
||||
if (err < 0) {
|
||||
dev_err(client->dev,
|
||||
"failed to initialize output: %d\n",
|
||||
if (err < 0)
|
||||
dev_err(dsi->dev, "failed to initialize output: %d\n",
|
||||
err);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
dsi->output.encoder.possible_crtcs = 0x3;
|
||||
}
|
||||
@ -1055,10 +1054,6 @@ static int tegra_dsi_init(struct host1x_client *client)
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
reset:
|
||||
reset_control_assert(dsi->rst);
|
||||
return err;
|
||||
}
|
||||
|
||||
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))
|
||||
tegra_dsi_debugfs_exit(dsi);
|
||||
|
||||
reset_control_assert(dsi->rst);
|
||||
regulator_disable(dsi->vdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1501,67 +1496,41 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
||||
dsi->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dsi->clk)) {
|
||||
dev_err(&pdev->dev, "cannot get DSI clock\n");
|
||||
err = 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;
|
||||
return PTR_ERR(dsi->clk);
|
||||
}
|
||||
|
||||
dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
|
||||
if (IS_ERR(dsi->clk_lp)) {
|
||||
dev_err(&pdev->dev, "cannot get low-power clock\n");
|
||||
err = 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;
|
||||
return PTR_ERR(dsi->clk_lp);
|
||||
}
|
||||
|
||||
dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
||||
if (IS_ERR(dsi->clk_parent)) {
|
||||
dev_err(&pdev->dev, "cannot get parent clock\n");
|
||||
err = PTR_ERR(dsi->clk_parent);
|
||||
goto disable_clk_lp;
|
||||
return PTR_ERR(dsi->clk_parent);
|
||||
}
|
||||
|
||||
dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
|
||||
if (IS_ERR(dsi->vdd)) {
|
||||
dev_err(&pdev->dev, "cannot get VDD supply\n");
|
||||
err = 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;
|
||||
return PTR_ERR(dsi->vdd);
|
||||
}
|
||||
|
||||
err = tegra_dsi_setup_clocks(dsi);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "cannot setup clocks\n");
|
||||
goto disable_vdd;
|
||||
return err;
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dsi->regs = devm_ioremap_resource(&pdev->dev, regs);
|
||||
if (IS_ERR(dsi->regs)) {
|
||||
err = PTR_ERR(dsi->regs);
|
||||
goto disable_vdd;
|
||||
}
|
||||
if (IS_ERR(dsi->regs))
|
||||
return PTR_ERR(dsi->regs);
|
||||
|
||||
dsi->mipi = tegra_mipi_request(&pdev->dev);
|
||||
if (IS_ERR(dsi->mipi)) {
|
||||
err = PTR_ERR(dsi->mipi);
|
||||
goto disable_vdd;
|
||||
}
|
||||
if (IS_ERR(dsi->mipi))
|
||||
return PTR_ERR(dsi->mipi);
|
||||
|
||||
dsi->host.ops = &tegra_dsi_host_ops;
|
||||
dsi->host.dev = &pdev->dev;
|
||||
@ -1572,6 +1541,9 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
||||
goto mipi_free;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dsi);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
INIT_LIST_HEAD(&dsi->client.list);
|
||||
dsi->client.ops = &dsi_client_ops;
|
||||
dsi->client.dev = &pdev->dev;
|
||||
@ -1583,22 +1555,12 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dsi);
|
||||
|
||||
return 0;
|
||||
|
||||
unregister:
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
mipi_free:
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1607,6 +1569,8 @@ static int tegra_dsi_remove(struct platform_device *pdev)
|
||||
struct tegra_dsi *dsi = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
err = host1x_client_unregister(&dsi->client);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
@ -1619,14 +1583,78 @@ static int tegra_dsi_remove(struct platform_device *pdev)
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
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);
|
||||
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;
|
||||
|
||||
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[] = {
|
||||
{ .compatible = "nvidia,tegra210-dsi", },
|
||||
{ .compatible = "nvidia,tegra132-dsi", },
|
||||
@ -1640,6 +1668,7 @@ struct platform_driver tegra_dsi_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-dsi",
|
||||
.of_match_table = tegra_dsi_of_match,
|
||||
.pm = &tegra_dsi_pm_ops,
|
||||
},
|
||||
.probe = tegra_dsi_probe,
|
||||
.remove = tegra_dsi_remove,
|
||||
|
Loading…
Reference in New Issue
Block a user