diff options
Diffstat (limited to 'drivers/staging/vc04_services/bcm2835-audio')
9 files changed, 2666 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 000000000000..9f536533c257 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/Kconfig @@ -0,0 +1,8 @@ +config SND_BCM2835 + tristate "BCM2835 Audio" + depends on ARCH_BCM2835 && SND + select SND_PCM + select BCM2835_VCHIQ + help + Say Y or M if you want to support BCM2835 built in audio + diff --git a/drivers/staging/vc04_services/bcm2835-audio/Makefile b/drivers/staging/vc04_services/bcm2835-audio/Makefile new file mode 100644 index 000000000000..d7b88d164d15 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_SND_BCM2835) += snd-bcm2835.o +snd-bcm2835-objs := bcm2835.o bcm2835-ctl.o bcm2835-pcm.o bcm2835-vchiq.o + +ccflags-y += -Idrivers/staging/vc04_services -D__VCCOREVER__=0x04000000 + diff --git a/drivers/staging/vc04_services/bcm2835-audio/TODO b/drivers/staging/vc04_services/bcm2835-audio/TODO new file mode 100644 index 000000000000..73d41fa631ac --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/TODO @@ -0,0 +1,29 @@ +***************************************************************************** +* * +* TODO: BCM2835-AUDIO * +* * +***************************************************************************** + + +1) Document the device tree node + +The downstream tree(the tree that the driver was imported from) at +http://www.github.com/raspberrypi/linux uses this node: + +audio: audio { + compatible = "brcm,bcm2835-audio"; + brcm,pwm-channels = <8>; +}; + +Since the driver requires the use of VCHIQ, it may be useful to have a link +in the device tree to the VCHIQ driver. + +2) Gracefully handle the case where VCHIQ is missing from the device tree or +it has not been initialized yet. + +3) Review error handling and remove duplicate code. + +4) Cleanup the logging mechanism. The driver should probably be using the +standard kernel logging mechanisms such as dev_info, dev_dbg, and friends. + +5) Fix the remaining checkpatch.pl errors and warnings. 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 000000000000..f484bb055df7 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-ctl.c @@ -0,0 +1,426 @@ +/***************************************************************************** + * Copyright 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + *****************************************************************************/ + +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rawmidi.h> +#include <sound/initval.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 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; +} + +/* toggles mute on or off depending on the value of nmute, and returns + * 1 if the mute value was changed, otherwise 0 + */ +static int toggle_mute(struct bcm2835_chip *chip, int nmute) +{ + /* if settings are ok, just return 0 */ + if (chip->mute == nmute) + return 0; + + /* if the sound is muted then we need to unmute */ + if (chip->mute == CTRL_VOL_MUTE) { + chip->volume = chip->old_volume; /* copy the old volume back */ + audio_info("Unmuting, old_volume = %d, volume = %d ...\n", chip->old_volume, chip->volume); + } else /* otherwise we mute */ { + chip->old_volume = chip->volume; + chip->volume = 26214; /* set volume to minimum level AKA mute */ + audio_info("Muting, old_volume = %d, volume = %d ...\n", chip->old_volume, chip->volume); + } + + chip->mute = nmute; + return 1; +} + +static int snd_bcm2835_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + BUG_ON(!chip && !(chip->avail_substreams & AVAIL_SUBSTREAMS_MASK)); + + if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) + ucontrol->value.integer.value[0] = chip2alsa(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 changed = 0; + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) { + audio_info("Volume change attempted.. volume = %d new_volume = %d\n", chip->volume, (int)ucontrol->value.integer.value[0]); + if (chip->mute == CTRL_VOL_MUTE) { + /* changed = toggle_mute(chip, CTRL_VOL_UNMUTE); */ + changed = 1; /* should return 0 to signify no change but the mixer takes this as the opposite sign (no idea why) */ + goto unlock; + } + if (changed || (ucontrol->value.integer.value[0] != chip2alsa(chip->volume))) { + chip->volume = alsa2chip(ucontrol->value.integer.value[0]); + changed = 1; + } + + } else if (kcontrol->private_value == PCM_PLAYBACK_MUTE) { + /* Now implemented */ + audio_info(" Mute attempted\n"); + changed = toggle_mute(chip, ucontrol->value.integer.value[0]); + + } else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE) { + if (ucontrol->value.integer.value[0] != chip->dest) { + chip->dest = ucontrol->value.integer.value[0]; + changed = 1; + } + } + + if (changed && bcm2835_audio_set_ctls(chip)) + dev_err(chip->card->dev, "Failed to set ALSA controls..\n"); + +unlock: + mutex_unlock(&chip->audio_mutex); + return changed; +} + +static DECLARE_TLV_DB_SCALE(snd_bcm2835_db_scale, CTRL_VOL_MIN, 1, 1); + +static struct snd_kcontrol_new snd_bcm2835_ctl[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .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, + .count = 1, + .tlv = {.p = snd_bcm2835_db_scale} + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .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, + .count = 1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Route", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = PCM_PLAYBACK_DEVICE, + .info = snd_bcm2835_ctl_info, + .get = snd_bcm2835_ctl_get, + .put = snd_bcm2835_ctl_put, + .count = 1, + }, +}; + +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; + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + 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; + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + 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 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; + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + 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_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; + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + 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 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(struct bcm2835_chip *chip) +{ + int err; + unsigned int idx; + + strcpy(chip->card->mixername, "Broadcom Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_ctl); idx++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_bcm2835_ctl[idx], 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; +} + +static struct snd_kcontrol_new snd_bcm2835_headphones_ctl[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .index = 0, + .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, + .count = 1, + .tlv = {.p = snd_bcm2835_db_scale} + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .index = 0, + .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, + .count = 1, + } +}; + +int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip) +{ + int err; + unsigned int idx; + + strcpy(chip->card->mixername, "Broadcom Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_headphones_ctl); idx++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_bcm2835_headphones_ctl[idx], + chip)); + if (err) + return err; + } + return 0; +} + +static struct snd_kcontrol_new snd_bcm2835_hdmi[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Playback Volume", + .index = 0, + .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, + .count = 1, + .tlv = {.p = snd_bcm2835_db_scale} + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Playback Switch", + .index = 0, + .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, + .count = 1, + } +}; + +int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip) +{ + int err; + unsigned int idx; + + strcpy(chip->card->mixername, "Broadcom Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_hdmi); idx++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_bcm2835_hdmi[idx], chip)); + if (err) + return err; + } + return 0; +} + 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 000000000000..e8cf0b97bf02 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c @@ -0,0 +1,568 @@ +/***************************************************************************** + * Copyright 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + *****************************************************************************/ + +#include <linux/interrupt.h> +#include <linux/slab.h> + +#include <sound/asoundef.h> + +#include "bcm2835.h" + +/* hardware definition */ +static 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), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .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 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"); + kfree(runtime->private_data); + runtime->private_data = NULL; +} + +void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream) +{ + unsigned int consumed = 0; + int new_period = 0; + + + audio_info("alsa_stream=%p substream=%p\n", alsa_stream, + alsa_stream ? alsa_stream->substream : 0); + + if (alsa_stream->open) + consumed = bcm2835_audio_retrieve_buffers(alsa_stream); + + /* We get called only if playback was triggered, So, the number of buffers we retrieve in + * each iteration are the buffers that have been played out already + */ + + if (alsa_stream->period_size) { + if ((alsa_stream->pos / alsa_stream->period_size) != + ((alsa_stream->pos + consumed) / alsa_stream->period_size)) + new_period = 1; + } + audio_debug("updating pos cur: %d + %d max:%d period_bytes:%d, hw_ptr: %d new_period:%d\n", + alsa_stream->pos, + consumed, + alsa_stream->buffer_size, + (int) (alsa_stream->period_size * alsa_stream->substream->runtime->periods), + frames_to_bytes(alsa_stream->substream->runtime, alsa_stream->substream->runtime->status->hw_ptr), + new_period); + if (alsa_stream->buffer_size) { + alsa_stream->pos += consumed & ~(1 << 30); + alsa_stream->pos %= alsa_stream->buffer_size; + } + + if (alsa_stream->substream) { + if (new_period) + snd_pcm_period_elapsed(alsa_stream->substream); + } else { + audio_warning(" unexpected NULL substream\n"); + } +} + +/* 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; + + + if (mutex_lock_interruptible(&chip->audio_mutex)) { + audio_error("Interrupted whilst waiting for lock\n"); + return -EINTR; + } + audio_info("Alsa open (%d)\n", substream->number); + 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) { + audio_error + ("substream(%d) device doesn't exist max(%d) substreams allowed\n", + idx, MAX_SUBSTREAMS); + err = -ENODEV; + goto out; + } + + /* Check if we are ready */ + if (!(chip->avail_substreams & (1 << idx))) { + /* We are not ready yet */ + audio_error("substream(%d) device is not ready yet\n", idx); + err = -EAGAIN; + 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; + + spin_lock_init(&alsa_stream->lock); + + 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); + + chip->alsa_stream[idx] = alsa_stream; + + chip->opened |= (1 << idx); + alsa_stream->open = 1; + alsa_stream->draining = 1; + +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); +} + +/* close callback */ +static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream) +{ + /* the hardware-specific codes will be here */ + + struct bcm2835_chip *chip; + struct snd_pcm_runtime *runtime; + struct bcm2835_alsa_stream *alsa_stream; + + + chip = snd_pcm_substream_chip(substream); + if (mutex_lock_interruptible(&chip->audio_mutex)) { + audio_error("Interrupted whilst waiting for lock\n"); + return -EINTR; + } + runtime = substream->runtime; + alsa_stream = runtime->private_data; + + audio_info("Alsa close\n"); + + /* + * Call stop if it's still running. This happens when app + * is force killed and we don't get a stop trigger. + */ + if (alsa_stream->running) { + int err; + err = bcm2835_audio_stop(alsa_stream); + alsa_stream->running = 0; + if (err) + audio_error(" Failed to STOP alsa device\n"); + } + + alsa_stream->period_size = 0; + alsa_stream->buffer_size = 0; + + if (alsa_stream->open) { + alsa_stream->open = 0; + bcm2835_audio_close(alsa_stream); + } + if (alsa_stream->chip) + 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; +} + +/* hw_params callback */ +static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct bcm2835_alsa_stream *alsa_stream = runtime->private_data; + int err; + + + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) { + audio_error + (" pcm_lib_malloc failed to allocated pages for buffers\n"); + return err; + } + + 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)); + + return err; +} + +/* hw_free callback */ +static int snd_bcm2835_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* prepare callback */ +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; + + + if (mutex_lock_interruptible(&chip->audio_mutex)) + return -EINTR; + + /* 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 = + 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); + alsa_stream->pos = 0; + + audio_debug("buffer_size=%d, period_size=%d pos=%d frame_bits=%d\n", + alsa_stream->buffer_size, alsa_stream->period_size, + alsa_stream->pos, runtime->frame_bits); + + mutex_unlock(&chip->audio_mutex); + 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); + int err; + + err = bcm2835_audio_write(alsa_stream, bytes, src); + if (err) + audio_error(" Failed to transfer to alsa device (%d)\n", err); + +} + +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; + + pcm_indirect->hw_queue_size = runtime->hw.buffer_bytes_max; + snd_pcm_indirect_playback_transfer(substream, pcm_indirect, + snd_bcm2835_pcm_transfer); + return 0; +} + +/* 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; + int err = 0; + + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + audio_debug("bcm2835_AUDIO_TRIGGER_START running=%d\n", + alsa_stream->running); + if (!alsa_stream->running) { + err = bcm2835_audio_start(alsa_stream); + if (!err) { + alsa_stream->pcm_indirect.hw_io = + alsa_stream->pcm_indirect.hw_data = + bytes_to_frames(runtime, + alsa_stream->pos); + substream->ops->ack(substream); + alsa_stream->running = 1; + alsa_stream->draining = 1; + } else { + audio_error(" Failed to START alsa device (%d)\n", err); + } + } + break; + case SNDRV_PCM_TRIGGER_STOP: + audio_debug + ("bcm2835_AUDIO_TRIGGER_STOP running=%d draining=%d\n", + alsa_stream->running, runtime->status->state == SNDRV_PCM_STATE_DRAINING); + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + audio_info("DRAINING\n"); + alsa_stream->draining = 1; + } else { + audio_info("DROPPING\n"); + alsa_stream->draining = 0; + } + if (alsa_stream->running) { + err = bcm2835_audio_stop(alsa_stream); + if (err != 0) + audio_error(" Failed to STOP alsa device (%d)\n", err); + alsa_stream->running = 0; + } + break; + default: + err = -EINVAL; + } + + return err; +} + +/* 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; + + + audio_debug("pcm_pointer... (%d) hwptr=%d appl=%d pos=%d\n", 0, + frames_to_bytes(runtime, runtime->status->hw_ptr), + frames_to_bytes(runtime, runtime->control->appl_ptr), + alsa_stream->pos); + + return snd_pcm_indirect_playback_pointer(substream, + &alsa_stream->pcm_indirect, + alsa_stream->pos); +} + +static int snd_bcm2835_pcm_lib_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + int ret = snd_pcm_lib_ioctl(substream, cmd, arg); + + audio_info(" .. substream=%p, cmd=%d, arg=%p (%x) ret=%d\n", substream, + cmd, arg, arg ? *(unsigned *) arg : 0, ret); + return ret; +} + +/* operators */ +static struct snd_pcm_ops snd_bcm2835_playback_ops = { + .open = snd_bcm2835_playback_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, +}; + +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(struct bcm2835_chip *chip, u32 numchannels) +{ + struct snd_pcm *pcm; + int err; + + mutex_init(&chip->audio_mutex); + if (mutex_lock_interruptible(&chip->audio_mutex)) { + audio_error("Interrupted whilst waiting for lock\n"); + return -EINTR; + } + err = snd_pcm_new(chip->card, "bcm2835 ALSA", 0, numchannels, 0, &pcm); + if (err < 0) + goto out; + pcm->private_data = chip; + strcpy(pcm->name, "bcm2835 ALSA"); + chip->pcm = pcm; + chip->dest = AUDIO_DEST_AUTO; + chip->volume = alsa2chip(0); + chip->mute = CTRL_VOL_UNMUTE; /*disable mute on startup */ + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_bcm2835_playback_ops); + + /* pre-allocation of buffers */ + /* NOTE: this may fail */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + snd_bcm2835_playback_hw.buffer_bytes_max, + snd_bcm2835_playback_hw.buffer_bytes_max); + + +out: + mutex_unlock(&chip->audio_mutex); + + return 0; +} + +int snd_bcm2835_new_spdif_pcm(struct bcm2835_chip *chip) +{ + struct snd_pcm *pcm; + int err; + + if (mutex_lock_interruptible(&chip->audio_mutex)) { + audio_error("Interrupted whilst waiting for lock\n"); + return -EINTR; + } + err = snd_pcm_new(chip->card, "bcm2835 ALSA", 1, 1, 0, &pcm); + if (err < 0) + goto out; + + 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); + + /* pre-allocation of buffers */ + /* NOTE: this may fail */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + snd_bcm2835_playback_spdif_hw.buffer_bytes_max, snd_bcm2835_playback_spdif_hw.buffer_bytes_max); +out: + mutex_unlock(&chip->audio_mutex); + + return 0; +} + +int snd_bcm2835_new_simple_pcm(struct bcm2835_chip *chip, + const char *name, + enum snd_bcm2835_route route, + u32 numchannels) +{ + struct snd_pcm *pcm; + int err; + + mutex_init(&chip->audio_mutex); + + err = snd_pcm_new(chip->card, name, 0, numchannels, + 0, &pcm); + if (err) + return err; + + pcm->private_data = chip; + strcpy(pcm->name, name); + chip->pcm = pcm; + chip->dest = route; + chip->volume = alsa2chip(0); + chip->mute = CTRL_VOL_UNMUTE; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_bcm2835_playback_ops); + + snd_pcm_lib_preallocate_pages_for_all( + pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + snd_bcm2835_playback_hw.buffer_bytes_max, + snd_bcm2835_playback_hw.buffer_bytes_max); + + 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 000000000000..5f3d8f2339e3 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c @@ -0,0 +1,875 @@ +/***************************************************************************** + * Copyright 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + *****************************************************************************/ + +#include <linux/device.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/syscalls.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/atomic.h> +#include <linux/module.h> +#include <linux/completion.h> + +#include "bcm2835.h" + +/* ---- Include Files -------------------------------------------------------- */ + +#include "interface/vchi/vchi.h" +#include "vc_vchi_audioserv_defs.h" + +/* ---- Private Constants and Types ------------------------------------------ */ + +#define BCM2835_AUDIO_STOP 0 +#define BCM2835_AUDIO_START 1 +#define BCM2835_AUDIO_WRITE 2 + +/* Logging macros (for remapping to other logging mechanisms, i.e., printf) */ +#ifdef AUDIO_DEBUG_ENABLE +#define LOG_ERR(fmt, arg...) pr_err("%s:%d " fmt, __func__, __LINE__, ##arg) +#define LOG_WARN(fmt, arg...) pr_info("%s:%d " fmt, __func__, __LINE__, ##arg) +#define LOG_INFO(fmt, arg...) pr_info("%s:%d " fmt, __func__, __LINE__, ##arg) +#define LOG_DBG(fmt, arg...) pr_info("%s:%d " fmt, __func__, __LINE__, ##arg) +#else +#define LOG_ERR(fmt, arg...) pr_err("%s:%d " fmt, __func__, __LINE__, ##arg) +#define LOG_WARN(fmt, arg...) no_printk(fmt, ##arg) +#define LOG_INFO(fmt, arg...) no_printk(fmt, ##arg) +#define LOG_DBG(fmt, arg...) no_printk(fmt, ##arg) +#endif + +struct bcm2835_audio_instance { + unsigned int num_connections; + VCHI_SERVICE_HANDLE_T vchi_handle[VCHI_MAX_NUM_CONNECTIONS]; + struct completion msg_avail_comp; + struct mutex vchi_mutex; + struct bcm2835_alsa_stream *alsa_stream; + int result; + short peer_version; +}; + +static bool force_bulk; + +/* ---- Private Variables ---------------------------------------------------- */ + +/* ---- Private Function Prototypes ------------------------------------------ */ + +/* ---- Private Functions ---------------------------------------------------- */ + +static int bcm2835_audio_stop_worker(struct bcm2835_alsa_stream *alsa_stream); +static int bcm2835_audio_start_worker(struct bcm2835_alsa_stream *alsa_stream); +static int bcm2835_audio_write_worker(struct bcm2835_alsa_stream *alsa_stream, + unsigned int count, void *src); + +// Routine to send a message across a service + +static int +bcm2835_vchi_msg_queue(VCHI_SERVICE_HANDLE_T handle, + void *data, + unsigned int size) +{ + return vchi_queue_kernel_message(handle, + data, + size); +} + +static const u32 BCM2835_AUDIO_WRITE_COOKIE1 = ('B' << 24 | 'C' << 16 | + 'M' << 8 | 'A'); +static const u32 BCM2835_AUDIO_WRITE_COOKIE2 = ('D' << 24 | 'A' << 16 | + 'T' << 8 | 'A'); + +struct bcm2835_audio_work { + struct work_struct my_work; + struct bcm2835_alsa_stream *alsa_stream; + int cmd; + void *src; + unsigned int count; +}; + +static void my_wq_function(struct work_struct *work) +{ + struct bcm2835_audio_work *w = + container_of(work, struct bcm2835_audio_work, my_work); + int ret = -9; + + switch (w->cmd) { + case BCM2835_AUDIO_START: + ret = bcm2835_audio_start_worker(w->alsa_stream); + break; + case BCM2835_AUDIO_STOP: + ret = bcm2835_audio_stop_worker(w->alsa_stream); + break; + case BCM2835_AUDIO_WRITE: + ret = bcm2835_audio_write_worker(w->alsa_stream, w->count, + w->src); + break; + default: + LOG_ERR(" Unexpected work: %p:%d\n", w->alsa_stream, w->cmd); + break; + } + kfree((void *)work); +} + +int bcm2835_audio_start(struct bcm2835_alsa_stream *alsa_stream) +{ + if (alsa_stream->my_wq) { + struct bcm2835_audio_work *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + /*--- Queue some work (item 1) ---*/ + if (!work) { + LOG_ERR(" .. Error: NULL work kmalloc\n"); + return -ENOMEM; + } + INIT_WORK(&work->my_work, my_wq_function); + work->alsa_stream = alsa_stream; + work->cmd = BCM2835_AUDIO_START; + if (!queue_work(alsa_stream->my_wq, &work->my_work)) { + kfree(work); + return -EBUSY; + } + } + return 0; +} + +int bcm2835_audio_stop(struct bcm2835_alsa_stream *alsa_stream) +{ + if (alsa_stream->my_wq) { + struct bcm2835_audio_work *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + /*--- Queue some work (item 1) ---*/ + if (!work) { + LOG_ERR(" .. Error: NULL work kmalloc\n"); + return -ENOMEM; + } + INIT_WORK(&work->my_work, my_wq_function); + work->alsa_stream = alsa_stream; + work->cmd = BCM2835_AUDIO_STOP; + if (!queue_work(alsa_stream->my_wq, &work->my_work)) { + kfree(work); + return -EBUSY; + } + } + return 0; +} + +int bcm2835_audio_write(struct bcm2835_alsa_stream *alsa_stream, + unsigned int count, void *src) +{ + if (alsa_stream->my_wq) { + struct bcm2835_audio_work *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + /*--- Queue some work (item 1) ---*/ + if (!work) { + LOG_ERR(" .. Error: NULL work kmalloc\n"); + return -ENOMEM; + } + INIT_WORK(&work->my_work, my_wq_function); + work->alsa_stream = alsa_stream; + work->cmd = BCM2835_AUDIO_WRITE; + work->src = src; + work->count = count; + if (!queue_work(alsa_stream->my_wq, &work->my_work)) { + kfree(work); + return -EBUSY; + } + } + return 0; +} + +static void my_workqueue_init(struct bcm2835_alsa_stream *alsa_stream) +{ + alsa_stream->my_wq = alloc_workqueue("my_queue", WQ_HIGHPRI, 1); +} + +static void my_workqueue_quit(struct bcm2835_alsa_stream *alsa_stream) +{ + if (alsa_stream->my_wq) { + flush_workqueue(alsa_stream->my_wq); + destroy_workqueue(alsa_stream->my_wq); + alsa_stream->my_wq = NULL; + } +} + +static void audio_vchi_callback(void *param, + const VCHI_CALLBACK_REASON_T reason, + void *msg_handle) +{ + struct bcm2835_audio_instance *instance = param; + int status; + int msg_len; + struct vc_audio_msg m; + + if (reason != VCHI_CALLBACK_MSG_AVAILABLE) + return; + + if (!instance) { + LOG_ERR(" .. instance is null\n"); + BUG(); + return; + } + if (!instance->vchi_handle[0]) { + LOG_ERR(" .. instance->vchi_handle[0] is null\n"); + BUG(); + return; + } + status = vchi_msg_dequeue(instance->vchi_handle[0], + &m, sizeof(m), &msg_len, VCHI_FLAGS_NONE); + if (m.type == VC_AUDIO_MSG_TYPE_RESULT) { + LOG_DBG(" .. instance=%p, m.type=VC_AUDIO_MSG_TYPE_RESULT, success=%d\n", + instance, m.u.result.success); + instance->result = m.u.result.success; + complete(&instance->msg_avail_comp); + } else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) { + struct bcm2835_alsa_stream *alsa_stream = instance->alsa_stream; + + LOG_DBG(" .. instance=%p, m.type=VC_AUDIO_MSG_TYPE_COMPLETE, complete=%d\n", + instance, m.u.complete.count); + if (m.u.complete.cookie1 != BCM2835_AUDIO_WRITE_COOKIE1 || + m.u.complete.cookie2 != BCM2835_AUDIO_WRITE_COOKIE2) + LOG_ERR(" .. response is corrupt\n"); + else if (alsa_stream) { + atomic_add(m.u.complete.count, + &alsa_stream->retrieved); + bcm2835_playback_fifo(alsa_stream); + } else { + LOG_ERR(" .. unexpected alsa_stream=%p\n", + alsa_stream); + } + } else { + LOG_ERR(" .. unexpected m.type=%d\n", m.type); + } +} + +static struct bcm2835_audio_instance * +vc_vchi_audio_init(VCHI_INSTANCE_T vchi_instance, + VCHI_CONNECTION_T **vchi_connections, + unsigned int num_connections) +{ + unsigned int i; + struct bcm2835_audio_instance *instance; + int status; + int ret; + + LOG_DBG("%s: start", __func__); + + if (num_connections > VCHI_MAX_NUM_CONNECTIONS) { + LOG_ERR("%s: unsupported number of connections %u (max=%u)\n", + __func__, num_connections, VCHI_MAX_NUM_CONNECTIONS); + + return ERR_PTR(-EINVAL); + } + /* Allocate memory for this instance */ + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) + return ERR_PTR(-ENOMEM); + + instance->num_connections = num_connections; + + /* Create a lock for exclusive, serialized VCHI connection access */ + mutex_init(&instance->vchi_mutex); + /* Open the VCHI service connections */ + for (i = 0; i < num_connections; i++) { + SERVICE_CREATION_T params = { + .version = VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER), + .service_id = VC_AUDIO_SERVER_NAME, + .connection = vchi_connections[i], + .rx_fifo_size = 0, + .tx_fifo_size = 0, + .callback = audio_vchi_callback, + .callback_param = instance, + .want_unaligned_bulk_rx = 1, //TODO: remove VCOS_FALSE + .want_unaligned_bulk_tx = 1, //TODO: remove VCOS_FALSE + .want_crc = 0 + }; + + LOG_DBG("%s: about to open %i\n", __func__, i); + status = vchi_service_open(vchi_instance, ¶ms, + &instance->vchi_handle[i]); + + LOG_DBG("%s: opened %i: %p=%d\n", __func__, i, instance->vchi_handle[i], status); + if (status) { + LOG_ERR("%s: failed to open VCHI service connection (status=%d)\n", + __func__, status); + ret = -EPERM; + goto err_close_services; + } + /* Finished with the service for now */ + vchi_service_release(instance->vchi_handle[i]); + } + + LOG_DBG("%s: okay\n", __func__); + return instance; + +err_close_services: + for (i = 0; i < instance->num_connections; i++) { + LOG_ERR("%s: closing %i: %p\n", __func__, i, instance->vchi_handle[i]); + if (instance->vchi_handle[i]) + vchi_service_close(instance->vchi_handle[i]); + } + + kfree(instance); + LOG_ERR("%s: error\n", __func__); + + return ERR_PTR(ret); +} + +static int vc_vchi_audio_deinit(struct bcm2835_audio_instance *instance) +{ + unsigned int i; + + + if (!instance) { + LOG_ERR("%s: invalid handle %p\n", __func__, instance); + + return -1; + } + + LOG_DBG(" .. about to lock (%d)\n", instance->num_connections); + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", + instance->num_connections); + return -EINTR; + } + + /* Close all VCHI service connections */ + for (i = 0; i < instance->num_connections; i++) { + int status; + + LOG_DBG(" .. %i:closing %p\n", i, instance->vchi_handle[i]); + vchi_service_use(instance->vchi_handle[i]); + + status = vchi_service_close(instance->vchi_handle[i]); + if (status) { + LOG_DBG("%s: failed to close VCHI service connection (status=%d)\n", + __func__, status); + } + } + + mutex_unlock(&instance->vchi_mutex); + + kfree(instance); + + + return 0; +} + +static int bcm2835_audio_open_connection(struct bcm2835_alsa_stream *alsa_stream) +{ + static VCHI_INSTANCE_T vchi_instance; + static VCHI_CONNECTION_T *vchi_connection; + static int initted; + struct bcm2835_audio_instance *instance = + (struct bcm2835_audio_instance *)alsa_stream->instance; + int ret; + + + LOG_INFO("%s: start\n", __func__); + BUG_ON(instance); + if (instance) { + LOG_ERR("%s: VCHI instance already open (%p)\n", + __func__, instance); + instance->alsa_stream = alsa_stream; + alsa_stream->instance = instance; + ret = 0; // xxx todo -1; + goto err_free_mem; + } + + /* Initialize and create a VCHI connection */ + if (!initted) { + ret = vchi_initialise(&vchi_instance); + if (ret) { + LOG_ERR("%s: failed to initialise VCHI instance (ret=%d)\n", + __func__, ret); + + ret = -EIO; + goto err_free_mem; + } + ret = vchi_connect(NULL, 0, vchi_instance); + if (ret) { + LOG_ERR("%s: failed to connect VCHI instance (ret=%d)\n", + __func__, ret); + + ret = -EIO; + goto err_free_mem; + } + initted = 1; + } + + /* Initialize an instance of the audio service */ + instance = vc_vchi_audio_init(vchi_instance, &vchi_connection, 1); + + if (IS_ERR(instance)) { + LOG_ERR("%s: failed to initialize audio service\n", __func__); + + ret = PTR_ERR(instance); + goto err_free_mem; + } + + instance->alsa_stream = alsa_stream; + alsa_stream->instance = instance; + + LOG_DBG(" success !\n"); + ret = 0; +err_free_mem: + kfree(vchi_instance); + + return ret; +} + +int bcm2835_audio_open(struct bcm2835_alsa_stream *alsa_stream) +{ + struct bcm2835_audio_instance *instance; + struct vc_audio_msg m; + int status; + int ret; + + + my_workqueue_init(alsa_stream); + + ret = bcm2835_audio_open_connection(alsa_stream); + if (ret) { + ret = -1; + goto exit; + } + instance = alsa_stream->instance; + LOG_DBG(" instance (%p)\n", instance); + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + m.type = VC_AUDIO_MSG_TYPE_OPEN; + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); +exit: + return ret; +} + +static int bcm2835_audio_set_ctls_chan(struct bcm2835_alsa_stream *alsa_stream, + struct bcm2835_chip *chip) +{ + struct vc_audio_msg m; + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int status; + int ret; + + + LOG_INFO(" Setting ALSA dest(%d), volume(%d)\n", + chip->dest, chip->volume); + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", + instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + instance->result = -1; + + m.type = VC_AUDIO_MSG_TYPE_CONTROL; + m.u.control.dest = chip->dest; + m.u.control.volume = chip->volume; + + /* Create the message available completion */ + init_completion(&instance->msg_avail_comp); + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + + /* We are expecting a reply from the videocore */ + wait_for_completion(&instance->msg_avail_comp); + + if (instance->result) { + LOG_ERR("%s: result=%d\n", __func__, instance->result); + + ret = -1; + goto unlock; + } + + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); + + return ret; +} + +int bcm2835_audio_set_ctls(struct bcm2835_chip *chip) +{ + int i; + int ret = 0; + + LOG_DBG(" Setting ALSA dest(%d), volume(%d)\n", chip->dest, chip->volume); + + /* change ctls for all substreams */ + for (i = 0; i < MAX_SUBSTREAMS; i++) { + if (chip->avail_substreams & (1 << i)) { + if (!chip->alsa_stream[i]) { + LOG_DBG(" No ALSA stream available?! %i:%p (%x)\n", i, chip->alsa_stream[i], chip->avail_substreams); + ret = 0; + } else if (bcm2835_audio_set_ctls_chan(chip->alsa_stream[i], chip) != 0) { + LOG_ERR("Couldn't set the controls for stream %d\n", i); + ret = -1; + } else { + LOG_DBG(" Controls set for stream %d\n", i); + } + } + } + return ret; +} + +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; + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int status; + int ret; + + + LOG_INFO(" Setting ALSA channels(%d), samplerate(%d), bits-per-sample(%d)\n", + channels, samplerate, bps); + + /* resend ctls - alsa_stream may not have been open when first send */ + ret = bcm2835_audio_set_ctls_chan(alsa_stream, alsa_stream->chip); + if (ret) { + LOG_ERR(" Alsa controls not supported\n"); + return -EINVAL; + } + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + instance->result = -1; + + m.type = VC_AUDIO_MSG_TYPE_CONFIG; + m.u.config.channels = channels; + m.u.config.samplerate = samplerate; + m.u.config.bps = bps; + + /* Create the message available completion */ + init_completion(&instance->msg_avail_comp); + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + + /* We are expecting a reply from the videocore */ + wait_for_completion(&instance->msg_avail_comp); + + if (instance->result) { + LOG_ERR("%s: result=%d", __func__, instance->result); + + ret = -1; + goto unlock; + } + + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); + + return ret; +} + +int bcm2835_audio_setup(struct bcm2835_alsa_stream *alsa_stream) +{ + + + return 0; +} + +static int bcm2835_audio_start_worker(struct bcm2835_alsa_stream *alsa_stream) +{ + struct vc_audio_msg m; + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int status; + int ret; + + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", + instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + m.type = VC_AUDIO_MSG_TYPE_START; + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); + return ret; +} + +static int bcm2835_audio_stop_worker(struct bcm2835_alsa_stream *alsa_stream) +{ + struct vc_audio_msg m; + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int status; + int ret; + + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", + instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + m.type = VC_AUDIO_MSG_TYPE_STOP; + m.u.stop.draining = alsa_stream->draining; + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); + return ret; +} + +int bcm2835_audio_close(struct bcm2835_alsa_stream *alsa_stream) +{ + struct vc_audio_msg m; + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int status; + int ret; + + + my_workqueue_quit(alsa_stream); + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", + instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + m.type = VC_AUDIO_MSG_TYPE_CLOSE; + + /* Create the message available completion */ + init_completion(&instance->msg_avail_comp); + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + ret = -1; + goto unlock; + } + + /* We are expecting a reply from the videocore */ + wait_for_completion(&instance->msg_avail_comp); + + if (instance->result) { + LOG_ERR("%s: failed result (result=%d)\n", + __func__, instance->result); + + ret = -1; + goto unlock; + } + + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); + + /* Stop the audio service */ + vc_vchi_audio_deinit(instance); + alsa_stream->instance = NULL; + + return ret; +} + +static int bcm2835_audio_write_worker(struct bcm2835_alsa_stream *alsa_stream, + unsigned int count, void *src) +{ + struct vc_audio_msg m; + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int status; + int ret; + + + LOG_INFO(" Writing %d bytes from %p\n", count, src); + + if (mutex_lock_interruptible(&instance->vchi_mutex)) { + LOG_DBG("Interrupted whilst waiting for lock on (%d)\n", + instance->num_connections); + return -EINTR; + } + vchi_service_use(instance->vchi_handle[0]); + + if (instance->peer_version == 0 && + vchi_get_peer_version(instance->vchi_handle[0], &instance->peer_version) == 0) + LOG_DBG("%s: client version %d connected\n", __func__, instance->peer_version); + + m.type = VC_AUDIO_MSG_TYPE_WRITE; + m.u.write.count = count; + // old version uses bulk, new version uses control + m.u.write.max_packet = instance->peer_version < 2 || force_bulk ? 0 : 4000; + m.u.write.cookie1 = BCM2835_AUDIO_WRITE_COOKIE1; + m.u.write.cookie2 = BCM2835_AUDIO_WRITE_COOKIE2; + m.u.write.silence = src == NULL; + + /* Send the message to the videocore */ + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + &m, sizeof(m)); + + if (status) { + LOG_ERR("%s: failed on vchi_msg_queue (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + if (!m.u.write.silence) { + if (!m.u.write.max_packet) { + /* Send the message to the videocore */ + status = vchi_bulk_queue_transmit(instance->vchi_handle[0], + src, count, + 0 * VCHI_FLAGS_BLOCK_UNTIL_QUEUED + + + 1 * VCHI_FLAGS_BLOCK_UNTIL_DATA_READ, + NULL); + } else { + while (count > 0) { + int bytes = min_t(int, m.u.write.max_packet, count); + + status = bcm2835_vchi_msg_queue(instance->vchi_handle[0], + src, bytes); + src = (char *)src + bytes; + count -= bytes; + } + } + if (status) { + LOG_ERR("%s: failed on vchi_bulk_queue_transmit (status=%d)\n", + __func__, status); + + ret = -1; + goto unlock; + } + } + ret = 0; + +unlock: + vchi_service_release(instance->vchi_handle[0]); + mutex_unlock(&instance->vchi_mutex); + return ret; +} + +/** + * Returns all buffers from arm->vc + */ +void bcm2835_audio_flush_buffers(struct bcm2835_alsa_stream *alsa_stream) +{ +} + +/** + * Forces VC to flush(drop) its filled playback buffers and + * return them the us. (VC->ARM) + */ +void bcm2835_audio_flush_playback_buffers(struct bcm2835_alsa_stream *alsa_stream) +{ +} + +unsigned int bcm2835_audio_retrieve_buffers(struct bcm2835_alsa_stream *alsa_stream) +{ + unsigned int count = atomic_read(&alsa_stream->retrieved); + + atomic_sub(count, &alsa_stream->retrieved); + return count; +} + +module_param(force_bulk, bool, 0444); +MODULE_PARM_DESC(force_bulk, "Force use of vchiq bulk for audio"); 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 000000000000..8f2d508183b2 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c @@ -0,0 +1,472 @@ +/***************************************************************************** + * Copyright 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + *****************************************************************************/ + +#include <linux/platform_device.h> + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/of.h> + +#include "bcm2835.h" + +static bool enable_hdmi; +static bool enable_headphones; +static bool enable_compat_alsa = true; + +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(enable_compat_alsa, bool, 0444); +MODULE_PARM_DESC(enable_compat_alsa, + "Enables ALSA compatibility virtual audio device"); + +static void snd_devm_unregister_child(struct device *dev, void *res) +{ + struct device *childdev = *(struct device **)res; + + device_unregister(childdev); +} + +static int snd_devm_add_child(struct device *dev, struct device *child) +{ + struct device **dr; + int ret; + + dr = devres_alloc(snd_devm_unregister_child, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = device_add(child); + if (ret) { + devres_free(dr); + return ret; + } + + *dr = child; + devres_add(dev, dr); + + return 0; +} + +static struct device * +snd_create_device(struct device *parent, + struct device_driver *driver, + const char *name) +{ + struct device *device; + int ret; + + device = devm_kzalloc(parent, sizeof(*device), GFP_KERNEL); + if (!device) + return ERR_PTR(-ENOMEM); + + device_initialize(device); + device->parent = parent; + device->driver = driver; + + dev_set_name(device, "%s", name); + + ret = snd_devm_add_child(parent, device); + if (ret) + return ERR_PTR(ret); + + return device; +} + +static int snd_bcm2835_free(struct bcm2835_chip *chip) +{ + kfree(chip); + return 0; +} + +/* component-destructor + * (see "Management of Cards and Components") + */ +static int snd_bcm2835_dev_free(struct snd_device *device) +{ + return snd_bcm2835_free(device->device_data); +} + +/* chip-specific constructor + * (see "Management of Cards and Components") + */ +static int snd_bcm2835_create(struct snd_card *card, + struct bcm2835_chip **rchip) +{ + struct bcm2835_chip *chip; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_bcm2835_dev_free, + }; + + *rchip = NULL; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->card = card; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err) { + snd_bcm2835_free(chip); + return err; + } + + *rchip = chip; + return 0; +} + +static void snd_devm_card_free(struct device *dev, void *res) +{ + struct snd_card *snd_card = *(struct snd_card **)res; + + snd_card_free(snd_card); +} + +static struct snd_card *snd_devm_card_new(struct device *dev) +{ + struct snd_card **dr; + struct snd_card *card; + int ret; + + dr = devres_alloc(snd_devm_card_free, sizeof(*dr), GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + ret = snd_card_new(dev, -1, NULL, THIS_MODULE, 0, &card); + if (ret) { + devres_free(dr); + return ERR_PTR(ret); + } + + *dr = card; + devres_add(dev, dr); + + return card; +} + +typedef int (*bcm2835_audio_newpcm_func)(struct bcm2835_chip *chip, + const char *name, + enum snd_bcm2835_route route, + u32 numchannels); + +typedef int (*bcm2835_audio_newctl_func)(struct bcm2835_chip *chip); + +struct bcm2835_audio_driver { + struct device_driver driver; + const char *shortname; + const char *longname; + int minchannels; + bcm2835_audio_newpcm_func newpcm; + bcm2835_audio_newctl_func newctl; + enum snd_bcm2835_route route; +}; + +static int bcm2835_audio_alsa_newpcm(struct bcm2835_chip *chip, + const char *name, + enum snd_bcm2835_route route, + u32 numchannels) +{ + int err; + + err = snd_bcm2835_new_pcm(chip, numchannels - 1); + if (err) + return err; + + err = snd_bcm2835_new_spdif_pcm(chip); + if (err) + return err; + + return 0; +} + +static struct bcm2835_audio_driver bcm2835_audio_alsa = { + .driver = { + .name = "bcm2835_alsa", + .owner = THIS_MODULE, + }, + .shortname = "bcm2835 ALSA", + .longname = "bcm2835 ALSA", + .minchannels = 2, + .newpcm = bcm2835_audio_alsa_newpcm, + .newctl = snd_bcm2835_new_ctl, +}; + +static struct bcm2835_audio_driver bcm2835_audio_hdmi = { + .driver = { + .name = "bcm2835_hdmi", + .owner = THIS_MODULE, + }, + .shortname = "bcm2835 HDMI", + .longname = "bcm2835 HDMI", + .minchannels = 1, + .newpcm = snd_bcm2835_new_simple_pcm, + .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 = snd_bcm2835_new_simple_pcm, + .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_alsa, + .is_enabled = &enable_compat_alsa, + }, + { + .audio_driver = &bcm2835_audio_hdmi, + .is_enabled = &enable_hdmi, + }, + { + .audio_driver = &bcm2835_audio_headphones, + .is_enabled = &enable_headphones, + }, +}; + +static int snd_add_child_device(struct device *device, + struct bcm2835_audio_driver *audio_driver, + u32 numchans) +{ + struct snd_card *card; + struct device *child; + struct bcm2835_chip *chip; + int err, i; + + child = snd_create_device(device, &audio_driver->driver, + audio_driver->driver.name); + if (IS_ERR(child)) { + dev_err(device, + "Unable to create child device %p, error %ld", + audio_driver->driver.name, + PTR_ERR(child)); + return PTR_ERR(child); + } + + card = snd_devm_card_new(child); + if (IS_ERR(card)) { + dev_err(child, "Failed to create card"); + return PTR_ERR(card); + } + + snd_card_set_dev(card, child); + strcpy(card->driver, audio_driver->driver.name); + strcpy(card->shortname, audio_driver->shortname); + strcpy(card->longname, audio_driver->longname); + + err = snd_bcm2835_create(card, &chip); + if (err) { + dev_err(child, "Failed to create chip, error %d\n", err); + return err; + } + + chip->dev = child; + + err = audio_driver->newpcm(chip, audio_driver->shortname, + audio_driver->route, + numchans); + if (err) { + dev_err(child, "Failed to create pcm, error %d\n", err); + return err; + } + + err = audio_driver->newctl(chip); + if (err) { + dev_err(child, "Failed to create controls, error %d\n", err); + return err; + } + + for (i = 0; i < numchans; i++) + chip->avail_substreams |= (1 << i); + + err = snd_card_register(card); + if (err) { + dev_err(child, "Failed to register card, error %d\n", err); + return err; + } + + dev_set_drvdata(child, card); + dev_info(child, "card created with %d channels\n", numchans); + + return 0; +} + +static int snd_add_child_devices(struct device *device, u32 numchans) +{ + int i; + int count_devices = 0; + int minchannels = 0; + int extrachannels = 0; + int extrachannels_per_driver = 0; + int extrachannels_remainder = 0; + + 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++) { + int err; + int numchannels_this_device; + struct bcm2835_audio_driver *audio_driver; + + 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_dt(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + u32 numchans; + int err; + + err = of_property_read_u32(dev->of_node, "brcm,pwm-channels", + &numchans); + if (err) { + dev_err(dev, "Failed to get DT property 'brcm,pwm-channels'"); + return err; + } + + if (numchans == 0 || numchans > MAX_SUBSTREAMS) { + numchans = MAX_SUBSTREAMS; + dev_warn(dev, + "Illegal 'brcm,pwm-channels' value, will use %u\n", + numchans); + } + + err = snd_add_child_devices(dev, numchans); + 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 const struct of_device_id snd_bcm2835_of_match_table[] = { + { .compatible = "brcm,bcm2835-audio",}, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_bcm2835_of_match_table); + +static struct platform_driver bcm2835_alsa0_driver = { + .probe = snd_bcm2835_alsa_probe_dt, +#ifdef CONFIG_PM + .suspend = snd_bcm2835_alsa_suspend, + .resume = snd_bcm2835_alsa_resume, +#endif + .driver = { + .name = "bcm2835_audio", + .owner = THIS_MODULE, + .of_match_table = snd_bcm2835_of_match_table, + }, +}; + +static int bcm2835_alsa_device_init(void) +{ + int retval; + + retval = platform_driver_register(&bcm2835_alsa0_driver); + if (retval) + pr_err("Error registering bcm2835_audio driver %d .\n", retval); + + return retval; +} + +static void bcm2835_alsa_device_exit(void) +{ + platform_driver_unregister(&bcm2835_alsa0_driver); +} + +late_initcall(bcm2835_alsa_device_init); +module_exit(bcm2835_alsa_device_exit); + +MODULE_AUTHOR("Dom Cobley"); +MODULE_DESCRIPTION("Alsa driver for BCM2835 chip"); +MODULE_LICENSE("GPL"); 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 000000000000..379604d3554e --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h @@ -0,0 +1,175 @@ +/***************************************************************************** + * Copyright 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + *****************************************************************************/ + +#ifndef __SOUND_ARM_BCM2835_H +#define __SOUND_ARM_BCM2835_H + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/pcm-indirect.h> +#include <linux/workqueue.h> + +/* + * #define AUDIO_DEBUG_ENABLE + * #define AUDIO_VERBOSE_DEBUG_ENABLE + */ + +/* Debug macros */ + +#ifdef AUDIO_DEBUG_ENABLE +#ifdef AUDIO_VERBOSE_DEBUG_ENABLE + +#define audio_debug(fmt, arg...) \ + pr_info("%s:%d " fmt, __func__, __LINE__, ##arg) + +#define audio_info(fmt, arg...) \ + pr_info("%s:%d " fmt, __func__, __LINE__, ##arg) + +#else + +#define audio_debug(fmt, arg...) + +#define audio_info(fmt, arg...) + +#endif /* AUDIO_VERBOSE_DEBUG_ENABLE */ + +#else + +#define audio_debug(fmt, arg...) + +#define audio_info(fmt, arg...) + +#endif /* AUDIO_DEBUG_ENABLE */ + +#define audio_error(fmt, arg...) \ + pr_err("%s:%d " fmt, __func__, __LINE__, ##arg) + +#define audio_warning(fmt, arg...) \ + pr_warn("%s:%d " fmt, __func__, __LINE__, ##arg) + +#define audio_alert(fmt, arg...) \ + pr_alert("%s:%d " fmt, __func__, __LINE__, ##arg) + +#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) + +/* 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, +}; + +/* definition of the chip-specific record */ +struct bcm2835_chip { + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm *pcm_spdif; + /* Bitmat for valid reg_base and irq numbers */ + unsigned int avail_substreams; + struct device *dev; + struct bcm2835_alsa_stream *alsa_stream[MAX_SUBSTREAMS]; + + int volume; + int old_volume; /* stores the volume value whist muted */ + int dest; + int mute; + + unsigned int opened; + unsigned int spdif_status; + struct mutex audio_mutex; +}; + +struct bcm2835_alsa_stream { + struct bcm2835_chip *chip; + struct snd_pcm_substream *substream; + struct snd_pcm_indirect pcm_indirect; + + spinlock_t lock; + volatile unsigned int control; + volatile unsigned int status; + + int open; + int running; + int draining; + + int channels; + int params_rate; + int pcm_format_width; + + unsigned int pos; + unsigned int buffer_size; + unsigned int period_size; + + atomic_t retrieved; + struct bcm2835_audio_instance *instance; + struct workqueue_struct *my_wq; + int idx; +}; + +int snd_bcm2835_new_ctl(struct bcm2835_chip *chip); +int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, u32 numchannels); +int snd_bcm2835_new_spdif_pcm(struct bcm2835_chip *chip); +int snd_bcm2835_new_simple_pcm(struct bcm2835_chip *chip, + const char *name, + enum snd_bcm2835_route route, + u32 numchannels); + +int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip); +int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip); + +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_setup(struct bcm2835_alsa_stream *alsa_stream); +int bcm2835_audio_start(struct bcm2835_alsa_stream *alsa_stream); +int bcm2835_audio_stop(struct bcm2835_alsa_stream *alsa_stream); +int bcm2835_audio_set_ctls(struct bcm2835_chip *chip); +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 bcm2835_audio_retrieve_buffers(struct bcm2835_alsa_stream *alsa_stream); +void bcm2835_audio_flush_buffers(struct bcm2835_alsa_stream *alsa_stream); +void bcm2835_audio_flush_playback_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 000000000000..da96f1bc2516 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/vc_vchi_audioserv_defs.h @@ -0,0 +1,108 @@ +/***************************************************************************** + * Copyright 2011 Broadcom Corporation. All rights reserved. + * + * Unless you and Broadcom execute a separate written software license + * agreement governing use of this software, this software is licensed to you + * under the terms of the GNU General Public License version 2, available at + * http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). + * + * Notwithstanding the above, under no circumstances may you combine this + * software in any way with any other Broadcom software provided under a + * license other than the GPL, without Broadcom's express prior written + * consent. + *****************************************************************************/ + +#ifndef _VC_AUDIO_DEFS_H_ +#define _VC_AUDIO_DEFS_H_ + +#define VC_AUDIOSERV_MIN_VER 1 +#define VC_AUDIOSERV_VER 2 + +/* FourCC code used for VCHI connection */ +#define VC_AUDIO_SERVER_NAME MAKE_FOURCC("AUDS") + +/* + * 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; + } u; +}; + +#endif /* _VC_AUDIO_DEFS_H_ */ |