Changes to existing code for interface available on Baytrail and CherryTrail This driver was downloaded from https://github.com/01org/baytrailaudio/ ...and had the changes to .config stripped and the revert on sound/init.c Cleanup, port to 4.4 and intel-drm by Pierre Bossart Signed-off-by: David Henningsson <david.henningsson@xxxxxxxxxxxxx> Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@xxxxxxxxxxxxxxx> --- drivers/gpu/drm/i915/i915_irq.c | 81 ++++++++++++++++ drivers/gpu/drm/i915/intel_display.c | 8 ++ drivers/gpu/drm/i915/intel_hdmi.c | 183 ++++++++++++++++++++++++++++++++++- 3 files changed, 271 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index d1a46ef..556fa80 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -603,6 +603,31 @@ i915_disable_pipestat(struct drm_i915_private *dev_priv, enum pipe pipe, __i915_disable_pipestat(dev_priv, pipe, enable_mask, status_mask); } +/* Added for HDMI AUDIO */ +void +i915_enable_lpe_pipestat(struct drm_i915_private *dev_priv, int pipe) +{ + u32 mask; + + mask = dev_priv->hdmi_audio_interrupt_mask; + mask |= I915_HDMI_AUDIO_UNDERRUN | I915_HDMI_AUDIO_BUFFER_DONE; + /* Enable the interrupt, clear any pending status */ + I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, mask); + POSTING_READ(I915_LPE_AUDIO_HDMI_STATUS_A); +} + +void +i915_disable_lpe_pipestat(struct drm_i915_private *dev_priv, int pipe) +{ + u32 mask; + + mask = dev_priv->hdmi_audio_interrupt_mask; + mask |= I915_HDMI_AUDIO_UNDERRUN | I915_HDMI_AUDIO_BUFFER_DONE; + /* Disable the interrupt, clear any pending status */ + I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, mask); + POSTING_READ(I915_LPE_AUDIO_HDMI_STATUS_A); +} + /** * i915_enable_asle_pipestat - enable ASLE pipestat for OpRegion * @dev: drm device @@ -1649,6 +1674,7 @@ static void valleyview_pipestat_irq_handler(struct drm_device *dev, u32 iir) struct drm_i915_private *dev_priv = dev->dev_private; u32 pipe_stats[I915_MAX_PIPES] = { }; int pipe; + int lpe_stream; spin_lock(&dev_priv->irq_lock); @@ -1719,6 +1745,24 @@ static void valleyview_pipestat_irq_handler(struct drm_device *dev, u32 iir) intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); } + if (iir & I915_LPE_PIPE_A_INTERRUPT) { + lpe_stream = I915_READ(I915_LPE_AUDIO_HDMI_STATUS_A); + if (lpe_stream & I915_HDMI_AUDIO_UNDERRUN) { + I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, + lpe_stream); + mid_hdmi_audio_signal_event(dev, + HAD_EVENT_AUDIO_BUFFER_UNDERRUN); + } + + lpe_stream = I915_READ(I915_LPE_AUDIO_HDMI_STATUS_A); + if (lpe_stream & I915_HDMI_AUDIO_BUFFER_DONE) { + I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, + lpe_stream); + mid_hdmi_audio_signal_event(dev, + HAD_EVENT_AUDIO_BUFFER_DONE); + } + } + if (pipe_stats[0] & PIPE_GMBUS_INTERRUPT_STATUS) gmbus_irq_handler(dev); } @@ -2804,6 +2848,43 @@ static void gen8_disable_vblank(struct drm_device *dev, unsigned int pipe) spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); } +/* Added fo HDMI AUdio */ +int i915_enable_hdmi_audio_int(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = (struct drm_i915_private *) dev->dev_private; + unsigned long irqflags; + u32 imr; + int pipe = 1; + + spin_lock_irqsave(&dev_priv->irq_lock, irqflags); + imr = I915_READ(VLV_IMR); + /* Audio is on Stream A */ + imr &= ~I915_LPE_PIPE_A_INTERRUPT; + I915_WRITE(VLV_IMR, imr); + i915_enable_lpe_pipestat(dev_priv, pipe); + spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); + + return 0; +} + +/* Added for HDMI Audio */ +int i915_disable_hdmi_audio_int(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = (struct drm_i915_private *) dev->dev_private; + unsigned long irqflags; + u32 imr; + int pipe = 1; + + spin_lock_irqsave(&dev_priv->irq_lock, irqflags); + imr = I915_READ(VLV_IMR); + imr |= I915_LPE_PIPE_A_INTERRUPT; + I915_WRITE(VLV_IMR, imr); + i915_disable_lpe_pipestat(dev_priv, pipe); + spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); + + return 0; +} + static bool ring_idle(struct intel_engine_cs *ring, u32 seqno) { diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 44fcff0..5831af4 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -8108,6 +8108,14 @@ static int i9xx_crtc_compute_clock(struct intel_crtc *crtc, num_connectors); } + /* Added for HDMI Audio */ + if (IS_VALLEYVIEW(dev) && intel_pipe_has_type(crtc, + INTEL_OUTPUT_HDMI)) { + dev_priv->tmds_clock_speed = crtc_state->port_clock; + mid_hdmi_audio_signal_event(dev_priv->dev, + HAD_EVENT_MODE_CHANGING); + } + return 0; } diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 1beb155..8b6c31a 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -1391,6 +1391,124 @@ intel_hdmi_set_edid(struct drm_connector *connector, bool force) return connected; } +static bool vlv_hdmi_live_status(struct drm_device *dev, + struct intel_hdmi *intel_hdmi) +{ + uint32_t bit; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_digital_port *intel_dig_port = + hdmi_to_dig_port(intel_hdmi); + + DRM_DEBUG_KMS("Reading Live status"); + switch (intel_dig_port->port) { + case PORT_B: + bit = HDMIB_HOTPLUG_LIVE_STATUS; + break; + case PORT_C: + bit = HDMIC_HOTPLUG_LIVE_STATUS; + break; + case PORT_D: + bit = HDMID_HOTPLUG_LIVE_STATUS; + break; + default: + bit = 0; + } + + /* Return results in trems of connector */ + return I915_READ(PORT_HOTPLUG_STAT) & bit; +} + + +/* + * intel_hdmi_live_status: detect live status of HDMI + * if device is gen 6 and above, read the live status reg + * else, do not block the detection, return true + */ +static bool intel_hdmi_live_status(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector); + + if (INTEL_INFO(dev)->gen > 6) { + /* Todo: Implement for other Gen 6+ archs*/ + if (IS_VALLEYVIEW(dev)) + return vlv_hdmi_live_status(dev, intel_hdmi); + } + + return true; +} + +/* Read DDC and get EDID */ +struct edid *intel_hdmi_get_edid(struct drm_connector *connector, bool force) +{ + bool current_state = false; + bool saved_state = false; + + struct edid *new_edid = NULL; + struct i2c_adapter *adapter = NULL; + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector); + u32 hotplug_status = dev_priv->hotplug_status; + enum port hdmi_port = hdmi_to_dig_port(intel_hdmi)->port; + unsigned char retry = HDMI_EDID_RETRY_COUNT; + + if (!intel_hdmi) { + DRM_ERROR("Invalid input to get hdmi\n"); + return NULL; + } + + /* Get the saved status from top half */ + saved_state = hotplug_status & (1 << (HDMI_LIVE_STATUS_BASE - hdmi_port)); + + /* + * Few monitors are slow to respond on EDID and live status, + * so read live status multiple times within a max delay of 30ms + */ + do { + mdelay(HDMI_LIVE_STATUS_DELAY_STEP); + current_state = intel_hdmi_live_status(connector); + if (current_state) + break; + } while (retry--); + + /* Compare current status, and saved status in top half */ + if (current_state != saved_state) + DRM_DEBUG_DRIVER("Warning: Saved HDMI status != current status"); + + /* Read EDID if live status or saved status is up, or we are forced */ + if (current_state || saved_state || force) { + + adapter = intel_gmbus_get_adapter(dev_priv, + intel_hdmi->ddc_bus); + if (!adapter) { + DRM_ERROR("Get_hdmi cant get adapter\n"); + return NULL; + } + + /* + * Few monitors issue EDID after some delay, so give them + * some chances, but within 30ms + */ + retry = 3; +READ_EDID: + new_edid = drm_get_edid(connector, adapter); + if (!new_edid) { + if (retry--) { + mdelay(HDMI_LIVE_STATUS_DELAY_STEP); + goto READ_EDID; + } + + DRM_ERROR("Get_hdmi cant read edid\n"); + return NULL; + } + + DRM_DEBUG_KMS("Live status up, got EDID"); + } + + return new_edid; +} + static enum drm_connector_status intel_hdmi_detect(struct drm_connector *connector, bool force) { @@ -1399,6 +1517,8 @@ intel_hdmi_detect(struct drm_connector *connector, bool force) struct drm_i915_private *dev_priv = to_i915(connector->dev); bool live_status = false; unsigned int try; + bool inform_audio = false; + struct drm_device *dev = connector->dev; DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, connector->name); @@ -1427,6 +1547,26 @@ intel_hdmi_detect(struct drm_connector *connector, bool force) intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS); + /* Need to inform audio about the event */ + intel_hdmi = intel_attached_hdmi(connector); + if (intel_hdmi->has_audio) + inform_audio = true; + + if (status == connector_status_connected) { + if (intel_hdmi->has_audio) + i915_notify_had = 1; + } else { + /* Send a disconnect event to audio */ + if (inform_audio) { + DRM_DEBUG_DRIVER("Sending event to audio"); + mid_hdmi_audio_signal_event(dev_priv->dev, + HAD_EVENT_HOT_UNPLUG); + } + } + + if (IS_VALLEYVIEW(dev)) + i915_hdmi_state = status; + return status; } @@ -1450,12 +1590,22 @@ intel_hdmi_force(struct drm_connector *connector) static int intel_hdmi_get_modes(struct drm_connector *connector) { struct edid *edid; + int ret; + struct drm_i915_private *dev_priv = connector->dev->dev_private; edid = to_intel_connector(connector)->detect_edid; if (edid == NULL) return 0; - return intel_connector_update_modes(connector, edid); + ret = intel_connector_update_modes(connector, edid); + + if (i915_notify_had) { + mid_hdmi_audio_signal_event(dev_priv->dev, + HAD_EVENT_HOT_PLUG); + i915_notify_had = 0; + } + + return ret; } static bool @@ -2159,6 +2309,20 @@ void intel_hdmi_init_connector(struct intel_digital_port *intel_dig_port, u32 temp = I915_READ(PEG_BAND_GAP_DATA); I915_WRITE(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd); } + + i915_notify_had = 1; +} + +/* Added for HDMI Audio */ +void i915_had_wq(struct work_struct *work) +{ + struct drm_i915_private *dev_priv = container_of(work, + struct drm_i915_private, hdmi_audio_wq); + + if (i915_hdmi_state == connector_status_connected) { + mid_hdmi_audio_signal_event(dev_priv->dev, + HAD_EVENT_HOT_PLUG); + } } void intel_hdmi_init(struct drm_device *dev, @@ -2168,6 +2332,8 @@ void intel_hdmi_init(struct drm_device *dev, struct intel_digital_port *intel_dig_port; struct intel_encoder *intel_encoder; struct intel_connector *intel_connector; + /* Added for HDMI Audio */ + struct hdmi_audio_priv *hdmi_priv; intel_dig_port = kzalloc(sizeof(*intel_dig_port), GFP_KERNEL); if (!intel_dig_port) @@ -2239,4 +2405,19 @@ void intel_hdmi_init(struct drm_device *dev, intel_dig_port->max_lanes = 4; intel_hdmi_init_connector(intel_dig_port, intel_connector); + + /* Added for HDMI Audio */ + /* HDMI private data */ + INIT_WORK(&dev_priv->hdmi_audio_wq, i915_had_wq); + hdmi_priv = kzalloc(sizeof(struct hdmi_audio_priv), GFP_KERNEL); + if (!hdmi_priv) { + pr_err("failed to allocate memory"); + } else { + hdmi_priv->dev = dev; + hdmi_priv->hdmib_reg = HDMIB; + hdmi_priv->monitor_type = MONITOR_TYPE_HDMI; + hdmi_priv->is_hdcp_supported = true; + i915_hdmi_audio_init(hdmi_priv); + } + } -- 1.9.1 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx