Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4f6e3d66 authored by Boris Brezillon's avatar Boris Brezillon Committed by Eric Anholt
Browse files

drm/vc4: Add runtime PM support to the HDMI encoder driver



The HDMI driver is currently enabling all clocks at probe time and
keeps the power-domain connected to the HDMI encoder enabled.

Move all activation code to vc4_hdmi_encoder_enable() and make sure
the clks and power domain are released when the HDMI encoder is not used
by adding deactivation steps in vc4_hdmi_encoder_disable().

Note that the sequencing imposed by the IP requires that we move
vc4_hdmi_encoder_mode_set() code into vc4_hdmi_encoder_enable().

Signed-off-by: default avatarBoris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: default avatarEric Anholt <eric@anholt.net>
Reviewed-by: default avatarEric Anholt <eric@anholt.net>
parent 75cccac4
Loading
Loading
Loading
Loading
+92 −82
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@
#include "linux/of_address.h"
#include "linux/of_gpio.h"
#include "linux/of_platform.h"
#include "linux/pm_runtime.h"
#include "linux/rational.h"
#include "sound/dmaengine_pcm.h"
#include "sound/pcm_drm_eld.h"
@@ -449,13 +450,38 @@ static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)
	vc4_hdmi_set_spd_infoframe(encoder);
}

static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder,
				      struct drm_display_mode *unadjusted_mode,
				      struct drm_display_mode *mode)
static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder)
{
	struct drm_device *dev = encoder->dev;
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	struct vc4_hdmi *hdmi = vc4->hdmi;
	int ret;

	HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, 0);

	HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16);
	HD_WRITE(VC4_HD_VID_CTL,
		 HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);

	HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST);
	udelay(1);
	HD_WRITE(VC4_HD_M_CTL, 0);

	clk_disable_unprepare(hdmi->hsm_clock);
	clk_disable_unprepare(hdmi->pixel_clock);

	ret = pm_runtime_put(&hdmi->pdev->dev);
	if (ret < 0)
		DRM_ERROR("Failed to release power domain: %d\n", ret);
}

static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)
{
	struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
	struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);
	struct drm_device *dev = encoder->dev;
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	struct vc4_hdmi *hdmi = vc4->hdmi;
	bool debug_dump_regs = false;
	bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
	bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
@@ -475,6 +501,64 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder,
					interlaced,
					VC4_HDMI_VERTB_VBP));
	u32 csc_ctl;
	int ret;

	ret = pm_runtime_get_sync(&hdmi->pdev->dev);
	if (ret < 0) {
		DRM_ERROR("Failed to retain power domain: %d\n", ret);
		return;
	}

	/* This is the rate that is set by the firmware.  The number
	 * needs to be a bit higher than the pixel clock rate
	 * (generally 148.5Mhz).
	 */
	ret = clk_set_rate(hdmi->hsm_clock, 163682864);
	if (ret) {
		DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
		return;
	}

	ret = clk_set_rate(hdmi->pixel_clock,
			   mode->clock * 1000 *
			   ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1));
	if (ret) {
		DRM_ERROR("Failed to set pixel clock rate: %d\n", ret);
		return;
	}

	ret = clk_prepare_enable(hdmi->pixel_clock);
	if (ret) {
		DRM_ERROR("Failed to turn on pixel clock: %d\n", ret);
		return;
	}

	ret = clk_prepare_enable(hdmi->hsm_clock);
	if (ret) {
		DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n",
			  ret);
		clk_disable_unprepare(hdmi->pixel_clock);
		return;
	}

	HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST);
	udelay(1);
	HD_WRITE(VC4_HD_M_CTL, 0);

	HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE);

	HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL,
		   VC4_HDMI_SW_RESET_HDMI |
		   VC4_HDMI_SW_RESET_FORMAT_DETECT);

	HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, 0);

	/* PHY should be in reset, like
	 * vc4_hdmi_encoder_disable() does.
	 */
	HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16);

	HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0);

	if (debug_dump_regs) {
		DRM_INFO("HDMI regs before:\n");
@@ -483,9 +567,6 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder,

	HD_WRITE(VC4_HD_VID_CTL, 0);

	clk_set_rate(vc4->hdmi->pixel_clock, mode->clock * 1000 *
		     ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1));

	HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL,
		   HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) |
		   VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT |
