Skip to content

Commit

Permalink
snd-bcm2835: Add support for spdif/hdmi passthrough
Browse files Browse the repository at this point in the history
This adds a dedicated subdevice which can be used for passthrough of non-audio
formats (ie encoded a52) through the hdmi audio link. In addition to this
driver extension an appropriate card config is required to make alsa-lib
support the AES parameters for this device.
  • Loading branch information
julianscheel committed Feb 26, 2014
1 parent 26d1de2 commit db269a8
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 19 deletions.
123 changes: 123 additions & 0 deletions sound/arm/bcm2835-ctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <sound/rawmidi.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <sound/asoundef.h>

#include "bcm2835.h"

Expand Down Expand Up @@ -183,6 +184,122 @@ static struct snd_kcontrol_new snd_bcm2835_ctl[] = {
},
};

static int snd_bcm2835_spdif_default_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}

static int snd_bcm2835_spdif_default_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
int i;

for (i = 0; i < 4; i++)
ucontrol->value.iec958.status[i] =
(chip->spdif_status >> (i * 8)) && 0xff;

return 0;
}

static int snd_bcm2835_spdif_default_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
unsigned int val = 0;
int i, change;

for (i = 0; i < 4; i++)
val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8);

change = val != chip->spdif_status;
chip->spdif_status = val;

return change;
}

static int snd_bcm2835_spdif_mask_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}

static int snd_bcm2835_spdif_mask_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* bcm2835 supports only consumer mode and sets all other format flags
* automatically. So the only thing left is signalling non-audio
* content */
ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO;
return 0;
}

static int snd_bcm2835_spdif_stream_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}

static int snd_bcm2835_spdif_stream_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
int i;

for (i = 0; i < 4; i++)
ucontrol->value.iec958.status[i] =
(chip->spdif_status >> (i * 8)) & 0xff;
return 0;
}

static int snd_bcm2835_spdif_stream_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
unsigned int val = 0;
int i, change;

for (i = 0; i < 4; i++)
val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8);
change = val != chip->spdif_status;
chip->spdif_status = val;

return change;
}

static struct snd_kcontrol_new snd_bcm2835_spdif[] __devinitdata = {
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
.info = snd_bcm2835_spdif_default_info,
.get = snd_bcm2835_spdif_default_get,
.put = snd_bcm2835_spdif_default_put
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
.info = snd_bcm2835_spdif_mask_info,
.get = snd_bcm2835_spdif_mask_get,
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
.info = snd_bcm2835_spdif_stream_info,
.get = snd_bcm2835_spdif_stream_get,
.put = snd_bcm2835_spdif_stream_put,
},
};

int snd_bcm2835_new_ctl(bcm2835_chip_t * chip)
{
int err;
Expand All @@ -196,5 +313,11 @@ int snd_bcm2835_new_ctl(bcm2835_chip_t * chip)
if (err < 0)
return err;
}
for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_spdif); idx++) {
err = snd_ctl_add(chip->card,
snd_ctl_new1(&snd_bcm2835_spdif[idx], chip));
if (err < 0)
return err;
}
return 0;
}
128 changes: 110 additions & 18 deletions sound/arm/bcm2835-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <linux/interrupt.h>
#include <linux/slab.h>

#include <sound/asoundef.h>

#include "bcm2835.h"

/* hardware definition */
Expand All @@ -34,6 +36,23 @@ static struct snd_pcm_hardware snd_bcm2835_playback_hw = {
.periods_max = 128,
};

static struct snd_pcm_hardware snd_bcm2835_playback_spdif_hw = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000,
.rate_min = 44100,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 1 * 1024,
.period_bytes_max = 128 * 1024,
.periods_min = 1,
.periods_max = 128,
};

static void snd_bcm2835_playback_free(struct snd_pcm_runtime *runtime)
{
audio_info("Freeing up alsa stream here ..\n");
Expand Down Expand Up @@ -89,7 +108,8 @@ static irqreturn_t bcm2835_playback_fifo_irq(int irq, void *dev_id)
}

/* open callback */
static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
static int snd_bcm2835_playback_open_generic(
struct snd_pcm_substream *substream, int spdif)
{
bcm2835_chip_t *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
Expand All @@ -102,6 +122,11 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
audio_info("Alsa open (%d)\n", substream->number);
idx = substream->number;

if (spdif && chip->opened != 0)
return -EBUSY;
else if (!spdif && (chip->opened & (1 << idx)))
return -EBUSY;

if (idx > MAX_SUBSTREAMS) {
audio_error
("substream(%d) device doesn't exist max(%d) substreams allowed\n",
Expand Down Expand Up @@ -138,7 +163,13 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)

runtime->private_data = alsa_stream;
runtime->private_free = snd_bcm2835_playback_free;
runtime->hw = snd_bcm2835_playback_hw;
if (spdif) {
runtime->hw = snd_bcm2835_playback_spdif_hw;
} else {
/* clear spdif status, as we are not in spdif mode */
chip->spdif_status = 0;
runtime->hw = snd_bcm2835_playback_hw;
}
/* minimum 16 bytes alignment (for vchiq bulk transfers) */
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
16);
Expand All @@ -150,6 +181,7 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
}
chip->alsa_stream[idx] = alsa_stream;

