diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 9a9d82220fd0d7..f229ede9dbf039 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -366,6 +366,11 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->pdata = plat_data; sdev->first_boot = true; + /* this will be set to true by platforms for which + * the SOF_IPC_FW_READY sequence needs to be initiated by + * them instead of the FW. + */ + sdev->init_fw_ready = false; dev_set_drvdata(dev, sdev); if (sof_core_debug) diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig index 4751b04d5e6fe3..98658e8430a5d6 100644 --- a/sound/soc/sof/imx/Kconfig +++ b/sound/soc/sof/imx/Kconfig @@ -50,4 +50,13 @@ config SND_SOC_SOF_IMX8ULP Say Y if you have such a device. If unsure select "N". +config SND_SOC_SOF_IMX93 + tristate "SOF support for i.MX93" + depends on IMX_DSP + select SND_SOC_SOF_IMX_COMMON + help + This adds support for Sound Open Firmware for NXP i.MX93 platform. + Say Y if you have such a device. + If unsure select "N". + endif ## SND_SOC_SOF_IMX_TOPLEVEL diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile index 798b43a415bf96..62c426f74d3e41 100644 --- a/sound/soc/sof/imx/Makefile +++ b/sound/soc/sof/imx/Makefile @@ -2,6 +2,7 @@ snd-sof-imx8-objs := imx8.o snd-sof-imx8m-objs := imx8m.o snd-sof-imx8ulp-objs := imx8ulp.o +snd-sof-imx93-objs := imx93.o snd-sof-imx-common-objs := imx-common.o @@ -9,3 +10,4 @@ obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o obj-$(CONFIG_SND_SOC_SOF_IMX8M) += snd-sof-imx8m.o obj-$(CONFIG_SND_SOC_SOF_IMX8ULP) += snd-sof-imx8ulp.o obj-$(CONFIG_SND_SOC_SOF_IMX_COMMON) += imx-common.o +obj-$(CONFIG_SND_SOC_SOF_IMX93) += snd-sof-imx93.o diff --git a/sound/soc/sof/imx/imx93.c b/sound/soc/sof/imx/imx93.c new file mode 100644 index 00000000000000..7a09f9d74a7072 --- /dev/null +++ b/sound/soc/sof/imx/imx93.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +/* + * Copyright 2023 NXP + * + * Author: Laurentiu Mihalcea + */ + +#include +#include +#include +#include + +#include "../sof-priv.h" +#include "../sof-of-dev.h" +#include "../ops.h" + +/* since sdev->bar[SOF_FW_BLK_TYPE_SRAM] holds the base address + * of the mailbox regions, the mailbox offset is 0. + */ +#define MBOX_OFFSET 0 + +struct imx93_priv { + struct snd_sof_dev *sdev; + struct platform_device *ipc_dev; + struct imx_dsp_ipc *dummy_dsp_ipc; + struct clk_bulk_data *clks; + int num_clks; +}; + +static void imx93_dummy_dsp_handle_reply(struct imx_dsp_ipc *ipc) +{ + struct imx93_priv *priv; + unsigned long flags; + + priv = imx_dsp_get_data(ipc); + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + snd_sof_ipc_process_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void imx93_dummy_dsp_handle_request(struct imx_dsp_ipc *ipc) +{ + struct imx93_priv *priv = imx_dsp_get_data(ipc); + + /* TODO: handle panic case here if need be */ + + snd_sof_ipc_msgs_rx(priv->sdev); +} + +static struct imx_dsp_ops dummy_dsp_ops = { + .handle_reply = imx93_dummy_dsp_handle_reply, + .handle_request = imx93_dummy_dsp_handle_request, +}; + +static int imx93_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int imx93_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static int imx93_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev; + struct resource res; + struct imx93_priv *priv; + struct device_node *res_node, *np; + int ret; + + pdev = container_of(sdev->dev, struct platform_device, dev); + np = pdev->dev.of_node; + + priv = devm_kzalloc(sdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->num_cores = 1; + + /* this will make host initiate SOF_IPC_FW_READY sequence */ + sdev->init_fw_ready = true; + + sdev->pdata->hw_pdata = priv; + priv->sdev = sdev; + + res_node = of_parse_phandle(np, "mbox-base", 0); + if (!res_node) { + dev_err(sdev->dev, "failed to get mbox-base node.\n"); + return -ENODEV; + } + + ret = of_address_to_resource(res_node, 0, &res); + of_node_put(res_node); + if (ret) { + dev_err(sdev->dev, "failed to get mbox-base address.\n"); + return ret; + } + + /* map mailbox region into the kernel space */ + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_resource(sdev->dev, &res); + if (IS_ERR(sdev->bar[SOF_FW_BLK_TYPE_SRAM])) { + /* devm_ioremap_resource error message should be fine on + * its own but this additional error message will help + * debug cases in which memory isn't reserved properly + * at boot time. + */ + dev_err(sdev->dev, "failed to ioremap mailbox region. Are you sure you have reserved at least 800MB of memory using 'mem' boot arg?\n"); + return -ENOMEM; + } + + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* this is needed because SOF_IPC_FW_READY data will be sent + * as a reply by the FW. Because we don't know the offsets + * for the other mailbox regions in advance and we don't want + * to hard code them here just add the following restriction: + * + * host_box needs to be placed at the base of the mailbox + * region. + */ + sdev->host_box.offset = 0; + + /* initialize IPC driver */ + priv->ipc_dev = platform_device_register_data(sdev->dev, + "imx-dsp", + PLATFORM_DEVID_NONE, + pdev, + sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + dev_err(sdev->dev, "failed to register platform device data.\n"); + return PTR_ERR(priv->ipc_dev); + } + + priv->dummy_dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dummy_dsp_ipc) { + /* DSP driver not probed */ + dev_err(sdev->dev, "failed to get drvdata.\n"); + ret = -EPROBE_DEFER; + goto err_pdev_unregister; + } + + imx_dsp_set_data(priv->dummy_dsp_ipc, priv); + priv->dummy_dsp_ipc->ops = &dummy_dsp_ops; + + ret = devm_clk_bulk_get_all(sdev->dev, &priv->clks); + if (ret < 0) { + dev_err(sdev->dev, "failed to get clocks.\n"); + goto err_pdev_unregister; + } + priv->num_clks = ret; + + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); + if (ret < 0) { + dev_err(sdev->dev, "failed to enable clocks.\n"); + goto err_pdev_unregister; + } + + return 0; + +err_pdev_unregister: + platform_device_unregister(priv->ipc_dev); + + return ret; +} + +static int imx93_remove(struct snd_sof_dev *sdev) +{ + struct imx93_priv *priv = sdev->pdata->hw_pdata; + + platform_device_unregister(priv->ipc_dev); + + /* disable clocks */ + clk_bulk_disable_unprepare(priv->num_clks, priv->clks); + + return 0; +} + +static int imx93_run(struct snd_sof_dev *sdev) +{ + /* nothing to be done here */ + return 0; +} + +static int imx93_load_firmware(struct snd_sof_dev *sdev) +{ + /* nothing to be done here */ + return 0; +} + +static int imx93_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct imx93_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + + imx_dsp_ring_doorbell(priv->dummy_dsp_ipc, 0); + + return 0; +} + +static int imx93_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + switch (type) { + case SOF_FW_BLK_TYPE_SRAM: + return type; + default: + return -EINVAL; + } +} + +/* the values in this structure are taken from fsl_sai.c */ +static struct snd_soc_dai_driver imx93_dai[] = { + { + .name = "sai3", + .playback = { + .channels_min = 1, + .channels_max = 32, + }, + .capture = { + .channels_min = 1, + .channels_max = 32, + }, + }, +}; + +static struct snd_sof_dsp_ops sof_imx93_ops = { + /* probe/remove operations */ + .probe = imx93_probe, + .remove = imx93_remove, + + /* DSP core boot */ + .run = imx93_run, + + /* block I/O */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* mailbox I/O */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + + /* IPC */ + .send_msg = imx93_send_msg, + .get_mailbox_offset = imx93_get_mailbox_offset, + .get_window_offset = imx93_get_window_offset, + .ipc_msg_data = sof_ipc_msg_data, + + .set_stream_data_offset = sof_set_stream_data_offset, + + .get_bar_index = imx93_get_bar_index, + + /* firmware loading */ + .load_firmware = imx93_load_firmware, + + /* DAI drivers */ + .drv = imx93_dai, + .num_drv = ARRAY_SIZE(imx93_dai), + + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + +}; + +static struct sof_dev_desc sof_of_imx93_desc = { + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .ops = &sof_imx93_ops, +}; + +static const struct of_device_id sof_of_imx93_ids[] = { + { .compatible = "fsl,imx93-dummy-dsp", .data = &sof_of_imx93_desc }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sof_of_imx93_ids); + +static struct platform_driver snd_sof_of_imx93_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of-imx93", + /* for now, PM is not supported */ + .pm = NULL, + .of_match_table = sof_of_imx93_ids, + }, +}; + +module_platform_driver(snd_sof_of_imx93_driver); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index b53abc92302669..4553c59b3c1b58 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -40,7 +40,13 @@ int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, struct snd_sof_ipc_msg *msg; int ret; - if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE) + /* if init_fw_ready is set to true then that means the host will + * try to send SOF_IPC_FW_READY to FW before it has been + * confirmed that the FW has booted in which case its state can + * be != SOF_FW_BOOT_COMPLETE. + */ + if (ipc->disable_ipc_tx || (sdev->fw_state != SOF_FW_BOOT_COMPLETE + && !sdev->init_fw_ready)) return -ENODEV; /* @@ -211,6 +217,11 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) if (ops->init && ops->init(sdev)) return NULL; + if (!ops->init_reply_data_buffer && sdev->init_fw_ready) { + dev_err(sdev->dev, "Missing init_reply_data_buffer IPC op\n"); + return NULL; + } + ipc->ops = ops; return ipc; diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index c6776774209399..933aee5d808c46 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -223,59 +223,6 @@ static inline void ipc3_log_header(struct device *dev, u8 *text, u32 cmd) } #endif -static int sof_ipc3_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply *reply; - int ret = 0; - - /* get the generic reply */ - reply = msg->reply_data; - snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, reply, sizeof(*reply)); - - if (reply->error < 0) - return reply->error; - - if (!reply->hdr.size) { - /* Reply should always be >= sizeof(struct sof_ipc_reply) */ - if (msg->reply_size) - dev_err(sdev->dev, - "empty reply received, expected %zu bytes\n", - msg->reply_size); - else - dev_err(sdev->dev, "empty reply received\n"); - - return -EINVAL; - } - - if (msg->reply_size > 0) { - if (reply->hdr.size == msg->reply_size) { - ret = 0; - } else if (reply->hdr.size < msg->reply_size) { - dev_dbg(sdev->dev, - "reply size (%u) is less than expected (%zu)\n", - reply->hdr.size, msg->reply_size); - - msg->reply_size = reply->hdr.size; - ret = 0; - } else { - dev_err(sdev->dev, - "reply size (%u) exceeds the buffer size (%zu)\n", - reply->hdr.size, msg->reply_size); - ret = -EINVAL; - } - - /* - * get the full message if reply->hdr.size <= msg->reply_size - * and the reply->hdr.size > sizeof(struct sof_ipc_reply) - */ - if (!ret && msg->reply_size > sizeof(*reply)) - snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - return ret; -} /* wait for IPC message reply */ static int ipc3_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data) @@ -799,6 +746,13 @@ static int ipc3_fw_ready(struct snd_sof_dev *sdev, u32 cmd) return offset; } + /* when the host sends SOF_IPC_FW_READY, we need to + * skip the reply structure in order to get to the + * sof_ipc_fw_ready data. + */ + if (sdev->init_fw_ready) + offset += sizeof(struct sof_ipc_reply); + dev_dbg(sdev->dev, "DSP is ready 0x%8.8x offset 0x%x\n", cmd, offset); /* no need to re-check version/ABI for subsequent boots */ @@ -827,9 +781,87 @@ static int ipc3_fw_ready(struct snd_sof_dev *sdev, u32 cmd) ipc3_get_windows(sdev); + /* reply buffer is already initialized at this point */ + if (sdev->init_fw_ready) + return 0; + return ipc3_init_reply_data_buffer(sdev); } +static int sof_ipc3_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply *reply; + u32 cmd; + int ret = 0; + + /* get the generic reply */ + reply = msg->reply_data; + snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, reply, sizeof(*reply)); + + if (reply->error < 0) + return reply->error; + + if (!reply->hdr.size) { + /* Reply should always be >= sizeof(struct sof_ipc_reply) */ + if (msg->reply_size) + dev_err(sdev->dev, + "empty reply received, expected %zu bytes\n", + msg->reply_size); + else + dev_err(sdev->dev, "empty reply received\n"); + + return -EINVAL; + } + + if (!ret && sdev->init_fw_ready && + sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { + cmd = reply->hdr.cmd & SOF_GLB_TYPE_MASK; + + /* check if host received a reply for the + * SOF_IPC_FW_READY message it has sent to + * the FW. + */ + if (cmd == SOF_IPC_FW_READY) { + ret = ipc3_fw_ready(sdev, cmd); + if (ret < 0) + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); + else + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK); + + return 0; + } + } + + if (msg->reply_size > 0) { + if (reply->hdr.size == msg->reply_size) { + ret = 0; + } else if (reply->hdr.size < msg->reply_size) { + dev_dbg(sdev->dev, + "reply size (%u) is less than expected (%zu)\n", + reply->hdr.size, msg->reply_size); + + msg->reply_size = reply->hdr.size; + ret = 0; + } else { + dev_err(sdev->dev, + "reply size (%u) exceeds the buffer size (%zu)\n", + reply->hdr.size, msg->reply_size); + ret = -EINVAL; + } + + /* + * get the full message if reply->hdr.size <= msg->reply_size + * and the reply->hdr.size > sizeof(struct sof_ipc_reply) + */ + if (!ret && msg->reply_size > sizeof(*reply)) + snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + return ret; +} + /* IPC stream position. */ static void ipc3_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) { @@ -1107,4 +1139,5 @@ const struct sof_ipc_ops ipc3_ops = { .rx_msg = sof_ipc3_rx_msg, .set_get_data = sof_ipc3_set_get_data, .get_reply = sof_ipc3_get_reply, + .init_reply_data_buffer = ipc3_init_reply_data_buffer, }; diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 2f8555f11c0364..81e63b88071031 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -109,6 +109,8 @@ EXPORT_SYMBOL(snd_sof_load_firmware_memcpy); int snd_sof_run_firmware(struct snd_sof_dev *sdev) { int ret; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply reply; init_waitqueue_head(&sdev->boot_wait); @@ -145,20 +147,41 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) return ret; } - /* - * now wait for the DSP to boot. There are 3 possible outcomes: - * 1. Boot wait times out indicating FW boot failure. - * 2. FW boots successfully and fw_ready op succeeds. - * 3. FW boots but fw_ready op fails. - */ - ret = wait_event_timeout(sdev->boot_wait, - sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS, - msecs_to_jiffies(sdev->boot_timeout)); - if (ret == 0) { - snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout", - SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX | - SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI); - return -EIO; + if (!sdev->init_fw_ready) { + /* + * now wait for the DSP to boot. There are 3 possible outcomes: + * 1. Boot wait times out indicating FW boot failure. + * 2. FW boots successfully and fw_ready op succeeds. + * 3. FW boots but fw_ready op fails. + */ + ret = wait_event_timeout(sdev->boot_wait, + sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS, + msecs_to_jiffies(sdev->boot_timeout)); + if (ret == 0) { + snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout", + SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX | + SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI); + return -EIO; + } + } else { + /* initialize IPC reply buffer if need be */ + if (!sdev->ipc->max_payload_size) { + ret = snd_sof_ipc_init_reply_data_buffer(sdev); + if (ret < 0) + return ret; + } + + /* host needs to initiate SOF_IPC_FW_READY. The + * sof_ipc_fw_ready data that was previously signaled by + * a FW-initiated IPC will come as a reply to host's + * IPC. + */ + hdr.cmd = SOF_IPC_FW_READY; + hdr.size = sizeof(reply); + + ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size, &reply, sizeof(reply)); + if (ret < 0) + return ret; } if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED) diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index cd4f6ac126eca7..9413fef3998d2f 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -474,6 +474,10 @@ struct sof_ipc_pcm_ops; * @get_reply: Function pointer for fetching the reply to * sdev->ipc->msg.reply_data * @rx_msg: Function pointer for handling a received message + * @init_reply_data_buffer: Optional pointer for IPC reply data + * initialization. Used for cases where + * the host needs to initialize the + * SOF_IPC_FW_READY sequence. * * Note: both @tx_msg and @set_get_data considered as TX functions and they are * serialized for the duration of the instructed transfer. A large message sent @@ -497,6 +501,17 @@ struct sof_ipc_ops { bool set); int (*get_reply)(struct snd_sof_dev *sdev); void (*rx_msg)(struct snd_sof_dev *sdev); + /* this operation is required for cases where the host might + * want to send the firmware a message before SOF_IPC_FW_READY + * is received. + * + * One of these cases is on i.MX93 platform which requires the + * host to send a SOF_IPC_FW_READY message to firmware in order + * to receive the data expected from SOF_IPC_FW_READY. This time + * said data will be received as a reply so the reply_data + * buffer needs to be prepared. + */ + int (*init_reply_data_buffer)(struct snd_sof_dev *sdev); }; /* SOF generic IPC data */ @@ -663,6 +678,10 @@ struct snd_sof_dev { u16 mclk_id_quirk; /* same size as in IPC3 definitions */ void *private; /* core does not touch this */ + /* set to true if the host needs to initiate + * the SOF_IPC_FW_READY sequence. + */ + bool init_fw_ready; }; /* @@ -728,6 +747,11 @@ static inline int sof_ipc_tx_message_no_pm_no_reply(struct snd_sof_ipc *ipc, voi int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, size_t reply_bytes); +static inline int snd_sof_ipc_init_reply_data_buffer(struct snd_sof_dev *sdev) +{ + return sdev->ipc->ops->init_reply_data_buffer(sdev); +} + static inline void snd_sof_ipc_process_reply(struct snd_sof_dev *sdev, u32 msg_id) { snd_sof_ipc_get_reply(sdev);