drm/i915/skl: Implementation of SKL display power well support
authorSatheeshakrishna M <satheeshakrishna.m@intel.com>
Wed, 4 Feb 2015 13:57:44 +0000 (13:57 +0000)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Fri, 13 Feb 2015 22:28:01 +0000 (23:28 +0100)
This patch implements core logic of SKL display power well.

v2: Addressed Imre's comments
- Added respective DDIs under power well #1 and #2
- Simplified repetitive code in power well programming

v3: Implemented Imre's comments
- Further simplified power well programming
- Made sure that PW 1 is enabled prior to PW 2

v4: Fix minor conflict with the the cherryview support (Damien)

v5: Add the PLL power domain to the always on power well (Damien)

v6: Disable BIOS power well (Imre)
    Use power well data for comparison (Imre)
    Put the PLL power domain into PW1 as its needed for CDCLK (Satheesh,
    Damien)

v7: Addressed Imre's comments
  - Lowered the time out to 1ms
  - Added parantheses in macro
  - Moved debug message and fixed wait_for interval

v8:
  - Add a WARN() when swiching on an unknown power well (Imre, done by Damien)
  - Whitespace fixes (spaces instead of tabs) (Damien)

v9: (Imre, done by Damien)
  - Merge the register definitions with this patch
  - Merge the MISC IO power well in this patch

v10: (Imre, done by Damien)

  - Define the Misc I/O power domains to be the power well 1 ones as Misc I/O
    needs to be enabled with PW1
  - Added Transcoder A and VGA domains to PW 2
  - Remove the MISC_IO power domains as well in the the always on
    domains definition
  - Move Misc I/O power well at the top of the power well list so it's turned
    on right after PW1.

Reviewed-by: Imre Deak <imre.deak@intel.com>
Signed-off-by: Satheeshakrishna M <satheeshakrishna.m@intel.com> (v3,v6,v7)
Signed-off-by: Damien Lespiau <damien.lespiau@intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/i915/i915_reg.h
drivers/gpu/drm/i915/intel_runtime_pm.c

index 33b3d0a..cd3430f 100644 (file)
@@ -586,6 +586,19 @@ enum punit_power_well {
        PUNIT_POWER_WELL_NUM,
 };
 
+enum skl_disp_power_wells {
+       SKL_DISP_PW_MISC_IO,
+       SKL_DISP_PW_DDI_A_E,
+       SKL_DISP_PW_DDI_B,
+       SKL_DISP_PW_DDI_C,
+       SKL_DISP_PW_DDI_D,
+       SKL_DISP_PW_1 = 14,
+       SKL_DISP_PW_2,
+};
+
+#define SKL_POWER_WELL_STATE(pw) (1 << ((pw) * 2))
+#define SKL_POWER_WELL_REQ(pw) (1 << (((pw) * 2) + 1))
+
 #define PUNIT_REG_PWRGT_CTRL                   0x60
 #define PUNIT_REG_PWRGT_STATUS                 0x61
 #define   PUNIT_PWRGT_MASK(power_well)         (3 << ((power_well) * 2))
@@ -6351,6 +6364,13 @@ enum punit_power_well {
 #define   HSW_PWR_WELL_FORCE_ON                        (1<<19)
 #define HSW_PWR_WELL_CTL6                      0x45414
 
+/* SKL Fuse Status */
+#define SKL_FUSE_STATUS                                0x42000
+#define  SKL_FUSE_DOWNLOAD_STATUS              (1<<31)
+#define  SKL_FUSE_PG0_DIST_STATUS              (1<<27)
+#define  SKL_FUSE_PG1_DIST_STATUS              (1<<26)
+#define  SKL_FUSE_PG2_DIST_STATUS              (1<<25)
+
 /* Per-pipe DDI Function Control */
 #define TRANS_DDI_FUNC_CTL_A           0x60400
 #define TRANS_DDI_FUNC_CTL_B           0x61400
index 49695d7..6d8e29a 100644 (file)
@@ -230,6 +230,136 @@ static void hsw_set_power_well(struct drm_i915_private *dev_priv,
        }
 }
 
