1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 2 // 3 // This file is provided under a dual BSD/GPLv2 license. When using or 4 // redistributing this file, you may do so under either license. 5 // 6 // Copyright(c) 2018 Intel Corporation. All rights reserved. 7 // 8 // Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com> 9 // Ranjani Sridharan <ranjani.sridharan@linux.intel.com> 10 // Rander Wang <rander.wang@intel.com> 11 // Keyon Jie <yang.jie@linux.intel.com> 12 // 13 14 /* 15 * Hardware interface for generic Intel audio DSP HDA IP 16 */ 17 18 #include "../ops.h" 19 #include "hda.h" 20 21 static void hda_dsp_ipc_host_done(struct snd_sof_dev *sdev) 22 { 23 /* 24 * tell DSP cmd is done - clear busy 25 * interrupt and send reply msg to dsp 26 */ 27 snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, 28 HDA_DSP_REG_HIPCT, 29 HDA_DSP_REG_HIPCT_BUSY, 30 HDA_DSP_REG_HIPCT_BUSY); 31 32 /* unmask BUSY interrupt */ 33 snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 34 HDA_DSP_REG_HIPCCTL, 35 HDA_DSP_REG_HIPCCTL_BUSY, 36 HDA_DSP_REG_HIPCCTL_BUSY); 37 } 38 39 static void hda_dsp_ipc_dsp_done(struct snd_sof_dev *sdev) 40 { 41 /* 42 * set DONE bit - tell DSP we have received the reply msg 43 * from DSP, and processed it, don't send more reply to host 44 */ 45 snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, 46 HDA_DSP_REG_HIPCIE, 47 HDA_DSP_REG_HIPCIE_DONE, 48 HDA_DSP_REG_HIPCIE_DONE); 49 50 /* unmask Done interrupt */ 51 snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 52 HDA_DSP_REG_HIPCCTL, 53 HDA_DSP_REG_HIPCCTL_DONE, 54 HDA_DSP_REG_HIPCCTL_DONE); 55 } 56 57 int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) 58 { 59 /* send IPC message to DSP */ 60 sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, 61 msg->msg_size); 62 snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, 63 HDA_DSP_REG_HIPCI_BUSY); 64 65 return 0; 66 } 67 68 void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) 69 { 70 struct snd_sof_ipc_msg *msg = sdev->msg; 71 struct sof_ipc_reply reply; 72 struct sof_ipc_cmd_hdr *hdr; 73 74 /* 75 * Sometimes, there is unexpected reply ipc arriving. The reply 76 * ipc belongs to none of the ipcs sent from driver. 77 * In this case, the driver must ignore the ipc. 78 */ 79 if (!msg) { 80 dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); 81 return; 82 } 83 84 hdr = msg->msg_data; 85 if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) || 86 hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { 87 /* 88 * memory windows are powered off before sending IPC reply, 89 * so we can't read the mailbox for CTX_SAVE and PM_GATE 90 * replies. 91 */ 92 reply.error = 0; 93 reply.hdr.cmd = SOF_IPC_GLB_REPLY; 94 reply.hdr.size = sizeof(reply); 95 memcpy(msg->reply_data, &reply, sizeof(reply)); 96 97 msg->reply_error = 0; 98 } else { 99 snd_sof_ipc_get_reply(sdev); 100 } 101 } 102 103 /* IPC handler thread */ 104 irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) 105 { 106 struct snd_sof_dev *sdev = context; 107 u32 hipci; 108 u32 hipcie; 109 u32 hipct; 110 u32 hipcte; 111 u32 msg; 112 u32 msg_ext; 113 bool ipc_irq = false; 114 115 /* read IPC status */ 116 hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, 117 HDA_DSP_REG_HIPCIE); 118 hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); 119 hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI); 120 hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE); 121 122 /* is this a reply message from the DSP */ 123 if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { 124 msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK; 125 msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK; 126 127 dev_vdbg(sdev->dev, 128 "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", 129 msg, msg_ext); 130 131 /* mask Done interrupt */ 132 snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 133 HDA_DSP_REG_HIPCCTL, 134 HDA_DSP_REG_HIPCCTL_DONE, 0); 135 136 /* 137 * Make sure the interrupt thread cannot be preempted between 138 * waking up the sender and re-enabling the interrupt. Also 139 * protect against a theoretical race with sof_ipc_tx_message(): 140 * if the DSP is fast enough to receive an IPC message, reply to 141 * it, and the host interrupt processing calls this function on 142 * a different core from the one, where the sending is taking 143 * place, the message might not yet be marked as expecting a 144 * reply. 145 */ 146 spin_lock_irq(&sdev->ipc_lock); 147 148 /* handle immediate reply from DSP core */ 149 hda_dsp_ipc_get_reply(sdev); 150 snd_sof_ipc_reply(sdev, msg); 151 152 /* set the done bit */ 153 hda_dsp_ipc_dsp_done(sdev); 154 155 spin_unlock_irq(&sdev->ipc_lock); 156 157 ipc_irq = true; 158 } 159 160 /* is this a new message from DSP */ 161 if (hipct & HDA_DSP_REG_HIPCT_BUSY) { 162 msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; 163 msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; 164 165 dev_vdbg(sdev->dev, 166 "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", 167 msg, msg_ext); 168 169 /* mask BUSY interrupt */ 170 snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, 171 HDA_DSP_REG_HIPCCTL, 172 HDA_DSP_REG_HIPCCTL_BUSY, 0); 173 174 /* handle messages from DSP */ 175 if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { 176 struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; 177 bool non_recoverable = true; 178 179 /* 180 * This is a PANIC message! 181 * 182 * If it is arriving during firmware boot and it is not 183 * the last boot attempt then change the non_recoverable 184 * to false as the DSP might be able to boot in the next 185 * iteration(s) 186 */ 187 if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS && 188 hda->boot_iteration < HDA_FW_BOOT_ATTEMPTS) 189 non_recoverable = false; 190 191 snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext), 192 non_recoverable); 193 } else { 194 /* normal message - process normally */ 195 snd_sof_ipc_msgs_rx(sdev); 196 } 197 198 hda_dsp_ipc_host_done(sdev); 199 200 ipc_irq = true; 201 } 202 203 if (!ipc_irq) { 204 /* 205 * This interrupt is not shared so no need to return IRQ_NONE. 206 */ 207 dev_dbg_ratelimited(sdev->dev, 208 "nothing to do in IPC IRQ thread\n"); 209 } 210 211 return IRQ_HANDLED; 212 } 213 214 /* Check if an IPC IRQ occurred */ 215 bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) 216 { 217 bool ret = false; 218 u32 irq_status; 219 220 /* store status */ 221 irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); 222 dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status); 223 224 /* invalid message ? */ 225 if (irq_status == 0xffffffff) 226 goto out; 227 228 /* IPC message ? */ 229 if (irq_status & HDA_DSP_ADSPIS_IPC) 230 ret = true; 231 232 out: 233 return ret; 234 } 235 236 int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) 237 { 238 return HDA_DSP_MBOX_UPLINK_OFFSET; 239 } 240 241 int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) 242 { 243 return SRAM_WINDOW_OFFSET(id); 244 } 245 246 int hda_ipc_msg_data(struct snd_sof_dev *sdev, 247 struct snd_pcm_substream *substream, 248 void *p, size_t sz) 249 { 250 if (!substream || !sdev->stream_box.size) { 251 sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); 252 } else { 253 struct hdac_stream *hstream = substream->runtime->private_data; 254 struct sof_intel_hda_stream *hda_stream; 255 256 hda_stream = container_of(hstream, 257 struct sof_intel_hda_stream, 258 hext_stream.hstream); 259 260 /* The stream might already be closed */ 261 if (!hstream) 262 return -ESTRPIPE; 263 264 sof_mailbox_read(sdev, hda_stream->sof_intel_stream.posn_offset, p, sz); 265 } 266 267 return 0; 268 } 269 270 int hda_set_stream_data_offset(struct snd_sof_dev *sdev, 271 struct snd_pcm_substream *substream, 272 size_t posn_offset) 273 { 274 struct hdac_stream *hstream = substream->runtime->private_data; 275 struct sof_intel_hda_stream *hda_stream; 276 277 hda_stream = container_of(hstream, struct sof_intel_hda_stream, 278 hext_stream.hstream); 279 280 /* check for unaligned offset or overflow */ 281 if (posn_offset > sdev->stream_box.size || 282 posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) 283 return -EINVAL; 284 285 hda_stream->sof_intel_stream.posn_offset = sdev->stream_box.offset + posn_offset; 286 287 dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", 288 substream->stream, hda_stream->sof_intel_stream.posn_offset); 289 290 return 0; 291 } 292