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