summaryrefslogtreecommitdiffstats
path: root/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c')
-rw-r--r--drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c378
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 0000000000..d74110ca17
--- /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 int 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 0;
+
+ 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 0;
+}
+
+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, &params,
+ &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;
+}