Commit 8a2d6ae1 authored by Ville Syrjälä's avatar Ville Syrjälä

ALSA: x86: Register multiple PCM devices for the LPE audio card

Now that everything is in place let's register a PCM device for
each port of the display engine. This will make it possible to
actually output audio to multiple displays at the same time. And
it avoids modesets on unrelated displays from clobbering up the
ELD and whatnot for the display currently doing the playback.

v2: Add a PCM per port instead of per pipe
v3: Fix off by one error with port numbers (Pierre-Louis)
    Fix .notify_audio_lpe() prototype (Pierre-Louis)

Cc: Takashi Iwai <tiwai@suse.de>
Cc: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: default avatarVille Syrjälä <ville.syrjala@linux.intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170427160231.13337-12-ville.syrjala@linux.intel.comReviewed-by: default avatarTakashi Iwai <tiwai@suse.de>
parent b4eb0d52
...@@ -111,7 +111,11 @@ lpe_audio_platdev_create(struct drm_i915_private *dev_priv) ...@@ -111,7 +111,11 @@ lpe_audio_platdev_create(struct drm_i915_private *dev_priv)
pinfo.size_data = sizeof(*pdata); pinfo.size_data = sizeof(*pdata);
pinfo.dma_mask = DMA_BIT_MASK(32); pinfo.dma_mask = DMA_BIT_MASK(32);
pdata->port.pipe = -1; pdata->num_pipes = INTEL_INFO(dev_priv)->num_pipes;
pdata->num_ports = IS_CHERRYVIEW(dev_priv) ? 3 : 2; /* B,C,D or B,C */
pdata->port[0].pipe = -1;
pdata->port[1].pipe = -1;
pdata->port[2].pipe = -1;
spin_lock_init(&pdata->lpe_audio_slock); spin_lock_init(&pdata->lpe_audio_slock);
platdev = platform_device_register_full(&pinfo); platdev = platform_device_register_full(&pinfo);
...@@ -319,7 +323,7 @@ void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, ...@@ -319,7 +323,7 @@ void intel_lpe_audio_notify(struct drm_i915_private *dev_priv,
enum pipe pipe, enum port port, enum pipe pipe, enum port port,
const void *eld, int ls_clock, bool dp_output) const void *eld, int ls_clock, bool dp_output)
{ {
unsigned long irq_flags; unsigned long irqflags;
struct intel_hdmi_lpe_audio_pdata *pdata; struct intel_hdmi_lpe_audio_pdata *pdata;
struct intel_hdmi_lpe_audio_port_pdata *ppdata; struct intel_hdmi_lpe_audio_port_pdata *ppdata;
u32 audio_enable; u32 audio_enable;
...@@ -328,14 +332,12 @@ void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, ...@@ -328,14 +332,12 @@ void intel_lpe_audio_notify(struct drm_i915_private *dev_priv,
return; return;
pdata = dev_get_platdata(&dev_priv->lpe_audio.platdev->dev); pdata = dev_get_platdata(&dev_priv->lpe_audio.platdev->dev);
ppdata = &pdata->port; ppdata = &pdata->port[port - PORT_B];
spin_lock_irqsave(&pdata->lpe_audio_slock, irq_flags); spin_lock_irqsave(&pdata->lpe_audio_slock, irqflags);
audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port)); audio_enable = I915_READ(VLV_AUD_PORT_EN_DBG(port));
ppdata->port = port;
if (eld != NULL) { if (eld != NULL) {
memcpy(ppdata->eld, eld, HDMI_MAX_ELD_BYTES); memcpy(ppdata->eld, eld, HDMI_MAX_ELD_BYTES);
ppdata->pipe = pipe; ppdata->pipe = pipe;
...@@ -357,8 +359,7 @@ void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, ...@@ -357,8 +359,7 @@ void intel_lpe_audio_notify(struct drm_i915_private *dev_priv,
} }
if (pdata->notify_audio_lpe) if (pdata->notify_audio_lpe)
pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev); pdata->notify_audio_lpe(dev_priv->lpe_audio.platdev, port - PORT_B);
spin_unlock_irqrestore(&pdata->lpe_audio_slock, spin_unlock_irqrestore(&pdata->lpe_audio_slock, irqflags);
irq_flags);
} }
...@@ -40,9 +40,11 @@ struct intel_hdmi_lpe_audio_port_pdata { ...@@ -40,9 +40,11 @@ struct intel_hdmi_lpe_audio_port_pdata {
}; };
struct intel_hdmi_lpe_audio_pdata { struct intel_hdmi_lpe_audio_pdata {
struct intel_hdmi_lpe_audio_port_pdata port; struct intel_hdmi_lpe_audio_port_pdata port[3]; /* for ports B,C,D */
int num_ports;
int num_pipes;
void (*notify_audio_lpe)(struct platform_device *pdev); void (*notify_audio_lpe)(struct platform_device *pdev, int port); /* port: 0==B,1==C,2==D */
spinlock_t lpe_audio_slock; spinlock_t lpe_audio_slock;
}; };
......
...@@ -42,6 +42,8 @@ ...@@ -42,6 +42,8 @@
#include <drm/intel_lpe_audio.h> #include <drm/intel_lpe_audio.h>
#include "intel_hdmi_audio.h" #include "intel_hdmi_audio.h"
#define for_each_pipe(card_ctx, pipe) \
for ((pipe) = 0; (pipe) < (card_ctx)->num_pipes; (pipe)++)
#define for_each_port(card_ctx, port) \ #define for_each_port(card_ctx, port) \
for ((port) = 0; (port) < (card_ctx)->num_ports; (port)++) for ((port) = 0; (port) < (card_ctx)->num_ports; (port)++)
...@@ -192,15 +194,30 @@ static void had_substream_put(struct snd_intelhad *intelhaddata) ...@@ -192,15 +194,30 @@ static void had_substream_put(struct snd_intelhad *intelhaddata)
spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags);
} }
static u32 had_config_offset(int pipe)
{
switch (pipe) {
default:
case 0:
return AUDIO_HDMI_CONFIG_A;
case 1:
return AUDIO_HDMI_CONFIG_B;
case 2:
return AUDIO_HDMI_CONFIG_C;
}
}
/* Register access functions */ /* Register access functions */
static u32 had_read_register_raw(struct snd_intelhad *ctx, u32 reg) static u32 had_read_register_raw(struct snd_intelhad_card *card_ctx,
int pipe, u32 reg)
{ {
return ioread32(ctx->card_ctx->mmio_start + ctx->had_config_offset + reg); return ioread32(card_ctx->mmio_start + had_config_offset(pipe) + reg);
} }
static void had_write_register_raw(struct snd_intelhad *ctx, u32 reg, u32 val) static void had_write_register_raw(struct snd_intelhad_card *card_ctx,
int pipe, u32 reg, u32 val)
{ {
iowrite32(val, ctx->card_ctx->mmio_start + ctx->had_config_offset + reg); iowrite32(val, card_ctx->mmio_start + had_config_offset(pipe) + reg);
} }
static void had_read_register(struct snd_intelhad *ctx, u32 reg, u32 *val) static void had_read_register(struct snd_intelhad *ctx, u32 reg, u32 *val)
...@@ -208,13 +225,13 @@ static void had_read_register(struct snd_intelhad *ctx, u32 reg, u32 *val) ...@@ -208,13 +225,13 @@ static void had_read_register(struct snd_intelhad *ctx, u32 reg, u32 *val)
if (!ctx->connected) if (!ctx->connected)
*val = 0; *val = 0;
else else
*val = had_read_register_raw(ctx, reg); *val = had_read_register_raw(ctx->card_ctx, ctx->pipe, reg);
} }
static void had_write_register(struct snd_intelhad *ctx, u32 reg, u32 val) static void had_write_register(struct snd_intelhad *ctx, u32 reg, u32 val)
{ {
if (ctx->connected) if (ctx->connected)
had_write_register_raw(ctx, reg, val); had_write_register_raw(ctx->card_ctx, ctx->pipe, reg, val);
} }
/* /*
...@@ -1361,6 +1378,9 @@ static void had_process_hot_plug(struct snd_intelhad *intelhaddata) ...@@ -1361,6 +1378,9 @@ static void had_process_hot_plug(struct snd_intelhad *intelhaddata)
return; return;
} }
/* Disable Audio */
had_enable_audio(intelhaddata, false);
intelhaddata->connected = true; intelhaddata->connected = true;
dev_dbg(intelhaddata->dev, dev_dbg(intelhaddata->dev,
"%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_CONNECTED\n", "%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_CONNECTED\n",
...@@ -1523,26 +1543,31 @@ static const struct snd_kcontrol_new had_controls[] = { ...@@ -1523,26 +1543,31 @@ static const struct snd_kcontrol_new had_controls[] = {
static irqreturn_t display_pipe_interrupt_handler(int irq, void *dev_id) static irqreturn_t display_pipe_interrupt_handler(int irq, void *dev_id)
{ {
struct snd_intelhad_card *card_ctx = dev_id; struct snd_intelhad_card *card_ctx = dev_id;
int port; u32 audio_stat[3] = {};
int pipe, port;
for_each_port(card_ctx, port) {
struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port];
u32 audio_stat;
for_each_pipe(card_ctx, pipe) {
/* use raw register access to ack IRQs even while disconnected */ /* use raw register access to ack IRQs even while disconnected */
audio_stat = had_read_register_raw(ctx, AUD_HDMI_STATUS); audio_stat[pipe] = had_read_register_raw(card_ctx, pipe,
AUD_HDMI_STATUS) &
(HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE);
if (audio_stat & HDMI_AUDIO_UNDERRUN) { if (audio_stat[pipe])
had_write_register_raw(ctx, AUD_HDMI_STATUS, had_write_register_raw(card_ctx, pipe,
HDMI_AUDIO_UNDERRUN); AUD_HDMI_STATUS, audio_stat[pipe]);
had_process_buffer_underrun(ctx);
} }
if (audio_stat & HDMI_AUDIO_BUFFER_DONE) { for_each_port(card_ctx, port) {
had_write_register_raw(ctx, AUD_HDMI_STATUS, struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port];
HDMI_AUDIO_BUFFER_DONE); int pipe = ctx->pipe;
if (pipe < 0)
continue;
if (audio_stat[pipe] & HDMI_AUDIO_BUFFER_DONE)
had_process_buffer_done(ctx); had_process_buffer_done(ctx);
} if (audio_stat[pipe] & HDMI_AUDIO_UNDERRUN)
had_process_buffer_underrun(ctx);
} }
return IRQ_HANDLED; return IRQ_HANDLED;
...@@ -1551,16 +1576,12 @@ static irqreturn_t display_pipe_interrupt_handler(int irq, void *dev_id) ...@@ -1551,16 +1576,12 @@ static irqreturn_t display_pipe_interrupt_handler(int irq, void *dev_id)
/* /*
* monitor plug/unplug notification from i915; just kick off the work * monitor plug/unplug notification from i915; just kick off the work
*/ */
static void notify_audio_lpe(struct platform_device *pdev) static void notify_audio_lpe(struct platform_device *pdev, int port)
{ {
struct snd_intelhad_card *card_ctx = platform_get_drvdata(pdev); struct snd_intelhad_card *card_ctx = platform_get_drvdata(pdev);
int port;
for_each_port(card_ctx, port) {
struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port]; struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port];
schedule_work(&ctx->hdmi_audio_wq); schedule_work(&ctx->hdmi_audio_wq);
}
} }
/* the work to handle monitor hot plug/unplug */ /* the work to handle monitor hot plug/unplug */
...@@ -1569,34 +1590,27 @@ static void had_audio_wq(struct work_struct *work) ...@@ -1569,34 +1590,27 @@ static void had_audio_wq(struct work_struct *work)
struct snd_intelhad *ctx = struct snd_intelhad *ctx =
container_of(work, struct snd_intelhad, hdmi_audio_wq); container_of(work, struct snd_intelhad, hdmi_audio_wq);
struct intel_hdmi_lpe_audio_pdata *pdata = ctx->dev->platform_data; struct intel_hdmi_lpe_audio_pdata *pdata = ctx->dev->platform_data;
struct intel_hdmi_lpe_audio_port_pdata *ppdata = &pdata->port; struct intel_hdmi_lpe_audio_port_pdata *ppdata = &pdata->port[ctx->port];
pm_runtime_get_sync(ctx->dev); pm_runtime_get_sync(ctx->dev);
mutex_lock(&ctx->mutex); mutex_lock(&ctx->mutex);
if (ppdata->pipe < 0) { if (ppdata->pipe < 0) {
dev_dbg(ctx->dev, "%s: Event: HAD_NOTIFY_HOT_UNPLUG\n", dev_dbg(ctx->dev, "%s: Event: HAD_NOTIFY_HOT_UNPLUG : port = %d\n",
__func__); __func__, ctx->port);
memset(ctx->eld, 0, sizeof(ctx->eld)); /* clear the old ELD */ memset(ctx->eld, 0, sizeof(ctx->eld)); /* clear the old ELD */
ctx->dp_output = false;
ctx->tmds_clock_speed = 0;
ctx->link_rate = 0;
/* Shut down the stream */
had_process_hot_unplug(ctx); had_process_hot_unplug(ctx);
ctx->pipe = -1;
} else { } else {
dev_dbg(ctx->dev, "%s: HAD_NOTIFY_ELD : port = %d, tmds = %d\n", dev_dbg(ctx->dev, "%s: HAD_NOTIFY_ELD : port = %d, tmds = %d\n",
__func__, ppdata->port, ppdata->ls_clock); __func__, ctx->port, ppdata->ls_clock);
switch (ppdata->pipe) {
case 0:
ctx->had_config_offset = AUDIO_HDMI_CONFIG_A;
break;
case 1:
ctx->had_config_offset = AUDIO_HDMI_CONFIG_B;
break;
case 2:
ctx->had_config_offset = AUDIO_HDMI_CONFIG_C;
break;
default:
dev_dbg(ctx->dev, "Invalid pipe %d\n",
ppdata->pipe);
break;
}
memcpy(ctx->eld, ppdata->eld, sizeof(ctx->eld)); memcpy(ctx->eld, ppdata->eld, sizeof(ctx->eld));
...@@ -1609,11 +1623,18 @@ static void had_audio_wq(struct work_struct *work) ...@@ -1609,11 +1623,18 @@ static void had_audio_wq(struct work_struct *work)
ctx->link_rate = 0; ctx->link_rate = 0;
} }
/*
* Shut down the stream before we change
* the pipe assignment for this pcm device
*/
had_process_hot_plug(ctx); had_process_hot_plug(ctx);
/* Process mode change if stream is active */ ctx->pipe = ppdata->pipe;
/* Restart the stream if necessary */
had_process_mode_change(ctx); had_process_mode_change(ctx);
} }
mutex_unlock(&ctx->mutex); mutex_unlock(&ctx->mutex);
pm_runtime_mark_last_busy(ctx->dev); pm_runtime_mark_last_busy(ctx->dev);
pm_runtime_put_autosuspend(ctx->dev); pm_runtime_put_autosuspend(ctx->dev);
...@@ -1794,7 +1815,8 @@ static int hdmi_lpe_audio_probe(struct platform_device *pdev) ...@@ -1794,7 +1815,8 @@ static int hdmi_lpe_audio_probe(struct platform_device *pdev)
init_channel_allocations(); init_channel_allocations();
card_ctx->num_ports = 1; card_ctx->num_pipes = pdata->num_pipes;
card_ctx->num_ports = pdata->num_ports;
for_each_port(card_ctx, port) { for_each_port(card_ctx, port) {
struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port]; struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port];
...@@ -1802,12 +1824,12 @@ static int hdmi_lpe_audio_probe(struct platform_device *pdev) ...@@ -1802,12 +1824,12 @@ static int hdmi_lpe_audio_probe(struct platform_device *pdev)
ctx->card_ctx = card_ctx; ctx->card_ctx = card_ctx;
ctx->dev = card_ctx->dev; ctx->dev = card_ctx->dev;
ctx->port = port;
ctx->pipe = -1;
INIT_WORK(&ctx->hdmi_audio_wq, had_audio_wq); INIT_WORK(&ctx->hdmi_audio_wq, had_audio_wq);
ctx->had_config_offset = AUDIO_HDMI_CONFIG_A; ret = snd_pcm_new(card, INTEL_HAD, port, MAX_PB_STREAMS,
ret = snd_pcm_new(card, INTEL_HAD, PCM_INDEX, MAX_PB_STREAMS,
MAX_CAP_STREAMS, &pcm); MAX_CAP_STREAMS, &pcm);
if (ret) if (ret)
goto err; goto err;
......
...@@ -32,7 +32,6 @@ ...@@ -32,7 +32,6 @@
#include "intel_hdmi_lpe_audio.h" #include "intel_hdmi_lpe_audio.h"
#define PCM_INDEX 0
#define MAX_PB_STREAMS 1 #define MAX_PB_STREAMS 1
#define MAX_CAP_STREAMS 0 #define MAX_CAP_STREAMS 0
#define BYTES_PER_WORD 0x4 #define BYTES_PER_WORD 0x4
...@@ -112,6 +111,8 @@ struct snd_intelhad { ...@@ -112,6 +111,8 @@ struct snd_intelhad {
struct snd_pcm_chmap *chmap; struct snd_pcm_chmap *chmap;
int tmds_clock_speed; int tmds_clock_speed;
int link_rate; int link_rate;
int port; /* fixed */
int pipe; /* can change dynamically */
/* ring buffer (BD) position index */ /* ring buffer (BD) position index */
unsigned int bd_head; unsigned int bd_head;
...@@ -123,7 +124,6 @@ struct snd_intelhad { ...@@ -123,7 +124,6 @@ struct snd_intelhad {
unsigned int period_bytes; /* PCM period size in bytes */ unsigned int period_bytes; /* PCM period size in bytes */
/* internal stuff */ /* internal stuff */
unsigned int had_config_offset;
union aud_cfg aud_config; /* AUD_CONFIG reg value cache */ union aud_cfg aud_config; /* AUD_CONFIG reg value cache */
struct work_struct hdmi_audio_wq; struct work_struct hdmi_audio_wq;
struct mutex mutex; /* for protecting chmap and eld */ struct mutex mutex; /* for protecting chmap and eld */
...@@ -138,8 +138,9 @@ struct snd_intelhad_card { ...@@ -138,8 +138,9 @@ struct snd_intelhad_card {
/* internal stuff */ /* internal stuff */
int irq; int irq;
void __iomem *mmio_start; void __iomem *mmio_start;
int num_pipes;
int num_ports; int num_ports;
struct snd_intelhad pcm_ctx[3]; struct snd_intelhad pcm_ctx[3]; /* one for each port */
}; };
#endif /* _INTEL_HDMI_AUDIO_ */ #endif /* _INTEL_HDMI_AUDIO_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment