David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 1 | // SPDX-License-Identifier: (GPL-2.0 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 | int ret = 0; |
| 74 | |
| 75 | /* |
| 76 | * Sometimes, there is unexpected reply ipc arriving. The reply |
| 77 | * ipc belongs to none of the ipcs sent from driver. |
| 78 | * In this case, the driver must ignore the ipc. |
| 79 | */ |
| 80 | if (!msg) { |
| 81 | dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); |
| 82 | return; |
| 83 | } |
| 84 | |
| 85 | hdr = msg->msg_data; |
| 86 | if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE)) { |
| 87 | /* |
| 88 | * memory windows are powered off before sending IPC reply, |
| 89 | * so we can't read the mailbox for CTX_SAVE reply. |
| 90 | */ |
| 91 | reply.error = 0; |
| 92 | reply.hdr.cmd = SOF_IPC_GLB_REPLY; |
| 93 | reply.hdr.size = sizeof(reply); |
| 94 | memcpy(msg->reply_data, &reply, sizeof(reply)); |
| 95 | goto out; |
| 96 | } |
| 97 | |
| 98 | /* get IPC reply from DSP in the mailbox */ |
| 99 | sof_mailbox_read(sdev, sdev->host_box.offset, &reply, |
| 100 | sizeof(reply)); |
| 101 | |
| 102 | if (reply.error < 0) { |
| 103 | memcpy(msg->reply_data, &reply, sizeof(reply)); |
| 104 | ret = reply.error; |
| 105 | } else { |
| 106 | /* reply correct size ? */ |
| 107 | if (reply.hdr.size != msg->reply_size) { |
| 108 | dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", |
| 109 | msg->reply_size, reply.hdr.size); |
| 110 | ret = -EINVAL; |
| 111 | } |
| 112 | |
| 113 | /* read the message */ |
| 114 | if (msg->reply_size > 0) |
| 115 | sof_mailbox_read(sdev, sdev->host_box.offset, |
| 116 | msg->reply_data, msg->reply_size); |
| 117 | } |
| 118 | |
| 119 | out: |
| 120 | msg->reply_error = ret; |
| 121 | |
| 122 | } |
| 123 | |
| 124 | static bool hda_dsp_ipc_is_sof(uint32_t msg) |
| 125 | { |
| 126 | return (msg & (HDA_DSP_IPC_PURGE_FW | 0xf << 9)) != msg || |
| 127 | (msg & HDA_DSP_IPC_PURGE_FW) != HDA_DSP_IPC_PURGE_FW; |
| 128 | } |
| 129 | |
| 130 | /* IPC handler thread */ |
| 131 | irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) |
| 132 | { |
| 133 | struct snd_sof_dev *sdev = context; |
| 134 | u32 hipci; |
| 135 | u32 hipcie; |
| 136 | u32 hipct; |
| 137 | u32 hipcte; |
| 138 | u32 msg; |
| 139 | u32 msg_ext; |
| 140 | bool ipc_irq = false; |
| 141 | |
| 142 | /* read IPC status */ |
| 143 | hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, |
| 144 | HDA_DSP_REG_HIPCIE); |
| 145 | hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); |
| 146 | hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI); |
| 147 | hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE); |
| 148 | |
| 149 | /* is this a reply message from the DSP */ |
| 150 | if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { |
| 151 | msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK; |
| 152 | msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK; |
| 153 | |
| 154 | dev_vdbg(sdev->dev, |
| 155 | "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", |
| 156 | msg, msg_ext); |
| 157 | |
| 158 | /* mask Done interrupt */ |
| 159 | snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, |
| 160 | HDA_DSP_REG_HIPCCTL, |
| 161 | HDA_DSP_REG_HIPCCTL_DONE, 0); |
| 162 | |
| 163 | /* |
| 164 | * Make sure the interrupt thread cannot be preempted between |
| 165 | * waking up the sender and re-enabling the interrupt. Also |
| 166 | * protect against a theoretical race with sof_ipc_tx_message(): |
| 167 | * if the DSP is fast enough to receive an IPC message, reply to |
| 168 | * it, and the host interrupt processing calls this function on |
| 169 | * a different core from the one, where the sending is taking |
| 170 | * place, the message might not yet be marked as expecting a |
| 171 | * reply. |
| 172 | */ |
| 173 | spin_lock_irq(&sdev->ipc_lock); |
| 174 | |
| 175 | /* handle immediate reply from DSP core - ignore ROM messages */ |
| 176 | if (hda_dsp_ipc_is_sof(msg)) { |
| 177 | hda_dsp_ipc_get_reply(sdev); |
| 178 | snd_sof_ipc_reply(sdev, msg); |
| 179 | } |
| 180 | |
| 181 | /* wake up sleeper if we are loading code */ |
| 182 | if (sdev->code_loading) { |
| 183 | sdev->code_loading = 0; |
| 184 | wake_up(&sdev->waitq); |
| 185 | } |
| 186 | |
| 187 | /* set the done bit */ |
| 188 | hda_dsp_ipc_dsp_done(sdev); |
| 189 | |
| 190 | spin_unlock_irq(&sdev->ipc_lock); |
| 191 | |
| 192 | ipc_irq = true; |
| 193 | } |
| 194 | |
| 195 | /* is this a new message from DSP */ |
| 196 | if (hipct & HDA_DSP_REG_HIPCT_BUSY) { |
| 197 | msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; |
| 198 | msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; |
| 199 | |
| 200 | dev_vdbg(sdev->dev, |
| 201 | "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", |
| 202 | msg, msg_ext); |
| 203 | |
| 204 | /* mask BUSY interrupt */ |
| 205 | snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, |
| 206 | HDA_DSP_REG_HIPCCTL, |
| 207 | HDA_DSP_REG_HIPCCTL_BUSY, 0); |
| 208 | |
| 209 | /* handle messages from DSP */ |
| 210 | if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { |
| 211 | /* this is a PANIC message !! */ |
| 212 | snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); |
| 213 | } else { |
| 214 | /* normal message - process normally */ |
| 215 | snd_sof_ipc_msgs_rx(sdev); |
| 216 | } |
| 217 | |
| 218 | hda_dsp_ipc_host_done(sdev); |
| 219 | |
| 220 | ipc_irq = true; |
| 221 | } |
| 222 | |
| 223 | if (!ipc_irq) { |
| 224 | /* |
| 225 | * This interrupt is not shared so no need to return IRQ_NONE. |
| 226 | */ |
| 227 | dev_dbg_ratelimited(sdev->dev, |
| 228 | "nothing to do in IPC IRQ thread\n"); |
| 229 | } |
| 230 | |
| 231 | /* re-enable IPC interrupt */ |
| 232 | snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, |
| 233 | HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); |
| 234 | |
| 235 | return IRQ_HANDLED; |
| 236 | } |
| 237 | |
| 238 | /* is this IRQ for ADSP ? - we only care about IPC here */ |
| 239 | irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context) |
| 240 | { |
| 241 | struct snd_sof_dev *sdev = context; |
| 242 | int ret = IRQ_NONE; |
| 243 | u32 irq_status; |
| 244 | |
| 245 | spin_lock(&sdev->hw_lock); |
| 246 | |
| 247 | /* store status */ |
| 248 | irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); |
| 249 | dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status); |
| 250 | |
| 251 | /* invalid message ? */ |
| 252 | if (irq_status == 0xffffffff) |
| 253 | goto out; |
| 254 | |
| 255 | /* IPC message ? */ |
| 256 | if (irq_status & HDA_DSP_ADSPIS_IPC) { |
| 257 | /* disable IPC interrupt */ |
| 258 | snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, |
| 259 | HDA_DSP_REG_ADSPIC, |
| 260 | HDA_DSP_ADSPIC_IPC, 0); |
| 261 | ret = IRQ_WAKE_THREAD; |
| 262 | } |
| 263 | |
| 264 | out: |
| 265 | spin_unlock(&sdev->hw_lock); |
| 266 | return ret; |
| 267 | } |
| 268 | |
| 269 | int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) |
| 270 | { |
| 271 | return HDA_DSP_MBOX_UPLINK_OFFSET; |
| 272 | } |
| 273 | |
| 274 | int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) |
| 275 | { |
| 276 | return SRAM_WINDOW_OFFSET(id); |
| 277 | } |
| 278 | |
| 279 | void hda_ipc_msg_data(struct snd_sof_dev *sdev, |
| 280 | struct snd_pcm_substream *substream, |
| 281 | void *p, size_t sz) |
| 282 | { |
| 283 | if (!substream || !sdev->stream_box.size) { |
| 284 | sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); |
| 285 | } else { |
| 286 | struct hdac_stream *hstream = substream->runtime->private_data; |
| 287 | struct sof_intel_hda_stream *hda_stream; |
| 288 | |
| 289 | hda_stream = container_of(hstream, |
| 290 | struct sof_intel_hda_stream, |
| 291 | hda_stream.hstream); |
| 292 | |
| 293 | /* The stream might already be closed */ |
| 294 | if (hstream) |
| 295 | sof_mailbox_read(sdev, hda_stream->stream.posn_offset, |
| 296 | p, sz); |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | int hda_ipc_pcm_params(struct snd_sof_dev *sdev, |
| 301 | struct snd_pcm_substream *substream, |
| 302 | const struct sof_ipc_pcm_params_reply *reply) |
| 303 | { |
| 304 | struct hdac_stream *hstream = substream->runtime->private_data; |
| 305 | struct sof_intel_hda_stream *hda_stream; |
| 306 | /* validate offset */ |
| 307 | size_t posn_offset = reply->posn_offset; |
| 308 | |
| 309 | hda_stream = container_of(hstream, struct sof_intel_hda_stream, |
| 310 | hda_stream.hstream); |
| 311 | |
| 312 | /* check for unaligned offset or overflow */ |
| 313 | if (posn_offset > sdev->stream_box.size || |
| 314 | posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) |
| 315 | return -EINVAL; |
| 316 | |
| 317 | hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset; |
| 318 | |
| 319 | dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", |
| 320 | substream->stream, hda_stream->stream.posn_offset); |
| 321 | |
| 322 | return 0; |
| 323 | } |