[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