diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/staging/vc04_services/bcm2835-audio | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/staging/vc04_services/bcm2835-audio')
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, ¶ms, + &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_ */ |