summaryrefslogtreecommitdiffstats
path: root/sound/soc/intel/catpt
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sound/soc/intel/catpt/Makefile6
-rw-r--r--sound/soc/intel/catpt/core.h188
-rw-r--r--sound/soc/intel/catpt/device.c355
-rw-r--r--sound/soc/intel/catpt/dsp.c591
-rw-r--r--sound/soc/intel/catpt/ipc.c298
-rw-r--r--sound/soc/intel/catpt/loader.c671
-rw-r--r--sound/soc/intel/catpt/messages.c313
-rw-r--r--sound/soc/intel/catpt/messages.h401
-rw-r--r--sound/soc/intel/catpt/pcm.c1184
-rw-r--r--sound/soc/intel/catpt/registers.h178
-rw-r--r--sound/soc/intel/catpt/sysfs.c55
-rw-r--r--sound/soc/intel/catpt/trace.h83
12 files changed, 4323 insertions, 0 deletions
diff --git a/sound/soc/intel/catpt/Makefile b/sound/soc/intel/catpt/Makefile
new file mode 100644
index 000000000..c393a4579
--- /dev/null
+++ b/sound/soc/intel/catpt/Makefile
@@ -0,0 +1,6 @@
+snd-soc-catpt-objs := device.o dsp.o loader.o ipc.o messages.o pcm.o sysfs.o
+
+# tell define_trace.h where to find the trace header
+CFLAGS_device.o := -I$(src)
+
+obj-$(CONFIG_SND_SOC_INTEL_CATPT) += snd-soc-catpt.o
diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h
new file mode 100644
index 000000000..88dc3fb63
--- /dev/null
+++ b/sound/soc/intel/catpt/core.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SND_SOC_INTEL_CATPT_CORE_H
+#define __SND_SOC_INTEL_CATPT_CORE_H
+
+#include <linux/dma/dw.h>
+#include <linux/irqreturn.h>
+#include "messages.h"
+#include "registers.h"
+
+struct catpt_dev;
+
+extern const struct attribute_group *catpt_attr_groups[];
+
+void catpt_sram_init(struct resource *sram, u32 start, u32 size);
+void catpt_sram_free(struct resource *sram);
+struct resource *
+catpt_request_region(struct resource *root, resource_size_t size);
+
+static inline bool catpt_resource_overlapping(struct resource *r1,
+ struct resource *r2,
+ struct resource *ret)
+{
+ if (!resource_overlaps(r1, r2))
+ return false;
+ ret->start = max(r1->start, r2->start);
+ ret->end = min(r1->end, r2->end);
+ return true;
+}
+
+struct catpt_ipc_msg {
+ union {
+ u32 header;
+ union catpt_global_msg rsp;
+ };
+ void *data;
+ size_t size;
+};
+
+struct catpt_ipc {
+ struct device *dev;
+
+ struct catpt_ipc_msg rx;
+ struct catpt_fw_ready config;
+ u32 default_timeout;
+ bool ready;
+
+ spinlock_t lock;
+ struct mutex mutex;
+ struct completion done_completion;
+ struct completion busy_completion;
+};
+
+void catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev);
+
+struct catpt_module_type {
+ bool loaded;
+ u32 entry_point;
+ u32 persistent_size;
+ u32 scratch_size;
+ /* DRAM, initial module state */
+ u32 state_offset;
+ u32 state_size;
+
+ struct list_head node;
+};
+
+struct catpt_spec {
+ struct snd_soc_acpi_mach *machines;
+ u8 core_id;
+ u32 host_dram_offset;
+ u32 host_iram_offset;
+ u32 host_shim_offset;
+ u32 host_dma_offset[CATPT_DMA_COUNT];
+ u32 host_ssp_offset[CATPT_SSP_COUNT];
+ u32 dram_mask;
+ u32 iram_mask;
+ void (*pll_shutdown)(struct catpt_dev *cdev, bool enable);
+ int (*power_up)(struct catpt_dev *cdev);
+ int (*power_down)(struct catpt_dev *cdev);
+};
+
+struct catpt_dev {
+ struct device *dev;
+ struct dw_dma_chip *dmac;
+ struct catpt_ipc ipc;
+
+ void __iomem *pci_ba;
+ void __iomem *lpe_ba;
+ u32 lpe_base;
+ int irq;
+
+ const struct catpt_spec *spec;
+ struct completion fw_ready;
+
+ struct resource dram;
+ struct resource iram;
+ struct resource *scratch;
+
+ struct catpt_mixer_stream_info mixer;
+ struct catpt_module_type modules[CATPT_MODULE_COUNT];
+ struct catpt_ssp_device_format devfmt[CATPT_SSP_COUNT];
+ struct list_head stream_list;
+ spinlock_t list_lock;
+ struct mutex clk_mutex;
+
+ struct catpt_dx_context dx_ctx;
+ void *dxbuf_vaddr;
+ dma_addr_t dxbuf_paddr;
+};
+
+int catpt_dmac_probe(struct catpt_dev *cdev);
+void catpt_dmac_remove(struct catpt_dev *cdev);
+struct dma_chan *catpt_dma_request_config_chan(struct catpt_dev *cdev);
+int catpt_dma_memcpy_todsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size);
+int catpt_dma_memcpy_fromdsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size);
+
+void lpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable);
+void wpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable);
+int lpt_dsp_power_up(struct catpt_dev *cdev);
+int lpt_dsp_power_down(struct catpt_dev *cdev);
+int wpt_dsp_power_up(struct catpt_dev *cdev);
+int wpt_dsp_power_down(struct catpt_dev *cdev);
+int catpt_dsp_stall(struct catpt_dev *cdev, bool stall);
+void catpt_dsp_update_srampge(struct catpt_dev *cdev, struct resource *sram,
+ unsigned long mask);
+int catpt_dsp_update_lpclock(struct catpt_dev *cdev);
+irqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id);
+irqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id);
+
+/*
+ * IPC handlers may return positive values which denote successful
+ * HOST <-> DSP communication yet failure to process specific request.
+ * Use below macro to convert returned non-zero values appropriately
+ */
+#define CATPT_IPC_ERROR(err) (((err) < 0) ? (err) : -EREMOTEIO)
+
+int catpt_dsp_send_msg_timeout(struct catpt_dev *cdev,
+ struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply, int timeout);
+int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply);
+
+int catpt_first_boot_firmware(struct catpt_dev *cdev);
+int catpt_boot_firmware(struct catpt_dev *cdev, bool restore);
+int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan);
+int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan);
+int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan);
+int catpt_coredump(struct catpt_dev *cdev);
+
+#include <sound/memalloc.h>
+#include <uapi/sound/asound.h>
+
+struct snd_pcm_substream;
+struct catpt_stream_template;
+
+struct catpt_stream_runtime {
+ struct snd_pcm_substream *substream;
+
+ struct catpt_stream_template *template;
+ struct catpt_stream_info info;
+ struct resource *persistent;
+ struct snd_dma_buffer pgtbl;
+
+ bool allocated;
+ bool prepared;
+
+ struct list_head node;
+};
+
+int catpt_register_plat_component(struct catpt_dev *cdev);
+void catpt_stream_update_position(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream,
+ struct catpt_notify_position *pos);
+struct catpt_stream_runtime *
+catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_arm_stream_templates(struct catpt_dev *cdev);
+
+#endif
diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c
new file mode 100644
index 000000000..a70179959
--- /dev/null
+++ b/sound/soc/intel/catpt/device.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+// Special thanks to:
+// Marcin Barlik <marcin.barlik@intel.com>
+// Piotr Papierkowski <piotr.papierkowski@intel.com>
+//
+// for sharing LPT-LP and WTP-LP AudioDSP architecture expertise and
+// helping backtrack its historical background
+//
+
+#include <linux/acpi.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include "core.h"
+#include "registers.h"
+
+#define CREATE_TRACE_POINTS
+#include "trace.h"
+
+static int __maybe_unused catpt_suspend(struct device *dev)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+ struct dma_chan *chan;
+ int ret;
+
+ chan = catpt_dma_request_config_chan(cdev);
+ if (IS_ERR(chan))
+ return PTR_ERR(chan);
+
+ memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx));
+ ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx);
+ if (ret) {
+ ret = CATPT_IPC_ERROR(ret);
+ goto release_dma_chan;
+ }
+
+ ret = catpt_dsp_stall(cdev, true);
+ if (ret)
+ goto release_dma_chan;
+
+ ret = catpt_store_memdumps(cdev, chan);
+ if (ret) {
+ dev_err(cdev->dev, "store memdumps failed: %d\n", ret);
+ goto release_dma_chan;
+ }
+
+ ret = catpt_store_module_states(cdev, chan);
+ if (ret) {
+ dev_err(cdev->dev, "store module states failed: %d\n", ret);
+ goto release_dma_chan;
+ }
+
+ ret = catpt_store_streams_context(cdev, chan);
+ if (ret)
+ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret);
+
+release_dma_chan:
+ dma_release_channel(chan);
+ if (ret)
+ return ret;
+ return cdev->spec->power_down(cdev);
+}
+
+static int __maybe_unused catpt_resume(struct device *dev)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+ int ret, i;
+
+ ret = cdev->spec->power_up(cdev);
+ if (ret)
+ return ret;
+
+ if (!try_module_get(dev->driver->owner)) {
+ dev_info(dev, "module unloading, skipping fw boot\n");
+ return 0;
+ }
+ module_put(dev->driver->owner);
+
+ ret = catpt_boot_firmware(cdev, true);
+ if (ret) {
+ dev_err(cdev->dev, "boot firmware failed: %d\n", ret);
+ return ret;
+ }
+
+ /* reconfigure SSP devices after Dx transition */
+ for (i = 0; i < CATPT_SSP_COUNT; i++) {
+ if (cdev->devfmt[i].iface == UINT_MAX)
+ continue;
+
+ ret = catpt_ipc_set_device_format(cdev, &cdev->devfmt[i]);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused catpt_runtime_suspend(struct device *dev)
+{
+ if (!try_module_get(dev->driver->owner)) {
+ dev_info(dev, "module unloading, skipping suspend\n");
+ return 0;
+ }
+ module_put(dev->driver->owner);
+
+ return catpt_suspend(dev);
+}
+
+static int __maybe_unused catpt_runtime_resume(struct device *dev)
+{
+ return catpt_resume(dev);
+}
+
+static const struct dev_pm_ops catpt_dev_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(catpt_suspend, catpt_resume)
+ SET_RUNTIME_PM_OPS(catpt_runtime_suspend, catpt_runtime_resume, NULL)
+};
+
+/* machine board owned by CATPT is removed with this hook */
+static void board_pdev_unregister(void *data)
+{
+ platform_device_unregister(data);
+}
+
+static int catpt_register_board(struct catpt_dev *cdev)
+{
+ const struct catpt_spec *spec = cdev->spec;
+ struct snd_soc_acpi_mach *mach;
+ struct platform_device *board;
+
+ mach = snd_soc_acpi_find_machine(spec->machines);
+ if (!mach) {
+ dev_info(cdev->dev, "no machines present\n");
+ return 0;
+ }
+
+ mach->mach_params.platform = "catpt-platform";
+ board = platform_device_register_data(NULL, mach->drv_name,
+ PLATFORM_DEVID_NONE,
+ (const void *)mach, sizeof(*mach));
+ if (IS_ERR(board)) {
+ dev_err(cdev->dev, "board register failed\n");
+ return PTR_ERR(board);
+ }
+
+ return devm_add_action_or_reset(cdev->dev, board_pdev_unregister,
+ board);
+}
+
+static int catpt_probe_components(struct catpt_dev *cdev)
+{
+ int ret;
+
+ ret = cdev->spec->power_up(cdev);
+ if (ret)
+ return ret;
+
+ ret = catpt_dmac_probe(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "DMAC probe failed: %d\n", ret);
+ goto err_dmac_probe;
+ }
+
+ ret = catpt_first_boot_firmware(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "first fw boot failed: %d\n", ret);
+ goto err_boot_fw;
+ }
+
+ ret = catpt_register_plat_component(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "register plat comp failed: %d\n", ret);
+ goto err_boot_fw;
+ }
+
+ ret = catpt_register_board(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "register board failed: %d\n", ret);
+ goto err_reg_board;
+ }
+
+ /* reflect actual ADSP state in pm_runtime */
+ pm_runtime_set_active(cdev->dev);
+
+ pm_runtime_set_autosuspend_delay(cdev->dev, 2000);
+ pm_runtime_use_autosuspend(cdev->dev);
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_enable(cdev->dev);
+ return 0;
+
+err_reg_board:
+ snd_soc_unregister_component(cdev->dev);
+err_boot_fw:
+ catpt_dmac_remove(cdev);
+err_dmac_probe:
+ cdev->spec->power_down(cdev);
+
+ return ret;
+}
+
+static void catpt_dev_init(struct catpt_dev *cdev, struct device *dev,
+ const struct catpt_spec *spec)
+{
+ cdev->dev = dev;
+ cdev->spec = spec;
+ init_completion(&cdev->fw_ready);
+ INIT_LIST_HEAD(&cdev->stream_list);
+ spin_lock_init(&cdev->list_lock);
+ mutex_init(&cdev->clk_mutex);
+
+ /*
+ * Mark both device formats as uninitialized. Once corresponding
+ * cpu_dai's pcm is created, proper values are assigned.
+ */
+ cdev->devfmt[CATPT_SSP_IFACE_0].iface = UINT_MAX;
+ cdev->devfmt[CATPT_SSP_IFACE_1].iface = UINT_MAX;
+
+ catpt_ipc_init(&cdev->ipc, dev);
+
+ catpt_sram_init(&cdev->dram, spec->host_dram_offset,
+ catpt_dram_size(cdev));
+ catpt_sram_init(&cdev->iram, spec->host_iram_offset,
+ catpt_iram_size(cdev));
+}
+
+static int catpt_acpi_probe(struct platform_device *pdev)
+{
+ const struct catpt_spec *spec;
+ struct catpt_dev *cdev;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret;
+
+ spec = device_get_match_data(dev);
+ if (!spec)
+ return -ENODEV;
+
+ cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return -ENOMEM;
+
+ catpt_dev_init(cdev, dev, spec);
+
+ /* map DSP bar address */
+ cdev->lpe_ba = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(cdev->lpe_ba))
+ return PTR_ERR(cdev->lpe_ba);
+ cdev->lpe_base = res->start;
+
+ /* map PCI bar address */
+ cdev->pci_ba = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(cdev->pci_ba))
+ return PTR_ERR(cdev->pci_ba);
+
+ /* alloc buffer for storing DRAM context during dx transitions */
+ cdev->dxbuf_vaddr = dmam_alloc_coherent(dev, catpt_dram_size(cdev),
+ &cdev->dxbuf_paddr, GFP_KERNEL);
+ if (!cdev->dxbuf_vaddr)
+ return -ENOMEM;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+ cdev->irq = ret;
+
+ platform_set_drvdata(pdev, cdev);
+
+ ret = devm_request_threaded_irq(dev, cdev->irq, catpt_dsp_irq_handler,
+ catpt_dsp_irq_thread,
+ IRQF_SHARED, "AudioDSP", cdev);
+ if (ret)
+ return ret;
+
+ return catpt_probe_components(cdev);
+}
+
+static int catpt_acpi_remove(struct platform_device *pdev)
+{
+ struct catpt_dev *cdev = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(cdev->dev);
+
+ snd_soc_unregister_component(cdev->dev);
+ catpt_dmac_remove(cdev);
+ cdev->spec->power_down(cdev);
+
+ catpt_sram_free(&cdev->iram);
+ catpt_sram_free(&cdev->dram);
+
+ return 0;
+}
+
+static struct catpt_spec lpt_desc = {
+ .machines = snd_soc_acpi_intel_haswell_machines,
+ .core_id = 0x01,
+ .host_dram_offset = 0x000000,
+ .host_iram_offset = 0x080000,
+ .host_shim_offset = 0x0E7000,
+ .host_dma_offset = { 0x0F0000, 0x0F8000 },
+ .host_ssp_offset = { 0x0E8000, 0x0E9000 },
+ .dram_mask = LPT_VDRTCTL0_DSRAMPGE_MASK,
+ .iram_mask = LPT_VDRTCTL0_ISRAMPGE_MASK,
+ .pll_shutdown = lpt_dsp_pll_shutdown,
+ .power_up = lpt_dsp_power_up,
+ .power_down = lpt_dsp_power_down,
+};
+
+static struct catpt_spec wpt_desc = {
+ .machines = snd_soc_acpi_intel_broadwell_machines,
+ .core_id = 0x02,
+ .host_dram_offset = 0x000000,
+ .host_iram_offset = 0x0A0000,
+ .host_shim_offset = 0x0FB000,
+ .host_dma_offset = { 0x0FE000, 0x0FF000 },
+ .host_ssp_offset = { 0x0FC000, 0x0FD000 },
+ .dram_mask = WPT_VDRTCTL0_DSRAMPGE_MASK,
+ .iram_mask = WPT_VDRTCTL0_ISRAMPGE_MASK,
+ .pll_shutdown = wpt_dsp_pll_shutdown,
+ .power_up = wpt_dsp_power_up,
+ .power_down = wpt_dsp_power_down,
+};
+
+static const struct acpi_device_id catpt_ids[] = {
+ { "INT33C8", (unsigned long)&lpt_desc },
+ { "INT3438", (unsigned long)&wpt_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, catpt_ids);
+
+static struct platform_driver catpt_acpi_driver = {
+ .probe = catpt_acpi_probe,
+ .remove = catpt_acpi_remove,
+ .driver = {
+ .name = "intel_catpt",
+ .acpi_match_table = catpt_ids,
+ .pm = &catpt_dev_pm,
+ .dev_groups = catpt_attr_groups,
+ },
+};
+module_platform_driver(catpt_acpi_driver);
+
+MODULE_AUTHOR("Cezary Rojewski <cezary.rojewski@intel.com>");
+MODULE_DESCRIPTION("Intel LPT/WPT AudioDSP driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/intel/catpt/dsp.c b/sound/soc/intel/catpt/dsp.c
new file mode 100644
index 000000000..38a92bbc1
--- /dev/null
+++ b/sound/soc/intel/catpt/dsp.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/devcoredump.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/pxa2xx_ssp.h>
+#include "core.h"
+#include "messages.h"
+#include "registers.h"
+
+static bool catpt_dma_filter(struct dma_chan *chan, void *param)
+{
+ return param == chan->device->dev;
+}
+
+/*
+ * Either engine 0 or 1 can be used for image loading.
+ * Align with Windows driver equivalent and stick to engine 1.
+ */
+#define CATPT_DMA_DEVID 1
+#define CATPT_DMA_DSP_ADDR_MASK GENMASK(31, 20)
+
+struct dma_chan *catpt_dma_request_config_chan(struct catpt_dev *cdev)
+{
+ struct dma_slave_config config;
+ struct dma_chan *chan;
+ dma_cap_mask_t mask;
+ int ret;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ chan = dma_request_channel(mask, catpt_dma_filter, cdev->dev);
+ if (!chan) {
+ dev_err(cdev->dev, "request channel failed\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ memset(&config, 0, sizeof(config));
+ config.direction = DMA_MEM_TO_DEV;
+ config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.src_maxburst = 16;
+ config.dst_maxburst = 16;
+
+ ret = dmaengine_slave_config(chan, &config);
+ if (ret) {
+ dev_err(cdev->dev, "slave config failed: %d\n", ret);
+ dma_release_channel(chan);
+ return ERR_PTR(ret);
+ }
+
+ return chan;
+}
+
+static int catpt_dma_memcpy(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size)
+{
+ struct dma_async_tx_descriptor *desc;
+ enum dma_status status;
+ int ret;
+
+ desc = dmaengine_prep_dma_memcpy(chan, dst_addr, src_addr, size,
+ DMA_CTRL_ACK);
+ if (!desc) {
+ dev_err(cdev->dev, "prep dma memcpy failed\n");
+ return -EIO;
+ }
+
+ /* enable demand mode for dma channel */
+ catpt_updatel_shim(cdev, HMDC,
+ CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id),
+ CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id));
+
+ ret = dma_submit_error(dmaengine_submit(desc));
+ if (ret) {
+ dev_err(cdev->dev, "submit tx failed: %d\n", ret);
+ goto clear_hdda;
+ }
+
+ status = dma_wait_for_async_tx(desc);
+ ret = (status == DMA_COMPLETE) ? 0 : -EPROTO;
+
+clear_hdda:
+ /* regardless of status, disable access to HOST memory in demand mode */
+ catpt_updatel_shim(cdev, HMDC,
+ CATPT_HMDC_HDDA(CATPT_DMA_DEVID, chan->chan_id), 0);
+
+ return ret;
+}
+
+int catpt_dma_memcpy_todsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size)
+{
+ return catpt_dma_memcpy(cdev, chan, dst_addr | CATPT_DMA_DSP_ADDR_MASK,
+ src_addr, size);
+}
+
+int catpt_dma_memcpy_fromdsp(struct catpt_dev *cdev, struct dma_chan *chan,
+ dma_addr_t dst_addr, dma_addr_t src_addr,
+ size_t size)
+{
+ return catpt_dma_memcpy(cdev, chan, dst_addr,
+ src_addr | CATPT_DMA_DSP_ADDR_MASK, size);
+}
+
+int catpt_dmac_probe(struct catpt_dev *cdev)
+{
+ struct dw_dma_chip *dmac;
+ int ret;
+
+ dmac = devm_kzalloc(cdev->dev, sizeof(*dmac), GFP_KERNEL);
+ if (!dmac)
+ return -ENOMEM;
+
+ dmac->regs = cdev->lpe_ba + cdev->spec->host_dma_offset[CATPT_DMA_DEVID];
+ dmac->dev = cdev->dev;
+ dmac->irq = cdev->irq;
+
+ ret = dma_coerce_mask_and_coherent(cdev->dev, DMA_BIT_MASK(31));
+ if (ret)
+ return ret;
+ /*
+ * Caller is responsible for putting device in D0 to allow
+ * for I/O and memory access before probing DW.
+ */
+ ret = dw_dma_probe(dmac);
+ if (ret)
+ return ret;
+
+ cdev->dmac = dmac;
+ return 0;
+}
+
+void catpt_dmac_remove(struct catpt_dev *cdev)
+{
+ /*
+ * As do_dma_remove() juggles with pm_runtime_get_xxx() and
+ * pm_runtime_put_xxx() while both ADSP and DW 'devices' are part of
+ * the same module, caller makes sure pm_runtime_disable() is invoked
+ * before removing DW to prevent postmortem resume and suspend.
+ */
+ dw_dma_remove(cdev->dmac);
+}
+
+static void catpt_dsp_set_srampge(struct catpt_dev *cdev, struct resource *sram,
+ unsigned long mask, unsigned long new)
+{
+ unsigned long old;
+ u32 off = sram->start;
+ u32 b = __ffs(mask);
+
+ old = catpt_readl_pci(cdev, VDRTCTL0) & mask;
+ dev_dbg(cdev->dev, "SRAMPGE [0x%08lx] 0x%08lx -> 0x%08lx",
+ mask, old, new);
+
+ if (old == new)
+ return;
+
+ catpt_updatel_pci(cdev, VDRTCTL0, mask, new);
+ /* wait for SRAM power gating to propagate */
+ udelay(60);
+
+ /*
+ * Dummy read as the very first access after block enable
+ * to prevent byte loss in future operations.
+ */
+ for_each_clear_bit_from(b, &new, fls_long(mask)) {
+ u8 buf[4];
+
+ /* newly enabled: new bit=0 while old bit=1 */
+ if (test_bit(b, &old)) {
+ dev_dbg(cdev->dev, "sanitize block %ld: off 0x%08x\n",
+ b - __ffs(mask), off);
+ memcpy_fromio(buf, cdev->lpe_ba + off, sizeof(buf));
+ }
+ off += CATPT_MEMBLOCK_SIZE;
+ }
+}
+
+void catpt_dsp_update_srampge(struct catpt_dev *cdev, struct resource *sram,
+ unsigned long mask)
+{
+ struct resource *res;
+ unsigned long new = 0;
+
+ /* flag all busy blocks */
+ for (res = sram->child; res; res = res->sibling) {
+ u32 h, l;
+
+ h = (res->end - sram->start) / CATPT_MEMBLOCK_SIZE;
+ l = (res->start - sram->start) / CATPT_MEMBLOCK_SIZE;
+ new |= GENMASK(h, l);
+ }
+
+ /* offset value given mask's start and invert it as ON=b0 */
+ new = ~(new << __ffs(mask)) & mask;
+
+ /* disable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0);
+
+ catpt_dsp_set_srampge(cdev, sram, mask, new);
+
+ /* enable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE,
+ CATPT_VDRTCTL2_DCLCGE);
+}
+
+int catpt_dsp_stall(struct catpt_dev *cdev, bool stall)
+{
+ u32 reg, val;
+
+ val = stall ? CATPT_CS_STALL : 0;
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_STALL, val);
+
+ return catpt_readl_poll_shim(cdev, CS1,
+ reg, (reg & CATPT_CS_STALL) == val,
+ 500, 10000);
+}
+
+static int catpt_dsp_reset(struct catpt_dev *cdev, bool reset)
+{
+ u32 reg, val;
+
+ val = reset ? CATPT_CS_RST : 0;
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_RST, val);
+
+ return catpt_readl_poll_shim(cdev, CS1,
+ reg, (reg & CATPT_CS_RST) == val,
+ 500, 10000);
+}
+
+void lpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable)
+{
+ u32 val;
+
+ val = enable ? LPT_VDRTCTL0_APLLSE : 0;
+ catpt_updatel_pci(cdev, VDRTCTL0, LPT_VDRTCTL0_APLLSE, val);
+}
+
+void wpt_dsp_pll_shutdown(struct catpt_dev *cdev, bool enable)
+{
+ u32 val;
+
+ val = enable ? WPT_VDRTCTL2_APLLSE : 0;
+ catpt_updatel_pci(cdev, VDRTCTL2, WPT_VDRTCTL2_APLLSE, val);
+}
+
+static int catpt_dsp_select_lpclock(struct catpt_dev *cdev, bool lp, bool waiti)
+{
+ u32 mask, reg, val;
+ int ret;
+
+ mutex_lock(&cdev->clk_mutex);
+
+ val = lp ? CATPT_CS_LPCS : 0;
+ reg = catpt_readl_shim(cdev, CS1) & CATPT_CS_LPCS;
+ dev_dbg(cdev->dev, "LPCS [0x%08lx] 0x%08x -> 0x%08x",
+ CATPT_CS_LPCS, reg, val);
+
+ if (reg == val) {
+ mutex_unlock(&cdev->clk_mutex);
+ return 0;
+ }
+
+ if (waiti) {
+ /* wait for DSP to signal WAIT state */
+ ret = catpt_readl_poll_shim(cdev, ISD,
+ reg, (reg & CATPT_ISD_DCPWM),
+ 500, 10000);
+ if (ret) {
+ dev_warn(cdev->dev, "await WAITI timeout\n");
+ /* no signal - only high clock selection allowed */
+ if (lp) {
+ mutex_unlock(&cdev->clk_mutex);
+ return 0;
+ }
+ }
+ }
+
+ ret = catpt_readl_poll_shim(cdev, CLKCTL,
+ reg, !(reg & CATPT_CLKCTL_CFCIP),
+ 500, 10000);
+ if (ret)
+ dev_warn(cdev->dev, "clock change still in progress\n");
+
+ /* default to DSP core & audio fabric high clock */
+ val |= CATPT_CS_DCS_HIGH;
+ mask = CATPT_CS_LPCS | CATPT_CS_DCS;
+ catpt_updatel_shim(cdev, CS1, mask, val);
+
+ ret = catpt_readl_poll_shim(cdev, CLKCTL,
+ reg, !(reg & CATPT_CLKCTL_CFCIP),
+ 500, 10000);
+ if (ret)
+ dev_warn(cdev->dev, "clock change still in progress\n");
+
+ /* update PLL accordingly */
+ cdev->spec->pll_shutdown(cdev, lp);
+
+ mutex_unlock(&cdev->clk_mutex);
+ return 0;
+}
+
+int catpt_dsp_update_lpclock(struct catpt_dev *cdev)
+{
+ struct catpt_stream_runtime *stream;
+
+ list_for_each_entry(stream, &cdev->stream_list, node)
+ if (stream->prepared)
+ return catpt_dsp_select_lpclock(cdev, false, true);
+
+ return catpt_dsp_select_lpclock(cdev, true, true);
+}
+
+/* bring registers to their defaults as HW won't reset itself */
+static void catpt_dsp_set_regs_defaults(struct catpt_dev *cdev)
+{
+ int i;
+
+ catpt_writel_shim(cdev, CS1, CATPT_CS_DEFAULT);
+ catpt_writel_shim(cdev, ISC, CATPT_ISC_DEFAULT);
+ catpt_writel_shim(cdev, ISD, CATPT_ISD_DEFAULT);
+ catpt_writel_shim(cdev, IMC, CATPT_IMC_DEFAULT);
+ catpt_writel_shim(cdev, IMD, CATPT_IMD_DEFAULT);
+ catpt_writel_shim(cdev, IPCC, CATPT_IPCC_DEFAULT);
+ catpt_writel_shim(cdev, IPCD, CATPT_IPCD_DEFAULT);
+ catpt_writel_shim(cdev, CLKCTL, CATPT_CLKCTL_DEFAULT);
+ catpt_writel_shim(cdev, CS2, CATPT_CS2_DEFAULT);
+ catpt_writel_shim(cdev, LTRC, CATPT_LTRC_DEFAULT);
+ catpt_writel_shim(cdev, HMDC, CATPT_HMDC_DEFAULT);
+
+ for (i = 0; i < CATPT_SSP_COUNT; i++) {
+ catpt_writel_ssp(cdev, i, SSCR0, CATPT_SSC0_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSCR1, CATPT_SSC1_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSSR, CATPT_SSS_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSITR, CATPT_SSIT_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSDR, CATPT_SSD_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSTO, CATPT_SSTO_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSPSP, CATPT_SSPSP_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSTSA, CATPT_SSTSA_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSRSA, CATPT_SSRSA_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSTSS, CATPT_SSTSS_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSCR2, CATPT_SSCR2_DEFAULT);
+ catpt_writel_ssp(cdev, i, SSPSP2, CATPT_SSPSP2_DEFAULT);
+ }
+}
+
+int lpt_dsp_power_down(struct catpt_dev *cdev)
+{
+ catpt_dsp_reset(cdev, true);
+
+ /* set 24Mhz clock for both SSPs */
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ catpt_dsp_select_lpclock(cdev, true, false);
+
+ /* DRAM power gating all */
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask,
+ cdev->spec->dram_mask);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask,
+ cdev->spec->iram_mask);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D3hot);
+ /* give hw time to drop off */
+ udelay(50);
+
+ return 0;
+}
+
+int lpt_dsp_power_up(struct catpt_dev *cdev)
+{
+ /* SRAM power gating none */
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask, 0);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask, 0);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D0);
+ /* give hw time to wake up */
+ udelay(100);
+
+ catpt_dsp_select_lpclock(cdev, false, false);
+ catpt_updatel_shim(cdev, CS1,
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ /* stagger DSP reset after clock selection */
+ udelay(50);
+
+ catpt_dsp_reset(cdev, false);
+ /* generate int deassert msg to fix inversed int logic */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB | CATPT_IMC_IPCCD, 0);
+
+ return 0;
+}
+
+int wpt_dsp_power_down(struct catpt_dev *cdev)
+{
+ u32 mask, val;
+
+ /* disable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0);
+
+ catpt_dsp_reset(cdev, true);
+ /* set 24Mhz clock for both SSPs */
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ catpt_dsp_select_lpclock(cdev, true, false);
+ /* disable MCLK */
+ catpt_updatel_shim(cdev, CLKCTL, CATPT_CLKCTL_SMOS, 0);
+
+ catpt_dsp_set_regs_defaults(cdev);
+
+ /* switch clock gating */
+ mask = CATPT_VDRTCTL2_CGEALL & (~CATPT_VDRTCTL2_DCLCGE);
+ val = mask & (~CATPT_VDRTCTL2_DTCGE);
+ catpt_updatel_pci(cdev, VDRTCTL2, mask, val);
+ /* enable DTCGE separatelly */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DTCGE,
+ CATPT_VDRTCTL2_DTCGE);
+
+ /* SRAM power gating all */
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask,
+ cdev->spec->dram_mask);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask,
+ cdev->spec->iram_mask);
+ mask = WPT_VDRTCTL0_D3SRAMPGD | WPT_VDRTCTL0_D3PGD;
+ catpt_updatel_pci(cdev, VDRTCTL0, mask, WPT_VDRTCTL0_D3PGD);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D3hot);
+ /* give hw time to drop off */
+ udelay(50);
+
+ /* enable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE,
+ CATPT_VDRTCTL2_DCLCGE);
+ udelay(50);
+
+ return 0;
+}
+
+int wpt_dsp_power_up(struct catpt_dev *cdev)
+{
+ u32 mask, val;
+
+ /* disable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE, 0);
+
+ /* switch clock gating */
+ mask = CATPT_VDRTCTL2_CGEALL & (~CATPT_VDRTCTL2_DCLCGE);
+ val = mask & (~CATPT_VDRTCTL2_DTCGE);
+ catpt_updatel_pci(cdev, VDRTCTL2, mask, val);
+
+ catpt_updatel_pci(cdev, PMCS, PCI_PM_CTRL_STATE_MASK, PCI_D0);
+
+ /* SRAM power gating none */
+ mask = WPT_VDRTCTL0_D3SRAMPGD | WPT_VDRTCTL0_D3PGD;
+ catpt_updatel_pci(cdev, VDRTCTL0, mask, mask);
+ catpt_dsp_set_srampge(cdev, &cdev->dram, cdev->spec->dram_mask, 0);
+ catpt_dsp_set_srampge(cdev, &cdev->iram, cdev->spec->iram_mask, 0);
+
+ catpt_dsp_set_regs_defaults(cdev);
+
+ /* restore MCLK */
+ catpt_updatel_shim(cdev, CLKCTL, CATPT_CLKCTL_SMOS, CATPT_CLKCTL_SMOS);
+ catpt_dsp_select_lpclock(cdev, false, false);
+ /* set 24Mhz clock for both SSPs */
+ catpt_updatel_shim(cdev, CS1, CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1),
+ CATPT_CS_SBCS(0) | CATPT_CS_SBCS(1));
+ catpt_dsp_reset(cdev, false);
+
+ /* enable core clock gating */
+ catpt_updatel_pci(cdev, VDRTCTL2, CATPT_VDRTCTL2_DCLCGE,
+ CATPT_VDRTCTL2_DCLCGE);
+
+ /* generate int deassert msg to fix inversed int logic */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB | CATPT_IMC_IPCCD, 0);
+
+ return 0;
+}
+
+#define CATPT_DUMP_MAGIC 0xcd42
+#define CATPT_DUMP_SECTION_ID_FILE 0x00
+#define CATPT_DUMP_SECTION_ID_IRAM 0x01
+#define CATPT_DUMP_SECTION_ID_DRAM 0x02
+#define CATPT_DUMP_SECTION_ID_REGS 0x03
+#define CATPT_DUMP_HASH_SIZE 20
+
+struct catpt_dump_section_hdr {
+ u16 magic;
+ u8 core_id;
+ u8 section_id;
+ u32 size;
+};
+
+int catpt_coredump(struct catpt_dev *cdev)
+{
+ struct catpt_dump_section_hdr *hdr;
+ size_t dump_size, regs_size;
+ u8 *dump, *pos;
+ const char *eof;
+ char *info;
+ int i;
+
+ regs_size = CATPT_SHIM_REGS_SIZE;
+ regs_size += CATPT_DMA_COUNT * CATPT_DMA_REGS_SIZE;
+ regs_size += CATPT_SSP_COUNT * CATPT_SSP_REGS_SIZE;
+ dump_size = resource_size(&cdev->dram);
+ dump_size += resource_size(&cdev->iram);
+ dump_size += regs_size;
+ /* account for header of each section and hash chunk */
+ dump_size += 4 * sizeof(*hdr) + CATPT_DUMP_HASH_SIZE;
+
+ dump = vzalloc(dump_size);
+ if (!dump)
+ return -ENOMEM;
+
+ pos = dump;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_FILE;
+ hdr->size = dump_size - sizeof(*hdr);
+ pos += sizeof(*hdr);
+
+ info = cdev->ipc.config.fw_info;
+ eof = info + FW_INFO_SIZE_MAX;
+ /* navigate to fifth info segment (fw hash) */
+ for (i = 0; i < 4 && info < eof; i++, info++) {
+ /* info segments are separated by space each */
+ info = strnchr(info, eof - info, ' ');
+ if (!info)
+ break;
+ }
+
+ if (i == 4 && info)
+ memcpy(pos, info, min_t(u32, eof - info, CATPT_DUMP_HASH_SIZE));
+ pos += CATPT_DUMP_HASH_SIZE;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_IRAM;
+ hdr->size = resource_size(&cdev->iram);
+ pos += sizeof(*hdr);
+
+ memcpy_fromio(pos, cdev->lpe_ba + cdev->iram.start, hdr->size);
+ pos += hdr->size;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_DRAM;
+ hdr->size = resource_size(&cdev->dram);
+ pos += sizeof(*hdr);
+
+ memcpy_fromio(pos, cdev->lpe_ba + cdev->dram.start, hdr->size);
+ pos += hdr->size;
+
+ hdr = (struct catpt_dump_section_hdr *)pos;
+ hdr->magic = CATPT_DUMP_MAGIC;
+ hdr->core_id = cdev->spec->core_id;
+ hdr->section_id = CATPT_DUMP_SECTION_ID_REGS;
+ hdr->size = regs_size;
+ pos += sizeof(*hdr);
+
+ memcpy_fromio(pos, catpt_shim_addr(cdev), CATPT_SHIM_REGS_SIZE);
+ pos += CATPT_SHIM_REGS_SIZE;
+
+ for (i = 0; i < CATPT_SSP_COUNT; i++) {
+ memcpy_fromio(pos, catpt_ssp_addr(cdev, i),
+ CATPT_SSP_REGS_SIZE);
+ pos += CATPT_SSP_REGS_SIZE;
+ }
+ for (i = 0; i < CATPT_DMA_COUNT; i++) {
+ memcpy_fromio(pos, catpt_dma_addr(cdev, i),
+ CATPT_DMA_REGS_SIZE);
+ pos += CATPT_DMA_REGS_SIZE;
+ }
+
+ dev_coredumpv(cdev->dev, dump, dump_size, GFP_KERNEL);
+
+ return 0;
+}
diff --git a/sound/soc/intel/catpt/ipc.c b/sound/soc/intel/catpt/ipc.c
new file mode 100644
index 000000000..5b718a846
--- /dev/null
+++ b/sound/soc/intel/catpt/ipc.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/irqreturn.h>
+#include "core.h"
+#include "messages.h"
+#include "registers.h"
+#include "trace.h"
+
+#define CATPT_IPC_TIMEOUT_MS 300
+
+void catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev)
+{
+ ipc->dev = dev;
+ ipc->ready = false;
+ ipc->default_timeout = CATPT_IPC_TIMEOUT_MS;
+ init_completion(&ipc->done_completion);
+ init_completion(&ipc->busy_completion);
+ spin_lock_init(&ipc->lock);
+ mutex_init(&ipc->mutex);
+}
+
+static int catpt_ipc_arm(struct catpt_ipc *ipc, struct catpt_fw_ready *config)
+{
+ /*
+ * Both tx and rx are put into and received from outbox. Inbox is
+ * only used for notifications where payload size is known upfront,
+ * thus no separate buffer is allocated for it.
+ */
+ ipc->rx.data = devm_kzalloc(ipc->dev, config->outbox_size, GFP_KERNEL);
+ if (!ipc->rx.data)
+ return -ENOMEM;
+
+ memcpy(&ipc->config, config, sizeof(*config));
+ ipc->ready = true;
+
+ return 0;
+}
+
+static void catpt_ipc_msg_init(struct catpt_ipc *ipc,
+ struct catpt_ipc_msg *reply)
+{
+ lockdep_assert_held(&ipc->lock);
+
+ ipc->rx.header = 0;
+ ipc->rx.size = reply ? reply->size : 0;
+ reinit_completion(&ipc->done_completion);
+ reinit_completion(&ipc->busy_completion);
+}
+
+static void catpt_dsp_send_tx(struct catpt_dev *cdev,
+ const struct catpt_ipc_msg *tx)
+{
+ u32 header = tx->header | CATPT_IPCC_BUSY;
+
+ trace_catpt_ipc_request(header);
+ trace_catpt_ipc_payload(tx->data, tx->size);
+
+ memcpy_toio(catpt_outbox_addr(cdev), tx->data, tx->size);
+ catpt_writel_shim(cdev, IPCC, header);
+}
+
+static int catpt_wait_msg_completion(struct catpt_dev *cdev, int timeout)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+ int ret;
+
+ ret = wait_for_completion_timeout(&ipc->done_completion,
+ msecs_to_jiffies(timeout));
+ if (!ret)
+ return -ETIMEDOUT;
+ if (ipc->rx.rsp.status != CATPT_REPLY_PENDING)
+ return 0;
+
+ /* wait for delayed reply */
+ ret = wait_for_completion_timeout(&ipc->busy_completion,
+ msecs_to_jiffies(timeout));
+ return ret ? 0 : -ETIMEDOUT;
+}
+
+static int catpt_dsp_do_send_msg(struct catpt_dev *cdev,
+ struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply, int timeout)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+ unsigned long flags;
+ int ret;
+
+ if (!ipc->ready)
+ return -EPERM;
+ if (request.size > ipc->config.outbox_size ||
+ (reply && reply->size > ipc->config.outbox_size))
+ return -EINVAL;
+
+ spin_lock_irqsave(&ipc->lock, flags);
+ catpt_ipc_msg_init(ipc, reply);
+ catpt_dsp_send_tx(cdev, &request);
+ spin_unlock_irqrestore(&ipc->lock, flags);
+
+ ret = catpt_wait_msg_completion(cdev, timeout);
+ if (ret) {
+ dev_crit(cdev->dev, "communication severed: %d, rebooting dsp..\n",
+ ret);
+ ipc->ready = false;
+ /* TODO: attempt recovery */
+ return ret;
+ }
+
+ ret = ipc->rx.rsp.status;
+ if (reply) {
+ reply->header = ipc->rx.header;
+
+ if (!ret && reply->data)
+ memcpy(reply->data, ipc->rx.data, reply->size);
+ }
+
+ return ret;
+}
+
+int catpt_dsp_send_msg_timeout(struct catpt_dev *cdev,
+ struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply, int timeout)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+ int ret;
+
+ mutex_lock(&ipc->mutex);
+ ret = catpt_dsp_do_send_msg(cdev, request, reply, timeout);
+ mutex_unlock(&ipc->mutex);
+
+ return ret;
+}
+
+int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request,
+ struct catpt_ipc_msg *reply)
+{
+ return catpt_dsp_send_msg_timeout(cdev, request, reply,
+ cdev->ipc.default_timeout);
+}
+
+static void
+catpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg)
+{
+ struct catpt_stream_runtime *stream;
+ struct catpt_notify_position pos;
+ struct catpt_notify_glitch glitch;
+
+ stream = catpt_stream_find(cdev, msg.stream_hw_id);
+ if (!stream) {
+ dev_warn(cdev->dev, "notify %d for non-existent stream %d\n",
+ msg.notify_reason, msg.stream_hw_id);
+ return;
+ }
+
+ switch (msg.notify_reason) {
+ case CATPT_NOTIFY_POSITION_CHANGED:
+ memcpy_fromio(&pos, catpt_inbox_addr(cdev), sizeof(pos));
+ trace_catpt_ipc_payload((u8 *)&pos, sizeof(pos));
+
+ catpt_stream_update_position(cdev, stream, &pos);
+ break;
+
+ case CATPT_NOTIFY_GLITCH_OCCURRED:
+ memcpy_fromio(&glitch, catpt_inbox_addr(cdev), sizeof(glitch));
+ trace_catpt_ipc_payload((u8 *)&glitch, sizeof(glitch));
+
+ dev_warn(cdev->dev, "glitch %d at pos: 0x%08llx, wp: 0x%08x\n",
+ glitch.type, glitch.presentation_pos,
+ glitch.write_pos);
+ break;
+
+ default:
+ dev_warn(cdev->dev, "unknown notification: %d received\n",
+ msg.notify_reason);
+ break;
+ }
+}
+
+static void catpt_dsp_copy_rx(struct catpt_dev *cdev, u32 header)
+{
+ struct catpt_ipc *ipc = &cdev->ipc;
+
+ ipc->rx.header = header;
+ if (ipc->rx.rsp.status != CATPT_REPLY_SUCCESS)
+ return;
+
+ memcpy_fromio(ipc->rx.data, catpt_outbox_addr(cdev), ipc->rx.size);
+ trace_catpt_ipc_payload(ipc->rx.data, ipc->rx.size);
+}
+
+static void catpt_dsp_process_response(struct catpt_dev *cdev, u32 header)
+{
+ union catpt_notify_msg msg = CATPT_MSG(header);
+ struct catpt_ipc *ipc = &cdev->ipc;
+
+ if (msg.fw_ready) {
+ struct catpt_fw_ready config;
+ /* to fit 32b header original address is shifted right by 3 */
+ u32 off = msg.mailbox_address << 3;
+
+ memcpy_fromio(&config, cdev->lpe_ba + off, sizeof(config));
+ trace_catpt_ipc_payload((u8 *)&config, sizeof(config));
+
+ catpt_ipc_arm(ipc, &config);
+ complete(&cdev->fw_ready);
+ return;
+ }
+
+ switch (msg.global_msg_type) {
+ case CATPT_GLB_REQUEST_CORE_DUMP:
+ dev_err(cdev->dev, "ADSP device coredump received\n");
+ ipc->ready = false;
+ catpt_coredump(cdev);
+ /* TODO: attempt recovery */
+ break;
+
+ case CATPT_GLB_STREAM_MESSAGE:
+ switch (msg.stream_msg_type) {
+ case CATPT_STRM_NOTIFICATION:
+ catpt_dsp_notify_stream(cdev, msg);
+ break;
+ default:
+ catpt_dsp_copy_rx(cdev, header);
+ /* signal completion of delayed reply */
+ complete(&ipc->busy_completion);
+ break;
+ }
+ break;
+
+ default:
+ dev_warn(cdev->dev, "unknown response: %d received\n",
+ msg.global_msg_type);
+ break;
+ }
+}
+
+irqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id)
+{
+ struct catpt_dev *cdev = dev_id;
+ u32 ipcd;
+
+ ipcd = catpt_readl_shim(cdev, IPCD);
+ trace_catpt_ipc_notify(ipcd);
+
+ /* ensure there is delayed reply or notification to process */
+ if (!(ipcd & CATPT_IPCD_BUSY))
+ return IRQ_NONE;
+
+ catpt_dsp_process_response(cdev, ipcd);
+
+ /* tell DSP processing is completed */
+ catpt_updatel_shim(cdev, IPCD, CATPT_IPCD_BUSY | CATPT_IPCD_DONE,
+ CATPT_IPCD_DONE);
+ /* unmask dsp BUSY interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, 0);
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id)
+{
+ struct catpt_dev *cdev = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ u32 isc, ipcc;
+
+ isc = catpt_readl_shim(cdev, ISC);
+ trace_catpt_irq(isc);
+
+ /* immediate reply */
+ if (isc & CATPT_ISC_IPCCD) {
+ /* mask host DONE interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, CATPT_IMC_IPCCD);
+
+ ipcc = catpt_readl_shim(cdev, IPCC);
+ trace_catpt_ipc_reply(ipcc);
+ catpt_dsp_copy_rx(cdev, ipcc);
+ complete(&cdev->ipc.done_completion);
+
+ /* tell DSP processing is completed */
+ catpt_updatel_shim(cdev, IPCC, CATPT_IPCC_DONE, 0);
+ /* unmask host DONE interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, 0);
+ ret = IRQ_HANDLED;
+ }
+
+ /* delayed reply or notification */
+ if (isc & CATPT_ISC_IPCDB) {
+ /* mask dsp BUSY interrupt */
+ catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, CATPT_IMC_IPCDB);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
+}
diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c
new file mode 100644
index 000000000..8a5f20abc
--- /dev/null
+++ b/sound/soc/intel/catpt/loader.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include "core.h"
+#include "registers.h"
+
+/* FW load (200ms) plus operational delays */
+#define FW_READY_TIMEOUT_MS 250
+
+#define FW_SIGNATURE "$SST"
+#define FW_SIGNATURE_SIZE 4
+
+struct catpt_fw_hdr {
+ char signature[FW_SIGNATURE_SIZE];
+ u32 file_size;
+ u32 modules;
+ u32 file_format;
+ u32 reserved[4];
+} __packed;
+
+struct catpt_fw_mod_hdr {
+ char signature[FW_SIGNATURE_SIZE];
+ u32 mod_size;
+ u32 blocks;
+ u16 slot;
+ u16 module_id;
+ u32 entry_point;
+ u32 persistent_size;
+ u32 scratch_size;
+} __packed;
+
+enum catpt_ram_type {
+ CATPT_RAM_TYPE_IRAM = 1,
+ CATPT_RAM_TYPE_DRAM = 2,
+ /* DRAM with module's initial state */
+ CATPT_RAM_TYPE_INSTANCE = 3,
+};
+
+struct catpt_fw_block_hdr {
+ u32 ram_type;
+ u32 size;
+ u32 ram_offset;
+ u32 rsvd;
+} __packed;
+
+void catpt_sram_init(struct resource *sram, u32 start, u32 size)
+{
+ sram->start = start;
+ sram->end = start + size - 1;
+}
+
+void catpt_sram_free(struct resource *sram)
+{
+ struct resource *res, *save;
+
+ for (res = sram->child; res;) {
+ save = res->sibling;
+ release_resource(res);
+ kfree(res);
+ res = save;
+ }
+}
+
+struct resource *
+catpt_request_region(struct resource *root, resource_size_t size)
+{
+ struct resource *res = root->child;
+ resource_size_t addr = root->start;
+
+ for (;;) {
+ if (res->start - addr >= size)
+ break;
+ addr = res->end + 1;
+ res = res->sibling;
+ if (!res)
+ return NULL;
+ }
+
+ return __request_region(root, addr, size, NULL, 0);
+}
+
+int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ struct catpt_stream_runtime *stream;
+
+ list_for_each_entry(stream, &cdev->stream_list, node) {
+ u32 off, size;
+ int ret;
+
+ off = stream->persistent->start;
+ size = resource_size(stream->persistent);
+ dev_dbg(cdev->dev, "storing stream %d ctx: off 0x%08x size %d\n",
+ stream->info.stream_hw_id, off, size);
+
+ ret = catpt_dma_memcpy_fromdsp(cdev, chan,
+ cdev->dxbuf_paddr + off,
+ cdev->lpe_base + off,
+ ALIGN(size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cdev->modules); i++) {
+ struct catpt_module_type *type;
+ u32 off;
+ int ret;
+
+ type = &cdev->modules[i];
+ if (!type->loaded || !type->state_size)
+ continue;
+
+ off = type->state_offset;
+ dev_dbg(cdev->dev, "storing mod %d state: off 0x%08x size %d\n",
+ i, off, type->state_size);
+
+ ret = catpt_dma_memcpy_fromdsp(cdev, chan,
+ cdev->dxbuf_paddr + off,
+ cdev->lpe_base + off,
+ ALIGN(type->state_size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ int i;
+
+ for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) {
+ struct catpt_save_meminfo *info;
+ u32 off;
+ int ret;
+
+ info = &cdev->dx_ctx.meminfo[i];
+ if (info->source != CATPT_DX_TYPE_MEMORY_DUMP)
+ continue;
+
+ off = catpt_to_host_offset(info->offset);
+ if (off < cdev->dram.start || off > cdev->dram.end)
+ continue;
+
+ dev_dbg(cdev->dev, "storing memdump: off 0x%08x size %d\n",
+ off, info->size);
+
+ ret = catpt_dma_memcpy_fromdsp(cdev, chan,
+ cdev->dxbuf_paddr + off,
+ cdev->lpe_base + off,
+ ALIGN(info->size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int
+catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ struct catpt_stream_runtime *stream;
+
+ list_for_each_entry(stream, &cdev->stream_list, node) {
+ u32 off, size;
+ int ret;
+
+ off = stream->persistent->start;
+ size = resource_size(stream->persistent);
+ dev_dbg(cdev->dev, "restoring stream %d ctx: off 0x%08x size %d\n",
+ stream->info.stream_hw_id, off, size);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan,
+ cdev->lpe_base + off,
+ cdev->dxbuf_paddr + off,
+ ALIGN(size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan)
+{
+ int i;
+
+ for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) {
+ struct catpt_save_meminfo *info;
+ u32 off;
+ int ret;
+
+ info = &cdev->dx_ctx.meminfo[i];
+ if (info->source != CATPT_DX_TYPE_MEMORY_DUMP)
+ continue;
+
+ off = catpt_to_host_offset(info->offset);
+ if (off < cdev->dram.start || off > cdev->dram.end)
+ continue;
+
+ dev_dbg(cdev->dev, "restoring memdump: off 0x%08x size %d\n",
+ off, info->size);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan,
+ cdev->lpe_base + off,
+ cdev->dxbuf_paddr + off,
+ ALIGN(info->size, 4));
+ if (ret) {
+ dev_err(cdev->dev, "restore block failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int catpt_restore_fwimage(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_block_hdr *blk)
+{
+ struct resource r1, r2, common;
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ blk, sizeof(*blk), false);
+
+ r1.start = cdev->dram.start + blk->ram_offset;
+ r1.end = r1.start + blk->size - 1;
+ /* advance to data area */
+ paddr += sizeof(*blk);
+
+ for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) {
+ struct catpt_save_meminfo *info;
+ u32 off;
+ int ret;
+
+ info = &cdev->dx_ctx.meminfo[i];
+
+ if (info->source != CATPT_DX_TYPE_FW_IMAGE)
+ continue;
+
+ off = catpt_to_host_offset(info->offset);
+ if (off < cdev->dram.start || off > cdev->dram.end)
+ continue;
+
+ r2.start = off;
+ r2.end = r2.start + info->size - 1;
+
+ if (!catpt_resource_overlapping(&r2, &r1, &common))
+ continue;
+ /* calculate start offset of common data area */
+ off = common.start - r1.start;
+
+ dev_dbg(cdev->dev, "restoring fwimage: %pr\n", &common);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan, common.start,
+ paddr + off,
+ resource_size(&common));
+ if (ret) {
+ dev_err(cdev->dev, "memcpy todsp failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int catpt_load_block(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_block_hdr *blk, bool alloc)
+{
+ struct resource *sram, *res;
+ dma_addr_t dst_addr;
+ int ret;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ blk, sizeof(*blk), false);
+
+ switch (blk->ram_type) {
+ case CATPT_RAM_TYPE_IRAM:
+ sram = &cdev->iram;
+ break;
+ default:
+ sram = &cdev->dram;
+ break;
+ };
+
+ dst_addr = sram->start + blk->ram_offset;
+ if (alloc) {
+ res = __request_region(sram, dst_addr, blk->size, NULL, 0);
+ if (!res)
+ return -EBUSY;
+ }
+
+ /* advance to data area */
+ paddr += sizeof(*blk);
+
+ ret = catpt_dma_memcpy_todsp(cdev, chan, dst_addr, paddr, blk->size);
+ if (ret) {
+ dev_err(cdev->dev, "memcpy error: %d\n", ret);
+ __release_region(sram, dst_addr, blk->size);
+ }
+
+ return ret;
+}
+
+static int catpt_restore_basefw(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_mod_hdr *basefw)
+{
+ u32 offset = sizeof(*basefw);
+ int ret, i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ basefw, sizeof(*basefw), false);
+
+ /* restore basefw image */
+ for (i = 0; i < basefw->blocks; i++) {
+ struct catpt_fw_block_hdr *blk;
+
+ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset);
+
+ switch (blk->ram_type) {
+ case CATPT_RAM_TYPE_IRAM:
+ ret = catpt_load_block(cdev, chan, paddr + offset,
+ blk, false);
+ break;
+ default:
+ ret = catpt_restore_fwimage(cdev, chan, paddr + offset,
+ blk);
+ break;
+ }
+
+ if (ret) {
+ dev_err(cdev->dev, "restore block failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*blk) + blk->size;
+ }
+
+ /* then proceed with memory dumps */
+ ret = catpt_restore_memdumps(cdev, chan);
+ if (ret)
+ dev_err(cdev->dev, "restore memdumps failed: %d\n", ret);
+
+ return ret;
+}
+
+static int catpt_restore_module(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_mod_hdr *mod)
+{
+ u32 offset = sizeof(*mod);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ mod, sizeof(*mod), false);
+
+ for (i = 0; i < mod->blocks; i++) {
+ struct catpt_fw_block_hdr *blk;
+ int ret;
+
+ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset);
+
+ switch (blk->ram_type) {
+ case CATPT_RAM_TYPE_INSTANCE:
+ /* restore module state */
+ ret = catpt_dma_memcpy_todsp(cdev, chan,
+ cdev->lpe_base + blk->ram_offset,
+ cdev->dxbuf_paddr + blk->ram_offset,
+ ALIGN(blk->size, 4));
+ break;
+ default:
+ ret = catpt_load_block(cdev, chan, paddr + offset,
+ blk, false);
+ break;
+ }
+
+ if (ret) {
+ dev_err(cdev->dev, "restore block failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*blk) + blk->size;
+ }
+
+ return 0;
+}
+
+static int catpt_load_module(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_mod_hdr *mod)
+{
+ struct catpt_module_type *type;
+ u32 offset = sizeof(*mod);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ mod, sizeof(*mod), false);
+
+ type = &cdev->modules[mod->module_id];
+
+ for (i = 0; i < mod->blocks; i++) {
+ struct catpt_fw_block_hdr *blk;
+ int ret;
+
+ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset);
+
+ ret = catpt_load_block(cdev, chan, paddr + offset, blk, true);
+ if (ret) {
+ dev_err(cdev->dev, "load block failed: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Save state window coordinates - these will be
+ * used to capture module state on D0 exit.
+ */
+ if (blk->ram_type == CATPT_RAM_TYPE_INSTANCE) {
+ type->state_offset = blk->ram_offset;
+ type->state_size = blk->size;
+ }
+
+ offset += sizeof(*blk) + blk->size;
+ }
+
+ /* init module type static info */
+ type->loaded = true;
+ /* DSP expects address from module header substracted by 4 */
+ type->entry_point = mod->entry_point - 4;
+ type->persistent_size = mod->persistent_size;
+ type->scratch_size = mod->scratch_size;
+
+ return 0;
+}
+
+static int catpt_restore_firmware(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_hdr *fw)
+{
+ u32 offset = sizeof(*fw);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ fw, sizeof(*fw), false);
+
+ for (i = 0; i < fw->modules; i++) {
+ struct catpt_fw_mod_hdr *mod;
+ int ret;
+
+ mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset);
+ if (strncmp(fw->signature, mod->signature,
+ FW_SIGNATURE_SIZE)) {
+ dev_err(cdev->dev, "module signature mismatch\n");
+ return -EINVAL;
+ }
+
+ if (mod->module_id > CATPT_MODID_LAST)
+ return -EINVAL;
+
+ switch (mod->module_id) {
+ case CATPT_MODID_BASE_FW:
+ ret = catpt_restore_basefw(cdev, chan, paddr + offset,
+ mod);
+ break;
+ default:
+ ret = catpt_restore_module(cdev, chan, paddr + offset,
+ mod);
+ break;
+ }
+
+ if (ret) {
+ dev_err(cdev->dev, "restore module failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*mod) + mod->mod_size;
+ }
+
+ return 0;
+}
+
+static int catpt_load_firmware(struct catpt_dev *cdev,
+ struct dma_chan *chan, dma_addr_t paddr,
+ struct catpt_fw_hdr *fw)
+{
+ u32 offset = sizeof(*fw);
+ int i;
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4,
+ fw, sizeof(*fw), false);
+
+ for (i = 0; i < fw->modules; i++) {
+ struct catpt_fw_mod_hdr *mod;
+ int ret;
+
+ mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset);
+ if (strncmp(fw->signature, mod->signature,
+ FW_SIGNATURE_SIZE)) {
+ dev_err(cdev->dev, "module signature mismatch\n");
+ return -EINVAL;
+ }
+
+ if (mod->module_id > CATPT_MODID_LAST)
+ return -EINVAL;
+
+ ret = catpt_load_module(cdev, chan, paddr + offset, mod);
+ if (ret) {
+ dev_err(cdev->dev, "load module failed: %d\n", ret);
+ return ret;
+ }
+
+ offset += sizeof(*mod) + mod->mod_size;
+ }
+
+ return 0;
+}
+
+static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan,
+ const char *name, const char *signature,
+ bool restore)
+{
+ struct catpt_fw_hdr *fw;
+ struct firmware *img;
+ dma_addr_t paddr;
+ void *vaddr;
+ int ret;
+
+ ret = request_firmware((const struct firmware **)&img, name, cdev->dev);
+ if (ret)
+ return ret;
+
+ fw = (struct catpt_fw_hdr *)img->data;
+ if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) {
+ dev_err(cdev->dev, "firmware signature mismatch\n");
+ ret = -EINVAL;
+ goto release_fw;
+ }
+
+ vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL);
+ if (!vaddr) {
+ ret = -ENOMEM;
+ goto release_fw;
+ }
+
+ memcpy(vaddr, img->data, img->size);
+ fw = (struct catpt_fw_hdr *)vaddr;
+ if (restore)
+ ret = catpt_restore_firmware(cdev, chan, paddr, fw);
+ else
+ ret = catpt_load_firmware(cdev, chan, paddr, fw);
+
+ dma_free_coherent(cdev->dev, img->size, vaddr, paddr);
+release_fw:
+ release_firmware(img);
+ return ret;
+}
+
+static int catpt_load_images(struct catpt_dev *cdev, bool restore)
+{
+ static const char *const names[] = {
+ "intel/IntcSST1.bin",
+ "intel/IntcSST2.bin",
+ };
+ struct dma_chan *chan;
+ int ret;
+
+ chan = catpt_dma_request_config_chan(cdev);
+ if (IS_ERR(chan))
+ return PTR_ERR(chan);
+
+ ret = catpt_load_image(cdev, chan, names[cdev->spec->core_id - 1],
+ FW_SIGNATURE, restore);
+ if (ret)
+ goto release_dma_chan;
+
+ if (!restore)
+ goto release_dma_chan;
+ ret = catpt_restore_streams_context(cdev, chan);
+ if (ret)
+ dev_err(cdev->dev, "restore streams ctx failed: %d\n", ret);
+release_dma_chan:
+ dma_release_channel(chan);
+ return ret;
+}
+
+int catpt_boot_firmware(struct catpt_dev *cdev, bool restore)
+{
+ int ret;
+
+ catpt_dsp_stall(cdev, true);
+
+ ret = catpt_load_images(cdev, restore);
+ if (ret) {
+ dev_err(cdev->dev, "load binaries failed: %d\n", ret);
+ return ret;
+ }
+
+ reinit_completion(&cdev->fw_ready);
+ catpt_dsp_stall(cdev, false);
+
+ ret = wait_for_completion_timeout(&cdev->fw_ready,
+ msecs_to_jiffies(FW_READY_TIMEOUT_MS));
+ if (!ret) {
+ dev_err(cdev->dev, "firmware ready timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ /* update sram pg & clock once done booting */
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+ catpt_dsp_update_srampge(cdev, &cdev->iram, cdev->spec->iram_mask);
+
+ return catpt_dsp_update_lpclock(cdev);
+}
+
+int catpt_first_boot_firmware(struct catpt_dev *cdev)
+{
+ struct resource *res;
+ int ret;
+
+ ret = catpt_boot_firmware(cdev, false);
+ if (ret) {
+ dev_err(cdev->dev, "basefw boot failed: %d\n", ret);
+ return ret;
+ }
+
+ /* restrict FW Core dump area */
+ __request_region(&cdev->dram, 0, 0x200, NULL, 0);
+ /* restrict entire area following BASE_FW - highest offset in DRAM */
+ for (res = cdev->dram.child; res->sibling; res = res->sibling)
+ ;
+ __request_region(&cdev->dram, res->end + 1,
+ cdev->dram.end - res->end, NULL, 0);
+
+ ret = catpt_ipc_get_mixer_stream_info(cdev, &cdev->mixer);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ ret = catpt_arm_stream_templates(cdev);
+ if (ret) {
+ dev_err(cdev->dev, "arm templates failed: %d\n", ret);
+ return ret;
+ }
+
+ /* update dram pg for scratch and restricted regions */
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+ return 0;
+}
diff --git a/sound/soc/intel/catpt/messages.c b/sound/soc/intel/catpt/messages.c
new file mode 100644
index 000000000..a793d114a
--- /dev/null
+++ b/sound/soc/intel/catpt/messages.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/slab.h>
+#include "core.h"
+#include "messages.h"
+#include "registers.h"
+
+int catpt_ipc_get_fw_version(struct catpt_dev *cdev,
+ struct catpt_fw_version *version)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(GET_FW_VERSION);
+ struct catpt_ipc_msg request = {{0}}, reply;
+ int ret;
+
+ request.header = msg.val;
+ reply.size = sizeof(*version);
+ reply.data = version;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "get fw version failed: %d\n", ret);
+
+ return ret;
+}
+
+struct catpt_alloc_stream_input {
+ enum catpt_path_id path_id:8;
+ enum catpt_stream_type stream_type:8;
+ enum catpt_format_id format_id:8;
+ u8 reserved;
+ struct catpt_audio_format input_format;
+ struct catpt_ring_info ring_info;
+ u8 num_entries;
+ /* flex array with entries here */
+ struct catpt_memory_info persistent_mem;
+ struct catpt_memory_info scratch_mem;
+ u32 num_notifications; /* obsolete */
+} __packed;
+
+int catpt_ipc_alloc_stream(struct catpt_dev *cdev,
+ enum catpt_path_id path_id,
+ enum catpt_stream_type type,
+ struct catpt_audio_format *afmt,
+ struct catpt_ring_info *rinfo,
+ u8 num_modules,
+ struct catpt_module_entry *modules,
+ struct resource *persistent,
+ struct resource *scratch,
+ struct catpt_stream_info *sinfo)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(ALLOCATE_STREAM);
+ struct catpt_alloc_stream_input input;
+ struct catpt_ipc_msg request, reply;
+ size_t size, arrsz;
+ u8 *payload;
+ off_t off;
+ int ret;
+
+ off = offsetof(struct catpt_alloc_stream_input, persistent_mem);
+ arrsz = sizeof(*modules) * num_modules;
+ size = sizeof(input) + arrsz;
+
+ payload = kzalloc(size, GFP_KERNEL);
+ if (!payload)
+ return -ENOMEM;
+
+ memset(&input, 0, sizeof(input));
+ input.path_id = path_id;
+ input.stream_type = type;
+ input.format_id = CATPT_FORMAT_PCM;
+ input.input_format = *afmt;
+ input.ring_info = *rinfo;
+ input.num_entries = num_modules;
+ input.persistent_mem.offset = catpt_to_dsp_offset(persistent->start);
+ input.persistent_mem.size = resource_size(persistent);
+ if (scratch) {
+ input.scratch_mem.offset = catpt_to_dsp_offset(scratch->start);
+ input.scratch_mem.size = resource_size(scratch);
+ }
+
+ /* re-arrange the input: account for flex array 'entries' */
+ memcpy(payload, &input, sizeof(input));
+ memmove(payload + off + arrsz, payload + off, sizeof(input) - off);
+ memcpy(payload + off, modules, arrsz);
+
+ request.header = msg.val;
+ request.size = size;
+ request.data = payload;
+ reply.size = sizeof(*sinfo);
+ reply.data = sinfo;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "alloc stream type %d failed: %d\n",
+ type, ret);
+
+ kfree(payload);
+ return ret;
+}
+
+int catpt_ipc_free_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(FREE_STREAM);
+ struct catpt_ipc_msg request;
+ int ret;
+
+ request.header = msg.val;
+ request.size = sizeof(stream_hw_id);
+ request.data = &stream_hw_id;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "free stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_set_device_format(struct catpt_dev *cdev,
+ struct catpt_ssp_device_format *devfmt)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(SET_DEVICE_FORMATS);
+ struct catpt_ipc_msg request;
+ int ret;
+
+ request.header = msg.val;
+ request.size = sizeof(*devfmt);
+ request.data = devfmt;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "set device format failed: %d\n", ret);
+
+ return ret;
+}
+
+int catpt_ipc_enter_dxstate(struct catpt_dev *cdev, enum catpt_dx_state state,
+ struct catpt_dx_context *context)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(ENTER_DX_STATE);
+ struct catpt_ipc_msg request, reply;
+ int ret;
+
+ request.header = msg.val;
+ request.size = sizeof(state);
+ request.data = &state;
+ reply.size = sizeof(*context);
+ reply.data = context;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "enter dx state failed: %d\n", ret);
+
+ return ret;
+}
+
+int catpt_ipc_get_mixer_stream_info(struct catpt_dev *cdev,
+ struct catpt_mixer_stream_info *info)
+{
+ union catpt_global_msg msg = CATPT_GLOBAL_MSG(GET_MIXER_STREAM_INFO);
+ struct catpt_ipc_msg request = {{0}}, reply;
+ int ret;
+
+ request.header = msg.val;
+ reply.size = sizeof(*info);
+ reply.data = info;
+
+ ret = catpt_dsp_send_msg(cdev, request, &reply);
+ if (ret)
+ dev_err(cdev->dev, "get mixer info failed: %d\n", ret);
+
+ return ret;
+}
+
+int catpt_ipc_reset_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_stream_msg msg = CATPT_STREAM_MSG(RESET_STREAM);
+ struct catpt_ipc_msg request = {{0}};
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "reset stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_pause_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_stream_msg msg = CATPT_STREAM_MSG(PAUSE_STREAM);
+ struct catpt_ipc_msg request = {{0}};
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "pause stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_resume_stream(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ union catpt_stream_msg msg = CATPT_STREAM_MSG(RESUME_STREAM);
+ struct catpt_ipc_msg request = {{0}};
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "resume stream %d failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+struct catpt_set_volume_input {
+ u32 channel;
+ u32 target_volume;
+ u64 curve_duration;
+ u32 curve_type;
+} __packed;
+
+int catpt_ipc_set_volume(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 channel, u32 volume,
+ u32 curve_duration,
+ enum catpt_audio_curve_type curve_type)
+{
+ union catpt_stream_msg msg = CATPT_STAGE_MSG(SET_VOLUME);
+ struct catpt_ipc_msg request;
+ struct catpt_set_volume_input input;
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ input.channel = channel;
+ input.target_volume = volume;
+ input.curve_duration = curve_duration;
+ input.curve_type = curve_type;
+
+ request.header = msg.val;
+ request.size = sizeof(input);
+ request.data = &input;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "set stream %d volume failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+struct catpt_set_write_pos_input {
+ u32 new_write_pos;
+ bool end_of_buffer;
+ bool low_latency;
+} __packed;
+
+int catpt_ipc_set_write_pos(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 pos, bool eob, bool ll)
+{
+ union catpt_stream_msg msg = CATPT_STAGE_MSG(SET_WRITE_POSITION);
+ struct catpt_ipc_msg request;
+ struct catpt_set_write_pos_input input;
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ input.new_write_pos = pos;
+ input.end_of_buffer = eob;
+ input.low_latency = ll;
+
+ request.header = msg.val;
+ request.size = sizeof(input);
+ request.data = &input;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "set stream %d write pos failed: %d\n",
+ stream_hw_id, ret);
+
+ return ret;
+}
+
+int catpt_ipc_mute_loopback(struct catpt_dev *cdev, u8 stream_hw_id, bool mute)
+{
+ union catpt_stream_msg msg = CATPT_STAGE_MSG(MUTE_LOOPBACK);
+ struct catpt_ipc_msg request;
+ int ret;
+
+ msg.stream_hw_id = stream_hw_id;
+ request.header = msg.val;
+ request.size = sizeof(mute);
+ request.data = &mute;
+
+ ret = catpt_dsp_send_msg(cdev, request, NULL);
+ if (ret)
+ dev_err(cdev->dev, "mute loopback failed: %d\n", ret);
+
+ return ret;
+}
diff --git a/sound/soc/intel/catpt/messages.h b/sound/soc/intel/catpt/messages.h
new file mode 100644
index 000000000..978a20b3f
--- /dev/null
+++ b/sound/soc/intel/catpt/messages.h
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SND_SOC_INTEL_CATPT_MSG_H
+#define __SND_SOC_INTEL_CATPT_MSG_H
+
+struct catpt_dev;
+
+/* IPC messages base types */
+
+enum catpt_reply_status {
+ CATPT_REPLY_SUCCESS = 0,
+ CATPT_REPLY_ERROR_INVALID_PARAM = 1,
+ CATPT_REPLY_UNKNOWN_MESSAGE_TYPE = 2,
+ CATPT_REPLY_OUT_OF_RESOURCES = 3,
+ CATPT_REPLY_BUSY = 4,
+ CATPT_REPLY_PENDING = 5,
+ CATPT_REPLY_FAILURE = 6,
+ CATPT_REPLY_INVALID_REQUEST = 7,
+ CATPT_REPLY_UNINITIALIZED = 8,
+ CATPT_REPLY_NOT_FOUND = 9,
+ CATPT_REPLY_SOURCE_NOT_STARTED = 10,
+};
+
+/* GLOBAL messages */
+
+enum catpt_global_msg_type {
+ CATPT_GLB_GET_FW_VERSION = 0,
+ CATPT_GLB_ALLOCATE_STREAM = 3,
+ CATPT_GLB_FREE_STREAM = 4,
+ CATPT_GLB_STREAM_MESSAGE = 6,
+ CATPT_GLB_REQUEST_CORE_DUMP = 7,
+ CATPT_GLB_SET_DEVICE_FORMATS = 10,
+ CATPT_GLB_ENTER_DX_STATE = 12,
+ CATPT_GLB_GET_MIXER_STREAM_INFO = 13,
+};
+
+union catpt_global_msg {
+ u32 val;
+ struct {
+ u32 status:5;
+ u32 context:19; /* stream or module specific */
+ u32 global_msg_type:5;
+ u32 fw_ready:1;
+ u32 done:1;
+ u32 busy:1;
+ };
+} __packed;
+
+#define CATPT_MSG(hdr) { .val = hdr }
+#define CATPT_GLOBAL_MSG(msg_type) \
+ { .global_msg_type = CATPT_GLB_##msg_type }
+
+#define BUILD_HASH_SIZE 40
+
+struct catpt_fw_version {
+ u8 build;
+ u8 minor;
+ u8 major;
+ u8 type;
+ u8 build_hash[BUILD_HASH_SIZE];
+ u32 log_providers_hash;
+} __packed;
+
+int catpt_ipc_get_fw_version(struct catpt_dev *cdev,
+ struct catpt_fw_version *version);
+
+enum catpt_pin_id {
+ CATPT_PIN_ID_SYSTEM = 0,
+ CATPT_PIN_ID_REFERENCE = 1,
+ CATPT_PIN_ID_CAPTURE1 = 2,
+ CATPT_PIN_ID_CAPTURE2 = 3,
+ CATPT_PIN_ID_OFFLOAD1 = 4,
+ CATPT_PIN_ID_OFFLOAD2 = 5,
+ CATPT_PIN_ID_MIXER = 7,
+ CATPT_PIN_ID_BLUETOOTH_CAPTURE = 8,
+ CATPT_PIN_ID_BLUETOOTH_RENDER = 9,
+};
+
+enum catpt_path_id {
+ CATPT_PATH_SSP0_OUT = 0,
+ CATPT_PATH_SSP0_IN = 1,
+ CATPT_PATH_SSP1_OUT = 2,
+ CATPT_PATH_SSP1_IN = 3,
+ /* duplicated audio in capture path */
+ CATPT_PATH_SSP0_IN_DUP = 4,
+};
+
+enum catpt_stream_type {
+ CATPT_STRM_TYPE_RENDER = 0, /* offload */
+ CATPT_STRM_TYPE_SYSTEM = 1,
+ CATPT_STRM_TYPE_CAPTURE = 2,
+ CATPT_STRM_TYPE_LOOPBACK = 3,
+ CATPT_STRM_TYPE_BLUETOOTH_RENDER = 4,
+ CATPT_STRM_TYPE_BLUETOOTH_CAPTURE = 5,
+};
+
+enum catpt_format_id {
+ CATPT_FORMAT_PCM = 0,
+ CATPT_FORMAT_MP3 = 1,
+ CATPT_FORMAT_AAC = 2,
+ CATPT_FORMAT_WMA = 3,
+};
+
+enum catpt_channel_index {
+ CATPT_CHANNEL_LEFT = 0x0,
+ CATPT_CHANNEL_CENTER = 0x1,
+ CATPT_CHANNEL_RIGHT = 0x2,
+ CATPT_CHANNEL_LEFT_SURROUND = 0x3,
+ CATPT_CHANNEL_CENTER_SURROUND = 0x3,
+ CATPT_CHANNEL_RIGHT_SURROUND = 0x4,
+ CATPT_CHANNEL_LFE = 0x7,
+ CATPT_CHANNEL_INVALID = 0xF,
+};
+
+enum catpt_channel_config {
+ CATPT_CHANNEL_CONFIG_MONO = 0, /* One channel only */
+ CATPT_CHANNEL_CONFIG_STEREO = 1, /* L & R */
+ CATPT_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only */
+ CATPT_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only */
+ CATPT_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only */
+ CATPT_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only */
+ CATPT_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only */
+ CATPT_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs */
+ CATPT_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE */
+ CATPT_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two */
+ CATPT_CHANNEL_CONFIG_INVALID = 10,
+};
+
+enum catpt_interleaving_style {
+ CATPT_INTERLEAVING_PER_CHANNEL = 0,
+ CATPT_INTERLEAVING_PER_SAMPLE = 1,
+};
+
+struct catpt_audio_format {
+ u32 sample_rate;
+ u32 bit_depth;
+ u32 channel_map;
+ u32 channel_config;
+ u32 interleaving;
+ u8 num_channels;
+ u8 valid_bit_depth;
+ u8 reserved[2];
+} __packed;
+
+struct catpt_ring_info {
+ u32 page_table_addr;
+ u32 num_pages;
+ u32 size;
+ u32 offset;
+ u32 ring_first_page_pfn;
+} __packed;
+
+#define CATPT_MODULE_COUNT (CATPT_MODID_LAST + 1)
+
+enum catpt_module_id {
+ CATPT_MODID_BASE_FW = 0x0,
+ CATPT_MODID_MP3 = 0x1,
+ CATPT_MODID_AAC_5_1 = 0x2,
+ CATPT_MODID_AAC_2_0 = 0x3,
+ CATPT_MODID_SRC = 0x4,
+ CATPT_MODID_WAVES = 0x5,
+ CATPT_MODID_DOLBY = 0x6,
+ CATPT_MODID_BOOST = 0x7,
+ CATPT_MODID_LPAL = 0x8,
+ CATPT_MODID_DTS = 0x9,
+ CATPT_MODID_PCM_CAPTURE = 0xA,
+ CATPT_MODID_PCM_SYSTEM = 0xB,
+ CATPT_MODID_PCM_REFERENCE = 0xC,
+ CATPT_MODID_PCM = 0xD, /* offload */
+ CATPT_MODID_BLUETOOTH_RENDER = 0xE,
+ CATPT_MODID_BLUETOOTH_CAPTURE = 0xF,
+ CATPT_MODID_LAST = CATPT_MODID_BLUETOOTH_CAPTURE,
+};
+
+struct catpt_module_entry {
+ u32 module_id;
+ u32 entry_point;
+} __packed;
+
+struct catpt_module_map {
+ u8 num_entries;
+ struct catpt_module_entry entries[];
+} __packed;
+
+struct catpt_memory_info {
+ u32 offset;
+ u32 size;
+} __packed;
+
+#define CATPT_CHANNELS_MAX 4
+#define CATPT_ALL_CHANNELS_MASK UINT_MAX
+
+struct catpt_stream_info {
+ u32 stream_hw_id;
+ u32 reserved;
+ u32 read_pos_regaddr;
+ u32 pres_pos_regaddr;
+ u32 peak_meter_regaddr[CATPT_CHANNELS_MAX];
+ u32 volume_regaddr[CATPT_CHANNELS_MAX];
+} __packed;
+
+int catpt_ipc_alloc_stream(struct catpt_dev *cdev,
+ enum catpt_path_id path_id,
+ enum catpt_stream_type type,
+ struct catpt_audio_format *afmt,
+ struct catpt_ring_info *rinfo,
+ u8 num_modules,
+ struct catpt_module_entry *modules,
+ struct resource *persistent,
+ struct resource *scratch,
+ struct catpt_stream_info *sinfo);
+int catpt_ipc_free_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+
+enum catpt_ssp_iface {
+ CATPT_SSP_IFACE_0 = 0,
+ CATPT_SSP_IFACE_1 = 1,
+ CATPT_SSP_IFACE_LAST = CATPT_SSP_IFACE_1,
+};
+
+#define CATPT_SSP_COUNT (CATPT_SSP_IFACE_LAST + 1)
+
+enum catpt_mclk_frequency {
+ CATPT_MCLK_OFF = 0,
+ CATPT_MCLK_FREQ_6_MHZ = 1,
+ CATPT_MCLK_FREQ_21_MHZ = 2,
+ CATPT_MCLK_FREQ_24_MHZ = 3,
+};
+
+enum catpt_ssp_mode {
+ CATPT_SSP_MODE_I2S_CONSUMER = 0,
+ CATPT_SSP_MODE_I2S_PROVIDER = 1,
+ CATPT_SSP_MODE_TDM_PROVIDER = 2,
+};
+
+struct catpt_ssp_device_format {
+ u32 iface;
+ u32 mclk;
+ u32 mode;
+ u16 clock_divider;
+ u8 channels;
+} __packed;
+
+int catpt_ipc_set_device_format(struct catpt_dev *cdev,
+ struct catpt_ssp_device_format *devfmt);
+
+enum catpt_dx_state {
+ CATPT_DX_STATE_D3 = 3,
+};
+
+enum catpt_dx_type {
+ CATPT_DX_TYPE_FW_IMAGE = 0,
+ CATPT_DX_TYPE_MEMORY_DUMP = 1,
+};
+
+struct catpt_save_meminfo {
+ u32 offset;
+ u32 size;
+ u32 source;
+} __packed;
+
+#define SAVE_MEMINFO_MAX 14
+
+struct catpt_dx_context {
+ u32 num_meminfo;
+ struct catpt_save_meminfo meminfo[SAVE_MEMINFO_MAX];
+} __packed;
+
+int catpt_ipc_enter_dxstate(struct catpt_dev *cdev, enum catpt_dx_state state,
+ struct catpt_dx_context *context);
+
+struct catpt_mixer_stream_info {
+ u32 mixer_hw_id;
+ u32 peak_meter_regaddr[CATPT_CHANNELS_MAX];
+ u32 volume_regaddr[CATPT_CHANNELS_MAX];
+} __packed;
+
+int catpt_ipc_get_mixer_stream_info(struct catpt_dev *cdev,
+ struct catpt_mixer_stream_info *info);
+
+/* STREAM messages */
+
+enum catpt_stream_msg_type {
+ CATPT_STRM_RESET_STREAM = 0,
+ CATPT_STRM_PAUSE_STREAM = 1,
+ CATPT_STRM_RESUME_STREAM = 2,
+ CATPT_STRM_STAGE_MESSAGE = 3,
+ CATPT_STRM_NOTIFICATION = 4,
+};
+
+enum catpt_stage_action {
+ CATPT_STG_SET_VOLUME = 1,
+ CATPT_STG_SET_WRITE_POSITION = 2,
+ CATPT_STG_MUTE_LOOPBACK = 3,
+};
+
+union catpt_stream_msg {
+ u32 val;
+ struct {
+ u32 status:5;
+ u32 reserved:7;
+ u32 stage_action:4;
+ u32 stream_hw_id:4;
+ u32 stream_msg_type:4;
+ u32 global_msg_type:5;
+ u32 fw_ready:1;
+ u32 done:1;
+ u32 busy:1;
+ };
+} __packed;
+
+#define CATPT_STREAM_MSG(msg_type) \
+{ \
+ .stream_msg_type = CATPT_STRM_##msg_type, \
+ .global_msg_type = CATPT_GLB_STREAM_MESSAGE }
+#define CATPT_STAGE_MSG(msg_type) \
+{ \
+ .stage_action = CATPT_STG_##msg_type, \
+ .stream_msg_type = CATPT_STRM_STAGE_MESSAGE, \
+ .global_msg_type = CATPT_GLB_STREAM_MESSAGE }
+
+int catpt_ipc_reset_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_ipc_pause_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+int catpt_ipc_resume_stream(struct catpt_dev *cdev, u8 stream_hw_id);
+
+/* STREAM messages - STAGE subtype */
+
+enum catpt_audio_curve_type {
+ CATPT_AUDIO_CURVE_NONE = 0,
+ CATPT_AUDIO_CURVE_WINDOWS_FADE = 1,
+};
+
+int catpt_ipc_set_volume(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 channel, u32 volume,
+ u32 curve_duration,
+ enum catpt_audio_curve_type curve_type);
+
+int catpt_ipc_set_write_pos(struct catpt_dev *cdev, u8 stream_hw_id,
+ u32 pos, bool eob, bool ll);
+
+int catpt_ipc_mute_loopback(struct catpt_dev *cdev, u8 stream_hw_id, bool mute);
+
+/* NOTIFICATION messages */
+
+enum catpt_notify_reason {
+ CATPT_NOTIFY_POSITION_CHANGED = 0,
+ CATPT_NOTIFY_GLITCH_OCCURRED = 1,
+};
+
+union catpt_notify_msg {
+ u32 val;
+ struct {
+ u32 mailbox_address:29;
+ u32 fw_ready:1;
+ u32 done:1;
+ u32 busy:1;
+ };
+ struct {
+ u32 status:5;
+ u32 reserved:7;
+ u32 notify_reason:4;
+ u32 stream_hw_id:4;
+ u32 stream_msg_type:4;
+ u32 global_msg_type:5;
+ u32 hdr:3; /* fw_ready, done, busy */
+ };
+} __packed;
+
+#define FW_INFO_SIZE_MAX 100
+
+struct catpt_fw_ready {
+ u32 inbox_offset;
+ u32 outbox_offset;
+ u32 inbox_size;
+ u32 outbox_size;
+ u32 fw_info_size;
+ char fw_info[FW_INFO_SIZE_MAX];
+} __packed;
+
+struct catpt_notify_position {
+ u32 stream_position;
+ u32 fw_cycle_count;
+} __packed;
+
+enum catpt_glitch_type {
+ CATPT_GLITCH_UNDERRUN = 1,
+ CATPT_GLITCH_DECODER_ERROR = 2,
+ CATPT_GLITCH_DOUBLED_WRITE_POS = 3,
+};
+
+struct catpt_notify_glitch {
+ u32 type;
+ u64 presentation_pos;
+ u32 write_pos;
+} __packed;
+
+#endif
diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c
new file mode 100644
index 000000000..408e64e3b
--- /dev/null
+++ b/sound/soc/intel/catpt/pcm.c
@@ -0,0 +1,1184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <uapi/sound/tlv.h>
+#include "core.h"
+#include "messages.h"
+
+struct catpt_stream_template {
+ enum catpt_path_id path_id;
+ enum catpt_stream_type type;
+ u32 persistent_size;
+ u8 num_entries;
+ struct catpt_module_entry entries[];
+};
+
+static struct catpt_stream_template system_pb = {
+ .path_id = CATPT_PATH_SSP0_OUT,
+ .type = CATPT_STRM_TYPE_SYSTEM,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM_SYSTEM, 0 }},
+};
+
+static struct catpt_stream_template system_cp = {
+ .path_id = CATPT_PATH_SSP0_IN,
+ .type = CATPT_STRM_TYPE_CAPTURE,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM_CAPTURE, 0 }},
+};
+
+static struct catpt_stream_template offload_pb = {
+ .path_id = CATPT_PATH_SSP0_OUT,
+ .type = CATPT_STRM_TYPE_RENDER,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM, 0 }},
+};
+
+static struct catpt_stream_template loopback_cp = {
+ .path_id = CATPT_PATH_SSP0_OUT,
+ .type = CATPT_STRM_TYPE_LOOPBACK,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_PCM_REFERENCE, 0 }},
+};
+
+static struct catpt_stream_template bluetooth_pb = {
+ .path_id = CATPT_PATH_SSP1_OUT,
+ .type = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_BLUETOOTH_RENDER, 0 }},
+};
+
+static struct catpt_stream_template bluetooth_cp = {
+ .path_id = CATPT_PATH_SSP1_IN,
+ .type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE,
+ .num_entries = 1,
+ .entries = {{ CATPT_MODID_BLUETOOTH_CAPTURE, 0 }},
+};
+
+static struct catpt_stream_template *catpt_topology[] = {
+ [CATPT_STRM_TYPE_RENDER] = &offload_pb,
+ [CATPT_STRM_TYPE_SYSTEM] = &system_pb,
+ [CATPT_STRM_TYPE_CAPTURE] = &system_cp,
+ [CATPT_STRM_TYPE_LOOPBACK] = &loopback_cp,
+ [CATPT_STRM_TYPE_BLUETOOTH_RENDER] = &bluetooth_pb,
+ [CATPT_STRM_TYPE_BLUETOOTH_CAPTURE] = &bluetooth_cp,
+};
+
+static struct catpt_stream_template *
+catpt_get_stream_template(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtm = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
+ enum catpt_stream_type type;
+
+ type = cpu_dai->driver->id;
+
+ /* account for capture in bidirectional dais */
+ switch (type) {
+ case CATPT_STRM_TYPE_SYSTEM:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ type = CATPT_STRM_TYPE_CAPTURE;
+ break;
+ case CATPT_STRM_TYPE_BLUETOOTH_RENDER:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ type = CATPT_STRM_TYPE_BLUETOOTH_CAPTURE;
+ break;
+ default:
+ break;
+ };
+
+ return catpt_topology[type];
+}
+
+struct catpt_stream_runtime *
+catpt_stream_find(struct catpt_dev *cdev, u8 stream_hw_id)
+{
+ struct catpt_stream_runtime *pos, *result = NULL;
+
+ spin_lock(&cdev->list_lock);
+ list_for_each_entry(pos, &cdev->stream_list, node) {
+ if (pos->info.stream_hw_id == stream_hw_id) {
+ result = pos;
+ break;
+ }
+ }
+
+ spin_unlock(&cdev->list_lock);
+ return result;
+}
+
+static u32 catpt_stream_read_position(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream)
+{
+ u32 pos;
+
+ memcpy_fromio(&pos, cdev->lpe_ba + stream->info.read_pos_regaddr,
+ sizeof(pos));
+ return pos;
+}
+
+static u32 catpt_stream_volume(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream, u32 channel)
+{
+ u32 volume, offset;
+
+ if (channel >= CATPT_CHANNELS_MAX)
+ channel = 0;
+
+ offset = stream->info.volume_regaddr[channel];
+ memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
+ return volume;
+}
+
+static u32 catpt_mixer_volume(struct catpt_dev *cdev,
+ struct catpt_mixer_stream_info *info, u32 channel)
+{
+ u32 volume, offset;
+
+ if (channel >= CATPT_CHANNELS_MAX)
+ channel = 0;
+
+ offset = info->volume_regaddr[channel];
+ memcpy_fromio(&volume, cdev->lpe_ba + offset, sizeof(volume));
+ return volume;
+}
+
+static void catpt_arrange_page_table(struct snd_pcm_substream *substream,
+ struct snd_dma_buffer *pgtbl)
+{
+ struct snd_pcm_runtime *rtm = substream->runtime;
+ struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream);
+ int i, pages;
+
+ pages = snd_sgbuf_aligned_pages(rtm->dma_bytes);
+
+ for (i = 0; i < pages; i++) {
+ u32 pfn, offset;
+ u32 *page_table;
+
+ pfn = PFN_DOWN(snd_sgbuf_get_addr(databuf, i * PAGE_SIZE));
+ /* incrementing by 2 on even and 3 on odd */
+ offset = ((i << 2) + i) >> 1;
+ page_table = (u32 *)(pgtbl->area + offset);
+
+ if (i & 1)
+ *page_table |= (pfn << 4);
+ else
+ *page_table |= pfn;
+ }
+}
+
+static u32 catpt_get_channel_map(enum catpt_channel_config config)
+{
+ switch (config) {
+ case CATPT_CHANNEL_CONFIG_MONO:
+ return GENMASK(31, 4) | CATPT_CHANNEL_CENTER;
+
+ case CATPT_CHANNEL_CONFIG_STEREO:
+ return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_RIGHT << 4);
+
+ case CATPT_CHANNEL_CONFIG_2_POINT_1:
+ return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_RIGHT << 4)
+ | (CATPT_CHANNEL_LFE << 8);
+
+ case CATPT_CHANNEL_CONFIG_3_POINT_0:
+ return GENMASK(31, 12) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8);
+
+ case CATPT_CHANNEL_CONFIG_3_POINT_1:
+ return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_LFE << 12);
+
+ case CATPT_CHANNEL_CONFIG_QUATRO:
+ return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_RIGHT << 4)
+ | (CATPT_CHANNEL_LEFT_SURROUND << 8)
+ | (CATPT_CHANNEL_RIGHT_SURROUND << 12);
+
+ case CATPT_CHANNEL_CONFIG_4_POINT_0:
+ return GENMASK(31, 16) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_CENTER_SURROUND << 12);
+
+ case CATPT_CHANNEL_CONFIG_5_POINT_0:
+ return GENMASK(31, 20) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_CENTER << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_LEFT_SURROUND << 12)
+ | (CATPT_CHANNEL_RIGHT_SURROUND << 16);
+
+ case CATPT_CHANNEL_CONFIG_5_POINT_1:
+ return GENMASK(31, 24) | CATPT_CHANNEL_CENTER
+ | (CATPT_CHANNEL_LEFT << 4)
+ | (CATPT_CHANNEL_RIGHT << 8)
+ | (CATPT_CHANNEL_LEFT_SURROUND << 12)
+ | (CATPT_CHANNEL_RIGHT_SURROUND << 16)
+ | (CATPT_CHANNEL_LFE << 20);
+
+ case CATPT_CHANNEL_CONFIG_DUAL_MONO:
+ return GENMASK(31, 8) | CATPT_CHANNEL_LEFT
+ | (CATPT_CHANNEL_LEFT << 4);
+
+ default:
+ return U32_MAX;
+ }
+}
+
+static enum catpt_channel_config catpt_get_channel_config(u32 num_channels)
+{
+ switch (num_channels) {
+ case 6:
+ return CATPT_CHANNEL_CONFIG_5_POINT_1;
+ case 5:
+ return CATPT_CHANNEL_CONFIG_5_POINT_0;
+ case 4:
+ return CATPT_CHANNEL_CONFIG_QUATRO;
+ case 3:
+ return CATPT_CHANNEL_CONFIG_2_POINT_1;
+ case 1:
+ return CATPT_CHANNEL_CONFIG_MONO;
+ case 2:
+ default:
+ return CATPT_CHANNEL_CONFIG_STEREO;
+ }
+}
+
+static int catpt_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_template *template;
+ struct catpt_stream_runtime *stream;
+ struct resource *res;
+ int ret;
+
+ template = catpt_get_stream_template(substream);
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, cdev->dev, PAGE_SIZE,
+ &stream->pgtbl);
+ if (ret)
+ goto err_pgtbl;
+
+ res = catpt_request_region(&cdev->dram, template->persistent_size);
+ if (!res) {
+ ret = -EBUSY;
+ goto err_request;
+ }
+
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+ stream->template = template;
+ stream->persistent = res;
+ stream->substream = substream;
+ INIT_LIST_HEAD(&stream->node);
+ snd_soc_dai_set_dma_data(dai, substream, stream);
+
+ spin_lock(&cdev->list_lock);
+ list_add_tail(&stream->node, &cdev->stream_list);
+ spin_unlock(&cdev->list_lock);
+
+ return 0;
+
+err_request:
+ snd_dma_free_pages(&stream->pgtbl);
+err_pgtbl:
+ kfree(stream);
+ return ret;
+}
+
+static void catpt_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ spin_lock(&cdev->list_lock);
+ list_del(&stream->node);
+ spin_unlock(&cdev->list_lock);
+
+ release_resource(stream->persistent);
+ kfree(stream->persistent);
+ catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+ snd_dma_free_pages(&stream->pgtbl);
+ kfree(stream);
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int catpt_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+ struct catpt_audio_format afmt;
+ struct catpt_ring_info rinfo;
+ struct snd_pcm_runtime *rtm = substream->runtime;
+ struct snd_dma_buffer *dmab;
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (stream->allocated)
+ return 0;
+
+ memset(&afmt, 0, sizeof(afmt));
+ afmt.sample_rate = params_rate(params);
+ afmt.bit_depth = params_physical_width(params);
+ afmt.valid_bit_depth = params_width(params);
+ afmt.num_channels = params_channels(params);
+ afmt.channel_config = catpt_get_channel_config(afmt.num_channels);
+ afmt.channel_map = catpt_get_channel_map(afmt.channel_config);
+ afmt.interleaving = CATPT_INTERLEAVING_PER_CHANNEL;
+
+ dmab = snd_pcm_get_dma_buf(substream);
+ catpt_arrange_page_table(substream, &stream->pgtbl);
+
+ memset(&rinfo, 0, sizeof(rinfo));
+ rinfo.page_table_addr = stream->pgtbl.addr;
+ rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE);
+ rinfo.size = rtm->dma_bytes;
+ rinfo.offset = 0;
+ rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0));
+
+ ret = catpt_ipc_alloc_stream(cdev, stream->template->path_id,
+ stream->template->type,
+ &afmt, &rinfo,
+ stream->template->num_entries,
+ stream->template->entries,
+ stream->persistent,
+ cdev->scratch,
+ &stream->info);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ stream->allocated = true;
+ return 0;
+}
+
+static int catpt_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (!stream->allocated)
+ return 0;
+
+ catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
+ catpt_ipc_free_stream(cdev, stream->info.stream_hw_id);
+
+ stream->allocated = false;
+ return 0;
+}
+
+static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol);
+
+static int catpt_dai_apply_usettings(struct snd_soc_dai *dai,
+ struct catpt_stream_runtime *stream)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct snd_soc_component *component = dai->component;
+ struct snd_kcontrol *pos, *kctl = NULL;
+ const char *name;
+ int ret;
+ u32 id = stream->info.stream_hw_id;
+
+ /* only selected streams have individual controls */
+ switch (id) {
+ case CATPT_PIN_ID_OFFLOAD1:
+ name = "Media0 Playback Volume";
+ break;
+ case CATPT_PIN_ID_OFFLOAD2:
+ name = "Media1 Playback Volume";
+ break;
+ case CATPT_PIN_ID_CAPTURE1:
+ name = "Mic Capture Volume";
+ break;
+ case CATPT_PIN_ID_REFERENCE:
+ name = "Loopback Mute";
+ break;
+ default:
+ return 0;
+ };
+
+ list_for_each_entry(pos, &component->card->snd_card->controls, list) {
+ if (pos->private_data == component &&
+ !strncmp(name, pos->id.name, sizeof(pos->id.name))) {
+ kctl = pos;
+ break;
+ }
+ }
+ if (!kctl)
+ return -ENOENT;
+
+ if (stream->template->type != CATPT_STRM_TYPE_LOOPBACK)
+ return catpt_set_dspvol(cdev, id, (long *)kctl->private_value);
+ ret = catpt_ipc_mute_loopback(cdev, id, *(bool *)kctl->private_value);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ return 0;
+}
+
+static int catpt_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (stream->prepared)
+ return 0;
+
+ ret = catpt_ipc_reset_stream(cdev, stream->info.stream_hw_id);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ ret = catpt_dai_apply_usettings(dai, stream);
+ if (ret)
+ return ret;
+
+ stream->prepared = true;
+ return 0;
+}
+
+static int catpt_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_stream_runtime *stream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_uframes_t pos;
+ int ret;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* only offload is set_write_pos driven */
+ if (stream->template->type != CATPT_STRM_TYPE_RENDER)
+ goto resume_stream;
+
+ pos = frames_to_bytes(runtime, runtime->start_threshold);
+ /*
+ * Dsp operates on buffer halves, thus max 2x set_write_pos
+ * (entire buffer filled) prior to stream start.
+ */
+ ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id,
+ pos, false, false);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ resume_stream:
+ catpt_dsp_update_lpclock(cdev);
+ ret = catpt_ipc_resume_stream(cdev, stream->info.stream_hw_id);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ stream->prepared = false;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = catpt_ipc_pause_stream(cdev, stream->info.stream_hw_id);
+ catpt_dsp_update_lpclock(cdev);
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void catpt_stream_update_position(struct catpt_dev *cdev,
+ struct catpt_stream_runtime *stream,
+ struct catpt_notify_position *pos)
+{
+ struct snd_pcm_substream *substream = stream->substream;
+ struct snd_pcm_runtime *r = substream->runtime;
+ snd_pcm_uframes_t dsppos, newpos;
+ int ret;
+
+ dsppos = bytes_to_frames(r, pos->stream_position);
+
+ if (!stream->prepared)
+ goto exit;
+ /* only offload is set_write_pos driven */
+ if (stream->template->type != CATPT_STRM_TYPE_RENDER)
+ goto exit;
+
+ if (dsppos >= r->buffer_size / 2)
+ newpos = r->buffer_size / 2;
+ else
+ newpos = 0;
+ /*
+ * Dsp operates on buffer halves, thus on every notify position
+ * (buffer half consumed) update wp to allow stream progression.
+ */
+ ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id,
+ frames_to_bytes(r, newpos),
+ false, false);
+ if (ret) {
+ dev_err(cdev->dev, "update position for stream %d failed: %d\n",
+ stream->info.stream_hw_id, ret);
+ return;
+ }
+exit:
+ snd_pcm_period_elapsed(substream);
+}
+
+/* 200 ms for 2 32-bit channels at 48kHz (native format) */
+#define CATPT_BUFFER_MAX_SIZE 76800
+#define CATPT_PCM_PERIODS_MAX 4
+#define CATPT_PCM_PERIODS_MIN 2
+
+static const struct snd_pcm_hardware catpt_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = PAGE_SIZE,
+ .period_bytes_max = CATPT_BUFFER_MAX_SIZE / CATPT_PCM_PERIODS_MIN,
+ .periods_min = CATPT_PCM_PERIODS_MIN,
+ .periods_max = CATPT_PCM_PERIODS_MAX,
+ .buffer_bytes_max = CATPT_BUFFER_MAX_SIZE,
+};
+
+static int catpt_component_pcm_construct(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtm)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+
+ snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG,
+ cdev->dev,
+ catpt_pcm_hardware.buffer_bytes_max,
+ catpt_pcm_hardware.buffer_bytes_max);
+
+ return 0;
+}
+
+static int catpt_component_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtm = substream->private_data;
+
+ if (rtm->dai_link->no_pcm)
+ return 0;
+ snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware);
+ return 0;
+}
+
+static snd_pcm_uframes_t
+catpt_component_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ struct snd_soc_pcm_runtime *rtm = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtm, 0);
+ u32 pos;
+
+ if (rtm->dai_link->no_pcm)
+ return 0;
+
+ stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
+ pos = catpt_stream_read_position(cdev, stream);
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static const struct snd_soc_dai_ops catpt_fe_dai_ops = {
+ .startup = catpt_dai_startup,
+ .shutdown = catpt_dai_shutdown,
+ .hw_params = catpt_dai_hw_params,
+ .hw_free = catpt_dai_hw_free,
+ .prepare = catpt_dai_prepare,
+ .trigger = catpt_dai_trigger,
+};
+
+static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0);
+ struct catpt_dev *cdev = dev_get_drvdata(dai->dev);
+ struct catpt_ssp_device_format devfmt;
+ int ret;
+
+ devfmt.iface = dai->driver->id;
+ devfmt.channels = codec_dai->driver->capture.channels_max;
+
+ switch (devfmt.iface) {
+ case CATPT_SSP_IFACE_0:
+ devfmt.mclk = CATPT_MCLK_FREQ_24_MHZ;
+
+ switch (devfmt.channels) {
+ case 4:
+ devfmt.mode = CATPT_SSP_MODE_TDM_PROVIDER;
+ devfmt.clock_divider = 4;
+ break;
+ case 2:
+ default:
+ devfmt.mode = CATPT_SSP_MODE_I2S_PROVIDER;
+ devfmt.clock_divider = 9;
+ break;
+ }
+ break;
+
+ case CATPT_SSP_IFACE_1:
+ devfmt.mclk = CATPT_MCLK_OFF;
+ devfmt.mode = CATPT_SSP_MODE_I2S_CONSUMER;
+ devfmt.clock_divider = 0;
+ break;
+ }
+
+ /* see if this is a new configuration */
+ if (!memcmp(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt)))
+ return 0;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_ipc_set_device_format(cdev, &devfmt);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ /* store device format set for given SSP */
+ memcpy(&cdev->devfmt[devfmt.iface], &devfmt, sizeof(devfmt));
+ return 0;
+}
+
+static struct snd_soc_dai_driver dai_drivers[] = {
+/* FE DAIs */
+{
+ .name = "System Pin",
+ .id = CATPT_STRM_TYPE_SYSTEM,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "System Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .stream_name = "Analog Capture",
+ .channels_min = 2,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Offload0 Pin",
+ .id = CATPT_STRM_TYPE_RENDER,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "Offload0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Offload1 Pin",
+ .id = CATPT_STRM_TYPE_RENDER,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "Offload1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Loopback Pin",
+ .id = CATPT_STRM_TYPE_LOOPBACK,
+ .ops = &catpt_fe_dai_ops,
+ .capture = {
+ .stream_name = "Loopback Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Bluetooth Pin",
+ .id = CATPT_STRM_TYPE_BLUETOOTH_RENDER,
+ .ops = &catpt_fe_dai_ops,
+ .playback = {
+ .stream_name = "Bluetooth Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Bluetooth Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+},
+/* BE DAIs */
+{
+ .name = "ssp0-port",
+ .id = CATPT_SSP_IFACE_0,
+ .pcm_new = catpt_dai_pcm_new,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "ssp1-port",
+ .id = CATPT_SSP_IFACE_1,
+ .pcm_new = catpt_dai_pcm_new,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+};
+
+#define DSP_VOLUME_MAX S32_MAX /* 0db */
+#define DSP_VOLUME_STEP_MAX 30
+
+static u32 ctlvol_to_dspvol(u32 value)
+{
+ if (value > DSP_VOLUME_STEP_MAX)
+ value = 0;
+ return DSP_VOLUME_MAX >> (DSP_VOLUME_STEP_MAX - value);
+}
+
+static u32 dspvol_to_ctlvol(u32 volume)
+{
+ if (volume > DSP_VOLUME_MAX)
+ return DSP_VOLUME_STEP_MAX;
+ return volume ? __fls(volume) : 0;
+}
+
+static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol)
+{
+ u32 dspvol;
+ int ret, i;
+
+ for (i = 1; i < CATPT_CHANNELS_MAX; i++)
+ if (ctlvol[i] != ctlvol[0])
+ break;
+
+ if (i == CATPT_CHANNELS_MAX) {
+ dspvol = ctlvol_to_dspvol(ctlvol[0]);
+
+ ret = catpt_ipc_set_volume(cdev, stream_id,
+ CATPT_ALL_CHANNELS_MASK, dspvol,
+ 0, CATPT_AUDIO_CURVE_NONE);
+ } else {
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+ dspvol = ctlvol_to_dspvol(ctlvol[i]);
+
+ ret = catpt_ipc_set_volume(cdev, stream_id,
+ i, dspvol,
+ 0, CATPT_AUDIO_CURVE_NONE);
+ if (ret)
+ break;
+ }
+ }
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+ return 0;
+}
+
+static int catpt_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = CATPT_CHANNELS_MAX;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = DSP_VOLUME_STEP_MAX;
+ return 0;
+}
+
+static int catpt_mixer_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ u32 dspvol;
+ int i;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+ dspvol = catpt_mixer_volume(cdev, &cdev->mixer, i);
+ ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
+ }
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ return 0;
+}
+
+static int catpt_mixer_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ int ret;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_set_dspvol(cdev, cdev->mixer.mixer_hw_id,
+ ucontrol->value.integer.value);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ return ret;
+}
+
+static int catpt_stream_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ enum catpt_pin_id pin_id)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ long *ctlvol = (long *)kcontrol->private_value;
+ u32 dspvol;
+ int i;
+
+ stream = catpt_stream_find(cdev, pin_id);
+ if (!stream) {
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+ ucontrol->value.integer.value[i] = ctlvol[i];
+ return 0;
+ }
+
+ pm_runtime_get_sync(cdev->dev);
+
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++) {
+ dspvol = catpt_stream_volume(cdev, stream, i);
+ ucontrol->value.integer.value[i] = dspvol_to_ctlvol(dspvol);
+ }
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ return 0;
+}
+
+static int catpt_stream_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ enum catpt_pin_id pin_id)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ long *ctlvol = (long *)kcontrol->private_value;
+ int ret, i;
+
+ stream = catpt_stream_find(cdev, pin_id);
+ if (!stream) {
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+ ctlvol[i] = ucontrol->value.integer.value[i];
+ return 0;
+ }
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_set_dspvol(cdev, stream->info.stream_hw_id,
+ ucontrol->value.integer.value);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return ret;
+
+ for (i = 0; i < CATPT_CHANNELS_MAX; i++)
+ ctlvol[i] = ucontrol->value.integer.value[i];
+ return 0;
+}
+
+static int catpt_offload1_volume_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
+}
+
+static int catpt_offload1_volume_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD1);
+}
+
+static int catpt_offload2_volume_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
+}
+
+static int catpt_offload2_volume_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_OFFLOAD2);
+}
+
+static int catpt_capture_volume_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_get(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
+}
+
+static int catpt_capture_volume_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ return catpt_stream_volume_put(kctl, uctl, CATPT_PIN_ID_CAPTURE1);
+}
+
+static int catpt_loopback_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = *(bool *)kcontrol->private_value;
+ return 0;
+}
+
+static int catpt_loopback_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct catpt_dev *cdev = dev_get_drvdata(component->dev);
+ struct catpt_stream_runtime *stream;
+ bool mute;
+ int ret;
+
+ mute = (bool)ucontrol->value.integer.value[0];
+ stream = catpt_stream_find(cdev, CATPT_PIN_ID_REFERENCE);
+ if (!stream) {
+ *(bool *)kcontrol->private_value = mute;
+ return 0;
+ }
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_ipc_mute_loopback(cdev, stream->info.stream_hw_id, mute);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ *(bool *)kcontrol->private_value = mute;
+ return 0;
+}
+
+static int catpt_waves_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return 0;
+}
+
+static int catpt_waves_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return 0;
+}
+
+static int catpt_waves_param_get(struct snd_kcontrol *kcontrol,
+ unsigned int __user *bytes,
+ unsigned int size)
+{
+ return 0;
+}
+
+static int catpt_waves_param_put(struct snd_kcontrol *kcontrol,
+ const unsigned int __user *bytes,
+ unsigned int size)
+{
+ return 0;
+}
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(catpt_volume_tlv, -9000, 300, 1);
+
+#define CATPT_VOLUME_CTL(kname, sname) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = (kname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = catpt_volume_info, \
+ .get = catpt_##sname##_volume_get, \
+ .put = catpt_##sname##_volume_put, \
+ .tlv.p = catpt_volume_tlv, \
+ .private_value = (unsigned long) \
+ &(long[CATPT_CHANNELS_MAX]) {0} }
+
+static const struct snd_kcontrol_new component_kcontrols[] = {
+/* Master volume (mixer stream) */
+CATPT_VOLUME_CTL("Master Playback Volume", mixer),
+/* Individual volume controls for offload and capture */
+CATPT_VOLUME_CTL("Media0 Playback Volume", offload1),
+CATPT_VOLUME_CTL("Media1 Playback Volume", offload2),
+CATPT_VOLUME_CTL("Mic Capture Volume", capture),
+SOC_SINGLE_BOOL_EXT("Loopback Mute", (unsigned long)&(bool[1]) {0},
+ catpt_loopback_switch_get, catpt_loopback_switch_put),
+/* Enable or disable WAVES module */
+SOC_SINGLE_BOOL_EXT("Waves Switch", 0,
+ catpt_waves_switch_get, catpt_waves_switch_put),
+/* WAVES module parameter control */
+SND_SOC_BYTES_TLV("Waves Set Param", 128,
+ catpt_waves_param_get, catpt_waves_param_put),
+};
+
+static const struct snd_soc_dapm_widget component_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route component_routes[] = {
+ {"Playback VMixer", NULL, "System Playback"},
+ {"Playback VMixer", NULL, "Offload0 Playback"},
+ {"Playback VMixer", NULL, "Offload1 Playback"},
+
+ {"SSP0 CODEC OUT", NULL, "Playback VMixer"},
+
+ {"Analog Capture", NULL, "SSP0 CODEC IN"},
+ {"Loopback Capture", NULL, "SSP0 CODEC IN"},
+
+ {"SSP1 BT OUT", NULL, "Bluetooth Playback"},
+ {"Bluetooth Capture", NULL, "SSP1 BT IN"},
+};
+
+static const struct snd_soc_component_driver catpt_comp_driver = {
+ .name = "catpt-platform",
+
+ .pcm_construct = catpt_component_pcm_construct,
+ .open = catpt_component_open,
+ .pointer = catpt_component_pointer,
+
+ .controls = component_kcontrols,
+ .num_controls = ARRAY_SIZE(component_kcontrols),
+ .dapm_widgets = component_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(component_widgets),
+ .dapm_routes = component_routes,
+ .num_dapm_routes = ARRAY_SIZE(component_routes),
+};
+
+int catpt_arm_stream_templates(struct catpt_dev *cdev)
+{
+ struct resource *res;
+ u32 scratch_size = 0;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(catpt_topology); i++) {
+ struct catpt_stream_template *template;
+ struct catpt_module_entry *entry;
+ struct catpt_module_type *type;
+
+ template = catpt_topology[i];
+ template->persistent_size = 0;
+
+ for (j = 0; j < template->num_entries; j++) {
+ entry = &template->entries[j];
+ type = &cdev->modules[entry->module_id];
+
+ if (!type->loaded)
+ return -ENOENT;
+
+ entry->entry_point = type->entry_point;
+ template->persistent_size += type->persistent_size;
+ if (type->scratch_size > scratch_size)
+ scratch_size = type->scratch_size;
+ }
+ }
+
+ if (scratch_size) {
+ /* allocate single scratch area for all modules */
+ res = catpt_request_region(&cdev->dram, scratch_size);
+ if (!res)
+ return -EBUSY;
+ cdev->scratch = res;
+ }
+
+ return 0;
+}
+
+int catpt_register_plat_component(struct catpt_dev *cdev)
+{
+ struct snd_soc_component *component;
+ int ret;
+
+ component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL);
+ if (!component)
+ return -ENOMEM;
+
+ ret = snd_soc_component_initialize(component, &catpt_comp_driver,
+ cdev->dev);
+ if (ret)
+ return ret;
+
+ component->name = catpt_comp_driver.name;
+ return snd_soc_add_component(component, dai_drivers,
+ ARRAY_SIZE(dai_drivers));
+}
diff --git a/sound/soc/intel/catpt/registers.h b/sound/soc/intel/catpt/registers.h
new file mode 100644
index 000000000..47280d828
--- /dev/null
+++ b/sound/soc/intel/catpt/registers.h
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#ifndef __SND_SOC_INTEL_CATPT_REGS_H
+#define __SND_SOC_INTEL_CATPT_REGS_H
+
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <uapi/linux/pci_regs.h>
+
+#define CATPT_SHIM_REGS_SIZE 4096
+#define CATPT_DMA_REGS_SIZE 1024
+#define CATPT_DMA_COUNT 2
+#define CATPT_SSP_REGS_SIZE 512
+
+/* DSP Shim registers */
+
+#define CATPT_SHIM_CS1 0x00
+#define CATPT_SHIM_ISC 0x18
+#define CATPT_SHIM_ISD 0x20
+#define CATPT_SHIM_IMC 0x28
+#define CATPT_SHIM_IMD 0x30
+#define CATPT_SHIM_IPCC 0x38
+#define CATPT_SHIM_IPCD 0x40
+#define CATPT_SHIM_CLKCTL 0x78
+#define CATPT_SHIM_CS2 0x80
+#define CATPT_SHIM_LTRC 0xE0
+#define CATPT_SHIM_HMDC 0xE8
+
+#define CATPT_CS_LPCS BIT(31)
+#define CATPT_CS_SFCR(ssp) BIT(27 + (ssp))
+#define CATPT_CS_S1IOCS BIT(23)
+#define CATPT_CS_S0IOCS BIT(21)
+#define CATPT_CS_PCE BIT(15)
+#define CATPT_CS_SDPM(ssp) BIT(11 + (ssp))
+#define CATPT_CS_STALL BIT(10)
+#define CATPT_CS_DCS GENMASK(6, 4)
+/* b100 DSP core & audio fabric high clock */
+#define CATPT_CS_DCS_HIGH (0x4 << 4)
+#define CATPT_CS_SBCS(ssp) BIT(2 + (ssp))
+#define CATPT_CS_RST BIT(1)
+
+#define CATPT_ISC_IPCDB BIT(1)
+#define CATPT_ISC_IPCCD BIT(0)
+#define CATPT_ISD_DCPWM BIT(31)
+#define CATPT_ISD_IPCCB BIT(1)
+#define CATPT_ISD_IPCDD BIT(0)
+
+#define CATPT_IMC_IPCDB BIT(1)
+#define CATPT_IMC_IPCCD BIT(0)
+#define CATPT_IMD_IPCCB BIT(1)
+#define CATPT_IMD_IPCDD BIT(0)
+
+#define CATPT_IPCC_BUSY BIT(31)
+#define CATPT_IPCC_DONE BIT(30)
+#define CATPT_IPCD_BUSY BIT(31)
+#define CATPT_IPCD_DONE BIT(30)
+
+#define CATPT_CLKCTL_CFCIP BIT(31)
+#define CATPT_CLKCTL_SMOS GENMASK(25, 24)
+
+#define CATPT_HMDC_HDDA(e, ch) BIT(8 * (e) + (ch))
+
+/* defaults to reset SHIM registers to after each power cycle */
+#define CATPT_CS_DEFAULT 0x8480040E
+#define CATPT_ISC_DEFAULT 0x0
+#define CATPT_ISD_DEFAULT 0x0
+#define CATPT_IMC_DEFAULT 0x7FFF0003
+#define CATPT_IMD_DEFAULT 0x7FFF0003
+#define CATPT_IPCC_DEFAULT 0x0
+#define CATPT_IPCD_DEFAULT 0x0
+#define CATPT_CLKCTL_DEFAULT 0x7FF
+#define CATPT_CS2_DEFAULT 0x0
+#define CATPT_LTRC_DEFAULT 0x0
+#define CATPT_HMDC_DEFAULT 0x0
+
+/* PCI Configuration registers */
+
+#define CATPT_PCI_PMCAPID 0x80
+#define CATPT_PCI_PMCS (CATPT_PCI_PMCAPID + PCI_PM_CTRL)
+#define CATPT_PCI_VDRTCTL0 0xA0
+#define CATPT_PCI_VDRTCTL2 0xA8
+
+#define CATPT_VDRTCTL2_DTCGE BIT(10)
+#define CATPT_VDRTCTL2_DCLCGE BIT(1)
+#define CATPT_VDRTCTL2_CGEALL 0xF7F
+
+/* LPT PCI Configuration bits */
+
+#define LPT_VDRTCTL0_DSRAMPGE(b) BIT(16 + (b))
+#define LPT_VDRTCTL0_DSRAMPGE_MASK GENMASK(31, 16)
+#define LPT_VDRTCTL0_ISRAMPGE(b) BIT(6 + (b))
+#define LPT_VDRTCTL0_ISRAMPGE_MASK GENMASK(15, 6)
+#define LPT_VDRTCTL0_D3SRAMPGD BIT(2)
+#define LPT_VDRTCTL0_D3PGD BIT(1)
+#define LPT_VDRTCTL0_APLLSE BIT(0)
+
+/* WPT PCI Configuration bits */
+
+#define WPT_VDRTCTL0_DSRAMPGE(b) BIT(12 + (b))
+#define WPT_VDRTCTL0_DSRAMPGE_MASK GENMASK(31, 12)
+#define WPT_VDRTCTL0_ISRAMPGE(b) BIT(2 + (b))
+#define WPT_VDRTCTL0_ISRAMPGE_MASK GENMASK(11, 2)
+#define WPT_VDRTCTL0_D3SRAMPGD BIT(1)
+#define WPT_VDRTCTL0_D3PGD BIT(0)
+
+#define WPT_VDRTCTL2_APLLSE BIT(31)
+
+/* defaults to reset SSP registers to after each power cycle */
+#define CATPT_SSC0_DEFAULT 0x0
+#define CATPT_SSC1_DEFAULT 0x0
+#define CATPT_SSS_DEFAULT 0xF004
+#define CATPT_SSIT_DEFAULT 0x0
+#define CATPT_SSD_DEFAULT 0xC43893A3
+#define CATPT_SSTO_DEFAULT 0x0
+#define CATPT_SSPSP_DEFAULT 0x0
+#define CATPT_SSTSA_DEFAULT 0x0
+#define CATPT_SSRSA_DEFAULT 0x0
+#define CATPT_SSTSS_DEFAULT 0x0
+#define CATPT_SSCR2_DEFAULT 0x0
+#define CATPT_SSPSP2_DEFAULT 0x0
+
+/* Physically the same block, access address differs between host and dsp */
+#define CATPT_DSP_DRAM_OFFSET 0x400000
+#define catpt_to_host_offset(offset) ((offset) & ~(CATPT_DSP_DRAM_OFFSET))
+#define catpt_to_dsp_offset(offset) ((offset) | CATPT_DSP_DRAM_OFFSET)
+
+#define CATPT_MEMBLOCK_SIZE 0x8000
+#define catpt_num_dram(cdev) (hweight_long((cdev)->spec->dram_mask))
+#define catpt_num_iram(cdev) (hweight_long((cdev)->spec->iram_mask))
+#define catpt_dram_size(cdev) (catpt_num_dram(cdev) * CATPT_MEMBLOCK_SIZE)
+#define catpt_iram_size(cdev) (catpt_num_iram(cdev) * CATPT_MEMBLOCK_SIZE)
+
+/* registry I/O helpers */
+
+#define catpt_shim_addr(cdev) \
+ ((cdev)->lpe_ba + (cdev)->spec->host_shim_offset)
+#define catpt_dma_addr(cdev, dma) \
+ ((cdev)->lpe_ba + (cdev)->spec->host_dma_offset[dma])
+#define catpt_ssp_addr(cdev, ssp) \
+ ((cdev)->lpe_ba + (cdev)->spec->host_ssp_offset[ssp])
+#define catpt_inbox_addr(cdev) \
+ ((cdev)->lpe_ba + (cdev)->ipc.config.inbox_offset)
+#define catpt_outbox_addr(cdev) \
+ ((cdev)->lpe_ba + (cdev)->ipc.config.outbox_offset)
+
+#define catpt_writel_ssp(cdev, ssp, reg, val) \
+ writel(val, catpt_ssp_addr(cdev, ssp) + (reg))
+
+#define catpt_readl_shim(cdev, reg) \
+ readl(catpt_shim_addr(cdev) + CATPT_SHIM_##reg)
+#define catpt_writel_shim(cdev, reg, val) \
+ writel(val, catpt_shim_addr(cdev) + CATPT_SHIM_##reg)
+#define catpt_updatel_shim(cdev, reg, mask, val) \
+ catpt_writel_shim(cdev, reg, \
+ (catpt_readl_shim(cdev, reg) & ~(mask)) | (val))
+
+#define catpt_readl_poll_shim(cdev, reg, val, cond, delay_us, timeout_us) \
+ readl_poll_timeout(catpt_shim_addr(cdev) + CATPT_SHIM_##reg, \
+ val, cond, delay_us, timeout_us)
+
+#define catpt_readl_pci(cdev, reg) \
+ readl(cdev->pci_ba + CATPT_PCI_##reg)
+#define catpt_writel_pci(cdev, reg, val) \
+ writel(val, cdev->pci_ba + CATPT_PCI_##reg)
+#define catpt_updatel_pci(cdev, reg, mask, val) \
+ catpt_writel_pci(cdev, reg, \
+ (catpt_readl_pci(cdev, reg) & ~(mask)) | (val))
+
+#define catpt_readl_poll_pci(cdev, reg, val, cond, delay_us, timeout_us) \
+ readl_poll_timeout((cdev)->pci_ba + CATPT_PCI_##reg, \
+ val, cond, delay_us, timeout_us)
+
+#endif
diff --git a/sound/soc/intel/catpt/sysfs.c b/sound/soc/intel/catpt/sysfs.c
new file mode 100644
index 000000000..9579e233a
--- /dev/null
+++ b/sound/soc/intel/catpt/sysfs.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/pm_runtime.h>
+#include "core.h"
+
+static ssize_t fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+ struct catpt_fw_version version;
+ int ret;
+
+ pm_runtime_get_sync(cdev->dev);
+
+ ret = catpt_ipc_get_fw_version(cdev, &version);
+
+ pm_runtime_mark_last_busy(cdev->dev);
+ pm_runtime_put_autosuspend(cdev->dev);
+
+ if (ret)
+ return CATPT_IPC_ERROR(ret);
+
+ return sprintf(buf, "%d.%d.%d.%d\n", version.type, version.major,
+ version.minor, version.build);
+}
+static DEVICE_ATTR_RO(fw_version);
+
+static ssize_t fw_info_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct catpt_dev *cdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", cdev->ipc.config.fw_info);
+}
+static DEVICE_ATTR_RO(fw_info);
+
+static struct attribute *catpt_attrs[] = {
+ &dev_attr_fw_version.attr,
+ &dev_attr_fw_info.attr,
+ NULL
+};
+
+static const struct attribute_group catpt_attr_group = {
+ .attrs = catpt_attrs,
+};
+
+const struct attribute_group *catpt_attr_groups[] = {
+ &catpt_attr_group,
+ NULL
+};
diff --git a/sound/soc/intel/catpt/trace.h b/sound/soc/intel/catpt/trace.h
new file mode 100644
index 000000000..bb3d627db
--- /dev/null
+++ b/sound/soc/intel/catpt/trace.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ *
+ * Author: Cezary Rojewski <cezary.rojewski@intel.com>
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM intel_catpt
+
+#if !defined(__SND_SOC_INTEL_CATPT_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define __SND_SOC_INTEL_CATPT_TRACE_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+DECLARE_EVENT_CLASS(catpt_ipc_msg,
+
+ TP_PROTO(u32 header),
+
+ TP_ARGS(header),
+
+ TP_STRUCT__entry(
+ __field(u32, header)
+ ),
+
+ TP_fast_assign(
+ __entry->header = header;
+ ),
+
+ TP_printk("0x%08x", __entry->header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_irq,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_request,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_reply,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+DEFINE_EVENT(catpt_ipc_msg, catpt_ipc_notify,
+ TP_PROTO(u32 header),
+ TP_ARGS(header)
+);
+
+TRACE_EVENT_CONDITION(catpt_ipc_payload,
+
+ TP_PROTO(const u8 *data, size_t size),
+
+ TP_ARGS(data, size),
+
+ TP_CONDITION(data && size),
+
+ TP_STRUCT__entry(
+ __dynamic_array(u8, buf, size)
+ ),
+
+ TP_fast_assign(
+ memcpy(__get_dynamic_array(buf), data, size);
+ ),
+
+ TP_printk("%u byte(s)%s",
+ __get_dynamic_array_len(buf),
+ __print_hex_dump("", DUMP_PREFIX_NONE, 16, 4,
+ __get_dynamic_array(buf),
+ __get_dynamic_array_len(buf), false))
+);
+
+#endif /* __SND_SOC_INTEL_CATPT_TRACE_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE trace
+#include <trace/define_trace.h>