18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// This file is provided under a dual BSD/GPLv2 license. When using or 48c2ecf20Sopenharmony_ci// redistributing this file, you may do so under either license. 58c2ecf20Sopenharmony_ci// 68c2ecf20Sopenharmony_ci// Copyright(c) 2018 Intel Corporation. All rights reserved. 78c2ecf20Sopenharmony_ci// 88c2ecf20Sopenharmony_ci// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> 98c2ecf20Sopenharmony_ci// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> 108c2ecf20Sopenharmony_ci// Rander Wang <rander.wang@intel.com> 118c2ecf20Sopenharmony_ci// Keyon Jie <yang.jie@linux.intel.com> 128c2ecf20Sopenharmony_ci// 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci/* 158c2ecf20Sopenharmony_ci * Hardware interface for generic Intel audio DSP HDA IP 168c2ecf20Sopenharmony_ci */ 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "../ops.h" 198c2ecf20Sopenharmony_ci#include "hda.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic void hda_dsp_ipc_host_done(struct snd_sof_dev *sdev) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci /* 248c2ecf20Sopenharmony_ci * tell DSP cmd is done - clear busy 258c2ecf20Sopenharmony_ci * interrupt and send reply msg to dsp 268c2ecf20Sopenharmony_ci */ 278c2ecf20Sopenharmony_ci snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, 288c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCT, 298c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCT_BUSY, 308c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCT_BUSY); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci /* unmask BUSY interrupt */ 338c2ecf20Sopenharmony_ci snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 348c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL, 358c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL_BUSY, 368c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL_BUSY); 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic void hda_dsp_ipc_dsp_done(struct snd_sof_dev *sdev) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci /* 428c2ecf20Sopenharmony_ci * set DONE bit - tell DSP we have received the reply msg 438c2ecf20Sopenharmony_ci * from DSP, and processed it, don't send more reply to host 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_ci snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, 468c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCIE, 478c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCIE_DONE, 488c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCIE_DONE); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci /* unmask Done interrupt */ 518c2ecf20Sopenharmony_ci snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 528c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL, 538c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL_DONE, 548c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL_DONE); 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ciint hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci /* send IPC message to DSP */ 608c2ecf20Sopenharmony_ci sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, 618c2ecf20Sopenharmony_ci msg->msg_size); 628c2ecf20Sopenharmony_ci snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, 638c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCI_BUSY); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci return 0; 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_civoid hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct snd_sof_ipc_msg *msg = sdev->msg; 718c2ecf20Sopenharmony_ci struct sof_ipc_reply reply; 728c2ecf20Sopenharmony_ci struct sof_ipc_cmd_hdr *hdr; 738c2ecf20Sopenharmony_ci int ret = 0; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci /* 768c2ecf20Sopenharmony_ci * Sometimes, there is unexpected reply ipc arriving. The reply 778c2ecf20Sopenharmony_ci * ipc belongs to none of the ipcs sent from driver. 788c2ecf20Sopenharmony_ci * In this case, the driver must ignore the ipc. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_ci if (!msg) { 818c2ecf20Sopenharmony_ci dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); 828c2ecf20Sopenharmony_ci return; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci hdr = msg->msg_data; 868c2ecf20Sopenharmony_ci if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) || 878c2ecf20Sopenharmony_ci hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { 888c2ecf20Sopenharmony_ci /* 898c2ecf20Sopenharmony_ci * memory windows are powered off before sending IPC reply, 908c2ecf20Sopenharmony_ci * so we can't read the mailbox for CTX_SAVE and PM_GATE 918c2ecf20Sopenharmony_ci * replies. 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_ci reply.error = 0; 948c2ecf20Sopenharmony_ci reply.hdr.cmd = SOF_IPC_GLB_REPLY; 958c2ecf20Sopenharmony_ci reply.hdr.size = sizeof(reply); 968c2ecf20Sopenharmony_ci memcpy(msg->reply_data, &reply, sizeof(reply)); 978c2ecf20Sopenharmony_ci goto out; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci /* get IPC reply from DSP in the mailbox */ 1018c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, sdev->host_box.offset, &reply, 1028c2ecf20Sopenharmony_ci sizeof(reply)); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (reply.error < 0) { 1058c2ecf20Sopenharmony_ci memcpy(msg->reply_data, &reply, sizeof(reply)); 1068c2ecf20Sopenharmony_ci ret = reply.error; 1078c2ecf20Sopenharmony_ci } else { 1088c2ecf20Sopenharmony_ci /* reply correct size ? */ 1098c2ecf20Sopenharmony_ci if (reply.hdr.size != msg->reply_size && 1108c2ecf20Sopenharmony_ci /* getter payload is never known upfront */ 1118c2ecf20Sopenharmony_ci ((reply.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_PROBE)) { 1128c2ecf20Sopenharmony_ci dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", 1138c2ecf20Sopenharmony_ci msg->reply_size, reply.hdr.size); 1148c2ecf20Sopenharmony_ci ret = -EINVAL; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* read the message */ 1188c2ecf20Sopenharmony_ci if (msg->reply_size > 0) 1198c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, sdev->host_box.offset, 1208c2ecf20Sopenharmony_ci msg->reply_data, msg->reply_size); 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ciout: 1248c2ecf20Sopenharmony_ci msg->reply_error = ret; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci/* IPC handler thread */ 1298c2ecf20Sopenharmony_ciirqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev = context; 1328c2ecf20Sopenharmony_ci u32 hipci; 1338c2ecf20Sopenharmony_ci u32 hipcie; 1348c2ecf20Sopenharmony_ci u32 hipct; 1358c2ecf20Sopenharmony_ci u32 hipcte; 1368c2ecf20Sopenharmony_ci u32 msg; 1378c2ecf20Sopenharmony_ci u32 msg_ext; 1388c2ecf20Sopenharmony_ci bool ipc_irq = false; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci /* read IPC status */ 1418c2ecf20Sopenharmony_ci hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, 1428c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCIE); 1438c2ecf20Sopenharmony_ci hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); 1448c2ecf20Sopenharmony_ci hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI); 1458c2ecf20Sopenharmony_ci hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* is this a reply message from the DSP */ 1488c2ecf20Sopenharmony_ci if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { 1498c2ecf20Sopenharmony_ci msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK; 1508c2ecf20Sopenharmony_ci msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci dev_vdbg(sdev->dev, 1538c2ecf20Sopenharmony_ci "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", 1548c2ecf20Sopenharmony_ci msg, msg_ext); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* mask Done interrupt */ 1578c2ecf20Sopenharmony_ci snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 1588c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL, 1598c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL_DONE, 0); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci /* 1628c2ecf20Sopenharmony_ci * Make sure the interrupt thread cannot be preempted between 1638c2ecf20Sopenharmony_ci * waking up the sender and re-enabling the interrupt. Also 1648c2ecf20Sopenharmony_ci * protect against a theoretical race with sof_ipc_tx_message(): 1658c2ecf20Sopenharmony_ci * if the DSP is fast enough to receive an IPC message, reply to 1668c2ecf20Sopenharmony_ci * it, and the host interrupt processing calls this function on 1678c2ecf20Sopenharmony_ci * a different core from the one, where the sending is taking 1688c2ecf20Sopenharmony_ci * place, the message might not yet be marked as expecting a 1698c2ecf20Sopenharmony_ci * reply. 1708c2ecf20Sopenharmony_ci */ 1718c2ecf20Sopenharmony_ci spin_lock_irq(&sdev->ipc_lock); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci /* handle immediate reply from DSP core */ 1748c2ecf20Sopenharmony_ci hda_dsp_ipc_get_reply(sdev); 1758c2ecf20Sopenharmony_ci snd_sof_ipc_reply(sdev, msg); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* set the done bit */ 1788c2ecf20Sopenharmony_ci hda_dsp_ipc_dsp_done(sdev); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci spin_unlock_irq(&sdev->ipc_lock); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci ipc_irq = true; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci /* is this a new message from DSP */ 1868c2ecf20Sopenharmony_ci if (hipct & HDA_DSP_REG_HIPCT_BUSY) { 1878c2ecf20Sopenharmony_ci msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; 1888c2ecf20Sopenharmony_ci msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci dev_vdbg(sdev->dev, 1918c2ecf20Sopenharmony_ci "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", 1928c2ecf20Sopenharmony_ci msg, msg_ext); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* mask BUSY interrupt */ 1958c2ecf20Sopenharmony_ci snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 1968c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL, 1978c2ecf20Sopenharmony_ci HDA_DSP_REG_HIPCCTL_BUSY, 0); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci /* handle messages from DSP */ 2008c2ecf20Sopenharmony_ci if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { 2018c2ecf20Sopenharmony_ci /* this is a PANIC message !! */ 2028c2ecf20Sopenharmony_ci snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); 2038c2ecf20Sopenharmony_ci } else { 2048c2ecf20Sopenharmony_ci /* normal message - process normally */ 2058c2ecf20Sopenharmony_ci snd_sof_ipc_msgs_rx(sdev); 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci hda_dsp_ipc_host_done(sdev); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci ipc_irq = true; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (!ipc_irq) { 2148c2ecf20Sopenharmony_ci /* 2158c2ecf20Sopenharmony_ci * This interrupt is not shared so no need to return IRQ_NONE. 2168c2ecf20Sopenharmony_ci */ 2178c2ecf20Sopenharmony_ci dev_dbg_ratelimited(sdev->dev, 2188c2ecf20Sopenharmony_ci "nothing to do in IPC IRQ thread\n"); 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci/* Check if an IPC IRQ occurred */ 2258c2ecf20Sopenharmony_cibool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) 2268c2ecf20Sopenharmony_ci{ 2278c2ecf20Sopenharmony_ci bool ret = false; 2288c2ecf20Sopenharmony_ci u32 irq_status; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* store status */ 2318c2ecf20Sopenharmony_ci irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); 2328c2ecf20Sopenharmony_ci dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* invalid message ? */ 2358c2ecf20Sopenharmony_ci if (irq_status == 0xffffffff) 2368c2ecf20Sopenharmony_ci goto out; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci /* IPC message ? */ 2398c2ecf20Sopenharmony_ci if (irq_status & HDA_DSP_ADSPIS_IPC) 2408c2ecf20Sopenharmony_ci ret = true; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ciout: 2438c2ecf20Sopenharmony_ci return ret; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ciint hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci return HDA_DSP_MBOX_UPLINK_OFFSET; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ciint hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) 2528c2ecf20Sopenharmony_ci{ 2538c2ecf20Sopenharmony_ci return SRAM_WINDOW_OFFSET(id); 2548c2ecf20Sopenharmony_ci} 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_civoid hda_ipc_msg_data(struct snd_sof_dev *sdev, 2578c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream, 2588c2ecf20Sopenharmony_ci void *p, size_t sz) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci if (!substream || !sdev->stream_box.size) { 2618c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); 2628c2ecf20Sopenharmony_ci } else { 2638c2ecf20Sopenharmony_ci struct hdac_stream *hstream = substream->runtime->private_data; 2648c2ecf20Sopenharmony_ci struct sof_intel_hda_stream *hda_stream; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci hda_stream = container_of(hstream, 2678c2ecf20Sopenharmony_ci struct sof_intel_hda_stream, 2688c2ecf20Sopenharmony_ci hda_stream.hstream); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci /* The stream might already be closed */ 2718c2ecf20Sopenharmony_ci if (hstream) 2728c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, hda_stream->stream.posn_offset, 2738c2ecf20Sopenharmony_ci p, sz); 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ciint hda_ipc_pcm_params(struct snd_sof_dev *sdev, 2788c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream, 2798c2ecf20Sopenharmony_ci const struct sof_ipc_pcm_params_reply *reply) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct hdac_stream *hstream = substream->runtime->private_data; 2828c2ecf20Sopenharmony_ci struct sof_intel_hda_stream *hda_stream; 2838c2ecf20Sopenharmony_ci /* validate offset */ 2848c2ecf20Sopenharmony_ci size_t posn_offset = reply->posn_offset; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci hda_stream = container_of(hstream, struct sof_intel_hda_stream, 2878c2ecf20Sopenharmony_ci hda_stream.hstream); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci /* check for unaligned offset or overflow */ 2908c2ecf20Sopenharmony_ci if (posn_offset > sdev->stream_box.size || 2918c2ecf20Sopenharmony_ci posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) 2928c2ecf20Sopenharmony_ci return -EINVAL; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", 2978c2ecf20Sopenharmony_ci substream->stream, hda_stream->stream.posn_offset); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci return 0; 3008c2ecf20Sopenharmony_ci} 301