1e149ca29SPierre-Louis Bossart // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 253e0c72dSLiam Girdwood // 353e0c72dSLiam Girdwood // This file is provided under a dual BSD/GPLv2 license. When using or 453e0c72dSLiam Girdwood // redistributing this file, you may do so under either license. 553e0c72dSLiam Girdwood // 653e0c72dSLiam Girdwood // Copyright(c) 2018 Intel Corporation. All rights reserved. 753e0c72dSLiam Girdwood // 853e0c72dSLiam Girdwood // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> 953e0c72dSLiam Girdwood // 1053e0c72dSLiam Girdwood // Generic IPC layer that can work over MMIO and SPI/I2C. PHY layer provided 1153e0c72dSLiam Girdwood // by platform driver code. 1253e0c72dSLiam Girdwood // 1353e0c72dSLiam Girdwood 1453e0c72dSLiam Girdwood #include <linux/mutex.h> 1553e0c72dSLiam Girdwood #include <linux/types.h> 1653e0c72dSLiam Girdwood 1753e0c72dSLiam Girdwood #include "sof-priv.h" 18ee1e79b7SRanjani Sridharan #include "sof-audio.h" 1953e0c72dSLiam Girdwood #include "ops.h" 2053e0c72dSLiam Girdwood 21b4dcafe4SPeter Ujfalusi /** 22b4dcafe4SPeter Ujfalusi * sof_ipc_send_msg - generic function to prepare and send one IPC message 23b4dcafe4SPeter Ujfalusi * @sdev: pointer to SOF core device struct 24b4dcafe4SPeter Ujfalusi * @msg_data: pointer to a message to send 25b4dcafe4SPeter Ujfalusi * @msg_bytes: number of bytes in the message 26b4dcafe4SPeter Ujfalusi * @reply_bytes: number of bytes available for the reply. 27b4dcafe4SPeter Ujfalusi * The buffer for the reply data is not passed to this 28b4dcafe4SPeter Ujfalusi * function, the available size is an information for the 29b4dcafe4SPeter Ujfalusi * reply handling functions. 30b4dcafe4SPeter Ujfalusi * 31b4dcafe4SPeter Ujfalusi * On success the function returns 0, otherwise negative error number. 32b4dcafe4SPeter Ujfalusi * 33b4dcafe4SPeter Ujfalusi * Note: higher level sdev->ipc->tx_mutex must be held to make sure that 34b4dcafe4SPeter Ujfalusi * transfers are synchronized. 35b4dcafe4SPeter Ujfalusi */ 36b4dcafe4SPeter Ujfalusi int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, 37b4dcafe4SPeter Ujfalusi size_t reply_bytes) 38b4dcafe4SPeter Ujfalusi { 39b4dcafe4SPeter Ujfalusi struct snd_sof_ipc *ipc = sdev->ipc; 40b4dcafe4SPeter Ujfalusi struct snd_sof_ipc_msg *msg; 41b4dcafe4SPeter Ujfalusi int ret; 42b4dcafe4SPeter Ujfalusi 43b4dcafe4SPeter Ujfalusi if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE) 44b4dcafe4SPeter Ujfalusi return -ENODEV; 45b4dcafe4SPeter Ujfalusi 46b4dcafe4SPeter Ujfalusi /* 47b4dcafe4SPeter Ujfalusi * The spin-lock is needed to protect message objects against other 48b4dcafe4SPeter Ujfalusi * atomic contexts. 49b4dcafe4SPeter Ujfalusi */ 50b4dcafe4SPeter Ujfalusi spin_lock_irq(&sdev->ipc_lock); 51b4dcafe4SPeter Ujfalusi 52b4dcafe4SPeter Ujfalusi /* initialise the message */ 53b4dcafe4SPeter Ujfalusi msg = &ipc->msg; 54b4dcafe4SPeter Ujfalusi 55b4dcafe4SPeter Ujfalusi /* attach message data */ 56b4dcafe4SPeter Ujfalusi msg->msg_data = msg_data; 57b4dcafe4SPeter Ujfalusi msg->msg_size = msg_bytes; 58b4dcafe4SPeter Ujfalusi 59b4dcafe4SPeter Ujfalusi msg->reply_size = reply_bytes; 60b4dcafe4SPeter Ujfalusi msg->reply_error = 0; 61b4dcafe4SPeter Ujfalusi 62b4dcafe4SPeter Ujfalusi sdev->msg = msg; 63b4dcafe4SPeter Ujfalusi 64b4dcafe4SPeter Ujfalusi ret = snd_sof_dsp_send_msg(sdev, msg); 65b4dcafe4SPeter Ujfalusi /* Next reply that we receive will be related to this message */ 66b4dcafe4SPeter Ujfalusi if (!ret) 67b4dcafe4SPeter Ujfalusi msg->ipc_complete = false; 68b4dcafe4SPeter Ujfalusi 69b4dcafe4SPeter Ujfalusi spin_unlock_irq(&sdev->ipc_lock); 70b4dcafe4SPeter Ujfalusi 71b4dcafe4SPeter Ujfalusi return ret; 72b4dcafe4SPeter Ujfalusi } 73b4dcafe4SPeter Ujfalusi 7453e0c72dSLiam Girdwood /* send IPC message from host to DSP */ 752a51c0f8SPeter Ujfalusi int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, 762a51c0f8SPeter Ujfalusi void *reply_data, size_t reply_bytes) 7753e0c72dSLiam Girdwood { 7885d0f881SPeter Ujfalusi if (msg_bytes > ipc->max_payload_size || 7985d0f881SPeter Ujfalusi reply_bytes > ipc->max_payload_size) 8085d0f881SPeter Ujfalusi return -ENOBUFS; 8163e51fd3SRanjani Sridharan 8285d0f881SPeter Ujfalusi return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, 8385d0f881SPeter Ujfalusi reply_bytes, false); 8463e51fd3SRanjani Sridharan } 8563e51fd3SRanjani Sridharan EXPORT_SYMBOL(sof_ipc_tx_message); 8663e51fd3SRanjani Sridharan 8763e51fd3SRanjani Sridharan /* 8863e51fd3SRanjani Sridharan * send IPC message from host to DSP without modifying the DSP state. 8963e51fd3SRanjani Sridharan * This will be used for IPC's that can be handled by the DSP 9063e51fd3SRanjani Sridharan * even in a low-power D0 substate. 9163e51fd3SRanjani Sridharan */ 922a51c0f8SPeter Ujfalusi int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, 9363e51fd3SRanjani Sridharan void *reply_data, size_t reply_bytes) 9463e51fd3SRanjani Sridharan { 9578935913SPeter Ujfalusi if (msg_bytes > ipc->max_payload_size || 9678935913SPeter Ujfalusi reply_bytes > ipc->max_payload_size) 9753e0c72dSLiam Girdwood return -ENOBUFS; 9853e0c72dSLiam Girdwood 9985d0f881SPeter Ujfalusi return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, 10085d0f881SPeter Ujfalusi reply_bytes, true); 10153e0c72dSLiam Girdwood } 10263e51fd3SRanjani Sridharan EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); 10353e0c72dSLiam Girdwood 1048ae77801SPeter Ujfalusi /* Generic helper function to retrieve the reply */ 1058ae77801SPeter Ujfalusi void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev) 1068ae77801SPeter Ujfalusi { 1078ae77801SPeter Ujfalusi /* 1088ae77801SPeter Ujfalusi * Sometimes, there is unexpected reply ipc arriving. The reply 1098ae77801SPeter Ujfalusi * ipc belongs to none of the ipcs sent from driver. 1108ae77801SPeter Ujfalusi * In this case, the driver must ignore the ipc. 1118ae77801SPeter Ujfalusi */ 112045bc49bSPeter Ujfalusi if (!sdev->msg) { 1138ae77801SPeter Ujfalusi dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); 1148ae77801SPeter Ujfalusi return; 1158ae77801SPeter Ujfalusi } 1168ae77801SPeter Ujfalusi 117045bc49bSPeter Ujfalusi sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev); 1188ae77801SPeter Ujfalusi } 1198ae77801SPeter Ujfalusi EXPORT_SYMBOL(snd_sof_ipc_get_reply); 1208ae77801SPeter Ujfalusi 12153e0c72dSLiam Girdwood /* handle reply message from DSP */ 122d7a1ed26SRanjani Sridharan void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) 12353e0c72dSLiam Girdwood { 12453e0c72dSLiam Girdwood struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; 12553e0c72dSLiam Girdwood 12653e0c72dSLiam Girdwood if (msg->ipc_complete) { 127d7a1ed26SRanjani Sridharan dev_dbg(sdev->dev, 128d7a1ed26SRanjani Sridharan "no reply expected, received 0x%x, will be ignored", 12953e0c72dSLiam Girdwood msg_id); 130d7a1ed26SRanjani Sridharan return; 13153e0c72dSLiam Girdwood } 13253e0c72dSLiam Girdwood 13353e0c72dSLiam Girdwood /* wake up and return the error if we have waiters on this message ? */ 13453e0c72dSLiam Girdwood msg->ipc_complete = true; 13553e0c72dSLiam Girdwood wake_up(&msg->waitq); 13653e0c72dSLiam Girdwood } 13753e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_reply); 13853e0c72dSLiam Girdwood 13953e0c72dSLiam Girdwood struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) 14053e0c72dSLiam Girdwood { 14153e0c72dSLiam Girdwood struct snd_sof_ipc *ipc; 14253e0c72dSLiam Girdwood struct snd_sof_ipc_msg *msg; 143785b3fbeSPeter Ujfalusi const struct sof_ipc_ops *ops; 14453e0c72dSLiam Girdwood 14553e0c72dSLiam Girdwood ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); 14653e0c72dSLiam Girdwood if (!ipc) 14753e0c72dSLiam Girdwood return NULL; 14853e0c72dSLiam Girdwood 14953e0c72dSLiam Girdwood mutex_init(&ipc->tx_mutex); 15053e0c72dSLiam Girdwood ipc->sdev = sdev; 15153e0c72dSLiam Girdwood msg = &ipc->msg; 15253e0c72dSLiam Girdwood 15353e0c72dSLiam Girdwood /* indicate that we aren't sending a message ATM */ 15453e0c72dSLiam Girdwood msg->ipc_complete = true; 15553e0c72dSLiam Girdwood 15653e0c72dSLiam Girdwood init_waitqueue_head(&msg->waitq); 15753e0c72dSLiam Girdwood 1587006d20eSRanjani Sridharan /* 1597006d20eSRanjani Sridharan * Use IPC3 ops as it is the only available version now. With the addition of new IPC 1607006d20eSRanjani Sridharan * versions, this will need to be modified to use the selected version at runtime. 1617006d20eSRanjani Sridharan */ 1627006d20eSRanjani Sridharan ipc->ops = &ipc3_ops; 163785b3fbeSPeter Ujfalusi ops = ipc->ops; 1647006d20eSRanjani Sridharan 1657006d20eSRanjani Sridharan /* check for mandatory ops */ 166defad9d2SPeter Ujfalusi if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) { 167defad9d2SPeter Ujfalusi dev_err(sdev->dev, "Missing IPC message handling ops\n"); 168defad9d2SPeter Ujfalusi return NULL; 169defad9d2SPeter Ujfalusi } 170defad9d2SPeter Ujfalusi 1712a6099a7SPeter Ujfalusi if (!ops->fw_loader || !ops->fw_loader->validate || 1722a6099a7SPeter Ujfalusi !ops->fw_loader->parse_ext_manifest) { 1732a6099a7SPeter Ujfalusi dev_err(sdev->dev, "Missing IPC firmware loading ops\n"); 1742a6099a7SPeter Ujfalusi return NULL; 1752a6099a7SPeter Ujfalusi } 1762a6099a7SPeter Ujfalusi 177785b3fbeSPeter Ujfalusi if (!ops->pcm) { 178785b3fbeSPeter Ujfalusi dev_err(sdev->dev, "Missing IPC PCM ops\n"); 179785b3fbeSPeter Ujfalusi return NULL; 180785b3fbeSPeter Ujfalusi } 181785b3fbeSPeter Ujfalusi 182785b3fbeSPeter Ujfalusi if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) { 183785b3fbeSPeter Ujfalusi dev_err(sdev->dev, "Missing IPC topology ops\n"); 1847006d20eSRanjani Sridharan return NULL; 1857006d20eSRanjani Sridharan } 1867006d20eSRanjani Sridharan 187*1dedbe4fSPeter Ujfalusi if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend || 188*1dedbe4fSPeter Ujfalusi !ops->fw_tracing->resume)) { 189*1dedbe4fSPeter Ujfalusi dev_err(sdev->dev, "Missing firmware tracing ops\n"); 190*1dedbe4fSPeter Ujfalusi return NULL; 191*1dedbe4fSPeter Ujfalusi } 192*1dedbe4fSPeter Ujfalusi 19353e0c72dSLiam Girdwood return ipc; 19453e0c72dSLiam Girdwood } 19553e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_init); 19653e0c72dSLiam Girdwood 19753e0c72dSLiam Girdwood void snd_sof_ipc_free(struct snd_sof_dev *sdev) 19853e0c72dSLiam Girdwood { 19953e0c72dSLiam Girdwood struct snd_sof_ipc *ipc = sdev->ipc; 20053e0c72dSLiam Girdwood 201b06e4642SKai Vehmanen if (!ipc) 202b06e4642SKai Vehmanen return; 203b06e4642SKai Vehmanen 20453e0c72dSLiam Girdwood /* disable sending of ipc's */ 20553e0c72dSLiam Girdwood mutex_lock(&ipc->tx_mutex); 20653e0c72dSLiam Girdwood ipc->disable_ipc_tx = true; 20753e0c72dSLiam Girdwood mutex_unlock(&ipc->tx_mutex); 20853e0c72dSLiam Girdwood } 20953e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_free); 210