/* * intel_sst_pvt.c - Intel SST Driver for audio engine * * Copyright (C) 2008-10 Intel Corp * Authors: Vinod Koul * Harsha Priya * Dharageswari R * KP Jeeja * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * This driver exposes the audio engine functionalities to the ALSA * and middleware. * * This file contains all private functions */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include "intel_sst.h" #include "intel_sst_ioctl.h" #include "intel_sst_fw_ipc.h" #include "intel_sst_common.h" /* * sst_get_block_stream - get a new block stream * * @sst_drv_ctx: Driver context structure * * This function assigns a block for the calls that dont have stream context yet * the blocks are used for waiting on Firmware's response for any operation * Should be called with stream lock held */ int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx) { int i; for (i = 0; i < MAX_ACTIVE_STREAM; i++) { if (sst_drv_ctx->alloc_block[i].sst_id == BLOCK_UNINIT) { sst_drv_ctx->alloc_block[i].ops_block.condition = false; sst_drv_ctx->alloc_block[i].ops_block.ret_code = 0; sst_drv_ctx->alloc_block[i].sst_id = 0; break; } } if (i == MAX_ACTIVE_STREAM) { pr_err("max alloc_stream reached\n"); i = -EBUSY; /* active stream limit reached */ } return i; } /* * sst_wait_interruptible - wait on event * * @sst_drv_ctx: Driver context * @block: Driver block to wait on * * This function waits without a timeout (and is interruptable) for a * given block event */ int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block) { int retval = 0; if (!wait_event_interruptible(sst_drv_ctx->wait_queue, block->condition)) { /* event wake */ if (block->ret_code < 0) { pr_err("stream failed %d\n", block->ret_code); retval = -EBUSY; } else { pr_debug("event up\n"); retval = 0; } } else { pr_err("signal interrupted\n"); retval = -EINTR; } return retval; } /* * sst_wait_interruptible_timeout - wait on event interruptable * * @sst_drv_ctx: Driver context * @block: Driver block to wait on * @timeout: time for wait on * * This function waits with a timeout value (and is interruptible) on a * given block event */ int sst_wait_interruptible_timeout( struct intel_sst_drv *sst_drv_ctx, struct sst_block *block, int timeout) { int retval = 0; pr_debug("sst_wait_interruptible_timeout - waiting....\n"); if (wait_event_interruptible_timeout(sst_drv_ctx->wait_queue, block->condition, msecs_to_jiffies(timeout))) { if (block->ret_code < 0) pr_err("stream failed %d\n", block->ret_code); else pr_debug("event up\n"); retval = block->ret_code; } else { block->on = false; pr_err("timeout occurred...\n"); /*setting firmware state as uninit so that the firmware will get re-downloaded on next request this is because firmare not responding for 5 sec is equalant to some unrecoverable error of FW sst_drv_ctx->sst_state = SST_UN_INIT;*/ retval = -EBUSY; } return retval; } /* * sst_wait_timeout - wait on event for timeout * * @sst_drv_ctx: Driver context * @block: Driver block to wait on * * This function waits with a timeout value (and is not interruptible) on a * given block event */ int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct stream_alloc_block *block) { int retval = 0; /* NOTE: Observed that FW processes the alloc msg and replies even before the alloc thread has finished execution */ pr_debug("waiting for %x, condition %x\n", block->sst_id, block->ops_block.condition); if (wait_event_interruptible_timeout(sst_drv_ctx->wait_queue, block->ops_block.condition, msecs_to_jiffies(SST_BLOCK_TIMEOUT))) { /* event wake */ pr_debug("Event wake %x\n", block->ops_block.condition); pr_debug("message ret: %d\n", block->ops_block.ret_code); retval = block->ops_block.ret_code; } else { block->ops_block.on = false; pr_err("Wait timed-out %x\n", block->ops_block.condition); /* settign firmware state as uninit so that the firmware will get redownloaded on next request this is because firmare not responding for 5 sec is equalant to some unrecoverable error of FW sst_drv_ctx->sst_state = SST_UN_INIT;*/ retval = -EBUSY; } return retval; } /* * sst_create_large_msg - create a large IPC message * * @arg: ipc message * * this function allocates structures to send a large message to the firmware */ int sst_create_large_msg(struct ipc_post **arg) { struct ipc_post *msg; msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC); if (!msg) { pr_err("kzalloc msg failed\n"); return -ENOMEM; } msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC); if (!msg->mailbox_data) { kfree(msg); pr_err("kzalloc mailbox_data failed"); return -ENOMEM; } *arg = msg; return 0; } /* * sst_create_short_msg - create a short IPC message * * @arg: ipc message * * this function allocates structures to send a short message to the firmware */ int sst_create_short_msg(struct ipc_post **arg) { struct ipc_post *msg; msg = kzalloc(sizeof(*msg), GFP_ATOMIC); if (!msg) { pr_err("kzalloc msg failed\n"); return -ENOMEM; } msg->mailbox_data = NULL; *arg = msg; return 0; } /* * sst_clean_stream - clean the stream context * * @stream: stream structure * * this function resets the stream contexts * should be called in free */ void sst_clean_stream(struct stream_info *stream) { struct sst_stream_bufs *bufs = NULL, *_bufs; stream->status = STREAM_UN_INIT; stream->prev = STREAM_UN_INIT; mutex_lock(&stream->lock); list_for_each_entry_safe(bufs, _bufs, &stream->bufs, node) { list_del(&bufs->node); kfree(bufs); } mutex_unlock(&stream->lock); if (stream->ops != STREAM_OPS_PLAYBACK_DRM) kfree(stream->decode_ibuf); } /* * sst_wake_up_alloc_block - wake up waiting block * * @sst_drv_ctx: Driver context * @sst_id: stream id * @status: status of wakeup * @data: data pointer of wakeup * * This function wakes up a sleeping block event based on the response */ void sst_wake_up_alloc_block(struct intel_sst_drv *sst_drv_ctx, u8 sst_id, int status, void *data) { int i; /* Unblock with retval code */ for (i = 0; i < MAX_ACTIVE_STREAM; i++) { if (sst_id == sst_drv_ctx->alloc_block[i].sst_id) { sst_drv_ctx->alloc_block[i].ops_block.condition = true; sst_drv_ctx->alloc_block[i].ops_block.ret_code = status; sst_drv_ctx->alloc_block[i].ops_block.data = data; wake_up(&sst_drv_ctx->wait_queue); break; } } } /* * sst_enable_rx_timeslot - Send msg to query for stream parameters * @status: rx timeslot to be enabled * * This function is called when the RX timeslot is required to be enabled */ int sst_enable_rx_timeslot(int status) { int retval = 0; struct ipc_post *msg = NULL; if (sst_create_short_msg(&msg)) { pr_err("mem allocation failed\n"); return -ENOMEM; } pr_debug("ipc message sending: ENABLE_RX_TIME_SLOT\n"); sst_fill_header(&msg->header, IPC_IA_ENABLE_RX_TIME_SLOT, 0, 0); msg->header.part.data = status; sst_drv_ctx->hs_info_blk.condition = false; sst_drv_ctx->hs_info_blk.ret_code = 0; sst_drv_ctx->hs_info_blk.on = true; spin_lock(&sst_drv_ctx->list_spin_lock); list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list); spin_unlock(&sst_drv_ctx->list_spin_lock); sst_post_message(&sst_drv_ctx->ipc_post_msg_wq); retval = sst_wait_interruptible_timeout(sst_drv_ctx, &sst_drv_ctx->hs_info_blk, SST_BLOCK_TIMEOUT); return retval; }