@@ -559,28 +640,6 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder,
		DRM_INFO("HDMI regs after:\n");
		vc4_hdmi_dump_regs(dev);
	}
}

static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder)
{
	struct drm_device *dev = encoder->dev;
	struct vc4_dev *vc4 = to_vc4_dev(dev);

	HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, 0);

	HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16);
	HD_WRITE(VC4_HD_VID_CTL,
		 HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);
}

static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)
{
	struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);
	struct drm_device *dev = encoder->dev;
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	int ret;

	HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0);

	HD_WRITE(VC4_HD_VID_CTL,
		 HD_READ(VC4_HD_VID_CTL) |
@@ -646,7 +705,6 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)
}

static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {
	.mode_set = vc4_hdmi_encoder_mode_set,
	.disable = vc4_hdmi_encoder_disable,
	.enable = vc4_hdmi_encoder_enable,
};
@@ -1147,33 +1205,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
		return -EPROBE_DEFER;
	}

	/* Enable the clocks at startup.  We can't quite recover from
	 * turning off the pixel clock during disable/enables yet, so
	 * it's always running.
	 */
	ret = clk_prepare_enable(hdmi->pixel_clock);
	if (ret) {
		DRM_ERROR("Failed to turn on pixel clock: %d\n", ret);
		goto err_put_i2c;
	}

	/* This is the rate that is set by the firmware.  The number
	 * needs to be a bit higher than the pixel clock rate
	 * (generally 148.5Mhz).
	 */
	ret = clk_set_rate(hdmi->hsm_clock, 163682864);
	if (ret) {
		DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
		goto err_unprepare_pix;
	}

	ret = clk_prepare_enable(hdmi->hsm_clock);
	if (ret) {
		DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n",
			  ret);
		goto err_unprepare_pix;
	}

	/* Only use the GPIO HPD pin if present in the DT, otherwise
	 * we'll use the HDMI core's register.
	 */
@@ -1185,7 +1216,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
							 &hpd_gpio_flags);
		if (hdmi->hpd_gpio < 0) {
			ret = hdmi->hpd_gpio;
			goto err_unprepare_hsm;
			goto err_put_i2c;
		}

		hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW;
@@ -1193,25 +1224,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)

	vc4->hdmi = hdmi;

	/* HDMI core must be enabled. */
	if (!(HD_READ(VC4_HD_M_CTL) & VC4_HD_M_ENABLE)) {
		HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST);
		udelay(1);
		HD_WRITE(VC4_HD_M_CTL, 0);

		HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE);

		HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL,
			   VC4_HDMI_SW_RESET_HDMI |
			   VC4_HDMI_SW_RESET_FORMAT_DETECT);

		HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, 0);

		/* PHY should be in reset, like
		 * vc4_hdmi_encoder_disable() does.
		 */
		HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16);
	}
	pm_runtime_enable(dev);

	drm_encoder_init(drm, hdmi->encoder, &vc4_hdmi_encoder_funcs,
			 DRM_MODE_ENCODER_TMDS, NULL);
@@ -1231,10 +1244,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)

err_destroy_encoder:
	vc4_hdmi_encoder_destroy(hdmi->encoder);
err_unprepare_hsm:
	clk_disable_unprepare(hdmi->hsm_clock);
err_unprepare_pix:
	clk_disable_unprepare(hdmi->pixel_clock);
	pm_runtime_disable(dev);
err_put_i2c:
	put_device(&hdmi->ddc->dev);

@@ -1253,8 +1263,8 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master,
	vc4_hdmi_connector_destroy(hdmi->connector);
	vc4_hdmi_encoder_destroy(hdmi->encoder);

	clk_disable_unprepare(hdmi->pixel_clock);
	clk_disable_unprepare(hdmi->hsm_clock);
	pm_runtime_disable(dev);

	put_device(&hdmi->ddc->dev);

	vc4->hdmi = NULL;