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" 20657774acSRanjani Sridharan #include "ipc3-ops.h" 2153e0c72dSLiam Girdwood 22b4dcafe4SPeter Ujfalusi /** 23b4dcafe4SPeter Ujfalusi * sof_ipc_send_msg - generic function to prepare and send one IPC message 24b4dcafe4SPeter Ujfalusi * @sdev: pointer to SOF core device struct 25b4dcafe4SPeter Ujfalusi * @msg_data: pointer to a message to send 26b4dcafe4SPeter Ujfalusi * @msg_bytes: number of bytes in the message 27b4dcafe4SPeter Ujfalusi * @reply_bytes: number of bytes available for the reply. 28b4dcafe4SPeter Ujfalusi * The buffer for the reply data is not passed to this 29b4dcafe4SPeter Ujfalusi * function, the available size is an information for the 30b4dcafe4SPeter Ujfalusi * reply handling functions. 31b4dcafe4SPeter Ujfalusi * 32b4dcafe4SPeter Ujfalusi * On success the function returns 0, otherwise negative error number. 33b4dcafe4SPeter Ujfalusi * 34b4dcafe4SPeter Ujfalusi * Note: higher level sdev->ipc->tx_mutex must be held to make sure that 35b4dcafe4SPeter Ujfalusi * transfers are synchronized. 36b4dcafe4SPeter Ujfalusi */ 37b4dcafe4SPeter Ujfalusi int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, 38b4dcafe4SPeter Ujfalusi size_t reply_bytes) 39b4dcafe4SPeter Ujfalusi { 40b4dcafe4SPeter Ujfalusi struct snd_sof_ipc *ipc = sdev->ipc; 41b4dcafe4SPeter Ujfalusi struct snd_sof_ipc_msg *msg; 42b4dcafe4SPeter Ujfalusi int ret; 43b4dcafe4SPeter Ujfalusi 44b4dcafe4SPeter Ujfalusi if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE) 45b4dcafe4SPeter Ujfalusi return -ENODEV; 46b4dcafe4SPeter Ujfalusi 47b4dcafe4SPeter Ujfalusi /* 48b4dcafe4SPeter Ujfalusi * The spin-lock is needed to protect message objects against other 49b4dcafe4SPeter Ujfalusi * atomic contexts. 50b4dcafe4SPeter Ujfalusi */ 51b4dcafe4SPeter Ujfalusi spin_lock_irq(&sdev->ipc_lock); 52b4dcafe4SPeter Ujfalusi 53b4dcafe4SPeter Ujfalusi /* initialise the message */ 54b4dcafe4SPeter Ujfalusi msg = &ipc->msg; 55b4dcafe4SPeter Ujfalusi 56b4dcafe4SPeter Ujfalusi /* attach message data */ 57b4dcafe4SPeter Ujfalusi msg->msg_data = msg_data; 58b4dcafe4SPeter Ujfalusi msg->msg_size = msg_bytes; 59b4dcafe4SPeter Ujfalusi 60b4dcafe4SPeter Ujfalusi msg->reply_size = reply_bytes; 61b4dcafe4SPeter Ujfalusi msg->reply_error = 0; 62b4dcafe4SPeter Ujfalusi 63b4dcafe4SPeter Ujfalusi sdev->msg = msg; 64b4dcafe4SPeter Ujfalusi 65b4dcafe4SPeter Ujfalusi ret = snd_sof_dsp_send_msg(sdev, msg); 66b4dcafe4SPeter Ujfalusi /* Next reply that we receive will be related to this message */ 67b4dcafe4SPeter Ujfalusi if (!ret) 68b4dcafe4SPeter Ujfalusi msg->ipc_complete = false; 69b4dcafe4SPeter Ujfalusi 70b4dcafe4SPeter Ujfalusi spin_unlock_irq(&sdev->ipc_lock); 71b4dcafe4SPeter Ujfalusi 72b4dcafe4SPeter Ujfalusi return ret; 73b4dcafe4SPeter Ujfalusi } 74b4dcafe4SPeter Ujfalusi 7553e0c72dSLiam Girdwood /* send IPC message from host to DSP */ 762a51c0f8SPeter Ujfalusi int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, 772a51c0f8SPeter Ujfalusi void *reply_data, size_t reply_bytes) 7853e0c72dSLiam Girdwood { 7985d0f881SPeter Ujfalusi if (msg_bytes > ipc->max_payload_size || 8085d0f881SPeter Ujfalusi reply_bytes > ipc->max_payload_size) 8185d0f881SPeter Ujfalusi return -ENOBUFS; 8263e51fd3SRanjani Sridharan 8385d0f881SPeter Ujfalusi return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, 8485d0f881SPeter Ujfalusi reply_bytes, false); 8563e51fd3SRanjani Sridharan } 8663e51fd3SRanjani Sridharan EXPORT_SYMBOL(sof_ipc_tx_message); 8763e51fd3SRanjani Sridharan 8863e51fd3SRanjani Sridharan /* 8963e51fd3SRanjani Sridharan * send IPC message from host to DSP without modifying the DSP state. 9063e51fd3SRanjani Sridharan * This will be used for IPC's that can be handled by the DSP 9163e51fd3SRanjani Sridharan * even in a low-power D0 substate. 9263e51fd3SRanjani Sridharan */ 932a51c0f8SPeter Ujfalusi int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, 9463e51fd3SRanjani Sridharan void *reply_data, size_t reply_bytes) 9563e51fd3SRanjani Sridharan { 9678935913SPeter Ujfalusi if (msg_bytes > ipc->max_payload_size || 9778935913SPeter Ujfalusi reply_bytes > ipc->max_payload_size) 9853e0c72dSLiam Girdwood return -ENOBUFS; 9953e0c72dSLiam Girdwood 10085d0f881SPeter Ujfalusi return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, 10185d0f881SPeter Ujfalusi reply_bytes, true); 10253e0c72dSLiam Girdwood } 10363e51fd3SRanjani Sridharan EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); 10453e0c72dSLiam Girdwood 1058ae77801SPeter Ujfalusi /* Generic helper function to retrieve the reply */ 1068ae77801SPeter Ujfalusi void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev) 1078ae77801SPeter Ujfalusi { 1088ae77801SPeter Ujfalusi /* 1098ae77801SPeter Ujfalusi * Sometimes, there is unexpected reply ipc arriving. The reply 1108ae77801SPeter Ujfalusi * ipc belongs to none of the ipcs sent from driver. 1118ae77801SPeter Ujfalusi * In this case, the driver must ignore the ipc. 1128ae77801SPeter Ujfalusi */ 113045bc49bSPeter Ujfalusi if (!sdev->msg) { 1148ae77801SPeter Ujfalusi dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); 1158ae77801SPeter Ujfalusi return; 1168ae77801SPeter Ujfalusi } 1178ae77801SPeter Ujfalusi 118045bc49bSPeter Ujfalusi sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev); 1198ae77801SPeter Ujfalusi } 1208ae77801SPeter Ujfalusi EXPORT_SYMBOL(snd_sof_ipc_get_reply); 1218ae77801SPeter Ujfalusi 12253e0c72dSLiam Girdwood /* handle reply message from DSP */ 123d7a1ed26SRanjani Sridharan void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) 12453e0c72dSLiam Girdwood { 12553e0c72dSLiam Girdwood struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; 12653e0c72dSLiam Girdwood 12753e0c72dSLiam Girdwood if (msg->ipc_complete) { 128d7a1ed26SRanjani Sridharan dev_dbg(sdev->dev, 129d7a1ed26SRanjani Sridharan "no reply expected, received 0x%x, will be ignored", 13053e0c72dSLiam Girdwood msg_id); 131d7a1ed26SRanjani Sridharan return; 13253e0c72dSLiam Girdwood } 13353e0c72dSLiam Girdwood 13453e0c72dSLiam Girdwood /* wake up and return the error if we have waiters on this message ? */ 13553e0c72dSLiam Girdwood msg->ipc_complete = true; 13653e0c72dSLiam Girdwood wake_up(&msg->waitq); 13753e0c72dSLiam Girdwood } 13853e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_reply); 13953e0c72dSLiam Girdwood 14053e0c72dSLiam Girdwood int snd_sof_ipc_valid(struct snd_sof_dev *sdev) 14153e0c72dSLiam Girdwood { 14253e0c72dSLiam Girdwood struct sof_ipc_fw_ready *ready = &sdev->fw_ready; 14353e0c72dSLiam Girdwood struct sof_ipc_fw_version *v = &ready->version; 14453e0c72dSLiam Girdwood 14553e0c72dSLiam Girdwood dev_info(sdev->dev, 14653e0c72dSLiam Girdwood "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, 14753e0c72dSLiam Girdwood v->micro, v->tag); 14853e0c72dSLiam Girdwood dev_info(sdev->dev, 14953e0c72dSLiam Girdwood "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", 15053e0c72dSLiam Girdwood SOF_ABI_VERSION_MAJOR(v->abi_version), 15153e0c72dSLiam Girdwood SOF_ABI_VERSION_MINOR(v->abi_version), 15253e0c72dSLiam Girdwood SOF_ABI_VERSION_PATCH(v->abi_version), 15353e0c72dSLiam Girdwood SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); 15453e0c72dSLiam Girdwood 15553e0c72dSLiam Girdwood if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { 15653e0c72dSLiam Girdwood dev_err(sdev->dev, "error: incompatible FW ABI version\n"); 15753e0c72dSLiam Girdwood return -EINVAL; 15853e0c72dSLiam Girdwood } 15953e0c72dSLiam Girdwood 16053129e66SKai Vehmanen if (SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) { 1614acb1c2eSPierre-Louis Bossart if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { 1624acb1c2eSPierre-Louis Bossart dev_warn(sdev->dev, "warn: FW ABI is more recent than kernel\n"); 1634acb1c2eSPierre-Louis Bossart } else { 1644acb1c2eSPierre-Louis Bossart dev_err(sdev->dev, "error: FW ABI is more recent than kernel\n"); 1654acb1c2eSPierre-Louis Bossart return -EINVAL; 1664acb1c2eSPierre-Louis Bossart } 1674acb1c2eSPierre-Louis Bossart } 1684acb1c2eSPierre-Louis Bossart 169347d1c4bSSlawomir Blauciak if (ready->flags & SOF_IPC_INFO_BUILD) { 17053e0c72dSLiam Girdwood dev_info(sdev->dev, 17153e0c72dSLiam Girdwood "Firmware debug build %d on %s-%s - options:\n" 17253e0c72dSLiam Girdwood " GDB: %s\n" 17353e0c72dSLiam Girdwood " lock debug: %s\n" 17453e0c72dSLiam Girdwood " lock vdebug: %s\n", 17553e0c72dSLiam Girdwood v->build, v->date, v->time, 176847a040dSPierre-Louis Bossart (ready->flags & SOF_IPC_INFO_GDB) ? 177347d1c4bSSlawomir Blauciak "enabled" : "disabled", 178847a040dSPierre-Louis Bossart (ready->flags & SOF_IPC_INFO_LOCKS) ? 179347d1c4bSSlawomir Blauciak "enabled" : "disabled", 180847a040dSPierre-Louis Bossart (ready->flags & SOF_IPC_INFO_LOCKSV) ? 181347d1c4bSSlawomir Blauciak "enabled" : "disabled"); 18253e0c72dSLiam Girdwood } 18353e0c72dSLiam Girdwood 18453e0c72dSLiam Girdwood /* copy the fw_version into debugfs at first boot */ 18553e0c72dSLiam Girdwood memcpy(&sdev->fw_version, v, sizeof(*v)); 18653e0c72dSLiam Girdwood 18753e0c72dSLiam Girdwood return 0; 18853e0c72dSLiam Girdwood } 18953e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_valid); 19053e0c72dSLiam Girdwood 19153e0c72dSLiam Girdwood struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) 19253e0c72dSLiam Girdwood { 19353e0c72dSLiam Girdwood struct snd_sof_ipc *ipc; 19453e0c72dSLiam Girdwood struct snd_sof_ipc_msg *msg; 195785b3fbeSPeter Ujfalusi const struct sof_ipc_ops *ops; 19653e0c72dSLiam Girdwood 19753e0c72dSLiam Girdwood ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); 19853e0c72dSLiam Girdwood if (!ipc) 19953e0c72dSLiam Girdwood return NULL; 20053e0c72dSLiam Girdwood 20153e0c72dSLiam Girdwood mutex_init(&ipc->tx_mutex); 20253e0c72dSLiam Girdwood ipc->sdev = sdev; 20353e0c72dSLiam Girdwood msg = &ipc->msg; 20453e0c72dSLiam Girdwood 20553e0c72dSLiam Girdwood /* indicate that we aren't sending a message ATM */ 20653e0c72dSLiam Girdwood msg->ipc_complete = true; 20753e0c72dSLiam Girdwood 20853e0c72dSLiam Girdwood init_waitqueue_head(&msg->waitq); 20953e0c72dSLiam Girdwood 2107006d20eSRanjani Sridharan /* 2117006d20eSRanjani Sridharan * Use IPC3 ops as it is the only available version now. With the addition of new IPC 2127006d20eSRanjani Sridharan * versions, this will need to be modified to use the selected version at runtime. 2137006d20eSRanjani Sridharan */ 2147006d20eSRanjani Sridharan ipc->ops = &ipc3_ops; 215785b3fbeSPeter Ujfalusi ops = ipc->ops; 2167006d20eSRanjani Sridharan 2177006d20eSRanjani Sridharan /* check for mandatory ops */ 218defad9d2SPeter Ujfalusi if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) { 219defad9d2SPeter Ujfalusi dev_err(sdev->dev, "Missing IPC message handling ops\n"); 220defad9d2SPeter Ujfalusi return NULL; 221defad9d2SPeter Ujfalusi } 222defad9d2SPeter Ujfalusi 223*2a6099a7SPeter Ujfalusi if (!ops->fw_loader || !ops->fw_loader->validate || 224*2a6099a7SPeter Ujfalusi !ops->fw_loader->parse_ext_manifest) { 225*2a6099a7SPeter Ujfalusi dev_err(sdev->dev, "Missing IPC firmware loading ops\n"); 226*2a6099a7SPeter Ujfalusi return NULL; 227*2a6099a7SPeter Ujfalusi } 228*2a6099a7SPeter Ujfalusi 229785b3fbeSPeter Ujfalusi if (!ops->pcm) { 230785b3fbeSPeter Ujfalusi dev_err(sdev->dev, "Missing IPC PCM ops\n"); 231785b3fbeSPeter Ujfalusi return NULL; 232785b3fbeSPeter Ujfalusi } 233785b3fbeSPeter Ujfalusi 234785b3fbeSPeter Ujfalusi if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) { 235785b3fbeSPeter Ujfalusi dev_err(sdev->dev, "Missing IPC topology ops\n"); 2367006d20eSRanjani Sridharan return NULL; 2377006d20eSRanjani Sridharan } 2387006d20eSRanjani Sridharan 23953e0c72dSLiam Girdwood return ipc; 24053e0c72dSLiam Girdwood } 24153e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_init); 24253e0c72dSLiam Girdwood 24353e0c72dSLiam Girdwood void snd_sof_ipc_free(struct snd_sof_dev *sdev) 24453e0c72dSLiam Girdwood { 24553e0c72dSLiam Girdwood struct snd_sof_ipc *ipc = sdev->ipc; 24653e0c72dSLiam Girdwood 247b06e4642SKai Vehmanen if (!ipc) 248b06e4642SKai Vehmanen return; 249b06e4642SKai Vehmanen 25053e0c72dSLiam Girdwood /* disable sending of ipc's */ 25153e0c72dSLiam Girdwood mutex_lock(&ipc->tx_mutex); 25253e0c72dSLiam Girdwood ipc->disable_ipc_tx = true; 25353e0c72dSLiam Girdwood mutex_unlock(&ipc->tx_mutex); 25453e0c72dSLiam Girdwood } 25553e0c72dSLiam Girdwood EXPORT_SYMBOL(snd_sof_ipc_free); 256