drm/vc4: Add runtime PM support to the HDMI encoder driver
authorBoris Brezillon <boris.brezillon@free-electrons.com>
Tue, 11 Apr 2017 16:39:25 +0000 (18:39 +0200)
committerEric Anholt <eric@anholt.net>
Tue, 18 Apr 2017 19:27:32 +0000 (12:27 -0700)
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: Boris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-by: Eric Anholt <eric@anholt.net>
drivers/gpu/drm/vc4/vc4_hdmi.c

index e9cbe269710bc194d7caab8031e0d71ec3d61b9e..3c2723f6345c36a66c595b495c73dec3ddaafde5 100644 (file)
@@ -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;