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 87*d8bc54a5SJyri Sarha /* IPC set or get data from host to DSP */ 88*d8bc54a5SJyri Sarha int sof_ipc_set_get_data(struct snd_sof_ipc *ipc, void *msg_data, 89*d8bc54a5SJyri Sarha size_t msg_bytes, bool set) 90*d8bc54a5SJyri Sarha { 91*d8bc54a5SJyri Sarha return ipc->ops->set_get_data(ipc->sdev, msg_data, msg_bytes, set); 92*d8bc54a5SJyri Sarha } 93*d8bc54a5SJyri Sarha EXPORT_SYMBOL(sof_ipc_set_get_data); 94*d8bc54a5SJyri Sarha 9563e51fd3SRanjani Sridharan /* 9663e51fd3SRanjani Sridharan * send IPC message from host to DSP without modifying the DSP state. 9763e51fd3SRanjani Sridharan * This will be used for IPC's that can be handled by the DSP 9863e51fd3SRanjani Sridharan * even in a low-power D0 substate. 9963e51fd3SRanjani Sridharan */ 1002a51c0f8SPeter Ujfalusi int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, 10163e51fd3SRanjani Sridharan void *reply_data, size_t reply_bytes) 10263e51fd3SRanjani Sridharan { 10378935913SPeter Ujfalusi if (msg_bytes > ipc->max_payload_size || 10478935913SPeter Ujfalusi reply_bytes > ipc->max_payload_size) 10553e0c72dSLiam Girdwood return -ENOBUFS; 10653e0c72dSLiam Girdwood 10785d0f881SPeter Ujfalusi return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, 10885d0f881SPeter Ujfalusi reply_bytes, true); 10953e0c72dSLiam Girdwood } 11063e51fd3SRanjani Sridharan EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); 11153e0c72dSLiam Girdwood 1128ae77801SPeter Ujfalusi /* Generic helper function to retrieve the reply */ 1138ae77801SPeter Ujfalusi void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev) 1148ae77801SPeter Ujfalusi { 1158ae77801SPeter Ujfalusi /* 1168ae77801SPeter Ujfalusi * Sometimes, there is unexpected reply ipc arriving. The reply 1178ae77801SPeter Ujfalusi * ipc belongs to none of the ipcs sent from driver. 1188ae77801SPeter Ujfalusi * In this case, the driver must ignore the ipc. 1198ae77801SPeter Ujfalusi */ 120045bc49bSPeter Ujfalusi if (!sdev->msg) { 1218ae77801SPeter Ujfalusi dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); 1228ae77801SPeter Ujfalusi return; 1238ae77801SPeter Ujfalusi } 1248ae77801SPeter Ujfalusi 125045bc49bSPeter Ujfalusi sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev); 1268ae77801SPeter Ujfalusi } 1278ae77801SPeter Ujfalusi EXPORT_SYMBOL(snd_sof_ipc_get_reply); 1288ae77801SPeter Ujfalusi 12953e0c72dSLiam Girdwood /* handle reply message from DSP */ 130d7a1ed26SRanjani Sridharan void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) 13153e0c72dSLiam Girdwood { 13253e0c72dSLiam Girdwood struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; 13353e0c72dSLiam Girdwood 13453e0c72dSLiam Girdwood if (msg->ipc_complete) { 135d7a1ed26SRanjani Sridharan dev_dbg(sdev->dev, 136d7a1ed26SRanjani Sridharan "no reply expected, received 0x%x, will be ignored", 13753e0c72dSLiam Girdwood msg_id); 138d7a1ed26SRanjani Sridharan return; 13953e0c72dSLiam Girdwood } 14053e0c72dSLiam Girdwood 14153e0c72dSLiam Girdwood /* wake up and return the error if we have waiters on this message ? */ 14253e0c72dSLiam Girdwood msg->ipc_complete = true; 14353e0c72dSLiam Girdwood wake_up(&msg->waitq); 14453e0c72dSLiam Girdwood } 14553e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_reply); 14653e0c72dSLiam Girdwood 14753e0c72dSLiam Girdwood struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) 14853e0c72dSLiam Girdwood { 14953e0c72dSLiam Girdwood struct snd_sof_ipc *ipc; 15053e0c72dSLiam Girdwood struct snd_sof_ipc_msg *msg; 151785b3fbeSPeter Ujfalusi const struct sof_ipc_ops *ops; 15253e0c72dSLiam Girdwood 15353e0c72dSLiam Girdwood ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); 15453e0c72dSLiam Girdwood if (!ipc) 15553e0c72dSLiam Girdwood return NULL; 15653e0c72dSLiam Girdwood 15753e0c72dSLiam Girdwood mutex_init(&ipc->tx_mutex); 15853e0c72dSLiam Girdwood ipc->sdev = sdev; 15953e0c72dSLiam Girdwood msg = &ipc->msg; 16053e0c72dSLiam Girdwood 16153e0c72dSLiam Girdwood /* indicate that we aren't sending a message ATM */ 16253e0c72dSLiam Girdwood msg->ipc_complete = true; 16353e0c72dSLiam Girdwood 16453e0c72dSLiam Girdwood init_waitqueue_head(&msg->waitq); 16553e0c72dSLiam Girdwood 1667ed1f83bSPeter Ujfalusi switch (sdev->pdata->ipc_type) { 1677ed1f83bSPeter Ujfalusi #if defined(CONFIG_SND_SOC_SOF_IPC3) 1687ed1f83bSPeter Ujfalusi case SOF_IPC: 1697ed1f83bSPeter Ujfalusi ops = &ipc3_ops; 1707ed1f83bSPeter Ujfalusi break; 1717ed1f83bSPeter Ujfalusi #endif 1727ed1f83bSPeter Ujfalusi #if defined(CONFIG_SND_SOC_SOF_INTEL_IPC4) 1737ed1f83bSPeter Ujfalusi case SOF_INTEL_IPC4: 1747ed1f83bSPeter Ujfalusi ops = &ipc4_ops; 1757ed1f83bSPeter Ujfalusi break; 1767ed1f83bSPeter Ujfalusi #endif 1777ed1f83bSPeter Ujfalusi default: 1787ed1f83bSPeter Ujfalusi dev_err(sdev->dev, "Not supported IPC version: %d\n", 1797ed1f83bSPeter Ujfalusi sdev->pdata->ipc_type); 1807ed1f83bSPeter Ujfalusi return NULL; 1817ed1f83bSPeter Ujfalusi } 1827006d20eSRanjani Sridharan 1837006d20eSRanjani Sridharan /* check for mandatory ops */ 184defad9d2SPeter Ujfalusi if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) { 185defad9d2SPeter Ujfalusi dev_err(sdev->dev, "Missing IPC message handling ops\n"); 186defad9d2SPeter Ujfalusi return NULL; 187defad9d2SPeter Ujfalusi } 188defad9d2SPeter Ujfalusi 1892a6099a7SPeter Ujfalusi if (!ops->fw_loader || !ops->fw_loader->validate || 1902a6099a7SPeter Ujfalusi !ops->fw_loader->parse_ext_manifest) { 1912a6099a7SPeter Ujfalusi dev_err(sdev->dev, "Missing IPC firmware loading ops\n"); 1922a6099a7SPeter Ujfalusi return NULL; 1932a6099a7SPeter Ujfalusi } 1942a6099a7SPeter Ujfalusi 195785b3fbeSPeter Ujfalusi if (!ops->pcm) { 196785b3fbeSPeter Ujfalusi dev_err(sdev->dev, "Missing IPC PCM ops\n"); 197785b3fbeSPeter Ujfalusi return NULL; 198785b3fbeSPeter Ujfalusi } 199785b3fbeSPeter Ujfalusi 200785b3fbeSPeter Ujfalusi if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) { 201785b3fbeSPeter Ujfalusi dev_err(sdev->dev, "Missing IPC topology ops\n"); 2027006d20eSRanjani Sridharan return NULL; 2037006d20eSRanjani Sridharan } 2047006d20eSRanjani Sridharan 2051dedbe4fSPeter Ujfalusi if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend || 2061dedbe4fSPeter Ujfalusi !ops->fw_tracing->resume)) { 2071dedbe4fSPeter Ujfalusi dev_err(sdev->dev, "Missing firmware tracing ops\n"); 2081dedbe4fSPeter Ujfalusi return NULL; 2091dedbe4fSPeter Ujfalusi } 2101dedbe4fSPeter Ujfalusi 211aa23b375SPeter Ujfalusi if (ops->init && ops->init(sdev)) 212aa23b375SPeter Ujfalusi return NULL; 213aa23b375SPeter Ujfalusi 2147ed1f83bSPeter Ujfalusi ipc->ops = ops; 2157ed1f83bSPeter Ujfalusi 21653e0c72dSLiam Girdwood return ipc; 21753e0c72dSLiam Girdwood } 21853e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_init); 21953e0c72dSLiam Girdwood 22053e0c72dSLiam Girdwood void snd_sof_ipc_free(struct snd_sof_dev *sdev) 22153e0c72dSLiam Girdwood { 22253e0c72dSLiam Girdwood struct snd_sof_ipc *ipc = sdev->ipc; 22353e0c72dSLiam Girdwood 224b06e4642SKai Vehmanen if (!ipc) 225b06e4642SKai Vehmanen return; 226b06e4642SKai Vehmanen 22753e0c72dSLiam Girdwood /* disable sending of ipc's */ 22853e0c72dSLiam Girdwood mutex_lock(&ipc->tx_mutex); 22953e0c72dSLiam Girdwood ipc->disable_ipc_tx = true; 23053e0c72dSLiam Girdwood mutex_unlock(&ipc->tx_mutex); 231aa23b375SPeter Ujfalusi 232aa23b375SPeter Ujfalusi if (ipc->ops->exit) 233aa23b375SPeter Ujfalusi ipc->ops->exit(sdev); 23453e0c72dSLiam Girdwood } 23553e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_free); 236