/* 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_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_DRAIN_TRIGGER),
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
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_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_DRAIN_TRIGGER),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000,
kfree(runtime->private_data);
}
-void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream)
+void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int bytes)
{
- unsigned int consumed = 0;
- int new_period = 0;
-
- audio_info("alsa_stream=%p substream=%p\n", alsa_stream,
- alsa_stream ? alsa_stream->substream : 0);
-
- 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;
+ 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;
}
- if (alsa_stream->substream) {
- if (new_period)
- snd_pcm_period_elapsed(alsa_stream->substream);
- } else {
- audio_warning(" unexpected NULL substream\n");
+ pos = atomic_read(&alsa_stream->pos);
+ pos += bytes;
+ pos %= alsa_stream->buffer_size;
+ atomic_set(&alsa_stream->pos, pos);
+
+ alsa_stream->period_offset += bytes;
+ if (alsa_stream->period_offset >= alsa_stream->period_size) {
+ alsa_stream->period_offset %= alsa_stream->period_size;
+ snd_pcm_period_elapsed(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;
+ atomic_set(&alsa_stream->pos, 0);
+ alsa_stream->period_offset = 0;
alsa_stream->draining = false;
return 0;
return bcm2835_audio_start(alsa_stream);
case SNDRV_PCM_TRIGGER_DRAIN:
alsa_stream->draining = true;
- return 0;
+ return bcm2835_audio_drain(alsa_stream);
case SNDRV_PCM_TRIGGER_STOP:
return bcm2835_audio_stop(alsa_stream);
default:
return snd_pcm_indirect_playback_pointer(substream,
&alsa_stream->pcm_indirect,
- alsa_stream->pos);
+ atomic_read(&alsa_stream->pos));
}
/* operators */
if (err < 0)
return err;
pcm->private_data = chip;
+ pcm->nonatomic = true;
strcpy(pcm->name, "bcm2835 ALSA");
chip->pcm = pcm;
chip->dest = AUDIO_DEST_AUTO;
return err;
pcm->private_data = chip;
+ pcm->nonatomic = true;
strcpy(pcm->name, "bcm2835 IEC958/HDMI");
chip->pcm_spdif = pcm;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
return err;
pcm->private_data = chip;
+ pcm->nonatomic = true;
strcpy(pcm->name, name);
chip->pcm = pcm;
chip->dest = route;
/* ---- 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)
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);
-
static void bcm2835_audio_lock(struct bcm2835_audio_instance *instance)
{
mutex_lock(&instance->vchi_mutex);
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)
-{
- 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)
-{
- 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)
-{
- 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_quit(struct bcm2835_alsa_stream *alsa_stream)
-{
- 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)
if (reason != VCHI_CALLBACK_MSG_AVAILABLE)
return;
- if (!instance) {
- LOG_ERR(" .. instance is null\n");
- BUG();
- return;
- }
- if (!instance->vchi_handle) {
- LOG_ERR(" .. instance->vchi_handle is null\n");
- BUG();
- return;
- }
status = vchi_msg_dequeue(instance->vchi_handle,
&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);
- }
+ LOG_ERR("invalid cookie\n");
+ else
+ bcm2835_playback_fifo(instance->alsa_stream,
+ m.u.complete.count);
} else {
- LOG_ERR(" .. unexpected m.type=%d\n", m.type);
+ LOG_ERR("unexpected callback type=%d\n", m.type);
}
}
-static struct bcm2835_audio_instance *
+static int
vc_vchi_audio_init(VCHI_INSTANCE_T vchi_instance,
- VCHI_CONNECTION_T *vchi_connection)
+ VCHI_CONNECTION_T *vchi_connection,
+ struct bcm2835_audio_instance *instance)
{
SERVICE_CREATION_T params = {
.version = VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER),
.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
};
- struct bcm2835_audio_instance *instance;
int status;
- /* Allocate memory for this instance */
- instance = kzalloc(sizeof(*instance), GFP_KERNEL);
- if (!instance)
- return ERR_PTR(-ENOMEM);
-
- /* Create a lock for exclusive, serialized VCHI connection access */
- mutex_init(&instance->vchi_mutex);
/* Open the VCHI service connections */
- params.callback_param = instance,
-
status = vchi_service_open(vchi_instance, ¶ms,
&instance->vchi_handle);
LOG_ERR("%s: failed to open VCHI service connection (status=%d)\n",
__func__, status);
kfree(instance);
- return ERR_PTR(-EPERM);
+ return -EPERM;
}
/* Finished with the service for now */
vchi_service_release(instance->vchi_handle);
- return instance;
+ return 0;
}
-static int vc_vchi_audio_deinit(struct bcm2835_audio_instance *instance)
+static void vc_vchi_audio_deinit(struct bcm2835_audio_instance *instance)
{
int status;
}
mutex_unlock(&instance->vchi_mutex);
-
- kfree(instance);
-
- return 0;
}
int bcm2835_new_vchi_ctx(struct bcm2835_vchi_ctx *vchi_ctx)
vchi_ctx->vchi_instance = NULL;
}
-static int bcm2835_audio_open_connection(struct bcm2835_alsa_stream *alsa_stream)
-{
- struct bcm2835_audio_instance *instance =
- (struct bcm2835_audio_instance *)alsa_stream->instance;
- struct bcm2835_vchi_ctx *vhci_ctx = alsa_stream->chip->vchi_ctx;
-
- /* Initialize an instance of the audio service */
- instance = vc_vchi_audio_init(vhci_ctx->vchi_instance,
- vhci_ctx->vchi_connection);
-
- if (IS_ERR(instance))
- return PTR_ERR(instance);
-
- instance->alsa_stream = alsa_stream;
- alsa_stream->instance = instance;
-
- return 0;
-}
-
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;
- alsa_stream->my_wq = alloc_workqueue("my_queue", WQ_HIGHPRI, 1);
- if (!alsa_stream->my_wq)
+ /* Allocate memory for this instance */
+ instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+ if (!instance)
return -ENOMEM;
+ mutex_init(&instance->vchi_mutex);
+ instance->alsa_stream = alsa_stream;
+ alsa_stream->instance = instance;
- err = bcm2835_audio_open_connection(alsa_stream);
+ err = vc_vchi_audio_init(vchi_ctx->vchi_instance,
+ vchi_ctx->vchi_connection,
+ instance);
if (err < 0)
- goto free_wq;
-
- instance = alsa_stream->instance;
+ goto free_instance;
err = bcm2835_audio_send_simple(instance, VC_AUDIO_MSG_TYPE_OPEN,
false);
deinit:
vc_vchi_audio_deinit(instance);
- free_wq:
- destroy_workqueue(alsa_stream->my_wq);
+ free_instance:
+ alsa_stream->instance = NULL;
+ kfree(instance);
return err;
}
return bcm2835_audio_send_msg(alsa_stream->instance, &m, true);
}
-static int bcm2835_audio_start_worker(struct bcm2835_alsa_stream *alsa_stream)
+int bcm2835_audio_start(struct bcm2835_alsa_stream *alsa_stream)
{
return bcm2835_audio_send_simple(alsa_stream->instance,
VC_AUDIO_MSG_TYPE_START, false);
}
-static int bcm2835_audio_stop_worker(struct bcm2835_alsa_stream *alsa_stream)
+int bcm2835_audio_stop(struct bcm2835_alsa_stream *alsa_stream)
{
return bcm2835_audio_send_simple(alsa_stream->instance,
VC_AUDIO_MSG_TYPE_STOP, false);
}
+int bcm2835_audio_drain(struct bcm2835_alsa_stream *alsa_stream)
+{
+ struct vc_audio_msg m = {
+ .type = VC_AUDIO_MSG_TYPE_STOP,
+ .u.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;
- my_workqueue_quit(alsa_stream);
-
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;
}
-static int bcm2835_audio_write_worker(struct bcm2835_alsa_stream *alsa_stream,
- unsigned int size, void *src)
+int bcm2835_audio_write(struct bcm2835_alsa_stream *alsa_stream,
+ unsigned int size, void *src)
{
struct bcm2835_audio_instance *instance = alsa_stream->instance;
struct vc_audio_msg m = {
return err;
}
-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");