+#define SKL_DISPLAY_POWERWELL_2_POWER_DOMAINS (                \
+       BIT(POWER_DOMAIN_TRANSCODER_A) |                \
+       BIT(POWER_DOMAIN_PIPE_B) |                      \
+       BIT(POWER_DOMAIN_TRANSCODER_B) |                \
+       BIT(POWER_DOMAIN_PIPE_C) |                      \
+       BIT(POWER_DOMAIN_TRANSCODER_C) |                \
+       BIT(POWER_DOMAIN_PIPE_B_PANEL_FITTER) |         \
+       BIT(POWER_DOMAIN_PIPE_C_PANEL_FITTER) |         \
+       BIT(POWER_DOMAIN_PORT_DDI_B_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_B_4_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_C_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_C_4_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_D_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_D_4_LANES) |          \
+       BIT(POWER_DOMAIN_AUX_B) |                       \
+       BIT(POWER_DOMAIN_AUX_C) |                       \
+       BIT(POWER_DOMAIN_AUX_D) |                       \
+       BIT(POWER_DOMAIN_AUDIO) |                       \
+       BIT(POWER_DOMAIN_VGA) |                         \
+       BIT(POWER_DOMAIN_INIT))
+#define SKL_DISPLAY_POWERWELL_1_POWER_DOMAINS (                \
+       SKL_DISPLAY_POWERWELL_2_POWER_DOMAINS |         \
+       BIT(POWER_DOMAIN_PLLS) |                        \
+       BIT(POWER_DOMAIN_PIPE_A) |                      \
+       BIT(POWER_DOMAIN_TRANSCODER_EDP) |              \
+       BIT(POWER_DOMAIN_PIPE_A_PANEL_FITTER) |         \
+       BIT(POWER_DOMAIN_PORT_DDI_A_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_A_4_LANES) |          \
+       BIT(POWER_DOMAIN_AUX_A) |                       \
+       BIT(POWER_DOMAIN_INIT))
+#define SKL_DISPLAY_DDI_A_E_POWER_DOMAINS (            \
+       BIT(POWER_DOMAIN_PORT_DDI_A_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_A_4_LANES) |          \
+       BIT(POWER_DOMAIN_INIT))
+#define SKL_DISPLAY_DDI_B_POWER_DOMAINS (              \
+       BIT(POWER_DOMAIN_PORT_DDI_B_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_B_4_LANES) |          \
+       BIT(POWER_DOMAIN_INIT))
+#define SKL_DISPLAY_DDI_C_POWER_DOMAINS (              \
+       BIT(POWER_DOMAIN_PORT_DDI_C_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_C_4_LANES) |          \
+       BIT(POWER_DOMAIN_INIT))
+#define SKL_DISPLAY_DDI_D_POWER_DOMAINS (              \
+       BIT(POWER_DOMAIN_PORT_DDI_D_2_LANES) |          \
+       BIT(POWER_DOMAIN_PORT_DDI_D_4_LANES) |          \
+       BIT(POWER_DOMAIN_INIT))
+#define SKL_DISPLAY_MISC_IO_POWER_DOMAINS (            \
+       SKL_DISPLAY_POWERWELL_1_POWER_DOMAINS)
+#define SKL_DISPLAY_ALWAYS_ON_POWER_DOMAINS (          \
+       (POWER_DOMAIN_MASK & ~(SKL_DISPLAY_POWERWELL_1_POWER_DOMAINS |  \
+       SKL_DISPLAY_POWERWELL_2_POWER_DOMAINS |         \
+       SKL_DISPLAY_DDI_A_E_POWER_DOMAINS |             \
+       SKL_DISPLAY_DDI_B_POWER_DOMAINS |               \
+       SKL_DISPLAY_DDI_C_POWER_DOMAINS |               \
+       SKL_DISPLAY_DDI_D_POWER_DOMAINS |               \
+       SKL_DISPLAY_MISC_IO_POWER_DOMAINS)) |           \
+       BIT(POWER_DOMAIN_INIT))
+
+static void skl_set_power_well(struct drm_i915_private *dev_priv,
+                       struct i915_power_well *power_well, bool enable)
+{
+       uint32_t tmp, fuse_status;
+       uint32_t req_mask, state_mask;
+       bool check_fuse_status = false;
+
+       tmp = I915_READ(HSW_PWR_WELL_DRIVER);
+       fuse_status = I915_READ(SKL_FUSE_STATUS);
+
+       switch (power_well->data) {
+       case SKL_DISP_PW_1:
+               if (wait_for((I915_READ(SKL_FUSE_STATUS) &
+                       SKL_FUSE_PG0_DIST_STATUS), 1)) {
+                       DRM_ERROR("PG0 not enabled\n");
+                       return;
+               }
+               break;
+       case SKL_DISP_PW_2:
+               if (!(fuse_status & SKL_FUSE_PG1_DIST_STATUS)) {
+                       DRM_ERROR("PG1 in disabled state\n");
+                       return;
+               }
+               break;
+       case SKL_DISP_PW_DDI_A_E:
+       case SKL_DISP_PW_DDI_B:
+       case SKL_DISP_PW_DDI_C:
+       case SKL_DISP_PW_DDI_D:
+       case SKL_DISP_PW_MISC_IO:
+               break;
+       default:
+               WARN(1, "Unknown power well %lu\n", power_well->data);
+               return;
+       }
+
+       req_mask = SKL_POWER_WELL_REQ(power_well->data);
+       state_mask = SKL_POWER_WELL_STATE(power_well->data);
+
+       if (enable) {
+               if (!(tmp & req_mask)) {
+                       I915_WRITE(HSW_PWR_WELL_DRIVER, tmp | req_mask);
+                       DRM_DEBUG_KMS("Enabling %s\n", power_well->name);
+               }
+
+               if (!(tmp & state_mask)) {
+                       if (wait_for((I915_READ(HSW_PWR_WELL_DRIVER) &
+                               state_mask), 1))
+                               DRM_ERROR("%s enable timeout\n",
+                                       power_well->name);
+                       check_fuse_status = true;
+               }
+       } else {
+               if (tmp & req_mask) {
+                       I915_WRITE(HSW_PWR_WELL_DRIVER, tmp & ~req_mask);
+                       POSTING_READ(HSW_PWR_WELL_DRIVER);
+                       DRM_DEBUG_KMS("Disabling %s\n", power_well->name);
+               }
+       }
+
+       if (check_fuse_status) {
+               if (power_well->data == SKL_DISP_PW_1) {
+                       if (wait_for((I915_READ(SKL_FUSE_STATUS) &
+                               SKL_FUSE_PG1_DIST_STATUS), 1))
+                               DRM_ERROR("PG1 distributing status timeout\n");
+               } else if (power_well->data == SKL_DISP_PW_2) {
+                       if (wait_for((I915_READ(SKL_FUSE_STATUS) &
+                               SKL_FUSE_PG2_DIST_STATUS), 1))
+                               DRM_ERROR("PG2 distributing status timeout\n");
+               }
+       }
+}
+
 static void hsw_power_well_sync_hw(struct drm_i915_private *dev_priv,
                                   struct i915_power_well *power_well)
 {
@@ -255,6 +385,36 @@ static void hsw_power_well_disable(struct drm_i915_private *dev_priv,
        hsw_set_power_well(dev_priv, power_well, false);
 }
 
+static bool skl_power_well_enabled(struct drm_i915_private *dev_priv,
+                                       struct i915_power_well *power_well)
+{
+       uint32_t mask = SKL_POWER_WELL_REQ(power_well->data) |
+               SKL_POWER_WELL_STATE(power_well->data);
+
+       return (I915_READ(HSW_PWR_WELL_DRIVER) & mask) == mask;
+}
+
+static void skl_power_well_sync_hw(struct drm_i915_private *dev_priv,
+                               struct i915_power_well *power_well)
+{
+       skl_set_power_well(dev_priv, power_well, power_well->count > 0);
+
+       /* Clear any request made by BIOS as driver is taking over */
+       I915_WRITE(HSW_PWR_WELL_BIOS, 0);
+}
+
+static void skl_power_well_enable(struct drm_i915_private *dev_priv,
+                               struct i915_power_well *power_well)
+{
+       skl_set_power_well(dev_priv, power_well, true);
+}
+
+static void skl_power_well_disable(struct drm_i915_private *dev_priv,
+                               struct i915_power_well *power_well)
+{
+       skl_set_power_well(dev_priv, power_well, false);
+}
+
 static void i9xx_always_on_power_well_noop(struct drm_i915_private *dev_priv,
                                           struct i915_power_well *power_well)
 {
@@ -829,6 +989,13 @@ static const struct i915_power_well_ops hsw_power_well_ops = {
        .is_enabled = hsw_power_well_enabled,
 };
 
+static const struct i915_power_well_ops skl_power_well_ops = {
+       .sync_hw = skl_power_well_sync_hw,
+       .enable = skl_power_well_enable,
+       .disable = skl_power_well_disable,
+       .is_enabled = skl_power_well_enabled,
+};
+
 static struct i915_power_well hsw_power_wells[] = {
        {
                .name = "always-on",
@@ -1059,6 +1226,57 @@ static struct i915_power_well *lookup_power_well(struct drm_i915_private *dev_pr
        return NULL;
 }
 
+static struct i915_power_well skl_power_wells[] = {
+       {
+               .name = "always-on",
+               .always_on = 1,
+               .domains = SKL_DISPLAY_ALWAYS_ON_POWER_DOMAINS,
+               .ops = &i9xx_always_on_power_well_ops,
+       },
+       {
+               .name = "power well 1",
+               .domains = SKL_DISPLAY_POWERWELL_1_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_1,
+       },
+       {
+               .name = "MISC IO power well",
+               .domains = SKL_DISPLAY_MISC_IO_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_MISC_IO,
+       },
+       {
+               .name = "power well 2",
+               .domains = SKL_DISPLAY_POWERWELL_2_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_2,
+       },
+       {
+               .name = "DDI A/E power well",
+               .domains = SKL_DISPLAY_DDI_A_E_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_DDI_A_E,
+       },
+       {
+               .name = "DDI B power well",
+               .domains = SKL_DISPLAY_DDI_B_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_DDI_B,
+       },
+       {
+               .name = "DDI C power well",
+               .domains = SKL_DISPLAY_DDI_C_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_DDI_C,
+       },
+       {
+               .name = "DDI D power well",
+               .domains = SKL_DISPLAY_DDI_D_POWER_DOMAINS,
+               .ops = &skl_power_well_ops,
+               .data = SKL_DISP_PW_DDI_D,
+       },
+};
+
 #define set_power_wells(power_domains, __power_wells) ({               \
        (power_domains)->power_wells = (__power_wells);                 \
        (power_domains)->power_well_count = ARRAY_SIZE(__power_wells);  \
@@ -1085,6 +1303,8 @@ int intel_power_domains_init(struct drm_i915_private *dev_priv)
                set_power_wells(power_domains, hsw_power_wells);
        } else if (IS_BROADWELL(dev_priv->dev)) {
                set_power_wells(power_domains, bdw_power_wells);
+       } else if (IS_SKYLAKE(dev_priv->dev)) {
+               set_power_wells(power_domains, skl_power_wells);
        } else if (IS_CHERRYVIEW(dev_priv->dev)) {
                set_power_wells(power_domains, chv_power_wells);
        } else if (IS_VALLEYVIEW(dev_priv->dev)) {