// SPDX-License-Identifier: GPL-2.0 /* Copyright 2011 Broadcom Corporation. All rights reserved. */ #include #include #include #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; 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->service_handle); } static void bcm2835_audio_unlock(struct bcm2835_audio_instance *instance) { vchiq_release_service(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->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(enum vchiq_reason reason, struct vchiq_header *header, unsigned int handle, void *userdata) { struct bcm2835_audio_instance *instance = vchiq_get_service_userdata(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(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->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->service_handle); /* Close all VCHI service connections */ status = vchiq_close_service(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(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 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(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(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; }