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