diff options
Diffstat (limited to 'drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c')
-rw-r--r-- | drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c new file mode 100644 index 000000000..f4c2c9506 --- /dev/null +++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2011 Broadcom Corporation. All rights reserved. */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/completion.h> +#include "bcm2835.h" +#include "vc_vchi_audioserv_defs.h" + +struct bcm2835_audio_instance { + struct device *dev; + unsigned int service_handle; + struct completion msg_avail_comp; + struct mutex vchi_mutex; /* Serialize vchiq access */ + struct bcm2835_alsa_stream *alsa_stream; + int result; + unsigned int max_packet; + short peer_version; +}; + +static bool force_bulk; +module_param(force_bulk, bool, 0444); +MODULE_PARM_DESC(force_bulk, "Force use of vchiq bulk for audio"); + +static void bcm2835_audio_lock(struct bcm2835_audio_instance *instance) +{ + mutex_lock(&instance->vchi_mutex); + vchiq_use_service(instance->alsa_stream->chip->vchi_ctx->instance, + instance->service_handle); +} + +static void bcm2835_audio_unlock(struct bcm2835_audio_instance *instance) +{ + vchiq_release_service(instance->alsa_stream->chip->vchi_ctx->instance, + instance->service_handle); + mutex_unlock(&instance->vchi_mutex); +} + +static int bcm2835_audio_send_msg_locked(struct bcm2835_audio_instance *instance, + struct vc_audio_msg *m, bool wait) +{ + int status; + + if (wait) { + instance->result = -1; + init_completion(&instance->msg_avail_comp); + } + + status = vchiq_queue_kernel_message(instance->alsa_stream->chip->vchi_ctx->instance, + instance->service_handle, m, sizeof(*m)); + if (status) { + dev_err(instance->dev, + "vchi message queue failed: %d, msg=%d\n", + status, m->type); + return -EIO; + } + + if (wait) { + if (!wait_for_completion_timeout(&instance->msg_avail_comp, + msecs_to_jiffies(10 * 1000))) { + dev_err(instance->dev, + "vchi message timeout, msg=%d\n", m->type); + return -ETIMEDOUT; + } else if (instance->result) { + dev_err(instance->dev, + "vchi message response error:%d, msg=%d\n", + instance->result, m->type); + return -EIO; + } + } + + return 0; +} + +static int bcm2835_audio_send_msg(struct bcm2835_audio_instance *instance, + struct vc_audio_msg *m, bool wait) +{ + int err; + + bcm2835_audio_lock(instance); + err = bcm2835_audio_send_msg_locked(instance, m, wait); + bcm2835_audio_unlock(instance); + return err; +} + +static int bcm2835_audio_send_simple(struct bcm2835_audio_instance *instance, + int type, bool wait) +{ + struct vc_audio_msg m = { .type = type }; + + return bcm2835_audio_send_msg(instance, &m, wait); +} + +static enum vchiq_status audio_vchi_callback(struct vchiq_instance *vchiq_instance, + enum vchiq_reason reason, + struct vchiq_header *header, + unsigned int handle, void *userdata) +{ + struct bcm2835_audio_instance *instance = vchiq_get_service_userdata(vchiq_instance, + handle); + struct vc_audio_msg *m; + + if (reason != VCHIQ_MESSAGE_AVAILABLE) + return VCHIQ_SUCCESS; + + m = (void *)header->data; + if (m->type == VC_AUDIO_MSG_TYPE_RESULT) { + instance->result = m->result.success; + complete(&instance->msg_avail_comp); + } else if (m->type == VC_AUDIO_MSG_TYPE_COMPLETE) { + if (m->complete.cookie1 != VC_AUDIO_WRITE_COOKIE1 || + m->complete.cookie2 != VC_AUDIO_WRITE_COOKIE2) + dev_err(instance->dev, "invalid cookie\n"); + else + bcm2835_playback_fifo(instance->alsa_stream, + m->complete.count); + } else { + dev_err(instance->dev, "unexpected callback type=%d\n", m->type); + } + + vchiq_release_message(vchiq_instance, instance->service_handle, header); + return VCHIQ_SUCCESS; +} + +static int +vc_vchi_audio_init(struct vchiq_instance *vchiq_instance, + struct bcm2835_audio_instance *instance) +{ + struct vchiq_service_params_kernel params = { + .version = VC_AUDIOSERV_VER, + .version_min = VC_AUDIOSERV_MIN_VER, + .fourcc = VCHIQ_MAKE_FOURCC('A', 'U', 'D', 'S'), + .callback = audio_vchi_callback, + .userdata = instance, + }; + int status; + + /* Open the VCHI service connections */ + status = vchiq_open_service(vchiq_instance, ¶ms, + &instance->service_handle); + + if (status) { + dev_err(instance->dev, + "failed to open VCHI service connection (status=%d)\n", + status); + return -EPERM; + } + + /* Finished with the service for now */ + vchiq_release_service(instance->alsa_stream->chip->vchi_ctx->instance, + instance->service_handle); + + return 0; +} + +static void vc_vchi_audio_deinit(struct bcm2835_audio_instance *instance) +{ + int status; + + mutex_lock(&instance->vchi_mutex); + vchiq_use_service(instance->alsa_stream->chip->vchi_ctx->instance, + instance->service_handle); + + /* Close all VCHI service connections */ + status = vchiq_close_service(instance->alsa_stream->chip->vchi_ctx->instance, + instance->service_handle); + if (status) { + dev_err(instance->dev, + "failed to close VCHI service connection (status=%d)\n", + status); + } + + mutex_unlock(&instance->vchi_mutex); +} + +int bcm2835_new_vchi_ctx(struct device *dev, struct bcm2835_vchi_ctx *vchi_ctx) +{ + int ret; + + /* Initialize and create a VCHI connection */ + ret = vchiq_initialise(&vchi_ctx->instance); + if (ret) { + dev_err(dev, "failed to initialise VCHI instance (ret=%d)\n", + ret); + return -EIO; + } + + ret = vchiq_connect(vchi_ctx->instance); + if (ret) { + dev_dbg(dev, "failed to connect VCHI instance (ret=%d)\n", + ret); + + kfree(vchi_ctx->instance); + vchi_ctx->instance = NULL; + + return -EIO; + } + + return 0; +} + +void bcm2835_free_vchi_ctx(struct bcm2835_vchi_ctx *vchi_ctx) +{ + /* Close the VCHI connection - it will also free vchi_ctx->instance */ + WARN_ON(vchiq_shutdown(vchi_ctx->instance)); + + vchi_ctx->instance = NULL; +} + +int bcm2835_audio_open(struct bcm2835_alsa_stream *alsa_stream) +{ + struct bcm2835_vchi_ctx *vchi_ctx = alsa_stream->chip->vchi_ctx; + struct bcm2835_audio_instance *instance; + int err; + + /* Allocate memory for this instance */ + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) + return -ENOMEM; + mutex_init(&instance->vchi_mutex); + instance->dev = alsa_stream->chip->dev; + instance->alsa_stream = alsa_stream; + alsa_stream->instance = instance; + + err = vc_vchi_audio_init(vchi_ctx->instance, + instance); + if (err < 0) + goto free_instance; + + err = bcm2835_audio_send_simple(instance, VC_AUDIO_MSG_TYPE_OPEN, + false); + if (err < 0) + goto deinit; + + bcm2835_audio_lock(instance); + vchiq_get_peer_version(vchi_ctx->instance, instance->service_handle, + &instance->peer_version); + bcm2835_audio_unlock(instance); + if (instance->peer_version < 2 || force_bulk) + instance->max_packet = 0; /* bulk transfer */ + else + instance->max_packet = 4000; + + return 0; + + deinit: + vc_vchi_audio_deinit(instance); + free_instance: + alsa_stream->instance = NULL; + kfree(instance); + return err; +} + +int bcm2835_audio_set_ctls(struct bcm2835_alsa_stream *alsa_stream) +{ + struct bcm2835_chip *chip = alsa_stream->chip; + struct vc_audio_msg m = {}; + + m.type = VC_AUDIO_MSG_TYPE_CONTROL; + m.control.dest = chip->dest; + if (!chip->mute) + m.control.volume = CHIP_MIN_VOLUME; + else + m.control.volume = alsa2chip(chip->volume); + + return bcm2835_audio_send_msg(alsa_stream->instance, &m, true); +} + +int bcm2835_audio_set_params(struct bcm2835_alsa_stream *alsa_stream, + unsigned int channels, unsigned int samplerate, + unsigned int bps) +{ + struct vc_audio_msg m = { + .type = VC_AUDIO_MSG_TYPE_CONFIG, + .config.channels = channels, + .config.samplerate = samplerate, + .config.bps = bps, + }; + int err; + + /* resend ctls - alsa_stream may not have been open when first send */ + err = bcm2835_audio_set_ctls(alsa_stream); + if (err) + return err; + + return bcm2835_audio_send_msg(alsa_stream->instance, &m, true); +} + +int bcm2835_audio_start(struct bcm2835_alsa_stream *alsa_stream) +{ + return bcm2835_audio_send_simple(alsa_stream->instance, + VC_AUDIO_MSG_TYPE_START, false); +} + +int bcm2835_audio_stop(struct bcm2835_alsa_stream *alsa_stream) +{ + return bcm2835_audio_send_simple(alsa_stream->instance, + VC_AUDIO_MSG_TYPE_STOP, false); +} + +/* FIXME: this doesn't seem working as expected for "draining" */ +int bcm2835_audio_drain(struct bcm2835_alsa_stream *alsa_stream) +{ + struct vc_audio_msg m = { + .type = VC_AUDIO_MSG_TYPE_STOP, + .stop.draining = 1, + }; + + return bcm2835_audio_send_msg(alsa_stream->instance, &m, false); +} + +int bcm2835_audio_close(struct bcm2835_alsa_stream *alsa_stream) +{ + struct bcm2835_audio_instance *instance = alsa_stream->instance; + int err; + + err = bcm2835_audio_send_simple(alsa_stream->instance, + VC_AUDIO_MSG_TYPE_CLOSE, true); + + /* Stop the audio service */ + vc_vchi_audio_deinit(instance); + alsa_stream->instance = NULL; + kfree(instance); + + return err; +} + +int bcm2835_audio_write(struct bcm2835_alsa_stream *alsa_stream, + unsigned int size, void *src) +{ + struct bcm2835_audio_instance *instance = alsa_stream->instance; + struct bcm2835_vchi_ctx *vchi_ctx = alsa_stream->chip->vchi_ctx; + struct vchiq_instance *vchiq_instance = vchi_ctx->instance; + struct vc_audio_msg m = { + .type = VC_AUDIO_MSG_TYPE_WRITE, + .write.count = size, + .write.max_packet = instance->max_packet, + .write.cookie1 = VC_AUDIO_WRITE_COOKIE1, + .write.cookie2 = VC_AUDIO_WRITE_COOKIE2, + }; + unsigned int count; + int err, status; + + if (!size) + return 0; + + bcm2835_audio_lock(instance); + err = bcm2835_audio_send_msg_locked(instance, &m, false); + if (err < 0) + goto unlock; + + count = size; + if (!instance->max_packet) { + /* Send the message to the videocore */ + status = vchiq_bulk_transmit(vchiq_instance, instance->service_handle, src, count, + NULL, VCHIQ_BULK_MODE_BLOCKING); + } else { + while (count > 0) { + int bytes = min(instance->max_packet, count); + + status = vchiq_queue_kernel_message(vchiq_instance, + instance->service_handle, src, bytes); + src += bytes; + count -= bytes; + } + } + + if (status) { + dev_err(instance->dev, + "failed on %d bytes transfer (status=%d)\n", + size, status); + err = -EIO; + } + + unlock: + bcm2835_audio_unlock(instance); + return err; +} |