root/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
// 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 <linux/raspberrypi/vchiq_arm.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,
                                                 secs_to_jiffies(10))) {
                        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 *cb_data, void __user *cb_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)
{
        struct vchiq_drv_mgmt *mgmt = dev_get_drvdata(dev->parent);
        int ret;

        /* Initialize and create a VCHI connection */
        ret = vchiq_initialise(&mgmt->state, &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_obj(*instance);
        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;
}