Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: SOF: imx: Add i.MX8 HW support

Add support for the audio DSP hardware found on NXP i.MX8 platform.

Signed-off-by: Daniel Baluta <daniel.baluta@nxp.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20190821164730.7385-4-pierre-louis.bossart@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Daniel Baluta and committed by
Mark Brown
202acc56 b9132b89

+422
+1
sound/soc/sof/Kconfig
··· 173 173 When selected, the probe is handled in two steps, for example to 174 174 avoid lockdeps if request_module is used in the probe. 175 175 176 + source "sound/soc/sof/imx/Kconfig" 176 177 source "sound/soc/sof/intel/Kconfig" 177 178 source "sound/soc/sof/xtensa/Kconfig" 178 179
+1
sound/soc/sof/Makefile
··· 18 18 obj-$(CONFIG_SND_SOC_SOF_PCI) += snd-sof-pci.o 19 19 20 20 obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ 21 + obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ 21 22 obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/
+22
sound/soc/sof/imx/Kconfig
··· 1 + # SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 2 + 3 + config SND_SOC_SOF_IMX_TOPLEVEL 4 + bool "SOF support for NXP i.MX audio DSPs" 5 + depends on ARM64 && SND_SOC_SOF_OF || COMPILE_TEST 6 + help 7 + This adds support for Sound Open Firmware for NXP i.MX platforms. 8 + Say Y if you have such a device. 9 + If unsure select "N". 10 + 11 + if SND_SOC_SOF_IMX_TOPLEVEL 12 + 13 + config SND_SOC_SOF_IMX8 14 + tristate "SOF support for i.MX8" 15 + depends on IMX_SCU 16 + depends on IMX_DSP 17 + help 18 + This adds support for Sound Open Firmware for NXP i.MX8 platforms 19 + Say Y if you have such a device. 20 + If unsure select "N". 21 + 22 + endif ## SND_SOC_SOF_IMX_IMX_TOPLEVEL
+4
sound/soc/sof/imx/Makefile
··· 1 + # SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 2 + snd-sof-imx8-objs := imx8.o 3 + 4 + obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o
+394
sound/soc/sof/imx/imx8.c
··· 1 + // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 2 + // 3 + // Copyright 2019 NXP 4 + // 5 + // Author: Daniel Baluta <daniel.baluta@nxp.com> 6 + // 7 + // Hardware interface for audio DSP on i.MX8 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 + #include <linux/pm_domain.h> 14 + 15 + #include <linux/module.h> 16 + #include <sound/sof.h> 17 + #include <sound/sof/xtensa.h> 18 + #include <linux/firmware/imx/ipc.h> 19 + #include <linux/firmware/imx/dsp.h> 20 + 21 + #include <linux/firmware/imx/svc/misc.h> 22 + #include <dt-bindings/firmware/imx/rsrc.h> 23 + #include "../ops.h" 24 + 25 + /* DSP memories */ 26 + #define IRAM_OFFSET 0x10000 27 + #define IRAM_SIZE (2 * 1024) 28 + #define DRAM0_OFFSET 0x0 29 + #define DRAM0_SIZE (32 * 1024) 30 + #define DRAM1_OFFSET 0x8000 31 + #define DRAM1_SIZE (32 * 1024) 32 + #define SYSRAM_OFFSET 0x18000 33 + #define SYSRAM_SIZE (256 * 1024) 34 + #define SYSROM_OFFSET 0x58000 35 + #define SYSROM_SIZE (192 * 1024) 36 + 37 + #define RESET_VECTOR_VADDR 0x596f8000 38 + 39 + #define MBOX_OFFSET 0x800000 40 + #define MBOX_SIZE 0x1000 41 + 42 + struct imx8_priv { 43 + struct device *dev; 44 + struct snd_sof_dev *sdev; 45 + 46 + /* DSP IPC handler */ 47 + struct imx_dsp_ipc *dsp_ipc; 48 + struct platform_device *ipc_dev; 49 + 50 + /* System Controller IPC handler */ 51 + struct imx_sc_ipc *sc_ipc; 52 + 53 + /* Power domain handling */ 54 + int num_domains; 55 + struct device **pd_dev; 56 + struct device_link **link; 57 + 58 + }; 59 + 60 + static void imx8_get_reply(struct snd_sof_dev *sdev) 61 + { 62 + struct snd_sof_ipc_msg *msg = sdev->msg; 63 + struct sof_ipc_reply reply; 64 + int ret = 0; 65 + 66 + if (!msg) { 67 + dev_warn(sdev->dev, "unexpected ipc interrupt\n"); 68 + return; 69 + } 70 + 71 + /* get reply */ 72 + sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); 73 + 74 + if (reply.error < 0) { 75 + memcpy(msg->reply_data, &reply, sizeof(reply)); 76 + ret = reply.error; 77 + } else { 78 + /* reply has correct size? */ 79 + if (reply.hdr.size != msg->reply_size) { 80 + dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", 81 + msg->reply_size, reply.hdr.size); 82 + ret = -EINVAL; 83 + } 84 + 85 + /* read the message */ 86 + if (msg->reply_size > 0) 87 + sof_mailbox_read(sdev, sdev->host_box.offset, 88 + msg->reply_data, msg->reply_size); 89 + } 90 + 91 + msg->reply_error = ret; 92 + } 93 + 94 + static int imx8_get_mailbox_offset(struct snd_sof_dev *sdev) 95 + { 96 + return MBOX_OFFSET; 97 + } 98 + 99 + static int imx8_get_window_offset(struct snd_sof_dev *sdev, u32 id) 100 + { 101 + return MBOX_OFFSET; 102 + } 103 + 104 + void imx8_dsp_handle_reply(struct imx_dsp_ipc *ipc) 105 + { 106 + struct imx8_priv *priv = imx_dsp_get_data(ipc); 107 + unsigned long flags; 108 + 109 + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); 110 + imx8_get_reply(priv->sdev); 111 + snd_sof_ipc_reply(priv->sdev, 0); 112 + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); 113 + } 114 + 115 + void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc) 116 + { 117 + struct imx8_priv *priv = imx_dsp_get_data(ipc); 118 + 119 + snd_sof_ipc_msgs_rx(priv->sdev); 120 + } 121 + 122 + struct imx_dsp_ops dsp_ops = { 123 + .handle_reply = imx8_dsp_handle_reply, 124 + .handle_request = imx8_dsp_handle_request, 125 + }; 126 + 127 + static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) 128 + { 129 + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; 130 + 131 + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, 132 + msg->msg_size); 133 + imx_dsp_ring_doorbell(priv->dsp_ipc, 0); 134 + 135 + return 0; 136 + } 137 + 138 + /* 139 + * DSP control. 140 + */ 141 + static int imx8_run(struct snd_sof_dev *sdev) 142 + { 143 + struct imx8_priv *dsp_priv = (struct imx8_priv *)sdev->private; 144 + int ret; 145 + 146 + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, 147 + IMX_SC_C_OFS_SEL, 1); 148 + if (ret < 0) { 149 + dev_err(sdev->dev, "Error system address offset source select\n"); 150 + return ret; 151 + } 152 + 153 + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, 154 + IMX_SC_C_OFS_AUDIO, 0x80); 155 + if (ret < 0) { 156 + dev_err(sdev->dev, "Error system address offset of AUDIO\n"); 157 + return ret; 158 + } 159 + 160 + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, 161 + IMX_SC_C_OFS_PERIPH, 0x5A); 162 + if (ret < 0) { 163 + dev_err(sdev->dev, "Error system address offset of PERIPH %d\n", 164 + ret); 165 + return ret; 166 + } 167 + 168 + ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP, 169 + IMX_SC_C_OFS_IRQ, 0x51); 170 + if (ret < 0) { 171 + dev_err(sdev->dev, "Error system address offset of IRQ\n"); 172 + return ret; 173 + } 174 + 175 + imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true, 176 + RESET_VECTOR_VADDR); 177 + 178 + return 0; 179 + } 180 + 181 + static int imx8_probe(struct snd_sof_dev *sdev) 182 + { 183 + struct platform_device *pdev = 184 + container_of(sdev->dev, struct platform_device, dev); 185 + struct device_node *np = pdev->dev.of_node; 186 + struct device_node *res_node; 187 + struct resource *mmio; 188 + struct imx8_priv *priv; 189 + struct resource res; 190 + u32 base, size; 191 + int ret = 0; 192 + int i; 193 + 194 + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 195 + if (!priv) 196 + return -ENOMEM; 197 + 198 + sdev->private = priv; 199 + priv->dev = sdev->dev; 200 + priv->sdev = sdev; 201 + 202 + /* power up device associated power domains */ 203 + priv->num_domains = of_count_phandle_with_args(np, "power-domains", 204 + "#power-domain-cells"); 205 + if (priv->num_domains < 0) { 206 + dev_err(sdev->dev, "no power-domains property in %pOF\n", np); 207 + return priv->num_domains; 208 + } 209 + 210 + priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains, 211 + sizeof(*priv->pd_dev), GFP_KERNEL); 212 + if (!priv) 213 + return -ENOMEM; 214 + 215 + priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains, 216 + sizeof(*priv->link), GFP_KERNEL); 217 + if (!priv->link) 218 + return -ENOMEM; 219 + 220 + for (i = 0; i < priv->num_domains; i++) { 221 + priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i); 222 + if (IS_ERR(priv->pd_dev[i])) { 223 + ret = PTR_ERR(priv->pd_dev[i]); 224 + goto exit_unroll_pm; 225 + } 226 + priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i], 227 + DL_FLAG_STATELESS | 228 + DL_FLAG_PM_RUNTIME | 229 + DL_FLAG_RPM_ACTIVE); 230 + if (IS_ERR(priv->link[i])) { 231 + ret = PTR_ERR(priv->link[i]); 232 + dev_pm_domain_detach(priv->pd_dev[i], false); 233 + goto exit_unroll_pm; 234 + } 235 + } 236 + 237 + ret = imx_scu_get_handle(&priv->sc_ipc); 238 + if (ret) { 239 + dev_err(sdev->dev, "Cannot obtain SCU handle (err = %d)\n", 240 + ret); 241 + goto exit_unroll_pm; 242 + } 243 + 244 + priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", 245 + PLATFORM_DEVID_NONE, 246 + pdev, sizeof(*pdev)); 247 + if (IS_ERR(priv->ipc_dev)) { 248 + ret = PTR_ERR(priv->ipc_dev); 249 + goto exit_unroll_pm; 250 + } 251 + 252 + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); 253 + if (!priv->dsp_ipc) { 254 + /* DSP IPC driver not probed yet, try later */ 255 + ret = -EPROBE_DEFER; 256 + dev_err(sdev->dev, "Failed to get drvdata\n"); 257 + goto exit_pdev_unregister; 258 + } 259 + 260 + imx_dsp_set_data(priv->dsp_ipc, priv); 261 + priv->dsp_ipc->ops = &dsp_ops; 262 + 263 + /* DSP base */ 264 + mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); 265 + if (mmio) { 266 + base = mmio->start; 267 + size = resource_size(mmio); 268 + } else { 269 + dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); 270 + ret = -EINVAL; 271 + goto exit_pdev_unregister; 272 + } 273 + 274 + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); 275 + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { 276 + dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", 277 + base, size); 278 + ret = -ENODEV; 279 + goto exit_pdev_unregister; 280 + } 281 + sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; 282 + 283 + res_node = of_parse_phandle(np, "memory-region", 0); 284 + if (!res_node) { 285 + dev_err(&pdev->dev, "failed to get memory region node\n"); 286 + ret = -ENODEV; 287 + goto exit_pdev_unregister; 288 + } 289 + 290 + ret = of_address_to_resource(res_node, 0, &res); 291 + if (ret) { 292 + dev_err(&pdev->dev, "failed to get reserved region address\n"); 293 + goto exit_pdev_unregister; 294 + } 295 + 296 + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, 297 + res.end - res.start + 298 + 1); 299 + if (IS_ERR(sdev->bar[SOF_FW_BLK_TYPE_SRAM])) { 300 + dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", 301 + base, size); 302 + ret = PTR_ERR(sdev->bar[SOF_FW_BLK_TYPE_SRAM]); 303 + goto exit_pdev_unregister; 304 + } 305 + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; 306 + 307 + return 0; 308 + 309 + exit_pdev_unregister: 310 + platform_device_unregister(priv->ipc_dev); 311 + exit_unroll_pm: 312 + while (--i >= 0) { 313 + device_link_del(priv->link[i]); 314 + dev_pm_domain_detach(priv->pd_dev[i], false); 315 + } 316 + 317 + return ret; 318 + } 319 + 320 + static int imx8_remove(struct snd_sof_dev *sdev) 321 + { 322 + struct imx8_priv *priv = (struct imx8_priv *)sdev->private; 323 + int i; 324 + 325 + platform_device_unregister(priv->ipc_dev); 326 + 327 + for (i = 0; i < priv->num_domains; i++) { 328 + device_link_del(priv->link[i]); 329 + dev_pm_domain_detach(priv->pd_dev[i], false); 330 + } 331 + 332 + return 0; 333 + } 334 + 335 + /* on i.MX8 there is 1 to 1 match between type and BAR idx */ 336 + int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type) 337 + { 338 + return type; 339 + } 340 + 341 + void imx8_ipc_msg_data(struct snd_sof_dev *sdev, 342 + struct snd_pcm_substream *substream, 343 + void *p, size_t sz) 344 + { 345 + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); 346 + } 347 + 348 + int imx8_ipc_pcm_params(struct snd_sof_dev *sdev, 349 + struct snd_pcm_substream *substream, 350 + const struct sof_ipc_pcm_params_reply *reply) 351 + { 352 + return 0; 353 + } 354 + 355 + static struct snd_soc_dai_driver imx8_dai[] = { 356 + { 357 + .name = "esai-port", 358 + }, 359 + }; 360 + 361 + /* i.MX8 ops */ 362 + struct snd_sof_dsp_ops sof_imx8_ops = { 363 + /* probe and remove */ 364 + .probe = imx8_probe, 365 + .remove = imx8_remove, 366 + /* DSP core boot */ 367 + .run = imx8_run, 368 + 369 + /* Block IO */ 370 + .block_read = sof_block_read, 371 + .block_write = sof_block_write, 372 + 373 + /* ipc */ 374 + .send_msg = imx8_send_msg, 375 + .fw_ready = sof_fw_ready, 376 + .get_mailbox_offset = imx8_get_mailbox_offset, 377 + .get_window_offset = imx8_get_window_offset, 378 + 379 + .ipc_msg_data = imx8_ipc_msg_data, 380 + .ipc_pcm_params = imx8_ipc_pcm_params, 381 + 382 + /* module loading */ 383 + .load_module = snd_sof_parse_module_memcpy, 384 + .get_bar_index = imx8_get_bar_index, 385 + /* firmware loading */ 386 + .load_firmware = snd_sof_load_firmware_memcpy, 387 + 388 + /* DAI drivers */ 389 + .drv = imx8_dai, 390 + .num_drv = 1, /* we have only 1 ESAI interface on i.MX8 */ 391 + }; 392 + EXPORT_SYMBOL(sof_imx8_ops); 393 + 394 + MODULE_LICENSE("Dual BSD/GPL");