673 lines · c
1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)2//3// Copyright 2019 NXP4//5// Author: Daniel Baluta <daniel.baluta@nxp.com>6//7// Hardware interface for audio DSP on i.MX88 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#include "../sof-of-dev.h"25#include "imx-common.h"26 27/* DSP memories */28#define IRAM_OFFSET 0x1000029#define IRAM_SIZE (2 * 1024)30#define DRAM0_OFFSET 0x031#define DRAM0_SIZE (32 * 1024)32#define DRAM1_OFFSET 0x800033#define DRAM1_SIZE (32 * 1024)34#define SYSRAM_OFFSET 0x1800035#define SYSRAM_SIZE (256 * 1024)36#define SYSROM_OFFSET 0x5800037#define SYSROM_SIZE (192 * 1024)38 39#define RESET_VECTOR_VADDR 0x596f800040 41#define MBOX_OFFSET 0x80000042#define MBOX_SIZE 0x100043 44struct imx8_priv {45 struct device *dev;46 struct snd_sof_dev *sdev;47 48 /* DSP IPC handler */49 struct imx_dsp_ipc *dsp_ipc;50 struct platform_device *ipc_dev;51 52 /* System Controller IPC handler */53 struct imx_sc_ipc *sc_ipc;54 55 /* Power domain handling */56 int num_domains;57 struct device **pd_dev;58 struct device_link **link;59 60 struct clk_bulk_data *clks;61 int clk_num;62};63 64static int imx8_get_mailbox_offset(struct snd_sof_dev *sdev)65{66 return MBOX_OFFSET;67}68 69static int imx8_get_window_offset(struct snd_sof_dev *sdev, u32 id)70{71 return MBOX_OFFSET;72}73 74static void imx8_dsp_handle_reply(struct imx_dsp_ipc *ipc)75{76 struct imx8_priv *priv = imx_dsp_get_data(ipc);77 unsigned long flags;78 79 spin_lock_irqsave(&priv->sdev->ipc_lock, flags);80 snd_sof_ipc_process_reply(priv->sdev, 0);81 spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);82}83 84static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc)85{86 struct imx8_priv *priv = imx_dsp_get_data(ipc);87 u32 p; /* panic code */88 89 /* Read the message from the debug box. */90 sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p));91 92 /* Check to see if the message is a panic code (0x0dead***) */93 if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC)94 snd_sof_dsp_panic(priv->sdev, p, true);95 else96 snd_sof_ipc_msgs_rx(priv->sdev);97}98 99static struct imx_dsp_ops dsp_ops = {100 .handle_reply = imx8_dsp_handle_reply,101 .handle_request = imx8_dsp_handle_request,102};103 104static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)105{106 struct imx8_priv *priv = sdev->pdata->hw_pdata;107 108 sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,109 msg->msg_size);110 imx_dsp_ring_doorbell(priv->dsp_ipc, 0);111 112 return 0;113}114 115/*116 * DSP control.117 */118static int imx8x_run(struct snd_sof_dev *sdev)119{120 struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata;121 int ret;122 123 ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,124 IMX_SC_C_OFS_SEL, 1);125 if (ret < 0) {126 dev_err(sdev->dev, "Error system address offset source select\n");127 return ret;128 }129 130 ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,131 IMX_SC_C_OFS_AUDIO, 0x80);132 if (ret < 0) {133 dev_err(sdev->dev, "Error system address offset of AUDIO\n");134 return ret;135 }136 137 ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,138 IMX_SC_C_OFS_PERIPH, 0x5A);139 if (ret < 0) {140 dev_err(sdev->dev, "Error system address offset of PERIPH %d\n",141 ret);142 return ret;143 }144 145 ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,146 IMX_SC_C_OFS_IRQ, 0x51);147 if (ret < 0) {148 dev_err(sdev->dev, "Error system address offset of IRQ\n");149 return ret;150 }151 152 imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true,153 RESET_VECTOR_VADDR);154 155 return 0;156}157 158static int imx8_run(struct snd_sof_dev *sdev)159{160 struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata;161 int ret;162 163 ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,164 IMX_SC_C_OFS_SEL, 0);165 if (ret < 0) {166 dev_err(sdev->dev, "Error system address offset source select\n");167 return ret;168 }169 170 imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true,171 RESET_VECTOR_VADDR);172 173 return 0;174}175 176static int imx8_probe(struct snd_sof_dev *sdev)177{178 struct platform_device *pdev =179 container_of(sdev->dev, struct platform_device, dev);180 struct device_node *np = pdev->dev.of_node;181 struct device_node *res_node;182 struct resource *mmio;183 struct imx8_priv *priv;184 struct resource res;185 u32 base, size;186 int ret = 0;187 int i;188 189 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);190 if (!priv)191 return -ENOMEM;192 193 sdev->num_cores = 1;194 sdev->pdata->hw_pdata = priv;195 priv->dev = sdev->dev;196 priv->sdev = sdev;197 198 /* power up device associated power domains */199 priv->num_domains = of_count_phandle_with_args(np, "power-domains",200 "#power-domain-cells");201 if (priv->num_domains < 0) {202 dev_err(sdev->dev, "no power-domains property in %pOF\n", np);203 return priv->num_domains;204 }205 206 priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains,207 sizeof(*priv->pd_dev), GFP_KERNEL);208 if (!priv->pd_dev)209 return -ENOMEM;210 211 priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains,212 sizeof(*priv->link), GFP_KERNEL);213 if (!priv->link)214 return -ENOMEM;215 216 for (i = 0; i < priv->num_domains; i++) {217 priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i);218 if (IS_ERR(priv->pd_dev[i])) {219 ret = PTR_ERR(priv->pd_dev[i]);220 goto exit_unroll_pm;221 }222 priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i],223 DL_FLAG_STATELESS |224 DL_FLAG_PM_RUNTIME |225 DL_FLAG_RPM_ACTIVE);226 if (!priv->link[i]) {227 ret = -ENOMEM;228 dev_pm_domain_detach(priv->pd_dev[i], false);229 goto exit_unroll_pm;230 }231 }232 233 ret = imx_scu_get_handle(&priv->sc_ipc);234 if (ret) {235 dev_err(sdev->dev, "Cannot obtain SCU handle (err = %d)\n",236 ret);237 goto exit_unroll_pm;238 }239 240 priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp",241 PLATFORM_DEVID_NONE,242 pdev, sizeof(*pdev));243 if (IS_ERR(priv->ipc_dev)) {244 ret = PTR_ERR(priv->ipc_dev);245 goto exit_unroll_pm;246 }247 248 priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev);249 if (!priv->dsp_ipc) {250 /* DSP IPC driver not probed yet, try later */251 ret = -EPROBE_DEFER;252 dev_err(sdev->dev, "Failed to get drvdata\n");253 goto exit_pdev_unregister;254 }255 256 imx_dsp_set_data(priv->dsp_ipc, priv);257 priv->dsp_ipc->ops = &dsp_ops;258 259 /* DSP base */260 mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0);261 if (mmio) {262 base = mmio->start;263 size = resource_size(mmio);264 } else {265 dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n");266 ret = -EINVAL;267 goto exit_pdev_unregister;268 }269 270 sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size);271 if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) {272 dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n",273 base, size);274 ret = -ENODEV;275 goto exit_pdev_unregister;276 }277 sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM;278 279 res_node = of_parse_phandle(np, "memory-region", 0);280 if (!res_node) {281 dev_err(&pdev->dev, "failed to get memory region node\n");282 ret = -ENODEV;283 goto exit_pdev_unregister;284 }285 286 ret = of_address_to_resource(res_node, 0, &res);287 of_node_put(res_node);288 if (ret) {289 dev_err(&pdev->dev, "failed to get reserved region address\n");290 goto exit_pdev_unregister;291 }292 293 sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start,294 resource_size(&res));295 if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) {296 dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n",297 base, size);298 ret = -ENOMEM;299 goto exit_pdev_unregister;300 }301 sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM;302 303 /* set default mailbox offset for FW ready message */304 sdev->dsp_box.offset = MBOX_OFFSET;305 306 ret = devm_clk_bulk_get_all(sdev->dev, &priv->clks);307 if (ret < 0) {308 dev_err(sdev->dev, "failed to fetch clocks: %d\n", ret);309 goto exit_pdev_unregister;310 }311 priv->clk_num = ret;312 313 ret = clk_bulk_prepare_enable(priv->clk_num, priv->clks);314 if (ret < 0) {315 dev_err(sdev->dev, "failed to enable clocks: %d\n", ret);316 goto exit_pdev_unregister;317 }318 319 return 0;320 321exit_pdev_unregister:322 platform_device_unregister(priv->ipc_dev);323exit_unroll_pm:324 while (--i >= 0) {325 device_link_del(priv->link[i]);326 dev_pm_domain_detach(priv->pd_dev[i], false);327 }328 329 return ret;330}331 332static void imx8_remove(struct snd_sof_dev *sdev)333{334 struct imx8_priv *priv = sdev->pdata->hw_pdata;335 int i;336 337 clk_bulk_disable_unprepare(priv->clk_num, priv->clks);338 platform_device_unregister(priv->ipc_dev);339 340 for (i = 0; i < priv->num_domains; i++) {341 device_link_del(priv->link[i]);342 dev_pm_domain_detach(priv->pd_dev[i], false);343 }344}345 346/* on i.MX8 there is 1 to 1 match between type and BAR idx */347static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type)348{349 /* Only IRAM and SRAM bars are valid */350 switch (type) {351 case SOF_FW_BLK_TYPE_IRAM:352 case SOF_FW_BLK_TYPE_SRAM:353 return type;354 default:355 return -EINVAL;356 }357}358 359static void imx8_suspend(struct snd_sof_dev *sdev)360{361 int i;362 struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata;363 364 for (i = 0; i < DSP_MU_CHAN_NUM; i++)365 imx_dsp_free_channel(priv->dsp_ipc, i);366 367 clk_bulk_disable_unprepare(priv->clk_num, priv->clks);368}369 370static int imx8_resume(struct snd_sof_dev *sdev)371{372 struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata;373 int ret;374 int i;375 376 ret = clk_bulk_prepare_enable(priv->clk_num, priv->clks);377 if (ret < 0) {378 dev_err(sdev->dev, "failed to enable clocks: %d\n", ret);379 return ret;380 }381 382 for (i = 0; i < DSP_MU_CHAN_NUM; i++)383 imx_dsp_request_channel(priv->dsp_ipc, i);384 385 return 0;386}387 388static int imx8_dsp_runtime_resume(struct snd_sof_dev *sdev)389{390 int ret;391 const struct sof_dsp_power_state target_dsp_state = {392 .state = SOF_DSP_PM_D0,393 };394 395 ret = imx8_resume(sdev);396 if (ret < 0)397 return ret;398 399 return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);400}401 402static int imx8_dsp_runtime_suspend(struct snd_sof_dev *sdev)403{404 const struct sof_dsp_power_state target_dsp_state = {405 .state = SOF_DSP_PM_D3,406 };407 408 imx8_suspend(sdev);409 410 return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);411}412 413static int imx8_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state)414{415 const struct sof_dsp_power_state target_dsp_state = {416 .state = target_state,417 };418 419 if (!pm_runtime_suspended(sdev->dev))420 imx8_suspend(sdev);421 422 return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);423}424 425static int imx8_dsp_resume(struct snd_sof_dev *sdev)426{427 int ret;428 const struct sof_dsp_power_state target_dsp_state = {429 .state = SOF_DSP_PM_D0,430 };431 432 ret = imx8_resume(sdev);433 if (ret < 0)434 return ret;435 436 if (pm_runtime_suspended(sdev->dev)) {437 pm_runtime_disable(sdev->dev);438 pm_runtime_set_active(sdev->dev);439 pm_runtime_mark_last_busy(sdev->dev);440 pm_runtime_enable(sdev->dev);441 pm_runtime_idle(sdev->dev);442 }443 444 return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);445}446 447static struct snd_soc_dai_driver imx8_dai[] = {448{449 .name = "esai0",450 .playback = {451 .channels_min = 1,452 .channels_max = 8,453 },454 .capture = {455 .channels_min = 1,456 .channels_max = 8,457 },458},459{460 .name = "sai1",461 .playback = {462 .channels_min = 1,463 .channels_max = 32,464 },465 .capture = {466 .channels_min = 1,467 .channels_max = 32,468 },469},470};471 472static int imx8_dsp_set_power_state(struct snd_sof_dev *sdev,473 const struct sof_dsp_power_state *target_state)474{475 sdev->dsp_power_state = *target_state;476 477 return 0;478}479 480/* i.MX8 ops */481static const struct snd_sof_dsp_ops sof_imx8_ops = {482 /* probe and remove */483 .probe = imx8_probe,484 .remove = imx8_remove,485 /* DSP core boot */486 .run = imx8_run,487 488 /* Block IO */489 .block_read = sof_block_read,490 .block_write = sof_block_write,491 492 /* Mailbox IO */493 .mailbox_read = sof_mailbox_read,494 .mailbox_write = sof_mailbox_write,495 496 /* ipc */497 .send_msg = imx8_send_msg,498 .get_mailbox_offset = imx8_get_mailbox_offset,499 .get_window_offset = imx8_get_window_offset,500 501 .ipc_msg_data = sof_ipc_msg_data,502 .set_stream_data_offset = sof_set_stream_data_offset,503 504 .get_bar_index = imx8_get_bar_index,505 506 /* firmware loading */507 .load_firmware = snd_sof_load_firmware_memcpy,508 509 /* Debug information */510 .dbg_dump = imx8_dump,511 .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,512 513 /* stream callbacks */514 .pcm_open = sof_stream_pcm_open,515 .pcm_close = sof_stream_pcm_close,516 517 /* Firmware ops */518 .dsp_arch_ops = &sof_xtensa_arch_ops,519 520 /* DAI drivers */521 .drv = imx8_dai,522 .num_drv = ARRAY_SIZE(imx8_dai),523 524 /* ALSA HW info flags */525 .hw_info = SNDRV_PCM_INFO_MMAP |526 SNDRV_PCM_INFO_MMAP_VALID |527 SNDRV_PCM_INFO_INTERLEAVED |528 SNDRV_PCM_INFO_PAUSE |529 SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,530 531 /* PM */532 .runtime_suspend = imx8_dsp_runtime_suspend,533 .runtime_resume = imx8_dsp_runtime_resume,534 535 .suspend = imx8_dsp_suspend,536 .resume = imx8_dsp_resume,537 538 .set_power_state = imx8_dsp_set_power_state,539};540 541/* i.MX8X ops */542static const struct snd_sof_dsp_ops sof_imx8x_ops = {543 /* probe and remove */544 .probe = imx8_probe,545 .remove = imx8_remove,546 /* DSP core boot */547 .run = imx8x_run,548 549 /* Block IO */550 .block_read = sof_block_read,551 .block_write = sof_block_write,552 553 /* Mailbox IO */554 .mailbox_read = sof_mailbox_read,555 .mailbox_write = sof_mailbox_write,556 557 /* ipc */558 .send_msg = imx8_send_msg,559 .get_mailbox_offset = imx8_get_mailbox_offset,560 .get_window_offset = imx8_get_window_offset,561 562 .ipc_msg_data = sof_ipc_msg_data,563 .set_stream_data_offset = sof_set_stream_data_offset,564 565 .get_bar_index = imx8_get_bar_index,566 567 /* firmware loading */568 .load_firmware = snd_sof_load_firmware_memcpy,569 570 /* Debug information */571 .dbg_dump = imx8_dump,572 .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,573 574 /* stream callbacks */575 .pcm_open = sof_stream_pcm_open,576 .pcm_close = sof_stream_pcm_close,577 578 /* Firmware ops */579 .dsp_arch_ops = &sof_xtensa_arch_ops,580 581 /* DAI drivers */582 .drv = imx8_dai,583 .num_drv = ARRAY_SIZE(imx8_dai),584 585 /* PM */586 .runtime_suspend = imx8_dsp_runtime_suspend,587 .runtime_resume = imx8_dsp_runtime_resume,588 589 .suspend = imx8_dsp_suspend,590 .resume = imx8_dsp_resume,591 592 .set_power_state = imx8_dsp_set_power_state,593 594 /* ALSA HW info flags */595 .hw_info = SNDRV_PCM_INFO_MMAP |596 SNDRV_PCM_INFO_MMAP_VALID |597 SNDRV_PCM_INFO_INTERLEAVED |598 SNDRV_PCM_INFO_PAUSE |599 SNDRV_PCM_INFO_BATCH |600 SNDRV_PCM_INFO_NO_PERIOD_WAKEUP601};602 603static struct snd_sof_of_mach sof_imx8_machs[] = {604 {605 .compatible = "fsl,imx8qxp-mek",606 .sof_tplg_filename = "sof-imx8-wm8960.tplg",607 .drv_name = "asoc-audio-graph-card2",608 },609 {610 .compatible = "fsl,imx8qm-mek",611 .sof_tplg_filename = "sof-imx8-wm8960.tplg",612 .drv_name = "asoc-audio-graph-card2",613 },614 {}615};616 617static struct sof_dev_desc sof_of_imx8qxp_desc = {618 .of_machines = sof_imx8_machs,619 .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),620 .ipc_default = SOF_IPC_TYPE_3,621 .default_fw_path = {622 [SOF_IPC_TYPE_3] = "imx/sof",623 },624 .default_tplg_path = {625 [SOF_IPC_TYPE_3] = "imx/sof-tplg",626 },627 .default_fw_filename = {628 [SOF_IPC_TYPE_3] = "sof-imx8x.ri",629 },630 .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",631 .ops = &sof_imx8x_ops,632};633 634static struct sof_dev_desc sof_of_imx8qm_desc = {635 .of_machines = sof_imx8_machs,636 .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),637 .ipc_default = SOF_IPC_TYPE_3,638 .default_fw_path = {639 [SOF_IPC_TYPE_3] = "imx/sof",640 },641 .default_tplg_path = {642 [SOF_IPC_TYPE_3] = "imx/sof-tplg",643 },644 .default_fw_filename = {645 [SOF_IPC_TYPE_3] = "sof-imx8.ri",646 },647 .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",648 .ops = &sof_imx8_ops,649};650 651static const struct of_device_id sof_of_imx8_ids[] = {652 { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc},653 { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc},654 { }655};656MODULE_DEVICE_TABLE(of, sof_of_imx8_ids);657 658/* DT driver definition */659static struct platform_driver snd_sof_of_imx8_driver = {660 .probe = sof_of_probe,661 .remove = sof_of_remove,662 .driver = {663 .name = "sof-audio-of-imx8",664 .pm = &sof_of_pm,665 .of_match_table = sof_of_imx8_ids,666 },667};668module_platform_driver(snd_sof_of_imx8_driver);669 670MODULE_LICENSE("Dual BSD/GPL");671MODULE_DESCRIPTION("SOF support for IMX8 platforms");672MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);673