chip->opened |= (1 << idx);
alsa_stream->open = 1;
alsa_stream->draining = 1;

Expand All @@ -159,13 +191,24 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
return err;
}

static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
{
return snd_bcm2835_playback_open_generic(substream, 0);
}

static int snd_bcm2835_playback_spdif_open(struct snd_pcm_substream *substream)
{
return snd_bcm2835_playback_open_generic(substream, 1);
}

/* close callback */
static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
{
/* the hardware-specific codes will be here */

struct snd_pcm_runtime *runtime = substream->runtime;
bcm2835_alsa_stream_t *alsa_stream = runtime->private_data;
bcm2835_chip_t *chip = snd_pcm_substream_chip(substream);

audio_info(" .. IN\n");
audio_info("Alsa close\n");
Expand Down Expand Up @@ -196,6 +239,8 @@ static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
* runtime->private_free callback we registered in *_open above
*/

chip->opened &= ~(1 << substream->number);

audio_info(" .. OUT\n");

return 0;
Expand All @@ -205,10 +250,9 @@ static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
int err;
struct snd_pcm_runtime *runtime = substream->runtime;
bcm2835_alsa_stream_t *alsa_stream =
(bcm2835_alsa_stream_t *) runtime->private_data;
bcm2835_alsa_stream_t *alsa_stream = runtime->private_data;
int err;

audio_info(" .. IN\n");

Expand All @@ -219,19 +263,9 @@ static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream,
return err;
}

err = bcm2835_audio_set_params(alsa_stream, params_channels(params),
params_rate(params),
snd_pcm_format_width(params_format
(params)));
if (err < 0) {
audio_error(" error setting hw params\n");
}

bcm2835_audio_setup(alsa_stream);

/* in preparation of the stream, set the controls (volume level) of the stream */
bcm2835_audio_set_ctls(alsa_stream->chip);

alsa_stream->channels = params_channels(params);
alsa_stream->params_rate = params_rate(params);
alsa_stream->pcm_format_width = snd_pcm_format_width(params_format (params));
audio_info(" .. OUT\n");

return err;
Expand All @@ -247,11 +281,35 @@ static int snd_bcm2835_pcm_hw_free(struct snd_pcm_substream *substream)
/* prepare callback */
static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
{
bcm2835_chip_t *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
bcm2835_alsa_stream_t *alsa_stream = runtime->private_data;
int channels;
int err;

audio_info(" .. IN\n");

/* notify the vchiq that it should enter spdif passthrough mode by
* setting channels=0 (see
* https://github.com/raspberrypi/linux/issues/528) */
if (chip->spdif_status & IEC958_AES0_NONAUDIO)
channels = 0;
else
channels = alsa_stream->channels;

err = bcm2835_audio_set_params(alsa_stream, channels,
alsa_stream->params_rate,
alsa_stream->pcm_format_width);
if (err < 0) {
audio_error(" error setting hw params\n");
}

bcm2835_audio_setup(alsa_stream);

/* in preparation of the stream, set the controls (volume level) of the stream */
bcm2835_audio_set_ctls(alsa_stream->chip);


memset(&alsa_stream->pcm_indirect, 0, sizeof(alsa_stream->pcm_indirect));

alsa_stream->pcm_indirect.hw_buffer_size =
Expand Down Expand Up @@ -392,6 +450,18 @@ static struct snd_pcm_ops snd_bcm2835_playback_ops = {
.ack = snd_bcm2835_pcm_ack,
};

static struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
.open = snd_bcm2835_playback_spdif_open,
.close = snd_bcm2835_playback_close,
.ioctl = snd_bcm2835_pcm_lib_ioctl,
.hw_params = snd_bcm2835_pcm_hw_params,
.hw_free = snd_bcm2835_pcm_hw_free,
.prepare = snd_bcm2835_pcm_prepare,
.trigger = snd_bcm2835_pcm_trigger,
.pointer = snd_bcm2835_pcm_pointer,
.ack = snd_bcm2835_pcm_ack,
};

/* create a pcm device */
int snd_bcm2835_new_pcm(bcm2835_chip_t * chip)
{
Expand Down Expand Up @@ -424,3 +494,25 @@ int snd_bcm2835_new_pcm(bcm2835_chip_t * chip)

return 0;
}

int __devinit snd_bcm2835_new_spdif_pcm(bcm2835_chip_t * chip)
{
struct snd_pcm *pcm;
int err;

err = snd_pcm_new(chip->card, "bcm2835 ALSA", 1, 1, 0, &pcm);
if (err < 0)
return err;

pcm->private_data = chip;
strcpy(pcm->name, "bcm2835 IEC958/HDMI");
chip->pcm_spdif = pcm;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_bcm2835_playback_spdif_ops);

snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data (GFP_KERNEL),
64 * 1024, 64 * 1024);

return 0;
}
Loading

0 comments on commit db269a8

Please sign in to comment.