[alsa-devel] [PATCH 0/8] ASoC: Intel: sst - add the merrifield DSP driver
This path series add the Merrifield DSP driver which is used to load and managed the DSP along with sending and receiving IPCs for PCM and compressed audio
Vinod Koul (7): ASoC: Intel: add sst shim register start-end variables ASoC: Intel: mfld: add dsp error codes ASoC: Intel: mrlfd - add generic parameter interface ASoC: Intel: mrfld - add the dsp sst driver ASoC: Intel: sst: add power management handling ASoC: Intel: sst: load firmware using async callback ASoC: Intel: sst - add compressed ops handling
arch/x86/include/asm/platform_sst_audio.h | 33 + sound/soc/intel/Kconfig | 4 + sound/soc/intel/Makefile | 3 + sound/soc/intel/sst-dsp.h | 4 + sound/soc/intel/sst-mfld-dsp.h | 15 + sound/soc/intel/sst-mfld-platform.h | 5 +- sound/soc/intel/sst/Makefile | 3 + sound/soc/intel/sst/sst.c | 586 ++++++++++++++++++ sound/soc/intel/sst/sst.h | 666 ++++++++++++++++++++ sound/soc/intel/sst/sst_drv_interface.c | 819 +++++++++++++++++++++++++ sound/soc/intel/sst/sst_ipc.c | 368 +++++++++++ sound/soc/intel/sst/sst_loader.c | 951 +++++++++++++++++++++++++++++ sound/soc/intel/sst/sst_pvt.c | 208 +++++++ sound/soc/intel/sst/sst_stream.c | 534 ++++++++++++++++ 14 files changed, 4198 insertions(+), 1 deletions(-) create mode 100644 sound/soc/intel/sst/Makefile create mode 100644 sound/soc/intel/sst/sst.c create mode 100644 sound/soc/intel/sst/sst.h create mode 100644 sound/soc/intel/sst/sst_drv_interface.c create mode 100644 sound/soc/intel/sst/sst_ipc.c create mode 100644 sound/soc/intel/sst/sst_loader.c create mode 100644 sound/soc/intel/sst/sst_pvt.c create mode 100644 sound/soc/intel/sst/sst_stream.c
the shim registers start and end can be useful while parsing the shim addresses, so add these
Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/sst-dsp.h | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h index e44423b..967fb32 100644 --- a/sound/soc/intel/sst-dsp.h +++ b/sound/soc/intel/sst-dsp.h @@ -53,6 +53,10 @@ #define SST_CSR2 0x80 #define SST_LTRC 0xE0 #define SST_HDMC 0xE8 + +#define SST_SHIM_BEGIN SST_CSR +#define SST_SHIM_END SST_HDMC + #define SST_DBGO 0xF0
#define SST_SHIM_SIZE 0x100
DSP returns error codes for IPC return so add them in driver
Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/sst-mfld-dsp.h | 15 +++++++++++++++ 1 files changed, 15 insertions(+), 0 deletions(-)
diff --git a/sound/soc/intel/sst-mfld-dsp.h b/sound/soc/intel/sst-mfld-dsp.h index 2c88785..4257263 100644 --- a/sound/soc/intel/sst-mfld-dsp.h +++ b/sound/soc/intel/sst-mfld-dsp.h @@ -171,6 +171,21 @@ enum stream_type { SST_STREAM_TYPE_MUSIC = 1, };
+enum sst_error_codes { + /* Error code,response to msgId: Description */ + /* Common error codes */ + SST_SUCCESS = 0, /* Success */ + SST_ERR_INVALID_STREAM_ID = 1, + SST_ERR_INVALID_MSG_ID = 2, + SST_ERR_INVALID_STREAM_OP = 3, + SST_ERR_INVALID_PARAMS = 4, + SST_ERR_INVALID_CODEC = 5, + SST_ERR_INVALID_MEDIA_TYPE = 6, + SST_ERR_STREAM_ERR = 7, + + SST_ERR_STREAM_IN_USE = 15, +}; + struct ipc_dsp_hdr { u16 mod_index_id:8; /*!< DSP Command ID specific to tasks */ u16 pipe_id:8; /*!< instance of the module in the pipeline */
This interface will be used by subsequent patches to set/get parameters from DSP
Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/sst-mfld-platform.h | 5 ++++- 1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/sound/soc/intel/sst-mfld-platform.h b/sound/soc/intel/sst-mfld-platform.h index 9dc962f..6c6a42c 100644 --- a/sound/soc/intel/sst-mfld-platform.h +++ b/sound/soc/intel/sst-mfld-platform.h @@ -63,7 +63,9 @@ enum sst_controls { SST_SND_BUFFER_POINTER = 0x05, SST_SND_STREAM_INIT = 0x06, SST_SND_START = 0x07, - SST_MAX_CONTROLS = 0x07, + SST_SET_BYTE_STREAM = 0x100A, + SST_GET_BYTE_STREAM = 0x100B, + SST_MAX_CONTROLS = SST_GET_BYTE_STREAM, };
enum sst_stream_ops { @@ -127,6 +129,7 @@ struct compress_sst_ops { struct sst_ops { int (*open) (struct snd_sst_params *str_param); int (*device_control) (int cmd, void *arg); + int (*set_generic_params)(enum sst_controls cmd, void *arg); int (*close) (unsigned int str_id); };
On Wed, Jul 09, 2014 at 02:57:51PM +0530, Vinod Koul wrote:
This interface will be used by subsequent patches to set/get parameters from DSP
Applied, thanks. Please try to keep your subject lines consistent and word wrap your changelogs a bit more (they look like they go to exactly 80 columns which isn't good for replying).
On Mon, Jul 14, 2014 at 07:46:32PM +0100, Mark Brown wrote:
On Wed, Jul 09, 2014 at 02:57:51PM +0530, Vinod Koul wrote:
This interface will be used by subsequent patches to set/get parameters from DSP
Applied, thanks. Please try to keep your subject lines consistent and word wrap your changelogs a bit more (they look like they go to exactly 80 columns which isn't good for replying).
Yes set it to 80, i agree it cause issues while replying. What do you set, 76??
On Tue, Jul 15, 2014 at 10:55:55AM +0530, Vinod Koul wrote:
On Mon, Jul 14, 2014 at 07:46:32PM +0100, Mark Brown wrote:
Applied, thanks. Please try to keep your subject lines consistent and word wrap your changelogs a bit more (they look like they go to exactly 80 columns which isn't good for replying).
Yes set it to 80, i agree it cause issues while replying. What do you set, 76??
Something like that (I actually do it by eye a lot of the time).
The SST driver is the missing piece in our driver stack not upstreamed, so push it now :) This driver currently supports PCI device on Merrifield. Future updates will bring support for ACPI device as well as future update to PCI devices as well
This driver currently supports DSP loading suing memcpy, pcm operations and compressed ops (subsequent patch)
Signed-off-by: Vinod Koul vinod.koul@intel.com --- arch/x86/include/asm/platform_sst_audio.h | 33 + sound/soc/intel/Kconfig | 4 + sound/soc/intel/Makefile | 3 + sound/soc/intel/sst/Makefile | 3 + sound/soc/intel/sst/sst.c | 439 +++++++++++++ sound/soc/intel/sst/sst.h | 666 ++++++++++++++++++++ sound/soc/intel/sst/sst_drv_interface.c | 543 ++++++++++++++++ sound/soc/intel/sst/sst_ipc.c | 368 +++++++++++ sound/soc/intel/sst/sst_loader.c | 951 +++++++++++++++++++++++++++++ sound/soc/intel/sst/sst_pvt.c | 208 +++++++ sound/soc/intel/sst/sst_stream.c | 534 ++++++++++++++++ 11 files changed, 3752 insertions(+), 0 deletions(-) create mode 100644 sound/soc/intel/sst/Makefile create mode 100644 sound/soc/intel/sst/sst.c create mode 100644 sound/soc/intel/sst/sst.h create mode 100644 sound/soc/intel/sst/sst_drv_interface.c create mode 100644 sound/soc/intel/sst/sst_ipc.c create mode 100644 sound/soc/intel/sst/sst_loader.c create mode 100644 sound/soc/intel/sst/sst_pvt.c create mode 100644 sound/soc/intel/sst/sst_stream.c
diff --git a/arch/x86/include/asm/platform_sst_audio.h b/arch/x86/include/asm/platform_sst_audio.h index 0a4e140..565a617 100644 --- a/arch/x86/include/asm/platform_sst_audio.h +++ b/arch/x86/include/asm/platform_sst_audio.h @@ -16,6 +16,9 @@
#include <linux/sfi.h>
+#define MAX_NUM_STREAMS_MRFLD 25 +#define MAX_NUM_STREAMS MAX_NUM_STREAMS_MRFLD + enum sst_audio_task_id_mrfld { SST_TASK_ID_NONE = 0, SST_TASK_ID_SBA = 1, @@ -73,6 +76,36 @@ struct sst_platform_data { unsigned int strm_map_size; };
+struct sst_info { + u32 iram_start; + u32 iram_end; + bool iram_use; + u32 dram_start; + u32 dram_end; + bool dram_use; + u32 imr_start; + u32 imr_end; + bool imr_use; + u32 mailbox_start; + bool use_elf; + bool lpe_viewpt_rqd; + unsigned int max_streams; + u32 dma_max_len; + u8 num_probes; +}; + +struct sst_lib_dnld_info { + unsigned int mod_base; + unsigned int mod_end; + unsigned int mod_table_offset; + unsigned int mod_table_size; + bool mod_ddr_dnld; +}; + +struct sst_platform_info { + const struct sst_ipc_info *ipc_info; + const struct sst_lib_dnld_info *lib_info; +}; int add_sst_platform_device(void); #endif
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index c30fedb..e978a47 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -3,6 +3,7 @@ config SND_MFLD_MACHINE depends on INTEL_SCU_IPC select SND_SOC_SN95031 select SND_SST_MFLD_PLATFORM + select SND_SST_IPC help This adds support for ASoC machine driver for Intel(R) MID Medfield platform used as alsa device in audio substem in Intel(R) MID devices @@ -12,6 +13,9 @@ config SND_MFLD_MACHINE config SND_SST_MFLD_PLATFORM tristate
+config SND_SST_IPC + tristate + config SND_SOC_INTEL_SST tristate "ASoC support for Intel(R) Smart Sound Technology" select SND_SOC_INTEL_SST_ACPI if ACPI diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index 4bfca79..6f4cefe 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -28,3 +28,6 @@ snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o + +# DSP driver +obj-$(CONFIG_SND_SST_IPC) += sst/ diff --git a/sound/soc/intel/sst/Makefile b/sound/soc/intel/sst/Makefile new file mode 100644 index 0000000..4d0e79b --- /dev/null +++ b/sound/soc/intel/sst/Makefile @@ -0,0 +1,3 @@ +snd-intel-sst-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o + +obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst.o diff --git a/sound/soc/intel/sst/sst.c b/sound/soc/intel/sst/sst.c new file mode 100644 index 0000000..916b38c --- /dev/null +++ b/sound/soc/intel/sst/sst.c @@ -0,0 +1,439 @@ +/* + * sst.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R dharageswari.r@intel.com + * KP Jeeja jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <linux/async.h> +#include <linux/delay.h> +#include <linux/acpi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/intel-mid.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +MODULE_AUTHOR("Vinod Koul vinod.koul@intel.com"); +MODULE_AUTHOR("Harsha Priya priya.harsha@intel.com"); +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(SST_DRIVER_VERSION); + +struct intel_sst_drv *sst_drv_ctx; + +#define SST_IS_PROCESS_REPLY(header) ((header & PROCESS_MSG) ? true : false) +#define SST_VALIDATE_MAILBOX_SIZE(size) ((size <= SST_MAILBOX_SIZE) ? true : false) + +static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) +{ + union interrupt_reg_mrfld isr; + union ipc_header_mrfld header; + union sst_imr_reg_mrfld imr; + struct ipc_post *msg = NULL; + unsigned int size = 0; + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + irqreturn_t retval = IRQ_HANDLED; + + /* Interrupt arrived, check src */ + isr.full = sst_shim_read64(drv->shim, SST_ISRX); + if (isr.part.done_interrupt) { + /* Clear done bit */ + spin_lock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, + drv->ipc_reg.ipcx); + header.p.header_high.part.done = 0; + sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); + /* write 1 to clear status register */; + isr.part.done_interrupt = 1; + sst_shim_write64(drv->shim, SST_ISRX, isr.full); + spin_unlock(&drv->ipc_spin_lock); + queue_work(drv->post_msg_wq, &drv->ipc_post_msg.wq); + retval = IRQ_HANDLED; + } + if (isr.part.busy_interrupt) { + spin_lock(&drv->ipc_spin_lock); + imr.full = sst_shim_read64(drv->shim, SST_IMRX); + imr.part.busy_interrupt = 1; + sst_shim_write64(drv->shim, SST_IMRX, imr.full); + spin_unlock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); + if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { + pr_err("No memory available\n"); + drv->ops->clear_interrupt(); + return IRQ_HANDLED; + } + if (header.p.header_high.part.large) { + size = header.p.header_low_payload; + if (SST_VALIDATE_MAILBOX_SIZE(size)) { + memcpy_fromio(msg->mailbox_data, + drv->mailbox + drv->mailbox_recv_offset, size); + } else { + pr_err("Mailbox not copied, payload siz is: %u\n", size); + header.p.header_low_payload = 0; + } + } + msg->mrfld_header = header; + msg->is_process_reply = + SST_IS_PROCESS_REPLY(header.p.header_high.part.msg_id); + spin_lock(&drv->rx_msg_lock); + list_add_tail(&msg->node, &drv->rx_list); + spin_unlock(&drv->rx_msg_lock); + drv->ops->clear_interrupt(); + retval = IRQ_WAKE_THREAD; + } + return retval; +} + +static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) +{ + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + struct ipc_post *__msg, *msg = NULL; + unsigned long irq_flags; + + if (list_empty(&drv->rx_list)) + return IRQ_HANDLED; + + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { + + list_del(&msg->node); + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + if (msg->is_process_reply) + drv->ops->process_message(msg); + else + drv->ops->process_reply(msg); + + if (msg->is_large) + kfree(msg->mailbox_data); + kfree(msg); + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + } + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; +} + +static struct intel_sst_ops mrfld_ops = { + .interrupt = intel_sst_interrupt_mrfld, + .irq_thread = intel_sst_irq_thread_mrfld, + .clear_interrupt = intel_sst_clear_intr_mrfld, + .start = sst_start_mrfld, + .reset = intel_sst_reset_dsp_mrfld, + .post_message = sst_post_message_mrfld, + .sync_post_message = sst_sync_post_message_mrfld, + .process_reply = sst_process_reply_mrfld, + .alloc_stream = sst_alloc_stream_mrfld, + .post_download = sst_post_download_mrfld, +}; + +int sst_driver_ops(struct intel_sst_drv *sst) +{ + + switch (sst->pci_id) { + case SST_MRFLD_PCI_ID: + sst->tstamp = SST_TIME_STAMP_MRFLD; + sst->ops = &mrfld_ops; + return 0; + + default: + pr_err("SST Driver capablities missing for pci_id: %x", sst->pci_id); + return -EINVAL; + }; +} + +int sst_alloc_drv_context(struct device *dev) +{ + struct intel_sst_drv *ctx; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + pr_err("malloc fail\n"); + return -ENOMEM; + } + sst_drv_ctx = ctx; + return 0; +} + +/* +* intel_sst_probe - PCI probe function +* +* @pci: PCI device structure +* @pci_id: PCI device ID structure +* +* This function is called by OS when a device is found +* This enables the device, interrupt etc +*/ +static int intel_sst_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + int i, ret = 0; + struct intel_sst_ops *ops; + struct sst_platform_info *sst_pdata = pci->dev.platform_data; + int ddr_base; + + pr_debug("Probe for DID %x\n", pci->device); + ret = sst_alloc_drv_context(&pci->dev); + if (ret) + return ret; + + sst_drv_ctx->dev = &pci->dev; + sst_drv_ctx->pci_id = pci->device; + if (!sst_pdata) + return -EINVAL; + sst_drv_ctx->pdata = sst_pdata; + + if (0 != sst_driver_ops(sst_drv_ctx)) + return -EINVAL; + ops = sst_drv_ctx->ops; + mutex_init(&sst_drv_ctx->sst_lock); + + sst_drv_ctx->stream_cnt = 0; + sst_drv_ctx->fw_in_mem = NULL; + + /* we use dma, so set to 1*/ + sst_drv_ctx->use_dma = 1; + sst_drv_ctx->use_lli = 1; + + INIT_LIST_HEAD(&sst_drv_ctx->memcpy_list); + INIT_LIST_HEAD(&sst_drv_ctx->libmemcpy_list); + + INIT_LIST_HEAD(&sst_drv_ctx->ipc_dispatch_list); + INIT_LIST_HEAD(&sst_drv_ctx->block_list); + INIT_LIST_HEAD(&sst_drv_ctx->rx_list); + INIT_WORK(&sst_drv_ctx->ipc_post_msg.wq, ops->post_message); + init_waitqueue_head(&sst_drv_ctx->wait_queue); + + sst_drv_ctx->post_msg_wq = + create_singlethread_workqueue("sst_post_msg_wq"); + if (!sst_drv_ctx->post_msg_wq) { + ret = -EINVAL; + goto do_free_drv_ctx; + } + + spin_lock_init(&sst_drv_ctx->ipc_spin_lock); + spin_lock_init(&sst_drv_ctx->block_lock); + spin_lock_init(&sst_drv_ctx->rx_msg_lock); + + pr_info("Got drv data max stream %d\n", + sst_drv_ctx->info.max_streams); + for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) { + struct stream_info *stream = &sst_drv_ctx->streams[i]; + memset(stream, 0, sizeof(*stream)); + stream->pipe_id = PIPE_RSVD; + mutex_init(&stream->lock); + } + + /* Init the device */ + ret = pci_enable_device(pci); + if (ret) { + pr_err("device can't be enabled\n"); + goto do_free_mem; + } + sst_drv_ctx->pci = pci_dev_get(pci); + ret = pci_request_regions(pci, SST_DRV_NAME); + if (ret) + goto do_disable_device; + + /* map registers */ + /* SST Shim */ + if (sst_drv_ctx->pci_id == SST_MRFLD_PCI_ID) { + sst_drv_ctx->ddr_base = pci_resource_start(pci, 0); + /* + * check that the relocated IMR base matches with FW Binary + * put temporary check till better soln is available for FW + */ + ddr_base = relocate_imr_addr_mrfld(sst_drv_ctx->ddr_base); + if (!sst_drv_ctx->pdata->lib_info) { + pr_err("%s:lib_info pointer NULL\n", __func__); + ret = -EINVAL; + goto do_release_regions; + } + if (ddr_base != sst_drv_ctx->pdata->lib_info->mod_base) { + pr_err("FW LSP DDR BASE does not match with IFWI\n"); + ret = -EINVAL; + goto do_release_regions; + } + sst_drv_ctx->ddr_end = pci_resource_end(pci, 0); + + sst_drv_ctx->ddr = pci_ioremap_bar(pci, 0); + if (!sst_drv_ctx->ddr) { + ret = -EINVAL; + goto do_unmap_ddr; + } + pr_debug("sst: DDR Ptr %p\n", sst_drv_ctx->ddr); + } else { + sst_drv_ctx->ddr = NULL; + } + + /* SHIM */ + sst_drv_ctx->shim_phy_add = pci_resource_start(pci, 1); + sst_drv_ctx->shim = pci_ioremap_bar(pci, 1); + if (!sst_drv_ctx->shim) { + ret = -EINVAL; + goto do_release_regions; + } + pr_debug("SST Shim Ptr %p\n", sst_drv_ctx->shim); + + /* Shared SRAM */ + sst_drv_ctx->mailbox_add = pci_resource_start(pci, 2); + sst_drv_ctx->mailbox = pci_ioremap_bar(pci, 2); + if (!sst_drv_ctx->mailbox) { + ret = -EINVAL; + goto do_unmap_shim; + } + pr_debug("SRAM Ptr %p\n", sst_drv_ctx->mailbox); + + /* IRAM */ + sst_drv_ctx->iram_end = pci_resource_end(pci, 3); + sst_drv_ctx->iram_base = pci_resource_start(pci, 3); + sst_drv_ctx->iram = pci_ioremap_bar(pci, 3); + if (!sst_drv_ctx->iram) { + ret = -EINVAL; + goto do_unmap_sram; + } + pr_debug("IRAM Ptr %p\n", sst_drv_ctx->iram); + + /* DRAM */ + sst_drv_ctx->dram_end = pci_resource_end(pci, 4); + sst_drv_ctx->dram_base = pci_resource_start(pci, 4); + sst_drv_ctx->dram = pci_ioremap_bar(pci, 4); + if (!sst_drv_ctx->dram) { + ret = -EINVAL; + goto do_unmap_iram; + } + pr_debug("DRAM Ptr %p\n", sst_drv_ctx->dram); + + + sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); + sst_drv_ctx->irq_num = pci->irq; + /* Register the ISR */ + ret = devm_request_threaded_irq(&pci->dev, pci->irq, + sst_drv_ctx->ops->interrupt, + sst_drv_ctx->ops->irq_thread, 0, SST_DRV_NAME, + sst_drv_ctx); + if (ret) + goto do_unmap_dram; + pr_debug("Registered IRQ 0x%x\n", pci->irq); + + /* default intr are unmasked so set this as masked */ + if (sst_drv_ctx->pci_id == SST_MRFLD_PCI_ID) + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, 0xFFFF0038); + + pci_set_drvdata(pci, sst_drv_ctx); + pm_runtime_allow(sst_drv_ctx->dev); + pm_runtime_put_noidle(sst_drv_ctx->dev); + register_sst(sst_drv_ctx->dev); + sst_drv_ctx->qos = devm_kzalloc(&pci->dev, + sizeof(struct pm_qos_request), GFP_KERNEL); + if (!sst_drv_ctx->qos) { + ret = -ENOMEM; + goto do_unmap_dram; + } + pm_qos_add_request(sst_drv_ctx->qos, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + pr_info("%s successfully done!\n", __func__); + return ret; + +do_unmap_dram: + iounmap(sst_drv_ctx->dram); +do_unmap_iram: + iounmap(sst_drv_ctx->iram); +do_unmap_sram: + iounmap(sst_drv_ctx->mailbox); +do_unmap_shim: + iounmap(sst_drv_ctx->shim); +do_unmap_ddr: + if (sst_drv_ctx->ddr) + iounmap(sst_drv_ctx->ddr); +do_release_regions: + pci_release_regions(pci); +do_disable_device: + pci_disable_device(pci); +do_free_mem: + destroy_workqueue(sst_drv_ctx->post_msg_wq); +do_free_drv_ctx: + sst_drv_ctx = NULL; + pr_err("Probe failed with %d\n", ret); + return ret; +} + +/** +* intel_sst_remove - PCI remove function +* +* @pci: PCI device structure +* +* This function is called by OS when a device is unloaded +* This frees the interrupt etc +*/ +static void intel_sst_remove(struct pci_dev *pci) +{ + struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci); + + pm_runtime_get_noresume(sst_drv_ctx->dev); + pm_runtime_forbid(sst_drv_ctx->dev); + unregister_sst(sst_drv_ctx->dev); + pci_dev_put(sst_drv_ctx->pci); + sst_set_fw_state_locked(sst_drv_ctx, SST_SHUTDOWN); + + iounmap(sst_drv_ctx->dram); + iounmap(sst_drv_ctx->iram); + iounmap(sst_drv_ctx->mailbox); + iounmap(sst_drv_ctx->shim); + flush_scheduled_work(); + destroy_workqueue(sst_drv_ctx->post_msg_wq); + pm_qos_remove_request(sst_drv_ctx->qos); + kfree(sst_drv_ctx->fw_sg_list.src); + kfree(sst_drv_ctx->fw_sg_list.dst); + sst_drv_ctx->fw_sg_list.list_len = 0; + kfree(sst_drv_ctx->fw_in_mem); + sst_drv_ctx->fw_in_mem = NULL; + sst_memcpy_free_resources(); + sst_drv_ctx = NULL; + pci_release_regions(pci); + pci_disable_device(pci); + pci_set_drvdata(pci, NULL); +} + +/* PCI Routines */ +static struct pci_device_id intel_sst_ids[] = { + { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, + { 0, } +}; + +static struct pci_driver sst_driver = { + .name = SST_DRV_NAME, + .id_table = intel_sst_ids, + .probe = intel_sst_probe, + .remove = intel_sst_remove, +}; + +module_pci_driver(sst_driver); diff --git a/sound/soc/intel/sst/sst.h b/sound/soc/intel/sst/sst.h new file mode 100644 index 0000000..f6145d8 --- /dev/null +++ b/sound/soc/intel/sst/sst.h @@ -0,0 +1,666 @@ +/* + * sst.h - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R dharageswari.r@intel.com + * KP Jeeja jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Common private declarations for SST + */ +#ifndef __SST_H__ +#define __SST_H__ + +#include <linux/firmware.h> + +#define SST_DRIVER_VERSION "5.0.0" + +/* driver names */ +#define SST_DRV_NAME "intel_sst_driver" +#define SST_MRFLD_PCI_ID 0x119A + +#define SST_SUSPEND_DELAY 2000 +#define FW_CONTEXT_MEM (64*1024) +#define SST_ICCM_BOUNDARY 4 +#define SST_CONFIG_SSP_SIGN 0x7ffe8001 + +/* FIXME: All this info should come from platform data + * move this when the base framework is ready to pass + * platform data to SST driver + */ +#define MRFLD_FW_VIRTUAL_BASE 0xC0000000 +#define MRFLD_FW_DDR_BASE_OFFSET 0x0 +#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4 +#define MRFLD_FW_BSS_RESET_BIT 0 +extern struct intel_sst_drv *sst_drv_ctx; +enum sst_states { + SST_FW_LOADING = 1, + SST_FW_RUNNING, + SST_RESET, + SST_SHUTDOWN, +}; + +enum sst_algo_ops { + SST_SET_ALGO = 0, + SST_GET_ALGO = 1, +}; + +#define SST_BLOCK_TIMEOUT 1000 + +#define FW_SIGNATURE_SIZE 4 + +/* stream states */ +enum sst_stream_states { + STREAM_UN_INIT = 0, /* Freed/Not used stream */ + STREAM_RUNNING = 1, /* Running */ + STREAM_PAUSED = 2, /* Paused stream */ + STREAM_DECODE = 3, /* stream is in decoding only state */ + STREAM_INIT = 4, /* stream init, waiting for data */ + STREAM_RESET = 5, /* force reset on recovery */ +}; + +enum sst_ram_type { + SST_IRAM = 1, + SST_DRAM = 2, + SST_DDR = 5, + SST_CUSTOM_INFO = 7, /* consists of FW binary information */ +}; + +/* SST shim registers to structure mapping */ +union interrupt_reg { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_pisr_reg { + struct { + u32 pssp0:1; + u32 pssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:26; + } part; + u32 full; +}; + +union sst_pimr_reg { + struct { + u32 ssp0:1; + u32 ssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:10; + u32 ssp0_sc:1; + u32 ssp1_sc:1; + u32 rsvd2:3; + u32 dmac_sc:1; + u32 rsvd3:10; + } part; + u32 full; +}; + +union config_status_reg_mrfld { + struct { + u64 lpe_reset:1; + u64 lpe_reset_vector:1; + u64 runstall:1; + u64 pwaitmode:1; + u64 clk_sel:3; + u64 rsvd2:1; + u64 sst_clk:3; + u64 xt_snoop:1; + u64 rsvd3:4; + u64 clk_sel1:6; + u64 clk_enable:3; + u64 rsvd4:6; + u64 slim0baseclk:1; + u64 rsvd:32; + } part; + u64 full; +}; + +union interrupt_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_imr_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +/*This structure is used to block a user/fw data call to another +fw/user call +*/ +struct sst_block { + bool condition; /* condition for blocking check */ + int ret_code; /* ret code when block is released */ + void *data; /* data to be appsed for block if any */ + u32 size; + bool on; + u32 msg_id; /*msg_id = msgid in mfld/ctp, mrfld = 0 */ + u32 drv_id; /* = str_id in mfld/ctp, = drv_id in mrfld*/ + struct list_head node; +}; + +/** + * struct stream_info - structure that holds the stream information + * + * @status : stream current state + * @prev : stream prev state + * @ops : stream operation pb/cp/drm... + * @bufs: stream buffer list + * @lock : stream mutex for protecting state + * @pcm_substream : PCM substream + * @period_elapsed : PCM period elapsed callback + * @sfreq : stream sampling freq + * @str_type : stream type + * @cumm_bytes : cummulative bytes decoded + * @str_type : stream type + * @src : stream source + */ +struct stream_info { + unsigned int status; + unsigned int prev; + unsigned int ops; + struct mutex lock; /* mutex */ + + void *pcm_substream; + void (*period_elapsed)(void *pcm_substream); + + unsigned int sfreq; + u32 cumm_bytes; + + void *compr_cb_param; + void (*compr_cb)(void *compr_cb_param); + + void *drain_cb_param; + void (*drain_notify)(void *drain_cb_param); + + unsigned int num_ch; + unsigned int pipe_id; + unsigned int str_id; + unsigned int task_id; +}; + +#define SST_FW_SIGN "$SST" +#define SST_FW_LIB_SIGN "$LIB" + +/* + * struct fw_header - FW file headers + * + * @signature : FW signature + * @modules : # of modules + * @file_format : version of header format + * @reserved : reserved fields + */ +struct fw_header { + unsigned char signature[FW_SIGNATURE_SIZE]; /* FW signature */ + u32 file_size; /* size of fw minus this header */ + u32 modules; /* # of modules */ + u32 file_format; /* version of header format */ + u32 reserved[4]; +}; + +struct fw_module_header { + unsigned char signature[FW_SIGNATURE_SIZE]; /* module signature */ + u32 mod_size; /* size of module */ + u32 blocks; /* # of blocks */ + u32 type; /* codec type, pp lib */ + u32 entry_point; +}; + +struct fw_block_info { + enum sst_ram_type type; /* IRAM/DRAM */ + u32 size; /* Bytes */ + u32 ram_offset; /* Offset in I/DRAM */ + u32 rsvd; /* Reserved field */ +}; + +struct sst_ipc_msg_wq { + union ipc_header_mrfld mrfld_header; + struct ipc_dsp_hdr dsp_hdr; + char mailbox[SST_MAILBOX_SIZE]; + struct work_struct wq; + union ipc_header header; +}; + +struct sst_runtime_param { + struct snd_sst_runtime_params param; +}; + +struct sst_sg_list { + struct scatterlist *src; + struct scatterlist *dst; + int list_len; + unsigned int sg_idx; +}; + +struct sst_memcpy_list { + struct list_head memcpylist; + void *dstn; + const void *src; + u32 size; + bool is_io; +}; + +/* Firmware Module Information*/ +enum sst_lib_dwnld_status { + SST_LIB_NOT_FOUND = 0, + SST_LIB_FOUND, + SST_LIB_DOWNLOADED, +}; + +struct sst_module_info { + const char *name; /* Library name */ + u32 id; /* Module ID */ + u32 entry_pt; /* Module entry point */ + u8 status; /* module status*/ + u8 rsvd1; + u16 rsvd2; +}; + +/* Structure for managing the Library Region(1.5MB) + * in DDR in Merrifield + */ +struct sst_mem_mgr { + phys_addr_t current_base; + int avail; + unsigned int count; +}; + +struct sst_ipc_reg { + int ipcx; + int ipcd; +}; + +struct sst_shim_regs64 { + u64 csr; + u64 pisr; + u64 pimr; + u64 isrx; + u64 isrd; + u64 imrx; + u64 imrd; + u64 ipcx; + u64 ipcd; + u64 isrsc; + u64 isrlpesc; + u64 imrsc; + u64 imrlpesc; + u64 ipcsc; + u64 ipclpesc; + u64 clkctl; + u64 csr2; +}; + +/*** + * + * struct intel_sst_drv - driver ops + * + * @sst_state : current sst device state + * @pci_id : PCI device id loaded + * @shim : SST shim pointer + * @mailbox : SST mailbox pointer + * @iram : SST IRAM pointer + * @dram : SST DRAM pointer + * @pdata : SST info passed as a part of pci platform data + * @shim_phy_add : SST shim phy addr + * @shim_regs64: Struct to save shim registers + * @ipc_dispatch_list : ipc messages dispatched + * @rx_list : to copy the process_reply/process_msg from DSP + * @ipc_post_msg_wq : wq to post IPC messages context + * @ipc_post_msg : wq to post reply from FW context + * @mad_ops : MAD driver operations registered + * @mad_wq : MAD driver wq + * @post_msg_wq : wq to post IPC messages + * @streams : sst stream contexts + * @list_lock : sst driver list lock (deprecated) + * @ipc_spin_lock : spin lock to handle audio shim access and ipc queue + * @block_lock : spin lock to add block to block_list and assign pvt_id + * @rx_msg_lock : spin lock to handle the rx messages from the DSP + * @scard_ops : sst card ops + * @pci : sst pci device struture + * @dev : pointer to current device struct + * @sst_lock : sst device lock + * @pvt_id : sst private id + * @stream_cnt : total sst active stream count + * @pb_streams : total active pb streams + * @cp_streams : total active cp streams + * @audio_start : audio status + * @qos : PM Qos struct + * firmware_name : Firmware / Library name + */ +struct intel_sst_drv { + int sst_state; + int irq_num; + unsigned int pci_id; + void __iomem *ddr; + void __iomem *shim; + void __iomem *mailbox; + void __iomem *iram; + void __iomem *dram; + unsigned int mailbox_add; + unsigned int iram_base; + unsigned int dram_base; + unsigned int shim_phy_add; + unsigned int iram_end; + unsigned int dram_end; + unsigned int ddr_end; + unsigned int ddr_base; + unsigned int mailbox_recv_offset; + atomic_t pm_usage_count; + struct sst_shim_regs64 *shim_regs64; + struct list_head block_list; + struct list_head ipc_dispatch_list; + struct sst_platform_info *pdata; + struct sst_ipc_msg_wq ipc_post_msg; + struct list_head rx_list; + struct work_struct ipc_post_msg_wq; + wait_queue_head_t wait_queue; + struct workqueue_struct *post_msg_wq; + unsigned int tstamp; + struct stream_info streams[MAX_NUM_STREAMS+1]; /*str_id 0 is not used*/ + spinlock_t ipc_spin_lock; /* lock for Shim reg access and ipc queue */ + /* lock for adding block to block_list and assigning pvt_id */ + spinlock_t block_lock; + spinlock_t rx_msg_lock; + struct pci_dev *pci; + struct device *dev; + unsigned int pvt_id; + struct mutex sst_lock; + unsigned int stream_cnt; + unsigned int csr_value; + void *fw_in_mem; + struct sst_sg_list fw_sg_list, library_list; + struct intel_sst_ops *ops; + struct sst_info info; + struct pm_qos_request *qos; + unsigned int use_dma; + unsigned int use_lli; + atomic_t fw_clear_context; + atomic_t fw_clear_cache; + bool lib_dwnld_reqd; + struct list_head memcpy_list; + struct list_head libmemcpy_list; + struct sst_ipc_reg ipc_reg; + struct sst_mem_mgr lib_mem_mgr; + /* Holder for firmware name. Due to async call it needs to be + * persistent till worker thread gets called + */ + char firmware_name[20]; +}; + +extern struct intel_sst_drv *sst_drv_ctx; + +/* misc definitions */ +#define FW_DWNL_ID 0xFF + +struct intel_sst_ops { + irqreturn_t (*interrupt)(int, void *); + irqreturn_t (*irq_thread)(int, void *); + void (*clear_interrupt)(void); + int (*start)(void); + int (*reset)(void); + void (*process_reply)(struct ipc_post *msg); + void (*post_message)(struct work_struct *work); + int (*sync_post_message)(struct ipc_post *msg); + void (*process_message)(struct ipc_post *msg); + void (*set_bypass)(bool set); + int (*save_dsp_context)(struct intel_sst_drv *sst); + void (*restore_dsp_context)(void); + int (*alloc_stream)(char *params, struct sst_block *block); + void (*post_download)(struct intel_sst_drv *sst); +}; + +int sst_alloc_stream(char *params, struct sst_block *block); +int sst_pause_stream(int id); +int sst_resume_stream(int id); +int sst_drop_stream(int id); +int sst_next_track(void); +int sst_free_stream(int id); +int sst_start_stream(int str_id); +int sst_send_byte_stream_mrfld(void *sbytes); +int sst_set_stream_param(int str_id, struct snd_sst_params *str_param); +int sst_set_metadata(int str_id, char *params); +int sst_get_stream(struct snd_sst_params *str_param); +int sst_get_stream_allocated(struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld); +int sst_drain_stream(int str_id, bool partial_drain); + + +int sst_sync_post_message_mrfld(struct ipc_post *msg); +void sst_post_message_mrfld(struct work_struct *work); +void sst_process_reply_mrfld(struct ipc_post *msg); +int sst_start_mrfld(void); +int intel_sst_reset_dsp_mrfld(void); +void intel_sst_clear_intr_mrfld(void); + +int sst_load_fw(void); +int sst_load_library(struct snd_sst_lib_download *lib, u8 ops); +int sst_load_all_modules_elf(struct intel_sst_drv *ctx, + struct sst_module_info *mod_table, int mod_table_size); +int sst_get_next_lib_mem(struct sst_mem_mgr *mgr, int size, + unsigned long *lib_base); +void sst_post_download_mrfld(struct intel_sst_drv *ctx); +int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx); +void sst_memcpy_free_resources(void); + +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_create_ipc_msg(struct ipc_post **arg, bool large); +int sst_download_fw(void); +int free_stream_context(unsigned int str_id); +void sst_clean_stream(struct stream_info *stream); +int intel_sst_register_compress(struct intel_sst_drv *sst); +int intel_sst_remove_compress(struct intel_sst_drv *sst); +void sst_cdev_fragment_elapsed(int str_id); +int sst_send_sync_msg(int ipc, int str_id); +int sst_get_num_channel(struct snd_sst_params *str_param); +int sst_get_sfreq(struct snd_sst_params *str_param); +int intel_sst_check_device(void); +int sst_alloc_stream_ctp(char *params, struct sst_block *block); +int sst_alloc_stream_mrfld(char *params, struct sst_block *block); +void sst_restore_fw_context(void); +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id); +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id); +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed); +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size); +int sst_alloc_drv_context(struct device *dev); +int sst_request_firmware_async(struct intel_sst_drv *ctx); +int sst_driver_ops(struct intel_sst_drv *sst); +struct sst_platform_info *sst_get_acpi_driver_data(const char *hid); +void sst_firmware_load_cb(const struct firmware *fw, void *context); + +static inline int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{ + int ret; + + ret = pm_runtime_put_sync(sst_drv->dev); + if (ret < 0) + return ret; + atomic_dec(&sst_drv->pm_usage_count); + + pr_debug("%s: count is %d now..\n", __func__, + atomic_read(&sst_drv->pm_usage_count)); + return 0; +} + +static inline void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id) +{ + header->full = 0; + header->p.header_high.part.msg_id = msg; + header->p.header_high.part.task_id = task_id; + header->p.header_high.part.large = large; + header->p.header_high.part.drv_id = drv_id; + header->p.header_high.part.done = 0; + header->p.header_high.part.busy = 1; + header->p.header_high.part.res_rqd = 1; +} + +static inline void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len) +{ + dsp->cmd_id = msg; + dsp->mod_index_id = 0xff; + dsp->pipe_id = pipe_id; + dsp->length = len; + dsp->mod_id = 0; +} + +#define MAX_BLOCKS 15 +/* sst_assign_pvt_id - assign a pvt id for stream + * + * @sst_drv_ctx : driver context + * + * this inline function assigns a private id for calls that dont have stream + * context yet, should be called with lock held + */ +static inline unsigned int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx) +{ + unsigned int local; + + spin_lock(&sst_drv_ctx->block_lock); + sst_drv_ctx->pvt_id++; + if (sst_drv_ctx->pvt_id > MAX_BLOCKS) + sst_drv_ctx->pvt_id = 1; + local = sst_drv_ctx->pvt_id; + spin_unlock(&sst_drv_ctx->block_lock); + return local; +} + + +static inline void sst_init_stream(struct stream_info *stream, + int codec, int sst_id, int ops, u8 slot) +{ + stream->status = STREAM_INIT; + stream->prev = STREAM_UN_INIT; + stream->ops = ops; +} + +static inline int sst_validate_strid(int str_id) +{ + if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) { + pr_err("SST ERR: invalid stream id : %d, max %d\n", + str_id, sst_drv_ctx->info.max_streams); + return -EINVAL; + } else + return 0; +} + +static inline int sst_shim_write(void __iomem *addr, int offset, int value) +{ + writel(value, addr + offset); + return 0; +} + +static inline u32 sst_shim_read(void __iomem *addr, int offset) +{ + + return readl(addr + offset); +} + +static inline u64 sst_reg_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + + return val; +} + +static inline int sst_shim_write64(void __iomem *addr, int offset, u64 value) +{ + memcpy_toio(addr + offset, &value, sizeof(value)); + return 0; +} + +static inline u64 sst_shim_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + return val; +} + +static inline void +sst_set_fw_state_locked(struct intel_sst_drv *sst_drv_ctx, int sst_state) +{ + mutex_lock(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; + mutex_unlock(&sst_drv_ctx->sst_lock); +} + +static inline struct stream_info *get_stream_info(int str_id) +{ + if (sst_validate_strid(str_id)) + return NULL; + return &sst_drv_ctx->streams[str_id]; +} + +static inline int get_stream_id_mrfld(u32 pipe_id) +{ + int i; + + for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) + if (pipe_id == sst_drv_ctx->streams[i].pipe_id) + return i; + + pr_debug("%s: no such pipe_id(%u)", __func__, pipe_id); + return -1; +} + +int register_sst(struct device *); +int unregister_sst(struct device *); + +static inline u32 relocate_imr_addr_mrfld(u32 base_addr) +{ + /* Get the difference from 512MB aligned base addr */ + /* relocate the base */ + base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024)); + return base_addr; +} + +static inline void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags); + list_add_tail(&msg->node, &sst->ipc_dispatch_list); + spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags); + sst->ops->post_message(&sst->ipc_post_msg_wq); +} +#endif diff --git a/sound/soc/intel/sst/sst_drv_interface.c b/sound/soc/intel/sst/sst_drv_interface.c new file mode 100644 index 0000000..1ad3083 --- /dev/null +++ b/sound/soc/intel/sst/sst_drv_interface.c @@ -0,0 +1,543 @@ +/* + * sst_drv_interface.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R <dharageswari.r@intel.com) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <linux/math64.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + + + +#define NUM_CODEC 2 +#define MIN_FRAGMENT 2 +#define MAX_FRAGMENT 4 +#define MIN_FRAGMENT_SIZE (50 * 1024) +#define MAX_FRAGMENT_SIZE (1024 * 1024) +#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1) + +/* + * sst_download_fw - download the audio firmware to DSP + * + * This function is called when the FW needs to be downloaded to SST DSP engine + */ +int sst_download_fw(void) +{ + int retval = 0; + + retval = sst_load_fw(); + if (retval) + return retval; + pr_debug("fw loaded successful!!!\n"); + + if (sst_drv_ctx->ops->restore_dsp_context) + sst_drv_ctx->ops->restore_dsp_context(); + sst_drv_ctx->sst_state = SST_FW_RUNNING; + return retval; +} + +int free_stream_context(unsigned int str_id) +{ + struct stream_info *stream; + int ret = 0; + + stream = get_stream_info(str_id); + if (stream) { + /* str_id is valid, so stream is alloacted */ + ret = sst_free_stream(str_id); + if (ret) + sst_clean_stream(&sst_drv_ctx->streams[str_id]); + return ret; + } + return ret; +} + +/* + * sst_get_stream_allocated - this function gets a stream allocated with + * the given params + * + * @str_param : stream params + * @lib_dnld : pointer to pointer of lib downlaod struct + * + * This creates new stream id for a stream, in case lib is to be downloaded to + * DSP, it downloads that + */ +int sst_get_stream_allocated(struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld) +{ + int retval, str_id; + struct sst_block *block; + struct snd_sst_alloc_response *response; + struct stream_info *str_info; + + pr_debug("In %s\n", __func__); + block = sst_create_block(sst_drv_ctx, 0, 0); + if (block == NULL) + return -ENOMEM; + + retval = sst_drv_ctx->ops->alloc_stream((char *) str_param, block); + str_id = retval; + if (retval < 0) { + pr_err("sst_alloc_stream failed %d\n", retval); + goto free_block; + } + pr_debug("Stream allocated %d\n", retval); + str_info = get_stream_info(str_id); + if (str_info == NULL) { + pr_err("get stream info returned null\n"); + str_id = -EINVAL; + goto free_block; + } + + /* Block the call for reply */ + retval = sst_wait_timeout(sst_drv_ctx, block); + if (block->data) { + response = (struct snd_sst_alloc_response *)block->data; + retval = response->str_type.result; + if (!retval) + goto free_block; + + pr_err("sst: FW alloc failed retval %d\n", retval); + if (retval == SST_ERR_STREAM_IN_USE) { + pr_err("sst:FW not in clean state, send free for:%d\n", + str_id); + sst_free_stream(str_id); + *lib_dnld = NULL; + } else { + *lib_dnld = NULL; + } + str_id = -retval; + } else if (retval != 0) { + pr_err("sst: FW alloc failed retval %d\n", retval); + /* alloc failed, so reset the state to uninit */ + str_info->status = STREAM_UN_INIT; + str_id = retval; + } +free_block: + sst_free_block(sst_drv_ctx, block); + return str_id; /*will ret either error (in above if) or correct str id*/ +} + +/* + * sst_get_sfreq - this function returns the frequency of the stream + * + * @str_param : stream params + */ +int sst_get_sfreq(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.sfreq; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.externalsr; + case SST_CODEC_TYPE_MP3: + return 0; + default: + return -EINVAL; + } +} + +/* + * sst_get_sfreq - this function returns the frequency of the stream + * + * @str_param : stream params + */ +int sst_get_num_channel(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.num_chan; + case SST_CODEC_TYPE_MP3: + return str_param->sparams.uc.mp3_params.num_chan; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.num_chan; + default: + return -EINVAL; + } +} + +/* + * sst_get_stream - this function prepares for stream allocation + * + * @str_param : stream param + */ +int sst_get_stream(struct snd_sst_params *str_param) +{ + int retval; + struct stream_info *str_info; + struct snd_sst_lib_download *lib_dnld; + + pr_debug("In %s\n", __func__); + /* stream is not allocated, we are allocating */ + retval = sst_get_stream_allocated(str_param, &lib_dnld); + + if (retval <= 0) { + retval = -EIO; + goto err; + } + /* store sampling freq */ + str_info = &sst_drv_ctx->streams[retval]; + str_info->sfreq = sst_get_sfreq(str_param); + +err: + return retval; +} + +/** +* intel_sst_check_device - checks SST device +* +* This utility function checks the state of SST device and downlaods FW if +* not done, or resumes the device if suspended +*/ +int intel_sst_check_device(void) +{ + int retval = 0; + + pr_debug("In %s\n", __func__); + + pm_runtime_get_sync(sst_drv_ctx->dev); + atomic_inc(&sst_drv_ctx->pm_usage_count); + + pr_debug("%s: count is %d now\n", __func__, + atomic_read(&sst_drv_ctx->pm_usage_count)); + + mutex_lock(&sst_drv_ctx->sst_lock); + + if (sst_drv_ctx->sst_state == SST_RESET) { + + /* FW is not downloaded */ + pr_debug("DSP Downloading FW now...\n"); + retval = sst_download_fw(); + if (retval) { + pr_err("FW download fail %x\n", retval); + sst_drv_ctx->sst_state = SST_RESET; + mutex_unlock(&sst_drv_ctx->sst_lock); + sst_pm_runtime_put(sst_drv_ctx); + return retval; + } + } + mutex_unlock(&sst_drv_ctx->sst_lock); + return retval; +} + +/* + * sst_open_pcm_stream - Open PCM interface + * + * @str_param: parameters of pcm stream + * + * This function is called by MID sound card driver to open + * a new pcm interface + */ +static int sst_open_pcm_stream(struct snd_sst_params *str_param) +{ + int retval; + + if (!str_param) + return -EINVAL; + + pr_debug("%s: doing rtpm_get\n", __func__); + + retval = intel_sst_check_device(); + + if (retval) + return retval; + retval = sst_get_stream(str_param); + if (retval > 0) { + sst_drv_ctx->stream_cnt++; + } else { + pr_err("sst_get_stream returned err %d\n", retval); + sst_pm_runtime_put(sst_drv_ctx); + } + + return retval; +} + +/* + * sst_close_pcm_stream - Close PCM interface + * + * @str_id: stream id to be closed + * + * This function is called by MID sound card driver to close + * an existing pcm interface + */ +static int sst_close_pcm_stream(unsigned int str_id) +{ + struct stream_info *stream; + int retval = 0; + + pr_debug("%s: Entry\n", __func__); + stream = get_stream_info(str_id); + if (!stream) { + pr_err("stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + if (stream->status == STREAM_RESET) { + /* silently fail here as we have cleaned the stream */ + pr_debug("stream in reset state...\n"); + + retval = 0; + goto put; + } + + retval = free_stream_context(str_id); +put: + stream->pcm_substream = NULL; + stream->status = STREAM_UN_INIT; + stream->period_elapsed = NULL; + sst_drv_ctx->stream_cnt--; + + /* The free_stream will return a error if there is no stream to free, + (i.e. the alloc failure case). And in this case the open does a put in + the error scenario, so skip in this case. + In the close we need to handle put in the success scenario and + the timeout error(EBUSY) scenario. */ + if (!retval || (retval == -EBUSY)) + sst_pm_runtime_put(sst_drv_ctx); + else + pr_err("%s: free stream returned err %d\n", __func__, retval); + + pr_debug("%s: Exit\n", __func__); + return 0; +} + +static inline int sst_calc_tstamp(struct pcm_stream_info *info, + struct snd_pcm_substream *substream, + struct snd_sst_tstamp *fw_tstamp) +{ + size_t delay_bytes, delay_frames; + size_t buffer_sz; + u32 pointer_bytes, pointer_samples; + + pr_debug("mrfld ring_buffer_counter %llu in bytes\n", + fw_tstamp->ring_buffer_counter); + pr_debug("mrfld hardware_counter %llu in bytes\n", + fw_tstamp->hardware_counter); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter - + fw_tstamp->hardware_counter); + else + delay_bytes = (size_t) (fw_tstamp->hardware_counter - + fw_tstamp->ring_buffer_counter); + delay_frames = bytes_to_frames(substream->runtime, delay_bytes); + buffer_sz = snd_pcm_lib_buffer_bytes(substream); + div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes); + pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes); + + pr_debug("pcm delay %zu in bytes\n", delay_bytes); + + info->buffer_ptr = pointer_samples / substream->runtime->channels; + + info->pcm_delay = delay_frames / substream->runtime->channels; + pr_debug("buffer ptr %llu pcm_delay rep: %llu\n", + info->buffer_ptr, info->pcm_delay); + return 0; +} + +static int sst_read_timestamp(struct pcm_stream_info *info) +{ + struct stream_info *stream; + struct snd_pcm_substream *substream; + struct snd_sst_tstamp fw_tstamp; + unsigned int str_id; + + str_id = info->str_id; + stream = get_stream_info(str_id); + if (!stream) + return -EINVAL; + + if (!stream->pcm_substream) + return -EINVAL; + substream = stream->pcm_substream; + + memcpy_fromio(&fw_tstamp, + ((void *)(sst_drv_ctx->mailbox + sst_drv_ctx->tstamp) + + (str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + return sst_calc_tstamp(info, substream, &fw_tstamp); +} + +/* + * sst_device_control - Set Control params + * + * @cmd: control cmd to be set + * @arg: command argument + * + * This function is called by MID sound card driver to set + * SST/Sound card controls for an opened stream. + * This is registered with MID driver + */ +static int sst_device_control(int cmd, void *arg) +{ + int retval = 0, str_id = 0; + + if (sst_drv_ctx->sst_state != SST_FW_RUNNING) + return 0; + + switch (cmd) { + case SST_SND_START: { + struct stream_info *str_info; + int ipc; + + str_id = *(int *)arg; + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + ipc = IPC_IA_START_STREAM; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + sst_start_stream(str_id); + break; + } + case SST_SND_DROP: { + struct stream_info *str_info; + int ipc; + + str_id = *(int *)arg; + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + ipc = IPC_IA_DROP_STREAM; + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + retval = sst_drop_stream(str_id); + break; + } + case SST_SND_STREAM_INIT: { + struct pcm_stream_info *str_info; + struct stream_info *stream; + + pr_debug("stream init called\n"); + str_info = (struct pcm_stream_info *)arg; + str_id = str_info->str_id; + stream = get_stream_info(str_id); + if (!stream) { + retval = -EINVAL; + break; + } + pr_debug("setting the period ptrs\n"); + stream->pcm_substream = str_info->arg; + stream->period_elapsed = str_info->period_elapsed; + stream->sfreq = str_info->sfreq; + stream->prev = stream->status; + stream->status = STREAM_INIT; + pr_debug("pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n", + stream->pcm_substream, stream->period_elapsed, stream->sfreq, stream->status); + break; + } + + case SST_SND_BUFFER_POINTER: { + struct pcm_stream_info *stream_info; + + stream_info = (struct pcm_stream_info *)arg; + retval = sst_read_timestamp(stream_info); + pr_debug("pointer %llu, delay %llu\n", + stream_info->buffer_ptr, stream_info->pcm_delay); + break; + } + default: + /* Illegal case */ + pr_warn("illegal req\n"); + return -EINVAL; + } + + return retval; +} + +/* + * sst_set_generic_params - Set generic params + * + * @cmd: control cmd to be set + * @arg: command argument + * + * This function is called by MID sound card driver to configure + * SST runtime params. + */ +static int sst_set_generic_params(enum sst_controls cmd, void *arg) +{ + int ret_val = 0; + + if (NULL == arg) + return -EINVAL; + + switch (cmd) { + case SST_SET_BYTE_STREAM: { + ret_val = intel_sst_check_device(); + if (ret_val) + return ret_val; + + ret_val = sst_send_byte_stream_mrfld(arg); + sst_pm_runtime_put(sst_drv_ctx); + break; + } + default: + pr_err("Invalid cmd request:%d\n", cmd); + ret_val = -EINVAL; + } + return ret_val; +} + +static struct sst_ops pcm_ops = { + .open = sst_open_pcm_stream, + .device_control = sst_device_control, + .set_generic_params = sst_set_generic_params, + .close = sst_close_pcm_stream, +}; + +static struct sst_device sst_dsp_device = { + .name = "Intel(R) SST LPE", + .dev = NULL, + .ops = &pcm_ops, +}; + +/* + * register_sst - function to register DSP + * + * This functions registers DSP with the platform driver + */ +int register_sst(struct device *dev) +{ + int ret_val; + + sst_dsp_device.dev = dev; + ret_val = sst_register_dsp(&sst_dsp_device); + if (ret_val) + pr_err("Unable to register DSP with platform driver\n"); + + return ret_val; +} + +int unregister_sst(struct device *dev) +{ + return sst_unregister_dsp(&sst_dsp_device); +} diff --git a/sound/soc/intel/sst/sst_ipc.c b/sound/soc/intel/sst/sst_ipc.c new file mode 100644 index 0000000..7287524 --- /dev/null +++ b/sound/soc/intel/sst/sst_ipc.c @@ -0,0 +1,368 @@ +/* + * sst_ipc.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R dharageswari.r@intel.com + * KP Jeeja jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/pci.h> +#include <linux/firmware.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/intel-mid.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id) +{ + struct sst_block *msg = NULL; + + pr_debug("in %s\n", __func__); + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) { + pr_err("kzalloc block failed\n"); + return NULL; + } + msg->condition = false; + msg->on = true; + msg->msg_id = msg_id; + msg->drv_id = drv_id; + spin_lock_bh(&ctx->block_lock); + list_add_tail(&msg->node, &ctx->block_list); + spin_unlock_bh(&ctx->block_lock); + + return msg; +} + +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size) +{ + struct sst_block *block = NULL; + + pr_debug("in %s\n", __func__); + spin_lock_bh(&ctx->block_lock); + list_for_each_entry(block, &ctx->block_list, node) { + pr_debug("Block ipc %d, drv_id %d\n", block->msg_id, + block->drv_id); + if (block->msg_id == ipc && block->drv_id == drv_id) { + pr_debug("free up the block\n"); + block->ret_code = result; + block->data = data; + block->size = size; + block->condition = true; + spin_unlock_bh(&ctx->block_lock); + wake_up(&ctx->wait_queue); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + pr_debug("Block not found or a response is received for a short message for ipc %d, drv_id %d\n", + ipc, drv_id); + return -EINVAL; +} + +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) +{ + struct sst_block *block = NULL, *__block; + + pr_debug("in %s\n", __func__); + spin_lock_bh(&ctx->block_lock); + list_for_each_entry_safe(block, __block, &ctx->block_list, node) { + if (block == freed) { + list_del(&freed->node); + kfree(freed->data); + freed->data = NULL; + kfree(freed); + spin_unlock_bh(&ctx->block_lock); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + return -EINVAL; +} + +void sst_post_message_mrfld(struct work_struct *work) +{ + struct ipc_post *msg; + union ipc_header_mrfld header; + unsigned long irq_flags; + + pr_debug("Enter:%s\n", __func__); + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + /* check list */ + if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { + /* queue is empty, nothing to send */ + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + pr_debug("Empty msg queue... NO Action\n"); + return; + } + + /* check busy bit */ + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + if (header.p.header_high.part.busy) { + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + pr_debug("Busy not free... post later\n"); + return; + } + /* copy msg from list */ + msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, + struct ipc_post, node); + list_del(&msg->node); + pr_debug("sst: size: = %x\n", msg->mrfld_header.p.header_low_payload); + if (msg->mrfld_header.p.header_high.part.large) + memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, + msg->mailbox_data, msg->mrfld_header.p.header_low_payload); + + sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + pr_debug("sst: Post message: header = %x\n", + msg->mrfld_header.p.header_high.full); + kfree(msg->mailbox_data); + kfree(msg); +} + +int sst_sync_post_message_mrfld(struct ipc_post *msg) +{ + union ipc_header_mrfld header; + unsigned int loop_count = 0; + int retval = 0; + unsigned long irq_flags; + + pr_debug("Enter:%s\n", __func__); + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + + /* check busy bit */ + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + while (header.p.header_high.part.busy) { + if (loop_count > 10) { + pr_err("sst: Busy wait failed, cant send this msg\n"); + retval = -EBUSY; + goto out; + } + udelay(500); + loop_count++; + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + } + pr_debug("sst: Post message: header = %x\n", + msg->mrfld_header.p.header_high.full); + pr_debug("sst: size = 0x%x\n", msg->mrfld_header.p.header_low_payload); + if (msg->mrfld_header.p.header_high.part.large) + memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, + msg->mailbox_data, msg->mrfld_header.p.header_low_payload); + + sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); + +out: + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + kfree(msg->mailbox_data); + kfree(msg); + return retval; +} + +void intel_sst_clear_intr_mrfld(void) +{ + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; + unsigned long irq_flags; + + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + + /* write 1 to clear */ + isr.part.busy_interrupt = 1; + sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); + + /* Set IA done bit */ + clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); + + clear_ipc.p.header_high.part.busy = 0; + clear_ipc.p.header_high.part.done = 1; + clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; + sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); +} + + +/* + * process_fw_init - process the FW init msg + * + * @msg: IPC message mailbox data from FW + * + * This function processes the FW init msg from FW + * marks FW state and prints debug info of loaded FW + */ +static void process_fw_init(void *msg) +{ + struct ipc_header_fw_init *init = + (struct ipc_header_fw_init *)msg; + int retval = 0; + + pr_debug("*** FW Init msg came***\n"); + if (init->result) { + sst_drv_ctx->sst_state = SST_RESET; + pr_debug("FW Init failed, Error %x\n", init->result); + pr_err("FW Init failed, Error %x\n", init->result); + retval = init->result; + goto ret; + } + pr_info("FW Version %02x.%02x.%02x.%02x\n", + init->fw_version.type, init->fw_version.major, + init->fw_version.minor, init->fw_version.build); + pr_info("Build date %s Time %s\n", + init->build_info.date, init->build_info.time); + +ret: + sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); +} + +static void process_fw_async_msg(struct ipc_post *msg) +{ + u32 msg_id; + int str_id; + u32 data_size, i; + void *data_offset; + struct stream_info *stream; + union ipc_header_high msg_high; + u32 msg_low, pipe_id; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; + data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); + data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); + + switch (msg_id) { + case IPC_SST_PERIOD_ELAPSED_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(pipe_id); + if (str_id > 0) { + pr_debug("Period elapsed rcvd for pipe id 0x%x\n", pipe_id); + stream = &sst_drv_ctx->streams[str_id]; + if (stream->period_elapsed) + stream->period_elapsed(stream->pcm_substream); + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); + } + break; + + case IPC_IA_DRAIN_STREAM_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(pipe_id); + if (str_id > 0) { + stream = &sst_drv_ctx->streams[str_id]; + if (stream->drain_notify) + stream->drain_notify(stream->drain_cb_param); + } + break; + + case IPC_IA_FW_ASYNC_ERR_MRFLD: + pr_err("FW sent async error msg:\n"); + for (i = 0; i < (data_size/4); i++) + pr_err("0x%x\n", (*((unsigned int *)data_offset + i))); + break; + + case IPC_IA_FW_INIT_CMPLT_MRFLD: + process_fw_init(data_offset); + break; + + case IPC_IA_BUF_UNDER_RUN_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(pipe_id); + if (str_id > 0) + pr_err("Buffer under-run for pipe:%#x str_id:%d\n", + pipe_id, str_id); + break; + + default: + pr_err("Unrecognized async msg from FW msg_id %#x\n", msg_id); + } +} + +void sst_process_reply_mrfld(struct ipc_post *msg) +{ + unsigned int drv_id; + void *data; + union ipc_header_high msg_high; + u32 msg_low; + struct ipc_dsp_hdr *dsp_hdr; + unsigned int cmd_id; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + + pr_debug("IPC process message header %x payload %x\n", + msg->mrfld_header.p.header_high.full, + msg->mrfld_header.p.header_low_payload); + + drv_id = msg_high.part.drv_id; + + /* Check for async messages */ + if (drv_id == SST_ASYNC_DRV_ID) { + /* FW sent async large message */ + process_fw_async_msg(msg); + return; + } + + /* FW sent short error response for an IPC */ + if (msg_high.part.result && drv_id && !msg_high.part.large) { + /* 32-bit FW error code in msg_low */ + pr_err("FW sent error response 0x%x", msg_low); + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + return; + } + + /* Process all valid responses */ + /* if it is a large message, the payload contains the size to + * copy from mailbox */ + if (msg_high.part.large) { + data = kzalloc(msg_low, GFP_KERNEL); + if (!data) + return; + memcpy(data, (void *) msg->mailbox_data, msg_low); + /* Copy command id so that we can use to put sst to reset */ + dsp_hdr = (struct ipc_dsp_hdr *)data; + cmd_id = dsp_hdr->cmd_id; + pr_debug("cmd_id %d\n", dsp_hdr->cmd_id); + if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, data, msg_low)) + kfree(data); + } else { + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + } + +} diff --git a/sound/soc/intel/sst/sst_loader.c b/sound/soc/intel/sst/sst_loader.c new file mode 100644 index 0000000..35e7e6b --- /dev/null +++ b/sound/soc/intel/sst/sst_loader.c @@ -0,0 +1,951 @@ +/* + * sst_dsp.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R dharageswari.r@intel.com + * KP Jeeja jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file contains all dsp controlling functions like firmware download, + * setting/resetting dsp cores, etc + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/firmware.h> +#include <linux/dmaengine.h> +#include <linux/pm_runtime.h> +#include <linux/pm_qos.h> +#include <linux/elf.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +static struct sst_module_info sst_modules_mrfld[] = { + {"mp3_dec", SST_CODEC_TYPE_MP3, 0, SST_LIB_NOT_FOUND}, + {"aac_dec", SST_CODEC_TYPE_AAC, 0, SST_LIB_NOT_FOUND}, +}; + +/** + * intel_sst_reset_dsp_mrfld - Resetting SST DSP + * + * This resets DSP in case of MRFLD platfroms + */ +int intel_sst_reset_dsp_mrfld(void) +{ + union config_status_reg_mrfld csr; + + pr_debug("sst: Resetting the DSP in mrfld\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + pr_debug("value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + pr_debug("value:0x%llx\n", csr.full); + + csr.full &= ~(0x1); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + pr_debug("value:0x%llx\n", csr.full); + return 0; +} + +/** + * sst_start_merrifield - Start the SST DSP processor + * + * This starts the DSP in MERRIFIELD platfroms + */ +int sst_start_mrfld(void) +{ + union config_status_reg_mrfld csr; + + pr_debug("sst: Starting the DSP in mrfld LALALALA\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + pr_debug("value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + pr_debug("value:0x%llx\n", csr.full); + + csr.part.xt_snoop = 1; + csr.full &= ~(0x5); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + pr_debug("sst: Starting the DSP_merrifield:%llx\n", csr.full); + return 0; +} + +#define SST_CALC_DMA_DSTN(lpe_viewpt_rqd, ia_viewpt_addr, elf_paddr, \ + lpe_viewpt_addr) ((lpe_viewpt_rqd) ? \ + elf_paddr : (ia_viewpt_addr + elf_paddr - lpe_viewpt_addr)) + +static int sst_fill_dstn(struct intel_sst_drv *sst, struct sst_info info, + Elf32_Phdr *pr, void **dstn, unsigned int *dstn_phys, int *mem_type) +{ + /* work arnd-since only 4 byte align copying is only allowed for ICCM */ + if ((pr->p_paddr >= info.iram_start) && (pr->p_paddr < info.iram_end)) { + size_t data_size = pr->p_filesz % SST_ICCM_BOUNDARY; + + if (data_size) + pr->p_filesz += 4 - data_size; + *dstn = sst->iram + (pr->p_paddr - info.iram_start); + *dstn_phys = SST_CALC_DMA_DSTN(info.lpe_viewpt_rqd, + sst->iram_base, pr->p_paddr, info.iram_start); + *mem_type = 1; + } else if ((pr->p_paddr >= info.dram_start) && + (pr->p_paddr < info.dram_end)) { + + *dstn = sst->dram + (pr->p_paddr - info.dram_start); + *dstn_phys = SST_CALC_DMA_DSTN(info.lpe_viewpt_rqd, + sst->dram_base, pr->p_paddr, info.dram_start); + *mem_type = 1; + } else if ((pr->p_paddr >= info.imr_start) && + (pr->p_paddr < info.imr_end)) { + + *dstn = sst->ddr + (pr->p_paddr - info.imr_start); + *dstn_phys = sst->ddr_base + pr->p_paddr - info.imr_start; + *mem_type = 0; + } else { + return -EINVAL; + } + return 0; +} + +static void sst_fill_info(struct intel_sst_drv *sst, + struct sst_info *info) +{ + /* first we setup addresses to be used for elf sections */ + if (sst->info.iram_use) { + info->iram_start = sst->info.iram_start; + info->iram_end = sst->info.iram_end; + } else { + info->iram_start = sst->iram_base; + info->iram_end = sst->iram_end; + } + if (sst->info.dram_use) { + info->dram_start = sst->info.dram_start; + info->dram_end = sst->info.dram_end; + } else { + info->dram_start = sst->dram_base; + info->dram_end = sst->dram_end; + } + if (sst->info.imr_use) { + info->imr_start = sst->info.imr_start; + info->imr_end = sst->info.imr_end; + } else { + info->imr_start = relocate_imr_addr_mrfld(sst->ddr_base); + info->imr_end = relocate_imr_addr_mrfld(sst->ddr_end); + } + + info->lpe_viewpt_rqd = sst->info.lpe_viewpt_rqd; + info->dma_max_len = sst->info.dma_max_len; + pr_debug("%s: dma_max_len 0x%x", __func__, info->dma_max_len); +} + +static inline int sst_validate_elf(const struct firmware *sst_bin, bool dynamic) +{ + Elf32_Ehdr *elf; + + BUG_ON(!sst_bin); + + pr_debug("IN %s\n", __func__); + + elf = (Elf32_Ehdr *)sst_bin->data; + + if ((elf->e_ident[0] != 0x7F) || (elf->e_ident[1] != 'E') || + (elf->e_ident[2] != 'L') || (elf->e_ident[3] != 'F')) { + pr_debug("ELF Header Not found!%zu\n", sst_bin->size); + return -EINVAL; + } + + if (dynamic == true) { + if (elf->e_type != ET_DYN) { + pr_err("Not a dynamic loadable library\n"); + return -EINVAL; + } + } + pr_debug("Valid ELF Header...%zu\n", sst_bin->size); + return 0; +} + +static int sst_validate_fw_image(const void *sst_fw_in_mem, unsigned long size, + struct fw_module_header **module, u32 *num_modules) +{ + struct fw_header *header; + + pr_debug("%s\n", __func__); + + /* Read the header information from the data pointer */ + header = (struct fw_header *)sst_fw_in_mem; + pr_debug("header sign=%s size=%x modules=%x fmt=%x size=%zx\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* verify FW */ + if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) || + (size != header->file_size + sizeof(*header))) { + /* Invalid FW signature */ + pr_err("InvalidFW sign/filesize mismatch\n"); + return -EINVAL; + } + *num_modules = header->modules; + *module = (void *)sst_fw_in_mem + sizeof(*header); + + return 0; +} + +/* + * sst_fill_memcpy_list - Fill the memcpy list + * + * @memcpy_list: List to be filled + * @destn: Destination addr to be filled in the list + * @src: Source addr to be filled in the list + * @size: Size to be filled in the list + * + * Adds the node to the list after required fields + * are populated in the node + */ + +static int sst_fill_memcpy_list(struct list_head *memcpy_list, + void *destn, const void *src, u32 size, bool is_io) +{ + struct sst_memcpy_list *listnode; + + listnode = kzalloc(sizeof(*listnode), GFP_KERNEL); + if (listnode == NULL) + return -ENOMEM; + listnode->dstn = destn; + listnode->src = src; + listnode->size = size; + listnode->is_io = is_io; + list_add_tail(&listnode->memcpylist, memcpy_list); + + return 0; +} + +static int sst_parse_elf_module_memcpy(struct intel_sst_drv *sst, + const void *fw, struct sst_info info, Elf32_Phdr *pr, + struct list_head *memcpy_list) +{ + void *dstn; + unsigned int dstn_phys; + int ret_val = 0; + int mem_type; + + ret_val = sst_fill_dstn(sst, info, pr, &dstn, &dstn_phys, &mem_type); + if (ret_val) + return ret_val; + + ret_val = sst_fill_memcpy_list(memcpy_list, dstn, + (void *)fw + pr->p_offset, pr->p_filesz, mem_type); + if (ret_val) + return ret_val; + + return 0; +} + +static int +sst_parse_elf_fw_memcpy(struct intel_sst_drv *sst, const void *fw_in_mem, + struct list_head *memcpy_list) +{ + int i = 0; + + Elf32_Ehdr *elf; + Elf32_Phdr *pr; + struct sst_info info; + + BUG_ON(!fw_in_mem); + + elf = (Elf32_Ehdr *)fw_in_mem; + pr = (Elf32_Phdr *) (fw_in_mem + elf->e_phoff); + pr_debug("%s entry\n", __func__); + + sst_fill_info(sst, &info); + + while (i < elf->e_phnum) { + if (pr[i].p_type == PT_LOAD) + sst_parse_elf_module_memcpy(sst, fw_in_mem, info, + &pr[i], memcpy_list); + i++; + } + return 0; +} + +/** + * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list + * + * @module : FW module header + * @memcpy_list : Pointer to the list to be populated + * Create the memcpy list as the number of block to be copied + * returns error or 0 if module sizes are proper + */ +static int sst_parse_module_memcpy(struct fw_module_header *module, + struct list_head *memcpy_list) +{ + struct fw_block_info *block; + u32 count; + int ret_val = 0; + void __iomem *ram_iomem; + + pr_debug("module sign %s size %x blocks %x type %x\n", + module->signature, module->mod_size, + module->blocks, module->type); + pr_debug("module entrypoint 0x%x\n", module->entry_point); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + if (block->size <= 0) { + pr_err("block size invalid\n"); + return -EINVAL; + } + switch (block->type) { + case SST_IRAM: + ram_iomem = sst_drv_ctx->iram; + break; + case SST_DRAM: + ram_iomem = sst_drv_ctx->dram; + break; + case SST_DDR: + ram_iomem = sst_drv_ctx->ddr; + break; + case SST_CUSTOM_INFO: + block = (void *)block + sizeof(*block) + block->size; + continue; + default: + pr_err("wrong ram type0x%x in block0x%x\n", + block->type, count); + return -EINVAL; + } + + ret_val = sst_fill_memcpy_list(memcpy_list, + ram_iomem + block->ram_offset, + (void *)block + sizeof(*block), block->size, 1); + if (ret_val) + return ret_val; + + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +/** + * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy + * + * @sst_fw_in_mem : pointer to audio fw + * @size : size of the firmware + * @fw_list : pointer to list_head to be populated + * This function parses the FW image and saves the parsed image in the list + * for memcpy + */ +static int sst_parse_fw_memcpy(const void *sst_fw_in_mem, unsigned long size, + struct list_head *fw_list) +{ + struct fw_module_header *module; + u32 count, num_modules; + int ret_val; + + ret_val = sst_validate_fw_image(sst_fw_in_mem, size, + &module, &num_modules); + if (ret_val) + return ret_val; + + for (count = 0; count < num_modules; count++) { + /* module */ + ret_val = sst_parse_module_memcpy(module, fw_list); + if (ret_val) + return ret_val; + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +/** + * sst_do_memcpy - function initiates the memcpy + * + * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated + * + * Triggers the memcpy + */ +static void sst_do_memcpy(struct list_head *memcpy_list) +{ + struct sst_memcpy_list *listnode; + + list_for_each_entry(listnode, memcpy_list, memcpylist) { + if (listnode->is_io == true) + memcpy_toio((void __iomem *)listnode->dstn, listnode->src, + listnode->size); + else + memcpy(listnode->dstn, listnode->src, listnode->size); + } +} + +static void sst_memcpy_free_lib_resources(void) +{ + struct sst_memcpy_list *listnode, *tmplistnode; + + pr_debug("entry:%s\n", __func__); + + /*Free the list*/ + if (!list_empty(&sst_drv_ctx->libmemcpy_list)) { + list_for_each_entry_safe(listnode, tmplistnode, + &sst_drv_ctx->libmemcpy_list, memcpylist) { + list_del(&listnode->memcpylist); + kfree(listnode); + } + } +} + +void sst_memcpy_free_resources(void) +{ + struct sst_memcpy_list *listnode, *tmplistnode; + + pr_debug("entry:%s\n", __func__); + + /*Free the list*/ + if (!list_empty(&sst_drv_ctx->memcpy_list)) { + list_for_each_entry_safe(listnode, tmplistnode, + &sst_drv_ctx->memcpy_list, memcpylist) { + list_del(&listnode->memcpylist); + kfree(listnode); + } + } + sst_memcpy_free_lib_resources(); +} + +void sst_firmware_load_cb(const struct firmware *fw, void *context) +{ + struct intel_sst_drv *ctx = context; + int ret = 0; + + pr_debug("In %s\n", __func__); + + if (fw == NULL) { + pr_err("request fw failed\n"); + return; + } + + mutex_lock(&sst_drv_ctx->sst_lock); + + if (sst_drv_ctx->sst_state != SST_RESET || + ctx->fw_in_mem != NULL) + goto out; + + pr_debug("Request Fw completed\n"); + + if (ctx->info.use_elf == true) + ret = sst_validate_elf(fw, false); + + if (ret != 0) { + pr_err("FW image invalid...\n"); + goto out; + } + + ctx->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); + if (!ctx->fw_in_mem) { + pr_err("%s unable to allocate memory\n", __func__); + goto out; + } + + pr_debug("copied fw to %p", ctx->fw_in_mem); + pr_debug("phys: %lx", (unsigned long)virt_to_phys(ctx->fw_in_mem)); + memcpy(ctx->fw_in_mem, fw->data, fw->size); + + if (ctx->info.use_elf == true) + ret = sst_parse_elf_fw_memcpy(ctx, ctx->fw_in_mem, + &ctx->memcpy_list); + else + ret = sst_parse_fw_memcpy(ctx->fw_in_mem, fw->size, + &ctx->memcpy_list); + if (ret) { + kfree(ctx->fw_in_mem); + ctx->fw_in_mem = NULL; + goto out; + } + /* If static module download(download at boot time) is supported, + * set the flag to indicate lib download is to be done + */ + if (ctx->pdata->lib_info) + if (ctx->pdata->lib_info->mod_ddr_dnld) + ctx->lib_dwnld_reqd = true; + +out: + mutex_unlock(&sst_drv_ctx->sst_lock); + if (fw != NULL) + release_firmware(fw); +} + +/* + * sst_request_fw - requests audio fw from kernel and saves a copy + * + * This function requests the SST FW from the kernel, parses it and + * saves a copy in the driver context + */ +static int sst_request_fw(struct intel_sst_drv *sst) +{ + int retval = 0; + char name[20]; + const struct firmware *fw; + + snprintf(name, sizeof(name), "%s%04x%s", "fw_sst_", + sst->pci_id, ".bin"); + pr_debug("Requesting FW %s now...\n", name); + + retval = request_firmware(&fw, name, sst->dev); + if (fw == NULL) { + pr_err("fw is returning as null\n"); + return -EINVAL; + } + if (retval) { + pr_err("request fw failed %d\n", retval); + return retval; + } + if (sst->info.use_elf == true) + retval = sst_validate_elf(fw, false); + if (retval != 0) { + pr_err("FW image invalid...\n"); + goto end_release; + } + sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); + if (!sst->fw_in_mem) { + pr_err("%s unable to allocate memory\n", __func__); + retval = -ENOMEM; + goto end_release; + } + pr_debug("copied fw to %p", sst->fw_in_mem); + pr_debug("phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem)); + memcpy(sst->fw_in_mem, fw->data, fw->size); + if (sst->info.use_elf == true) + retval = sst_parse_elf_fw_memcpy(sst, sst->fw_in_mem, + &sst->memcpy_list); + else + retval = sst_parse_fw_memcpy(sst->fw_in_mem, fw->size, + &sst->memcpy_list); + if (retval) { + kfree(sst->fw_in_mem); + sst->fw_in_mem = NULL; + } + + /* If static module download(download at boot time) is supported, + * set the flag to indicate lib download is to be done + */ + if (sst->pdata->lib_info) + if (sst->pdata->lib_info->mod_ddr_dnld) + sst->lib_dwnld_reqd = true; +end_release: + release_firmware(fw); + return retval; +} + +static inline void print_lib_info(struct snd_sst_lib_download_info *resp) +{ + pr_debug("codec Type %d Ver %d Built %s: %s\n", + resp->dload_lib.lib_info.lib_type, + resp->dload_lib.lib_info.lib_version, + resp->dload_lib.lib_info.b_date, + resp->dload_lib.lib_info.b_time); +} + +/* + * Writing the DDR physical base to DCCM offset + * so that FW can use it to setup TLB + */ +static void sst_dccm_config_write(void __iomem *dram_base, unsigned int ddr_base) +{ + void __iomem *addr; + u32 bss_reset = 0; + + addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET); + memcpy_toio(addr, (void *)&ddr_base, sizeof(u32)); + bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT); + addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET); + memcpy_toio(addr, &bss_reset, sizeof(u32)); + pr_debug("%s: config written to DCCM\n", __func__); +} + +void sst_post_download_mrfld(struct intel_sst_drv *ctx) +{ + sst_dccm_config_write(ctx->dram, ctx->ddr_base); + /* For mrfld, download all libraries the first time fw is + * downloaded */ + pr_debug("%s: lib_dwnld = %u\n", __func__, ctx->lib_dwnld_reqd); + if (ctx->lib_dwnld_reqd) { + sst_load_all_modules_elf(ctx, sst_modules_mrfld, ARRAY_SIZE(sst_modules_mrfld)); + ctx->lib_dwnld_reqd = false; + } +} + +static void sst_init_lib_mem_mgr(struct intel_sst_drv *ctx) +{ + struct sst_mem_mgr *mgr = &ctx->lib_mem_mgr; + const struct sst_lib_dnld_info *lib_info = ctx->pdata->lib_info; + + memset(mgr, 0, sizeof(*mgr)); + mgr->current_base = lib_info->mod_base + lib_info->mod_table_offset + + lib_info->mod_table_size; + mgr->avail = lib_info->mod_end - mgr->current_base + 1; + + pr_debug("current base = 0x%lx , avail = 0x%x\n", + (unsigned long)mgr->current_base, mgr->avail); +} + +/** + * sst_load_fw - function to load FW into DSP + * + * + * Transfers the FW to DSP using dma/memcpy + */ +int sst_load_fw(void) +{ + int ret_val = 0; + struct sst_block *block; + + pr_debug("sst_load_fw\n"); + + if (sst_drv_ctx->sst_state != SST_RESET || + sst_drv_ctx->sst_state == SST_SHUTDOWN) + return -EAGAIN; + + if (!sst_drv_ctx->fw_in_mem) { + pr_debug("sst: FW not in memory retry to download\n"); + ret_val = sst_request_fw(sst_drv_ctx); + if (ret_val) + return ret_val; + } + + BUG_ON(!sst_drv_ctx->fw_in_mem); + block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID); + if (block == NULL) + return -ENOMEM; + + /* Prevent C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, 0); + + sst_drv_ctx->sst_state = SST_FW_LOADING; + + ret_val = sst_drv_ctx->ops->reset(); + if (ret_val) + goto restore; + + sst_do_memcpy(&sst_drv_ctx->memcpy_list); + + /* Write the DRAM/DCCM config before enabling FW */ + if (sst_drv_ctx->ops->post_download) + sst_drv_ctx->ops->post_download(sst_drv_ctx); + + /* bring sst out of reset */ + ret_val = sst_drv_ctx->ops->start(); + if (ret_val) + goto restore; + + ret_val = sst_wait_timeout(sst_drv_ctx, block); + if (ret_val) { + pr_err("fw download failed %d\n" , ret_val); + /* assume FW d/l failed due to timeout*/ + ret_val = -EBUSY; + + } + +restore: + /* Re-enable Deeper C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); + sst_free_block(sst_drv_ctx, block); + + return ret_val; +} + +/* In relocatable elf file, there can be relocatable variables and functions. + * Variables are kept in Global Address Offset Table (GOT) and functions in + * Procedural Linkage Table (PLT). In current codec binaries only relocatable + * variables are seen. So we use the GOT table. + */ +static int sst_find_got_table(Elf32_Shdr *shdr, int nsec, char *in_elf, + Elf32_Rela **got, unsigned int *cnt) +{ + int i = 0; + + while (i < nsec) { + if (shdr[i].sh_type == SHT_RELA) { + *got = (Elf32_Rela *)(in_elf + shdr[i].sh_offset); + *cnt = shdr[i].sh_size / sizeof(Elf32_Rela); + break; + } + i++; + } + if (i == nsec) + return -EINVAL; + + return 0; +} + +/* For each entry in the GOT table, find the unrelocated offset. Then + * add the relocation base to the offset and write back the new address to the + * original variable location. + */ +static int sst_relocate_got_entries(Elf32_Rela *table, unsigned int size, + char *in_elf, int elf_size, u32 rel_base) +{ + int i; + Elf32_Rela *entry; + Elf32_Addr *target_addr, unreloc_addr; + + for (i = 0; i < size; i++) { + entry = &table[i]; + if (ELF32_R_SYM(entry->r_info) != 0) { + return -EINVAL; + } else { + if (entry->r_offset > elf_size) { + pr_err("GOT table target addr out of range\n"); + return -EINVAL; + } + target_addr = (Elf32_Addr *)(in_elf + entry->r_offset); + unreloc_addr = *target_addr + entry->r_addend; + if (unreloc_addr > elf_size) { + pr_err("GOT table entry invalid\n"); + continue; + } + *target_addr = unreloc_addr + rel_base; + } + } + return 0; +} + +static int sst_relocate_elf(char *in_elf, int elf_size, phys_addr_t rel_base, + Elf32_Addr *entry_pt) +{ + int retval = 0; + Elf32_Ehdr *ehdr = (Elf32_Ehdr *)in_elf; + Elf32_Shdr *shdr = (Elf32_Shdr *) (in_elf + ehdr->e_shoff); + Elf32_Phdr *phdr = (Elf32_Phdr *) (in_elf + ehdr->e_phoff); + int i, num_sec; + Elf32_Rela *rel_table = NULL; + unsigned int rela_cnt = 0; + u32 rbase; + + BUG_ON(rel_base > (u32)(-1)); + rbase = (u32) (rel_base & (u32)(~0)); + + /* relocate the entry_pt */ + *entry_pt = (Elf32_Addr)(ehdr->e_entry + rbase); + num_sec = ehdr->e_shnum; + + /* Find the relocation(GOT) table through the section header */ + retval = sst_find_got_table(shdr, num_sec, in_elf, + &rel_table, &rela_cnt); + if (retval < 0) + return retval; + + /* Relocate all the entries in the GOT */ + retval = sst_relocate_got_entries(rel_table, rela_cnt, in_elf, + elf_size, rbase); + if (retval < 0) + return retval; + + pr_debug("GOT entries relocated\n"); + + /* Update the program headers in the ELF */ + for (i = 0; i < ehdr->e_phnum; i++) { + if (phdr[i].p_type == PT_LOAD) { + phdr[i].p_vaddr += rbase; + phdr[i].p_paddr += rbase; + } + } + pr_debug("program header entries updated\n"); + + return retval; +} + +#define ALIGN_256 0x100 + +int sst_get_next_lib_mem(struct sst_mem_mgr *mgr, int size, + unsigned long *lib_base) +{ + int retval = 0; + + pr_debug("library orig size = 0x%x", size); + if (size % ALIGN_256) + size += (ALIGN_256 - (size % ALIGN_256)); + if (size > mgr->avail) + return -ENOMEM; + + *lib_base = mgr->current_base; + mgr->current_base += size; + mgr->avail -= size; + mgr->count++; + pr_debug("library base = 0x%lx", *lib_base); + pr_debug("library aligned size = 0x%x", size); + pr_debug("lib count = %d\n", mgr->count); + return retval; + +} + +static int sst_download_lib_elf(struct intel_sst_drv *sst, const void *lib, + int size) +{ + int retval = 0; + + pr_debug("In %s\n", __func__); + + retval = sst_parse_elf_fw_memcpy(sst, lib, + &sst->libmemcpy_list); + if (retval) + return retval; + sst_do_memcpy(&sst->libmemcpy_list); + sst_memcpy_free_lib_resources(); + pr_debug("download lib complete"); + return retval; +} + +static void sst_fill_fw_module_table(struct sst_module_info *mod_list, + int list_size, unsigned long ddr_base) +{ + int i; + u32 *write_ptr = (u32 *)ddr_base; + + pr_debug("In %s\n", __func__); + + for (i = 0; i < list_size; i++) { + if (mod_list[i].status == SST_LIB_DOWNLOADED) { + pr_debug("status dnwld for %d\n", i); + pr_debug("module id %d\n", mod_list[i].id); + pr_debug("entry pt 0x%x\n", mod_list[i].entry_pt); + + *write_ptr++ = mod_list[i].id; + *write_ptr++ = mod_list[i].entry_pt; + } + } +} + +static int sst_request_lib_elf(struct sst_module_info *mod_entry, + const struct firmware **fw_lib, int pci_id, struct device *dev) +{ + char name[25]; + int retval = 0; + + snprintf(name, sizeof(name), "%s%s%04x%s", mod_entry->name, + "_", pci_id, ".bin"); + pr_debug("Requesting %s\n", name); + + retval = request_firmware(fw_lib, name, dev); + if (retval) { + pr_err("%s library load failed %d\n", name, retval); + return retval; + } + pr_debug("got lib\n"); + mod_entry->status = SST_LIB_FOUND; + return 0; +} + +static int sst_allocate_lib_mem(const struct firmware *lib, int size, + struct sst_mem_mgr *mem_mgr, char **out_elf, unsigned long *lib_start) +{ + int retval = 0; + + *out_elf = kzalloc(size, GFP_KERNEL); + if (!*out_elf) { + pr_err("cannot alloc mem for elf copy %d\n", retval); + goto mem_error; + } + + memcpy(*out_elf, lib->data, size); + retval = sst_get_next_lib_mem(mem_mgr, size, lib_start); + if (retval < 0) { + pr_err("cannot alloc ddr mem for lib: %d\n", retval); + kfree(*out_elf); + goto mem_error; + } + return 0; + +mem_error: + release_firmware(lib); + return -ENOMEM; +} + +int sst_load_all_modules_elf(struct intel_sst_drv *ctx, struct sst_module_info *mod_table, + int num_modules) +{ + int retval = 0; + int i; + const struct firmware *fw_lib; + struct sst_module_info *mod = NULL; + char *out_elf; + unsigned int lib_size = 0; + unsigned int mod_table_offset = ctx->pdata->lib_info->mod_table_offset; + unsigned long lib_base; + + pr_debug("In %s", __func__); + + sst_init_lib_mem_mgr(ctx); + + for (i = 0; i < num_modules; i++) { + mod = &mod_table[i]; + retval = sst_request_lib_elf(mod, &fw_lib, + ctx->pci_id, ctx->dev); + if (retval < 0) + continue; + lib_size = fw_lib->size; + + retval = sst_validate_elf(fw_lib, true); + if (retval < 0) { + pr_err("library is not valid elf %d\n", retval); + release_firmware(fw_lib); + continue; + } + retval = sst_allocate_lib_mem(fw_lib, lib_size, + &ctx->lib_mem_mgr, &out_elf, &lib_base); + if (retval < 0) { + pr_err("lib mem allocation failed: %d\n", retval); + continue; + } + + /* relocate in place */ + retval = sst_relocate_elf(out_elf, lib_size, + lib_base, &mod->entry_pt); + if (retval < 0) { + pr_err("lib elf relocation failed: %d\n", retval); + release_firmware(fw_lib); + kfree(out_elf); + continue; + } + release_firmware(fw_lib); + /* write to ddr imr region,use memcpy method */ + retval = sst_download_lib_elf(ctx, out_elf, lib_size); + mod->status = SST_LIB_DOWNLOADED; + kfree(out_elf); + } + + /* write module table to DDR */ + sst_fill_fw_module_table(mod_table, num_modules, + (unsigned long)(ctx->ddr + mod_table_offset)); + return retval; +} diff --git a/sound/soc/intel/sst/sst_pvt.c b/sound/soc/intel/sst/sst_pvt.c new file mode 100644 index 0000000..ac07dcd --- /dev/null +++ b/sound/soc/intel/sst/sst_pvt.c @@ -0,0 +1,208 @@ +/* + * sst_pvt.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R dharageswari.r@intel.com + * KP Jeeja jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file contains all private functions + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kobject.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <sound/asound.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +/* + * sst_wait_interruptible - wait on event + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits without a timeout (and is interruptable) for a + * given block event + */ +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block) +{ + int retval = 0; + + if (!wait_event_interruptible(sst_drv_ctx->wait_queue, + block->condition)) { + /* event wake */ + if (block->ret_code < 0) { + pr_err("stream failed %d\n", block->ret_code); + retval = -EBUSY; + } else { + pr_debug("event up\n"); + retval = 0; + } + } else { + pr_err("signal interrupted\n"); + retval = -EINTR; + } + return retval; + +} + +unsigned long long read_shim_data(struct intel_sst_drv *sst, int addr) +{ + unsigned long long val = 0; + + switch (sst->pci_id) { + case SST_MRFLD_PCI_ID: + val = sst_shim_read64(sst->shim, addr); + break; + } + return val; +} + +void write_shim_data(struct intel_sst_drv *sst, int addr, + unsigned long long data) +{ + switch (sst->pci_id) { + case SST_MRFLD_PCI_ID: + sst_shim_write64(sst->shim, addr, (u64) data); + break; + } +} + +/* + * sst_wait_timeout - wait on event for timeout + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits with a timeout value (and is not interruptible) on a + * given block event + */ +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block) +{ + int retval = 0; + + /* NOTE: + Observed that FW processes the alloc msg and replies even + before the alloc thread has finished execution */ + pr_debug("sst: waiting for condition %x ipc %d drv_id %d\n", + block->condition, block->msg_id, block->drv_id); + if (wait_event_timeout(sst_drv_ctx->wait_queue, + block->condition, + msecs_to_jiffies(SST_BLOCK_TIMEOUT))) { + /* event wake */ + pr_debug("sst: Event wake %x\n", block->condition); + pr_debug("sst: message ret: %d\n", block->ret_code); + retval = -block->ret_code; + } else { + block->on = false; + pr_err("sst: Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n", + block->condition, block->msg_id, sst_drv_ctx->sst_state); + + retval = -EBUSY; + } + return retval; +} + +/* + * sst_create_ipc_msg - create a IPC message + * + * @arg: ipc message + * @large: large or short message + * + * this function allocates structures to send a large or short + * message to the firmware + */ +int sst_create_ipc_msg(struct ipc_post **arg, bool large) +{ + struct ipc_post *msg; + + msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC); + if (!msg) { + pr_err("kzalloc ipc msg failed\n"); + return -ENOMEM; + } + if (large) { + msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC); + if (!msg->mailbox_data) { + kfree(msg); + pr_err("kzalloc mailbox_data failed"); + return -ENOMEM; + } + } else { + msg->mailbox_data = NULL; + } + msg->is_large = large; + *arg = msg; + return 0; +} + +/* + * sst_create_block_and_ipc_msg - Creates IPC message and sst block + * @arg: passed to sst_create_ipc_message API + * @large: large or short message + * @sst_drv_ctx: sst driver context + * @block: return block allocated + * @msg_id: IPC + * @drv_id: stream id or private id + */ +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id) +{ + int retval = 0; + + retval = sst_create_ipc_msg(arg, large); + if (retval) + return retval; + *block = sst_create_block(sst_drv_ctx, msg_id, drv_id); + if (*block == NULL) { + kfree(*arg); + return -ENOMEM; + } + return retval; +} + +/* + * sst_clean_stream - clean the stream context + * + * @stream: stream structure + * + * this function resets the stream contexts + * should be called in free + */ +void sst_clean_stream(struct stream_info *stream) +{ + stream->status = STREAM_UN_INIT; + stream->prev = STREAM_UN_INIT; + mutex_lock(&stream->lock); + stream->cumm_bytes = 0; + mutex_unlock(&stream->lock); +} + diff --git a/sound/soc/intel/sst/sst_stream.c b/sound/soc/intel/sst/sst_stream.c new file mode 100644 index 0000000..b6c8772 --- /dev/null +++ b/sound/soc/intel/sst/sst_stream.c @@ -0,0 +1,534 @@ +/* + * sst_stream.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul vinod.koul@intel.com + * Harsha Priya priya.harsha@intel.com + * Dharageswari R dharageswari.r@intel.com + * KP Jeeja jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file contains the stream operations of SST driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/pci.h> +#include <linux/firmware.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../sst-dsp.h" + +int sst_alloc_stream_mrfld(char *params, struct sst_block *block) +{ + struct ipc_post *msg = NULL; + struct snd_sst_alloc_mrfld alloc_param; + struct ipc_dsp_hdr dsp_hdr; + struct snd_sst_params *str_params; + struct snd_sst_tstamp fw_tstamp; + unsigned int str_id, pipe_id, pvt_id, task_id; + u32 len = 0; + struct stream_info *str_info; + int i, num_ch; + + pr_debug("In %s\n", __func__); + BUG_ON(!params); + + str_params = (struct snd_sst_params *)params; + memset(&alloc_param, 0, sizeof(alloc_param)); + alloc_param.operation = str_params->ops; + alloc_param.codec_type = str_params->codec; + alloc_param.sg_count = str_params->aparams.sg_count; + alloc_param.ring_buf_info[0].addr = str_params->aparams.ring_buf_info[0].addr; + alloc_param.ring_buf_info[0].size = str_params->aparams.ring_buf_info[0].size; + alloc_param.frag_size = str_params->aparams.frag_size; + + memcpy(&alloc_param.codec_params, &str_params->sparams, + sizeof(struct snd_sst_stream_params)); + + /* fill channel map params for multichannel support. + * Ideally channel map should be received from upper layers + * for multichannel support. + * Currently hardcoding as per FW reqm. + */ + num_ch = sst_get_num_channel(str_params); + for (i = 0; i < 8; i++) { + if (i < num_ch) + alloc_param.codec_params.uc.pcm_params.channel_map[i] = i; + else + alloc_param.codec_params.uc.pcm_params.channel_map[i] = 0xFF; + } + + str_id = str_params->stream_id; + pipe_id = str_params->device_type; + task_id = str_params->task; + sst_drv_ctx->streams[str_id].pipe_id = pipe_id; + sst_drv_ctx->streams[str_id].task_id = task_id; + sst_drv_ctx->streams[str_id].num_ch = num_ch; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + if (sst_drv_ctx->info.lpe_viewpt_rqd) + alloc_param.ts = sst_drv_ctx->info.mailbox_start + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + else + alloc_param.ts = sst_drv_ctx->mailbox_add + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + + pr_debug("alloc tstamp location = 0x%x\n", alloc_param.ts); + pr_debug("assigned pipe id 0x%x to task %d\n", pipe_id, task_id); + + /*allocate device type context*/ + sst_init_stream(&sst_drv_ctx->streams[str_id], alloc_param.codec_type, + str_id, alloc_param.operation, 0); + /* send msg to FW to allocate a stream */ + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + + block->drv_id = pvt_id; + block->msg_id = IPC_CMD; + + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + task_id, 1, pvt_id); + pr_debug("header:%x\n", msg->mrfld_header.p.header_high.full); + msg->mrfld_header.p.header_high.part.res_rqd = 1; + + len = msg->mrfld_header.p.header_low_payload = sizeof(alloc_param) + sizeof(dsp_hdr); + sst_fill_header_dsp(&dsp_hdr, IPC_IA_ALLOC_STREAM_MRFLD, pipe_id, sizeof(alloc_param)); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + memcpy(msg->mailbox_data + sizeof(dsp_hdr), &alloc_param, + sizeof(alloc_param)); + str_info = &sst_drv_ctx->streams[str_id]; + pr_debug("header:%x\n", msg->mrfld_header.p.header_high.full); + pr_debug("response rqd: %x", msg->mrfld_header.p.header_high.part.res_rqd); + pr_debug("calling post_message\n"); + pr_info("Alloc for str %d pipe %#x\n", str_id, pipe_id); + + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + return str_id; +} + +/** +* sst_stream_stream - Send msg for a pausing stream +* @str_id: stream ID +* +* This function is called by any function which wants to start +* a stream. +*/ +int sst_start_stream(int str_id) +{ + int retval = 0, pvt_id; + u32 len = 0; + struct ipc_post *msg = NULL; + struct ipc_dsp_hdr dsp_hdr; + struct stream_info *str_info; + + pr_debug("sst_start_stream for %d\n", str_id); + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING) + return -EBADRQC; + + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + pr_debug("pvt_id = %d, pipe id = %d, task = %d\n", + pvt_id, str_info->pipe_id, str_info->task_id); + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + str_info->task_id, 1, pvt_id); + + len = sizeof(u16) + sizeof(dsp_hdr); + msg->mrfld_header.p.header_low_payload = len; + sst_fill_header_dsp(&dsp_hdr, IPC_IA_START_STREAM_MRFLD, + str_info->pipe_id, sizeof(u16)); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + memset(msg->mailbox_data + sizeof(dsp_hdr), 0, sizeof(u16)); + + sst_drv_ctx->ops->sync_post_message(msg); + return retval; +} + +int sst_send_byte_stream_mrfld(void *sbytes) +{ + struct ipc_post *msg = NULL; + struct snd_sst_bytes_v2 *bytes = (struct snd_sst_bytes_v2 *) sbytes; + u32 length; + int pvt_id, ret = 0; + struct sst_block *block = NULL; + + pr_debug("%s: type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n", + __func__, bytes->type, bytes->ipc_msg, + bytes->block, bytes->task_id, + bytes->pipe_id, bytes->len); + + /* need some err check as this is user data, perhpas move this to the + * platform driver and pass the struct + */ + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg, bytes->task_id, + 1, pvt_id); + msg->mrfld_header.p.header_high.part.res_rqd = bytes->block; + length = bytes->len; + msg->mrfld_header.p.header_low_payload = length; + pr_debug("length is %d\n", length); + memcpy(msg->mailbox_data, &bytes->bytes, bytes->len); + if (bytes->block) { + block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id); + if (block == NULL) { + kfree(msg); + return -ENOMEM; + } + } + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + pr_debug("msg->mrfld_header.p.header_low_payload:%d", msg->mrfld_header.p.header_low_payload); + if (bytes->block) { + ret = sst_wait_timeout(sst_drv_ctx, block); + if (ret) { + pr_err("%s: fw returned err %d\n", __func__, ret); + sst_free_block(sst_drv_ctx, block); + return ret; + } + } + if (bytes->type == SND_SST_BYTES_GET) { + /* copy the reply and send back + * we need to update only sz and payload + */ + if (bytes->block) { + unsigned char *r = block->data; + + pr_debug("read back %d bytes", bytes->len); + memcpy(bytes->bytes, r, bytes->len); + } + } + if (bytes->block) + sst_free_block(sst_drv_ctx, block); + return 0; +} + +/* + * sst_pause_stream - Send msg for a pausing stream + * @str_id: stream ID + * + * This function is called by any function which wants to pause + * an already running stream. + */ +int sst_pause_stream(int str_id) +{ + int retval = 0, pvt_id, len; + struct ipc_post *msg = NULL; + struct stream_info *str_info; + struct intel_sst_ops *ops; + struct sst_block *block; + struct ipc_dsp_hdr dsp_hdr; + + pr_debug("SST DBG:sst_pause_stream for %d\n", str_id); + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + ops = sst_drv_ctx->ops; + if (str_info->status == STREAM_PAUSED) + return 0; + if (str_info->status == STREAM_RUNNING || + str_info->status == STREAM_INIT) { + if (str_info->prev == STREAM_UN_INIT) + return -EBADRQC; + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + retval = sst_create_block_and_ipc_msg(&msg, true, + sst_drv_ctx, &block, IPC_CMD, pvt_id); + if (retval) + return retval; + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + str_info->task_id, 1, pvt_id); + msg->mrfld_header.p.header_high.part.res_rqd = 1; + len = sizeof(dsp_hdr); + msg->mrfld_header.p.header_low_payload = len; + sst_fill_header_dsp(&dsp_hdr, IPC_IA_PAUSE_STREAM_MRFLD, + str_info->pipe_id, 0); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + retval = sst_wait_timeout(sst_drv_ctx, block); + sst_free_block(sst_drv_ctx, block); + if (retval == 0) { + str_info->prev = str_info->status; + str_info->status = STREAM_PAUSED; + } else if (retval == SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + pr_debug("SST DBG:BADRQC for stream\n "); + } + + return retval; +} + +/** + * sst_resume_stream - Send msg for resuming stream + * @str_id: stream ID + * + * This function is called by any function which wants to resume + * an already paused stream. + */ +int sst_resume_stream(int str_id) +{ + int retval = 0; + struct ipc_post *msg = NULL; + struct stream_info *str_info; + struct intel_sst_ops *ops; + struct sst_block *block = NULL; + int pvt_id, len; + struct ipc_dsp_hdr dsp_hdr; + + pr_debug("SST DBG:sst_resume_stream for %d\n", str_id); + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + ops = sst_drv_ctx->ops; + if (str_info->status == STREAM_RUNNING) + return 0; + if (str_info->status == STREAM_PAUSED) { + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + retval = sst_create_block_and_ipc_msg(&msg, true, + sst_drv_ctx, &block, IPC_CMD, pvt_id); + if (retval) + return retval; + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + str_info->task_id, 1, pvt_id); + msg->mrfld_header.p.header_high.part.res_rqd = 1; + len = sizeof(dsp_hdr); + msg->mrfld_header.p.header_low_payload = len; + sst_fill_header_dsp(&dsp_hdr, + IPC_IA_RESUME_STREAM_MRFLD, + str_info->pipe_id, 0); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + retval = sst_wait_timeout(sst_drv_ctx, block); + sst_free_block(sst_drv_ctx, block); + if (!retval) { + if (str_info->prev == STREAM_RUNNING) + str_info->status = STREAM_RUNNING; + else + str_info->status = STREAM_INIT; + str_info->prev = STREAM_PAUSED; + } else if (retval == -SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + pr_err("SST ERR: BADQRC for stream\n"); + } + + return retval; +} + + +/** + * sst_drop_stream - Send msg for stopping stream + * @str_id: stream ID + * + * This function is called by any function which wants to stop + * a stream. + */ +int sst_drop_stream(int str_id) +{ + int retval = 0, pvt_id; + struct stream_info *str_info; + struct ipc_post *msg = NULL; + struct ipc_dsp_hdr dsp_hdr; + + pr_debug("SST DBG:sst_drop_stream for %d\n", str_id); + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + + if (str_info->status != STREAM_UN_INIT) { + + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + str_info->cumm_bytes = 0; + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + str_info->task_id, 1, pvt_id); + msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr); + sst_fill_header_dsp(&dsp_hdr, IPC_IA_DROP_STREAM_MRFLD, + str_info->pipe_id, 0); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + + sst_drv_ctx->ops->sync_post_message(msg); + } else { + retval = -EBADRQC; + pr_debug("BADQRC for stream, state %x\n", str_info->status); + } + return retval; +} + +/** + * sst_next_track: notify next track + * @str_id: stream ID + * + * This function is called by any function which wants to + * set next track. Current this is NOP as FW doest care + */ +int sst_next_track(void) +{ + pr_debug("SST DBG: next_track"); + return 0; +} + +/** +* sst_drain_stream - Send msg for draining stream +* @str_id: stream ID +* +* This function is called by any function which wants to drain +* a stream. +*/ +int sst_drain_stream(int str_id, bool partial_drain) +{ + int retval = 0, pvt_id, len; + struct ipc_post *msg = NULL; + struct stream_info *str_info; + struct intel_sst_ops *ops; + struct sst_block *block = NULL; + struct ipc_dsp_hdr dsp_hdr; + + pr_debug("SST DBG:sst_drain_stream for %d\n", str_id); + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + ops = sst_drv_ctx->ops; + if (str_info->status != STREAM_RUNNING && + str_info->status != STREAM_INIT && + str_info->status != STREAM_PAUSED) { + pr_err("SST ERR: BADQRC for stream = %d\n", + str_info->status); + return -EBADRQC; + } + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + retval = sst_create_block_and_ipc_msg(&msg, true, + sst_drv_ctx, &block, IPC_CMD, pvt_id); + if (retval) + return retval; + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + str_info->task_id, 1, pvt_id); + pr_debug("header:%x\n", + (unsigned int)msg->mrfld_header.p.header_high.full); + msg->mrfld_header.p.header_high.part.res_rqd = 1; + len = sizeof(u8) + sizeof(dsp_hdr); + msg->mrfld_header.p.header_low_payload = len; + sst_fill_header_dsp(&dsp_hdr, IPC_IA_DRAIN_STREAM_MRFLD, + str_info->pipe_id, sizeof(u8)); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + memcpy(msg->mailbox_data + sizeof(dsp_hdr), + &partial_drain, sizeof(u8)); + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + /* with new non blocked drain implementation in core we dont need to + * wait for respsonse, and need to only invoke callback for drain + * complete + */ + + sst_free_block(sst_drv_ctx, block); + return retval; +} + +/** + * sst_free_stream - Frees a stream + * @str_id: stream ID + * + * This function is called by any function which wants to free + * a stream. + */ +int sst_free_stream(int str_id) +{ + int retval = 0; + unsigned int pvt_id; + struct ipc_post *msg = NULL; + struct stream_info *str_info; + struct intel_sst_ops *ops; + unsigned long irq_flags; + struct ipc_dsp_hdr dsp_hdr; + struct sst_block *block; + + pr_debug("SST DBG:sst_free_stream for %d\n", str_id); + + mutex_lock(&sst_drv_ctx->sst_lock); + if (sst_drv_ctx->sst_state == SST_RESET) { + mutex_unlock(&sst_drv_ctx->sst_lock); + return -ENODEV; + } + mutex_unlock(&sst_drv_ctx->sst_lock); + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + ops = sst_drv_ctx->ops; + + mutex_lock(&str_info->lock); + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = str_info->status; + str_info->status = STREAM_UN_INIT; + mutex_unlock(&str_info->lock); + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + retval = sst_create_block_and_ipc_msg(&msg, true, + sst_drv_ctx, &block, IPC_CMD, pvt_id); + if (retval) + return retval; + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + str_info->task_id, 1, pvt_id); + msg->mrfld_header.p.header_low_payload = + sizeof(dsp_hdr); + sst_fill_header_dsp(&dsp_hdr, IPC_IA_FREE_STREAM_MRFLD, + str_info->pipe_id, 0); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + pr_info("Free for str %d pipe %#x\n", str_id, str_info->pipe_id); + + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + list_add_tail(&msg->node, &sst_drv_ctx->ipc_dispatch_list); + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + ops->post_message(&sst_drv_ctx->ipc_post_msg_wq); + retval = sst_wait_timeout(sst_drv_ctx, block); + pr_debug("sst: wait for free returned %d\n", retval); + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + pr_debug("SST DBG:Stream freed\n"); + sst_free_block(sst_drv_ctx, block); + } else { + mutex_unlock(&str_info->lock); + retval = -EBADRQC; + pr_debug("SST DBG:BADQRC for stream\n"); + } + + return retval; +}
On Wed, Jul 09, 2014 at 02:57:52PM +0530, Vinod Koul wrote:
The SST driver is the missing piece in our driver stack not upstreamed, so push it now :) This driver currently supports PCI device on Merrifield. Future updates will bring support for ACPI device as well as future update to PCI devices as well
I've started reviewing this several times now but it's a really big change to review, and not just huge data tables but actual code. It'd really benefit from being split up a bit further.
Have you looked at the mailbox framework (not merged yet but hopefully getting close to it)?
+MODULE_VERSION(SST_DRIVER_VERSION);
Probably better to drop driver versions from mainline.
+#define SST_IS_PROCESS_REPLY(header) ((header & PROCESS_MSG) ? true : false) +#define SST_VALIDATE_MAILBOX_SIZE(size) ((size <= SST_MAILBOX_SIZE) ? true : false)
Inline functions please.
+static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) +{
- struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
- struct ipc_post *__msg, *msg = NULL;
- unsigned long irq_flags;
- if (list_empty(&drv->rx_list))
return IRQ_HANDLED;
Why would the IRQ thread be running with nothing to do, and if it can be...
- spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
- list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
...won't we handle that gracefully anyway?
+int sst_alloc_drv_context(struct device *dev) +{
- struct intel_sst_drv *ctx;
- ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
- if (!ctx) {
pr_err("malloc fail\n");
return -ENOMEM;
- }
- sst_drv_ctx = ctx;
- return 0;
+}
As others were saying I'd really like to see some sort of case being made for a global variable, and if we really do need a global variable why not just declare it rather than dynamically allocating?
- if (0 != sst_driver_ops(sst_drv_ctx))
return -EINVAL;
if (!sst_driver_ops())
+#define SST_DRIVER_VERSION "5.0.0"
Generally better to drop these from mainline, the kernel version should be enough.
+static inline int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{
- int ret;
- ret = pm_runtime_put_sync(sst_drv->dev);
- if (ret < 0)
return ret;
- atomic_dec(&sst_drv->pm_usage_count);
- pr_debug("%s: count is %d now..\n", __func__,
atomic_read(&sst_drv->pm_usage_count));
- return 0;
+}
I'm not entirely clear why this is here but if it's to provide trace just instrument the runtime PM core.
- @sst_drv_ctx : driver context
- this inline function assigns a private id for calls that dont have stream
- context yet, should be called with lock held
- */
+static inline unsigned int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx) +{
- unsigned int local;
- spin_lock(&sst_drv_ctx->block_lock);
- sst_drv_ctx->pvt_id++;
- if (sst_drv_ctx->pvt_id > MAX_BLOCKS)
sst_drv_ctx->pvt_id = 1;
- local = sst_drv_ctx->pvt_id;
- spin_unlock(&sst_drv_ctx->block_lock);
- return local;
+}
I would expect this to be checking to see if the IDs are free but it just hands them out in sequence? There appears to be an array of streams statically allocated, why not look there for an unused one?
+static inline void +sst_set_fw_state_locked(struct intel_sst_drv *sst_drv_ctx, int sst_state) +{
- mutex_lock(&sst_drv_ctx->sst_lock);
- sst_drv_ctx->sst_state = sst_state;
- mutex_unlock(&sst_drv_ctx->sst_lock);
+}
I can't see any references to this in the code which is good as it seems a little odd to have a function like this.
+int register_sst(struct device *); +int unregister_sst(struct device *);
Everything else is sst_
+int sst_download_fw(void) +{
- int retval = 0;
- retval = sst_load_fw();
- if (retval)
return retval;
- pr_debug("fw loaded successful!!!\n");
- if (sst_drv_ctx->ops->restore_dsp_context)
sst_drv_ctx->ops->restore_dsp_context();
- sst_drv_ctx->sst_state = SST_FW_RUNNING;
- return retval;
+}
So sst_load_fw() is wrapped with this function sst_download_fw() which does a tiny bit of extra stuff which might be mostly better done in the core function...
+int free_stream_context(unsigned int str_id) +{
- struct stream_info *stream;
- int ret = 0;
- stream = get_stream_info(str_id);
- if (stream) {
It's a bit scary that this might be used with an invalid ID - what happens if the ID got reallocated?
- retval = sst_drv_ctx->ops->alloc_stream((char *) str_param, block);
- str_id = retval;
This cast is scary... should alloc_stream() be taking a void?
- /* Block the call for reply */
- retval = sst_wait_timeout(sst_drv_ctx, block);
- if (block->data) {
- } else if (retval != 0) {
It'd be less surprising if the first check were for retval.
- pr_debug("In %s\n", __func__);
- /* stream is not allocated, we are allocating */
- retval = sst_get_stream_allocated(str_param, &lib_dnld);
- if (retval <= 0) {
Coding style (there's quite a bit of odd coding styles throughout this code, slightly odd indentation, missing blanks and so on - it generally doesn't quite look like Linux code).
+/** +* intel_sst_check_device - checks SST device +* +* This utility function checks the state of SST device and downlaods FW if +* not done, or resumes the device if suspended +*/ +int intel_sst_check_device(void)
Can we have a better name please? But this all seems very odd, why is runtime PM not causing the firmware to be loaded?
- /* The free_stream will return a error if there is no stream to free,
- (i.e. the alloc failure case). And in this case the open does a put in
- the error scenario, so skip in this case.
In the close we need to handle put in the success scenario and
- the timeout error(EBUSY) scenario. */
Another weird coding style thing.
+static int sst_device_control(int cmd, void *arg) +{
- int retval = 0, str_id = 0;
- if (sst_drv_ctx->sst_state != SST_FW_RUNNING)
return 0;
- switch (cmd) {
- case SST_SND_START: {
Eew, ioctl(). This would be clearer if it were a series of functions, there's nothing shared here.
+static int sst_set_generic_params(enum sst_controls cmd, void *arg) +{
- int ret_val = 0;
- if (NULL == arg)
return -EINVAL;
Again an ioctl()...
- /* check busy bit */
- header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
- while (header.p.header_high.part.busy) {
if (loop_count > 10) {
pr_err("sst: Busy wait failed, cant send this msg\n");
retval = -EBUSY;
goto out;
}
udelay(500);
loop_count++;
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
- }
Throw a cpu_relax() in there if we need to spin...
- pr_info("FW Version %02x.%02x.%02x.%02x\n",
init->fw_version.type, init->fw_version.major,
init->fw_version.minor, init->fw_version.build);
- pr_info("Build date %s Time %s\n",
init->build_info.date, init->build_info.time);
Same comment as for some of the other firmwares - consider providing this via /proc or sysfs as well.
+static inline int sst_validate_elf(const struct firmware *sst_bin, bool dynamic) +{
- Elf32_Ehdr *elf;
- BUG_ON(!sst_bin);
- pr_debug("IN %s\n", __func__);
- elf = (Elf32_Ehdr *)sst_bin->data;
- if ((elf->e_ident[0] != 0x7F) || (elf->e_ident[1] != 'E') ||
(elf->e_ident[2] != 'L') || (elf->e_ident[3] != 'F')) {
pr_debug("ELF Header Not found!%zu\n", sst_bin->size);
return -EINVAL;
- }
- if (dynamic == true) {
if (elf->e_type != ET_DYN) {
pr_err("Not a dynamic loadable library\n");
return -EINVAL;
}
- }
- pr_debug("Valid ELF Header...%zu\n", sst_bin->size);
- return 0;
+}
We have generic ELF handling code in the kernel, should this go there?
+{
- struct fw_header *header;
struct fw_header should probably be better namespaced?
+void sst_memcpy_free_resources(void) +{
- struct sst_memcpy_list *listnode, *tmplistnode;
- pr_debug("entry:%s\n", __func__);
- /*Free the list*/
- if (!list_empty(&sst_drv_ctx->memcpy_list)) {
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->memcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
- }
- sst_memcpy_free_lib_resources();
+}
I'm having a hard time seeing why we don't just copy the data as we go rather than allocating this list? It seems to just be making the code more complex.
+/**
- sst_next_track: notify next track
- @str_id: stream ID
- This function is called by any function which wants to
- set next track. Current this is NOP as FW doest care
- */
+int sst_next_track(void) +{
- pr_debug("SST DBG: next_track");
- return 0;
+}
Shouldn't the operation just be made optional then, obviously the upper layers don't care either?
On Fri, Jul 18, 2014 at 12:35:48PM +0100, Mark Brown wrote:
On Wed, Jul 09, 2014 at 02:57:52PM +0530, Vinod Koul wrote:
The SST driver is the missing piece in our driver stack not upstreamed, so push it now :) This driver currently supports PCI device on Merrifield. Future updates will bring support for ACPI device as well as future update to PCI devices as well
I've started reviewing this several times now but it's a really big change to review, and not just huge data tables but actual code. It'd really benefit from being split up a bit further.
well I tried a lot of split up. But further split could have caused lots of bisect issues. One way to do so was to add makefile last. I can try that next time if you are fine
Have you looked at the mailbox framework (not merged yet but hopefully getting close to it)?
+MODULE_VERSION(SST_DRIVER_VERSION);
Probably better to drop driver versions from mainline.
Sure thing!
+#define SST_IS_PROCESS_REPLY(header) ((header & PROCESS_MSG) ? true : false) +#define SST_VALIDATE_MAILBOX_SIZE(size) ((size <= SST_MAILBOX_SIZE) ? true : false)
Inline functions please.
+static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) +{
- struct intel_sst_drv *drv = (struct intel_sst_drv *) context;
- struct ipc_post *__msg, *msg = NULL;
- unsigned long irq_flags;
- if (list_empty(&drv->rx_list))
return IRQ_HANDLED;
Why would the IRQ thread be running with nothing to do, and if it can be...
Cant think of, we need to remove this!
- spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
- list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
...won't we handle that gracefully anyway?
Are you referring to lock + irqsave?
+int sst_alloc_drv_context(struct device *dev) +{
- struct intel_sst_drv *ctx;
- ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
- if (!ctx) {
pr_err("malloc fail\n");
return -ENOMEM;
- }
- sst_drv_ctx = ctx;
- return 0;
+}
As others were saying I'd really like to see some sort of case being made for a global variable, and if we really do need a global variable why not just declare it rather than dynamically allocating?
For this case none, we can try removing this which was more for legacy reasons when we had pvt ioctls for offload
- if (0 != sst_driver_ops(sst_drv_ctx))
return -EINVAL;
if (!sst_driver_ops())
+#define SST_DRIVER_VERSION "5.0.0"
Generally better to drop these from mainline, the kernel version should be enough.
+static inline int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{
- int ret;
- ret = pm_runtime_put_sync(sst_drv->dev);
- if (ret < 0)
return ret;
- atomic_dec(&sst_drv->pm_usage_count);
- pr_debug("%s: count is %d now..\n", __func__,
atomic_read(&sst_drv->pm_usage_count));
- return 0;
+}
I'm not entirely clear why this is here but if it's to provide trace just instrument the runtime PM core.
Well the only usage of pm_usage_count locally is to check in runtime pm suspend to S3 transitions. When we are runtime suspended and S3 triggers, the device usage count goes up to 1. Then runtime resume and classic suspend is invoked in that order. So if we are in this scenario we can't rely on checks using frameworks count.
- @sst_drv_ctx : driver context
- this inline function assigns a private id for calls that dont have stream
- context yet, should be called with lock held
- */
+static inline unsigned int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx) +{
- unsigned int local;
- spin_lock(&sst_drv_ctx->block_lock);
- sst_drv_ctx->pvt_id++;
- if (sst_drv_ctx->pvt_id > MAX_BLOCKS)
sst_drv_ctx->pvt_id = 1;
- local = sst_drv_ctx->pvt_id;
- spin_unlock(&sst_drv_ctx->block_lock);
- return local;
+}
I would expect this to be checking to see if the IDs are free but it just hands them out in sequence? There appears to be an array of streams statically allocated, why not look there for an unused one?
we use these incrementally. The driver stamps each IPC to DSP with a number. The size is limited by bits in the IPCs. So we keep going till we hit max, that time we set it back to 1
These ids are not freed or allocated as IPC latency is small enough and IPCs less. We haven't hot scenarios where we go complete round but IPC is still pending :)
+static inline void +sst_set_fw_state_locked(struct intel_sst_drv *sst_drv_ctx, int sst_state) +{
- mutex_lock(&sst_drv_ctx->sst_lock);
- sst_drv_ctx->sst_state = sst_state;
- mutex_unlock(&sst_drv_ctx->sst_lock);
+}
I can't see any references to this in the code which is good as it seems a little odd to have a function like this.
My bad, this was to be removed :(
+int register_sst(struct device *); +int unregister_sst(struct device *);
Everything else is sst_
Hmmm fair point!
+int sst_download_fw(void) +{
- int retval = 0;
- retval = sst_load_fw();
- if (retval)
return retval;
- pr_debug("fw loaded successful!!!\n");
- if (sst_drv_ctx->ops->restore_dsp_context)
sst_drv_ctx->ops->restore_dsp_context();
- sst_drv_ctx->sst_state = SST_FW_RUNNING;
- return retval;
+}
So sst_load_fw() is wrapped with this function sst_download_fw() which does a tiny bit of extra stuff which might be mostly better done in the core function...
ok
+int free_stream_context(unsigned int str_id) +{
- struct stream_info *stream;
- int ret = 0;
- stream = get_stream_info(str_id);
- if (stream) {
It's a bit scary that this might be used with an invalid ID - what happens if the ID got reallocated?
The free routine checks for valid ID first
- retval = sst_drv_ctx->ops->alloc_stream((char *) str_param, block);
- str_id = retval;
This cast is scary... should alloc_stream() be taking a void?
Well we had two versions die to tow gens of DSP FW, we mught be able to clean this up, will give it a shot!
- /* Block the call for reply */
- retval = sst_wait_timeout(sst_drv_ctx, block);
- if (block->data) {
- } else if (retval != 0) {
It'd be less surprising if the first check were for retval.
ok
- pr_debug("In %s\n", __func__);
- /* stream is not allocated, we are allocating */
- retval = sst_get_stream_allocated(str_param, &lib_dnld);
- if (retval <= 0) {
Coding style (there's quite a bit of odd coding styles throughout this code, slightly odd indentation, missing blanks and so on - it generally doesn't quite look like Linux code).
well this was started off in 2008, never got to a point to push upstream though tried few times!. Will fix these
+/** +* intel_sst_check_device - checks SST device +* +* This utility function checks the state of SST device and downlaods FW if +* not done, or resumes the device if suspended +*/ +int intel_sst_check_device(void)
Can we have a better name please? But this all seems very odd, why is runtime PM not causing the firmware to be loaded?
well as previously stated runtime resume might be invoked during S3, so we wnat to load fw after calling _get_sync() so rather than calling _get_sync() and download everywhere we wrapped it here
- /* The free_stream will return a error if there is no stream to free,
- (i.e. the alloc failure case). And in this case the open does a put in
- the error scenario, so skip in this case.
In the close we need to handle put in the success scenario and
- the timeout error(EBUSY) scenario. */
Another weird coding style thing.
will fix
+static int sst_device_control(int cmd, void *arg) +{
- int retval = 0, str_id = 0;
- if (sst_drv_ctx->sst_state != SST_FW_RUNNING)
return 0;
- switch (cmd) {
- case SST_SND_START: {
Eew, ioctl(). This would be clearer if it were a series of functions, there's nothing shared here.
+static int sst_set_generic_params(enum sst_controls cmd, void *arg) +{
- int ret_val = 0;
- if (NULL == arg)
return -EINVAL;
Again an ioctl()...
okay will move to series of functions
- /* check busy bit */
- header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
- while (header.p.header_high.part.busy) {
if (loop_count > 10) {
pr_err("sst: Busy wait failed, cant send this msg\n");
retval = -EBUSY;
goto out;
}
udelay(500);
loop_count++;
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
- }
Throw a cpu_relax() in there if we need to spin...
its more busy wait for the bit to be freed. Since IPC latency is supposed to be minimal checking like this helps to minimize.
- pr_info("FW Version %02x.%02x.%02x.%02x\n",
init->fw_version.type, init->fw_version.major,
init->fw_version.minor, init->fw_version.build);
- pr_info("Build date %s Time %s\n",
init->build_info.date, init->build_info.time);
Same comment as for some of the other firmwares - consider providing this via /proc or sysfs as well.
ok
+static inline int sst_validate_elf(const struct firmware *sst_bin, bool dynamic) +{
- Elf32_Ehdr *elf;
- BUG_ON(!sst_bin);
- pr_debug("IN %s\n", __func__);
- elf = (Elf32_Ehdr *)sst_bin->data;
- if ((elf->e_ident[0] != 0x7F) || (elf->e_ident[1] != 'E') ||
(elf->e_ident[2] != 'L') || (elf->e_ident[3] != 'F')) {
pr_debug("ELF Header Not found!%zu\n", sst_bin->size);
return -EINVAL;
- }
- if (dynamic == true) {
if (elf->e_type != ET_DYN) {
pr_err("Not a dynamic loadable library\n");
return -EINVAL;
}
- }
- pr_debug("Valid ELF Header...%zu\n", sst_bin->size);
- return 0;
+}
We have generic ELF handling code in the kernel, should this go there?
will check that
+{
- struct fw_header *header;
struct fw_header should probably be better namespaced?
+void sst_memcpy_free_resources(void) +{
- struct sst_memcpy_list *listnode, *tmplistnode;
- pr_debug("entry:%s\n", __func__);
- /*Free the list*/
- if (!list_empty(&sst_drv_ctx->memcpy_list)) {
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->memcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
- }
- sst_memcpy_free_lib_resources();
+}
I'm having a hard time seeing why we don't just copy the data as we go rather than allocating this list? It seems to just be making the code more complex.
because we support DMA too. so common parsing and then later we either do memcpy and dma. Will push DMA bits after this series.
+/**
- sst_next_track: notify next track
- @str_id: stream ID
- This function is called by any function which wants to
- set next track. Current this is NOP as FW doest care
- */
+int sst_next_track(void) +{
- pr_debug("SST DBG: next_track");
- return 0;
+}
Shouldn't the operation just be made optional then, obviously the upper layers don't care either?
Sure
On Fri, Jul 18, 2014 at 07:43:59PM +0530, Vinod Koul wrote:
On Fri, Jul 18, 2014 at 12:35:48PM +0100, Mark Brown wrote:
On Wed, Jul 09, 2014 at 02:57:52PM +0530, Vinod Koul wrote:
Have you looked at the mailbox framework (not merged yet but hopefully getting close to it)?
Not seeing an answer to this...
- if (list_empty(&drv->rx_list))
return IRQ_HANDLED;
Why would the IRQ thread be running with nothing to do, and if it can be...
Cant think of, we need to remove this!
- spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
- list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
...won't we handle that gracefully anyway?
Are you referring to lock + irqsave?
This is part of the thing about the list_empty() check being redundant.
+static inline int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{
- int ret;
- ret = pm_runtime_put_sync(sst_drv->dev);
- if (ret < 0)
return ret;
- atomic_dec(&sst_drv->pm_usage_count);
- pr_debug("%s: count is %d now..\n", __func__,
atomic_read(&sst_drv->pm_usage_count));
- return 0;
+}
I'm not entirely clear why this is here but if it's to provide trace just instrument the runtime PM core.
Well the only usage of pm_usage_count locally is to check in runtime pm suspend to S3 transitions. When we are runtime suspended and S3 triggers, the device usage count goes up to 1. Then runtime resume and classic suspend is invoked in that order. So if we are in this scenario we can't rely on checks using frameworks count.
Yes, you can. You can check to see if the device is runtime suspended and really this isn't at all specific to this driver - it's a general thing that affects lots of drivers so shouldn't be open coded in this one. IIRC you're looking for pm_runtime_is_suspended().
Essentially any time you need to use atomic variables in a driver you're probably doing something wrong.
+static inline unsigned int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx) +{
- unsigned int local;
- spin_lock(&sst_drv_ctx->block_lock);
- sst_drv_ctx->pvt_id++;
- if (sst_drv_ctx->pvt_id > MAX_BLOCKS)
sst_drv_ctx->pvt_id = 1;
- local = sst_drv_ctx->pvt_id;
- spin_unlock(&sst_drv_ctx->block_lock);
- return local;
+}
I would expect this to be checking to see if the IDs are free but it just hands them out in sequence? There appears to be an array of streams statically allocated, why not look there for an unused one?
we use these incrementally. The driver stamps each IPC to DSP with a number. The size is limited by bits in the IPCs. So we keep going till we hit max, that time we set it back to 1
These ids are not freed or allocated as IPC latency is small enough and IPCs less. We haven't hot scenarios where we go complete round but IPC is still pending :)
Yet.
+int free_stream_context(unsigned int str_id) +{
- struct stream_info *stream;
- int ret = 0;
- stream = get_stream_info(str_id);
- if (stream) {
It's a bit scary that this might be used with an invalid ID - what happens if the ID got reallocated?
The free routine checks for valid ID first
That's not the point - the point is that if you're freeing what might be an invalid ID I don't see any guarantee that the ID hasn't been allocated to something else. It's different if there's a specific ID that can be used for an invalid ID but this looks like it's working around double frees.
- while (header.p.header_high.part.busy) {
if (loop_count > 10) {
pr_err("sst: Busy wait failed, cant send this msg\n");
retval = -EBUSY;
goto out;
}
udelay(500);
loop_count++;
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
- }
Throw a cpu_relax() in there if we need to spin...
its more busy wait for the bit to be freed. Since IPC latency is supposed to be minimal checking like this helps to minimize.
It's a udelay(500) which is pretty long for a busy wait...
- if (!list_empty(&sst_drv_ctx->memcpy_list)) {
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->memcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
- }
- sst_memcpy_free_lib_resources();
+}
I'm having a hard time seeing why we don't just copy the data as we go rather than allocating this list? It seems to just be making the code more complex.
because we support DMA too. so common parsing and then later we either do memcpy and dma. Will push DMA bits after this series.
Keep it simple for now and just memcpy() directly, leaving the functions that select I/O vs memcpy() - you can always go back and add the DMA later.
On Fri, Jul 18, 2014 at 05:59:03PM +0100, Mark Brown wrote:
On Fri, Jul 18, 2014 at 07:43:59PM +0530, Vinod Koul wrote:
On Fri, Jul 18, 2014 at 12:35:48PM +0100, Mark Brown wrote:
On Wed, Jul 09, 2014 at 02:57:52PM +0530, Vinod Koul wrote:
Have you looked at the mailbox framework (not merged yet but hopefully getting close to it)?
Not seeing an answer to this...
Oops.. I had seen one of the revs.
We have single channel, single user system. The DSP driver and DAPM will push messages into a queue and we will keep posting. The framework would have helped if we had multiple channels, multiple users but for now seems a bit heavy for our usage. Am open to revist this if things get simplfied here. The IPC code here is least complex.
- if (list_empty(&drv->rx_list))
return IRQ_HANDLED;
Why would the IRQ thread be running with nothing to do, and if it can be...
Cant think of, we need to remove this!
- spin_lock_irqsave(&drv->rx_msg_lock, irq_flags);
- list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) {
...won't we handle that gracefully anyway?
Are you referring to lock + irqsave?
This is part of the thing about the list_empty() check being redundant.
Okay
+static inline int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{
- int ret;
- ret = pm_runtime_put_sync(sst_drv->dev);
- if (ret < 0)
return ret;
- atomic_dec(&sst_drv->pm_usage_count);
- pr_debug("%s: count is %d now..\n", __func__,
atomic_read(&sst_drv->pm_usage_count));
- return 0;
+}
I'm not entirely clear why this is here but if it's to provide trace just instrument the runtime PM core.
Well the only usage of pm_usage_count locally is to check in runtime pm suspend to S3 transitions. When we are runtime suspended and S3 triggers, the device usage count goes up to 1. Then runtime resume and classic suspend is invoked in that order. So if we are in this scenario we can't rely on checks using frameworks count.
Yes, you can. You can check to see if the device is runtime suspended and really this isn't at all specific to this driver - it's a general thing that affects lots of drivers so shouldn't be open coded in this one. IIRC you're looking for pm_runtime_is_suspended().
sorry didnt find the symbol, was it removed
Essentially any time you need to use atomic variables in a driver you're probably doing something wrong.
No issue, i think i can clean this bit up
+static inline unsigned int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx) +{
- unsigned int local;
- spin_lock(&sst_drv_ctx->block_lock);
- sst_drv_ctx->pvt_id++;
- if (sst_drv_ctx->pvt_id > MAX_BLOCKS)
sst_drv_ctx->pvt_id = 1;
- local = sst_drv_ctx->pvt_id;
- spin_unlock(&sst_drv_ctx->block_lock);
- return local;
+}
I would expect this to be checking to see if the IDs are free but it just hands them out in sequence? There appears to be an array of streams statically allocated, why not look there for an unused one?
we use these incrementally. The driver stamps each IPC to DSP with a number. The size is limited by bits in the IPCs. So we keep going till we hit max, that time we set it back to 1
These ids are not freed or allocated as IPC latency is small enough and IPCs less. We haven't hot scenarios where we go complete round but IPC is still pending :)
Yet.
For this genertaion of HW :) Next gen has different interface though so not worried :)
+int free_stream_context(unsigned int str_id) +{
- struct stream_info *stream;
- int ret = 0;
- stream = get_stream_info(str_id);
- if (stream) {
It's a bit scary that this might be used with an invalid ID - what happens if the ID got reallocated?
The free routine checks for valid ID first
That's not the point - the point is that if you're freeing what might be an invalid ID I don't see any guarantee that the ID hasn't been allocated to something else. It's different if there's a specific ID that can be used for an invalid ID but this looks like it's working around double frees.
The chances of that are remote as ID is unique for a FE. For first device we will always use ID 1. The same device close will not request again, so doble free situation shouldn't arise.
- while (header.p.header_high.part.busy) {
if (loop_count > 10) {
pr_err("sst: Busy wait failed, cant send this msg\n");
retval = -EBUSY;
goto out;
}
udelay(500);
loop_count++;
header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX);
- }
Throw a cpu_relax() in there if we need to spin...
its more busy wait for the bit to be freed. Since IPC latency is supposed to be minimal checking like this helps to minimize.
It's a udelay(500) which is pretty long for a busy wait...
ok
- if (!list_empty(&sst_drv_ctx->memcpy_list)) {
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->memcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
- }
- sst_memcpy_free_lib_resources();
+}
I'm having a hard time seeing why we don't just copy the data as we go rather than allocating this list? It seems to just be making the code more complex.
because we support DMA too. so common parsing and then later we either do memcpy and dma. Will push DMA bits after this series.
Keep it simple for now and just memcpy() directly, leaving the functions that select I/O vs memcpy() - you can always go back and add the DMA later.
well wont help to change code now and rework once this series is posted. I have DMA patches as well, want to send them after this series :) as latency is important.
This patch adds the runtime pm handlers and legacy pm handlers for this driver.
The runtime and legacy handlers are quite similar in nature as we follow same patch for both
Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/sst/sst.c | 126 +++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 126 insertions(+), 0 deletions(-)
diff --git a/sound/soc/intel/sst/sst.c b/sound/soc/intel/sst/sst.c index 916b38c..cb9863c 100644 --- a/sound/soc/intel/sst/sst.c +++ b/sound/soc/intel/sst/sst.c @@ -141,6 +141,47 @@ static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) return IRQ_HANDLED; }
+static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) +{ + unsigned int pvt_id; + struct ipc_post *msg = NULL; + struct ipc_dsp_hdr dsp_hdr; + struct sst_block *block; + + /*send msg to fw*/ + pvt_id = sst_assign_pvt_id(sst); + if (sst_create_block_and_ipc_msg(&msg, true, sst, &block, + IPC_CMD, pvt_id)) { + pr_err("msg/block alloc failed. Not proceeding with context save\n"); + return 0; + } + + sst_fill_header_mrfld(&msg->mrfld_header, IPC_CMD, + SST_TASK_ID_MEDIA, 1, pvt_id); + msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr); + msg->mrfld_header.p.header_high.part.res_rqd = 1; + sst_fill_header_dsp(&dsp_hdr, IPC_PREP_D3, PIPE_RSVD, pvt_id); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + + sst_add_to_dispatch_list_and_post(sst, msg); + /*wait for reply*/ + if (sst_wait_timeout(sst, block)) { + pr_err("sst: err fw context save timeout ...\n"); + pr_err("not suspending FW!!!"); + sst_free_block(sst, block); + return -EIO; + } + if (block->ret_code) { + pr_err("fw responded w/ error %d", block->ret_code); + sst_free_block(sst, block); + return -EIO; + } + + sst_free_block(sst, block); + return 0; +} + + static struct intel_sst_ops mrfld_ops = { .interrupt = intel_sst_interrupt_mrfld, .irq_thread = intel_sst_irq_thread_mrfld, @@ -150,6 +191,7 @@ static struct intel_sst_ops mrfld_ops = { .post_message = sst_post_message_mrfld, .sync_post_message = sst_sync_post_message_mrfld, .process_reply = sst_process_reply_mrfld, + .save_dsp_context = sst_save_dsp_context_v2, .alloc_stream = sst_alloc_stream_mrfld, .post_download = sst_post_download_mrfld, }; @@ -423,6 +465,85 @@ static void intel_sst_remove(struct pci_dev *pci) pci_set_drvdata(pci, NULL); }
+/* + * The runtime_suspend/resume is pretty much similar to the legacy + * suspend/resume with the noted exception below: The PCI core takes care of + * taking the system through D3hot and restoring it back to D0 and so there is + * no need to duplicate that here. + */ +static int intel_sst_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + pr_info("runtime_suspend called\n"); + if (ctx->sst_state == SST_RESET) { + pr_debug("LPE is already in RESET state, No action"); + return 0; + } + /*save fw context*/ + if (ctx->ops->save_dsp_context(ctx)) + return -EBUSY; + + /* Move the SST state to Reset */ + sst_set_fw_state_locked(ctx, SST_RESET); + + flush_workqueue(ctx->post_msg_wq); + synchronize_irq(ctx->irq_num); + + return ret; +} + +static int intel_sst_runtime_resume(struct device *dev) +{ + int ret = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + pr_info("runtime_resume called\n"); + + /* When fw_clear_cache is set, clear the cached firmware copy */ + /* fw_clear_cache is set through debugfs support */ + if (atomic_read(&ctx->fw_clear_cache) && ctx->fw_in_mem) { + pr_debug("Clearing the cached firmware\n"); + kfree(ctx->fw_in_mem); + ctx->fw_in_mem = NULL; + atomic_set(&ctx->fw_clear_cache, 0); + } + + sst_set_fw_state_locked(ctx, SST_RESET); + + return ret; +} + +static int intel_sst_suspend(struct device *dev) +{ + + return intel_sst_runtime_suspend(dev); +} + +static int intel_sst_runtime_idle(struct device *dev) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + pr_info("runtime_idle called\n"); + if (ctx->sst_state != SST_RESET) { + pm_schedule_suspend(dev, SST_SUSPEND_DELAY); + return -EBUSY; + } else { + return 0; + } + return -EBUSY; + +} + +static const struct dev_pm_ops intel_sst_pm = { + .suspend = intel_sst_suspend, + .resume = intel_sst_runtime_resume, + .runtime_suspend = intel_sst_runtime_suspend, + .runtime_resume = intel_sst_runtime_resume, + .runtime_idle = intel_sst_runtime_idle, +}; + /* PCI Routines */ static struct pci_device_id intel_sst_ids[] = { { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, @@ -434,6 +555,11 @@ static struct pci_driver sst_driver = { .id_table = intel_sst_ids, .probe = intel_sst_probe, .remove = intel_sst_remove, +#ifdef CONFIG_PM + .driver = { + .pm = &intel_sst_pm, + }, +#endif };
module_pci_driver(sst_driver);
On Wed, Jul 09, 2014 at 02:57:53PM +0530, Vinod Koul wrote:
+static int sst_save_dsp_context_v2(struct intel_sst_drv *sst)
_v2?
+static int intel_sst_runtime_suspend(struct device *dev) +{
- int ret = 0;
- struct intel_sst_drv *ctx = dev_get_drvdata(dev);
- pr_info("runtime_suspend called\n");
This is way too noisy.
- if (ctx->sst_state == SST_RESET) {
pr_debug("LPE is already in RESET state, No action");
return 0;
- }
- /*save fw context*/
- if (ctx->ops->save_dsp_context(ctx))
return -EBUSY;
- /* Move the SST state to Reset */
- sst_set_fw_state_locked(ctx, SST_RESET);
- flush_workqueue(ctx->post_msg_wq);
- synchronize_irq(ctx->irq_num);
This is very strange - we're flushing the work *after* resetting the DSP which suggests there might be something else trying to access the DSP while it is being put into reset. Presumably that'd be bad.
- /* When fw_clear_cache is set, clear the cached firmware copy */
- /* fw_clear_cache is set through debugfs support */
- if (atomic_read(&ctx->fw_clear_cache) && ctx->fw_in_mem) {
Why? It'd seem more direct to just have the debugfs write trigger trashing of the firmware on demand...
+static int intel_sst_suspend(struct device *dev) +{
- return intel_sst_runtime_suspend(dev);
+}
You currently need to check that the device isn't runtime suspended before you try to reuse runtime PM ops in suspend.
+static int intel_sst_runtime_idle(struct device *dev) +{
- struct intel_sst_drv *ctx = dev_get_drvdata(dev);
- pr_info("runtime_idle called\n");
- if (ctx->sst_state != SST_RESET) {
pm_schedule_suspend(dev, SST_SUSPEND_DELAY);
return -EBUSY;
- } else {
return 0;
- }
- return -EBUSY;
+}
This is very strange, what is going on?
On Fri, Jul 18, 2014 at 12:46:11PM +0100, Mark Brown wrote:
On Wed, Jul 09, 2014 at 02:57:53PM +0530, Vinod Koul wrote:
+static int sst_save_dsp_context_v2(struct intel_sst_drv *sst)
_v2?
v1 support older moorestown platforms. SO v2 here :)
+static int intel_sst_runtime_suspend(struct device *dev) +{
- int ret = 0;
- struct intel_sst_drv *ctx = dev_get_drvdata(dev);
- pr_info("runtime_suspend called\n");
This is way too noisy.
- if (ctx->sst_state == SST_RESET) {
pr_debug("LPE is already in RESET state, No action");
return 0;
- }
- /*save fw context*/
- if (ctx->ops->save_dsp_context(ctx))
return -EBUSY;
- /* Move the SST state to Reset */
- sst_set_fw_state_locked(ctx, SST_RESET);
- flush_workqueue(ctx->post_msg_wq);
- synchronize_irq(ctx->irq_num);
This is very strange - we're flushing the work *after* resetting the DSP which suggests there might be something else trying to access the DSP while it is being put into reset. Presumably that'd be bad.
the macro sst_set_fw_state_locked is used here :)
And state is marked reset, we dont do HW reset, so this is okay. After we return the suspend handler, PCI will put the device to D3.
- /* When fw_clear_cache is set, clear the cached firmware copy */
- /* fw_clear_cache is set through debugfs support */
- if (atomic_read(&ctx->fw_clear_cache) && ctx->fw_in_mem) {
Why? It'd seem more direct to just have the debugfs write trigger trashing of the firmware on demand...
That would work too but then sequencing the DSP while running would cause issues elsewhere. Since we dont have debugs support in the series we cna push this bits later when we send debugfs series amoung others :)
+static int intel_sst_suspend(struct device *dev) +{
- return intel_sst_runtime_suspend(dev);
+}
You currently need to check that the device isn't runtime suspended before you try to reuse runtime PM ops in suspend.
Sorry didnt quite get why?
+static int intel_sst_runtime_idle(struct device *dev) +{
- struct intel_sst_drv *ctx = dev_get_drvdata(dev);
- pr_info("runtime_idle called\n");
- if (ctx->sst_state != SST_RESET) {
pm_schedule_suspend(dev, SST_SUSPEND_DELAY);
return -EBUSY;
- } else {
return 0;
- }
- return -EBUSY;
+}
This is very strange, what is going on?
schedule suspend after SST_SUSPEND_DELAY. This should be updated with PM framework calls for delay, will update
On Fri, Jul 18, 2014 at 07:53:09PM +0530, Vinod Koul wrote:
On Fri, Jul 18, 2014 at 12:46:11PM +0100, Mark Brown wrote:
- /* Move the SST state to Reset */
- sst_set_fw_state_locked(ctx, SST_RESET);
- flush_workqueue(ctx->post_msg_wq);
- synchronize_irq(ctx->irq_num);
This is very strange - we're flushing the work *after* resetting the DSP which suggests there might be something else trying to access the DSP while it is being put into reset. Presumably that'd be bad.
the macro sst_set_fw_state_locked is used here :)
And state is marked reset, we dont do HW reset, so this is okay. After we return the suspend handler, PCI will put the device to D3.
It's still setting off alarm bells from a code review point of view - having the DSP marked as in reset may well break something that some of the other activity that's going on is doing.
+static int intel_sst_suspend(struct device *dev) +{
- return intel_sst_runtime_suspend(dev);
+}
You currently need to check that the device isn't runtime suspended before you try to reuse runtime PM ops in suspend.
Sorry didnt quite get why?
The driver should be restoring the device to the state it was in prior to suspend, and not repeating work that's already been done.
On Fri, Jul 18, 2014 at 06:14:01PM +0100, Mark Brown wrote:
On Fri, Jul 18, 2014 at 07:53:09PM +0530, Vinod Koul wrote:
On Fri, Jul 18, 2014 at 12:46:11PM +0100, Mark Brown wrote:
- /* Move the SST state to Reset */
- sst_set_fw_state_locked(ctx, SST_RESET);
- flush_workqueue(ctx->post_msg_wq);
- synchronize_irq(ctx->irq_num);
This is very strange - we're flushing the work *after* resetting the DSP which suggests there might be something else trying to access the DSP while it is being put into reset. Presumably that'd be bad.
the macro sst_set_fw_state_locked is used here :)
And state is marked reset, we dont do HW reset, so this is okay. After we return the suspend handler, PCI will put the device to D3.
It's still setting off alarm bells from a code review point of view - having the DSP marked as in reset may well break something that some of the other activity that's going on is doing.
yes that is a fair argument. But the next two calls will push any pending messages to DSP. The reset will stop any client calls.
But then we have runtime code too, so while we are suspending that will be waiting, so yes will move reset later.
+static int intel_sst_suspend(struct device *dev) +{
- return intel_sst_runtime_suspend(dev);
+}
You currently need to check that the device isn't runtime suspended before you try to reuse runtime PM ops in suspend.
Sorry didnt quite get why?
The driver should be restoring the device to the state it was in prior to suspend, and not repeating work that's already been done.
we dont support suspending when active. The Android usage model is that system will not be S3 if audio is running. So no extra save work is required here.
Moreover it needs active DSP FW support in order to save and restore, so we cant do yet. Yes it will make sense to check device acticty and complain loudly if we are in such a state, will add that.
We would like the DSP firmware to be availble in driver as soon as possible. So use the async callback in driver to probe to load the firmware as soon as usermode is up
Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/sst/sst.c | 21 +++++++++++++++++++++ 1 files changed, 21 insertions(+), 0 deletions(-)
diff --git a/sound/soc/intel/sst/sst.c b/sound/soc/intel/sst/sst.c index cb9863c..ae1f0eb 100644 --- a/sound/soc/intel/sst/sst.c +++ b/sound/soc/intel/sst/sst.c @@ -224,6 +224,22 @@ int sst_alloc_drv_context(struct device *dev) return 0; }
+int sst_request_firmware_async(struct intel_sst_drv *ctx) +{ + int ret = 0; + + snprintf(ctx->firmware_name, sizeof(ctx->firmware_name), + "%s%04x%s", "fw_sst_", + ctx->pci_id, ".bin"); + pr_debug("Requesting FW %s now...\n", ctx->firmware_name); + + ret = request_firmware_nowait(THIS_MODULE, 1, ctx->firmware_name, + ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb); + if (ret) + pr_err("could not load firmware %s error %d\n", ctx->firmware_name, ret); + + return ret; +} /* * intel_sst_probe - PCI probe function * @@ -293,6 +309,11 @@ static int intel_sst_probe(struct pci_dev *pci, mutex_init(&stream->lock); }
+ ret = sst_request_firmware_async(sst_drv_ctx); + if (ret) { + pr_err("Firmware download failed:%d\n", ret); + goto do_free_mem; + } /* Init the device */ ret = pci_enable_device(pci); if (ret) {
This patch add low level IPC handling for compressed stream operations
Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/sst/sst_drv_interface.c | 276 +++++++++++++++++++++++++++++++ 1 files changed, 276 insertions(+), 0 deletions(-)
diff --git a/sound/soc/intel/sst/sst_drv_interface.c b/sound/soc/intel/sst/sst_drv_interface.c index 1ad3083..c3d025d 100644 --- a/sound/soc/intel/sst/sst_drv_interface.c +++ b/sound/soc/intel/sst/sst_drv_interface.c @@ -282,6 +282,269 @@ static int sst_open_pcm_stream(struct snd_sst_params *str_param) return retval; }
+static int sst_cdev_open(struct snd_sst_params *str_params, + struct sst_compress_cb *cb) +{ + int str_id, retval; + struct stream_info *stream; + + pr_debug("%s: doing rtpm_get\n", __func__); + + retval = intel_sst_check_device(); + if (retval) + return retval; + + str_id = sst_get_stream(str_params); + if (str_id > 0) { + pr_debug("stream allocated in sst_cdev_open %d\n", str_id); + stream = &sst_drv_ctx->streams[str_id]; + stream->compr_cb = cb->compr_cb; + stream->compr_cb_param = cb->param; + stream->drain_notify = cb->drain_notify; + stream->drain_cb_param = cb->drain_cb_param; + } else { + pr_err("stream encountered error during alloc %d\n", str_id); + str_id = -EINVAL; + sst_pm_runtime_put(sst_drv_ctx); + } + return str_id; +} + +static int sst_cdev_close(unsigned int str_id) +{ + int retval; + struct stream_info *stream; + + pr_debug("%s: Entry\n", __func__); + stream = get_stream_info(str_id); + if (!stream) { + pr_err("stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + if (stream->status == STREAM_RESET) { + /* silently fail here as we have cleaned the stream */ + pr_debug("stream in reset state...\n"); + stream->status = STREAM_UN_INIT; + + retval = 0; + goto put; + } + + retval = sst_free_stream(str_id); +put: + stream->compr_cb_param = NULL; + stream->compr_cb = NULL; + + /* The free_stream will return a error if there is no stream to free, + (i.e. the alloc failure case). And in this case the open does a put in + the error scenario, so skip in this case. + In the close we need to handle put in the success scenario and + the timeout error(EBUSY) scenario. */ + if (!retval || (retval == -EBUSY)) + sst_pm_runtime_put(sst_drv_ctx); + else + pr_err("%s: free stream returned err %d\n", __func__, retval); + + pr_debug("%s: End\n", __func__); + return retval; + +} + +static int sst_cdev_ack(unsigned int str_id, unsigned long bytes) +{ + struct stream_info *stream; + struct snd_sst_tstamp fw_tstamp = {0,}; + int offset; + void __iomem *addr; + + pr_debug("sst: ackfor %d\n", str_id); + stream = get_stream_info(str_id); + if (!stream) + return -EINVAL; + + /* update bytes sent */ + stream->cumm_bytes += bytes; + pr_debug("bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes); + + memcpy_fromio(&fw_tstamp, + ((void *)(sst_drv_ctx->mailbox + sst_drv_ctx->tstamp) + +(str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + + fw_tstamp.bytes_copied = stream->cumm_bytes; + pr_debug("bytes sent to fw %llu inc by %ld\n", fw_tstamp.bytes_copied, + bytes); + + addr = ((void *)(sst_drv_ctx->mailbox + sst_drv_ctx->tstamp)) + + (str_id * sizeof(fw_tstamp)); + offset = offsetof(struct snd_sst_tstamp, bytes_copied); + sst_shim_write(addr, offset, fw_tstamp.bytes_copied); + return 0; + +} + +static int sst_cdev_set_metadata(unsigned int str_id, + struct snd_compr_metadata *metadata) +{ + int retval = 0, pvt_id, len; + struct ipc_post *msg = NULL; + struct stream_info *str_info; + struct ipc_dsp_hdr dsp_hdr; + + pr_debug("set metadata for stream %d\n", str_id); + + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + + if (sst_create_ipc_msg(&msg, 1)) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + pr_debug("pvt id = %d\n", pvt_id); + pr_debug("pipe id = %d\n", str_info->pipe_id); + sst_fill_header_mrfld(&msg->mrfld_header, + IPC_CMD, str_info->task_id, 1, pvt_id); + + len = sizeof(*metadata) + sizeof(dsp_hdr); + msg->mrfld_header.p.header_low_payload = len; + sst_fill_header_dsp(&dsp_hdr, IPC_IA_SET_STREAM_PARAMS_MRFLD, + str_info->pipe_id, sizeof(*metadata)); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + memcpy(msg->mailbox_data + sizeof(dsp_hdr), + metadata, sizeof(*metadata)); + + sst_drv_ctx->ops->sync_post_message(msg); + return retval; +} + +static int sst_cdev_control(unsigned int cmd, unsigned int str_id) +{ + pr_debug("recieved cmd %d on stream %d\n", cmd, str_id); + + if (sst_drv_ctx->sst_state != SST_FW_RUNNING) + return 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return sst_pause_stream(str_id); + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + return sst_resume_stream(str_id); + case SNDRV_PCM_TRIGGER_START: { + struct stream_info *str_info; + + str_info = get_stream_info(str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + return sst_start_stream(str_id); + } + case SNDRV_PCM_TRIGGER_STOP: + return sst_drop_stream(str_id); + case SND_COMPR_TRIGGER_DRAIN: + return sst_drain_stream(str_id, false); + case SND_COMPR_TRIGGER_NEXT_TRACK: + return sst_next_track(); + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + return sst_drain_stream(str_id, true); + default: + return -EINVAL; + } +} + +static int sst_cdev_tstamp(unsigned int str_id, struct snd_compr_tstamp *tstamp) +{ + struct snd_sst_tstamp fw_tstamp = {0,}; + struct stream_info *stream; + + memcpy_fromio(&fw_tstamp, + ((void *)(sst_drv_ctx->mailbox + sst_drv_ctx->tstamp) + +(str_id * sizeof(fw_tstamp))), + sizeof(fw_tstamp)); + + stream = get_stream_info(str_id); + if (!stream) + return -EINVAL; + pr_debug("rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter); + + tstamp->copied_total = fw_tstamp.ring_buffer_counter; + tstamp->pcm_frames = fw_tstamp.frames_decoded; + tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter, + (u64)((stream->num_ch) * SST_GET_BYTES_PER_SAMPLE(24))); + tstamp->sampling_rate = fw_tstamp.sampling_frequency; + pr_debug("PCM = %u\n", tstamp->pcm_io_frames); + pr_debug("Pointer Query on strid = %d copied_total %d, decodec %d\n", + str_id, tstamp->copied_total, tstamp->pcm_frames); + pr_debug("rendered %d\n", tstamp->pcm_io_frames); + return 0; +} + +static int sst_cdev_caps(struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + return 0; +} + +static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec) +{ + + if (codec->codec == SND_AUDIOCODEC_MP3) { + codec->num_descriptors = 2; + codec->descriptor[0].max_ch = 2; + codec->descriptor[0].sample_rates[0] = 48000; + codec->descriptor[0].sample_rates[1] = 44100; + codec->descriptor[0].sample_rates[2] = 32000; + codec->descriptor[0].sample_rates[3] = 16000; + codec->descriptor[0].sample_rates[4] = 8000; + codec->descriptor[0].num_sample_rates = 5; + codec->descriptor[0].bit_rate[0] = 320; /* 320kbps */ + codec->descriptor[0].bit_rate[1] = 192; + codec->descriptor[0].num_bitrates = 2; + codec->descriptor[0].profiles = 0; + codec->descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO; + codec->descriptor[0].formats = 0; + } else if (codec->codec == SND_AUDIOCODEC_AAC) { + codec->num_descriptors = 2; + codec->descriptor[1].max_ch = 2; + codec->descriptor[0].sample_rates[0] = 48000; + codec->descriptor[0].sample_rates[1] = 44100; + codec->descriptor[0].sample_rates[2] = 32000; + codec->descriptor[0].sample_rates[3] = 16000; + codec->descriptor[0].sample_rates[4] = 8000; + codec->descriptor[0].num_sample_rates = 5; + codec->descriptor[1].bit_rate[0] = 320; /* 320kbps */ + codec->descriptor[1].bit_rate[1] = 192; + codec->descriptor[1].num_bitrates = 2; + codec->descriptor[1].profiles = 0; + codec->descriptor[1].modes = 0; + codec->descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | + SND_AUDIOSTREAMFORMAT_RAW); + } else { + return -EINVAL; + } + + return 0; +} + +void sst_cdev_fragment_elapsed(int str_id) +{ + struct stream_info *stream; + + pr_debug("fragment elapsed from firmware for str_id %d\n", str_id); + stream = &sst_drv_ctx->streams[str_id]; + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); +} + /* * sst_close_pcm_stream - Close PCM interface * @@ -514,10 +777,23 @@ static struct sst_ops pcm_ops = { .close = sst_close_pcm_stream, };
+static struct compress_sst_ops compr_ops = { + .open = sst_cdev_open, + .close = sst_cdev_close, + .control = sst_cdev_control, + .tstamp = sst_cdev_tstamp, + .ack = sst_cdev_ack, + .get_caps = sst_cdev_caps, + .get_codec_caps = sst_cdev_codec_caps, + .set_metadata = sst_cdev_set_metadata, +}; + + static struct sst_device sst_dsp_device = { .name = "Intel(R) SST LPE", .dev = NULL, .ops = &pcm_ops, + .compr_ops = &compr_ops, };
/*
On Wed, Jul 09, 2014 at 02:57:55PM +0530, Vinod Koul wrote:
- if (codec->codec == SND_AUDIOCODEC_MP3) {
codec->num_descriptors = 2;
codec->descriptor[0].max_ch = 2;
codec->descriptor[0].sample_rates[0] = 48000;
codec->descriptor[0].sample_rates[1] = 44100;
codec->descriptor[0].sample_rates[2] = 32000;
codec->descriptor[0].sample_rates[3] = 16000;
codec->descriptor[0].sample_rates[4] = 8000;
codec->descriptor[0].num_sample_rates = 5;
codec->descriptor[0].bit_rate[0] = 320; /* 320kbps */
codec->descriptor[0].bit_rate[1] = 192;
codec->descriptor[0].num_bitrates = 2;
codec->descriptor[0].profiles = 0;
codec->descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO;
codec->descriptor[0].formats = 0;
It'd be more idiomatic to copy a static variable.
On Fri, Jul 18, 2014 at 12:48:23PM +0100, Mark Brown wrote:
On Wed, Jul 09, 2014 at 02:57:55PM +0530, Vinod Koul wrote:
- if (codec->codec == SND_AUDIOCODEC_MP3) {
codec->num_descriptors = 2;
codec->descriptor[0].max_ch = 2;
codec->descriptor[0].sample_rates[0] = 48000;
codec->descriptor[0].sample_rates[1] = 44100;
codec->descriptor[0].sample_rates[2] = 32000;
codec->descriptor[0].sample_rates[3] = 16000;
codec->descriptor[0].sample_rates[4] = 8000;
codec->descriptor[0].num_sample_rates = 5;
codec->descriptor[0].bit_rate[0] = 320; /* 320kbps */
codec->descriptor[0].bit_rate[1] = 192;
codec->descriptor[0].num_bitrates = 2;
codec->descriptor[0].profiles = 0;
codec->descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO;
codec->descriptor[0].formats = 0;
It'd be more idiomatic to copy a static variable.
will update...
This path series add the Merrifield DSP driver which is used to load and managed the DSP along with sending and receiving IPCs for PCM and compressed audio
Vinod Koul (8): ASoC: Intel: add sst shim register start-end variables ASoC: Intel: mfld: add dsp error codes ASoC: Intel: mrlfd - add generic parameter interface ASoC: Intel: mrfld - add the dsp sst driver ASoC: Intel: sst: add power management handling ASoC: Intel: sst: load firmware using async callback ASoC: Intel: sst - add compressed ops handling compile
arch/x86/include/asm/platform_sst_audio.h | 33 + sound/soc/intel/Kconfig | 6 +- sound/soc/intel/Makefile | 3 + sound/soc/intel/sst-dsp.h | 4 + sound/soc/intel/sst-mfld-dsp.h | 15 + sound/soc/intel/sst-mfld-platform.h | 5 +- sound/soc/intel/sst/Makefile | 3 + sound/soc/intel/sst/sst.c | 586 ++++++++++++++++++ sound/soc/intel/sst/sst.h | 666 ++++++++++++++++++++ sound/soc/intel/sst/sst_drv_interface.c | 819 +++++++++++++++++++++++++ sound/soc/intel/sst/sst_ipc.c | 368 +++++++++++ sound/soc/intel/sst/sst_loader.c | 951 +++++++++++++++++++++++++++++ sound/soc/intel/sst/sst_pvt.c | 208 +++++++ sound/soc/intel/sst/sst_stream.c | 534 ++++++++++++++++ 14 files changed, 4199 insertions(+), 2 deletions(-) create mode 100644 sound/soc/intel/sst/Makefile create mode 100644 sound/soc/intel/sst/sst.c create mode 100644 sound/soc/intel/sst/sst.h create mode 100644 sound/soc/intel/sst/sst_drv_interface.c create mode 100644 sound/soc/intel/sst/sst_ipc.c create mode 100644 sound/soc/intel/sst/sst_loader.c create mode 100644 sound/soc/intel/sst/sst_pvt.c create mode 100644 sound/soc/intel/sst/sst_stream.c
participants (2)
-
Mark Brown
-
Vinod Koul