From 2172c6578ef13acb8fcf5cac643cb1ee8e824117 Mon Sep 17 00:00:00 2001
From: Julian Scheel <julian@jusst.de>
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(-)

diff --git a/sound/arm/bcm2835-ctl.c b/sound/arm/bcm2835-ctl.c
index 8c5334a..aad905f 100755
--- a/sound/arm/bcm2835-ctl.c
+++ b/sound/arm/bcm2835-ctl.c
@@ -30,6 +30,7 @@
 #include <sound/rawmidi.h>
 #include <sound/initval.h>
 #include <sound/tlv.h>
+#include <sound/asoundef.h>
 
 #include "bcm2835.h"
 
@@ -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[] = {
+	{
+		.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 * 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;
 }
diff --git a/sound/arm/bcm2835-pcm.c b/sound/arm/bcm2835-pcm.c
index b4084bb..ebd3f62 100755
--- a/sound/arm/bcm2835-pcm.c
+++ b/sound/arm/bcm2835-pcm.c
@@ -15,6 +15,8 @@
 #include <linux/interrupt.h>
 #include <linux/slab.h>
 
+#include <sound/asoundef.h>
+
 #include "bcm2835.h"
 
 /* hardware definition */
@@ -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");
@@ -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;
@@ -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",
@@ -143,13 +168,20 @@ 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);
 
 	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(struct snd_pcm_substream *substream)
 
 	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(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;
@@ -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");
 
@@ -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;
@@ -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 =
@@ -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)
 {
@@ -424,3 +494,25 @@ int snd_bcm2835_new_pcm(bcm2835_chip_t * chip)
 
 	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;
+}
diff --git a/sound/arm/bcm2835.c b/sound/arm/bcm2835.c
index e2047a7..4136760 100755
--- a/sound/arm/bcm2835.c
+++ b/sound/arm/bcm2835.c
@@ -104,7 +104,7 @@ static int snd_bcm2835_alsa_probe(struct platform_device *pdev)
 		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 platform_device *pdev)
 		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);
diff --git a/sound/arm/bcm2835.h b/sound/arm/bcm2835.h
index 36afee3..8c2fe26 100755
--- 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);
-- 
1.9.1

