[Intel-gfx] [RFC 05/15] drm/i915: changes for non-HDAudio HDMI interface

Pierre-Louis Bossart pierre-louis.bossart at linux.intel.com
Sat Mar 5 02:50:42 UTC 2016


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 at canonical.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>
---
 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



More information about the Intel-gfx mailing list