| 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 |
| 7 | // |
| 8 | // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> |
| 9 | // |
| 10 | // Generic IPC layer that can work over MMIO and SPI/I2C. PHY layer provided |
| 11 | // by platform driver code. |
| 12 | // |
| 13 | |
| 14 | #include <linux/mutex.h> |
| 15 | #include <linux/types.h> |
| 16 | |
| 17 | #include "sof-priv.h" |
| 18 | #include "sof-audio.h" |
| 19 | #include "ops.h" |
| 20 | |
| 21 | /** |
| 22 | * sof_ipc_send_msg - generic function to prepare and send one IPC message |
| 23 | * @sdev: pointer to SOF core device struct |
| 24 | * @msg_data: pointer to a message to send |
| 25 | * @msg_bytes: number of bytes in the message |
| 26 | * @reply_bytes: number of bytes available for the reply. |
| 27 | * The buffer for the reply data is not passed to this |
| 28 | * function, the available size is an information for the |
| 29 | * reply handling functions. |
| 30 | * |
| 31 | * On success the function returns 0, otherwise negative error number. |
| 32 | * |
| 33 | * Note: higher level sdev->ipc->tx_mutex must be held to make sure that |
| 34 | * transfers are synchronized. |
| 35 | */ |
| 36 | int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, |
| 37 | size_t reply_bytes) |
| 38 | { |
| 39 | struct snd_sof_ipc *ipc = sdev->ipc; |
| 40 | struct snd_sof_ipc_msg *msg; |
| 41 | int ret; |
| 42 | |
| 43 | if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE) |
| 44 | return -ENODEV; |
| 45 | |
| 46 | /* |
| 47 | * The spin-lock is needed to protect message objects against other |
| 48 | * atomic contexts. |
| 49 | */ |
| 50 | spin_lock_irq(lock: &sdev->ipc_lock); |
| 51 | |
| 52 | /* initialise the message */ |
| 53 | msg = &ipc->msg; |
| 54 | |
| 55 | /* attach message data */ |
| 56 | msg->msg_data = msg_data; |
| 57 | msg->msg_size = msg_bytes; |
| 58 | |
| 59 | msg->reply_size = reply_bytes; |
| 60 | msg->reply_error = 0; |
| 61 | |
| 62 | sdev->msg = msg; |
| 63 | |
| 64 | ret = snd_sof_dsp_send_msg(sdev, msg); |
| 65 | /* Next reply that we receive will be related to this message */ |
| 66 | if (!ret) |
| 67 | msg->ipc_complete = false; |
| 68 | |
| 69 | spin_unlock_irq(lock: &sdev->ipc_lock); |
| 70 | |
| 71 | return ret; |
| 72 | } |
| 73 | |
| 74 | /* send IPC message from host to DSP */ |
| 75 | int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, |
| 76 | void *reply_data, size_t reply_bytes) |
| 77 | { |
| 78 | if (msg_bytes > ipc->max_payload_size || |
| 79 | reply_bytes > ipc->max_payload_size) |
| 80 | return -ENOBUFS; |
| 81 | |
| 82 | return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, |
| 83 | reply_bytes, false); |
| 84 | } |
| 85 | EXPORT_SYMBOL(sof_ipc_tx_message); |
| 86 | |
| 87 | /* IPC set or get data from host to DSP */ |
| 88 | int sof_ipc_set_get_data(struct snd_sof_ipc *ipc, void *msg_data, |
| 89 | size_t msg_bytes, bool set) |
| 90 | { |
| 91 | return ipc->ops->set_get_data(ipc->sdev, msg_data, msg_bytes, set); |
| 92 | } |
| 93 | EXPORT_SYMBOL(sof_ipc_set_get_data); |
| 94 | |
| 95 | /* |
| 96 | * send IPC message from host to DSP without modifying the DSP state. |
| 97 | * This will be used for IPC's that can be handled by the DSP |
| 98 | * even in a low-power D0 substate. |
| 99 | */ |
| 100 | int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, |
| 101 | void *reply_data, size_t reply_bytes) |
| 102 | { |
| 103 | if (msg_bytes > ipc->max_payload_size || |
| 104 | reply_bytes > ipc->max_payload_size) |
| 105 | return -ENOBUFS; |
| 106 | |
| 107 | return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, |
| 108 | reply_bytes, true); |
| 109 | } |
| 110 | EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); |
| 111 | |
| 112 | /* Generic helper function to retrieve the reply */ |
| 113 | void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev) |
| 114 | { |
| 115 | /* |
| 116 | * Sometimes, there is unexpected reply ipc arriving. The reply |
| 117 | * ipc belongs to none of the ipcs sent from driver. |
| 118 | * In this case, the driver must ignore the ipc. |
| 119 | */ |
| 120 | if (!sdev->msg) { |
| 121 | dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n" ); |
| 122 | return; |
| 123 | } |
| 124 | |
| 125 | sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev); |
| 126 | } |
| 127 | EXPORT_SYMBOL(snd_sof_ipc_get_reply); |
| 128 | |
| 129 | /* handle reply message from DSP */ |
| 130 | void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) |
| 131 | { |
| 132 | struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; |
| 133 | |
| 134 | if (msg->ipc_complete) { |
| 135 | dev_dbg(sdev->dev, |
| 136 | "no reply expected, received 0x%x, will be ignored" , |
| 137 | msg_id); |
| 138 | return; |
| 139 | } |
| 140 | |
| 141 | /* wake up and return the error if we have waiters on this message ? */ |
| 142 | msg->ipc_complete = true; |
| 143 | wake_up(&msg->waitq); |
| 144 | } |
| 145 | EXPORT_SYMBOL(snd_sof_ipc_reply); |
| 146 | |
| 147 | struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) |
| 148 | { |
| 149 | struct snd_sof_ipc *ipc; |
| 150 | struct snd_sof_ipc_msg *msg; |
| 151 | const struct sof_ipc_ops *ops; |
| 152 | |
| 153 | ipc = devm_kzalloc(dev: sdev->dev, size: sizeof(*ipc), GFP_KERNEL); |
| 154 | if (!ipc) |
| 155 | return NULL; |
| 156 | |
| 157 | mutex_init(&ipc->tx_mutex); |
| 158 | ipc->sdev = sdev; |
| 159 | msg = &ipc->msg; |
| 160 | |
| 161 | /* indicate that we aren't sending a message ATM */ |
| 162 | msg->ipc_complete = true; |
| 163 | |
| 164 | init_waitqueue_head(&msg->waitq); |
| 165 | |
| 166 | switch (sdev->pdata->ipc_type) { |
| 167 | #if defined(CONFIG_SND_SOC_SOF_IPC3) |
| 168 | case SOF_IPC_TYPE_3: |
| 169 | ops = &ipc3_ops; |
| 170 | break; |
| 171 | #endif |
| 172 | #if defined(CONFIG_SND_SOC_SOF_IPC4) |
| 173 | case SOF_IPC_TYPE_4: |
| 174 | ops = &ipc4_ops; |
| 175 | break; |
| 176 | #endif |
| 177 | default: |
| 178 | dev_err(sdev->dev, "Not supported IPC version: %d\n" , |
| 179 | sdev->pdata->ipc_type); |
| 180 | return NULL; |
| 181 | } |
| 182 | |
| 183 | /* check for mandatory ops */ |
| 184 | if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) { |
| 185 | dev_err(sdev->dev, "Missing IPC message handling ops\n" ); |
| 186 | return NULL; |
| 187 | } |
| 188 | |
| 189 | if (!ops->fw_loader || !ops->fw_loader->validate || |
| 190 | !ops->fw_loader->parse_ext_manifest) { |
| 191 | dev_err(sdev->dev, "Missing IPC firmware loading ops\n" ); |
| 192 | return NULL; |
| 193 | } |
| 194 | |
| 195 | if (!ops->pcm) { |
| 196 | dev_err(sdev->dev, "Missing IPC PCM ops\n" ); |
| 197 | return NULL; |
| 198 | } |
| 199 | |
| 200 | if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) { |
| 201 | dev_err(sdev->dev, "Missing IPC topology ops\n" ); |
| 202 | return NULL; |
| 203 | } |
| 204 | |
| 205 | if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend || |
| 206 | !ops->fw_tracing->resume)) { |
| 207 | dev_err(sdev->dev, "Missing firmware tracing ops\n" ); |
| 208 | return NULL; |
| 209 | } |
| 210 | |
| 211 | if (ops->init && ops->init(sdev)) |
| 212 | return NULL; |
| 213 | |
| 214 | ipc->ops = ops; |
| 215 | |
| 216 | return ipc; |
| 217 | } |
| 218 | EXPORT_SYMBOL(snd_sof_ipc_init); |
| 219 | |
| 220 | void snd_sof_ipc_free(struct snd_sof_dev *sdev) |
| 221 | { |
| 222 | struct snd_sof_ipc *ipc = sdev->ipc; |
| 223 | |
| 224 | if (!ipc) |
| 225 | return; |
| 226 | |
| 227 | /* disable sending of ipc's */ |
| 228 | mutex_lock(&ipc->tx_mutex); |
| 229 | ipc->disable_ipc_tx = true; |
| 230 | mutex_unlock(lock: &ipc->tx_mutex); |
| 231 | |
| 232 | if (ipc->ops->exit) |
| 233 | ipc->ops->exit(sdev); |
| 234 | } |
| 235 | EXPORT_SYMBOL(snd_sof_ipc_free); |
| 236 | |