| 1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
| 2 | // |
| 3 | // Copyright 2019-2025 NXP |
| 4 | // |
| 5 | // Author: Daniel Baluta <daniel.baluta@nxp.com> |
| 6 | // |
| 7 | // Hardware interface for audio DSP on i.MX8 |
| 8 | |
| 9 | #include <dt-bindings/firmware/imx/rsrc.h> |
| 10 | |
| 11 | #include <linux/arm-smccc.h> |
| 12 | #include <linux/firmware/imx/svc/misc.h> |
| 13 | #include <linux/mfd/syscon.h> |
| 14 | #include <linux/reset.h> |
| 15 | |
| 16 | #include "imx-common.h" |
| 17 | |
| 18 | /* imx8/imx8x macros */ |
| 19 | #define RESET_VECTOR_VADDR 0x596f8000 |
| 20 | |
| 21 | /* imx8m macros */ |
| 22 | #define IMX8M_DAP_DEBUG 0x28800000 |
| 23 | #define IMX8M_DAP_DEBUG_SIZE (64 * 1024) |
| 24 | #define IMX8M_DAP_PWRCTL (0x4000 + 0x3020) |
| 25 | #define IMX8M_PWRCTL_CORERESET BIT(16) |
| 26 | |
| 27 | /* imx8ulp macros */ |
| 28 | #define FSL_SIP_HIFI_XRDC 0xc200000e |
| 29 | #define SYSCTRL0 0x8 |
| 30 | #define EXECUTE_BIT BIT(13) |
| 31 | #define RESET_BIT BIT(16) |
| 32 | #define HIFI4_CLK_BIT BIT(17) |
| 33 | #define PB_CLK_BIT BIT(18) |
| 34 | #define PLAT_CLK_BIT BIT(19) |
| 35 | #define DEBUG_LOGIC_BIT BIT(25) |
| 36 | |
| 37 | struct imx8m_chip_data { |
| 38 | void __iomem *dap; |
| 39 | struct regmap *regmap; |
| 40 | struct reset_control *run_stall; |
| 41 | }; |
| 42 | |
| 43 | static int imx8_shutdown(struct snd_sof_dev *sdev) |
| 44 | { |
| 45 | /* |
| 46 | * Force the DSP to stall. After the firmware image is loaded, |
| 47 | * the stall will be removed during run() by a matching |
| 48 | * imx_sc_pm_cpu_start() call. |
| 49 | */ |
| 50 | imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, enable: false, |
| 51 | RESET_VECTOR_VADDR); |
| 52 | |
| 53 | return 0; |
| 54 | } |
| 55 | |
| 56 | /* |
| 57 | * DSP control. |
| 58 | */ |
| 59 | static int imx8x_run(struct snd_sof_dev *sdev) |
| 60 | { |
| 61 | int ret; |
| 62 | |
| 63 | ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP, |
| 64 | IMX_SC_C_OFS_SEL, val: 1); |
| 65 | if (ret < 0) { |
| 66 | dev_err(sdev->dev, "Error system address offset source select\n" ); |
| 67 | return ret; |
| 68 | } |
| 69 | |
| 70 | ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP, |
| 71 | IMX_SC_C_OFS_AUDIO, val: 0x80); |
| 72 | if (ret < 0) { |
| 73 | dev_err(sdev->dev, "Error system address offset of AUDIO\n" ); |
| 74 | return ret; |
| 75 | } |
| 76 | |
| 77 | ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP, |
| 78 | IMX_SC_C_OFS_PERIPH, val: 0x5A); |
| 79 | if (ret < 0) { |
| 80 | dev_err(sdev->dev, "Error system address offset of PERIPH %d\n" , |
| 81 | ret); |
| 82 | return ret; |
| 83 | } |
| 84 | |
| 85 | ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP, |
| 86 | IMX_SC_C_OFS_IRQ, val: 0x51); |
| 87 | if (ret < 0) { |
| 88 | dev_err(sdev->dev, "Error system address offset of IRQ\n" ); |
| 89 | return ret; |
| 90 | } |
| 91 | |
| 92 | imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, enable: true, |
| 93 | RESET_VECTOR_VADDR); |
| 94 | |
| 95 | return 0; |
| 96 | } |
| 97 | |
| 98 | static int imx8_run(struct snd_sof_dev *sdev) |
| 99 | { |
| 100 | int ret; |
| 101 | |
| 102 | ret = imx_sc_misc_set_control(get_chip_pdata(sdev), IMX_SC_R_DSP, |
| 103 | IMX_SC_C_OFS_SEL, val: 0); |
| 104 | if (ret < 0) { |
| 105 | dev_err(sdev->dev, "Error system address offset source select\n" ); |
| 106 | return ret; |
| 107 | } |
| 108 | |
| 109 | imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, enable: true, |
| 110 | RESET_VECTOR_VADDR); |
| 111 | |
| 112 | return 0; |
| 113 | } |
| 114 | |
| 115 | static int imx8_probe(struct snd_sof_dev *sdev) |
| 116 | { |
| 117 | struct imx_sc_ipc *sc_ipc_handle; |
| 118 | struct imx_common_data *common; |
| 119 | int ret; |
| 120 | |
| 121 | common = sdev->pdata->hw_pdata; |
| 122 | |
| 123 | ret = imx_scu_get_handle(ipc: &sc_ipc_handle); |
| 124 | if (ret < 0) |
| 125 | return dev_err_probe(dev: sdev->dev, err: ret, |
| 126 | fmt: "failed to fetch SC IPC handle\n" ); |
| 127 | |
| 128 | common->chip_pdata = sc_ipc_handle; |
| 129 | |
| 130 | return 0; |
| 131 | } |
| 132 | |
| 133 | static int imx8m_reset(struct snd_sof_dev *sdev) |
| 134 | { |
| 135 | struct imx8m_chip_data *chip; |
| 136 | u32 pwrctl; |
| 137 | |
| 138 | chip = get_chip_pdata(sdev); |
| 139 | |
| 140 | /* put DSP into reset and stall */ |
| 141 | pwrctl = readl(addr: chip->dap + IMX8M_DAP_PWRCTL); |
| 142 | pwrctl |= IMX8M_PWRCTL_CORERESET; |
| 143 | writel(val: pwrctl, addr: chip->dap + IMX8M_DAP_PWRCTL); |
| 144 | |
| 145 | /* keep reset asserted for 10 cycles */ |
| 146 | usleep_range(min: 1, max: 2); |
| 147 | |
| 148 | reset_control_assert(rstc: chip->run_stall); |
| 149 | |
| 150 | /* take the DSP out of reset and keep stalled for FW loading */ |
| 151 | pwrctl = readl(addr: chip->dap + IMX8M_DAP_PWRCTL); |
| 152 | pwrctl &= ~IMX8M_PWRCTL_CORERESET; |
| 153 | writel(val: pwrctl, addr: chip->dap + IMX8M_DAP_PWRCTL); |
| 154 | |
| 155 | return 0; |
| 156 | } |
| 157 | |
| 158 | static int imx8m_run(struct snd_sof_dev *sdev) |
| 159 | { |
| 160 | struct imx8m_chip_data *chip = get_chip_pdata(sdev); |
| 161 | |
| 162 | return reset_control_deassert(rstc: chip->run_stall); |
| 163 | } |
| 164 | |
| 165 | static int imx8m_probe(struct snd_sof_dev *sdev) |
| 166 | { |
| 167 | struct imx_common_data *common; |
| 168 | struct imx8m_chip_data *chip; |
| 169 | |
| 170 | common = sdev->pdata->hw_pdata; |
| 171 | |
| 172 | chip = devm_kzalloc(dev: sdev->dev, size: sizeof(*chip), GFP_KERNEL); |
| 173 | if (!chip) |
| 174 | return -ENOMEM; |
| 175 | |
| 176 | chip->dap = devm_ioremap(dev: sdev->dev, IMX8M_DAP_DEBUG, IMX8M_DAP_DEBUG_SIZE); |
| 177 | if (!chip->dap) |
| 178 | return dev_err_probe(dev: sdev->dev, err: -ENODEV, |
| 179 | fmt: "failed to ioremap DAP\n" ); |
| 180 | |
| 181 | chip->run_stall = devm_reset_control_get_exclusive(dev: sdev->dev, id: "runstall" ); |
| 182 | if (IS_ERR(ptr: chip->run_stall)) |
| 183 | return dev_err_probe(dev: sdev->dev, err: PTR_ERR(ptr: chip->run_stall), |
| 184 | fmt: "failed to get dsp runstall reset control\n" ); |
| 185 | |
| 186 | common->chip_pdata = chip; |
| 187 | |
| 188 | return 0; |
| 189 | } |
| 190 | |
| 191 | static int imx8ulp_run(struct snd_sof_dev *sdev) |
| 192 | { |
| 193 | struct regmap *regmap = get_chip_pdata(sdev); |
| 194 | |
| 195 | /* Controls the HiFi4 DSP Reset: 1 in reset, 0 out of reset */ |
| 196 | regmap_update_bits(map: regmap, SYSCTRL0, RESET_BIT, val: 0); |
| 197 | |
| 198 | /* Reset HiFi4 DSP Debug logic: 1 debug reset, 0 out of reset*/ |
| 199 | regmap_update_bits(map: regmap, SYSCTRL0, DEBUG_LOGIC_BIT, val: 0); |
| 200 | |
| 201 | /* Stall HIFI4 DSP Execution: 1 stall, 0 run */ |
| 202 | regmap_update_bits(map: regmap, SYSCTRL0, EXECUTE_BIT, val: 0); |
| 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | static int imx8ulp_reset(struct snd_sof_dev *sdev) |
| 208 | { |
| 209 | struct arm_smccc_res smc_res; |
| 210 | struct regmap *regmap; |
| 211 | |
| 212 | regmap = get_chip_pdata(sdev); |
| 213 | |
| 214 | /* HiFi4 Platform Clock Enable: 1 enabled, 0 disabled */ |
| 215 | regmap_update_bits(map: regmap, SYSCTRL0, PLAT_CLK_BIT, PLAT_CLK_BIT); |
| 216 | |
| 217 | /* HiFi4 PBCLK clock enable: 1 enabled, 0 disabled */ |
| 218 | regmap_update_bits(map: regmap, SYSCTRL0, PB_CLK_BIT, PB_CLK_BIT); |
| 219 | |
| 220 | /* HiFi4 Clock Enable: 1 enabled, 0 disabled */ |
| 221 | regmap_update_bits(map: regmap, SYSCTRL0, HIFI4_CLK_BIT, HIFI4_CLK_BIT); |
| 222 | |
| 223 | regmap_update_bits(map: regmap, SYSCTRL0, RESET_BIT, RESET_BIT); |
| 224 | |
| 225 | usleep_range(min: 1, max: 2); |
| 226 | |
| 227 | /* Stall HIFI4 DSP Execution: 1 stall, 0 not stall */ |
| 228 | regmap_update_bits(map: regmap, SYSCTRL0, EXECUTE_BIT, EXECUTE_BIT); |
| 229 | usleep_range(min: 1, max: 2); |
| 230 | |
| 231 | arm_smccc_smc(FSL_SIP_HIFI_XRDC, 0, 0, 0, 0, 0, 0, 0, &smc_res); |
| 232 | |
| 233 | return smc_res.a0; |
| 234 | } |
| 235 | |
| 236 | static int imx8ulp_probe(struct snd_sof_dev *sdev) |
| 237 | { |
| 238 | struct imx_common_data *common; |
| 239 | struct regmap *regmap; |
| 240 | |
| 241 | common = sdev->pdata->hw_pdata; |
| 242 | |
| 243 | regmap = syscon_regmap_lookup_by_phandle(np: sdev->dev->of_node, property: "fsl,dsp-ctrl" ); |
| 244 | if (IS_ERR(ptr: regmap)) |
| 245 | return dev_err_probe(dev: sdev->dev, err: PTR_ERR(ptr: regmap), |
| 246 | fmt: "failed to fetch dsp ctrl regmap\n" ); |
| 247 | |
| 248 | common->chip_pdata = regmap; |
| 249 | |
| 250 | return 0; |
| 251 | } |
| 252 | |
| 253 | static struct snd_soc_dai_driver imx8_dai[] = { |
| 254 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("esai0" , 1, 8), |
| 255 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai1" , 1, 32), |
| 256 | }; |
| 257 | |
| 258 | static struct snd_soc_dai_driver imx8m_dai[] = { |
| 259 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai1" , 1, 32), |
| 260 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai2" , 1, 32), |
| 261 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai3" , 1, 32), |
| 262 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai5" , 1, 32), |
| 263 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai6" , 1, 32), |
| 264 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai7" , 1, 32), |
| 265 | IMX_SOF_DAI_DRV_ENTRY("micfil" , 0, 0, 1, 8), |
| 266 | }; |
| 267 | |
| 268 | static struct snd_soc_dai_driver imx8ulp_dai[] = { |
| 269 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai5" , 1, 32), |
| 270 | IMX_SOF_DAI_DRV_ENTRY_BIDIR("sai6" , 1, 32), |
| 271 | }; |
| 272 | |
| 273 | static struct snd_sof_dsp_ops sof_imx8_ops; |
| 274 | |
| 275 | static int imx8_ops_init(struct snd_sof_dev *sdev) |
| 276 | { |
| 277 | /* first copy from template */ |
| 278 | memcpy(&sof_imx8_ops, &sof_imx_ops, sizeof(sof_imx_ops)); |
| 279 | |
| 280 | /* then set common imx8 ops */ |
| 281 | sof_imx8_ops.dbg_dump = imx8_dump; |
| 282 | sof_imx8_ops.dsp_arch_ops = &sof_xtensa_arch_ops; |
| 283 | sof_imx8_ops.debugfs_add_region_item = |
| 284 | snd_sof_debugfs_add_region_item_iomem; |
| 285 | |
| 286 | /* ... and finally set DAI driver */ |
| 287 | sof_imx8_ops.drv = get_chip_info(sdev)->drv; |
| 288 | sof_imx8_ops.num_drv = get_chip_info(sdev)->num_drv; |
| 289 | |
| 290 | return 0; |
| 291 | } |
| 292 | |
| 293 | static const struct imx_chip_ops imx8_chip_ops = { |
| 294 | .probe = imx8_probe, |
| 295 | .core_kick = imx8_run, |
| 296 | .core_shutdown = imx8_shutdown, |
| 297 | }; |
| 298 | |
| 299 | static const struct imx_chip_ops imx8x_chip_ops = { |
| 300 | .probe = imx8_probe, |
| 301 | .core_kick = imx8x_run, |
| 302 | .core_shutdown = imx8_shutdown, |
| 303 | }; |
| 304 | |
| 305 | static const struct imx_chip_ops imx8m_chip_ops = { |
| 306 | .probe = imx8m_probe, |
| 307 | .core_kick = imx8m_run, |
| 308 | .core_reset = imx8m_reset, |
| 309 | }; |
| 310 | |
| 311 | static const struct imx_chip_ops imx8ulp_chip_ops = { |
| 312 | .probe = imx8ulp_probe, |
| 313 | .core_kick = imx8ulp_run, |
| 314 | .core_reset = imx8ulp_reset, |
| 315 | }; |
| 316 | |
| 317 | static struct imx_memory_info imx8_memory_regions[] = { |
| 318 | { .name = "iram" , .reserved = false }, |
| 319 | { .name = "sram" , .reserved = true }, |
| 320 | { } |
| 321 | }; |
| 322 | |
| 323 | static struct imx_memory_info imx8m_memory_regions[] = { |
| 324 | { .name = "iram" , .reserved = false }, |
| 325 | { .name = "sram" , .reserved = true }, |
| 326 | { } |
| 327 | }; |
| 328 | |
| 329 | static struct imx_memory_info imx8ulp_memory_regions[] = { |
| 330 | { .name = "iram" , .reserved = false }, |
| 331 | { .name = "sram" , .reserved = true }, |
| 332 | { } |
| 333 | }; |
| 334 | |
| 335 | static const struct imx_chip_info imx8_chip_info = { |
| 336 | .ipc_info = { |
| 337 | .has_panic_code = true, |
| 338 | .boot_mbox_offset = 0x800000, |
| 339 | .window_offset = 0x800000, |
| 340 | }, |
| 341 | .memory = imx8_memory_regions, |
| 342 | .drv = imx8_dai, |
| 343 | .num_drv = ARRAY_SIZE(imx8_dai), |
| 344 | .ops = &imx8_chip_ops, |
| 345 | }; |
| 346 | |
| 347 | static const struct imx_chip_info imx8x_chip_info = { |
| 348 | .ipc_info = { |
| 349 | .has_panic_code = true, |
| 350 | .boot_mbox_offset = 0x800000, |
| 351 | .window_offset = 0x800000, |
| 352 | }, |
| 353 | .memory = imx8_memory_regions, |
| 354 | .drv = imx8_dai, |
| 355 | .num_drv = ARRAY_SIZE(imx8_dai), |
| 356 | .ops = &imx8x_chip_ops, |
| 357 | }; |
| 358 | |
| 359 | static const struct imx_chip_info imx8m_chip_info = { |
| 360 | .ipc_info = { |
| 361 | .has_panic_code = true, |
| 362 | .boot_mbox_offset = 0x800000, |
| 363 | .window_offset = 0x800000, |
| 364 | }, |
| 365 | .memory = imx8m_memory_regions, |
| 366 | .drv = imx8m_dai, |
| 367 | .num_drv = ARRAY_SIZE(imx8m_dai), |
| 368 | .ops = &imx8m_chip_ops, |
| 369 | }; |
| 370 | |
| 371 | static const struct imx_chip_info imx8ulp_chip_info = { |
| 372 | .ipc_info = { |
| 373 | .has_panic_code = true, |
| 374 | .boot_mbox_offset = 0x800000, |
| 375 | .window_offset = 0x800000, |
| 376 | }, |
| 377 | .has_dma_reserved = true, |
| 378 | .memory = imx8ulp_memory_regions, |
| 379 | .drv = imx8ulp_dai, |
| 380 | .num_drv = ARRAY_SIZE(imx8ulp_dai), |
| 381 | .ops = &imx8ulp_chip_ops, |
| 382 | }; |
| 383 | |
| 384 | static struct snd_sof_of_mach sof_imx8_machs[] = { |
| 385 | { |
| 386 | .compatible = "fsl,imx8qxp-mek" , |
| 387 | .sof_tplg_filename = "sof-imx8-wm8960.tplg" , |
| 388 | .drv_name = "asoc-audio-graph-card2" , |
| 389 | }, |
| 390 | { |
| 391 | .compatible = "fsl,imx8qxp-mek-wcpu" , |
| 392 | .sof_tplg_filename = "sof-imx8-wm8962.tplg" , |
| 393 | .drv_name = "asoc-audio-graph-card2" , |
| 394 | }, |
| 395 | { |
| 396 | .compatible = "fsl,imx8qm-mek" , |
| 397 | .sof_tplg_filename = "sof-imx8-wm8960.tplg" , |
| 398 | .drv_name = "asoc-audio-graph-card2" , |
| 399 | }, |
| 400 | { |
| 401 | .compatible = "fsl,imx8qm-mek-revd" , |
| 402 | .sof_tplg_filename = "sof-imx8-wm8962.tplg" , |
| 403 | .drv_name = "asoc-audio-graph-card2" , |
| 404 | }, |
| 405 | { |
| 406 | .compatible = "fsl,imx8qxp-mek-bb" , |
| 407 | .sof_tplg_filename = "sof-imx8-cs42888.tplg" , |
| 408 | .drv_name = "asoc-audio-graph-card2" , |
| 409 | }, |
| 410 | { |
| 411 | .compatible = "fsl,imx8qm-mek-bb" , |
| 412 | .sof_tplg_filename = "sof-imx8-cs42888.tplg" , |
| 413 | .drv_name = "asoc-audio-graph-card2" , |
| 414 | }, |
| 415 | { |
| 416 | .compatible = "fsl,imx8mp-evk" , |
| 417 | .sof_tplg_filename = "sof-imx8mp-wm8960.tplg" , |
| 418 | .drv_name = "asoc-audio-graph-card2" , |
| 419 | }, |
| 420 | { |
| 421 | .compatible = "fsl,imx8mp-evk-revb4" , |
| 422 | .sof_tplg_filename = "sof-imx8mp-wm8962.tplg" , |
| 423 | .drv_name = "asoc-audio-graph-card2" , |
| 424 | }, |
| 425 | { |
| 426 | .compatible = "fsl,imx8ulp-evk" , |
| 427 | .sof_tplg_filename = "sof-imx8ulp-btsco.tplg" , |
| 428 | .drv_name = "asoc-audio-graph-card2" , |
| 429 | }, |
| 430 | {} |
| 431 | }; |
| 432 | |
| 433 | IMX_SOF_DEV_DESC(imx8, sof_imx8_machs, &imx8_chip_info, &sof_imx8_ops, imx8_ops_init); |
| 434 | IMX_SOF_DEV_DESC(imx8x, sof_imx8_machs, &imx8x_chip_info, &sof_imx8_ops, imx8_ops_init); |
| 435 | IMX_SOF_DEV_DESC(imx8m, sof_imx8_machs, &imx8m_chip_info, &sof_imx8_ops, imx8_ops_init); |
| 436 | IMX_SOF_DEV_DESC(imx8ulp, sof_imx8_machs, &imx8ulp_chip_info, &sof_imx8_ops, imx8_ops_init); |
| 437 | |
| 438 | static const struct of_device_id sof_of_imx8_ids[] = { |
| 439 | { |
| 440 | .compatible = "fsl,imx8qxp-dsp" , |
| 441 | .data = &IMX_SOF_DEV_DESC_NAME(imx8x), |
| 442 | }, |
| 443 | { |
| 444 | .compatible = "fsl,imx8qm-dsp" , |
| 445 | .data = &IMX_SOF_DEV_DESC_NAME(imx8), |
| 446 | }, |
| 447 | { |
| 448 | .compatible = "fsl,imx8mp-dsp" , |
| 449 | .data = &IMX_SOF_DEV_DESC_NAME(imx8m), |
| 450 | }, |
| 451 | { |
| 452 | .compatible = "fsl,imx8ulp-dsp" , |
| 453 | .data = &IMX_SOF_DEV_DESC_NAME(imx8ulp), |
| 454 | }, |
| 455 | { } |
| 456 | }; |
| 457 | MODULE_DEVICE_TABLE(of, sof_of_imx8_ids); |
| 458 | |
| 459 | /* DT driver definition */ |
| 460 | static struct platform_driver snd_sof_of_imx8_driver = { |
| 461 | .probe = sof_of_probe, |
| 462 | .remove = sof_of_remove, |
| 463 | .driver = { |
| 464 | .name = "sof-audio-of-imx8" , |
| 465 | .pm = pm_ptr(&sof_of_pm), |
| 466 | .of_match_table = sof_of_imx8_ids, |
| 467 | }, |
| 468 | }; |
| 469 | module_platform_driver(snd_sof_of_imx8_driver); |
| 470 | |
| 471 | MODULE_LICENSE("Dual BSD/GPL" ); |
| 472 | MODULE_DESCRIPTION("SOF support for IMX8 platforms" ); |
| 473 | MODULE_IMPORT_NS("SND_SOC_SOF_XTENSA" ); |
| 474 | |