/* * sst_ipc.c - Intel SST Driver for audio engine * * Copyright (C) 2008-14 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #include #include #include #include #include #include #include #include #include #include #include #include "../sst-mfld-platform.h" #include "sst.h" #include "../common/sst-dsp.h" struct sst_block *sst_create_block(struct intel_sst_drv *ctx, u32 msg_id, u32 drv_id) { struct sst_block *msg = NULL; dev_dbg(ctx->dev, "Enter\n"); msg = kzalloc(sizeof(*msg), GFP_KERNEL); if (!msg) return NULL; msg->condition = false; msg->on = true; msg->msg_id = msg_id; msg->drv_id = drv_id; spin_lock_bh(&ctx->block_lock); list_add_tail(&msg->node, &ctx->block_list); spin_unlock_bh(&ctx->block_lock); return msg; } /* * while handling the interrupts, we need to check for message status and * then if we are blocking for a message * * here we are unblocking the blocked ones, this is based on id we have * passed and search that for block threads. * We will not find block in two cases * a) when its small message and block in not there, so silently ignore * them * b) when we are actually not able to find the block (bug perhaps) * * Since we have bit of small messages we can spam kernel log with err * print on above so need to keep as debug prints which should be enabled * via dynamic debug while debugging IPC issues */ int sst_wake_up_block(struct intel_sst_drv *ctx, int result, u32 drv_id, u32 ipc, void *data, u32 size) { struct sst_block *block = NULL; dev_dbg(ctx->dev, "Enter\n"); spin_lock_bh(&ctx->block_lock); list_for_each_entry(block, &ctx->block_list, node) { dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id, block->drv_id); if (block->msg_id == ipc && block->drv_id == drv_id) { dev_dbg(ctx->dev, "free up the block\n"); block->ret_code = result; block->data = data; block->size = size; block->condition = true; spin_unlock_bh(&ctx->block_lock); wake_up(&ctx->wait_queue); return 0; } } spin_unlock_bh(&ctx->block_lock); dev_dbg(ctx->dev, "Block not found or a response received for a short msg for ipc %d, drv_id %d\n", ipc, drv_id); return -EINVAL; } int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) { struct sst_block *block = NULL, *__block; dev_dbg(ctx->dev, "Enter\n"); spin_lock_bh(&ctx->block_lock); list_for_each_entry_safe(block, __block, &ctx->block_list, node) { if (block == freed) { pr_debug("pvt_id freed --> %d\n", freed->drv_id); /* toggle the index position of pvt_id */ list_del(&freed->node); spin_unlock_bh(&ctx->block_lock); kfree(freed->data); freed->data = NULL; kfree(freed); return 0; } } spin_unlock_bh(&ctx->block_lock); dev_err(ctx->dev, "block is already freed!!!\n"); return -EINVAL; } int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, struct ipc_post *ipc_msg, bool sync) { struct ipc_post *msg = ipc_msg; union ipc_header_mrfld header; unsigned int loop_count = 0; int retval = 0; unsigned long irq_flags; dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync); spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); if (sync) { while (header.p.header_high.part.busy) { if (loop_count > 25) { dev_err(sst_drv_ctx->dev, "sst: Busy wait failed, cant send this msg\n"); retval = -EBUSY; goto out; } cpu_relax(); loop_count++; header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); } } else { if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { /* queue is empty, nothing to send */ spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); dev_dbg(sst_drv_ctx->dev, "Empty msg queue... NO Action\n"); return 0; } if (header.p.header_high.part.busy) { spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n"); return 0; } /* copy msg from list */ msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, struct ipc_post, node); list_del(&msg->node); } dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n", msg->mrfld_header.p.header_high.full); dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n", msg->mrfld_header.p.header_low_payload); if (msg->mrfld_header.p.header_high.part.large) memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, msg->mailbox_data, msg->mrfld_header.p.header_low_payload); sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); out: spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); kfree(msg->mailbox_data); kfree(msg); return retval; } void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) { union interrupt_reg_mrfld isr; union interrupt_reg_mrfld imr; union ipc_header_mrfld clear_ipc; unsigned long irq_flags; spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); /* write 1 to clear*/ isr.part.busy_interrupt = 1; sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); /* Set IA done bit */ clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); clear_ipc.p.header_high.part.busy = 0; clear_ipc.p.header_high.part.done = 1; clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); /* un mask busy interrupt */ imr.part.busy_interrupt = 0; sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); } /* * process_fw_init - process the FW init msg * * @msg: IPC message mailbox data from FW * * This function processes the FW init msg from FW * marks FW state and prints debug info of loaded FW */ static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, void *msg) { struct ipc_header_fw_init *init = (struct ipc_header_fw_init *)msg; int retval = 0; dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n"); if (init->result) { sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n", init->result); retval = init->result; goto ret; } ret: sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); } static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx, struct ipc_post *msg) { u32 msg_id; int str_id; u32 data_size, i; void *data_offset; struct stream_info *stream; union ipc_header_high msg_high; u32 msg_low, pipe_id; msg_high = msg->mrfld_header.p.header_high; msg_low = msg->mrfld_header.p.header_low_payload; msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); switch (msg_id) { case IPC_SST_PERIOD_ELAPSED_MRFLD: pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); if (str_id > 0) { dev_dbg(sst_drv_ctx->dev, "Period elapsed rcvd for pipe id 0x%x\n", pipe_id); stream = &sst_drv_ctx->streams[str_id]; if (stream->period_elapsed) stream->period_elapsed(stream->pcm_substream); if (stream->compr_cb) stream->compr_cb(stream->compr_cb_param); } break; case IPC_IA_DRAIN_STREAM_MRFLD: pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); if (str_id > 0) { stream = &sst_drv_ctx->streams[str_id]; if (stream->drain_notify) stream->drain_notify(stream->drain_cb_param); } break; case IPC_IA_FW_ASYNC_ERR_MRFLD: dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n"); for (i = 0; i < (data_size/4); i++) print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, 16, 4, data_offset, data_size, false); break; case IPC_IA_FW_INIT_CMPLT_MRFLD: process_fw_init(sst_drv_ctx, data_offset); break; case IPC_IA_BUF_UNDER_RUN_MRFLD: pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); if (str_id > 0) dev_err(sst_drv_ctx->dev, "Buffer under-run for pipe:%#x str_id:%d\n", pipe_id, str_id); break; default: dev_err(sst_drv_ctx->dev, "Unrecognized async msg from FW msg_id %#x\n", msg_id); } } void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, struct ipc_post *msg) { unsigned int drv_id; void *data; union ipc_header_high msg_high; u32 msg_low; struct ipc_dsp_hdr *dsp_hdr; unsigned int cmd_id; msg_high = msg->mrfld_header.p.header_high; msg_low = msg->mrfld_header.p.header_low_payload; dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n", msg->mrfld_header.p.header_high.full, msg->mrfld_header.p.header_low_payload); drv_id = msg_high.part.drv_id; /* Check for async messages first */ if (drv_id == SST_ASYNC_DRV_ID) { /*FW sent async large message*/ process_fw_async_msg(sst_drv_ctx, msg); return; } /* FW sent short error response for an IPC */ if (msg_high.part.result && drv_id && !msg_high.part.large) { /* 32-bit FW error code in msg_low */ dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); sst_wake_up_block(sst_drv_ctx, msg_high.part.result, msg_high.part.drv_id, msg_high.part.msg_id, NULL, 0); return; } /* * Process all valid responses * if it is a large message, the payload contains the size to * copy from mailbox **/ if (msg_high.part.large) { data = kzalloc(msg_low, GFP_KERNEL); if (!data) return; memcpy(data, (void *) msg->mailbox_data, msg_low); /* Copy command id so that we can use to put sst to reset */ dsp_hdr = (struct ipc_dsp_hdr *)data; cmd_id = dsp_hdr->cmd_id; dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id); if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, msg_high.part.drv_id, msg_high.part.msg_id, data, msg_low)) kfree(data); } else { sst_wake_up_block(sst_drv_ctx, msg_high.part.result, msg_high.part.drv_id, msg_high.part.msg_id, NULL, 0); } }