Release 4.11 drivers/staging/bcm2835-audio/bcm2835-pcm.c
/*****************************************************************************
* 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");
if (runtime->private_data)
kfree(runtime->private_data);
runtime->private_data = NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 35 | 100.00% | 1 | 100.00% |
Total | 35 | 100.00% | 1 | 100.00% |
void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream)
{
unsigned int consumed = 0;
int new_period = 0;
audio_info(" .. IN\n");
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");
}
audio_info(" .. OUT\n");
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 207 | 100.00% | 2 | 100.00% |
Total | 207 | 100.00% | 2 | 100.00% |
/* 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;
audio_info(" .. IN (%d)\n", substream->number);
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;
sema_init(&alsa_stream->buffers_update_sem, 0);
sema_init(&alsa_stream->control_sem, 0);
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);
audio_info(" .. OUT =%d\n", err);
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 392 | 99.75% | 1 | 50.00% |
Dan Carpenter | 1 | 0.25% | 1 | 50.00% |
Total | 393 | 100.00% | 2 | 100.00% |
static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
{
return snd_bcm2835_playback_open_generic(substream, 0);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 19 | 100.00% | 1 | 100.00% |
Total | 19 | 100.00% | 1 | 100.00% |
static int snd_bcm2835_playback_spdif_open(struct snd_pcm_substream *substream)
{
return snd_bcm2835_playback_open_generic(substream, 1);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 19 | 100.00% | 1 | 100.00% |
Total | 19 | 100.00% | 1 | 100.00% |
/* 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;
audio_info(" .. IN\n");
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);
audio_info(" .. OUT\n");
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 191 | 100.00% | 1 | 100.00% |
Total | 191 | 100.00% | 1 | 100.00% |
/* 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;
audio_info(" .. IN\n");
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));
audio_info(" .. OUT\n");
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 108 | 100.00% | 1 | 100.00% |
Total | 108 | 100.00% | 1 | 100.00% |
/* hw_free callback */
static int snd_bcm2835_pcm_hw_free(struct snd_pcm_substream *substream)
{
audio_info(" .. IN\n");
return snd_pcm_lib_free_pages(substream);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 22 | 100.00% | 1 | 100.00% |
Total | 22 | 100.00% | 1 | 100.00% |
/* 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;
audio_info(" .. IN\n");
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);
audio_info(" .. OUT\n");
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 222 | 100.00% | 1 | 100.00% |
Total | 222 | 100.00% | 1 | 100.00% |
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);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 82 | 100.00% | 1 | 100.00% |
Total | 82 | 100.00% | 1 | 100.00% |
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 61 | 100.00% | 1 | 100.00% |
Total | 61 | 100.00% | 1 | 100.00% |
/* 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;
audio_info(" .. IN\n");
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;
}
audio_info(" .. OUT\n");
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 242 | 100.00% | 1 | 100.00% |
Total | 242 | 100.00% | 1 | 100.00% |
/* 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_info(" .. IN\n");
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);
audio_info(" .. OUT\n");
return snd_pcm_indirect_playback_pointer(substream,
&alsa_stream->pcm_indirect,
alsa_stream->pos);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 87 | 100.00% | 1 | 100.00% |
Total | 87 | 100.00% | 1 | 100.00% |
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 58 | 100.00% | 1 | 100.00% |
Total | 58 | 100.00% | 1 | 100.00% |
/* 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)
{
struct snd_pcm *pcm;
int err;
audio_info(" .. IN\n");
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, MAX_SUBSTREAMS, 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);
audio_info(" .. OUT\n");
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 175 | 100.00% | 1 | 100.00% |
Total | 175 | 100.00% | 1 | 100.00% |
int snd_bcm2835_new_spdif_pcm(struct bcm2835_chip *chip)
{
struct snd_pcm *pcm;
int err;
audio_info(" .. IN\n");
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);
audio_info(" .. OUT\n");
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 144 | 100.00% | 1 | 100.00% |
Total | 144 | 100.00% | 1 | 100.00% |
Overall Contributors
Person | Tokens | Prop | Commits | CommitProp |
Michael Zoran | 2361 | 99.96% | 2 | 66.67% |
Dan Carpenter | 1 | 0.04% | 1 | 33.33% |
Total | 2362 | 100.00% | 3 | 100.00% |
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.