1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 2 // 3 // Copyright 2020 NXP 4 // 5 // Author: Daniel Baluta <daniel.baluta@nxp.com> 6 // 7 // Hardware interface for audio DSP on i.MX8M 8 9 #include <linux/firmware.h> 10 #include <linux/of_platform.h> 11 #include <linux/of_address.h> 12 #include <linux/of_irq.h> 13 14 #include <linux/module.h> 15 #include <sound/sof.h> 16 #include <sound/sof/xtensa.h> 17 #include <linux/firmware/imx/dsp.h> 18 19 #include "../ops.h" 20 21 #define MBOX_OFFSET 0x800000 22 #define MBOX_SIZE 0x1000 23 24 struct imx8m_priv { 25 struct device *dev; 26 struct snd_sof_dev *sdev; 27 28 /* DSP IPC handler */ 29 struct imx_dsp_ipc *dsp_ipc; 30 struct platform_device *ipc_dev; 31 }; 32 33 static void imx8m_get_reply(struct snd_sof_dev *sdev) 34 { 35 struct snd_sof_ipc_msg *msg = sdev->msg; 36 struct sof_ipc_reply reply; 37 int ret = 0; 38 39 if (!msg) { 40 dev_warn(sdev->dev, "unexpected ipc interrupt\n"); 41 return; 42 } 43 44 /* get reply */ 45 sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); 46 47 if (reply.error < 0) { 48 memcpy(msg->reply_data, &reply, sizeof(reply)); 49 ret = reply.error; 50 } else { 51 /* reply has correct size? */ 52 if (reply.hdr.size != msg->reply_size) { 53 dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", 54 msg->reply_size, reply.hdr.size); 55 ret = -EINVAL; 56 } 57 58 /* read the message */ 59 if (msg->reply_size > 0) 60 sof_mailbox_read(sdev, sdev->host_box.offset, 61 msg->reply_data, msg->reply_size); 62 } 63 64 msg->reply_error = ret; 65 } 66 67 static int imx8m_get_mailbox_offset(struct snd_sof_dev *sdev) 68 { 69 return MBOX_OFFSET; 70 } 71 72 static int imx8m_get_window_offset(struct snd_sof_dev *sdev, u32 id) 73 { 74 return MBOX_OFFSET; 75 } 76 77 static void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc) 78 { 79 struct imx8m_priv *priv = imx_dsp_get_data(ipc); 80 unsigned long flags; 81 82 spin_lock_irqsave(&priv->sdev->ipc_lock, flags); 83 imx8m_get_reply(priv->sdev); 84 snd_sof_ipc_reply(priv->sdev, 0); 85 spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); 86 } 87 88 static void imx8m_dsp_handle_request(struct imx_dsp_ipc *ipc) 89 { 90 struct imx8m_priv *priv = imx_dsp_get_data(ipc); 91 92 snd_sof_ipc_msgs_rx(priv->sdev); 93 } 94 95 static struct imx_dsp_ops imx8m_dsp_ops = { 96 .handle_reply = imx8m_dsp_handle_reply, 97 .handle_request = imx8m_dsp_handle_request, 98 }; 99 100 static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) 101 { 102 struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; 103 104 sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, 105 msg->msg_size); 106 imx_dsp_ring_doorbell(priv->dsp_ipc, 0); 107 108 return 0; 109 } 110 111 /* 112 * DSP control. 113 */ 114 static int imx8m_run(struct snd_sof_dev *sdev) 115 { 116 /* TODO: start DSP using Audio MIX bits */ 117 return 0; 118 } 119 120 static int imx8m_probe(struct snd_sof_dev *sdev) 121 { 122 struct platform_device *pdev = 123 container_of(sdev->dev, struct platform_device, dev); 124 struct device_node *np = pdev->dev.of_node; 125 struct device_node *res_node; 126 struct resource *mmio; 127 struct imx8m_priv *priv; 128 struct resource res; 129 u32 base, size; 130 int ret = 0; 131 132 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 133 if (!priv) 134 return -ENOMEM; 135 136 sdev->private = priv; 137 priv->dev = sdev->dev; 138 priv->sdev = sdev; 139 140 priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", 141 PLATFORM_DEVID_NONE, 142 pdev, sizeof(*pdev)); 143 if (IS_ERR(priv->ipc_dev)) 144 return PTR_ERR(priv->ipc_dev); 145 146 priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); 147 if (!priv->dsp_ipc) { 148 /* DSP IPC driver not probed yet, try later */ 149 ret = -EPROBE_DEFER; 150 dev_err(sdev->dev, "Failed to get drvdata\n"); 151 goto exit_pdev_unregister; 152 } 153 154 imx_dsp_set_data(priv->dsp_ipc, priv); 155 priv->dsp_ipc->ops = &imx8m_dsp_ops; 156 157 /* DSP base */ 158 mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); 159 if (mmio) { 160 base = mmio->start; 161 size = resource_size(mmio); 162 } else { 163 dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); 164 ret = -EINVAL; 165 goto exit_pdev_unregister; 166 } 167 168 sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); 169 if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { 170 dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", 171 base, size); 172 ret = -ENODEV; 173 goto exit_pdev_unregister; 174 } 175 sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; 176 177 res_node = of_parse_phandle(np, "memory-region", 0); 178 if (!res_node) { 179 dev_err(&pdev->dev, "failed to get memory region node\n"); 180 ret = -ENODEV; 181 goto exit_pdev_unregister; 182 } 183 184 ret = of_address_to_resource(res_node, 0, &res); 185 if (ret) { 186 dev_err(&pdev->dev, "failed to get reserved region address\n"); 187 goto exit_pdev_unregister; 188 } 189 190 sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, 191 res.end - res.start + 192 1); 193 if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { 194 dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", 195 base, size); 196 ret = -ENOMEM; 197 goto exit_pdev_unregister; 198 } 199 sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; 200 201 /* set default mailbox offset for FW ready message */ 202 sdev->dsp_box.offset = MBOX_OFFSET; 203 204 return 0; 205 206 exit_pdev_unregister: 207 platform_device_unregister(priv->ipc_dev); 208 return ret; 209 } 210 211 static int imx8m_remove(struct snd_sof_dev *sdev) 212 { 213 struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private; 214 215 platform_device_unregister(priv->ipc_dev); 216 217 return 0; 218 } 219 220 /* on i.MX8 there is 1 to 1 match between type and BAR idx */ 221 static int imx8m_get_bar_index(struct snd_sof_dev *sdev, u32 type) 222 { 223 return type; 224 } 225 226 static void imx8m_ipc_msg_data(struct snd_sof_dev *sdev, 227 struct snd_pcm_substream *substream, 228 void *p, size_t sz) 229 { 230 sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); 231 } 232 233 static int imx8m_ipc_pcm_params(struct snd_sof_dev *sdev, 234 struct snd_pcm_substream *substream, 235 const struct sof_ipc_pcm_params_reply *reply) 236 { 237 return 0; 238 } 239 240 static struct snd_soc_dai_driver imx8m_dai[] = { 241 { 242 .name = "sai-port", 243 }, 244 }; 245 246 /* i.MX8 ops */ 247 struct snd_sof_dsp_ops sof_imx8m_ops = { 248 /* probe and remove */ 249 .probe = imx8m_probe, 250 .remove = imx8m_remove, 251 /* DSP core boot */ 252 .run = imx8m_run, 253 254 /* Block IO */ 255 .block_read = sof_block_read, 256 .block_write = sof_block_write, 257 258 /* ipc */ 259 .send_msg = imx8m_send_msg, 260 .fw_ready = sof_fw_ready, 261 .get_mailbox_offset = imx8m_get_mailbox_offset, 262 .get_window_offset = imx8m_get_window_offset, 263 264 .ipc_msg_data = imx8m_ipc_msg_data, 265 .ipc_pcm_params = imx8m_ipc_pcm_params, 266 267 /* module loading */ 268 .load_module = snd_sof_parse_module_memcpy, 269 .get_bar_index = imx8m_get_bar_index, 270 /* firmware loading */ 271 .load_firmware = snd_sof_load_firmware_memcpy, 272 273 /* DAI drivers */ 274 .drv = imx8m_dai, 275 .num_drv = 1, /* we have only 1 SAI interface on i.MX8M */ 276 277 .hw_info = SNDRV_PCM_INFO_MMAP | 278 SNDRV_PCM_INFO_MMAP_VALID | 279 SNDRV_PCM_INFO_INTERLEAVED | 280 SNDRV_PCM_INFO_PAUSE | 281 SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, 282 }; 283 EXPORT_SYMBOL(sof_imx8m_ops); 284 285 MODULE_LICENSE("Dual BSD/GPL"); 286