Commit fd67e9c6 authored by Thierry Reding's avatar Thierry Reding
Browse files

drm/tegra: Do not implement runtime PM



The Tegra DRM driver heavily relies on the implementations for runtime
suspend/resume to be called at specific times. Unfortunately, there are
some cases where that doesn't work. One example is if the user disables
runtime PM for a given subdevice. Another example is that the PM core
acquires a reference to runtime PM during system sleep, effectively
preventing devices from going into low power modes. This is intentional
to avoid nasty race conditions, but it also causes system sleep to not
function properly on all Tegra systems.

Fix this by not implementing runtime PM at all. Instead, a minimal,
reference-counted suspend/resume infrastructure is added to the host1x
bus. This has the benefit that it can be used regardless of the system
power state (or any transitions we might be in), or whether or not the
user allows runtime PM.

Atomic modesetting guarantees that these functions will end up being
called at the right point in time, so the pitfalls for the more generic
runtime PM do not apply here.

Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 608f43ad
Loading
Loading
Loading
Loading
+84 −57
Original line number Diff line number Diff line
@@ -1727,6 +1727,7 @@ static void tegra_crtc_atomic_disable(struct drm_crtc *crtc,
{
	struct tegra_dc *dc = to_tegra_dc(crtc);
	u32 value;
	int err;

	if (!tegra_dc_idle(dc)) {
		tegra_dc_stop(dc);
@@ -1773,7 +1774,9 @@ static void tegra_crtc_atomic_disable(struct drm_crtc *crtc,

	spin_unlock_irq(&crtc->dev->event_lock);

	pm_runtime_put_sync(dc->dev);
	err = host1x_client_suspend(&dc->client);
	if (err < 0)
		dev_err(dc->dev, "failed to suspend: %d\n", err);
}

static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
@@ -1783,8 +1786,13 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
	struct tegra_dc_state *state = to_dc_state(crtc->state);
	struct tegra_dc *dc = to_tegra_dc(crtc);
	u32 value;
	int err;

	pm_runtime_get_sync(dc->dev);
	err = host1x_client_resume(&dc->client);
	if (err < 0) {
		dev_err(dc->dev, "failed to resume: %d\n", err);
		return;
	}

	/* initialize display controller */
	if (dc->syncpt) {
@@ -2012,6 +2020,15 @@ static int tegra_dc_init(struct host1x_client *client)
	if (!tegra_dc_has_window_groups(dc))
		return 0;

	/*
	 * Set the display hub as the host1x client parent for the display
	 * controller. This is needed for the runtime reference counting that
	 * ensures the display hub is always powered when any of the display
	 * controllers are.
	 */
	if (dc->soc->has_nvdisplay)
		client->parent = &tegra->hub->client;

	dc->syncpt = host1x_syncpt_request(client, flags);
	if (!dc->syncpt)
		dev_warn(dc->dev, "failed to allocate syncpoint\n");
@@ -2121,9 +2138,74 @@ static int tegra_dc_exit(struct host1x_client *client)
	return 0;
}

static int tegra_dc_runtime_suspend(struct host1x_client *client)
{
	struct tegra_dc *dc = host1x_client_to_dc(client);
	struct device *dev = client->dev;
	int err;

	err = reset_control_assert(dc->rst);
	if (err < 0) {
		dev_err(dev, "failed to assert reset: %d\n", err);
		return err;
	}

	if (dc->soc->has_powergate)
		tegra_powergate_power_off(dc->powergate);

	clk_disable_unprepare(dc->clk);
	pm_runtime_put_sync(dev);

	return 0;
}

static int tegra_dc_runtime_resume(struct host1x_client *client)
{
	struct tegra_dc *dc = host1x_client_to_dc(client);
	struct device *dev = client->dev;
	int err;

	err = pm_runtime_get_sync(dev);
	if (err < 0) {
		dev_err(dev, "failed to get runtime PM: %d\n", err);
		return err;
	}

	if (dc->soc->has_powergate) {
		err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
							dc->rst);
		if (err < 0) {
			dev_err(dev, "failed to power partition: %d\n", err);
			goto put_rpm;
		}
	} else {
		err = clk_prepare_enable(dc->clk);
		if (err < 0) {
			dev_err(dev, "failed to enable clock: %d\n", err);
			goto put_rpm;
		}

		err = reset_control_deassert(dc->rst);
		if (err < 0) {
			dev_err(dev, "failed to deassert reset: %d\n", err);
			goto disable_clk;
		}
	}

	return 0;

disable_clk:
	clk_disable_unprepare(dc->clk);
put_rpm:
	pm_runtime_put_sync(dev);
	return err;
}

static const struct host1x_client_ops dc_client_ops = {
	.init = tegra_dc_init,
	.exit = tegra_dc_exit,
	.suspend = tegra_dc_runtime_suspend,
	.resume = tegra_dc_runtime_resume,
};

static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
@@ -2535,65 +2617,10 @@ static int tegra_dc_remove(struct platform_device *pdev)
	return 0;
}

#ifdef CONFIG_PM
static int tegra_dc_suspend(struct device *dev)
{
	struct tegra_dc *dc = dev_get_drvdata(dev);
	int err;

	err = reset_control_assert(dc->rst);
	if (err < 0) {
		dev_err(dev, "failed to assert reset: %d\n", err);
		return err;
	}

	if (dc->soc->has_powergate)
		tegra_powergate_power_off(dc->powergate);

	clk_disable_unprepare(dc->clk);

	return 0;
}

static int tegra_dc_resume(struct device *dev)
{
	struct tegra_dc *dc = dev_get_drvdata(dev);
	int err;

	if (dc->soc->has_powergate) {
		err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
							dc->rst);
		if (err < 0) {
			dev_err(dev, "failed to power partition: %d\n", err);
			return err;
		}
	} else {
		err = clk_prepare_enable(dc->clk);
		if (err < 0) {
			dev_err(dev, "failed to enable clock: %d\n", err);
			return err;
		}

		err = reset_control_deassert(dc->rst);
		if (err < 0) {
			dev_err(dev, "failed to deassert reset: %d\n", err);
			return err;
		}
	}

	return 0;
}
#endif

