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