From 2172c6578ef13acb8fcf5cac643cb1ee8e824117 Mon Sep 17 00:00:00 2001 From: Julian Scheel Date: Wed, 19 Feb 2014 16:06:59 +0100 Subject: [PATCH 32/54] snd-bcm2835: Add support for spdif/hdmi passthrough 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. --- sound/arm/bcm2835-ctl.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++ sound/arm/bcm2835-pcm.c | 128 +++++++++++++++++++++++++++++++++++++++++------- sound/arm/bcm2835.c | 9 +++- sound/arm/bcm2835.h | 9 ++++ 4 files changed, 250 insertions(+), 19 deletions(-) --- a/sound/arm/bcm2835-ctl.c +++ b/sound/arm/bcm2835-ctl.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "bcm2835.h" @@ -183,6 +184,122 @@ static struct snd_kcontrol_new snd_bcm28 }, }; +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[] = { + { + .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; @@ -196,5 +313,11 @@ int snd_bcm2835_new_ctl(bcm2835_chip_t * 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; } --- a/sound/arm/bcm2835-pcm.c +++ b/sound/arm/bcm2835-pcm.c @@ -15,6 +15,8 @@ #include #include +#include + #include "bcm2835.h" /* hardware definition */ @@ -34,6 +36,23 @@ static struct snd_pcm_hardware snd_bcm28 .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"); @@ -89,7 +108,8 @@ static irqreturn_t bcm2835_playback_fifo } /* 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; @@ -102,6 +122,11 @@ static int snd_bcm2835_playback_open(str 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", @@ -143,13 +168,20 @@ static int snd_bcm2835_playback_open(str } 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); chip->alsa_stream[idx] = alsa_stream; + chip->opened |= (1 << idx); alsa_stream->open = 1; alsa_stream->draining = 1; @@ -159,6 +191,16 @@ out: 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) { @@ -166,6 +208,7 @@ static int snd_bcm2835_playback_close(st 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"); @@ -196,6 +239,8 @@ static int snd_bcm2835_playback_close(st * runtime->private_free callback we registered in *_open above */ + chip->opened &= ~(1 << substream->number); + audio_info(" .. OUT\n"); return 0; @@ -205,10 +250,9 @@ static int snd_bcm2835_playback_close(st 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"); @@ -219,19 +263,9 @@ static int snd_bcm2835_pcm_hw_params(str 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; @@ -247,11 +281,35 @@ static int snd_bcm2835_pcm_hw_free(struc /* 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 = @@ -392,6 +450,18 @@ static struct snd_pcm_ops snd_bcm2835_pl .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) { @@ -424,3 +494,25 @@ int snd_bcm2835_new_pcm(bcm2835_chip_t * return 0; } + +int 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; +} --- a/sound/arm/bcm2835.c +++ b/sound/arm/bcm2835.c @@ -104,7 +104,7 @@ static int snd_bcm2835_alsa_probe(struct goto out; snd_card_set_dev(g_card, &pdev->dev); - strcpy(g_card->driver, "BRCM bcm2835 ALSA Driver"); + strcpy(g_card->driver, "bcm2835"); strcpy(g_card->shortname, "bcm2835 ALSA"); sprintf(g_card->longname, "%s", g_card->shortname); @@ -121,6 +121,12 @@ static int snd_bcm2835_alsa_probe(struct goto out_bcm2835_new_pcm; } + err = snd_bcm2835_new_spdif_pcm(chip); + if (err < 0) { + dev_err(&pdev->dev, "Failed to create new BCM2835 spdif pcm device\n"); + goto out_bcm2835_new_spdif; + } + err = snd_bcm2835_new_ctl(chip); if (err < 0) { dev_err(&pdev->dev, "Failed to create new BCM2835 ctl\n"); @@ -156,6 +162,7 @@ add_register_map: out_card_register: out_bcm2835_new_ctl: +out_bcm2835_new_spdif: out_bcm2835_new_pcm: out_bcm2835_create: BUG_ON(!g_card); --- a/sound/arm/bcm2835.h +++ b/sound/arm/bcm2835.h @@ -97,6 +97,7 @@ typedef enum { typedef struct bcm2835_chip { struct snd_card *card; struct snd_pcm *pcm; + struct snd_pcm *pcm_spdif; /* Bitmat for valid reg_base and irq numbers */ uint32_t avail_substreams; struct platform_device *pdev[MAX_SUBSTREAMS]; @@ -106,6 +107,9 @@ typedef struct bcm2835_chip { int old_volume; /* stores the volume value whist muted */ int dest; int mute; + + unsigned int opened; + unsigned int spdif_status; } bcm2835_chip_t; typedef struct bcm2835_alsa_stream { @@ -123,6 +127,10 @@ typedef struct bcm2835_alsa_stream { int running; int draining; + int channels; + int params_rate; + int pcm_format_width; + unsigned int pos; unsigned int buffer_size; unsigned int period_size; @@ -138,6 +146,7 @@ typedef struct bcm2835_alsa_stream { int snd_bcm2835_new_ctl(bcm2835_chip_t * chip); int snd_bcm2835_new_pcm(bcm2835_chip_t * chip); +int snd_bcm2835_new_spdif_pcm(bcm2835_chip_t * chip); int bcm2835_audio_open(bcm2835_alsa_stream_t * alsa_stream); int bcm2835_audio_close(bcm2835_alsa_stream_t * alsa_stream);