static const struct dev_pm_ops tegra_dc_pm_ops = {
	SET_RUNTIME_PM_OPS(tegra_dc_suspend, tegra_dc_resume, NULL)
};

struct platform_driver tegra_dc_driver = {
	.driver = {
		.name = "tegra-dc",
		.of_match_table = tegra_dc_of_match,
		.pm = &tegra_dc_pm_ops,
	},
	.probe = tegra_dc_probe,
	.remove = tegra_dc_remove,
+1 −1
Original line number Diff line number Diff line
@@ -588,7 +588,7 @@ static int tegra_dpaux_remove(struct platform_device *pdev)
	/* make sure pads are powered down when not in use */
	tegra_dpaux_pad_power_down(dpaux);

	pm_runtime_put(&pdev->dev);
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);

	drm_dp_aux_unregister(&dpaux->aux);
+2 −0
Original line number Diff line number Diff line
@@ -144,6 +144,8 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output);
void tegra_output_exit(struct tegra_output *output);
void tegra_output_find_possible_crtcs(struct tegra_output *output,
				      struct drm_device *drm);
int tegra_output_suspend(struct tegra_output *output);
int tegra_output_resume(struct tegra_output *output);

int tegra_output_connector_get_modes(struct drm_connector *connector);
enum drm_connector_status
+97 −78
Original line number Diff line number Diff line
@@ -840,7 +840,9 @@ static void tegra_dsi_unprepare(struct tegra_dsi *dsi)
		dev_err(dsi->dev, "failed to disable MIPI calibration: %d\n",
			err);

	pm_runtime_put(dsi->dev);
	err = host1x_client_suspend(&dsi->client);
	if (err < 0)
		dev_err(dsi->dev, "failed to suspend: %d\n", err);
}

static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
@@ -882,11 +884,15 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
	tegra_dsi_unprepare(dsi);
}

static void tegra_dsi_prepare(struct tegra_dsi *dsi)
static int tegra_dsi_prepare(struct tegra_dsi *dsi)
{
	int err;

	pm_runtime_get_sync(dsi->dev);
	err = host1x_client_resume(&dsi->client);
	if (err < 0) {
		dev_err(dsi->dev, "failed to resume: %d\n", err);
		return err;
	}

	err = tegra_mipi_enable(dsi->mipi);
	if (err < 0)
@@ -899,6 +905,8 @@ static void tegra_dsi_prepare(struct tegra_dsi *dsi)

	if (dsi->slave)
		tegra_dsi_prepare(dsi->slave);

	return 0;
}

static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
@@ -909,8 +917,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;

	tegra_dsi_prepare(dsi);
	err = tegra_dsi_prepare(dsi);
	if (err < 0) {
		dev_err(dsi->dev, "failed to prepare: %d\n", err);
		return;
	}

	state = tegra_dsi_get_state(dsi);

@@ -1075,9 +1088,89 @@ static int tegra_dsi_exit(struct host1x_client *client)
	return 0;
}

static int tegra_dsi_runtime_suspend(struct host1x_client *client)
{
	struct tegra_dsi *dsi = host1x_client_to_dsi(client);
	struct device *dev = client->dev;
	int err;

	if (dsi->rst) {
		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);

	regulator_disable(dsi->vdd);
	pm_runtime_put_sync(dev);

	return 0;
}

static int tegra_dsi_runtime_resume(struct host1x_client *client)
{
	struct tegra_dsi *dsi = host1x_client_to_dsi(client);
	struct device *dev = client->dev;
	int err;

	err = pm_runtime_get_sync(dev);
	if (err < 0) {
		dev_err(dev, "failed to get runtime PM: %d\n", err);
		return err;
	}

	err = regulator_enable(dsi->vdd);
	if (err < 0) {
		dev_err(dev, "failed to enable VDD supply: %d\n", err);
		goto put_rpm;
	}

	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);

	if (dsi->rst) {
		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);
put_rpm:
	pm_runtime_put_sync(dev);
	return err;
}

