1 // SPDX-License-Identifier: GPL-2.0-only 2 // 3 // Copyright(c) 2020 Intel Corporation 4 // 5 // Author: Cezary Rojewski <cezary.rojewski@intel.com> 6 // 7 // Special thanks to: 8 // Marcin Barlik <marcin.barlik@intel.com> 9 // Piotr Papierkowski <piotr.papierkowski@intel.com> 10 // 11 // for sharing LPT-LP and WTP-LP AudioDSP architecture expertise and 12 // helping backtrack its historical background 13 // 14 15 #include <linux/acpi.h> 16 #include <linux/dma-mapping.h> 17 #include <linux/interrupt.h> 18 #include <linux/module.h> 19 #include <linux/pci.h> 20 #include <linux/platform_device.h> 21 #include <linux/pm_runtime.h> 22 #include <sound/intel-dsp-config.h> 23 #include <sound/soc.h> 24 #include <sound/soc-acpi.h> 25 #include "core.h" 26 #include "registers.h" 27 28 #define CREATE_TRACE_POINTS 29 #include "trace.h" 30 31 static int catpt_do_suspend(struct device *dev) 32 { 33 struct catpt_dev *cdev = dev_get_drvdata(dev); 34 struct dma_chan *chan; 35 int ret; 36 37 chan = catpt_dma_request_config_chan(cdev); 38 if (IS_ERR(chan)) 39 return PTR_ERR(chan); 40 41 memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); 42 ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); 43 if (ret) { 44 ret = CATPT_IPC_ERROR(ret); 45 goto release_dma_chan; 46 } 47 48 ret = catpt_dsp_stall(cdev, true); 49 if (ret) 50 goto release_dma_chan; 51 52 ret = catpt_store_memdumps(cdev, chan); 53 if (ret) { 54 dev_err(cdev->dev, "store memdumps failed: %d\n", ret); 55 goto release_dma_chan; 56 } 57 58 ret = catpt_store_module_states(cdev, chan); 59 if (ret) { 60 dev_err(cdev->dev, "store module states failed: %d\n", ret); 61 goto release_dma_chan; 62 } 63 64 ret = catpt_store_streams_context(cdev, chan); 65 if (ret) 66 dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); 67 68 release_dma_chan: 69 dma_release_channel(chan); 70 if (ret) 71 return ret; 72 return catpt_dsp_power_down(cdev); 73 } 74 75 /* Do not block the system from suspending, recover on resume() if needed. */ 76 static int catpt_suspend(struct device *dev) 77 { 78 catpt_do_suspend(dev); 79 return 0; 80 } 81 82 static int catpt_resume(struct device *dev) 83 { 84 struct catpt_dev *cdev = dev_get_drvdata(dev); 85 int ret, i; 86 87 ret = catpt_dsp_power_up(cdev); 88 if (ret) 89 return ret; 90 91 if (!try_module_get(dev->driver->owner)) { 92 dev_info(dev, "module unloading, skipping fw boot\n"); 93 return 0; 94 } 95 module_put(dev->driver->owner); 96 97 ret = catpt_boot_firmware(cdev, true); 98 if (ret) { 99 dev_err(cdev->dev, "boot firmware failed: %d\n", ret); 100 return ret; 101 } 102 103 /* reconfigure SSP devices after Dx transition */ 104 for (i = 0; i < CATPT_SSP_COUNT; i++) { 105 if (cdev->devfmt[i].iface == UINT_MAX) 106 continue; 107 108 ret = catpt_ipc_set_device_format(cdev, &cdev->devfmt[i]); 109 if (ret) 110 return CATPT_IPC_ERROR(ret); 111 } 112 113 return 0; 114 } 115 116 static int catpt_runtime_suspend(struct device *dev) 117 { 118 if (!try_module_get(dev->driver->owner)) { 119 dev_info(dev, "module unloading, skipping suspend\n"); 120 return 0; 121 } 122 module_put(dev->driver->owner); 123 124 return catpt_do_suspend(dev); 125 } 126 127 static int catpt_runtime_resume(struct device *dev) 128 { 129 return catpt_resume(dev); 130 } 131 132 static const struct dev_pm_ops catpt_dev_pm = { 133 SYSTEM_SLEEP_PM_OPS(catpt_suspend, catpt_resume) 134 RUNTIME_PM_OPS(catpt_runtime_suspend, catpt_runtime_resume, NULL) 135 }; 136 137 /* machine board owned by CATPT is removed with this hook */ 138 static void board_pdev_unregister(void *data) 139 { 140 platform_device_unregister(data); 141 } 142 143 static int catpt_register_board(struct catpt_dev *cdev) 144 { 145 const struct catpt_spec *spec = cdev->spec; 146 struct snd_soc_acpi_mach *mach; 147 struct platform_device *board; 148 149 mach = snd_soc_acpi_find_machine(spec->machines); 150 if (!mach) { 151 dev_info(cdev->dev, "no machines present\n"); 152 return 0; 153 } 154 155 mach->mach_params.platform = "catpt-platform"; 156 board = platform_device_register_data(NULL, mach->drv_name, 157 PLATFORM_DEVID_NONE, 158 (const void *)mach, sizeof(*mach)); 159 if (IS_ERR(board)) { 160 dev_err(cdev->dev, "board register failed\n"); 161 return PTR_ERR(board); 162 } 163 164 return devm_add_action_or_reset(cdev->dev, board_pdev_unregister, 165 board); 166 } 167 168 static int catpt_probe_components(struct catpt_dev *cdev) 169 { 170 int ret; 171 172 ret = catpt_dsp_power_up(cdev); 173 if (ret) 174 return ret; 175 176 ret = catpt_dmac_probe(cdev); 177 if (ret) { 178 dev_err(cdev->dev, "DMAC probe failed: %d\n", ret); 179 goto err_dmac_probe; 180 } 181 182 ret = catpt_first_boot_firmware(cdev); 183 if (ret) { 184 dev_err(cdev->dev, "first fw boot failed: %d\n", ret); 185 goto err_boot_fw; 186 } 187 188 ret = catpt_register_plat_component(cdev); 189 if (ret) { 190 dev_err(cdev->dev, "register plat comp failed: %d\n", ret); 191 goto err_boot_fw; 192 } 193 194 /* reflect actual ADSP state in pm_runtime */ 195 pm_runtime_set_active(cdev->dev); 196 197 pm_runtime_set_autosuspend_delay(cdev->dev, 2000); 198 pm_runtime_use_autosuspend(cdev->dev); 199 pm_runtime_mark_last_busy(cdev->dev); 200 /* Enable PM before spawning child device. See catpt_dai_pcm_new(). */ 201 pm_runtime_enable(cdev->dev); 202 203 ret = catpt_register_board(cdev); 204 if (ret) { 205 dev_err(cdev->dev, "register board failed: %d\n", ret); 206 goto err_reg_board; 207 } 208 209 return 0; 210 211 err_reg_board: 212 pm_runtime_disable(cdev->dev); 213 snd_soc_unregister_component(cdev->dev); 214 err_boot_fw: 215 catpt_dmac_remove(cdev); 216 err_dmac_probe: 217 catpt_dsp_power_down(cdev); 218 219 return ret; 220 } 221 222 static void catpt_dev_init(struct catpt_dev *cdev, struct device *dev, 223 const struct catpt_spec *spec) 224 { 225 cdev->dev = dev; 226 cdev->spec = spec; 227 init_completion(&cdev->fw_ready); 228 INIT_LIST_HEAD(&cdev->stream_list); 229 spin_lock_init(&cdev->list_lock); 230 mutex_init(&cdev->clk_mutex); 231 232 /* 233 * Mark both device formats as uninitialized. Once corresponding 234 * cpu_dai's pcm is created, proper values are assigned. 235 */ 236 cdev->devfmt[CATPT_SSP_IFACE_0].iface = UINT_MAX; 237 cdev->devfmt[CATPT_SSP_IFACE_1].iface = UINT_MAX; 238 239 catpt_ipc_init(&cdev->ipc, dev); 240 241 catpt_sram_init(&cdev->dram, spec->host_dram_offset, 242 catpt_dram_size(cdev)); 243 catpt_sram_init(&cdev->iram, spec->host_iram_offset, 244 catpt_iram_size(cdev)); 245 } 246 247 static int catpt_acpi_probe(struct platform_device *pdev) 248 { 249 const struct catpt_spec *spec; 250 struct catpt_dev *cdev; 251 struct device *dev = &pdev->dev; 252 const struct acpi_device_id *id; 253 struct resource *res; 254 int ret; 255 256 id = acpi_match_device(dev->driver->acpi_match_table, dev); 257 if (!id) 258 return -ENODEV; 259 260 ret = snd_intel_acpi_dsp_driver_probe(dev, id->id); 261 if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SST) { 262 dev_dbg(dev, "CATPT ACPI driver not selected, aborting probe\n"); 263 return -ENODEV; 264 } 265 266 cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL); 267 if (!cdev) 268 return -ENOMEM; 269 270 spec = (const struct catpt_spec *)id->driver_data; 271 catpt_dev_init(cdev, dev, spec); 272 273 /* map DSP bar address */ 274 cdev->lpe_ba = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 275 if (IS_ERR(cdev->lpe_ba)) 276 return PTR_ERR(cdev->lpe_ba); 277 cdev->lpe_base = res->start; 278 279 /* map PCI bar address */ 280 cdev->pci_ba = devm_platform_ioremap_resource(pdev, 1); 281 if (IS_ERR(cdev->pci_ba)) 282 return PTR_ERR(cdev->pci_ba); 283 284 /* alloc buffer for storing DRAM context during dx transitions */ 285 cdev->dxbuf_vaddr = dmam_alloc_coherent(dev, catpt_dram_size(cdev), 286 &cdev->dxbuf_paddr, GFP_KERNEL); 287 if (!cdev->dxbuf_vaddr) 288 return -ENOMEM; 289 290 ret = platform_get_irq(pdev, 0); 291 if (ret < 0) 292 return ret; 293 cdev->irq = ret; 294 295 platform_set_drvdata(pdev, cdev); 296 297 ret = devm_request_threaded_irq(dev, cdev->irq, catpt_dsp_irq_handler, 298 catpt_dsp_irq_thread, 299 IRQF_SHARED, "AudioDSP", cdev); 300 if (ret) 301 return ret; 302 303 return catpt_probe_components(cdev); 304 } 305 306 static void catpt_acpi_remove(struct platform_device *pdev) 307 { 308 struct catpt_dev *cdev = platform_get_drvdata(pdev); 309 310 pm_runtime_disable(cdev->dev); 311 312 snd_soc_unregister_component(cdev->dev); 313 catpt_dmac_remove(cdev); 314 catpt_dsp_power_down(cdev); 315 316 catpt_sram_free(&cdev->iram); 317 catpt_sram_free(&cdev->dram); 318 } 319 320 static struct snd_soc_acpi_mach lpt_machines[] = { 321 { 322 .id = "INT33CA", 323 .drv_name = "hsw_rt5640", 324 }, 325 {} 326 }; 327 328 static struct snd_soc_acpi_mach wpt_machines[] = { 329 { 330 .id = "INT33CA", 331 .drv_name = "hsw_rt5640", 332 }, 333 { 334 .id = "INT343A", 335 .drv_name = "bdw_rt286", 336 }, 337 { 338 .id = "10EC5650", 339 .drv_name = "bdw-rt5650", 340 }, 341 { 342 .id = "RT5677CE", 343 .drv_name = "bdw-rt5677", 344 }, 345 {} 346 }; 347 348 static struct catpt_spec lpt_desc = { 349 .machines = lpt_machines, 350 .core_id = 0x01, 351 .host_dram_offset = 0x000000, 352 .host_iram_offset = 0x080000, 353 .host_shim_offset = 0x0E7000, 354 .host_dma_offset = { 0x0F0000, 0x0F8000 }, 355 .host_ssp_offset = { 0x0E8000, 0x0E9000 }, 356 .dram_mask = LPT_VDRTCTL0_DSRAMPGE_MASK, 357 .iram_mask = LPT_VDRTCTL0_ISRAMPGE_MASK, 358 .d3srampgd_bit = LPT_VDRTCTL0_D3SRAMPGD, 359 .d3pgd_bit = LPT_VDRTCTL0_D3PGD, 360 .pll_shutdown = lpt_dsp_pll_shutdown, 361 }; 362 363 static struct catpt_spec wpt_desc = { 364 .machines = wpt_machines, 365 .core_id = 0x02, 366 .host_dram_offset = 0x000000, 367 .host_iram_offset = 0x0A0000, 368 .host_shim_offset = 0x0FB000, 369 .host_dma_offset = { 0x0FE000, 0x0FF000 }, 370 .host_ssp_offset = { 0x0FC000, 0x0FD000 }, 371 .dram_mask = WPT_VDRTCTL0_DSRAMPGE_MASK, 372 .iram_mask = WPT_VDRTCTL0_ISRAMPGE_MASK, 373 .d3srampgd_bit = WPT_VDRTCTL0_D3SRAMPGD, 374 .d3pgd_bit = WPT_VDRTCTL0_D3PGD, 375 .pll_shutdown = wpt_dsp_pll_shutdown, 376 }; 377 378 static const struct acpi_device_id catpt_ids[] = { 379 { "INT33C8", (unsigned long)&lpt_desc }, 380 { "INT3438", (unsigned long)&wpt_desc }, 381 { } 382 }; 383 MODULE_DEVICE_TABLE(acpi, catpt_ids); 384 385 static struct platform_driver catpt_acpi_driver = { 386 .probe = catpt_acpi_probe, 387 .remove = catpt_acpi_remove, 388 .driver = { 389 .name = "intel_catpt", 390 .acpi_match_table = catpt_ids, 391 .pm = pm_ptr(&catpt_dev_pm), 392 .dev_groups = catpt_attr_groups, 393 }, 394 }; 395 module_platform_driver(catpt_acpi_driver); 396 397 MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>"); 398 MODULE_DESCRIPTION("Intel LPT/WPT AudioDSP driver"); 399 MODULE_LICENSE("GPL v2"); 400