linux/sound/soc/sof/imx/imx8.c
<<
>>
Prefs
   1// SPDX-License-Identifier: (GPL-2.0-only 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#include "imx-common.h"
  25
  26/* DSP memories */
  27#define IRAM_OFFSET             0x10000
  28#define IRAM_SIZE               (2 * 1024)
  29#define DRAM0_OFFSET            0x0
  30#define DRAM0_SIZE              (32 * 1024)
  31#define DRAM1_OFFSET            0x8000
  32#define DRAM1_SIZE              (32 * 1024)
  33#define SYSRAM_OFFSET           0x18000
  34#define SYSRAM_SIZE             (256 * 1024)
  35#define SYSROM_OFFSET           0x58000
  36#define SYSROM_SIZE             (192 * 1024)
  37
  38#define RESET_VECTOR_VADDR      0x596f8000
  39
  40#define MBOX_OFFSET     0x800000
  41#define MBOX_SIZE       0x1000
  42
  43struct imx8_priv {
  44        struct device *dev;
  45        struct snd_sof_dev *sdev;
  46
  47        /* DSP IPC handler */
  48        struct imx_dsp_ipc *dsp_ipc;
  49        struct platform_device *ipc_dev;
  50
  51        /* System Controller IPC handler */
  52        struct imx_sc_ipc *sc_ipc;
  53
  54        /* Power domain handling */
  55        int num_domains;
  56        struct device **pd_dev;
  57        struct device_link **link;
  58
  59};
  60
  61static void imx8_get_reply(struct snd_sof_dev *sdev)
  62{
  63        struct snd_sof_ipc_msg *msg = sdev->msg;
  64        struct sof_ipc_reply reply;
  65        int ret = 0;
  66
  67        if (!msg) {
  68                dev_warn(sdev->dev, "unexpected ipc interrupt\n");
  69                return;
  70        }
  71
  72        /* get reply */
  73        sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply));
  74
  75        if (reply.error < 0) {
  76                memcpy(msg->reply_data, &reply, sizeof(reply));
  77                ret = reply.error;
  78        } else {
  79                /* reply has correct size? */
  80                if (reply.hdr.size != msg->reply_size) {
  81                        dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
  82                                msg->reply_size, reply.hdr.size);
  83                        ret = -EINVAL;
  84                }
  85
  86                /* read the message */
  87                if (msg->reply_size > 0)
  88                        sof_mailbox_read(sdev, sdev->host_box.offset,
  89                                         msg->reply_data, msg->reply_size);
  90        }
  91
  92        msg->reply_error = ret;
  93}
  94
  95static int imx8_get_mailbox_offset(struct snd_sof_dev *sdev)
  96{
  97        return MBOX_OFFSET;
  98}
  99
 100static int imx8_get_window_offset(struct snd_sof_dev *sdev, u32 id)
 101{
 102        return MBOX_OFFSET;
 103}
 104
 105static void imx8_dsp_handle_reply(struct imx_dsp_ipc *ipc)
 106{
 107        struct imx8_priv *priv = imx_dsp_get_data(ipc);
 108        unsigned long flags;
 109
 110        spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
 111        imx8_get_reply(priv->sdev);
 112        snd_sof_ipc_reply(priv->sdev, 0);
 113        spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);
 114}
 115
 116static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc)
 117{
 118        struct imx8_priv *priv = imx_dsp_get_data(ipc);
 119        u32 p; /* panic code */
 120
 121        /* Read the message from the debug box. */
 122        sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p));
 123
 124        /* Check to see if the message is a panic code (0x0dead***) */
 125        if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC)
 126                snd_sof_dsp_panic(priv->sdev, p);
 127        else
 128                snd_sof_ipc_msgs_rx(priv->sdev);
 129}
 130
 131static struct imx_dsp_ops dsp_ops = {
 132        .handle_reply           = imx8_dsp_handle_reply,
 133        .handle_request         = imx8_dsp_handle_request,
 134};
 135
 136static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
 137{
 138        struct imx8_priv *priv = sdev->pdata->hw_pdata;
 139
 140        sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
 141                          msg->msg_size);
 142        imx_dsp_ring_doorbell(priv->dsp_ipc, 0);
 143
 144        return 0;
 145}
 146
 147/*
 148 * DSP control.
 149 */
 150static int imx8x_run(struct snd_sof_dev *sdev)
 151{
 152        struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata;
 153        int ret;
 154
 155        ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
 156                                      IMX_SC_C_OFS_SEL, 1);
 157        if (ret < 0) {
 158                dev_err(sdev->dev, "Error system address offset source select\n");
 159                return ret;
 160        }
 161
 162        ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
 163                                      IMX_SC_C_OFS_AUDIO, 0x80);
 164        if (ret < 0) {
 165                dev_err(sdev->dev, "Error system address offset of AUDIO\n");
 166                return ret;
 167        }
 168
 169        ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
 170                                      IMX_SC_C_OFS_PERIPH, 0x5A);
 171        if (ret < 0) {
 172                dev_err(sdev->dev, "Error system address offset of PERIPH %d\n",
 173                        ret);
 174                return ret;
 175        }
 176
 177        ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
 178                                      IMX_SC_C_OFS_IRQ, 0x51);
 179        if (ret < 0) {
 180                dev_err(sdev->dev, "Error system address offset of IRQ\n");
 181                return ret;
 182        }
 183
 184        imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true,
 185                            RESET_VECTOR_VADDR);
 186
 187        return 0;
 188}
 189
 190static int imx8_run(struct snd_sof_dev *sdev)
 191{
 192        struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata;
 193        int ret;
 194
 195        ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
 196                                      IMX_SC_C_OFS_SEL, 0);
 197        if (ret < 0) {
 198                dev_err(sdev->dev, "Error system address offset source select\n");
 199                return ret;
 200        }
 201
 202        imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true,
 203                            RESET_VECTOR_VADDR);
 204
 205        return 0;
 206}
 207
 208static int imx8_probe(struct snd_sof_dev *sdev)
 209{
 210        struct platform_device *pdev =
 211                container_of(sdev->dev, struct platform_device, dev);
 212        struct device_node *np = pdev->dev.of_node;
 213        struct device_node *res_node;
 214        struct resource *mmio;
 215        struct imx8_priv *priv;
 216        struct resource res;
 217        u32 base, size;
 218        int ret = 0;
 219        int i;
 220
 221        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 222        if (!priv)
 223                return -ENOMEM;
 224
 225        sdev->pdata->hw_pdata = priv;
 226        priv->dev = sdev->dev;
 227        priv->sdev = sdev;
 228
 229        /* power up device associated power domains */
 230        priv->num_domains = of_count_phandle_with_args(np, "power-domains",
 231                                                       "#power-domain-cells");
 232        if (priv->num_domains < 0) {
 233                dev_err(sdev->dev, "no power-domains property in %pOF\n", np);
 234                return priv->num_domains;
 235        }
 236
 237        priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains,
 238                                          sizeof(*priv->pd_dev), GFP_KERNEL);
 239        if (!priv->pd_dev)
 240                return -ENOMEM;
 241
 242        priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains,
 243                                        sizeof(*priv->link), GFP_KERNEL);
 244        if (!priv->link)
 245                return -ENOMEM;
 246
 247        for (i = 0; i < priv->num_domains; i++) {
 248                priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i);
 249                if (IS_ERR(priv->pd_dev[i])) {
 250                        ret = PTR_ERR(priv->pd_dev[i]);
 251                        goto exit_unroll_pm;
 252                }
 253                priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i],
 254                                                DL_FLAG_STATELESS |
 255                                                DL_FLAG_PM_RUNTIME |
 256                                                DL_FLAG_RPM_ACTIVE);
 257                if (!priv->link[i]) {
 258                        ret = -ENOMEM;
 259                        dev_pm_domain_detach(priv->pd_dev[i], false);
 260                        goto exit_unroll_pm;
 261                }
 262        }
 263
 264        ret = imx_scu_get_handle(&priv->sc_ipc);
 265        if (ret) {
 266                dev_err(sdev->dev, "Cannot obtain SCU handle (err = %d)\n",
 267                        ret);
 268                goto exit_unroll_pm;
 269        }
 270
 271        priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp",
 272                                                      PLATFORM_DEVID_NONE,
 273                                                      pdev, sizeof(*pdev));
 274        if (IS_ERR(priv->ipc_dev)) {
 275                ret = PTR_ERR(priv->ipc_dev);
 276                goto exit_unroll_pm;
 277        }
 278
 279        priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev);
 280        if (!priv->dsp_ipc) {
 281                /* DSP IPC driver not probed yet, try later */
 282                ret = -EPROBE_DEFER;
 283                dev_err(sdev->dev, "Failed to get drvdata\n");
 284                goto exit_pdev_unregister;
 285        }
 286
 287        imx_dsp_set_data(priv->dsp_ipc, priv);
 288        priv->dsp_ipc->ops = &dsp_ops;
 289
 290        /* DSP base */
 291        mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 292        if (mmio) {
 293                base = mmio->start;
 294                size = resource_size(mmio);
 295        } else {
 296                dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n");
 297                ret = -EINVAL;
 298                goto exit_pdev_unregister;
 299        }
 300
 301        sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size);
 302        if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) {
 303                dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n",
 304                        base, size);
 305                ret = -ENODEV;
 306                goto exit_pdev_unregister;
 307        }
 308        sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM;
 309
 310        res_node = of_parse_phandle(np, "memory-region", 0);
 311        if (!res_node) {
 312                dev_err(&pdev->dev, "failed to get memory region node\n");
 313                ret = -ENODEV;
 314                goto exit_pdev_unregister;
 315        }
 316
 317        ret = of_address_to_resource(res_node, 0, &res);
 318        if (ret) {
 319                dev_err(&pdev->dev, "failed to get reserved region address\n");
 320                goto exit_pdev_unregister;
 321        }
 322
 323        sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start,
 324                                                          resource_size(&res));
 325        if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) {
 326                dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n",
 327                        base, size);
 328                ret = -ENOMEM;
 329                goto exit_pdev_unregister;
 330        }
 331        sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM;
 332
 333        /* set default mailbox offset for FW ready message */
 334        sdev->dsp_box.offset = MBOX_OFFSET;
 335
 336        return 0;
 337
 338exit_pdev_unregister:
 339        platform_device_unregister(priv->ipc_dev);
 340exit_unroll_pm:
 341        while (--i >= 0) {
 342                device_link_del(priv->link[i]);
 343                dev_pm_domain_detach(priv->pd_dev[i], false);
 344        }
 345
 346        return ret;
 347}
 348
 349static int imx8_remove(struct snd_sof_dev *sdev)
 350{
 351        struct imx8_priv *priv = sdev->pdata->hw_pdata;
 352        int i;
 353
 354        platform_device_unregister(priv->ipc_dev);
 355
 356        for (i = 0; i < priv->num_domains; i++) {
 357                device_link_del(priv->link[i]);
 358                dev_pm_domain_detach(priv->pd_dev[i], false);
 359        }
 360
 361        return 0;
 362}
 363
 364/* on i.MX8 there is 1 to 1 match between type and BAR idx */
 365static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type)
 366{
 367        return type;
 368}
 369
 370static void imx8_ipc_msg_data(struct snd_sof_dev *sdev,
 371                              struct snd_pcm_substream *substream,
 372                              void *p, size_t sz)
 373{
 374        sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
 375}
 376
 377static int imx8_ipc_pcm_params(struct snd_sof_dev *sdev,
 378                               struct snd_pcm_substream *substream,
 379                               const struct sof_ipc_pcm_params_reply *reply)
 380{
 381        return 0;
 382}
 383
 384static struct snd_soc_dai_driver imx8_dai[] = {
 385{
 386        .name = "esai0",
 387        .playback = {
 388                .channels_min = 1,
 389                .channels_max = 8,
 390        },
 391        .capture = {
 392                .channels_min = 1,
 393                .channels_max = 8,
 394        },
 395},
 396{
 397        .name = "sai1",
 398        .playback = {
 399                .channels_min = 1,
 400                .channels_max = 32,
 401        },
 402        .capture = {
 403                .channels_min = 1,
 404                .channels_max = 32,
 405        },
 406},
 407};
 408
 409/* i.MX8 ops */
 410struct snd_sof_dsp_ops sof_imx8_ops = {
 411        /* probe and remove */
 412        .probe          = imx8_probe,
 413        .remove         = imx8_remove,
 414        /* DSP core boot */
 415        .run            = imx8_run,
 416
 417        /* Block IO */
 418        .block_read     = sof_block_read,
 419        .block_write    = sof_block_write,
 420
 421        /* Module IO */
 422        .read64 = sof_io_read64,
 423
 424        /* ipc */
 425        .send_msg       = imx8_send_msg,
 426        .fw_ready       = sof_fw_ready,
 427        .get_mailbox_offset     = imx8_get_mailbox_offset,
 428        .get_window_offset      = imx8_get_window_offset,
 429
 430        .ipc_msg_data   = imx8_ipc_msg_data,
 431        .ipc_pcm_params = imx8_ipc_pcm_params,
 432
 433        /* module loading */
 434        .load_module    = snd_sof_parse_module_memcpy,
 435        .get_bar_index  = imx8_get_bar_index,
 436        /* firmware loading */
 437        .load_firmware  = snd_sof_load_firmware_memcpy,
 438
 439        /* Debug information */
 440        .dbg_dump = imx8_dump,
 441
 442        /* Firmware ops */
 443        .arch_ops = &sof_xtensa_arch_ops,
 444
 445        /* DAI drivers */
 446        .drv = imx8_dai,
 447        .num_drv = ARRAY_SIZE(imx8_dai),
 448
 449        /* ALSA HW info flags */
 450        .hw_info =      SNDRV_PCM_INFO_MMAP |
 451                        SNDRV_PCM_INFO_MMAP_VALID |
 452                        SNDRV_PCM_INFO_INTERLEAVED |
 453                        SNDRV_PCM_INFO_PAUSE |
 454                        SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
 455};
 456EXPORT_SYMBOL(sof_imx8_ops);
 457
 458/* i.MX8X ops */
 459struct snd_sof_dsp_ops sof_imx8x_ops = {
 460        /* probe and remove */
 461        .probe          = imx8_probe,
 462        .remove         = imx8_remove,
 463        /* DSP core boot */
 464        .run            = imx8x_run,
 465
 466        /* Block IO */
 467        .block_read     = sof_block_read,
 468        .block_write    = sof_block_write,
 469
 470        /* Module IO */
 471        .read64 = sof_io_read64,
 472
 473        /* ipc */
 474        .send_msg       = imx8_send_msg,
 475        .fw_ready       = sof_fw_ready,
 476        .get_mailbox_offset     = imx8_get_mailbox_offset,
 477        .get_window_offset      = imx8_get_window_offset,
 478
 479        .ipc_msg_data   = imx8_ipc_msg_data,
 480        .ipc_pcm_params = imx8_ipc_pcm_params,
 481
 482        /* module loading */
 483        .load_module    = snd_sof_parse_module_memcpy,
 484        .get_bar_index  = imx8_get_bar_index,
 485        /* firmware loading */
 486        .load_firmware  = snd_sof_load_firmware_memcpy,
 487
 488        /* Debug information */
 489        .dbg_dump = imx8_dump,
 490
 491        /* Firmware ops */
 492        .arch_ops = &sof_xtensa_arch_ops,
 493
 494        /* DAI drivers */
 495        .drv = imx8_dai,
 496        .num_drv = ARRAY_SIZE(imx8_dai),
 497
 498        /* ALSA HW info flags */
 499        .hw_info =      SNDRV_PCM_INFO_MMAP |
 500                        SNDRV_PCM_INFO_MMAP_VALID |
 501                        SNDRV_PCM_INFO_INTERLEAVED |
 502                        SNDRV_PCM_INFO_PAUSE |
 503                        SNDRV_PCM_INFO_NO_PERIOD_WAKEUP
 504};
 505EXPORT_SYMBOL(sof_imx8x_ops);
 506
 507MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
 508MODULE_LICENSE("Dual BSD/GPL");
 509