| /* |
| * intel_sst_pvt.c - Intel SST Driver for audio engine |
| * |
| * Copyright (C) 2008-10 Intel Corp |
| * Authors: Vinod Koul <vinod.koul@intel.com> |
| * Harsha Priya <priya.harsha@intel.com> |
| * Dharageswari R <dharageswari.r@intel.com> |
| * KP Jeeja <jeeja.kp@intel.com> |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * 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 <linux/pci.h> |
| #include <linux/fs.h> |
| #include <linux/firmware.h> |
| #include <linux/sched.h> |
| #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; |
| } |
| |