18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Copyright 2020 NXP 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Author: Daniel Baluta <daniel.baluta@nxp.com> 68c2ecf20Sopenharmony_ci// 78c2ecf20Sopenharmony_ci// Hardware interface for audio DSP on i.MX8M 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/firmware.h> 108c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 118c2ecf20Sopenharmony_ci#include <linux/of_address.h> 128c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <sound/sof.h> 168c2ecf20Sopenharmony_ci#include <sound/sof/xtensa.h> 178c2ecf20Sopenharmony_ci#include <linux/firmware/imx/dsp.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include "../ops.h" 208c2ecf20Sopenharmony_ci#include "imx-common.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define MBOX_OFFSET 0x800000 238c2ecf20Sopenharmony_ci#define MBOX_SIZE 0x1000 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistruct imx8m_priv { 268c2ecf20Sopenharmony_ci struct device *dev; 278c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci /* DSP IPC handler */ 308c2ecf20Sopenharmony_ci struct imx_dsp_ipc *dsp_ipc; 318c2ecf20Sopenharmony_ci struct platform_device *ipc_dev; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic void imx8m_get_reply(struct snd_sof_dev *sdev) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci struct snd_sof_ipc_msg *msg = sdev->msg; 378c2ecf20Sopenharmony_ci struct sof_ipc_reply reply; 388c2ecf20Sopenharmony_ci int ret = 0; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci if (!msg) { 418c2ecf20Sopenharmony_ci dev_warn(sdev->dev, "unexpected ipc interrupt\n"); 428c2ecf20Sopenharmony_ci return; 438c2ecf20Sopenharmony_ci } 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* get reply */ 468c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci if (reply.error < 0) { 498c2ecf20Sopenharmony_ci memcpy(msg->reply_data, &reply, sizeof(reply)); 508c2ecf20Sopenharmony_ci ret = reply.error; 518c2ecf20Sopenharmony_ci } else { 528c2ecf20Sopenharmony_ci /* reply has correct size? */ 538c2ecf20Sopenharmony_ci if (reply.hdr.size != msg->reply_size) { 548c2ecf20Sopenharmony_ci dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", 558c2ecf20Sopenharmony_ci msg->reply_size, reply.hdr.size); 568c2ecf20Sopenharmony_ci ret = -EINVAL; 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* read the message */ 608c2ecf20Sopenharmony_ci if (msg->reply_size > 0) 618c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, sdev->host_box.offset, 628c2ecf20Sopenharmony_ci msg->reply_data, msg->reply_size); 638c2ecf20Sopenharmony_ci } 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci msg->reply_error = ret; 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int imx8m_get_mailbox_offset(struct snd_sof_dev *sdev) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci return MBOX_OFFSET; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int imx8m_get_window_offset(struct snd_sof_dev *sdev, u32 id) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci return MBOX_OFFSET; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci struct imx8m_priv *priv = imx_dsp_get_data(ipc); 818c2ecf20Sopenharmony_ci unsigned long flags; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->sdev->ipc_lock, flags); 848c2ecf20Sopenharmony_ci imx8m_get_reply(priv->sdev); 858c2ecf20Sopenharmony_ci snd_sof_ipc_reply(priv->sdev, 0); 868c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic void imx8m_dsp_handle_request(struct imx_dsp_ipc *ipc) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct imx8m_priv *priv = imx_dsp_get_data(ipc); 928c2ecf20Sopenharmony_ci u32 p; /* Panic code */ 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci /* Read the message from the debug box. */ 958c2ecf20Sopenharmony_ci sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p)); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci /* Check to see if the message is a panic code (0x0dead***) */ 988c2ecf20Sopenharmony_ci if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) 998c2ecf20Sopenharmony_ci snd_sof_dsp_panic(priv->sdev, p); 1008c2ecf20Sopenharmony_ci else 1018c2ecf20Sopenharmony_ci snd_sof_ipc_msgs_rx(priv->sdev); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic struct imx_dsp_ops imx8m_dsp_ops = { 1058c2ecf20Sopenharmony_ci .handle_reply = imx8m_dsp_handle_reply, 1068c2ecf20Sopenharmony_ci .handle_request = imx8m_dsp_handle_request, 1078c2ecf20Sopenharmony_ci}; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci struct imx8m_priv *priv = sdev->pdata->hw_pdata; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, 1148c2ecf20Sopenharmony_ci msg->msg_size); 1158c2ecf20Sopenharmony_ci imx_dsp_ring_doorbell(priv->dsp_ipc, 0); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci return 0; 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci/* 1218c2ecf20Sopenharmony_ci * DSP control. 1228c2ecf20Sopenharmony_ci */ 1238c2ecf20Sopenharmony_cistatic int imx8m_run(struct snd_sof_dev *sdev) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci /* TODO: start DSP using Audio MIX bits */ 1268c2ecf20Sopenharmony_ci return 0; 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic int imx8m_probe(struct snd_sof_dev *sdev) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci struct platform_device *pdev = 1328c2ecf20Sopenharmony_ci container_of(sdev->dev, struct platform_device, dev); 1338c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1348c2ecf20Sopenharmony_ci struct device_node *res_node; 1358c2ecf20Sopenharmony_ci struct resource *mmio; 1368c2ecf20Sopenharmony_ci struct imx8m_priv *priv; 1378c2ecf20Sopenharmony_ci struct resource res; 1388c2ecf20Sopenharmony_ci u32 base, size; 1398c2ecf20Sopenharmony_ci int ret = 0; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1428c2ecf20Sopenharmony_ci if (!priv) 1438c2ecf20Sopenharmony_ci return -ENOMEM; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci sdev->pdata->hw_pdata = priv; 1468c2ecf20Sopenharmony_ci priv->dev = sdev->dev; 1478c2ecf20Sopenharmony_ci priv->sdev = sdev; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", 1508c2ecf20Sopenharmony_ci PLATFORM_DEVID_NONE, 1518c2ecf20Sopenharmony_ci pdev, sizeof(*pdev)); 1528c2ecf20Sopenharmony_ci if (IS_ERR(priv->ipc_dev)) 1538c2ecf20Sopenharmony_ci return PTR_ERR(priv->ipc_dev); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); 1568c2ecf20Sopenharmony_ci if (!priv->dsp_ipc) { 1578c2ecf20Sopenharmony_ci /* DSP IPC driver not probed yet, try later */ 1588c2ecf20Sopenharmony_ci ret = -EPROBE_DEFER; 1598c2ecf20Sopenharmony_ci dev_err(sdev->dev, "Failed to get drvdata\n"); 1608c2ecf20Sopenharmony_ci goto exit_pdev_unregister; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci imx_dsp_set_data(priv->dsp_ipc, priv); 1648c2ecf20Sopenharmony_ci priv->dsp_ipc->ops = &imx8m_dsp_ops; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* DSP base */ 1678c2ecf20Sopenharmony_ci mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1688c2ecf20Sopenharmony_ci if (mmio) { 1698c2ecf20Sopenharmony_ci base = mmio->start; 1708c2ecf20Sopenharmony_ci size = resource_size(mmio); 1718c2ecf20Sopenharmony_ci } else { 1728c2ecf20Sopenharmony_ci dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); 1738c2ecf20Sopenharmony_ci ret = -EINVAL; 1748c2ecf20Sopenharmony_ci goto exit_pdev_unregister; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); 1788c2ecf20Sopenharmony_ci if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { 1798c2ecf20Sopenharmony_ci dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", 1808c2ecf20Sopenharmony_ci base, size); 1818c2ecf20Sopenharmony_ci ret = -ENODEV; 1828c2ecf20Sopenharmony_ci goto exit_pdev_unregister; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci res_node = of_parse_phandle(np, "memory-region", 0); 1878c2ecf20Sopenharmony_ci if (!res_node) { 1888c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get memory region node\n"); 1898c2ecf20Sopenharmony_ci ret = -ENODEV; 1908c2ecf20Sopenharmony_ci goto exit_pdev_unregister; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci ret = of_address_to_resource(res_node, 0, &res); 1948c2ecf20Sopenharmony_ci of_node_put(res_node); 1958c2ecf20Sopenharmony_ci if (ret) { 1968c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get reserved region address\n"); 1978c2ecf20Sopenharmony_ci goto exit_pdev_unregister; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, 2018c2ecf20Sopenharmony_ci resource_size(&res)); 2028c2ecf20Sopenharmony_ci if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { 2038c2ecf20Sopenharmony_ci dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", 2048c2ecf20Sopenharmony_ci base, size); 2058c2ecf20Sopenharmony_ci ret = -ENOMEM; 2068c2ecf20Sopenharmony_ci goto exit_pdev_unregister; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* set default mailbox offset for FW ready message */ 2118c2ecf20Sopenharmony_ci sdev->dsp_box.offset = MBOX_OFFSET; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci return 0; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ciexit_pdev_unregister: 2168c2ecf20Sopenharmony_ci platform_device_unregister(priv->ipc_dev); 2178c2ecf20Sopenharmony_ci return ret; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int imx8m_remove(struct snd_sof_dev *sdev) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci struct imx8m_priv *priv = sdev->pdata->hw_pdata; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci platform_device_unregister(priv->ipc_dev); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci/* on i.MX8 there is 1 to 1 match between type and BAR idx */ 2308c2ecf20Sopenharmony_cistatic int imx8m_get_bar_index(struct snd_sof_dev *sdev, u32 type) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci return type; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic void imx8m_ipc_msg_data(struct snd_sof_dev *sdev, 2368c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream, 2378c2ecf20Sopenharmony_ci void *p, size_t sz) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic int imx8m_ipc_pcm_params(struct snd_sof_dev *sdev, 2438c2ecf20Sopenharmony_ci struct snd_pcm_substream *substream, 2448c2ecf20Sopenharmony_ci const struct sof_ipc_pcm_params_reply *reply) 2458c2ecf20Sopenharmony_ci{ 2468c2ecf20Sopenharmony_ci return 0; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver imx8m_dai[] = { 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci .name = "sai3", 2528c2ecf20Sopenharmony_ci .playback = { 2538c2ecf20Sopenharmony_ci .channels_min = 1, 2548c2ecf20Sopenharmony_ci .channels_max = 32, 2558c2ecf20Sopenharmony_ci }, 2568c2ecf20Sopenharmony_ci .capture = { 2578c2ecf20Sopenharmony_ci .channels_min = 1, 2588c2ecf20Sopenharmony_ci .channels_max = 32, 2598c2ecf20Sopenharmony_ci }, 2608c2ecf20Sopenharmony_ci}, 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci/* i.MX8 ops */ 2648c2ecf20Sopenharmony_cistruct snd_sof_dsp_ops sof_imx8m_ops = { 2658c2ecf20Sopenharmony_ci /* probe and remove */ 2668c2ecf20Sopenharmony_ci .probe = imx8m_probe, 2678c2ecf20Sopenharmony_ci .remove = imx8m_remove, 2688c2ecf20Sopenharmony_ci /* DSP core boot */ 2698c2ecf20Sopenharmony_ci .run = imx8m_run, 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* Block IO */ 2728c2ecf20Sopenharmony_ci .block_read = sof_block_read, 2738c2ecf20Sopenharmony_ci .block_write = sof_block_write, 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* Module IO */ 2768c2ecf20Sopenharmony_ci .read64 = sof_io_read64, 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci /* ipc */ 2798c2ecf20Sopenharmony_ci .send_msg = imx8m_send_msg, 2808c2ecf20Sopenharmony_ci .fw_ready = sof_fw_ready, 2818c2ecf20Sopenharmony_ci .get_mailbox_offset = imx8m_get_mailbox_offset, 2828c2ecf20Sopenharmony_ci .get_window_offset = imx8m_get_window_offset, 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci .ipc_msg_data = imx8m_ipc_msg_data, 2858c2ecf20Sopenharmony_ci .ipc_pcm_params = imx8m_ipc_pcm_params, 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci /* module loading */ 2888c2ecf20Sopenharmony_ci .load_module = snd_sof_parse_module_memcpy, 2898c2ecf20Sopenharmony_ci .get_bar_index = imx8m_get_bar_index, 2908c2ecf20Sopenharmony_ci /* firmware loading */ 2918c2ecf20Sopenharmony_ci .load_firmware = snd_sof_load_firmware_memcpy, 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci /* Debug information */ 2948c2ecf20Sopenharmony_ci .dbg_dump = imx8_dump, 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci /* Firmware ops */ 2978c2ecf20Sopenharmony_ci .arch_ops = &sof_xtensa_arch_ops, 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci /* DAI drivers */ 3008c2ecf20Sopenharmony_ci .drv = imx8m_dai, 3018c2ecf20Sopenharmony_ci .num_drv = ARRAY_SIZE(imx8m_dai), 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci .hw_info = SNDRV_PCM_INFO_MMAP | 3048c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 3058c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 3068c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_PAUSE | 3078c2ecf20Sopenharmony_ci SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, 3088c2ecf20Sopenharmony_ci}; 3098c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sof_imx8m_ops); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ciMODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); 3128c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 313