static const struct host1x_client_ops dsi_client_ops = {
	.init = tegra_dsi_init,
	.exit = tegra_dsi_exit,
	.suspend = tegra_dsi_runtime_suspend,
	.resume = tegra_dsi_runtime_resume,
};

static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi)
@@ -1596,79 +1689,6 @@ static int tegra_dsi_remove(struct platform_device *pdev)
	return 0;
}

#ifdef CONFIG_PM
static int tegra_dsi_suspend(struct device *dev)
{
	struct tegra_dsi *dsi = dev_get_drvdata(dev);
	int err;

	if (dsi->rst) {
		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);

	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);

	if (dsi->rst) {
		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", },
@@ -1682,7 +1702,6 @@ 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,
+66 −50
Original line number Diff line number Diff line
@@ -1146,6 +1146,7 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
	struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
	struct tegra_hdmi *hdmi = to_hdmi(output);
	u32 value;
	int err;

	/*
	 * The following accesses registers of the display controller, so make
@@ -1171,7 +1172,9 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
	tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE);
	tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK);

	pm_runtime_put(hdmi->dev);
	err = host1x_client_suspend(&hdmi->client);
	if (err < 0)
		dev_err(hdmi->dev, "failed to suspend: %d\n", err);
}

static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
@@ -1186,7 +1189,11 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder)
	u32 value;
	int err;

	pm_runtime_get_sync(hdmi->dev);
	err = host1x_client_resume(&hdmi->client);
	if (err < 0) {
		dev_err(hdmi->dev, "failed to resume: %d\n", err);
		return;
	}

	/*
	 * Enable and unmask the HDA codec SCRATCH0 register interrupt. This
@@ -1489,9 +1496,66 @@ static int tegra_hdmi_exit(struct host1x_client *client)
	return 0;
}

static int tegra_hdmi_runtime_suspend(struct host1x_client *client)
{
	struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
	struct device *dev = client->dev;
	int err;

	err = reset_control_assert(hdmi->rst);
	if (err < 0) {
		dev_err(dev, "failed to assert reset: %d\n", err);
		return err;
	}

	usleep_range(1000, 2000);

	clk_disable_unprepare(hdmi->clk);
	pm_runtime_put_sync(dev);

	return 0;
}

static int tegra_hdmi_runtime_resume(struct host1x_client *client)
{
	struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
	struct device *dev = client->dev;
	int err;

	err = pm_runtime_get_sync(dev);
	if (err < 0) {
		dev_err(dev, "failed to get runtime PM: %d\n", err);
		return err;
	}

	err = clk_prepare_enable(hdmi->clk);
	if (err < 0) {
		dev_err(dev, "failed to enable clock: %d\n", err);
		goto put_rpm;
	}

	usleep_range(1000, 2000);

	err = reset_control_deassert(hdmi->rst);
	if (err < 0) {
		dev_err(dev, "failed to deassert reset: %d\n", err);
		goto disable_clk;
	}

	return 0;

disable_clk:
	clk_disable_unprepare(hdmi->clk);
put_rpm:
	pm_runtime_put_sync(dev);
	return err;
}

static const struct host1x_client_ops hdmi_client_ops = {
	.init = tegra_hdmi_init,
	.exit = tegra_hdmi_exit,
	.suspend = tegra_hdmi_runtime_suspend,
	.resume = tegra_hdmi_runtime_resume,
};

static const struct tegra_hdmi_config tegra20_hdmi_config = {
@@ -1699,58 +1763,10 @@ static int tegra_hdmi_remove(struct platform_device *pdev)
	return 0;
}

#ifdef CONFIG_PM
static int tegra_hdmi_suspend(struct device *dev)
{
	struct tegra_hdmi *hdmi = dev_get_drvdata(dev);
	int err;

	err = reset_control_assert(hdmi->rst);
	if (err < 0) {
		dev_err(dev, "failed to assert reset: %d\n", err);
		return err;
	}

	usleep_range(1000, 2000);

	clk_disable_unprepare(hdmi->clk);

	return 0;
}

static int tegra_hdmi_resume(struct device *dev)
{
	struct tegra_hdmi *hdmi = dev_get_drvdata(dev);
	int err;

	err = clk_prepare_enable(hdmi->clk);
	if (err < 0) {
		dev_err(dev, "failed to enable clock: %d\n", err);
		return err;
	}

	usleep_range(1000, 2000);

	err = reset_control_deassert(hdmi->rst);
	if (err < 0) {
		dev_err(dev, "failed to deassert reset: %d\n", err);
		clk_disable_unprepare(hdmi->clk);
		return err;
	}

	return 0;
}
#endif

static const struct dev_pm_ops tegra_hdmi_pm_ops = {
	SET_RUNTIME_PM_OPS(tegra_hdmi_suspend, tegra_hdmi_resume, NULL)
};

struct platform_driver tegra_hdmi_driver = {
	.driver = {
		.name = "tegra-hdmi",
		.of_match_table = tegra_hdmi_of_match,
		.pm = &tegra_hdmi_pm_ops,
	},
	.probe = tegra_hdmi_probe,
	.remove = tegra_hdmi_remove,
Loading