summaryrefslogtreecommitdiffstats
path: root/drivers/staging/vc04_services/bcm2835-audio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/vc04_services/bcm2835-audio')
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/Kconfig11
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/Makefile5
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c232
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c355
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c378
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/bcm2835.c323
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/bcm2835.h113
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/vc_vchi_audioserv_defs.h98
8 files changed, 1515 insertions, 0 deletions
diff --git a/drivers/staging/vc04_services/bcm2835-audio/Kconfig b/drivers/staging/vc04_services/bcm2835-audio/Kconfig
new file mode 100644
index 000000000..7f22f6c85
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+config SND_BCM2835
+ tristate "BCM2835 Audio"
+ depends on (ARCH_BCM2835 || COMPILE_TEST) && SND
+ select SND_PCM
+ select BCM2835_VCHIQ if HAS_DMA
+ help
+ Say Y or M if you want to support BCM2835 built in audio.
+ This driver handles both 3.5mm and HDMI audio, by leveraging
+ the VCHIQ messaging interface between the kernel and the firmware
+ running on VideoCore. \ No newline at end of file
diff --git a/drivers/staging/vc04_services/bcm2835-audio/Makefile b/drivers/staging/vc04_services/bcm2835-audio/Makefile
new file mode 100644
index 000000000..d59fe4dde
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_SND_BCM2835) += snd-bcm2835.o
+snd-bcm2835-objs := bcm2835.o bcm2835-ctl.o bcm2835-pcm.o bcm2835-vchiq.o
+
+ccflags-y += -I $(srctree)/$(src)/../include -D__VCCOREVER__=0x04000000
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c
new file mode 100644
index 000000000..1c1f04012
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2011 Broadcom Corporation. All rights reserved. */
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/asoundef.h>
+
+#include "bcm2835.h"
+
+/* volume maximum and minimum in terms of 0.01dB */
+#define CTRL_VOL_MAX 400
+#define CTRL_VOL_MIN -10239 /* originally -10240 */
+
+static int bcm2835_audio_set_chip_ctls(struct bcm2835_chip *chip)
+{
+ int i, err = 0;
+
+ /* change ctls for all substreams */
+ for (i = 0; i < MAX_SUBSTREAMS; i++) {
+ if (chip->alsa_stream[i]) {
+ err = bcm2835_audio_set_ctls(chip->alsa_stream[i]);
+ if (err < 0)
+ break;
+ }
+ }
+ return err;
+}
+
+static int snd_bcm2835_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = CTRL_VOL_MIN;
+ uinfo->value.integer.max = CTRL_VOL_MAX; /* 2303 */
+ } else if (kcontrol->private_value == PCM_PLAYBACK_MUTE) {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ } else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE) {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = AUDIO_DEST_MAX - 1;
+ }
+ return 0;
+}
+
+static int snd_bcm2835_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&chip->audio_mutex);
+
+ if (kcontrol->private_value == PCM_PLAYBACK_VOLUME)
+ ucontrol->value.integer.value[0] = chip->volume;
+ else if (kcontrol->private_value == PCM_PLAYBACK_MUTE)
+ ucontrol->value.integer.value[0] = chip->mute;
+ else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE)
+ ucontrol->value.integer.value[0] = chip->dest;
+
+ mutex_unlock(&chip->audio_mutex);
+ return 0;
+}
+
+static int snd_bcm2835_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
+ int val, *valp;
+ int changed = 0;
+
+ if (kcontrol->private_value == PCM_PLAYBACK_VOLUME)
+ valp = &chip->volume;
+ else if (kcontrol->private_value == PCM_PLAYBACK_MUTE)
+ valp = &chip->mute;
+ else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE)
+ valp = &chip->dest;
+ else
+ return -EINVAL;
+
+ val = ucontrol->value.integer.value[0];
+ mutex_lock(&chip->audio_mutex);
+ if (val != *valp) {
+ *valp = val;
+ changed = 1;
+ if (bcm2835_audio_set_chip_ctls(chip))
+ dev_err(chip->card->dev, "Failed to set ALSA controls..\n");
+ }
+ mutex_unlock(&chip->audio_mutex);
+ return changed;
+}
+
+static DECLARE_TLV_DB_SCALE(snd_bcm2835_db_scale, CTRL_VOL_MIN, 1, 1);
+
+static const struct snd_kcontrol_new snd_bcm2835_ctl[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .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,
+ .tlv = {.p = snd_bcm2835_db_scale}
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .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,
+ },
+};
+
+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;
+
+ mutex_lock(&chip->audio_mutex);
+
+ for (i = 0; i < 4; i++)
+ ucontrol->value.iec958.status[i] =
+ (chip->spdif_status >> (i * 8)) & 0xff;
+
+ mutex_unlock(&chip->audio_mutex);
+ 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;
+
+ mutex_lock(&chip->audio_mutex);
+
+ 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;
+
+ mutex_unlock(&chip->audio_mutex);
+ 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 const 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,
+ },
+};
+
+static int create_ctls(struct bcm2835_chip *chip, size_t size,
+ const struct snd_kcontrol_new *kctls)
+{
+ int i, err;
+
+ for (i = 0; i < size; i++) {
+ err = snd_ctl_add(chip->card, snd_ctl_new1(&kctls[i], chip));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip)
+{
+ strscpy(chip->card->mixername, "Broadcom Mixer", sizeof(chip->card->mixername));
+ return create_ctls(chip, ARRAY_SIZE(snd_bcm2835_ctl),
+ snd_bcm2835_ctl);
+}
+
+int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip)
+{
+ int err;
+
+ strscpy(chip->card->mixername, "Broadcom Mixer", sizeof(chip->card->mixername));
+ err = create_ctls(chip, ARRAY_SIZE(snd_bcm2835_ctl), snd_bcm2835_ctl);
+ if (err < 0)
+ return err;
+ return create_ctls(chip, ARRAY_SIZE(snd_bcm2835_spdif),
+ snd_bcm2835_spdif);
+}
+
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
new file mode 100644
index 000000000..68e8d491a
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2011 Broadcom Corporation. All rights reserved. */
+
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include <sound/asoundef.h>
+
+#include "bcm2835.h"
+
+/* hardware definition */
+static const struct snd_pcm_hardware snd_bcm2835_playback_hw = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = 512 * 1024,
+ .period_bytes_min = 1 * 1024,
+ .period_bytes_max = 512 * 1024,
+ .periods_min = 1,
+ .periods_max = 128,
+};
+
+static const 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 |
+ SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH),
+ .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)
+{
+ kfree(runtime->private_data);
+}
+
+void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int bytes)
+{
+ struct snd_pcm_substream *substream = alsa_stream->substream;
+ unsigned int pos;
+
+ if (!alsa_stream->period_size)
+ return;
+
+ if (bytes >= alsa_stream->buffer_size) {
+ snd_pcm_stream_lock(substream);
+ snd_pcm_stop(substream,
+ alsa_stream->draining ?
+ SNDRV_PCM_STATE_SETUP :
+ SNDRV_PCM_STATE_XRUN);
+ snd_pcm_stream_unlock(substream);
+ return;
+ }
+
+ pos = atomic_read(&alsa_stream->pos);
+ pos += bytes;
+ pos %= alsa_stream->buffer_size;
+ atomic_set(&alsa_stream->pos, pos);
+
+ alsa_stream->period_offset += bytes;
+ alsa_stream->interpolate_start = ktime_get();
+ if (alsa_stream->period_offset >= alsa_stream->period_size) {
+ alsa_stream->period_offset %= alsa_stream->period_size;
+ snd_pcm_period_elapsed(substream);
+ }
+}
+
+/* open callback */
+static int snd_bcm2835_playback_open_generic(struct snd_pcm_substream *substream, int spdif)
+{
+ struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm2835_alsa_stream *alsa_stream;
+ int idx;
+ int err;
+
+ mutex_lock(&chip->audio_mutex);
+ idx = substream->number;
+
+ if (spdif && chip->opened) {
+ err = -EBUSY;
+ goto out;
+ } else if (!spdif && (chip->opened & (1 << idx))) {
+ err = -EBUSY;
+ goto out;
+ }
+ if (idx >= MAX_SUBSTREAMS) {
+ dev_err(chip->dev,
+ "substream(%d) device doesn't exist max(%d) substreams allowed\n",
+ idx, MAX_SUBSTREAMS);
+ err = -ENODEV;
+ goto out;
+ }
+
+ alsa_stream = kzalloc(sizeof(*alsa_stream), GFP_KERNEL);
+ if (!alsa_stream) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* Initialise alsa_stream */
+ alsa_stream->chip = chip;
+ alsa_stream->substream = substream;
+ alsa_stream->idx = idx;
+
+ err = bcm2835_audio_open(alsa_stream);
+ if (err) {
+ kfree(alsa_stream);
+ goto out;
+ }
+ runtime->private_data = alsa_stream;
+ runtime->private_free = snd_bcm2835_playback_free;
+ 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);
+
+ /* position update is in 10ms order */
+ snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ 10 * 1000, UINT_MAX);
+
+ chip->alsa_stream[idx] = alsa_stream;
+
+ chip->opened |= (1 << idx);
+
+out:
+ mutex_unlock(&chip->audio_mutex);
+
+ 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);
+}
+
+static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
+{
+ struct bcm2835_alsa_stream *alsa_stream;
+ struct snd_pcm_runtime *runtime;
+ struct bcm2835_chip *chip;
+
+ chip = snd_pcm_substream_chip(substream);
+ mutex_lock(&chip->audio_mutex);
+ runtime = substream->runtime;
+ alsa_stream = runtime->private_data;
+
+ alsa_stream->period_size = 0;
+ alsa_stream->buffer_size = 0;
+
+ bcm2835_audio_close(alsa_stream);
+ alsa_stream->chip->alsa_stream[alsa_stream->idx] = NULL;
+ /*
+ * Do not free up alsa_stream here, it will be freed up by
+ * runtime->private_free callback we registered in *_open above
+ */
+
+ chip->opened &= ~(1 << substream->number);
+
+ mutex_unlock(&chip->audio_mutex);
+
+ return 0;
+}
+
+static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
+ int channels;
+ int err;
+
+ /* 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 = runtime->channels;
+
+ err = bcm2835_audio_set_params(alsa_stream, channels,
+ runtime->rate,
+ snd_pcm_format_width(runtime->format));
+ if (err < 0)
+ return err;
+
+ memset(&alsa_stream->pcm_indirect, 0, sizeof(alsa_stream->pcm_indirect));
+
+ alsa_stream->pcm_indirect.hw_buffer_size =
+ alsa_stream->pcm_indirect.sw_buffer_size =
+ snd_pcm_lib_buffer_bytes(substream);
+
+ alsa_stream->buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ alsa_stream->period_size = snd_pcm_lib_period_bytes(substream);
+ atomic_set(&alsa_stream->pos, 0);
+ alsa_stream->period_offset = 0;
+ alsa_stream->draining = false;
+ alsa_stream->interpolate_start = ktime_get();
+
+ return 0;
+}
+
+static void snd_bcm2835_pcm_transfer(struct snd_pcm_substream *substream,
+ struct snd_pcm_indirect *rec, size_t bytes)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
+ void *src = (void *)(substream->runtime->dma_area + rec->sw_data);
+
+ bcm2835_audio_write(alsa_stream, bytes, src);
+}
+
+static int snd_bcm2835_pcm_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
+ struct snd_pcm_indirect *pcm_indirect = &alsa_stream->pcm_indirect;
+
+ return snd_pcm_indirect_playback_transfer(substream, pcm_indirect,
+ snd_bcm2835_pcm_transfer);
+}
+
+/* trigger callback */
+static int snd_bcm2835_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ return bcm2835_audio_start(alsa_stream);
+ case SNDRV_PCM_TRIGGER_DRAIN:
+ alsa_stream->draining = true;
+ return bcm2835_audio_drain(alsa_stream);
+ case SNDRV_PCM_TRIGGER_STOP:
+ return bcm2835_audio_stop(alsa_stream);
+ default:
+ return -EINVAL;
+ }
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t
+snd_bcm2835_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
+ ktime_t now = ktime_get();
+
+ /* Give userspace better delay reporting by interpolating between GPU
+ * notifications, assuming audio speed is close enough to the clock
+ * used for ktime
+ */
+
+ if ((ktime_to_ns(alsa_stream->interpolate_start)) &&
+ (ktime_compare(alsa_stream->interpolate_start, now) < 0)) {
+ u64 interval =
+ (ktime_to_ns(ktime_sub(now,
+ alsa_stream->interpolate_start)));
+ u64 frames_output_in_interval =
+ div_u64((interval * runtime->rate), 1000000000);
+ snd_pcm_sframes_t frames_output_in_interval_sized =
+ -frames_output_in_interval;
+ runtime->delay = frames_output_in_interval_sized;
+ }
+
+ return snd_pcm_indirect_playback_pointer(substream,
+ &alsa_stream->pcm_indirect,
+ atomic_read(&alsa_stream->pos));
+}
+
+/* operators */
+static const struct snd_pcm_ops snd_bcm2835_playback_ops = {
+ .open = snd_bcm2835_playback_open,
+ .close = snd_bcm2835_playback_close,
+ .prepare = snd_bcm2835_pcm_prepare,
+ .trigger = snd_bcm2835_pcm_trigger,
+ .pointer = snd_bcm2835_pcm_pointer,
+ .ack = snd_bcm2835_pcm_ack,
+};
+
+static const struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
+ .open = snd_bcm2835_playback_spdif_open,
+ .close = snd_bcm2835_playback_close,
+ .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(struct bcm2835_chip *chip, const char *name,
+ int idx, enum snd_bcm2835_route route,
+ u32 numchannels, bool spdif)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(chip->card, name, idx, numchannels, 0, &pcm);
+ if (err)
+ return err;
+
+ pcm->private_data = chip;
+ pcm->nonatomic = true;
+ strscpy(pcm->name, name, sizeof(pcm->name));
+ if (!spdif) {
+ chip->dest = route;
+ chip->volume = 0;
+ chip->mute = CTRL_VOL_UNMUTE;
+ }
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ spdif ? &snd_bcm2835_playback_spdif_ops :
+ &snd_bcm2835_playback_ops);
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ chip->card->dev, 128 * 1024, 128 * 1024);
+
+ if (spdif)
+ chip->pcm_spdif = pcm;
+ else
+ chip->pcm = pcm;
+ return 0;
+}
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
new file mode 100644
index 000000000..f4c2c9506
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2011 Broadcom Corporation. All rights reserved. */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/completion.h>
+#include "bcm2835.h"
+#include "vc_vchi_audioserv_defs.h"
+
+struct bcm2835_audio_instance {
+ struct device *dev;
+ unsigned int service_handle;
+ struct completion msg_avail_comp;
+ struct mutex vchi_mutex; /* Serialize vchiq access */
+ struct bcm2835_alsa_stream *alsa_stream;
+ int result;
+ unsigned int max_packet;
+ short peer_version;
+};
+
+static bool force_bulk;
+module_param(force_bulk, bool, 0444);
+MODULE_PARM_DESC(force_bulk, "Force use of vchiq bulk for audio");
+
+static void bcm2835_audio_lock(struct bcm2835_audio_instance *instance)
+{
+ mutex_lock(&instance->vchi_mutex);
+ vchiq_use_service(instance->alsa_stream->chip->vchi_ctx->instance,
+ instance->service_handle);
+}
+
+static void bcm2835_audio_unlock(struct bcm2835_audio_instance *instance)
+{
+ vchiq_release_service(instance->alsa_stream->chip->vchi_ctx->instance,
+ instance->service_handle);
+ mutex_unlock(&instance->vchi_mutex);
+}
+
+static int bcm2835_audio_send_msg_locked(struct bcm2835_audio_instance *instance,
+ struct vc_audio_msg *m, bool wait)
+{
+ int status;
+
+ if (wait) {
+ instance->result = -1;
+ init_completion(&instance->msg_avail_comp);
+ }
+
+ status = vchiq_queue_kernel_message(instance->alsa_stream->chip->vchi_ctx->instance,
+ instance->service_handle, m, sizeof(*m));
+ if (status) {
+ dev_err(instance->dev,
+ "vchi message queue failed: %d, msg=%d\n",
+ status, m->type);
+ return -EIO;
+ }
+
+ if (wait) {
+ if (!wait_for_completion_timeout(&instance->msg_avail_comp,
+ msecs_to_jiffies(10 * 1000))) {
+ dev_err(instance->dev,
+ "vchi message timeout, msg=%d\n", m->type);
+ return -ETIMEDOUT;
+ } else if (instance->result) {
+ dev_err(instance->dev,
+ "vchi message response error:%d, msg=%d\n",
+ instance->result, m->type);
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static int bcm2835_audio_send_msg(struct bcm2835_audio_instance *instance,
+ struct vc_audio_msg *m, bool wait)
+{
+ int err;
+
+ bcm2835_audio_lock(instance);
+ err = bcm2835_audio_send_msg_locked(instance, m, wait);
+ bcm2835_audio_unlock(instance);
+ return err;
+}
+
+static int bcm2835_audio_send_simple(struct bcm2835_audio_instance *instance,
+ int type, bool wait)
+{
+ struct vc_audio_msg m = { .type = type };
+
+ return bcm2835_audio_send_msg(instance, &m, wait);
+}
+
+static enum vchiq_status audio_vchi_callback(struct vchiq_instance *vchiq_instance,
+ enum vchiq_reason reason,
+ struct vchiq_header *header,
+ unsigned int handle, void *userdata)
+{
+ struct bcm2835_audio_instance *instance = vchiq_get_service_userdata(vchiq_instance,
+ handle);
+ struct vc_audio_msg *m;
+
+ if (reason != VCHIQ_MESSAGE_AVAILABLE)
+ return VCHIQ_SUCCESS;
+
+ m = (void *)header->data;
+ if (m->type == VC_AUDIO_MSG_TYPE_RESULT) {
+ instance->result = m->result.success;
+ complete(&instance->msg_avail_comp);
+ } else if (m->type == VC_AUDIO_MSG_TYPE_COMPLETE) {
+ if (m->complete.cookie1 != VC_AUDIO_WRITE_COOKIE1 ||
+ m->complete.cookie2 != VC_AUDIO_WRITE_COOKIE2)
+ dev_err(instance->dev, "invalid cookie\n");
+ else
+ bcm2835_playback_fifo(instance->alsa_stream,
+ m->complete.count);
+ } else {
+ dev_err(instance->dev, "unexpected callback type=%d\n", m->type);
+ }
+
+ vchiq_release_message(vchiq_instance, instance->service_handle, header);
+ return VCHIQ_SUCCESS;
+}
+
+static int
+vc_vchi_audio_init(struct vchiq_instance *vchiq_instance,
+ struct bcm2835_audio_instance *instance)
+{
+ struct vchiq_service_params_kernel params = {
+ .version = VC_AUDIOSERV_VER,
+ .version_min = VC_AUDIOSERV_MIN_VER,
+ .fourcc = VCHIQ_MAKE_FOURCC('A', 'U', 'D', 'S'),
+ .callback = audio_vchi_callback,
+ .userdata = instance,
+ };
+ int status;
+
+ /* Open the VCHI service connections */
+ status = vchiq_open_service(vchiq_instance, &params,
+ &instance->service_handle);
+
+ if (status) {
+ dev_err(instance->dev,
+ "failed to open VCHI service connection (status=%d)\n",
+ status);
+ return -EPERM;
+ }
+
+ /* Finished with the service for now */
+ vchiq_release_service(instance->alsa_stream->chip->vchi_ctx->instance,
+ instance->service_handle);
+
+ return 0;
+}
+
+static void vc_vchi_audio_deinit(struct bcm2835_audio_instance *instance)
+{
+ int status;
+
+ mutex_lock(&instance->vchi_mutex);
+ vchiq_use_service(instance->alsa_stream->chip->vchi_ctx->instance,
+ instance->service_handle);
+
+ /* Close all VCHI service connections */
+ status = vchiq_close_service(instance->alsa_stream->chip->vchi_ctx->instance,
+ instance->service_handle);
+ if (status) {
+ dev_err(instance->dev,
+ "failed to close VCHI service connection (status=%d)\n",
+ status);
+ }
+
+ mutex_unlock(&instance->vchi_mutex);
+}
+
+int bcm2835_new_vchi_ctx(struct device *dev, struct bcm2835_vchi_ctx *vchi_ctx)
+{
+ int ret;
+
+ /* Initialize and create a VCHI connection */
+ ret = vchiq_initialise(&vchi_ctx->instance);
+ if (ret) {
+ dev_err(dev, "failed to initialise VCHI instance (ret=%d)\n",
+ ret);
+ return -EIO;
+ }
+
+ ret = vchiq_connect(vchi_ctx->instance);
+ if (ret) {
+ dev_dbg(dev, "failed to connect VCHI instance (ret=%d)\n",
+ ret);
+
+ kfree(vchi_ctx->instance);
+ vchi_ctx->instance = NULL;
+
+ return -EIO;
+ }
+
+ return 0;
+}
+
+void bcm2835_free_vchi_ctx(struct bcm2835_vchi_ctx *vchi_ctx)
+{
+ /* Close the VCHI connection - it will also free vchi_ctx->instance */
+ WARN_ON(vchiq_shutdown(vchi_ctx->instance));
+
+ vchi_ctx->instance = NULL;
+}
+
+int bcm2835_audio_open(struct bcm2835_alsa_stream *alsa_stream)
+{
+ struct bcm2835_vchi_ctx *vchi_ctx = alsa_stream->chip->vchi_ctx;
+ struct bcm2835_audio_instance *instance;
+ int err;
+
+ /* Allocate memory for this instance */
+ instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+ if (!instance)
+ return -ENOMEM;
+ mutex_init(&instance->vchi_mutex);
+ instance->dev = alsa_stream->chip->dev;
+ instance->alsa_stream = alsa_stream;
+ alsa_stream->instance = instance;
+
+ err = vc_vchi_audio_init(vchi_ctx->instance,
+ instance);
+ if (err < 0)
+ goto free_instance;
+
+ err = bcm2835_audio_send_simple(instance, VC_AUDIO_MSG_TYPE_OPEN,
+ false);
+ if (err < 0)
+ goto deinit;
+
+ bcm2835_audio_lock(instance);
+ vchiq_get_peer_version(vchi_ctx->instance, instance->service_handle,
+ &instance->peer_version);
+ bcm2835_audio_unlock(instance);
+ if (instance->peer_version < 2 || force_bulk)
+ instance->max_packet = 0; /* bulk transfer */
+ else
+ instance->max_packet = 4000;
+
+ return 0;
+
+ deinit:
+ vc_vchi_audio_deinit(instance);
+ free_instance:
+ alsa_stream->instance = NULL;
+ kfree(instance);
+ return err;
+}
+
+int bcm2835_audio_set_ctls(struct bcm2835_alsa_stream *alsa_stream)
+{
+ struct bcm2835_chip *chip = alsa_stream->chip;
+ struct vc_audio_msg m = {};
+
+ m.type = VC_AUDIO_MSG_TYPE_CONTROL;
+ m.control.dest = chip->dest;
+ if (!chip->mute)
+ m.control.volume = CHIP_MIN_VOLUME;
+ else
+ m.control.volume = alsa2chip(chip->volume);
+
+ return bcm2835_audio_send_msg(alsa_stream->instance, &m, true);
+}
+
+int bcm2835_audio_set_params(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int channels, unsigned int samplerate,
+ unsigned int bps)
+{
+ struct vc_audio_msg m = {
+ .type = VC_AUDIO_MSG_TYPE_CONFIG,
+ .config.channels = channels,
+ .config.samplerate = samplerate,
+ .config.bps = bps,
+ };
+ int err;
+
+ /* resend ctls - alsa_stream may not have been open when first send */
+ err = bcm2835_audio_set_ctls(alsa_stream);
+ if (err)
+ return err;
+
+ return bcm2835_audio_send_msg(alsa_stream->instance, &m, true);
+}
+
+int bcm2835_audio_start(struct bcm2835_alsa_stream *alsa_stream)
+{
+ return bcm2835_audio_send_simple(alsa_stream->instance,
+ VC_AUDIO_MSG_TYPE_START, false);
+}
+
+int bcm2835_audio_stop(struct bcm2835_alsa_stream *alsa_stream)
+{
+ return bcm2835_audio_send_simple(alsa_stream->instance,
+ VC_AUDIO_MSG_TYPE_STOP, false);
+}
+
+/* FIXME: this doesn't seem working as expected for "draining" */
+int bcm2835_audio_drain(struct bcm2835_alsa_stream *alsa_stream)
+{
+ struct vc_audio_msg m = {
+ .type = VC_AUDIO_MSG_TYPE_STOP,
+ .stop.draining = 1,
+ };
+
+ return bcm2835_audio_send_msg(alsa_stream->instance, &m, false);
+}
+
+int bcm2835_audio_close(struct bcm2835_alsa_stream *alsa_stream)
+{
+ struct bcm2835_audio_instance *instance = alsa_stream->instance;
+ int err;
+
+ err = bcm2835_audio_send_simple(alsa_stream->instance,
+ VC_AUDIO_MSG_TYPE_CLOSE, true);
+
+ /* Stop the audio service */
+ vc_vchi_audio_deinit(instance);
+ alsa_stream->instance = NULL;
+ kfree(instance);
+
+ return err;
+}
+
+int bcm2835_audio_write(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int size, void *src)
+{
+ struct bcm2835_audio_instance *instance = alsa_stream->instance;
+ struct bcm2835_vchi_ctx *vchi_ctx = alsa_stream->chip->vchi_ctx;
+ struct vchiq_instance *vchiq_instance = vchi_ctx->instance;
+ struct vc_audio_msg m = {
+ .type = VC_AUDIO_MSG_TYPE_WRITE,
+ .write.count = size,
+ .write.max_packet = instance->max_packet,
+ .write.cookie1 = VC_AUDIO_WRITE_COOKIE1,
+ .write.cookie2 = VC_AUDIO_WRITE_COOKIE2,
+ };
+ unsigned int count;
+ int err, status;
+
+ if (!size)
+ return 0;
+
+ bcm2835_audio_lock(instance);
+ err = bcm2835_audio_send_msg_locked(instance, &m, false);
+ if (err < 0)
+ goto unlock;
+
+ count = size;
+ if (!instance->max_packet) {
+ /* Send the message to the videocore */
+ status = vchiq_bulk_transmit(vchiq_instance, instance->service_handle, src, count,
+ NULL, VCHIQ_BULK_MODE_BLOCKING);
+ } else {
+ while (count > 0) {
+ int bytes = min(instance->max_packet, count);
+
+ status = vchiq_queue_kernel_message(vchiq_instance,
+ instance->service_handle, src, bytes);
+ src += bytes;
+ count -= bytes;
+ }
+ }
+
+ if (status) {
+ dev_err(instance->dev,
+ "failed on %d bytes transfer (status=%d)\n",
+ size, status);
+ err = -EIO;
+ }
+
+ unlock:
+ bcm2835_audio_unlock(instance);
+ return err;
+}
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c
new file mode 100644
index 000000000..00bc898b0
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2011 Broadcom Corporation. All rights reserved. */
+
+#include <linux/platform_device.h>
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include "bcm2835.h"
+
+static bool enable_hdmi;
+static bool enable_headphones = true;
+static int num_channels = MAX_SUBSTREAMS;
+
+module_param(enable_hdmi, bool, 0444);
+MODULE_PARM_DESC(enable_hdmi, "Enables HDMI virtual audio device");
+module_param(enable_headphones, bool, 0444);
+MODULE_PARM_DESC(enable_headphones, "Enables Headphones virtual audio device");
+module_param(num_channels, int, 0644);
+MODULE_PARM_DESC(num_channels, "Number of audio channels (default: 8)");
+
+static void bcm2835_devm_free_vchi_ctx(struct device *dev, void *res)
+{
+ struct bcm2835_vchi_ctx *vchi_ctx = res;
+
+ bcm2835_free_vchi_ctx(vchi_ctx);
+}
+
+static int bcm2835_devm_add_vchi_ctx(struct device *dev)
+{
+ struct bcm2835_vchi_ctx *vchi_ctx;
+ int ret;
+
+ vchi_ctx = devres_alloc(bcm2835_devm_free_vchi_ctx, sizeof(*vchi_ctx),
+ GFP_KERNEL);
+ if (!vchi_ctx)
+ return -ENOMEM;
+
+ ret = bcm2835_new_vchi_ctx(dev, vchi_ctx);
+ if (ret) {
+ devres_free(vchi_ctx);
+ return ret;
+ }
+
+ devres_add(dev, vchi_ctx);
+
+ return 0;
+}
+
+struct bcm2835_audio_driver {
+ struct device_driver driver;
+ const char *shortname;
+ const char *longname;
+ int minchannels;
+ int (*newpcm)(struct bcm2835_chip *chip, const char *name,
+ enum snd_bcm2835_route route, u32 numchannels);
+ int (*newctl)(struct bcm2835_chip *chip);
+ enum snd_bcm2835_route route;
+};
+
+static int bcm2835_audio_dual_newpcm(struct bcm2835_chip *chip,
+ const char *name,
+ enum snd_bcm2835_route route,
+ u32 numchannels)
+{
+ int err;
+
+ err = snd_bcm2835_new_pcm(chip, name, 0, route,
+ numchannels, false);
+
+ if (err)
+ return err;
+
+ err = snd_bcm2835_new_pcm(chip, "IEC958", 1, route, 1, true);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int bcm2835_audio_simple_newpcm(struct bcm2835_chip *chip,
+ const char *name,
+ enum snd_bcm2835_route route,
+ u32 numchannels)
+{
+ return snd_bcm2835_new_pcm(chip, name, 0, route, numchannels, false);
+}
+
+static struct bcm2835_audio_driver bcm2835_audio_hdmi = {
+ .driver = {
+ .name = "bcm2835_hdmi",
+ .owner = THIS_MODULE,
+ },
+ .shortname = "bcm2835 HDMI",
+ .longname = "bcm2835 HDMI",
+ .minchannels = 1,
+ .newpcm = bcm2835_audio_dual_newpcm,
+ .newctl = snd_bcm2835_new_hdmi_ctl,
+ .route = AUDIO_DEST_HDMI
+};
+
+static struct bcm2835_audio_driver bcm2835_audio_headphones = {
+ .driver = {
+ .name = "bcm2835_headphones",
+ .owner = THIS_MODULE,
+ },
+ .shortname = "bcm2835 Headphones",
+ .longname = "bcm2835 Headphones",
+ .minchannels = 1,
+ .newpcm = bcm2835_audio_simple_newpcm,
+ .newctl = snd_bcm2835_new_headphones_ctl,
+ .route = AUDIO_DEST_HEADPHONES
+};
+
+struct bcm2835_audio_drivers {
+ struct bcm2835_audio_driver *audio_driver;
+ const bool *is_enabled;
+};
+
+static struct bcm2835_audio_drivers children_devices[] = {
+ {
+ .audio_driver = &bcm2835_audio_hdmi,
+ .is_enabled = &enable_hdmi,
+ },
+ {
+ .audio_driver = &bcm2835_audio_headphones,
+ .is_enabled = &enable_headphones,
+ },
+};
+
+static void bcm2835_card_free(void *data)
+{
+ snd_card_free(data);
+}
+
+static int snd_add_child_device(struct device *dev,
+ struct bcm2835_audio_driver *audio_driver,
+ u32 numchans)
+{
+ struct bcm2835_chip *chip;
+ struct snd_card *card;
+ int err;
+
+ err = snd_card_new(dev, -1, NULL, THIS_MODULE, sizeof(*chip), &card);
+ if (err < 0) {
+ dev_err(dev, "Failed to create card");
+ return err;
+ }
+
+ chip = card->private_data;
+ chip->card = card;
+ chip->dev = dev;
+ mutex_init(&chip->audio_mutex);
+
+ chip->vchi_ctx = devres_find(dev,
+ bcm2835_devm_free_vchi_ctx, NULL, NULL);
+ if (!chip->vchi_ctx) {
+ err = -ENODEV;
+ goto error;
+ }
+
+ strscpy(card->driver, audio_driver->driver.name, sizeof(card->driver));
+ strscpy(card->shortname, audio_driver->shortname, sizeof(card->shortname));
+ strscpy(card->longname, audio_driver->longname, sizeof(card->longname));
+
+ err = audio_driver->newpcm(chip, audio_driver->shortname,
+ audio_driver->route,
+ numchans);
+ if (err) {
+ dev_err(dev, "Failed to create pcm, error %d\n", err);
+ goto error;
+ }
+
+ err = audio_driver->newctl(chip);
+ if (err) {
+ dev_err(dev, "Failed to create controls, error %d\n", err);
+ goto error;
+ }
+
+ err = snd_card_register(card);
+ if (err) {
+ dev_err(dev, "Failed to register card, error %d\n", err);
+ goto error;
+ }
+
+ dev_set_drvdata(dev, chip);
+
+ err = devm_add_action(dev, bcm2835_card_free, card);
+ if (err < 0) {
+ dev_err(dev, "Failed to add devm action, err %d\n", err);
+ goto error;
+ }
+
+ dev_info(dev, "card created with %d channels\n", numchans);
+ return 0;
+
+ error:
+ snd_card_free(card);
+ return err;
+}
+
+static int snd_add_child_devices(struct device *device, u32 numchans)
+{
+ int extrachannels_per_driver = 0;
+ int extrachannels_remainder = 0;
+ int count_devices = 0;
+ int extrachannels = 0;
+ int minchannels = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(children_devices); i++)
+ if (*children_devices[i].is_enabled)
+ count_devices++;
+
+ if (!count_devices)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(children_devices); i++)
+ if (*children_devices[i].is_enabled)
+ minchannels +=
+ children_devices[i].audio_driver->minchannels;
+
+ if (minchannels < numchans) {
+ extrachannels = numchans - minchannels;
+ extrachannels_per_driver = extrachannels / count_devices;
+ extrachannels_remainder = extrachannels % count_devices;
+ }
+
+ dev_dbg(device, "minchannels %d\n", minchannels);
+ dev_dbg(device, "extrachannels %d\n", extrachannels);
+ dev_dbg(device, "extrachannels_per_driver %d\n",
+ extrachannels_per_driver);
+ dev_dbg(device, "extrachannels_remainder %d\n",
+ extrachannels_remainder);
+
+ for (i = 0; i < ARRAY_SIZE(children_devices); i++) {
+ struct bcm2835_audio_driver *audio_driver;
+ int numchannels_this_device;
+ int err;
+
+ if (!*children_devices[i].is_enabled)
+ continue;
+
+ audio_driver = children_devices[i].audio_driver;
+
+ if (audio_driver->minchannels > numchans) {
+ dev_err(device,
+ "Out of channels, needed %d but only %d left\n",
+ audio_driver->minchannels,
+ numchans);
+ continue;
+ }
+
+ numchannels_this_device =
+ audio_driver->minchannels + extrachannels_per_driver +
+ extrachannels_remainder;
+ extrachannels_remainder = 0;
+
+ numchans -= numchannels_this_device;
+
+ err = snd_add_child_device(device, audio_driver,
+ numchannels_this_device);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_bcm2835_alsa_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int err;
+
+ if (num_channels <= 0 || num_channels > MAX_SUBSTREAMS) {
+ num_channels = MAX_SUBSTREAMS;
+ dev_warn(dev, "Illegal num_channels value, will use %u\n",
+ num_channels);
+ }
+
+ err = bcm2835_devm_add_vchi_ctx(dev);
+ if (err)
+ return err;
+
+ err = snd_add_child_devices(dev, num_channels);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int snd_bcm2835_alsa_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int snd_bcm2835_alsa_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#endif
+
+static struct platform_driver bcm2835_alsa_driver = {
+ .probe = snd_bcm2835_alsa_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_bcm2835_alsa_suspend,
+ .resume = snd_bcm2835_alsa_resume,
+#endif
+ .driver = {
+ .name = "bcm2835_audio",
+ },
+};
+module_platform_driver(bcm2835_alsa_driver);
+
+MODULE_AUTHOR("Dom Cobley");
+MODULE_DESCRIPTION("Alsa driver for BCM2835 chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bcm2835_audio");
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
new file mode 100644
index 000000000..38b7451d7
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright 2011 Broadcom Corporation. All rights reserved. */
+
+#ifndef __SOUND_ARM_BCM2835_H
+#define __SOUND_ARM_BCM2835_H
+
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/raspberrypi/vchiq.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm-indirect.h>
+
+#define MAX_SUBSTREAMS (8)
+#define AVAIL_SUBSTREAMS_MASK (0xff)
+
+enum {
+ CTRL_VOL_MUTE,
+ CTRL_VOL_UNMUTE
+};
+
+/* macros for alsa2chip and chip2alsa, instead of functions */
+
+// convert alsa to chip volume (defined as macro rather than function call)
+#define alsa2chip(vol) ((uint)(-(((vol) << 8) / 100)))
+
+// convert chip to alsa volume
+#define chip2alsa(vol) -(((vol) * 100) >> 8)
+
+#define CHIP_MIN_VOLUME 26214 /* minimum level aka mute */
+
+/* Some constants for values .. */
+enum snd_bcm2835_route {
+ AUDIO_DEST_AUTO = 0,
+ AUDIO_DEST_HEADPHONES = 1,
+ AUDIO_DEST_HDMI = 2,
+ AUDIO_DEST_MAX,
+};
+
+enum snd_bcm2835_ctrl {
+ PCM_PLAYBACK_VOLUME,
+ PCM_PLAYBACK_MUTE,
+ PCM_PLAYBACK_DEVICE,
+};
+
+struct bcm2835_vchi_ctx {
+ struct vchiq_instance *instance;
+};
+
+/* definition of the chip-specific record */
+struct bcm2835_chip {
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_pcm *pcm_spdif;
+ struct device *dev;
+ struct bcm2835_alsa_stream *alsa_stream[MAX_SUBSTREAMS];
+
+ int volume;
+ int dest;
+ int mute;
+
+ unsigned int opened;
+ unsigned int spdif_status;
+ struct mutex audio_mutex; /* Serialize chip data access */
+
+ struct bcm2835_vchi_ctx *vchi_ctx;
+};
+
+struct bcm2835_alsa_stream {
+ struct bcm2835_chip *chip;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_indirect pcm_indirect;
+
+ int draining;
+
+ atomic_t pos;
+ unsigned int period_offset;
+ unsigned int buffer_size;
+ unsigned int period_size;
+ ktime_t interpolate_start;
+
+ struct bcm2835_audio_instance *instance;
+ int idx;
+};
+
+int snd_bcm2835_new_ctl(struct bcm2835_chip *chip);
+int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name,
+ int idx, enum snd_bcm2835_route route,
+ u32 numchannels, bool spdif);
+
+int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip);
+int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip);
+
+int bcm2835_new_vchi_ctx(struct device *dev, struct bcm2835_vchi_ctx *vchi_ctx);
+void bcm2835_free_vchi_ctx(struct bcm2835_vchi_ctx *vchi_ctx);
+
+int bcm2835_audio_open(struct bcm2835_alsa_stream *alsa_stream);
+int bcm2835_audio_close(struct bcm2835_alsa_stream *alsa_stream);
+int bcm2835_audio_set_params(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int channels, unsigned int samplerate,
+ unsigned int bps);
+int bcm2835_audio_start(struct bcm2835_alsa_stream *alsa_stream);
+int bcm2835_audio_stop(struct bcm2835_alsa_stream *alsa_stream);
+int bcm2835_audio_drain(struct bcm2835_alsa_stream *alsa_stream);
+int bcm2835_audio_set_ctls(struct bcm2835_alsa_stream *alsa_stream);
+int bcm2835_audio_write(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int count,
+ void *src);
+void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int size);
+unsigned int bcm2835_audio_retrieve_buffers(struct bcm2835_alsa_stream *alsa_stream);
+
+#endif /* __SOUND_ARM_BCM2835_H */
diff --git a/drivers/staging/vc04_services/bcm2835-audio/vc_vchi_audioserv_defs.h b/drivers/staging/vc04_services/bcm2835-audio/vc_vchi_audioserv_defs.h
new file mode 100644
index 000000000..b4fa239c5
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-audio/vc_vchi_audioserv_defs.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright 2011 Broadcom Corporation. All rights reserved. */
+
+#ifndef _VC_AUDIO_DEFS_H_
+#define _VC_AUDIO_DEFS_H_
+
+#define VC_AUDIOSERV_MIN_VER 1
+#define VC_AUDIOSERV_VER 2
+
+/* FourCC codes used for VCHI communication */
+#define VC_AUDIO_WRITE_COOKIE1 VCHIQ_MAKE_FOURCC('B', 'C', 'M', 'A')
+#define VC_AUDIO_WRITE_COOKIE2 VCHIQ_MAKE_FOURCC('D', 'A', 'T', 'A')
+
+/*
+ * List of screens that are currently supported
+ * All message types supported for HOST->VC direction
+ */
+
+enum vc_audio_msg_type {
+ VC_AUDIO_MSG_TYPE_RESULT, // Generic result
+ VC_AUDIO_MSG_TYPE_COMPLETE, // Generic result
+ VC_AUDIO_MSG_TYPE_CONFIG, // Configure audio
+ VC_AUDIO_MSG_TYPE_CONTROL, // Configure audio
+ VC_AUDIO_MSG_TYPE_OPEN, // Configure audio
+ VC_AUDIO_MSG_TYPE_CLOSE, // Configure audio
+ VC_AUDIO_MSG_TYPE_START, // Configure audio
+ VC_AUDIO_MSG_TYPE_STOP, // Configure audio
+ VC_AUDIO_MSG_TYPE_WRITE, // Configure audio
+ VC_AUDIO_MSG_TYPE_MAX
+};
+
+/* configure the audio */
+
+struct vc_audio_config {
+ u32 channels;
+ u32 samplerate;
+ u32 bps;
+};
+
+struct vc_audio_control {
+ u32 volume;
+ u32 dest;
+};
+
+struct vc_audio_open {
+ u32 dummy;
+};
+
+struct vc_audio_close {
+ u32 dummy;
+};
+
+struct vc_audio_start {
+ u32 dummy;
+};
+
+struct vc_audio_stop {
+ u32 draining;
+};
+
+/* configure the write audio samples */
+struct vc_audio_write {
+ u32 count; // in bytes
+ u32 cookie1;
+ u32 cookie2;
+ s16 silence;
+ s16 max_packet;
+};
+
+/* Generic result for a request (VC->HOST) */
+struct vc_audio_result {
+ s32 success; // Success value
+};
+
+/* Generic result for a request (VC->HOST) */
+struct vc_audio_complete {
+ s32 count; // Success value
+ u32 cookie1;
+ u32 cookie2;
+};
+
+/* Message header for all messages in HOST->VC direction */
+struct vc_audio_msg {
+ s32 type; /* Message type (VC_AUDIO_MSG_TYPE) */
+ union {
+ struct vc_audio_config config;
+ struct vc_audio_control control;
+ struct vc_audio_open open;
+ struct vc_audio_close close;
+ struct vc_audio_start start;
+ struct vc_audio_stop stop;
+ struct vc_audio_write write;
+ struct vc_audio_result result;
+ struct vc_audio_complete complete;
+ };
+};
+
+#endif /* _VC_AUDIO_DEFS_H_ */