Commit 325b5b6c authored by Michael Zoran's avatar Michael Zoran Committed by Greg Kroah-Hartman

staging: bcm2835-audio: Add support for simultanous HDMI and Headphone audio

The firmware for the Raspberry PI already supports simultanous output
of audio through both the HDMI and the Headphone jack.  The current
implementation of ALSA doesn't expose this well to user mode since
the firmware audio is represented as a single card.

A newer approach is taken here and a virtual card is created for each
output(HDMI, Headphones, and Traditional ALSA).  The firmware has
the concept of channels or streams for which the number to use is
passed in the device tree. These streams are allocated to each of the
virtual cards.

As a side effect of this change, since each output is represented
independenly it's now very easy to use PulseAudio to control the
priorities of the outputs.

Testing:

Audacity and VLC were both loaded at the same time.  Each application
was assigned to a different card. With this change I was able to play
different music files at the same time through the HDMI and Headphones
jacks and control the audio independently.
Signed-off-by: default avatarMichael Zoran <mzoran@crowfest.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f7d51372
......@@ -340,3 +340,89 @@ int snd_bcm2835_new_ctl(struct bcm2835_chip *chip)
}
return 0;
}
static struct snd_kcontrol_new snd_bcm2835_headphones_ctl[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Headphone Playback Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.private_value = PCM_PLAYBACK_VOLUME,
.info = snd_bcm2835_ctl_info,
.get = snd_bcm2835_ctl_get,
.put = snd_bcm2835_ctl_put,
.count = 1,
.tlv = {.p = snd_bcm2835_db_scale}
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Headphone Playback Switch",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = PCM_PLAYBACK_MUTE,
.info = snd_bcm2835_ctl_info,
.get = snd_bcm2835_ctl_get,
.put = snd_bcm2835_ctl_put,
.count = 1,
}
};
int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip)
{
int err;
unsigned int idx;
strcpy(chip->card->mixername, "Broadcom Mixer");
for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_headphones_ctl); idx++) {
err = snd_ctl_add(chip->card,
snd_ctl_new1(&snd_bcm2835_headphones_ctl[idx],
chip));
if (err)
return err;
}
return 0;
}
static struct snd_kcontrol_new snd_bcm2835_hdmi[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Playback Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.private_value = PCM_PLAYBACK_VOLUME,
.info = snd_bcm2835_ctl_info,
.get = snd_bcm2835_ctl_get,
.put = snd_bcm2835_ctl_put,
.count = 1,
.tlv = {.p = snd_bcm2835_db_scale}
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "HDMI Playback Switch",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = PCM_PLAYBACK_MUTE,
.info = snd_bcm2835_ctl_info,
.get = snd_bcm2835_ctl_get,
.put = snd_bcm2835_ctl_put,
.count = 1,
}
};
int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip)
{
int err;
unsigned int idx;
strcpy(chip->card->mixername, "Broadcom Mixer");
for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_hdmi); idx++) {
err = snd_ctl_add(chip->card,
snd_ctl_new1(&snd_bcm2835_hdmi[idx], chip));
if (err)
return err;
}
return 0;
}
......@@ -479,7 +479,7 @@ static struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
};
/* create a pcm device */
int snd_bcm2835_new_pcm(struct bcm2835_chip *chip)
int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, u32 numchannels)
{
struct snd_pcm *pcm;
int err;
......@@ -490,7 +490,7 @@ int snd_bcm2835_new_pcm(struct bcm2835_chip *chip)
audio_error("Interrupted whilst waiting for lock\n");
return -EINTR;
}
err = snd_pcm_new(chip->card, "bcm2835 ALSA", 0, MAX_SUBSTREAMS, 0, &pcm);
err = snd_pcm_new(chip->card, "bcm2835 ALSA", 0, numchannels, 0, &pcm);
if (err < 0)
goto out;
pcm->private_data = chip;
......@@ -549,3 +549,39 @@ int snd_bcm2835_new_spdif_pcm(struct bcm2835_chip *chip)
return 0;
}
int snd_bcm2835_new_simple_pcm(struct bcm2835_chip *chip,
const char *name,
enum snd_bcm2835_route route,
u32 numchannels)
{
struct snd_pcm *pcm;
int err;
mutex_init(&chip->audio_mutex);
err = snd_pcm_new(chip->card, name, 0, numchannels,
0, &pcm);
if (err)
return err;
pcm->private_data = chip;
strcpy(pcm->name, name);
chip->pcm = pcm;
chip->dest = route;
chip->volume = alsa2chip(0);
chip->mute = CTRL_VOL_UNMUTE;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_bcm2835_playback_ops);
snd_pcm_lib_preallocate_pages_for_all(
pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
snd_bcm2835_playback_hw.buffer_bytes_max,
snd_bcm2835_playback_hw.buffer_bytes_max);
return 0;
}
......@@ -104,7 +104,7 @@ struct bcm2835_chip {
struct snd_pcm *pcm_spdif;
/* Bitmat for valid reg_base and irq numbers */
unsigned int avail_substreams;
struct platform_device *pdev[MAX_SUBSTREAMS];
struct device *dev;
struct bcm2835_alsa_stream *alsa_stream[MAX_SUBSTREAMS];
int volume;
......@@ -145,8 +145,15 @@ struct bcm2835_alsa_stream {
};
int snd_bcm2835_new_ctl(struct bcm2835_chip *chip);
int snd_bcm2835_new_pcm(struct bcm2835_chip *chip);
int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, u32 numchannels);
int snd_bcm2835_new_spdif_pcm(struct bcm2835_chip *chip);
int snd_bcm2835_new_simple_pcm(struct bcm2835_chip *chip,
const char *name,
enum snd_bcm2835_route route,
u32 numchannels);
int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip);
int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip);
int bcm2835_audio_open(struct bcm2835_alsa_stream *alsa_stream);
int bcm2835_audio_close(struct bcm2835_alsa_stream *alsa_stream);
......
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