linux/sound/soc/intel/catpt/device.c
<<
>>
Prefs
   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
  32static 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
  69release_dma_chan:
  70        dma_release_channel(chan);
  71        if (ret)
  72                return ret;
  73        return catpt_dsp_power_down(cdev);
  74}
  75
  76static 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
 110static 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
 121static int __maybe_unused catpt_runtime_resume(struct device *dev)
 122{
 123        return catpt_resume(dev);
 124}
 125
 126static 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 */
 132static void board_pdev_unregister(void *data)
 133{
 134        platform_device_unregister(data);
 135}
 136
 137static 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
 162static 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
 203err_reg_board:
 204        snd_soc_unregister_component(cdev->dev);
 205err_boot_fw:
 206        catpt_dmac_remove(cdev);
 207err_dmac_probe:
 208        catpt_dsp_power_down(cdev);
 209
 210        return ret;
 211}
 212
 213static 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
 238static 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        spec = device_get_match_data(dev);
 258        if (!spec)
 259                return -ENODEV;
 260
 261        cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
 262        if (!cdev)
 263                return -ENOMEM;
 264
 265        catpt_dev_init(cdev, dev, spec);
 266
 267        /* map DSP bar address */
 268        cdev->lpe_ba = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
 269        if (IS_ERR(cdev->lpe_ba))
 270                return PTR_ERR(cdev->lpe_ba);
 271        cdev->lpe_base = res->start;
 272
 273        /* map PCI bar address */
 274        cdev->pci_ba = devm_platform_ioremap_resource(pdev, 1);
 275        if (IS_ERR(cdev->pci_ba))
 276                return PTR_ERR(cdev->pci_ba);
 277
 278        /* alloc buffer for storing DRAM context during dx transitions */
 279        cdev->dxbuf_vaddr = dmam_alloc_coherent(dev, catpt_dram_size(cdev),
 280                                                &cdev->dxbuf_paddr, GFP_KERNEL);
 281        if (!cdev->dxbuf_vaddr)
 282                return -ENOMEM;
 283
 284        ret = platform_get_irq(pdev, 0);
 285        if (ret < 0)
 286                return ret;
 287        cdev->irq = ret;
 288
 289        platform_set_drvdata(pdev, cdev);
 290
 291        ret = devm_request_threaded_irq(dev, cdev->irq, catpt_dsp_irq_handler,
 292                                        catpt_dsp_irq_thread,
 293                                        IRQF_SHARED, "AudioDSP", cdev);
 294        if (ret)
 295                return ret;
 296
 297        return catpt_probe_components(cdev);
 298}
 299
 300static int catpt_acpi_remove(struct platform_device *pdev)
 301{
 302        struct catpt_dev *cdev = platform_get_drvdata(pdev);
 303
 304        pm_runtime_disable(cdev->dev);
 305
 306        snd_soc_unregister_component(cdev->dev);
 307        catpt_dmac_remove(cdev);
 308        catpt_dsp_power_down(cdev);
 309
 310        catpt_sram_free(&cdev->iram);
 311        catpt_sram_free(&cdev->dram);
 312
 313        return 0;
 314}
 315
 316static struct catpt_spec lpt_desc = {
 317        .machines = snd_soc_acpi_intel_haswell_machines,
 318        .core_id = 0x01,
 319        .host_dram_offset = 0x000000,
 320        .host_iram_offset = 0x080000,
 321        .host_shim_offset = 0x0E7000,
 322        .host_dma_offset = { 0x0F0000, 0x0F8000 },
 323        .host_ssp_offset = { 0x0E8000, 0x0E9000 },
 324        .dram_mask = LPT_VDRTCTL0_DSRAMPGE_MASK,
 325        .iram_mask = LPT_VDRTCTL0_ISRAMPGE_MASK,
 326        .d3srampgd_bit = LPT_VDRTCTL0_D3SRAMPGD,
 327        .d3pgd_bit = LPT_VDRTCTL0_D3PGD,
 328        .pll_shutdown = lpt_dsp_pll_shutdown,
 329};
 330
 331static struct catpt_spec wpt_desc = {
 332        .machines = snd_soc_acpi_intel_broadwell_machines,
 333        .core_id = 0x02,
 334        .host_dram_offset = 0x000000,
 335        .host_iram_offset = 0x0A0000,
 336        .host_shim_offset = 0x0FB000,
 337        .host_dma_offset = { 0x0FE000, 0x0FF000 },
 338        .host_ssp_offset = { 0x0FC000, 0x0FD000 },
 339        .dram_mask = WPT_VDRTCTL0_DSRAMPGE_MASK,
 340        .iram_mask = WPT_VDRTCTL0_ISRAMPGE_MASK,
 341        .d3srampgd_bit = WPT_VDRTCTL0_D3SRAMPGD,
 342        .d3pgd_bit = WPT_VDRTCTL0_D3PGD,
 343        .pll_shutdown = wpt_dsp_pll_shutdown,
 344};
 345
 346static const struct acpi_device_id catpt_ids[] = {
 347        { "INT33C8", (unsigned long)&lpt_desc },
 348        { "INT3438", (unsigned long)&wpt_desc },
 349        { }
 350};
 351MODULE_DEVICE_TABLE(acpi, catpt_ids);
 352
 353static struct platform_driver catpt_acpi_driver = {
 354        .probe = catpt_acpi_probe,
 355        .remove = catpt_acpi_remove,
 356        .driver = {
 357                .name = "intel_catpt",
 358                .acpi_match_table = catpt_ids,
 359                .pm = &catpt_dev_pm,
 360                .dev_groups = catpt_attr_groups,
 361        },
 362};
 363module_platform_driver(catpt_acpi_driver);
 364
 365MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>");
 366MODULE_DESCRIPTION("Intel LPT/WPT AudioDSP driver");
 367MODULE_LICENSE("GPL v2");
 368