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