--- /dev/null
- return -ENOMEM;
+ /*
+ * sst_acpi.c - SST (LPE) driver init file for ACPI enumeration.
+ *
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * Authors: Ramesh Babu K V <Ramesh.Babu@intel.com>
+ * Authors: Omair Mohammed Abdullah <omair.m.abdullah@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ *
+ */
+
+ #include <linux/module.h>
+ #include <linux/fs.h>
+ #include <linux/interrupt.h>
+ #include <linux/slab.h>
+ #include <linux/io.h>
+ #include <linux/miscdevice.h>
+ #include <linux/platform_device.h>
+ #include <linux/firmware.h>
+ #include <linux/pm_runtime.h>
+ #include <linux/pm_qos.h>
+ #include <linux/acpi.h>
+ #include <asm/platform_sst_audio.h>
+ #include <sound/core.h>
+ #include <sound/soc.h>
+ #include <sound/compress_driver.h>
+ #include <acpi/acbuffer.h>
+ #include <acpi/platform/acenv.h>
+ #include <acpi/platform/aclinux.h>
+ #include <acpi/actypes.h>
+ #include <acpi/acpi_bus.h>
+ #include "../sst-mfld-platform.h"
+ #include "../../common/sst-dsp.h"
+ #include "sst.h"
+
+ struct sst_machines {
+ char *codec_id;
+ char board[32];
+ char machine[32];
+ void (*machine_quirk)(void);
+ char firmware[FW_NAME_SIZE];
+ struct sst_platform_info *pdata;
+
+ };
+
+ /* LPE viewpoint addresses */
+ #define SST_BYT_IRAM_PHY_START 0xff2c0000
+ #define SST_BYT_IRAM_PHY_END 0xff2d4000
+ #define SST_BYT_DRAM_PHY_START 0xff300000
+ #define SST_BYT_DRAM_PHY_END 0xff320000
+ #define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */
+ #define SST_BYT_IMR_VIRT_END 0xc01fffff
+ #define SST_BYT_SHIM_PHY_ADDR 0xff340000
+ #define SST_BYT_MBOX_PHY_ADDR 0xff344000
+ #define SST_BYT_DMA0_PHY_ADDR 0xff298000
+ #define SST_BYT_DMA1_PHY_ADDR 0xff29c000
+ #define SST_BYT_SSP0_PHY_ADDR 0xff2a0000
+ #define SST_BYT_SSP2_PHY_ADDR 0xff2a2000
+
+ #define BYT_FW_MOD_TABLE_OFFSET 0x80000
+ #define BYT_FW_MOD_TABLE_SIZE 0x100
+ #define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE)
+
+ static const struct sst_info byt_fwparse_info = {
+ .use_elf = false,
+ .max_streams = 25,
+ .iram_start = SST_BYT_IRAM_PHY_START,
+ .iram_end = SST_BYT_IRAM_PHY_END,
+ .iram_use = true,
+ .dram_start = SST_BYT_DRAM_PHY_START,
+ .dram_end = SST_BYT_DRAM_PHY_END,
+ .dram_use = true,
+ .imr_start = SST_BYT_IMR_VIRT_START,
+ .imr_end = SST_BYT_IMR_VIRT_END,
+ .imr_use = true,
+ .mailbox_start = SST_BYT_MBOX_PHY_ADDR,
+ .num_probes = 0,
+ .lpe_viewpt_rqd = true,
+ };
+
+ static const struct sst_ipc_info byt_ipc_info = {
+ .ipc_offset = 0,
+ .mbox_recv_off = 0x400,
+ };
+
+ static const struct sst_lib_dnld_info byt_lib_dnld_info = {
+ .mod_base = SST_BYT_IMR_VIRT_START,
+ .mod_end = SST_BYT_IMR_VIRT_END,
+ .mod_table_offset = BYT_FW_MOD_TABLE_OFFSET,
+ .mod_table_size = BYT_FW_MOD_TABLE_SIZE,
+ .mod_ddr_dnld = false,
+ };
+
+ static const struct sst_res_info byt_rvp_res_info = {
+ .shim_offset = 0x140000,
+ .shim_size = 0x000100,
+ .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR,
+ .ssp0_offset = 0xa0000,
+ .ssp0_size = 0x1000,
+ .dma0_offset = 0x98000,
+ .dma0_size = 0x4000,
+ .dma1_offset = 0x9c000,
+ .dma1_size = 0x4000,
+ .iram_offset = 0x0c0000,
+ .iram_size = 0x14000,
+ .dram_offset = 0x100000,
+ .dram_size = 0x28000,
+ .mbox_offset = 0x144000,
+ .mbox_size = 0x1000,
+ .acpi_lpe_res_index = 0,
+ .acpi_ddr_index = 2,
+ .acpi_ipc_irq_index = 5,
+ };
+
+ static struct sst_platform_info byt_rvp_platform_data = {
+ .probe_data = &byt_fwparse_info,
+ .ipc_info = &byt_ipc_info,
+ .lib_info = &byt_lib_dnld_info,
+ .res_info = &byt_rvp_res_info,
+ .platform = "sst-mfld-platform",
+ };
+
+ /* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail,
+ * so pdata is same as Baytrail.
+ */
+ static struct sst_platform_info chv_platform_data = {
+ .probe_data = &byt_fwparse_info,
+ .ipc_info = &byt_ipc_info,
+ .lib_info = &byt_lib_dnld_info,
+ .res_info = &byt_rvp_res_info,
+ .platform = "sst-mfld-platform",
+ };
+
+ static int sst_platform_get_resources(struct intel_sst_drv *ctx)
+ {
+ struct resource *rsrc;
+ struct platform_device *pdev = to_platform_device(ctx->dev);
+
+ /* All ACPI resource request here */
+ /* Get Shim addr */
+ rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
+ ctx->pdata->res_info->acpi_lpe_res_index);
+ if (!rsrc) {
+ dev_err(ctx->dev, "Invalid SHIM base from IFWI");
+ return -EIO;
+ }
+ dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start,
+ (unsigned int)resource_size(rsrc));
+
+ ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset;
+ ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1;
+ dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base);
+ ctx->iram = devm_ioremap_nocache(ctx->dev, ctx->iram_base,
+ ctx->pdata->res_info->iram_size);
+ if (!ctx->iram) {
+ dev_err(ctx->dev, "unable to map IRAM");
+ return -EIO;
+ }
+
+ ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset;
+ ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1;
+ dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base);
+ ctx->dram = devm_ioremap_nocache(ctx->dev, ctx->dram_base,
+ ctx->pdata->res_info->dram_size);
+ if (!ctx->dram) {
+ dev_err(ctx->dev, "unable to map DRAM");
+ return -EIO;
+ }
+
+ ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset;
+ dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add);
+ ctx->shim = devm_ioremap_nocache(ctx->dev, ctx->shim_phy_add,
+ ctx->pdata->res_info->shim_size);
+ if (!ctx->shim) {
+ dev_err(ctx->dev, "unable to map SHIM");
+ return -EIO;
+ }
+
+ /* reassign physical address to LPE viewpoint address */
+ ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr;
+
+ /* Get mailbox addr */
+ ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset;
+ dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add);
+ ctx->mailbox = devm_ioremap_nocache(ctx->dev, ctx->mailbox_add,
+ ctx->pdata->res_info->mbox_size);
+ if (!ctx->mailbox) {
+ dev_err(ctx->dev, "unable to map mailbox");
+ return -EIO;
+ }
+
+ /* reassign physical address to LPE viewpoint address */
+ ctx->mailbox_add = ctx->info.mailbox_start;
+
+ rsrc = platform_get_resource(pdev, IORESOURCE_MEM,
+ ctx->pdata->res_info->acpi_ddr_index);
+ if (!rsrc) {
+ dev_err(ctx->dev, "Invalid DDR base from IFWI");
+ return -EIO;
+ }
+ ctx->ddr_base = rsrc->start;
+ ctx->ddr_end = rsrc->end;
+ dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base);
+ ctx->ddr = devm_ioremap_nocache(ctx->dev, ctx->ddr_base,
+ resource_size(rsrc));
+ if (!ctx->ddr) {
+ dev_err(ctx->dev, "unable to map DDR");
+ return -EIO;
+ }
+
+ /* Find the IRQ */
+ ctx->irq_num = platform_get_irq(pdev,
+ ctx->pdata->res_info->acpi_ipc_irq_index);
+ return 0;
+ }
+
+ static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level,
+ void *context, void **ret)
+ {
+ *(bool *)context = true;
+ return AE_OK;
+ }
+
+ static struct sst_machines *sst_acpi_find_machine(
+ struct sst_machines *machines)
+ {
+ struct sst_machines *mach;
+ bool found = false;
+
+ for (mach = machines; mach->codec_id; mach++)
+ if (ACPI_SUCCESS(acpi_get_devices(mach->codec_id,
+ sst_acpi_mach_match,
+ &found, NULL)) && found)
+ return mach;
+
+ return NULL;
+ }
+
+ static int sst_acpi_probe(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+ int ret = 0;
+ struct intel_sst_drv *ctx;
+ const struct acpi_device_id *id;
+ struct sst_machines *mach;
+ struct platform_device *mdev;
+ struct platform_device *plat_dev;
+ unsigned int dev_id;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+ dev_dbg(dev, "for %s", id->id);
+
+ mach = (struct sst_machines *)id->driver_data;
+ mach = sst_acpi_find_machine(mach);
+ if (mach == NULL) {
+ dev_err(dev, "No matching machine driver found\n");
+ return -ENODEV;
+ }
+
+ ret = kstrtouint(id->id, 16, &dev_id);
+ if (ret < 0) {
+ dev_err(dev, "Unique device id conversion error: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "ACPI device id: %x\n", dev_id);
+
+ plat_dev = platform_device_register_data(dev, mach->pdata->platform, -1, NULL, 0);
+ if (IS_ERR(plat_dev)) {
+ dev_err(dev, "Failed to create machine device: %s\n", mach->pdata->platform);
+ return PTR_ERR(plat_dev);
+ }
+
+ /* Create platform device for sst machine driver */
+ mdev = platform_device_register_data(dev, mach->machine, -1, NULL, 0);
+ if (IS_ERR(mdev)) {
+ dev_err(dev, "Failed to create machine device: %s\n", mach->machine);
+ return PTR_ERR(mdev);
+ }
+
+ ret = sst_alloc_drv_context(&ctx, dev, dev_id);
+ if (ret < 0)
+ return ret;
+
+ /* Fill sst platform data */
+ ctx->pdata = mach->pdata;
+ strcpy(ctx->firmware_name, mach->firmware);
+
+ ret = sst_platform_get_resources(ctx);
+ if (ret)
+ return ret;
+
+ ret = sst_context_init(ctx);
+ if (ret < 0)
+ return ret;
+
+ /* need to save shim registers in BYT */
+ ctx->shim_regs64 = devm_kzalloc(ctx->dev, sizeof(*ctx->shim_regs64),
+ GFP_KERNEL);
+ if (!ctx->shim_regs64) {
++ ret = -ENOMEM;
+ goto do_sst_cleanup;
+ }
+
+ sst_configure_runtime_pm(ctx);
+ platform_set_drvdata(pdev, ctx);
+ return ret;
+
+ do_sst_cleanup:
+ sst_context_cleanup(ctx);
+ platform_set_drvdata(pdev, NULL);
+ dev_err(ctx->dev, "failed with %d\n", ret);
+ return ret;
+ }
+
+ /**
+ * intel_sst_remove - remove function
+ *
+ * @pdev: platform device structure
+ *
+ * This function is called by OS when a device is unloaded
+ * This frees the interrupt etc
+ */
+ static int sst_acpi_remove(struct platform_device *pdev)
+ {
+ struct intel_sst_drv *ctx;
+
+ ctx = platform_get_drvdata(pdev);
+ sst_context_cleanup(ctx);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+ }
+
+ static struct sst_machines sst_acpi_bytcr[] = {
+ {"10EC5640", "T100", "bytt100_rt5640", NULL, "intel/fw_sst_0f28.bin",
+ &byt_rvp_platform_data },
+ {},
+ };
+
+ /* Cherryview-based platforms: CherryTrail and Braswell */
+ static struct sst_machines sst_acpi_chv[] = {
+ {"10EC5670", "cht-bsw", "cht-bsw-rt5672", NULL, "intel/fw_sst_22a8.bin",
+ &chv_platform_data },
+ {"10EC5645", "cht-bsw", "cht-bsw-rt5645", NULL, "intel/fw_sst_22a8.bin",
+ &chv_platform_data },
+ {},
+ };
+
+ static const struct acpi_device_id sst_acpi_ids[] = {
+ { "80860F28", (unsigned long)&sst_acpi_bytcr},
+ { "808622A8", (unsigned long) &sst_acpi_chv},
+ { },
+ };
+
+ MODULE_DEVICE_TABLE(acpi, sst_acpi_ids);
+
+ static struct platform_driver sst_acpi_driver = {
+ .driver = {
+ .name = "intel_sst_acpi",
+ .acpi_match_table = ACPI_PTR(sst_acpi_ids),
+ .pm = &intel_sst_pm,
+ },
+ .probe = sst_acpi_probe,
+ .remove = sst_acpi_remove,
+ };
+
+ module_platform_driver(sst_acpi_driver);
+
+ MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver");
+ MODULE_AUTHOR("Ramesh Babu K V");
+ MODULE_AUTHOR("Omair Mohammed Abdullah");
+ MODULE_LICENSE("GPL v2");
+ MODULE_ALIAS("sst");
--- /dev/null
+ /*
+ * Intel SST loader on ACPI systems
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+ #include <linux/acpi.h>
+ #include <linux/device.h>
+ #include <linux/firmware.h>
+ #include <linux/module.h>
+ #include <linux/platform_device.h>
+
+ #include "sst-dsp.h"
+
+ #define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000
+ #define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000
+ #define SST_LPT_DSP_DMA_SIZE (1024 - 1)
+
+ /* Descriptor for SST ASoC machine driver */
+ struct sst_acpi_mach {
+ /* ACPI ID for the matching machine driver. Audio codec for instance */
+ const u8 id[ACPI_ID_LEN];
+ /* machine driver name */
+ const char *drv_name;
+ /* firmware file name */
+ const char *fw_filename;
+ };
+
+ /* Descriptor for setting up SST platform data */
+ struct sst_acpi_desc {
+ const char *drv_name;
+ struct sst_acpi_mach *machines;
+ /* Platform resource indexes. Must set to -1 if not used */
+ int resindex_lpe_base;
+ int resindex_pcicfg_base;
+ int resindex_fw_base;
+ int irqindex_host_ipc;
+ int resindex_dma_base;
+ /* Unique number identifying the SST core on platform */
+ int sst_id;
+ /* DMA only valid when resindex_dma_base != -1*/
+ int dma_engine;
+ int dma_size;
+ };
+
+ struct sst_acpi_priv {
+ struct platform_device *pdev_mach;
+ struct platform_device *pdev_pcm;
+ struct sst_pdata sst_pdata;
+ struct sst_acpi_desc *desc;
+ struct sst_acpi_mach *mach;
+ };
+
+ static void sst_acpi_fw_cb(const struct firmware *fw, void *context)
+ {
+ struct platform_device *pdev = context;
+ struct device *dev = &pdev->dev;
+ struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev);
+ struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata;
+ struct sst_acpi_desc *desc = sst_acpi->desc;
+ struct sst_acpi_mach *mach = sst_acpi->mach;
+
+ sst_pdata->fw = fw;
+ if (!fw) {
+ dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename);
+ return;
+ }
+
+ /* register PCM and DAI driver */
+ sst_acpi->pdev_pcm =
+ platform_device_register_data(dev, desc->drv_name, -1,
+ sst_pdata, sizeof(*sst_pdata));
+ if (IS_ERR(sst_acpi->pdev_pcm)) {
+ dev_err(dev, "Cannot register device %s. Error %d\n",
+ desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm));
+ }
+
+ return;
+ }
+
+ static acpi_status sst_acpi_mach_match(acpi_handle handle, u32 level,
+ void *context, void **ret)
+ {
+ *(bool *)context = true;
+ return AE_OK;
+ }
+
+ static struct sst_acpi_mach *sst_acpi_find_machine(
+ struct sst_acpi_mach *machines)
+ {
+ struct sst_acpi_mach *mach;
+ bool found = false;
+
+ for (mach = machines; mach->id[0]; mach++)
+ if (ACPI_SUCCESS(acpi_get_devices(mach->id,
+ sst_acpi_mach_match,
+ &found, NULL)) && found)
+ return mach;
+
+ return NULL;
+ }
+
+ static int sst_acpi_probe(struct platform_device *pdev)
+ {
+ const struct acpi_device_id *id;
+ struct device *dev = &pdev->dev;
+ struct sst_acpi_priv *sst_acpi;
+ struct sst_pdata *sst_pdata;
+ struct sst_acpi_mach *mach;
+ struct sst_acpi_desc *desc;
+ struct resource *mmio;
+ int ret = 0;
+
+ sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL);
+ if (sst_acpi == NULL)
+ return -ENOMEM;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+
+ desc = (struct sst_acpi_desc *)id->driver_data;
+ mach = sst_acpi_find_machine(desc->machines);
+ if (mach == NULL) {
+ dev_err(dev, "No matching ASoC machine driver found\n");
+ return -ENODEV;
+ }
+
+ sst_pdata = &sst_acpi->sst_pdata;
+ sst_pdata->id = desc->sst_id;
+ sst_pdata->dma_dev = dev;
+ sst_acpi->desc = desc;
+ sst_acpi->mach = mach;
+
++ sst_pdata->resindex_dma_base = desc->resindex_dma_base;
+ if (desc->resindex_dma_base >= 0) {
+ sst_pdata->dma_engine = desc->dma_engine;
+ sst_pdata->dma_base = desc->resindex_dma_base;
+ sst_pdata->dma_size = desc->dma_size;
+ }
+
+ if (desc->irqindex_host_ipc >= 0)
+ sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc);
+
+ if (desc->resindex_lpe_base >= 0) {
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM,
+ desc->resindex_lpe_base);
+ if (mmio) {
+ sst_pdata->lpe_base = mmio->start;
+ sst_pdata->lpe_size = resource_size(mmio);
+ }
+ }
+
+ if (desc->resindex_pcicfg_base >= 0) {
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM,
+ desc->resindex_pcicfg_base);
+ if (mmio) {
+ sst_pdata->pcicfg_base = mmio->start;
+ sst_pdata->pcicfg_size = resource_size(mmio);
+ }
+ }
+
+ if (desc->resindex_fw_base >= 0) {
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM,
+ desc->resindex_fw_base);
+ if (mmio) {
+ sst_pdata->fw_base = mmio->start;
+ sst_pdata->fw_size = resource_size(mmio);
+ }
+ }
+
+ platform_set_drvdata(pdev, sst_acpi);
+
+ /* register machine driver */
+ sst_acpi->pdev_mach =
+ platform_device_register_data(dev, mach->drv_name, -1,
+ sst_pdata, sizeof(*sst_pdata));
+ if (IS_ERR(sst_acpi->pdev_mach))
+ return PTR_ERR(sst_acpi->pdev_mach);
+
+ /* continue SST probing after firmware is loaded */
+ ret = request_firmware_nowait(THIS_MODULE, true, mach->fw_filename,
+ dev, GFP_KERNEL, pdev, sst_acpi_fw_cb);
+ if (ret)
+ platform_device_unregister(sst_acpi->pdev_mach);
+
+ return ret;
+ }
+
+ static int sst_acpi_remove(struct platform_device *pdev)
+ {
+ struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev);
+ struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata;
+
+ platform_device_unregister(sst_acpi->pdev_mach);
+ if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm))
+ platform_device_unregister(sst_acpi->pdev_pcm);
+ release_firmware(sst_pdata->fw);
+
+ return 0;
+ }
+
+ static struct sst_acpi_mach haswell_machines[] = {
+ { "INT33CA", "haswell-audio", "intel/IntcSST1.bin" },
+ {}
+ };
+
+ static struct sst_acpi_desc sst_acpi_haswell_desc = {
+ .drv_name = "haswell-pcm-audio",
+ .machines = haswell_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_fw_base = -1,
+ .irqindex_host_ipc = 0,
+ .sst_id = SST_DEV_ID_LYNX_POINT,
+ .dma_engine = SST_DMA_TYPE_DW,
+ .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET,
+ .dma_size = SST_LPT_DSP_DMA_SIZE,
+ };
+
+ static struct sst_acpi_mach broadwell_machines[] = {
+ { "INT343A", "broadwell-audio", "intel/IntcSST2.bin" },
+ {}
+ };
+
+ static struct sst_acpi_desc sst_acpi_broadwell_desc = {
+ .drv_name = "haswell-pcm-audio",
+ .machines = broadwell_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_fw_base = -1,
+ .irqindex_host_ipc = 0,
+ .sst_id = SST_DEV_ID_WILDCAT_POINT,
+ .dma_engine = SST_DMA_TYPE_DW,
+ .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET,
+ .dma_size = SST_LPT_DSP_DMA_SIZE,
+ };
+
+ static struct sst_acpi_mach baytrail_machines[] = {
+ { "10EC5640", "byt-rt5640", "intel/fw_sst_0f28.bin-48kHz_i2s_master" },
+ { "193C9890", "byt-max98090", "intel/fw_sst_0f28.bin-48kHz_i2s_master" },
+ {}
+ };
+
+ static struct sst_acpi_desc sst_acpi_baytrail_desc = {
+ .drv_name = "baytrail-pcm-audio",
+ .machines = baytrail_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_fw_base = 2,
+ .irqindex_host_ipc = 5,
+ .sst_id = SST_DEV_ID_BYT,
+ .resindex_dma_base = -1,
+ };
+
+ static struct acpi_device_id sst_acpi_match[] = {
+ { "INT33C8", (unsigned long)&sst_acpi_haswell_desc },
+ { "INT3438", (unsigned long)&sst_acpi_broadwell_desc },
+ { "80860F28", (unsigned long)&sst_acpi_baytrail_desc },
+ { }
+ };
+ MODULE_DEVICE_TABLE(acpi, sst_acpi_match);
+
+ static struct platform_driver sst_acpi_driver = {
+ .probe = sst_acpi_probe,
+ .remove = sst_acpi_remove,
+ .driver = {
+ .name = "sst-acpi",
+ .acpi_match_table = ACPI_PTR(sst_acpi_match),
+ },
+ };
+ module_platform_driver(sst_acpi_driver);
+
+ MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
+ MODULE_DESCRIPTION("Intel SST loader on ACPI systems");
+ MODULE_LICENSE("GPL v2");
--- /dev/null
+ /*
+ * Intel Smart Sound Technology (SST) Core
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+ #ifndef __SOUND_SOC_SST_DSP_H
+ #define __SOUND_SOC_SST_DSP_H
+
+ #include <linux/kernel.h>
+ #include <linux/types.h>
+ #include <linux/interrupt.h>
+
+ /* SST Device IDs */
+ #define SST_DEV_ID_LYNX_POINT 0x33C8
+ #define SST_DEV_ID_WILDCAT_POINT 0x3438
+ #define SST_DEV_ID_BYT 0x0F28
+
+ /* Supported SST DMA Devices */
+ #define SST_DMA_TYPE_DW 1
+
+ /* autosuspend delay 5s*/
+ #define SST_RUNTIME_SUSPEND_DELAY (5 * 1000)
+
+ /* SST Shim register map
+ * The register naming can differ between products. Some products also
+ * contain extra functionality.
+ */
+ #define SST_CSR 0x00
+ #define SST_PISR 0x08
+ #define SST_PIMR 0x10
+ #define SST_ISRX 0x18
+ #define SST_ISRD 0x20
+ #define SST_IMRX 0x28
+ #define SST_IMRD 0x30
+ #define SST_IPCX 0x38 /* IPC IA -> SST */
+ #define SST_IPCD 0x40 /* IPC SST -> IA */
+ #define SST_ISRSC 0x48
+ #define SST_ISRLPESC 0x50
+ #define SST_IMRSC 0x58
+ #define SST_IMRLPESC 0x60
+ #define SST_IPCSC 0x68
+ #define SST_IPCLPESC 0x70
+ #define SST_CLKCTL 0x78
+ #define SST_CSR2 0x80
+ #define SST_LTRC 0xE0
+ #define SST_HMDC 0xE8
+
+ #define SST_SHIM_BEGIN SST_CSR
+ #define SST_SHIM_END SST_HDMC
+
+ #define SST_DBGO 0xF0
+
+ #define SST_SHIM_SIZE 0x100
+ #define SST_PWMCTRL 0x1000
+
+ /* SST Shim Register bits
+ * The register bit naming can differ between products. Some products also
+ * contain extra functionality.
+ */
+
+ /* CSR / CS */
+ #define SST_CSR_RST (0x1 << 1)
+ #define SST_CSR_SBCS0 (0x1 << 2)
+ #define SST_CSR_SBCS1 (0x1 << 3)
+ #define SST_CSR_DCS(x) (x << 4)
+ #define SST_CSR_DCS_MASK (0x7 << 4)
+ #define SST_CSR_STALL (0x1 << 10)
+ #define SST_CSR_S0IOCS (0x1 << 21)
+ #define SST_CSR_S1IOCS (0x1 << 23)
+ #define SST_CSR_LPCS (0x1 << 31)
+ #define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS)
+ #define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1)
+ #define SST_BYT_CSR_RST (0x1 << 0)
+ #define SST_BYT_CSR_VECTOR_SEL (0x1 << 1)
+ #define SST_BYT_CSR_STALL (0x1 << 2)
+ #define SST_BYT_CSR_PWAITMODE (0x1 << 3)
+
+ /* ISRX / ISC */
+ #define SST_ISRX_BUSY (0x1 << 1)
+ #define SST_ISRX_DONE (0x1 << 0)
+ #define SST_BYT_ISRX_REQUEST (0x1 << 1)
+
+ /* ISRD / ISD */
+ #define SST_ISRD_BUSY (0x1 << 1)
+ #define SST_ISRD_DONE (0x1 << 0)
+
+ /* IMRX / IMC */
+ #define SST_IMRX_BUSY (0x1 << 1)
+ #define SST_IMRX_DONE (0x1 << 0)
+ #define SST_BYT_IMRX_REQUEST (0x1 << 1)
+
+ /* IMRD / IMD */
+ #define SST_IMRD_DONE (0x1 << 0)
+ #define SST_IMRD_BUSY (0x1 << 1)
+ #define SST_IMRD_SSP0 (0x1 << 16)
+ #define SST_IMRD_DMAC0 (0x1 << 21)
+ #define SST_IMRD_DMAC1 (0x1 << 22)
+ #define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1)
+
+ /* IPCX / IPCC */
+ #define SST_IPCX_DONE (0x1 << 30)
+ #define SST_IPCX_BUSY (0x1 << 31)
+ #define SST_BYT_IPCX_DONE ((u64)0x1 << 62)
+ #define SST_BYT_IPCX_BUSY ((u64)0x1 << 63)
+
+ /* IPCD */
+ #define SST_IPCD_DONE (0x1 << 30)
+ #define SST_IPCD_BUSY (0x1 << 31)
+ #define SST_BYT_IPCD_DONE ((u64)0x1 << 62)
+ #define SST_BYT_IPCD_BUSY ((u64)0x1 << 63)
+
+ /* CLKCTL */
+ #define SST_CLKCTL_SMOS(x) (x << 24)
+ #define SST_CLKCTL_MASK (3 << 24)
+ #define SST_CLKCTL_DCPLCG (1 << 18)
+ #define SST_CLKCTL_SCOE1 (1 << 17)
+ #define SST_CLKCTL_SCOE0 (1 << 16)
+
+ /* CSR2 / CS2 */
+ #define SST_CSR2_SDFD_SSP0 (1 << 1)
+ #define SST_CSR2_SDFD_SSP1 (1 << 2)
+
+ /* LTRC */
+ #define SST_LTRC_VAL(x) (x << 0)
+
+ /* HMDC */
+ #define SST_HMDC_HDDA0(x) (x << 0)
+ #define SST_HMDC_HDDA1(x) (x << 7)
+ #define SST_HMDC_HDDA_E0_CH0 1
+ #define SST_HMDC_HDDA_E0_CH1 2
+ #define SST_HMDC_HDDA_E0_CH2 4
+ #define SST_HMDC_HDDA_E0_CH3 8
+ #define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0)
+ #define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1)
+ #define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2)
+ #define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3)
+ #define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \
+ SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3)
+ #define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \
+ SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3)
+
+
+ /* SST Vendor Defined Registers and bits */
+ #define SST_VDRTCTL0 0xa0
+ #define SST_VDRTCTL1 0xa4
+ #define SST_VDRTCTL2 0xa8
+ #define SST_VDRTCTL3 0xaC
+
+ /* VDRTCTL0 */
+ #define SST_VDRTCL0_D3PGD (1 << 0)
+ #define SST_VDRTCL0_D3SRAMPGD (1 << 1)
+ #define SST_VDRTCL0_DSRAMPGE_SHIFT 12
+ #define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT)
+ #define SST_VDRTCL0_ISRAMPGE_SHIFT 2
+ #define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT)
+
+ /* VDRTCTL2 */
+ #define SST_VDRTCL2_DCLCGE (1 << 1)
+ #define SST_VDRTCL2_DTCGE (1 << 10)
+ #define SST_VDRTCL2_APLLSE_MASK (1 << 31)
+
+ /* PMCS */
+ #define SST_PMCS 0x84
+ #define SST_PMCS_PS_MASK 0x3
+
+ struct sst_dsp;
+
+ /*
+ * SST Device.
+ *
+ * This structure is populated by the SST core driver.
+ */
+ struct sst_dsp_device {
+ /* Mandatory fields */
+ struct sst_ops *ops;
+ irqreturn_t (*thread)(int irq, void *context);
+ void *thread_context;
+ };
+
+ /*
+ * SST Platform Data.
+ */
+ struct sst_pdata {
+ /* ACPI data */
+ u32 lpe_base;
+ u32 lpe_size;
+ u32 pcicfg_base;
+ u32 pcicfg_size;
+ u32 fw_base;
+ u32 fw_size;
+ int irq;
+
+ /* Firmware */
+ const struct firmware *fw;
+
+ /* DMA */
++ int resindex_dma_base; /* other fields invalid if equals to -1 */
+ u32 dma_base;
+ u32 dma_size;
+ int dma_engine;
+ struct device *dma_dev;
+
+ /* DSP */
+ u32 id;
+ void *dsp;
+ };
+
+ /* Initialization */
+ struct sst_dsp *sst_dsp_new(struct device *dev,
+ struct sst_dsp_device *sst_dev, struct sst_pdata *pdata);
+ void sst_dsp_free(struct sst_dsp *sst);
+
+ /* SHIM Read / Write */
+ void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value);
+ u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset);
+ int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value);
+ void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value);
+ u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset);
+ int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset,
+ u64 mask, u64 value);
+
+ /* SHIM Read / Write Unlocked for callers already holding sst lock */
+ void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value);
+ u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset);
+ int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset,
+ u32 mask, u32 value);
+ void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value);
+ u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset);
+ int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset,
+ u64 mask, u64 value);
+
+ /* Internal generic low-level SST IO functions - can be overidden */
+ void sst_shim32_write(void __iomem *addr, u32 offset, u32 value);
+ u32 sst_shim32_read(void __iomem *addr, u32 offset);
+ void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value);
+ u64 sst_shim32_read64(void __iomem *addr, u32 offset);
+ void sst_memcpy_toio_32(struct sst_dsp *sst,
+ void __iomem *dest, void *src, size_t bytes);
+ void sst_memcpy_fromio_32(struct sst_dsp *sst,
+ void *dest, void __iomem *src, size_t bytes);
+
+ /* DSP reset & boot */
+ void sst_dsp_reset(struct sst_dsp *sst);
+ int sst_dsp_boot(struct sst_dsp *sst);
+ int sst_dsp_wake(struct sst_dsp *sst);
+ void sst_dsp_sleep(struct sst_dsp *sst);
+ void sst_dsp_stall(struct sst_dsp *sst);
+
+ /* DMA */
+ int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id);
+ void sst_dsp_dma_put_channel(struct sst_dsp *dsp);
+ int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size);
+ int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size);
+
+ /* Msg IO */
+ void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg);
+ u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp);
+
+ /* Mailbox management */
+ int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset,
+ size_t inbox_size, u32 outbox_offset, size_t outbox_size);
+ void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes);
+ void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes);
+ void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes);
+ void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes);
+ void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes);
+
+ /* Debug */
+ void sst_dsp_dump(struct sst_dsp *sst);
+
+ #endif
--- /dev/null
+ /*
+ * Intel SST Firmware Loader
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+ #include <linux/kernel.h>
+ #include <linux/slab.h>
+ #include <linux/sched.h>
+ #include <linux/firmware.h>
+ #include <linux/export.h>
+ #include <linux/platform_device.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/dmaengine.h>
+ #include <linux/pci.h>
+ #include <linux/acpi.h>
+
+ /* supported DMA engine drivers */
+ #include <linux/platform_data/dma-dw.h>
+ #include <linux/dma/dw.h>
+
+ #include <asm/page.h>
+ #include <asm/pgtable.h>
+
+ #include "sst-dsp.h"
+ #include "sst-dsp-priv.h"
+
+ #define SST_DMA_RESOURCES 2
+ #define SST_DSP_DMA_MAX_BURST 0x3
+ #define SST_HSW_BLOCK_ANY 0xffffffff
+
+ #define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000
+
+ struct sst_dma {
+ struct sst_dsp *sst;
+
+ struct dw_dma_chip *chip;
+
+ struct dma_async_tx_descriptor *desc;
+ struct dma_chan *ch;
+ };
+
+ static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes)
+ {
+ /* __iowrite32_copy use 32bit size values so divide by 4 */
+ __iowrite32_copy((void *)dest, src, bytes/4);
+ }
+
+ static void sst_dma_transfer_complete(void *arg)
+ {
+ struct sst_dsp *sst = (struct sst_dsp *)arg;
+
+ dev_dbg(sst->dev, "DMA: callback\n");
+ }
+
+ static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size)
+ {
+ struct dma_async_tx_descriptor *desc;
+ struct sst_dma *dma = sst->dma;
+
+ if (dma->ch == NULL) {
+ dev_err(sst->dev, "error: no DMA channel\n");
+ return -ENODEV;
+ }
+
+ dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n",
+ (unsigned long)src_addr, (unsigned long)dest_addr, size);
+
+ desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr,
+ src_addr, size, DMA_CTRL_ACK);
+ if (!desc){
+ dev_err(sst->dev, "error: dma prep memcpy failed\n");
+ return -EINVAL;
+ }
+
+ desc->callback = sst_dma_transfer_complete;
+ desc->callback_param = sst;
+
+ desc->tx_submit(desc);
+ dma_wait_for_async_tx(desc);
+
+ return 0;
+ }
+
+ /* copy to DSP */
+ int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size)
+ {
+ return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP,
+ src_addr, size);
+ }
+ EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto);
+
+ /* copy from DSP */
+ int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr,
+ dma_addr_t src_addr, size_t size)
+ {
+ return sst_dsp_dma_copy(sst, dest_addr,
+ src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size);
+ }
+ EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom);
+
+ /* remove module from memory - callers hold locks */
+ static void block_list_remove(struct sst_dsp *dsp,
+ struct list_head *block_list)
+ {
+ struct sst_mem_block *block, *tmp;
+ int err;
+
+ /* disable each block */
+ list_for_each_entry(block, block_list, module_list) {
+
+ if (block->ops && block->ops->disable) {
+ err = block->ops->disable(block);
+ if (err < 0)
+ dev_err(dsp->dev,
+ "error: cant disable block %d:%d\n",
+ block->type, block->index);
+ }
+ }
+
+ /* mark each block as free */
+ list_for_each_entry_safe(block, tmp, block_list, module_list) {
+ list_del(&block->module_list);
+ list_move(&block->list, &dsp->free_block_list);
+ dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ }
+ }
+
+ /* prepare the memory block to receive data from host - callers hold locks */
+ static int block_list_prepare(struct sst_dsp *dsp,
+ struct list_head *block_list)
+ {
+ struct sst_mem_block *block;
+ int ret = 0;
+
+ /* enable each block so that's it'e ready for data */
+ list_for_each_entry(block, block_list, module_list) {
+
+ if (block->ops && block->ops->enable && !block->users) {
+ ret = block->ops->enable(block);
+ if (ret < 0) {
+ dev_err(dsp->dev,
+ "error: cant disable block %d:%d\n",
+ block->type, block->index);
+ goto err;
+ }
+ }
+ }
+ return ret;
+
+ err:
+ list_for_each_entry(block, block_list, module_list) {
+ if (block->ops && block->ops->disable)
+ block->ops->disable(block);
+ }
+ return ret;
+ }
+
+ static struct dw_dma_platform_data dw_pdata = {
+ .is_private = 1,
+ .chan_allocation_order = CHAN_ALLOCATION_ASCENDING,
+ .chan_priority = CHAN_PRIORITY_ASCENDING,
+ };
+
+ static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem,
+ int irq)
+ {
+ struct dw_dma_chip *chip;
+ int err;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return ERR_PTR(-ENOMEM);
+
+ chip->irq = irq;
+ chip->regs = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(chip->regs))
+ return ERR_CAST(chip->regs);
+
+ err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31));
+ if (err)
+ return ERR_PTR(err);
+
+ chip->dev = dev;
+ err = dw_dma_probe(chip, &dw_pdata);
+ if (err)
+ return ERR_PTR(err);
+
+ return chip;
+ }
+
+ static void dw_remove(struct dw_dma_chip *chip)
+ {
+ dw_dma_remove(chip);
+ }
+
+ static bool dma_chan_filter(struct dma_chan *chan, void *param)
+ {
+ struct sst_dsp *dsp = (struct sst_dsp *)param;
+
+ return chan->device->dev == dsp->dma_dev;
+ }
+
+ int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id)
+ {
+ struct sst_dma *dma = dsp->dma;
+ struct dma_slave_config slave;
+ dma_cap_mask_t mask;
+ int ret;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ dma->ch = dma_request_channel(mask, dma_chan_filter, dsp);
+ if (dma->ch == NULL) {
+ dev_err(dsp->dev, "error: DMA request channel failed\n");
+ return -EIO;
+ }
+
+ memset(&slave, 0, sizeof(slave));
+ slave.direction = DMA_MEM_TO_DEV;
+ slave.src_addr_width =
+ slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST;
+
+ ret = dmaengine_slave_config(dma->ch, &slave);
+ if (ret) {
+ dev_err(dsp->dev, "error: unable to set DMA slave config %d\n",
+ ret);
+ dma_release_channel(dma->ch);
+ dma->ch = NULL;
+ }
+
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel);
+
+ void sst_dsp_dma_put_channel(struct sst_dsp *dsp)
+ {
+ struct sst_dma *dma = dsp->dma;
+
+ if (!dma->ch)
+ return;
+
+ dma_release_channel(dma->ch);
+ dma->ch = NULL;
+ }
+ EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel);
+
+ int sst_dma_new(struct sst_dsp *sst)
+ {
+ struct sst_pdata *sst_pdata = sst->pdata;
+ struct sst_dma *dma;
+ struct resource mem;
+ const char *dma_dev_name;
+ int ret = 0;
+
++ if (sst->pdata->resindex_dma_base == -1)
++ /* DMA is not used, return and squelsh error messages */
++ return 0;
++
+ /* configure the correct platform data for whatever DMA engine
+ * is attached to the ADSP IP. */
+ switch (sst->pdata->dma_engine) {
+ case SST_DMA_TYPE_DW:
+ dma_dev_name = "dw_dmac";
+ break;
+ default:
+ dev_err(sst->dev, "error: invalid DMA engine %d\n",
+ sst->pdata->dma_engine);
+ return -EINVAL;
+ }
+
+ dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+
+ dma->sst = sst;
+
+ memset(&mem, 0, sizeof(mem));
+
+ mem.start = sst->addr.lpe_base + sst_pdata->dma_base;
+ mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1;
+ mem.flags = IORESOURCE_MEM;
+
+ /* now register DMA engine device */
+ dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq);
+ if (IS_ERR(dma->chip)) {
+ dev_err(sst->dev, "error: DMA device register failed\n");
+ ret = PTR_ERR(dma->chip);
+ goto err_dma_dev;
+ }
+
+ sst->dma = dma;
+ sst->fw_use_dma = true;
+ return 0;
+
+ err_dma_dev:
+ devm_kfree(sst->dev, dma);
+ return ret;
+ }
+ EXPORT_SYMBOL(sst_dma_new);
+
+ void sst_dma_free(struct sst_dma *dma)
+ {
+
+ if (dma == NULL)
+ return;
+
+ if (dma->ch)
+ dma_release_channel(dma->ch);
+
+ if (dma->chip)
+ dw_remove(dma->chip);
+
+ }
+ EXPORT_SYMBOL(sst_dma_free);
+
+ /* create new generic firmware object */
+ struct sst_fw *sst_fw_new(struct sst_dsp *dsp,
+ const struct firmware *fw, void *private)
+ {
+ struct sst_fw *sst_fw;
+ int err;
+
+ if (!dsp->ops->parse_fw)
+ return NULL;
+
+ sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL);
+ if (sst_fw == NULL)
+ return NULL;
+
+ sst_fw->dsp = dsp;
+ sst_fw->private = private;
+ sst_fw->size = fw->size;
+
+ /* allocate DMA buffer to store FW data */
+ sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size,
+ &sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL);
+ if (!sst_fw->dma_buf) {
+ dev_err(dsp->dev, "error: DMA alloc failed\n");
+ kfree(sst_fw);
+ return NULL;
+ }
+
+ /* copy FW data to DMA-able memory */
+ memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size);
+
+ if (dsp->fw_use_dma) {
+ err = sst_dsp_dma_get_channel(dsp, 0);
+ if (err < 0)
+ goto chan_err;
+ }
+
+ /* call core specific FW paser to load FW data into DSP */
+ err = dsp->ops->parse_fw(sst_fw);
+ if (err < 0) {
+ dev_err(dsp->dev, "error: parse fw failed %d\n", err);
+ goto parse_err;
+ }
+
+ if (dsp->fw_use_dma)
+ sst_dsp_dma_put_channel(dsp);
+
+ mutex_lock(&dsp->mutex);
+ list_add(&sst_fw->list, &dsp->fw_list);
+ mutex_unlock(&dsp->mutex);
+
+ return sst_fw;
+
+ parse_err:
+ if (dsp->fw_use_dma)
+ sst_dsp_dma_put_channel(dsp);
+ chan_err:
+ dma_free_coherent(dsp->dma_dev, sst_fw->size,
+ sst_fw->dma_buf,
+ sst_fw->dmable_fw_paddr);
+ sst_fw->dma_buf = NULL;
+ kfree(sst_fw);
+ return NULL;
+ }
+ EXPORT_SYMBOL_GPL(sst_fw_new);
+
+ int sst_fw_reload(struct sst_fw *sst_fw)
+ {
+ struct sst_dsp *dsp = sst_fw->dsp;
+ int ret;
+
+ dev_dbg(dsp->dev, "reloading firmware\n");
+
+ /* call core specific FW paser to load FW data into DSP */
+ ret = dsp->ops->parse_fw(sst_fw);
+ if (ret < 0)
+ dev_err(dsp->dev, "error: parse fw failed %d\n", ret);
+
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_fw_reload);
+
+ void sst_fw_unload(struct sst_fw *sst_fw)
+ {
+ struct sst_dsp *dsp = sst_fw->dsp;
+ struct sst_module *module, *mtmp;
+ struct sst_module_runtime *runtime, *rtmp;
+
+ dev_dbg(dsp->dev, "unloading firmware\n");
+
+ mutex_lock(&dsp->mutex);
+
+ /* check module by module */
+ list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) {
+ if (module->sst_fw == sst_fw) {
+
+ /* remove runtime modules */
+ list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) {
+
+ block_list_remove(dsp, &runtime->block_list);
+ list_del(&runtime->list);
+ kfree(runtime);
+ }
+
+ /* now remove the module */
+ block_list_remove(dsp, &module->block_list);
+ list_del(&module->list);
+ kfree(module);
+ }
+ }
+
+ /* remove all scratch blocks */
+ block_list_remove(dsp, &dsp->scratch_block_list);
+
+ mutex_unlock(&dsp->mutex);
+ }
+ EXPORT_SYMBOL_GPL(sst_fw_unload);
+
+ /* free single firmware object */
+ void sst_fw_free(struct sst_fw *sst_fw)
+ {
+ struct sst_dsp *dsp = sst_fw->dsp;
+
+ mutex_lock(&dsp->mutex);
+ list_del(&sst_fw->list);
+ mutex_unlock(&dsp->mutex);
+
+ if (sst_fw->dma_buf)
+ dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf,
+ sst_fw->dmable_fw_paddr);
+ kfree(sst_fw);
+ }
+ EXPORT_SYMBOL_GPL(sst_fw_free);
+
+ /* free all firmware objects */
+ void sst_fw_free_all(struct sst_dsp *dsp)
+ {
+ struct sst_fw *sst_fw, *t;
+
+ mutex_lock(&dsp->mutex);
+ list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) {
+
+ list_del(&sst_fw->list);
+ dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf,
+ sst_fw->dmable_fw_paddr);
+ kfree(sst_fw);
+ }
+ mutex_unlock(&dsp->mutex);
+ }
+ EXPORT_SYMBOL_GPL(sst_fw_free_all);
+
+ /* create a new SST generic module from FW template */
+ struct sst_module *sst_module_new(struct sst_fw *sst_fw,
+ struct sst_module_template *template, void *private)
+ {
+ struct sst_dsp *dsp = sst_fw->dsp;
+ struct sst_module *sst_module;
+
+ sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL);
+ if (sst_module == NULL)
+ return NULL;
+
+ sst_module->id = template->id;
+ sst_module->dsp = dsp;
+ sst_module->sst_fw = sst_fw;
+ sst_module->scratch_size = template->scratch_size;
+ sst_module->persistent_size = template->persistent_size;
+ sst_module->entry = template->entry;
+ sst_module->state = SST_MODULE_STATE_UNLOADED;
+
+ INIT_LIST_HEAD(&sst_module->block_list);
+ INIT_LIST_HEAD(&sst_module->runtime_list);
+
+ mutex_lock(&dsp->mutex);
+ list_add(&sst_module->list, &dsp->module_list);
+ mutex_unlock(&dsp->mutex);
+
+ return sst_module;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_new);
+
+ /* free firmware module and remove from available list */
+ void sst_module_free(struct sst_module *sst_module)
+ {
+ struct sst_dsp *dsp = sst_module->dsp;
+
+ mutex_lock(&dsp->mutex);
+ list_del(&sst_module->list);
+ mutex_unlock(&dsp->mutex);
+
+ kfree(sst_module);
+ }
+ EXPORT_SYMBOL_GPL(sst_module_free);
+
+ struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module,
+ int id, void *private)
+ {
+ struct sst_dsp *dsp = module->dsp;
+ struct sst_module_runtime *runtime;
+
+ runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
+ if (runtime == NULL)
+ return NULL;
+
+ runtime->id = id;
+ runtime->dsp = dsp;
+ runtime->module = module;
+ INIT_LIST_HEAD(&runtime->block_list);
+
+ mutex_lock(&dsp->mutex);
+ list_add(&runtime->list, &module->runtime_list);
+ mutex_unlock(&dsp->mutex);
+
+ return runtime;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_new);
+
+ void sst_module_runtime_free(struct sst_module_runtime *runtime)
+ {
+ struct sst_dsp *dsp = runtime->dsp;
+
+ mutex_lock(&dsp->mutex);
+ list_del(&runtime->list);
+ mutex_unlock(&dsp->mutex);
+
+ kfree(runtime);
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_free);
+
+ static struct sst_mem_block *find_block(struct sst_dsp *dsp,
+ struct sst_block_allocator *ba)
+ {
+ struct sst_mem_block *block;
+
+ list_for_each_entry(block, &dsp->free_block_list, list) {
+ if (block->type == ba->type && block->offset == ba->offset)
+ return block;
+ }
+
+ return NULL;
+ }
+
+ /* Block allocator must be on block boundary */
+ static int block_alloc_contiguous(struct sst_dsp *dsp,
+ struct sst_block_allocator *ba, struct list_head *block_list)
+ {
+ struct list_head tmp = LIST_HEAD_INIT(tmp);
+ struct sst_mem_block *block;
+ u32 block_start = SST_HSW_BLOCK_ANY;
+ int size = ba->size, offset = ba->offset;
+
+ while (ba->size > 0) {
+
+ block = find_block(dsp, ba);
+ if (!block) {
+ list_splice(&tmp, &dsp->free_block_list);
+
+ ba->size = size;
+ ba->offset = offset;
+ return -ENOMEM;
+ }
+
+ list_move_tail(&block->list, &tmp);
+ ba->offset += block->size;
+ ba->size -= block->size;
+ }
+ ba->size = size;
+ ba->offset = offset;
+
+ list_for_each_entry(block, &tmp, list) {
+
+ if (block->offset < block_start)
+ block_start = block->offset;
+
+ list_add(&block->module_list, block_list);
+
+ dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ }
+
+ list_splice(&tmp, &dsp->used_block_list);
+ return 0;
+ }
+
+ /* allocate first free DSP blocks for data - callers hold locks */
+ static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list)
+ {
+ struct sst_mem_block *block, *tmp;
+ int ret = 0;
+
+ if (ba->size == 0)
+ return 0;
+
+ /* find first free whole blocks that can hold module */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ if (ba->size > block->size)
+ continue;
+
+ ba->offset = block->offset;
+ block->bytes_used = ba->size % block->size;
+ list_add(&block->module_list, block_list);
+ list_move(&block->list, &dsp->used_block_list);
+ dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ return 0;
+ }
+
+ /* then find free multiple blocks that can hold module */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ /* do we span > 1 blocks */
+ if (ba->size > block->size) {
+
+ /* align ba to block boundary */
+ ba->offset = block->offset;
+
+ ret = block_alloc_contiguous(dsp, ba, block_list);
+ if (ret == 0)
+ return ret;
+
+ }
+ }
+
+ /* not enough free block space */
+ return -ENOMEM;
+ }
+
+ int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list)
+ {
+ int ret;
+
+ dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n",
+ ba->size, ba->offset, ba->type);
+
+ mutex_lock(&dsp->mutex);
+
+ ret = block_alloc(dsp, ba, block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret);
+ goto out;
+ }
+
+ /* prepare DSP blocks for module usage */
+ ret = block_list_prepare(dsp, block_list);
+ if (ret < 0)
+ dev_err(dsp->dev, "error: prepare failed\n");
+
+ out:
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_alloc_blocks);
+
+ int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list)
+ {
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, block_list);
+ mutex_unlock(&dsp->mutex);
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(sst_free_blocks);
+
+ /* allocate memory blocks for static module addresses - callers hold locks */
+ static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba,
+ struct list_head *block_list)
+ {
+ struct sst_mem_block *block, *tmp;
+ struct sst_block_allocator ba_tmp = *ba;
+ u32 end = ba->offset + ba->size, block_end;
+ int err;
+
+ /* only IRAM/DRAM blocks are managed */
+ if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM)
+ return 0;
+
+ /* are blocks already attached to this module */
+ list_for_each_entry_safe(block, tmp, block_list, module_list) {
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ block_end = block->offset + block->size;
+
+ /* find block that holds section */
+ if (ba->offset >= block->offset && end <= block_end)
+ return 0;
+
+ /* does block span more than 1 section */
+ if (ba->offset >= block->offset && ba->offset < block_end) {
+
+ /* align ba to block boundary */
+ ba_tmp.size -= block_end - ba->offset;
+ ba_tmp.offset = block_end;
+ err = block_alloc_contiguous(dsp, &ba_tmp, block_list);
+ if (err < 0)
+ return -ENOMEM;
+
+ /* module already owns blocks */
+ return 0;
+ }
+ }
+
+ /* find first free blocks that can hold section in free list */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+ block_end = block->offset + block->size;
+
+ /* ignore blocks with wrong type */
+ if (block->type != ba->type)
+ continue;
+
+ /* find block that holds section */
+ if (ba->offset >= block->offset && end <= block_end) {
+
+ /* add block */
+ list_move(&block->list, &dsp->used_block_list);
+ list_add(&block->module_list, block_list);
+ dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+ return 0;
+ }
+
+ /* does block span more than 1 section */
+ if (ba->offset >= block->offset && ba->offset < block_end) {
+
+ /* add block */
+ list_move(&block->list, &dsp->used_block_list);
+ list_add(&block->module_list, block_list);
+ /* align ba to block boundary */
+ ba_tmp.size -= block_end - ba->offset;
+ ba_tmp.offset = block_end;
+
+ err = block_alloc_contiguous(dsp, &ba_tmp, block_list);
+ if (err < 0)
+ return -ENOMEM;
+
+ return 0;
+ }
+ }
+
+ return -ENOMEM;
+ }
+
+ /* Load fixed module data into DSP memory blocks */
+ int sst_module_alloc_blocks(struct sst_module *module)
+ {
+ struct sst_dsp *dsp = module->dsp;
+ struct sst_fw *sst_fw = module->sst_fw;
+ struct sst_block_allocator ba;
+ int ret;
+
+ memset(&ba, 0, sizeof(ba));
+ ba.size = module->size;
+ ba.type = module->type;
+ ba.offset = module->offset;
+
+ dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n",
+ ba.size, ba.offset, ba.type);
+
+ mutex_lock(&dsp->mutex);
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc_fixed(dsp, &ba, &module->block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev,
+ "error: no free blocks for section at offset 0x%x size 0x%x\n",
+ module->offset, module->size);
+ mutex_unlock(&dsp->mutex);
+ return -ENOMEM;
+ }
+
+ /* prepare DSP blocks for module copy */
+ ret = block_list_prepare(dsp, &module->block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: fw module prepare failed\n");
+ goto err;
+ }
+
+ /* copy partial module data to blocks */
+ if (dsp->fw_use_dma) {
+ ret = sst_dsp_dma_copyto(dsp,
+ dsp->addr.lpe_base + module->offset,
+ sst_fw->dmable_fw_paddr + module->data_offset,
+ module->size);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: module copy failed\n");
+ goto err;
+ }
+ } else
+ sst_memcpy32(dsp->addr.lpe + module->offset, module->data,
+ module->size);
+
+ mutex_unlock(&dsp->mutex);
+ return ret;
+
+ err:
+ block_list_remove(dsp, &module->block_list);
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_alloc_blocks);
+
+ /* Unload entire module from DSP memory */
+ int sst_module_free_blocks(struct sst_module *module)
+ {
+ struct sst_dsp *dsp = module->dsp;
+
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, &module->block_list);
+ mutex_unlock(&dsp->mutex);
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_free_blocks);
+
+ int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime,
+ int offset)
+ {
+ struct sst_dsp *dsp = runtime->dsp;
+ struct sst_module *module = runtime->module;
+ struct sst_block_allocator ba;
+ int ret;
+
+ if (module->persistent_size == 0)
+ return 0;
+
+ memset(&ba, 0, sizeof(ba));
+ ba.size = module->persistent_size;
+ ba.type = SST_MEM_DRAM;
+
+ mutex_lock(&dsp->mutex);
+
+ /* do we need to allocate at a fixed address ? */
+ if (offset != 0) {
+
+ ba.offset = offset;
+
+ dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n",
+ ba.size, ba.type, ba.offset);
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc_fixed(dsp, &ba, &runtime->block_list);
+
+ } else {
+ dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n",
+ ba.size, ba.type);
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc(dsp, &ba, &runtime->block_list);
+ }
+ if (ret < 0) {
+ dev_err(dsp->dev,
+ "error: no free blocks for runtime module size 0x%x\n",
+ module->persistent_size);
+ mutex_unlock(&dsp->mutex);
+ return -ENOMEM;
+ }
+ runtime->persistent_offset = ba.offset;
+
+ /* prepare DSP blocks for module copy */
+ ret = block_list_prepare(dsp, &runtime->block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: runtime block prepare failed\n");
+ goto err;
+ }
+
+ mutex_unlock(&dsp->mutex);
+ return ret;
+
+ err:
+ block_list_remove(dsp, &module->block_list);
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks);
+
+ int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime)
+ {
+ struct sst_dsp *dsp = runtime->dsp;
+
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, &runtime->block_list);
+ mutex_unlock(&dsp->mutex);
+ return 0;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks);
+
+ int sst_module_runtime_save(struct sst_module_runtime *runtime,
+ struct sst_module_runtime_context *context)
+ {
+ struct sst_dsp *dsp = runtime->dsp;
+ struct sst_module *module = runtime->module;
+ int ret = 0;
+
+ dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n",
+ runtime->id, runtime->persistent_offset,
+ module->persistent_size);
+
+ context->buffer = dma_alloc_coherent(dsp->dma_dev,
+ module->persistent_size,
+ &context->dma_buffer, GFP_DMA | GFP_KERNEL);
+ if (!context->buffer) {
+ dev_err(dsp->dev, "error: DMA context alloc failed\n");
+ return -ENOMEM;
+ }
+
+ mutex_lock(&dsp->mutex);
+
+ if (dsp->fw_use_dma) {
+
+ ret = sst_dsp_dma_get_channel(dsp, 0);
+ if (ret < 0)
+ goto err;
+
+ ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer,
+ dsp->addr.lpe_base + runtime->persistent_offset,
+ module->persistent_size);
+ sst_dsp_dma_put_channel(dsp);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: context copy failed\n");
+ goto err;
+ }
+ } else
+ sst_memcpy32(context->buffer, dsp->addr.lpe +
+ runtime->persistent_offset,
+ module->persistent_size);
+
+ err:
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_save);
+
+ int sst_module_runtime_restore(struct sst_module_runtime *runtime,
+ struct sst_module_runtime_context *context)
+ {
+ struct sst_dsp *dsp = runtime->dsp;
+ struct sst_module *module = runtime->module;
+ int ret = 0;
+
+ dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n",
+ runtime->id, runtime->persistent_offset,
+ module->persistent_size);
+
+ mutex_lock(&dsp->mutex);
+
+ if (!context->buffer) {
+ dev_info(dsp->dev, "no context buffer need to restore!\n");
+ goto err;
+ }
+
+ if (dsp->fw_use_dma) {
+
+ ret = sst_dsp_dma_get_channel(dsp, 0);
+ if (ret < 0)
+ goto err;
+
+ ret = sst_dsp_dma_copyto(dsp,
+ dsp->addr.lpe_base + runtime->persistent_offset,
+ context->dma_buffer, module->persistent_size);
+ sst_dsp_dma_put_channel(dsp);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: module copy failed\n");
+ goto err;
+ }
+ } else
+ sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset,
+ context->buffer, module->persistent_size);
+
+ dma_free_coherent(dsp->dma_dev, module->persistent_size,
+ context->buffer, context->dma_buffer);
+ context->buffer = NULL;
+
+ err:
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_restore);
+
+ /* register a DSP memory block for use with FW based modules */
+ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset,
+ u32 size, enum sst_mem_type type, struct sst_block_ops *ops, u32 index,
+ void *private)
+ {
+ struct sst_mem_block *block;
+
+ block = kzalloc(sizeof(*block), GFP_KERNEL);
+ if (block == NULL)
+ return NULL;
+
+ block->offset = offset;
+ block->size = size;
+ block->index = index;
+ block->type = type;
+ block->dsp = dsp;
+ block->private = private;
+ block->ops = ops;
+
+ mutex_lock(&dsp->mutex);
+ list_add(&block->list, &dsp->free_block_list);
+ mutex_unlock(&dsp->mutex);
+
+ return block;
+ }
+ EXPORT_SYMBOL_GPL(sst_mem_block_register);
+
+ /* unregister all DSP memory blocks */
+ void sst_mem_block_unregister_all(struct sst_dsp *dsp)
+ {
+ struct sst_mem_block *block, *tmp;
+
+ mutex_lock(&dsp->mutex);
+
+ /* unregister used blocks */
+ list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) {
+ list_del(&block->list);
+ kfree(block);
+ }
+
+ /* unregister free blocks */
+ list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) {
+ list_del(&block->list);
+ kfree(block);
+ }
+
+ mutex_unlock(&dsp->mutex);
+ }
+ EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all);
+
+ /* allocate scratch buffer blocks */
+ int sst_block_alloc_scratch(struct sst_dsp *dsp)
+ {
+ struct sst_module *module;
+ struct sst_block_allocator ba;
+ int ret;
+
+ mutex_lock(&dsp->mutex);
+
+ /* calculate required scratch size */
+ dsp->scratch_size = 0;
+ list_for_each_entry(module, &dsp->module_list, list) {
+ dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n",
+ module->id, module->scratch_size);
+ if (dsp->scratch_size < module->scratch_size)
+ dsp->scratch_size = module->scratch_size;
+ }
+
+ dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n",
+ dsp->scratch_size);
+
+ if (dsp->scratch_size == 0) {
+ dev_info(dsp->dev, "no modules need scratch buffer\n");
+ mutex_unlock(&dsp->mutex);
+ return 0;
+ }
+
+ /* allocate blocks for module scratch buffers */
+ dev_dbg(dsp->dev, "allocating scratch blocks\n");
+
+ ba.size = dsp->scratch_size;
+ ba.type = SST_MEM_DRAM;
+
+ /* do we need to allocate at fixed offset */
+ if (dsp->scratch_offset != 0) {
+
+ dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n",
+ ba.size, ba.type, ba.offset);
+
+ ba.offset = dsp->scratch_offset;
+
+ /* alloc blocks that includes this section */
+ ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list);
+
+ } else {
+ dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n",
+ ba.size, ba.type);
+
+ ba.offset = 0;
+ ret = block_alloc(dsp, &ba, &dsp->scratch_block_list);
+ }
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: can't alloc scratch blocks\n");
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+
+ ret = block_list_prepare(dsp, &dsp->scratch_block_list);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: scratch block prepare failed\n");
+ mutex_unlock(&dsp->mutex);
+ return ret;
+ }
+
+ /* assign the same offset of scratch to each module */
+ dsp->scratch_offset = ba.offset;
+ mutex_unlock(&dsp->mutex);
+ return dsp->scratch_size;
+ }
+ EXPORT_SYMBOL_GPL(sst_block_alloc_scratch);
+
+ /* free all scratch blocks */
+ void sst_block_free_scratch(struct sst_dsp *dsp)
+ {
+ mutex_lock(&dsp->mutex);
+ block_list_remove(dsp, &dsp->scratch_block_list);
+ mutex_unlock(&dsp->mutex);
+ }
+ EXPORT_SYMBOL_GPL(sst_block_free_scratch);
+
+ /* get a module from it's unique ID */
+ struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id)
+ {
+ struct sst_module *module;
+
+ mutex_lock(&dsp->mutex);
+
+ list_for_each_entry(module, &dsp->module_list, list) {
+ if (module->id == id) {
+ mutex_unlock(&dsp->mutex);
+ return module;
+ }
+ }
+
+ mutex_unlock(&dsp->mutex);
+ return NULL;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_get_from_id);
+
+ struct sst_module_runtime *sst_module_runtime_get_from_id(
+ struct sst_module *module, u32 id)
+ {
+ struct sst_module_runtime *runtime;
+ struct sst_dsp *dsp = module->dsp;
+
+ mutex_lock(&dsp->mutex);
+
+ list_for_each_entry(runtime, &module->runtime_list, list) {
+ if (runtime->id == id) {
+ mutex_unlock(&dsp->mutex);
+ return runtime;
+ }
+ }
+
+ mutex_unlock(&dsp->mutex);
+ return NULL;
+ }
+ EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id);
+
+ /* returns block address in DSP address space */
+ u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset,
+ enum sst_mem_type type)
+ {
+ switch (type) {
+ case SST_MEM_IRAM:
+ return offset - dsp->addr.iram_offset +
+ dsp->addr.dsp_iram_offset;
+ case SST_MEM_DRAM:
+ return offset - dsp->addr.dram_offset +
+ dsp->addr.dsp_dram_offset;
+ default:
+ return 0;
+ }
+ }
+ EXPORT_SYMBOL_GPL(sst_dsp_get_offset);