/* * intel_sst_stream.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 file contains the stream operations of SST driver */ #include #include #include #include #include #ifdef CONFIG_MRST_RAR_HANDLER #include "../../../drivers/staging/memrar/memrar.h" #endif #include "intel_sst_ioctl.h" #include "intel_sst.h" #include "intel_sst_fw_ipc.h" #include "intel_sst_common.h" /** * sst_get_stream_params - Send msg to query for stream parameters * @str_id: stream id for which the parameters are queried for * @get_params: out parameters to which the parameters are copied to * * This function is called when the stream parameters are queiried for */ int sst_get_stream_params(int str_id, struct snd_sst_get_stream_params *get_params) { int retval = 0; struct ipc_post *msg = NULL; struct stream_info *str_info; struct snd_sst_fw_get_stream_params *fw_params; pr_debug("sst: get_stream for %d\n", str_id); retval = sst_validate_strid(str_id); if (retval) return retval; str_info = &sst_drv_ctx->streams[str_id]; if (str_info->status != STREAM_UN_INIT) { if (str_info->ctrl_blk.on == true) { pr_err("sst: control path in use\n"); return -EINVAL; } if (sst_create_short_msg(&msg)) { pr_err("sst: message creation failed\n"); return -ENOMEM; } fw_params = kzalloc(sizeof(*fw_params), GFP_ATOMIC); if (!fw_params) { pr_err("sst: mem allcoation failed\n "); kfree(msg); return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_GET_STREAM_PARAMS, 0, str_id); str_info->ctrl_blk.condition = false; str_info->ctrl_blk.ret_code = 0; str_info->ctrl_blk.on = true; str_info->ctrl_blk.data = (void *) fw_params; 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, &str_info->ctrl_blk, SST_BLOCK_TIMEOUT); if (retval) { get_params->codec_params.result = retval; kfree(fw_params); return -EIO; } memcpy(&get_params->pcm_params, &fw_params->pcm_params, sizeof(fw_params->pcm_params)); memcpy(&get_params->codec_params.sparams, &fw_params->codec_params, sizeof(fw_params->codec_params)); get_params->codec_params.result = 0; get_params->codec_params.stream_id = str_id; get_params->codec_params.codec = str_info->codec; get_params->codec_params.ops = str_info->ops; get_params->codec_params.stream_type = str_info->str_type; kfree(fw_params); } else { pr_debug("sst: Stream is not in the init state\n"); } return retval; } /** * sst_set_stream_param - Send msg for setting stream parameters * * @str_id: stream id * @str_param: stream params * * This function sets stream params during runtime */ int sst_set_stream_param(int str_id, struct snd_sst_params *str_param) { int retval = 0; struct ipc_post *msg = NULL; struct stream_info *str_info; BUG_ON(!str_param); if (sst_drv_ctx->streams[str_id].ops != str_param->ops) { pr_err("sst: Invalid operation\n"); return -EINVAL; } retval = sst_validate_strid(str_id); if (retval) return retval; pr_debug("sst: set_stream for %d\n", str_id); str_info = &sst_drv_ctx->streams[str_id]; if (sst_drv_ctx->streams[str_id].status == STREAM_INIT) { if (str_info->ctrl_blk.on == true) { pr_err("sst: control path in use\n"); return -EAGAIN; } if (sst_create_large_msg(&msg)) return -ENOMEM; sst_fill_header(&msg->header, IPC_IA_SET_STREAM_PARAMS, 1, str_id); str_info->ctrl_blk.condition = false; str_info->ctrl_blk.ret_code = 0; str_info->ctrl_blk.on = true; msg->header.part.data = sizeof(u32) + sizeof(str_param->sparams); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), &str_param->sparams, sizeof(str_param->sparams)); 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, &str_info->ctrl_blk, SST_BLOCK_TIMEOUT); if (retval < 0) { retval = -EIO; sst_clean_stream(str_info); } } else { retval = -EBADRQC; pr_err("sst: BADQRC for stream\n"); } return retval; } /** * sst_get_vol - This fuction allows to get the premix gain or gain of a stream * * @get_vol: this is an output param through which the volume * structure is passed back to user * * This function is called when the premix gain or stream gain is queried for */ int sst_get_vol(struct snd_sst_vol *get_vol) { int retval = 0; struct ipc_post *msg = NULL; struct snd_sst_vol *fw_get_vol; int str_id = get_vol->stream_id; pr_debug("sst: get vol called\n"); if (sst_create_short_msg(&msg)) return -ENOMEM; sst_fill_header(&msg->header, IPC_IA_GET_STREAM_VOL, 0, str_id); sst_drv_ctx->vol_info_blk.condition = false; sst_drv_ctx->vol_info_blk.ret_code = 0; sst_drv_ctx->vol_info_blk.on = true; fw_get_vol = kzalloc(sizeof(*fw_get_vol), GFP_ATOMIC); if (!fw_get_vol) { pr_err("sst: mem allocation failed\n"); kfree(msg); return -ENOMEM; } sst_drv_ctx->vol_info_blk.data = (void *)fw_get_vol; 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->vol_info_blk, SST_BLOCK_TIMEOUT); if (retval) retval = -EIO; else { pr_debug("sst: stream id %d\n", fw_get_vol->stream_id); pr_debug("sst: volume %d\n", fw_get_vol->volume); pr_debug("sst: ramp duration %d\n", fw_get_vol->ramp_duration); pr_debug("sst: ramp_type %d\n", fw_get_vol->ramp_type); memcpy(get_vol, fw_get_vol, sizeof(*fw_get_vol)); } return retval; } /** * sst_set_vol - This fuction allows to set the premix gain or gain of a stream * * @set_vol: this holds the volume structure that needs to be set * * This function is called when premix gain or stream gain is requested to be set */ int sst_set_vol(struct snd_sst_vol *set_vol) { int retval = 0; struct ipc_post *msg = NULL; pr_debug("sst: set vol called\n"); if (sst_create_large_msg(&msg)) { pr_err("sst: message creation failed\n"); return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_SET_STREAM_VOL, 1, set_vol->stream_id); msg->header.part.data = sizeof(u32) + sizeof(*set_vol); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), set_vol, sizeof(*set_vol)); sst_drv_ctx->vol_info_blk.condition = false; sst_drv_ctx->vol_info_blk.ret_code = 0; sst_drv_ctx->vol_info_blk.on = true; sst_drv_ctx->vol_info_blk.data = set_vol; 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->vol_info_blk, SST_BLOCK_TIMEOUT); if (retval) { pr_err("sst: error in set_vol = %d\n", retval); retval = -EIO; } return retval; } /** * sst_set_mute - This fuction sets premix mute or soft mute of a stream * * @set_mute: this holds the mute structure that needs to be set * * This function is called when premix mute or stream mute requested to be set */ int sst_set_mute(struct snd_sst_mute *set_mute) { int retval = 0; struct ipc_post *msg = NULL; pr_debug("sst: set mute called\n"); if (sst_create_large_msg(&msg)) { pr_err("sst: message creation failed\n"); return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_SET_STREAM_MUTE, 1, set_mute->stream_id); sst_drv_ctx->mute_info_blk.condition = false; sst_drv_ctx->mute_info_blk.ret_code = 0; sst_drv_ctx->mute_info_blk.on = true; sst_drv_ctx->mute_info_blk.data = set_mute; msg->header.part.data = sizeof(u32) + sizeof(*set_mute); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), set_mute, sizeof(*set_mute)); 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->mute_info_blk, SST_BLOCK_TIMEOUT); if (retval) { pr_err("sst: error in set_mute = %d\n", retval); retval = -EIO; } return retval; } int sst_prepare_target(struct snd_sst_slot_info *slot) { if (slot->target_device == SND_SST_TARGET_PMIC && slot->device_instance == 1) { /*music mode*/ if (sst_drv_ctx->pmic_port_instance == 0) sst_drv_ctx->scard_ops->set_voice_port( DEACTIVATE); } else if ((slot->target_device == SND_SST_TARGET_PMIC || slot->target_device == SND_SST_TARGET_MODEM) && slot->device_instance == 0) { /*voip mode where pcm0 is active*/ if (sst_drv_ctx->pmic_port_instance == 1) sst_drv_ctx->scard_ops->set_audio_port( DEACTIVATE); } return 0; } int sst_activate_target(struct snd_sst_slot_info *slot) { if (slot->target_device == SND_SST_TARGET_PMIC && slot->device_instance == 1) { /*music mode*/ sst_drv_ctx->pmic_port_instance = 1; sst_drv_ctx->scard_ops->set_audio_port(ACTIVATE); sst_drv_ctx->scard_ops->set_pcm_audio_params( slot->pcm_params.sfreq, slot->pcm_params.pcm_wd_sz, slot->pcm_params.num_chan); if (sst_drv_ctx->pb_streams) sst_drv_ctx->scard_ops->power_up_pmic_pb(1); if (sst_drv_ctx->cp_streams) sst_drv_ctx->scard_ops->power_up_pmic_cp(1); } else if ((slot->target_device == SND_SST_TARGET_PMIC || slot->target_device == SND_SST_TARGET_MODEM) && slot->device_instance == 0) { /*voip mode where pcm0 is active*/ sst_drv_ctx->pmic_port_instance = 0; sst_drv_ctx->scard_ops->set_voice_port( ACTIVATE); sst_drv_ctx->scard_ops->power_up_pmic_pb(0); /*sst_drv_ctx->scard_ops->power_up_pmic_cp(0);*/ } return 0; } int sst_parse_target(struct snd_sst_slot_info *slot) { int retval = 0; if (slot->action == SND_SST_PORT_ACTIVATE && slot->device_type == SND_SST_DEVICE_PCM) { retval = sst_activate_target(slot); if (retval) pr_err("sst: SST_Activate_target_fail\n"); else pr_err("sst: SST_Activate_target_pass\n"); return retval; } else if (slot->action == SND_SST_PORT_PREPARE && slot->device_type == SND_SST_DEVICE_PCM) { retval = sst_prepare_target(slot); if (retval) pr_err("sst: SST_prepare_target_fail\n"); else pr_err("sst: SST_prepare_target_pass\n"); return retval; } else { pr_err("sst: slot_action : %d, device_type: %d\n", slot->action, slot->device_type); return retval; } } int sst_send_target(struct snd_sst_target_device *target) { int retval; struct ipc_post *msg; if (sst_create_large_msg(&msg)) { pr_err("sst: message creation failed\n"); return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_TARGET_DEV_SELECT, 1, 0); sst_drv_ctx->tgt_dev_blk.condition = false; sst_drv_ctx->tgt_dev_blk.ret_code = 0; sst_drv_ctx->tgt_dev_blk.on = true; msg->header.part.data = sizeof(u32) + sizeof(*target); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), target, sizeof(*target)); 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); pr_debug("sst: message sent- waiting\n"); retval = sst_wait_interruptible_timeout(sst_drv_ctx, &sst_drv_ctx->tgt_dev_blk, TARGET_DEV_BLOCK_TIMEOUT); if (retval) pr_err("sst: target device ipc failed = 0x%x\n", retval); return retval; } int sst_target_device_validate(struct snd_sst_target_device *target) { int retval = 0; int i; for (i = 0; i < SST_MAX_TARGET_DEVICES; i++) { if (target->devices[i].device_type == SND_SST_DEVICE_PCM) { /*pcm device, check params*/ if (target->devices[i].device_instance == 1) { if ((target->devices[i].device_mode != SND_SST_DEV_MODE_PCM_MODE4_I2S) && (target->devices[i].device_mode != SND_SST_DEV_MODE_PCM_MODE4_RIGHT_JUSTIFIED) && (target->devices[i].device_mode != SND_SST_DEV_MODE_PCM_MODE1)) goto err; } else if (target->devices[i].device_instance == 0) { if ((target->devices[i].device_mode != SND_SST_DEV_MODE_PCM_MODE2) && (target->devices[i].device_mode != SND_SST_DEV_MODE_PCM_MODE4_I2S) && (target->devices[i].device_mode != SND_SST_DEV_MODE_PCM_MODE1)) goto err; if (target->devices[i].pcm_params.sfreq != 8000 || target->devices[i].pcm_params.num_chan != 1 || target->devices[i].pcm_params.pcm_wd_sz != 16) goto err; } else { err: pr_err("sst: i/p params incorrect\n"); return -EINVAL; } } } return retval; } /** * sst_target_device_select - This fuction sets the target device configurations * * @target: this parameter holds the configurations to be set * * This function is called when the user layer wants to change the target * device's configurations */ int sst_target_device_select(struct snd_sst_target_device *target) { int retval, i, prepare_count = 0; pr_debug("sst: Target Device Select\n"); if (target->device_route < 0 || target->device_route > 2) { pr_err("sst: device route is invalid\n"); return -EINVAL; } if (target->device_route != 0) { pr_err("sst: Unsupported config\n"); return -EIO; } retval = sst_target_device_validate(target); if (retval) return retval; retval = sst_send_target(target); if (retval) return retval; for (i = 0; i < SST_MAX_TARGET_DEVICES; i++) { if (target->devices[i].action == SND_SST_PORT_ACTIVATE) { pr_debug("sst: activate called in %d\n", i); retval = sst_parse_target(&target->devices[i]); if (retval) return retval; } else if (target->devices[i].action == SND_SST_PORT_PREPARE) { pr_debug("sst: PREPARE in %d, Forwading\n", i); retval = sst_parse_target(&target->devices[i]); if (retval) { pr_err("sst: Parse Target fail %d", retval); return retval; } pr_debug("sst: Parse Target successful %d", retval); if (target->devices[i].device_type == SND_SST_DEVICE_PCM) prepare_count++; } } if (target->devices[0].action == SND_SST_PORT_PREPARE && prepare_count == 0) sst_drv_ctx->scard_ops->power_down_pmic(); return retval; } #ifdef CONFIG_MRST_RAR_HANDLER /*This function gets the physical address of the secure memory from the handle*/ static inline int sst_get_RAR(struct RAR_buffer *buffers, int count) { int retval = 0, rar_status = 0; rar_status = rar_handle_to_bus(buffers, count); if (count != rar_status) { pr_err("sst: The rar CALL Failed"); retval = -EIO; } if (buffers->info.type != RAR_TYPE_AUDIO) { pr_err("sst: Invalid RAR type\n"); return -EINVAL; } return retval; } #endif /* This function creates the scatter gather list to be sent to firmware to capture/playback data*/ static int sst_create_sg_list(struct stream_info *stream, struct sst_frame_info *sg_list) { struct sst_stream_bufs *kbufs = NULL; #ifdef CONFIG_MRST_RAR_HANDLER struct RAR_buffer rar_buffers; int retval = 0; #endif int i = 0; list_for_each_entry(kbufs, &stream->bufs, node) { if (kbufs->in_use == false) { #ifdef CONFIG_MRST_RAR_HANDLER if (stream->ops == STREAM_OPS_PLAYBACK_DRM) { pr_debug("sst: DRM playback handling\n"); rar_buffers.info.handle = (__u32)kbufs->addr; rar_buffers.info.size = kbufs->size; pr_debug("sst: rar handle 0x%x size=0x%x", rar_buffers.info.handle, rar_buffers.info.size); retval = sst_get_RAR(&rar_buffers, 1); if (retval) return retval; sg_list->addr[i].addr = rar_buffers.bus_address; /* rar_buffers.info.size; */ sg_list->addr[i].size = (__u32)kbufs->size; pr_debug("sst: phyaddr[%d] 0x%x Size:0x%x\n" , i, sg_list->addr[i].addr, sg_list->addr[i].size); } #endif if (stream->ops != STREAM_OPS_PLAYBACK_DRM) { sg_list->addr[i].addr = virt_to_phys((void *) kbufs->addr + kbufs->offset); sg_list->addr[i].size = kbufs->size; pr_debug("sst: phyaddr[%d]:0x%x Size:0x%x\n" , i , sg_list->addr[i].addr, kbufs->size); } stream->curr_bytes += sg_list->addr[i].size; kbufs->in_use = true; i++; } if (i >= MAX_NUM_SCATTER_BUFFERS) break; } sg_list->num_entries = i; pr_debug("sst:sg list entries = %d\n", sg_list->num_entries); return i; } /** * sst_play_frame - Send msg for sending stream frames * * @str_id: ID of stream * * This function is called to send data to be played out * to the firmware */ int sst_play_frame(int str_id) { int i = 0, retval = 0; struct ipc_post *msg = NULL; struct sst_frame_info sg_list = {0}; struct sst_stream_bufs *kbufs = NULL, *_kbufs; struct stream_info *stream; pr_debug("sst: play frame for %d\n", str_id); retval = sst_validate_strid(str_id); if (retval) return retval; stream = &sst_drv_ctx->streams[str_id]; /* clear prev sent buffers */ list_for_each_entry_safe(kbufs, _kbufs, &stream->bufs, node) { if (kbufs->in_use == true) { spin_lock(&stream->pcm_lock); list_del(&kbufs->node); spin_unlock(&stream->pcm_lock); kfree(kbufs); } } /* update bytes sent */ stream->cumm_bytes += stream->curr_bytes; stream->curr_bytes = 0; if (list_empty(&stream->bufs)) { /* no user buffer available */ pr_debug("sst: Null buffer stream status %d\n", stream->status); stream->prev = stream->status; stream->status = STREAM_INIT; pr_debug("sst:new stream status = %d\n", stream->status); if (stream->need_draining == true) { pr_debug("sst:draining stream\n"); if (sst_create_short_msg(&msg)) { pr_err("sst: mem alloc failed\n"); return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_DRAIN_STREAM, 0, str_id); 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); } else if (stream->data_blk.on == true) { pr_debug("sst:user list empty.. wake\n"); /* unblock */ stream->data_blk.ret_code = 0; stream->data_blk.condition = true; stream->data_blk.on = false; wake_up(&sst_drv_ctx->wait_queue); } return 0; } /* create list */ i = sst_create_sg_list(stream, &sg_list); /* post msg */ if (sst_create_large_msg(&msg)) return -ENOMEM; sst_fill_header(&msg->header, IPC_IA_PLAY_FRAMES, 1, str_id); msg->header.part.data = sizeof(u32) + sizeof(sg_list); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), &sg_list, sizeof(sg_list)); 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); return 0; } /** * sst_capture_frame - Send msg for sending stream frames * * @str_id: ID of stream * * This function is called to capture data from the firmware */ int sst_capture_frame(int str_id) { int i = 0, retval = 0; struct ipc_post *msg = NULL; struct sst_frame_info sg_list = {0}; struct sst_stream_bufs *kbufs = NULL, *_kbufs; struct stream_info *stream; pr_debug("sst:capture frame for %d\n", str_id); retval = sst_validate_strid(str_id); if (retval) return retval; stream = &sst_drv_ctx->streams[str_id]; /* clear prev sent buffers */ list_for_each_entry_safe(kbufs, _kbufs, &stream->bufs, node) { if (kbufs->in_use == true) { list_del(&kbufs->node); kfree(kbufs); pr_debug("sst:del node\n"); } } if (list_empty(&stream->bufs)) { /* no user buffer available */ pr_debug("sst:Null buffer!!!!stream status %d\n", stream->status); stream->prev = stream->status; stream->status = STREAM_INIT; pr_debug("sst:new stream status = %d\n", stream->status); if (stream->data_blk.on == true) { pr_debug("sst:user list empty.. wake\n"); /* unblock */ stream->data_blk.ret_code = 0; stream->data_blk.condition = true; stream->data_blk.on = false; wake_up(&sst_drv_ctx->wait_queue); } return 0; } /* create new sg list */ i = sst_create_sg_list(stream, &sg_list); /* post msg */ if (sst_create_large_msg(&msg)) return -ENOMEM; sst_fill_header(&msg->header, IPC_IA_CAPT_FRAMES, 1, str_id); msg->header.part.data = sizeof(u32) + sizeof(sg_list); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), &sg_list, sizeof(sg_list)); 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); /*update bytes recevied*/ stream->cumm_bytes += stream->curr_bytes; stream->curr_bytes = 0; pr_debug("sst:Cum bytes = %d\n", stream->cumm_bytes); return 0; } /*This function is used to calculate the minimum size of input buffers given*/ static unsigned int calculate_min_size(struct snd_sst_buffs *bufs) { int i, min_val = bufs->buff_entry[0].size; for (i = 1 ; i < bufs->entries; i++) { if (bufs->buff_entry[i].size < min_val) min_val = bufs->buff_entry[i].size; } pr_debug("sst:min_val = %d\n", min_val); return min_val; } static unsigned int calculate_max_size(struct snd_sst_buffs *bufs) { int i, max_val = bufs->buff_entry[0].size; for (i = 1 ; i < bufs->entries; i++) { if (bufs->buff_entry[i].size > max_val) max_val = bufs->buff_entry[i].size; } pr_debug("sst:max_val = %d\n", max_val); return max_val; } /*This function is used to allocate input and output buffers to be sent to the firmware that will take encoded data and return decoded data*/ static int sst_allocate_decode_buf(struct stream_info *str_info, struct snd_sst_dbufs *dbufs, unsigned int cum_input_given, unsigned int cum_output_given) { #ifdef CONFIG_MRST_RAR_HANDLER if (str_info->ops == STREAM_OPS_PLAYBACK_DRM) { if (dbufs->ibufs->type == SST_BUF_RAR && dbufs->obufs->type == SST_BUF_RAR) { if (dbufs->ibufs->entries == dbufs->obufs->entries) return 0; else { pr_err("sst: RAR entries dont match\n"); return -EINVAL; } } else str_info->decode_osize = cum_output_given; return 0; } #endif if (!str_info->decode_ibuf) { pr_debug("sst:no i/p buffers, trying full size\n"); str_info->decode_isize = cum_input_given; str_info->decode_ibuf = kzalloc(str_info->decode_isize, GFP_KERNEL); str_info->idecode_alloc = str_info->decode_isize; } if (!str_info->decode_ibuf) { pr_debug("sst:buff alloc failed, try max size\n"); str_info->decode_isize = calculate_max_size(dbufs->ibufs); str_info->decode_ibuf = kzalloc( str_info->decode_isize, GFP_KERNEL); str_info->idecode_alloc = str_info->decode_isize; } if (!str_info->decode_ibuf) { pr_debug("sst:buff alloc failed, try min size\n"); str_info->decode_isize = calculate_min_size(dbufs->ibufs); str_info->decode_ibuf = kzalloc(str_info->decode_isize, GFP_KERNEL); if (!str_info->decode_ibuf) { pr_err("sst: mem allocation failed\n"); return -ENOMEM; } str_info->idecode_alloc = str_info->decode_isize; } str_info->decode_osize = cum_output_given; if (str_info->decode_osize > sst_drv_ctx->mmap_len) str_info->decode_osize = sst_drv_ctx->mmap_len; return 0; } /*This function is used to send the message to firmware to decode the data*/ static int sst_send_decode_mess(int str_id, struct stream_info *str_info, struct snd_sst_decode_info *dec_info) { struct ipc_post *msg = NULL; int retval = 0; pr_debug("SST DBGsst_set_mute:called\n"); if (str_info->decode_ibuf_type == SST_BUF_RAR) { #ifdef CONFIG_MRST_RAR_HANDLER dec_info->frames_in.addr[0].addr = (unsigned long)str_info->decode_ibuf; dec_info->frames_in.addr[0].size = str_info->decode_isize; #endif } else { dec_info->frames_in.addr[0].addr = virt_to_phys((void *) str_info->decode_ibuf); dec_info->frames_in.addr[0].size = str_info->decode_isize; } if (str_info->decode_obuf_type == SST_BUF_RAR) { #ifdef CONFIG_MRST_RAR_HANDLER dec_info->frames_out.addr[0].addr = (unsigned long)str_info->decode_obuf; dec_info->frames_out.addr[0].size = str_info->decode_osize; #endif } else { dec_info->frames_out.addr[0].addr = virt_to_phys((void *) str_info->decode_obuf) ; dec_info->frames_out.addr[0].size = str_info->decode_osize; } dec_info->frames_in.num_entries = 1; dec_info->frames_out.num_entries = 1; dec_info->frames_in.rsrvd = 0; dec_info->frames_out.rsrvd = 0; dec_info->input_bytes_consumed = 0; dec_info->output_bytes_produced = 0; if (sst_create_large_msg(&msg)) { pr_err("sst: message creation failed\n"); return -ENOMEM; } sst_fill_header(&msg->header, IPC_IA_DECODE_FRAMES, 1, str_id); msg->header.part.data = sizeof(u32) + sizeof(*dec_info); memcpy(msg->mailbox_data, &msg->header, sizeof(u32)); memcpy(msg->mailbox_data + sizeof(u32), dec_info, sizeof(*dec_info)); 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); str_info->data_blk.condition = false; str_info->data_blk.ret_code = 0; str_info->data_blk.on = true; str_info->data_blk.data = dec_info; sst_post_message(&sst_drv_ctx->ipc_post_msg_wq); retval = sst_wait_interruptible(sst_drv_ctx, &str_info->data_blk); return retval; } static int sst_prepare_input_buffers_rar(struct stream_info *str_info, struct snd_sst_dbufs *dbufs, int *input_index, int *in_copied, int *input_index_valid_size, int *new_entry_flag) { int retval = 0; #ifdef CONFIG_MRST_RAR_HANDLER int i; if (str_info->ops == STREAM_OPS_PLAYBACK_DRM) { struct RAR_buffer rar_buffers; __u32 info; retval = copy_from_user((void *) &info, dbufs->ibufs->buff_entry[i].buffer, sizeof(__u32)); if (retval) { pr_err("sst:cpy from user fail\n"); return -EAGAIN; } rar_buffers.info.type = dbufs->ibufs->type; rar_buffers.info.size = dbufs->ibufs->buff_entry[i].size; rar_buffers.info.handle = info; pr_debug("rar in DnR(input buffer function)=0x%x size=0x%x", rar_buffers.info.handle, rar_buffers.info.size); retval = sst_get_RAR(&rar_buffers, 1); if (retval) { pr_debug("SST ERR: RAR API failed\n"); return retval; } str_info->decode_ibuf = (void *) ((unsigned long) rar_buffers.bus_address); pr_debug("RAR buf addr in DnR (input buffer function)0x%lu", (unsigned long) str_info->decode_ibuf); pr_debug("rar in DnR decode funtion/output b_add rar =0x%lu", (unsigned long) rar_buffers.bus_address); *input_index = i + 1; str_info->decode_isize = dbufs->ibufs->buff_entry[i].size; str_info->decode_ibuf_type = dbufs->ibufs->type; *in_copied = str_info->decode_isize; } #endif return retval; } /*This function is used to prepare the kernel input buffers with contents before sending for decode*/ static int sst_prepare_input_buffers(struct stream_info *str_info, struct snd_sst_dbufs *dbufs, int *input_index, int *in_copied, int *input_index_valid_size, int *new_entry_flag) { int i, cpy_size, retval = 0; pr_debug("sst:input_index = %d, input entries = %d\n", *input_index, dbufs->ibufs->entries); for (i = *input_index; i < dbufs->ibufs->entries; i++) { #ifdef CONFIG_MRST_RAR_HANDLER retval = sst_prepare_input_buffers_rar(str_info, dbufs, input_index, in_copied, input_index_valid_size, new_entry_flag); if (retval) { pr_err("sst: In prepare input buffers for RAR\n"); return -EIO; } #endif *input_index = i; if (*input_index_valid_size == 0) *input_index_valid_size = dbufs->ibufs->buff_entry[i].size; pr_debug("sst:inout addr = %p, size = %d\n", dbufs->ibufs->buff_entry[i].buffer, *input_index_valid_size); pr_debug("sst:decode_isize = %d, in_copied %d\n", str_info->decode_isize, *in_copied); if (*input_index_valid_size <= (str_info->decode_isize - *in_copied)) cpy_size = *input_index_valid_size; else cpy_size = str_info->decode_isize - *in_copied; pr_debug("sst:cpy size = %d\n", cpy_size); if (!dbufs->ibufs->buff_entry[i].buffer) { pr_err("sst: i/p buffer is null\n"); return -EINVAL; } pr_debug("sst:Try copy To %p, From %p, size %d\n", str_info->decode_ibuf + *in_copied, dbufs->ibufs->buff_entry[i].buffer, cpy_size); retval = copy_from_user((void *)(str_info->decode_ibuf + *in_copied), (void *) dbufs->ibufs->buff_entry[i].buffer, cpy_size); if (retval) { pr_err("sst: copy from user failed\n"); return -EIO; } *in_copied += cpy_size; *input_index_valid_size -= cpy_size; pr_debug("sst:in buff size = %d, in_copied = %d\n", *input_index_valid_size, *in_copied); if (*input_index_valid_size != 0) { pr_debug("sst:more input buffers left\n"); dbufs->ibufs->buff_entry[i].buffer += cpy_size; break; } if (*in_copied == str_info->decode_isize && *input_index_valid_size == 0 && (i+1) <= dbufs->ibufs->entries) { pr_debug("sst:all input buffers copied\n"); *new_entry_flag = true; *input_index = i + 1; break; } } return retval; } /* This function is used to copy the decoded data from kernel buffers to the user output buffers with contents after decode*/ static int sst_prepare_output_buffers(struct stream_info *str_info, struct snd_sst_dbufs *dbufs, int *output_index, int output_size, int *out_copied) { int i, cpy_size, retval = 0; pr_debug("sst:output_index = %d, output entries = %d\n", *output_index, dbufs->obufs->entries); for (i = *output_index; i < dbufs->obufs->entries; i++) { *output_index = i; pr_debug("sst:output addr = %p, size = %d\n", dbufs->obufs->buff_entry[i].buffer, dbufs->obufs->buff_entry[i].size); pr_debug("sst:output_size = %d, out_copied = %d\n", output_size, *out_copied); if (dbufs->obufs->buff_entry[i].size < (output_size - *out_copied)) cpy_size = dbufs->obufs->buff_entry[i].size; else cpy_size = output_size - *out_copied; pr_debug("sst:cpy size = %d\n", cpy_size); pr_debug("sst:Try copy To: %p, From %p, size %d\n", dbufs->obufs->buff_entry[i].buffer, sst_drv_ctx->mmap_mem + *out_copied, cpy_size); retval = copy_to_user(dbufs->obufs->buff_entry[i].buffer, sst_drv_ctx->mmap_mem + *out_copied, cpy_size); if (retval) { pr_err("sst: copy to user failed\n"); return -EIO; } else pr_debug("sst:copy to user passed\n"); *out_copied += cpy_size; dbufs->obufs->buff_entry[i].size -= cpy_size; pr_debug("sst:o/p buff size %d, out_copied %d\n", dbufs->obufs->buff_entry[i].size, *out_copied); if (dbufs->obufs->buff_entry[i].size != 0) { *output_index = i; dbufs->obufs->buff_entry[i].buffer += cpy_size; break; } else if (*out_copied == output_size) { *output_index = i + 1; break; } } return retval; } /** * sst_decode - Send msg for decoding frames * * @str_id: ID of stream * @dbufs: param that holds the user input and output buffers and size * * This function is called to decode data from the firmware */ int sst_decode(int str_id, struct snd_sst_dbufs *dbufs) { int retval = 0, i; unsigned long long total_input = 0 , total_output = 0; unsigned int cum_input_given = 0 , cum_output_given = 0; int copy_in_done = false, copy_out_done = false; int input_index = 0, output_index = 0; int input_index_valid_size = 0; int in_copied, out_copied; int new_entry_flag; u64 output_size; struct stream_info *str_info; struct snd_sst_decode_info dec_info; unsigned long long input_bytes, output_bytes; sst_drv_ctx->scard_ops->power_down_pmic(); pr_debug("sst: Powering_down_PMIC...\n"); retval = sst_validate_strid(str_id); if (retval) return retval; str_info = &sst_drv_ctx->streams[str_id]; if (str_info->status != STREAM_INIT) { pr_err("sst: invalid stream state = %d\n", str_info->status); return -EINVAL; } str_info->prev = str_info->status; str_info->status = STREAM_DECODE; for (i = 0; i < dbufs->ibufs->entries; i++) cum_input_given += dbufs->ibufs->buff_entry[i].size; for (i = 0; i < dbufs->obufs->entries; i++) cum_output_given += dbufs->obufs->buff_entry[i].size; /* input and output buffer allocation */ retval = sst_allocate_decode_buf(str_info, dbufs, cum_input_given, cum_output_given); if (retval) { pr_err("sst: mem allocation failed, abort!!!\n"); retval = -ENOMEM; goto finish; } str_info->decode_isize = str_info->idecode_alloc; str_info->decode_ibuf_type = dbufs->ibufs->type; str_info->decode_obuf_type = dbufs->obufs->type; while ((copy_out_done == false) && (copy_in_done == false)) { in_copied = 0; new_entry_flag = false; retval = sst_prepare_input_buffers(str_info,\ dbufs, &input_index, &in_copied, &input_index_valid_size, &new_entry_flag); if (retval) { pr_err("sst: prepare in buffers failed\n"); goto finish; } if (str_info->ops != STREAM_OPS_PLAYBACK_DRM) str_info->decode_obuf = sst_drv_ctx->mmap_mem; #ifdef CONFIG_MRST_RAR_HANDLER else { if (dbufs->obufs->type == SST_BUF_RAR) { struct RAR_buffer rar_buffers; __u32 info; pr_debug("DRM"); retval = copy_from_user((void *) &info, dbufs->obufs-> buff_entry[output_index].buffer, sizeof(__u32)); rar_buffers.info.size = dbufs->obufs-> buff_entry[output_index].size; rar_buffers.info.handle = info; retval = sst_get_RAR(&rar_buffers, 1); if (retval) return retval; str_info->decode_obuf = (void *)((unsigned long) rar_buffers.bus_address); str_info->decode_osize = dbufs->obufs-> buff_entry[output_index].size; str_info->decode_obuf_type = dbufs->obufs->type; pr_debug("sst:DRM handling\n"); pr_debug("o/p_add=0x%lu Size=0x%x", (unsigned long) str_info->decode_obuf, str_info->decode_osize); } else { str_info->decode_obuf = sst_drv_ctx->mmap_mem; str_info->decode_osize = dbufs->obufs-> buff_entry[output_index].size; } } #endif if (str_info->ops != STREAM_OPS_PLAYBACK_DRM) { if (str_info->decode_isize > in_copied) { str_info->decode_isize = in_copied; pr_debug("sst:i/p size = %d\n", str_info->decode_isize); } } retval = sst_send_decode_mess(str_id, str_info, &dec_info); if (retval || dec_info.input_bytes_consumed == 0) { pr_err( "SST ERR: mess failed or no input consumed\n"); goto finish; } input_bytes = dec_info.input_bytes_consumed; output_bytes = dec_info.output_bytes_produced; pr_debug("sst:in_copied=%d, con=%lld, prod=%lld\n", in_copied, input_bytes, output_bytes); if (dbufs->obufs->type == SST_BUF_RAR) { output_index += 1; if (output_index == dbufs->obufs->entries) { copy_in_done = true; pr_debug("sst:all i/p cpy done\n"); } total_output += output_bytes; } else { out_copied = 0; output_size = output_bytes; retval = sst_prepare_output_buffers(str_info, dbufs, &output_index, output_size, &out_copied); if (retval) { pr_err("sst:prep out buff fail\n"); goto finish; } if (str_info->ops != STREAM_OPS_PLAYBACK_DRM) { if (in_copied != input_bytes) { int bytes_left = in_copied - input_bytes; pr_debug("sst:bytes %d\n", bytes_left); if (new_entry_flag == true) input_index--; while (bytes_left) { struct snd_sst_buffs *ibufs; struct snd_sst_buff_entry *buff_entry; unsigned int size_sent; ibufs = dbufs->ibufs; buff_entry = &ibufs->buff_entry[input_index]; size_sent = buff_entry->size -\ input_index_valid_size; if (bytes_left == size_sent) { bytes_left = 0; } else if (bytes_left < size_sent) { buff_entry->buffer += (size_sent - bytes_left); buff_entry->size -= (size_sent - bytes_left); bytes_left = 0; } else { bytes_left -= size_sent; input_index--; input_index_valid_size = 0; } } } } total_output += out_copied; if (str_info->decode_osize != out_copied) { str_info->decode_osize -= out_copied; pr_debug("sst:output size modified = %d\n", str_info->decode_osize); } } total_input += input_bytes; if (str_info->ops == STREAM_OPS_PLAYBACK_DRM) { if (total_input == cum_input_given) copy_in_done = true; copy_out_done = true; } else { if (total_output == cum_output_given) { copy_out_done = true; pr_debug("sst:all o/p cpy done\n"); } if (total_input == cum_input_given) { copy_in_done = true; pr_debug("sst:all i/p cpy done\n"); } } pr_debug("sst:copy_out = %d, copy_in = %d\n", copy_out_done, copy_in_done); } finish: dbufs->input_bytes_consumed = total_input; dbufs->output_bytes_produced = total_output; str_info->status = str_info->prev; str_info->prev = STREAM_DECODE; str_info->decode_ibuf = NULL; kfree(str_info->decode_ibuf); return retval; }