/* * intel_sst_ipc.c - Intel SST Driver for audio engine * * Copyright (C) 2008-10 Intel Corporation * 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 defines all ipc functions */ #include #include #include #include "intel_sst.h" #include "intel_sst_ioctl.h" #include "intel_sst_fw_ipc.h" #include "intel_sst_common.h" /* * sst_send_sound_card_type - send sound card type * * this function sends the sound card type to sst dsp engine */ static void sst_send_sound_card_type(void) { struct ipc_post *msg = NULL; if (sst_create_short_msg(&msg)) return; sst_fill_header(&msg->header, IPC_IA_SET_PMIC_TYPE, 0, 0); msg->header.part.data = sst_drv_ctx->pmic_vendor; 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; } /** * sst_post_message - Posts message to SST * * @work: Pointer to work structure * * This function is called by any component in driver which * wants to send an IPC message. This will post message only if * busy bit is free */ void sst_post_message(struct work_struct *work) { struct ipc_post *msg; union ipc_header header; union interrupt_reg imr; int retval = 0; imr.full = 0; /*To check if LPE is in stalled state.*/ retval = sst_stalled(); if (retval < 0) { pr_err("sst: in stalled state\n"); return; } pr_debug("sst: post message called\n"); spin_lock(&sst_drv_ctx->list_spin_lock); /* check list */ if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { /* list is empty, mask imr */ pr_debug("sst: Empty msg queue... masking\n"); imr.full = readl(sst_drv_ctx->shim + SST_IMRX); imr.part.done_interrupt = 1; /* dummy register for shim workaround */ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); spin_unlock(&sst_drv_ctx->list_spin_lock); return; } /* check busy bit */ header.full = sst_shim_read(sst_drv_ctx->shim, SST_IPCX); if (header.part.busy) { /* busy, unmask */ pr_debug("sst: Busy not free... unmasking\n"); imr.full = readl(sst_drv_ctx->shim + SST_IMRX); imr.part.done_interrupt = 0; /* dummy register for shim workaround */ sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); spin_unlock(&sst_drv_ctx->list_spin_lock); return; } /* copy msg from list */ msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, struct ipc_post, node); list_del(&msg->node); pr_debug("sst: Post message: header = %x\n", msg->header.full); pr_debug("sst: size: = %x\n", msg->header.part.data); if (msg->header.part.large) memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, msg->mailbox_data, msg->header.part.data); /* dummy register for shim workaround */ sst_shim_write(sst_drv_ctx->shim, SST_IPCX, msg->header.full); spin_unlock(&sst_drv_ctx->list_spin_lock); kfree(msg->mailbox_data); kfree(msg); return; } /* * sst_clear_interrupt - clear the SST FW interrupt * * This function clears the interrupt register after the interrupt * bottom half is complete allowing next interrupt to arrive */ void sst_clear_interrupt(void) { union interrupt_reg isr; union interrupt_reg imr; union ipc_header clear_ipc; imr.full = sst_shim_read(sst_drv_ctx->shim, SST_IMRX); isr.full = sst_shim_read(sst_drv_ctx->shim, SST_ISRX); /* write 1 to clear */; isr.part.busy_interrupt = 1; sst_shim_write(sst_drv_ctx->shim, SST_ISRX, isr.full); /* Set IA done bit */ clear_ipc.full = sst_shim_read(sst_drv_ctx->shim, SST_IPCD); clear_ipc.part.busy = 0; clear_ipc.part.done = 1; clear_ipc.part.data = IPC_ACK_SUCCESS; sst_shim_write(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); /* un mask busy interrupt */ imr.part.busy_interrupt = 0; sst_shim_write(sst_drv_ctx->shim, SST_IMRX, imr.full); } /* * process_fw_init - process the FW init msg * * @msg: IPC message from FW * * This function processes the FW init msg from FW * marks FW state and prints debug info of loaded FW */ int process_fw_init(struct sst_ipc_msg_wq *msg) { struct ipc_header_fw_init *init = (struct ipc_header_fw_init *)msg->mailbox; int retval = 0; pr_debug("sst: *** FW Init msg came***\n"); if (init->result) { mutex_lock(&sst_drv_ctx->sst_lock); sst_drv_ctx->sst_state = SST_ERROR; mutex_unlock(&sst_drv_ctx->sst_lock); pr_debug("sst: FW Init failed, Error %x\n", init->result); pr_err("sst: FW Init failed, Error %x\n", init->result); retval = -init->result; return retval; } if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) sst_send_sound_card_type(); mutex_lock(&sst_drv_ctx->sst_lock); sst_drv_ctx->sst_state = SST_FW_RUNNING; mutex_unlock(&sst_drv_ctx->sst_lock); pr_debug("sst: FW Version %x.%x\n", init->fw_version.major, init->fw_version.minor); pr_debug("sst: Build No %x Type %x\n", init->fw_version.build, init->fw_version.type); pr_debug("sst: Build date %s Time %s\n", init->build_info.date, init->build_info.time); sst_wake_up_alloc_block(sst_drv_ctx, FW_DWNL_ID, retval, NULL); return retval; } /** * sst_process_message - Processes message from SST * * @work: Pointer to work structure * * This function is scheduled by ISR * It take a msg from process_queue and does action based on msg */ void sst_process_message(struct work_struct *work) { struct sst_ipc_msg_wq *msg = container_of(work, struct sst_ipc_msg_wq, wq); int str_id = msg->header.part.str_id; pr_debug("sst: IPC process for %x\n", msg->header.full); /* based on msg in list call respective handler */ switch (msg->header.part.msg_id) { case IPC_SST_BUF_UNDER_RUN: case IPC_SST_BUF_OVER_RUN: if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n", str_id); break; } pr_err("sst: Buffer under/overrun for%d\n", msg->header.part.str_id); pr_err("sst: Got Underrun & not to send data...ignore\n"); break; case IPC_SST_GET_PLAY_FRAMES: if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) { struct stream_info *stream ; if (sst_validate_strid(str_id)) { pr_err("sst: strid %d invalid\n", str_id); break; } /* call sst_play_frame */ stream = &sst_drv_ctx->streams[str_id]; pr_debug("sst: sst_play_frames for %d\n", msg->header.part.str_id); mutex_lock(&sst_drv_ctx->streams[str_id].lock); sst_play_frame(msg->header.part.str_id); mutex_unlock(&sst_drv_ctx->streams[str_id].lock); break; } else pr_err("sst: sst_play_frames for Penwell!!\n"); case IPC_SST_GET_CAPT_FRAMES: if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) { struct stream_info *stream; /* call sst_capture_frame */ if (sst_validate_strid(str_id)) { pr_err("sst: str id %d invalid\n", str_id); break; } stream = &sst_drv_ctx->streams[str_id]; pr_debug("sst: sst_capture_frames for %d\n", msg->header.part.str_id); mutex_lock(&stream->lock); if (stream->mmapped == false && stream->src == SST_DRV) { pr_debug("sst: waking up block for copy.\n"); stream->data_blk.ret_code = 0; stream->data_blk.condition = true; stream->data_blk.on = false; wake_up(&sst_drv_ctx->wait_queue); } else sst_capture_frame(msg->header.part.str_id); mutex_unlock(&stream->lock); } else pr_err("sst: sst_play_frames for Penwell!!\n"); break; case IPC_IA_PRINT_STRING: pr_debug("sst: been asked to print something by fw\n"); /* TBD */ break; case IPC_IA_FW_INIT_CMPLT: { /* send next data to FW */ process_fw_init(msg); break; } case IPC_SST_STREAM_PROCESS_FATAL_ERR: if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n", str_id); break; } pr_err("sst: codec fatal error %x stream %d...\n", msg->header.full, msg->header.part.str_id); pr_err("sst: Dropping the stream\n"); sst_drop_stream(msg->header.part.str_id); break; case IPC_IA_LPE_GETTING_STALLED: sst_drv_ctx->lpe_stalled = 1; break; case IPC_IA_LPE_UNSTALLED: sst_drv_ctx->lpe_stalled = 0; break; default: /* Illegal case */ pr_err("sst: Unhandled msg %x header %x\n", msg->header.part.msg_id, msg->header.full); } sst_clear_interrupt(); return; } /** * sst_process_reply - Processes reply message from SST * * @work: Pointer to work structure * * This function is scheduled by ISR * It take a reply msg from response_queue and * does action based on msg */ void sst_process_reply(struct work_struct *work) { struct sst_ipc_msg_wq *msg = container_of(work, struct sst_ipc_msg_wq, wq); int str_id = msg->header.part.str_id; struct stream_info *str_info; switch (msg->header.part.msg_id) { case IPC_IA_TARGET_DEV_SELECT: if (!msg->header.part.data) { sst_drv_ctx->tgt_dev_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); sst_drv_ctx->tgt_dev_blk.ret_code = -msg->header.part.data; } if (sst_drv_ctx->tgt_dev_blk.on == true) { sst_drv_ctx->tgt_dev_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_GET_FW_INFO: { struct snd_sst_fw_info *fw_info = (struct snd_sst_fw_info *)msg->mailbox; if (msg->header.part.large) { int major = fw_info->fw_version.major; int minor = fw_info->fw_version.minor; int build = fw_info->fw_version.build; pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); pr_debug("INFO: ***FW*** = %02d.%02d.%02d\n", major, minor, build); memcpy_fromio(sst_drv_ctx->fw_info_blk.data, ((struct snd_sst_fw_info *)(msg->mailbox)), sizeof(struct snd_sst_fw_info)); sst_drv_ctx->fw_info_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); sst_drv_ctx->fw_info_blk.ret_code = -msg->header.part.data; } if (sst_drv_ctx->fw_info_blk.on == true) { pr_debug("sst: Memcopy succedded\n"); sst_drv_ctx->fw_info_blk.on = false; sst_drv_ctx->fw_info_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; } case IPC_IA_SET_STREAM_MUTE: if (!msg->header.part.data) { pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); sst_drv_ctx->mute_info_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); sst_drv_ctx->mute_info_blk.ret_code = -msg->header.part.data; } if (sst_drv_ctx->mute_info_blk.on == true) { sst_drv_ctx->mute_info_blk.on = false; sst_drv_ctx->mute_info_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_SET_STREAM_VOL: if (!msg->header.part.data) { pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); sst_drv_ctx->vol_info_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); sst_drv_ctx->vol_info_blk.ret_code = -msg->header.part.data; } if (sst_drv_ctx->vol_info_blk.on == true) { sst_drv_ctx->vol_info_blk.on = false; sst_drv_ctx->vol_info_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_GET_STREAM_VOL: if (msg->header.part.large) { pr_debug("sst: Large Msg Received Successfully\n"); pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); memcpy_fromio(sst_drv_ctx->vol_info_blk.data, (void *) msg->mailbox, sizeof(struct snd_sst_vol)); sst_drv_ctx->vol_info_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); sst_drv_ctx->vol_info_blk.ret_code = -msg->header.part.data; } if (sst_drv_ctx->vol_info_blk.on == true) { sst_drv_ctx->vol_info_blk.on = false; sst_drv_ctx->vol_info_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_GET_STREAM_PARAMS: if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n", str_id); break; } str_info = &sst_drv_ctx->streams[str_id]; if (msg->header.part.large) { pr_debug("sst: Get stream large success\n"); memcpy_fromio(str_info->ctrl_blk.data, ((void *)(msg->mailbox)), sizeof(struct snd_sst_fw_get_stream_params)); str_info->ctrl_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); str_info->ctrl_blk.ret_code = -msg->header.part.data; } if (str_info->ctrl_blk.on == true) { str_info->ctrl_blk.on = false; str_info->ctrl_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_DECODE_FRAMES: if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n", str_id); break; } str_info = &sst_drv_ctx->streams[str_id]; if (msg->header.part.large) { pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); memcpy_fromio(str_info->data_blk.data, ((void *)(msg->mailbox)), sizeof(struct snd_sst_decode_info)); str_info->data_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); str_info->data_blk.ret_code = -msg->header.part.data; } if (str_info->data_blk.on == true) { str_info->data_blk.on = false; str_info->data_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_DRAIN_STREAM: if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n", str_id); break; } str_info = &sst_drv_ctx->streams[str_id]; if (!msg->header.part.data) { pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); str_info->ctrl_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); str_info->ctrl_blk.ret_code = -msg->header.part.data; } str_info = &sst_drv_ctx->streams[str_id]; if (str_info->data_blk.on == true) { str_info->data_blk.on = false; str_info->data_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_DROP_STREAM: if (sst_validate_strid(str_id)) { pr_err("sst: str id %d invalid\n", str_id); break; } str_info = &sst_drv_ctx->streams[str_id]; if (msg->header.part.large) { struct snd_sst_drop_response *drop_resp = (struct snd_sst_drop_response *)msg->mailbox; pr_debug("sst: Drop ret bytes %x\n", drop_resp->bytes); str_info->curr_bytes = drop_resp->bytes; str_info->ctrl_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); str_info->ctrl_blk.ret_code = -msg->header.part.data; } if (str_info->ctrl_blk.on == true) { str_info->ctrl_blk.on = false; str_info->ctrl_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_ENABLE_RX_TIME_SLOT: if (!msg->header.part.data) { pr_debug("sst: RX_TIME_SLOT success\n"); sst_drv_ctx->hs_info_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); sst_drv_ctx->hs_info_blk.ret_code = -msg->header.part.data; } if (sst_drv_ctx->hs_info_blk.on == true) { sst_drv_ctx->hs_info_blk.on = false; sst_drv_ctx->hs_info_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_PAUSE_STREAM: case IPC_IA_RESUME_STREAM: case IPC_IA_SET_STREAM_PARAMS: str_info = &sst_drv_ctx->streams[str_id]; if (!msg->header.part.data) { pr_debug("sst: Msg succedded %x\n", msg->header.part.msg_id); str_info->ctrl_blk.ret_code = 0; } else { pr_err("sst: Msg %x reply error %x\n", msg->header.part.msg_id, msg->header.part.data); str_info->ctrl_blk.ret_code = -msg->header.part.data; } if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n", str_id); break; } if (str_info->ctrl_blk.on == true) { str_info->ctrl_blk.on = false; str_info->ctrl_blk.condition = true; wake_up(&sst_drv_ctx->wait_queue); } break; case IPC_IA_FREE_STREAM: if (!msg->header.part.data) { pr_debug("sst: Stream %d freed\n", str_id); } else { pr_err("sst: Free for %d ret error %x\n", str_id, msg->header.part.data); } break; case IPC_IA_ALLOC_STREAM: { /* map to stream, call play */ struct snd_sst_alloc_response *resp = (struct snd_sst_alloc_response *)msg->mailbox; if (resp->str_type.result) pr_err("sst: error alloc stream = %x\n", resp->str_type.result); sst_alloc_stream_response(str_id, resp); break; } case IPC_IA_PLAY_FRAMES: case IPC_IA_CAPT_FRAMES: if (sst_validate_strid(str_id)) { pr_err("sst: stream id %d invalid\n" , str_id); break; } pr_debug("sst: Ack for play/capt frames recived\n"); break; case IPC_IA_PREP_LIB_DNLD: { struct snd_sst_str_type *str_type = (struct snd_sst_str_type *)msg->mailbox; pr_debug("sst: Prep Lib download %x\n", msg->header.part.msg_id); if (str_type->result) pr_err("sst: Prep lib download %x\n", str_type->result); else pr_debug("sst: Can download codec now...\n"); sst_wake_up_alloc_block(sst_drv_ctx, str_id, str_type->result, NULL); break; } case IPC_IA_LIB_DNLD_CMPLT: { struct snd_sst_lib_download_info *resp = (struct snd_sst_lib_download_info *)msg->mailbox; int retval = resp->result; pr_debug("sst: Lib downloaded %x\n", msg->header.part.msg_id); if (resp->result) { pr_err("sst: err in lib dload %x\n", resp->result); } else { pr_debug("sst: Codec download complete...\n"); pr_debug("sst: codec Type %d Ver %d Built %s: %s\n", resp->dload_lib.lib_info.lib_type, resp->dload_lib.lib_info.lib_version, resp->dload_lib.lib_info.b_date, resp->dload_lib.lib_info.b_time); } sst_wake_up_alloc_block(sst_drv_ctx, str_id, retval, NULL); break; } case IPC_IA_GET_FW_VERSION: { struct ipc_header_fw_init *version = (struct ipc_header_fw_init *)msg->mailbox; int major = version->fw_version.major; int minor = version->fw_version.minor; int build = version->fw_version.build; dev_info(&sst_drv_ctx->pci->dev, "INFO: ***LOADED SST FW VERSION*** = %02d.%02d.%02d\n", major, minor, build); break; } case IPC_IA_GET_FW_BUILD_INF: { struct sst_fw_build_info *build = (struct sst_fw_build_info *)msg->mailbox; pr_debug("sst: Build date:%sTime:%s", build->date, build->time); break; } case IPC_IA_SET_PMIC_TYPE: break; case IPC_IA_START_STREAM: pr_debug("sst: reply for START STREAM %x\n", msg->header.full); break; default: /* Illegal case */ pr_err("sst: process reply:default = %x\n", msg->header.full); } sst_clear_interrupt(); return; }