| // 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; |
| VCHI_SERVICE_HANDLE_T vchi_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); |
| vchi_service_use(instance->vchi_handle); |
| } |
| |
| static void bcm2835_audio_unlock(struct bcm2835_audio_instance *instance) |
| { |
| vchi_service_release(instance->vchi_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 = vchi_queue_kernel_message(instance->vchi_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 void audio_vchi_callback(void *param, |
| const VCHI_CALLBACK_REASON_T reason, |
| void *msg_handle) |
| { |
| struct bcm2835_audio_instance *instance = param; |
| struct vc_audio_msg m; |
| int msg_len; |
| int status; |
| |
| if (reason != VCHI_CALLBACK_MSG_AVAILABLE) |
| return; |
| |
| status = vchi_msg_dequeue(instance->vchi_handle, |
| &m, sizeof(m), &msg_len, VCHI_FLAGS_NONE); |
| 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); |
| } |
| } |
| |
| static int |
| vc_vchi_audio_init(VCHI_INSTANCE_T vchi_instance, |
| struct bcm2835_audio_instance *instance) |
| { |
| struct service_creation params = { |
| .version = VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER), |
| .service_id = VC_AUDIO_SERVER_NAME, |
| .callback = audio_vchi_callback, |
| .callback_param = instance, |
| }; |
| int status; |
| |
| /* Open the VCHI service connections */ |
| status = vchi_service_open(vchi_instance, ¶ms, |
| &instance->vchi_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 */ |
| vchi_service_release(instance->vchi_handle); |
| |
| return 0; |
| } |
| |
| static void vc_vchi_audio_deinit(struct bcm2835_audio_instance *instance) |
| { |
| int status; |
| |
| mutex_lock(&instance->vchi_mutex); |
| vchi_service_use(instance->vchi_handle); |
| |
| /* Close all VCHI service connections */ |
| status = vchi_service_close(instance->vchi_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 = vchi_initialise(&vchi_ctx->vchi_instance); |
| if (ret) { |
| dev_err(dev, "failed to initialise VCHI instance (ret=%d)\n", |
| ret); |
| return -EIO; |
| } |
| |
| ret = vchi_connect(vchi_ctx->vchi_instance); |
| if (ret) { |
| dev_dbg(dev, "failed to connect VCHI instance (ret=%d)\n", |
| ret); |
| |
| kfree(vchi_ctx->vchi_instance); |
| vchi_ctx->vchi_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_instance */ |
| WARN_ON(vchi_disconnect(vchi_ctx->vchi_instance)); |
| |
| vchi_ctx->vchi_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->vchi_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); |
| vchi_get_peer_version(instance->vchi_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); |
| } |
| |
| 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 = vchi_bulk_queue_transmit(instance->vchi_handle, |
| src, count, |
| VCHI_FLAGS_BLOCK_UNTIL_DATA_READ, |
| NULL); |
| } else { |
| while (count > 0) { |
| int bytes = min(instance->max_packet, count); |
| |
| status = vchi_queue_kernel_message(instance->vchi_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; |
| } |