From: "Subhransu S. Prusty" subhransu.s.prusty@intel.com
This adds APIs for Tx/Rx of IPC to dsp, manager DSP pipeline.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/soc-hda-sst-dsp.h | 4 + include/sound/soc-hda-sst-ipc.h | 134 +++++ sound/soc/hda/intel/Makefile | 5 + sound/soc/hda/intel/soc-hda-sst-ipc.c | 963 +++++++++++++++++++++++++++++++++ 4 files changed, 1106 insertions(+) create mode 100644 include/sound/soc-hda-sst-ipc.h create mode 100644 sound/soc/hda/intel/Makefile create mode 100644 sound/soc/hda/intel/soc-hda-sst-ipc.c
diff --git a/include/sound/soc-hda-sst-dsp.h b/include/sound/soc-hda-sst-dsp.h index daeee1840b94..44e8d67aab3a 100644 --- a/include/sound/soc-hda-sst-dsp.h +++ b/include/sound/soc-hda-sst-dsp.h @@ -112,6 +112,7 @@ struct ssth_window {
struct ssth_lib { struct device *dev; + struct ssth_ipc *ipc; void __iomem *mmio_base; struct ssth_window window; int irq; @@ -120,6 +121,9 @@ struct ssth_lib { struct mutex sst_lock; spinlock_t reg_lock; const struct firmware *fw; + + struct workqueue_struct *intr_wq; + struct work_struct ipc_process_msg_work; };
enum ssth_states { diff --git a/include/sound/soc-hda-sst-ipc.h b/include/sound/soc-hda-sst-ipc.h new file mode 100644 index 000000000000..2553a027a51a --- /dev/null +++ b/include/sound/soc-hda-sst-ipc.h @@ -0,0 +1,134 @@ +/* + * Intel SST IPC Support + * + * Copyright (C) 2014-15, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __HDA_SST_IPC_H +#define __HDA_SST_IPC_H + +#include <linux/kthread.h> +#include <linux/irqreturn.h> + + +struct ssth_lib; + +enum ssth_pipeline_state { + PPL_INVALID_STATE = 0, + PPL_UNINITIALIZED = 1, + PPL_RESET = 2, + PPL_PAUSED = 3, + PPL_RUNNING = 4, + PPL_ERROR_STOP = 5, + PPL_SAVED = 6, + PPL_RESTORED = 7 +}; + +struct ssth_ipc_header { + u32 primary; + u32 extension; +}; + +struct ssth_ipc *ssth_ipc_init( + struct device *dev, + struct ssth_lib *dsp); + +/* IPC data */ +struct ssth_ipc { + struct device *dev; + struct ssth_lib *dsp; + +/* IPC messaging */ + struct list_head tx_list; + struct list_head rx_list; + struct list_head empty_list; + wait_queue_head_t wait_txq; + spinlock_t ipc_lock; + struct task_struct *tx_thread; + struct kthread_worker kworker; + struct kthread_work kwork; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + bool shutdown; +}; + +struct ssth_init_instance_msg { + u32 module_id; + u32 instance_id; + u16 param_data_size; + u8 ppl_instance_id; + u8 core_id; +}; + +struct ssth_bind_unbind_msg { + u32 module_id; + u32 instance_id; + u32 dst_module_id; + u32 dst_instance_id; + u8 src_queue; + u8 dst_queue; + bool bind; +}; + +struct ssth_large_config_msg { + u32 module_id; + u32 instance_id; + u32 large_param_id; + u32 param_data_size; +}; + +#define IPC_BOOT_MSECS 3000 + +struct ssth_dxstate_info { + u32 core_mask; + u32 dx_mask; +}; + +#define ADSP_IPC_D3_MASK 0 +#define ADSP_IPC_D0_MASK 3 + +void ssth_ipc_process_msg(struct work_struct *work); + +int ssth_ipc_tx_message_wait(struct ssth_ipc *sst_ipc, + struct ssth_ipc_header header, void *tx_data, size_t tx_bytes, void *rx_data, + size_t rx_bytes); + +int ssth_ipc_create_pipeline(struct ssth_ipc *sst_ipc, u16 ppl_mem_size, + u8 ppl_type, u8 instance_id); + +int ssth_ipc_delete_pipeline(struct ssth_ipc *sst_ipc, u8 instance_id); + +int ssth_ipc_set_pipeline_state(struct ssth_ipc *sst_ipc, u8 instance_id, + enum ssth_pipeline_state state); + +int ssth_ipc_init_instance(struct ssth_ipc *sst_ipc, struct ssth_init_instance_msg *msg, + void *param_data); + +int ssth_ipc_bind_unbind(struct ssth_ipc *sst_ipc, struct ssth_bind_unbind_msg *msg); + +int ssth_ipc_set_dx(struct ssth_ipc *ipc, u8 instance_id, u16 module_id, + struct ssth_dxstate_info *dx); + +int ssth_ipc_set_large_config(struct ssth_ipc *ipc, struct ssth_large_config_msg *msg, + u32 *param); + +void ssth_ipc_int_enable(struct ssth_lib *dsp); +void ssth_ipc_op_int_enable(struct ssth_lib *ctx); +void ssth_ipc_int_disable(struct ssth_lib *dsp); + +bool ssth_ipc_int_status(struct ssth_lib *dsp); +void ssth_ipc_free(struct ssth_ipc *ipc); + +#endif /* __HDA_SST_IPC_H */ diff --git a/sound/soc/hda/intel/Makefile b/sound/soc/hda/intel/Makefile new file mode 100644 index 000000000000..77c44428f40d --- /dev/null +++ b/sound/soc/hda/intel/Makefile @@ -0,0 +1,5 @@ + +snd-soc-hda-sst-dsp-objs := soc-hda-sst-ipc.o soc-hda-sst-dsp.o + +# SST DSP Library +obj-$(CONFIG_SND_SOC_HDA_SST_DSP) += snd-soc-hda-sst-dsp.o diff --git a/sound/soc/hda/intel/soc-hda-sst-ipc.c b/sound/soc/hda/intel/soc-hda-sst-ipc.c new file mode 100644 index 000000000000..b99f90f23244 --- /dev/null +++ b/sound/soc/hda/intel/soc-hda-sst-ipc.c @@ -0,0 +1,963 @@ +/* + * Intel skl IPC Support + * + * Copyright (C) 2014-15, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/kthread.h> +#include <linux/firmware.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/irqreturn.h> +#include <sound/soc-hda-sst-dsp.h> +#include <sound/soc-hda-sst-ipc.h> + +#define IXC_STATUS_BITS 24 + +/* Global Message - Generic */ +#define IPC_GLB_TYPE_SHIFT 24 +#define IPC_GLB_TYPE_MASK (0xf << IPC_GLB_TYPE_SHIFT) +#define IPC_GLB_TYPE(x) ((x) << IPC_GLB_TYPE_SHIFT) + +/* Global Message - Reply */ +#define IPC_GLB_REPLY_STATUS_SHIFT 24 +#define IPC_GLB_REPLY_STATUS_MASK ((0x1 << IPC_GLB_REPLY_STATUS_SHIFT) - 1) +#define IPC_GLB_REPLY_STATUS(x) ((x) << IPC_GLB_REPLY_STATUS_SHIFT) + +#define IPC_TIMEOUT_MSECS 3000 + +#define IPC_EMPTY_LIST_SIZE 8 + +#define IPC_MSG_TARGET_SHIFT 30 +#define IPC_MSG_TARGET_MASK 0x1 +#define IPC_MSG_TARGET(x) (((x) & IPC_MSG_TARGET_MASK) \ + << IPC_MSG_TARGET_SHIFT) + +#define IPC_MSG_DIR_SHIFT 29 +#define IPC_MSG_DIR_MASK 0x1 +#define IPC_MSG_DIR(x) (((x) & IPC_MSG_DIR_MASK) \ + << IPC_MSG_DIR_SHIFT) +/*Global Notofication Message*/ +#define IPC_GLB_NOTIFI_TYPE_SHIFT 16 +#define IPC_GLB_NOTIFI_TYPE_MASK 0xFF +#define IPC_GLB_NOTIFI_TYPE(x) (((x) >> IPC_GLB_NOTIFI_TYPE_SHIFT) \ + & IPC_GLB_NOTIFI_TYPE_MASK) + +#define IPC_GLB_NOTIFI_MSG_TYPE_SHIFT 24 +#define IPC_GLB_NOTIFI_MSG_TYPE_MASK 0x1F +#define IPC_GLB_NOTIFI_MSG_TYPE(x) (((x) >> IPC_GLB_NOTIFI_MSG_TYPE_SHIFT) & IPC_GLB_NOTIFI_MSG_TYPE_MASK) + +#define IPC_GLB_NOTIFI_RSP_SHIFT 29 +#define IPC_GLB_NOTIFI_RSP_MASK 0x1 +#define IPC_GLB_NOTIFI_RSP_TYPE(x) (((x) >> IPC_GLB_NOTIFI_RSP_SHIFT) \ + & IPC_GLB_NOTIFI_RSP_MASK) + +/** Pipeline operations **/ +/* Create pipeline message */ +#define IPC_PPL_MEM_SIZE_SHIFT 0 +#define IPC_PPL_MEM_SIZE_MASK 0x7FF +#define IPC_PPL_MEM_SIZE(x) (((x) & IPC_PPL_MEM_SIZE_MASK) \ + << IPC_PPL_MEM_SIZE_SHIFT) + +#define IPC_PPL_TYPE_SHIFT 11 +#define IPC_PPL_TYPE_MASK 0x1F +#define IPC_PPL_TYPE(x) (((x) & IPC_PPL_TYPE_MASK) \ + << IPC_PPL_TYPE_SHIFT) + +#define IPC_INSTANCE_ID_SHIFT 16 +#define IPC_INSTANCE_ID_MASK 0xFF +#define IPC_INSTANCE_ID(x) (((x) & IPC_INSTANCE_ID_MASK) \ + << IPC_INSTANCE_ID_SHIFT) + +/* Set pipeline state message */ +#define IPC_PPL_STATE_SHIFT 0 +#define IPC_PPL_STATE_MASK 0x1F +#define IPC_PPL_STATE(x) (((x) & IPC_PPL_STATE_MASK) \ + << IPC_PPL_STATE_SHIFT) + +/** Module operations **/ +/* primary register */ +#define IPC_MODULE_ID_SHIFT 0 +#define IPC_MODULE_ID_MASK 0xFFFF +#define IPC_MODULE_ID(x) (((x) & IPC_MODULE_ID_MASK) \ + << IPC_MODULE_ID_SHIFT) + +#define IPC_MODULE_INSTANCE_ID_SHIFT 16 +#define IPC_MODULE_INSTANCE_ID_MASK 0xFF +#define IPC_MODULE_INSTANCE_ID(x) (((x) & IPC_MODULE_INSTANCE_ID_MASK) \ + << IPC_MODULE_INSTANCE_ID_SHIFT) + +/* Init instance message */ +/* extension register */ +#define IPC_PARAM_BLOCK_SIZE_SHIFT 0 +#define IPC_PARAM_BLOCK_SIZE_MASK 0xFFFF +#define IPC_PARAM_BLOCK_SIZE(x) (((x) & IPC_PARAM_BLOCK_SIZE_MASK) \ + << IPC_PARAM_BLOCK_SIZE_SHIFT) + +#define IPC_PPL_INSTANCE_ID_SHIFT 16 +#define IPC_PPL_INSTANCE_ID_MASK 0xFF +#define IPC_PPL_INSTANCE_ID(x) (((x) & IPC_PPL_INSTANCE_ID_MASK) \ + << IPC_PPL_INSTANCE_ID_SHIFT) + +#define IPC_CORE_ID_SHIFT 24 +#define IPC_CORE_ID_MASK 0x1F +#define IPC_CORE_ID(x) (((x) & IPC_CORE_ID_MASK) \ + << IPC_CORE_ID_SHIFT) + +/* Bind/Unbind message */ +/* extension register */ +#define IPC_DST_MODULE_ID_SHIFT 0 +#define IPC_DST_MODULE_ID(x) (((x) & IPC_MODULE_ID_MASK) \ + << IPC_DST_MODULE_ID_SHIFT) + +#define IPC_DST_MODULE_INSTANCE_ID_SHIFT 16 +#define IPC_DST_MODULE_INSTANCE_ID(x) (((x) & IPC_MODULE_INSTANCE_ID_MASK) \ + << IPC_DST_MODULE_INSTANCE_ID_SHIFT) + +#define IPC_DST_QUEUE_SHIFT 24 +#define IPC_DST_QUEUE_MASK 0x7 +#define IPC_DST_QUEUE(x) (((x) & IPC_DST_QUEUE_MASK) \ + << IPC_DST_QUEUE_SHIFT) + +#define IPC_SRC_QUEUE_SHIFT 27 +#define IPC_SRC_QUEUE_MASK 0x7 +#define IPC_SRC_QUEUE(x) (((x) & IPC_SRC_QUEUE_MASK) \ + << IPC_SRC_QUEUE_SHIFT) + +/*Save pipeline messgae extension register */ +#define IPC_DMA_ID_SHIFT 0 +#define IPC_DMA_ID_MASK 0x1F +#define IPC_DMA_ID(x) (((x) & IPC_DMA_ID_MASK) \ + << IPC_DMA_ID_SHIFT) +/* Large Config message */ +/* extension register */ +#define IPC_DATA_OFFSET_SZ_SHIFT 0 +#define IPC_DATA_OFFSET_SZ_MASK 0xFFFFF +#define IPC_DATA_OFFSET_SZ(x) (((x) & IPC_DATA_OFFSET_SZ_MASK) \ + << IPC_DATA_OFFSET_SZ_SHIFT) +#define IPC_DATA_OFFSET_SZ_CLEAR ~(IPC_DATA_OFFSET_SZ_MASK \ + << IPC_DATA_OFFSET_SZ_SHIFT) + +#define IPC_LARGE_PARAM_ID_SHIFT 20 +#define IPC_LARGE_PARAM_ID_MASK 0xFF +#define IPC_LARGE_PARAM_ID(x) (((x) & IPC_LARGE_PARAM_ID_MASK) \ + << IPC_LARGE_PARAM_ID_SHIFT) + +#define IPC_FINAL_BLOCK_SHIFT 28 +#define IPC_FINAL_BLOCK_MASK 0x1 +#define IPC_FINAL_BLOCK(x) (((x) & IPC_FINAL_BLOCK_MASK) \ + << IPC_FINAL_BLOCK_SHIFT) + +#define IPC_INITIAL_BLOCK_SHIFT 29 +#define IPC_INITIAL_BLOCK_MASK 0x1 +#define IPC_INITIAL_BLOCK(x) (((x) & IPC_INITIAL_BLOCK_MASK) \ + << IPC_INITIAL_BLOCK_SHIFT) +#define IPC_INITIAL_BLOCK_CLEAR ~(IPC_INITIAL_BLOCK_MASK \ + << IPC_INITIAL_BLOCK_SHIFT) + +#define W0_SIZE 2048 +#define W1_SIZE 4096 + +enum ssth_ipc_msg_target { + IPC_FW_GEN_MSG = 0, + IPC_MODULE_MSG = 1 +}; + +enum ssth_ipc_msg_direction { + IPC_MSG_REQUEST = 0, + IPC_MSG_REPLY = 1 +}; + +/* Global Message Types */ +enum ssth_ipc_glb_type { + IPC_GLB_GET_FW_VERSION = 0, /**< Retrieves firmware version */ + IPC_GLB_LOAD_MULTIPLE_MODULES = 15, + IPC_GLB_UNLOAD_MULTIPLE_MODULES = 16, + IPC_GLB_CREATE_PIPELINE = 17, + IPC_GLB_DELETE_PIPELINE = 18, + IPC_GLB_SET_PIPELINE_STATE = 19, + IPC_GLB_GET_PIPELINE_STATE = 20, + IPC_GLB_GET_PIPELINE_CONTEXT_SIZE = 21, + IPC_GLB_SAVE_PIPELINE = 22, + IPC_GLB_RESTORE_PIPELINE = 23, + IPC_GLB_NOTIFICATION = 26, + IPC_GLB_MAX_IPC_MESSAGE_TYPE = 31 /**< Maximum message number */ +}; + +enum ssth_ipc_glb_reply { + IPC_GLB_REPLY_SUCCESS = 0, + + IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 1, + IPC_GLB_REPLY_ERROR_INVALID_PARAM = 2, + + IPC_GLB_REPLY_BUSY = 3, + IPC_GLB_REPLY_PENDING = 4, + IPC_GLB_REPLY_FAILURE = 5, + IPC_GLB_REPLY_INVALID_REQUEST = 6, + + IPC_GLB_REPLY_OUT_OF_MEMORY = 7, + IPC_GLB_REPLY_OUT_OF_MIPS = 8, + + IPC_GLB_REPLY_INVALID_RESOURCE_ID = 9, + IPC_GLB_REPLY_INVALID_RESOURCE_STATE = 10, + + IPC_GLB_REPLY_MOD_MGMT_ERROR = 100, + IPC_GLB_REPLY_MOD_LOAD_CL_FAILED = 101, + IPC_GLB_REPLY_MOD_LOAD_INVALID_HASH = 102, + + IPC_GLB_REPLY_MOD_UNLOAD_INST_EXIST = 103, + IPC_GLB_REPLY_MOD_NOT_INITIALIZED = 104, + + IPC_GLB_REPLY_INVALID_CONFIG_PARAM_ID = 120, + IPC_GLB_REPLY_INVALID_CONFIG_DATA_LEN = 121, + IPC_GLB_REPLY_GATEWAY_NOT_INITIALIZED = 140, + IPC_GLB_REPLY_GATEWAY_NOT_EXIST = 141, + + IPC_GLB_REPLY_PIPELINE_NOT_INITIALIZED = 160, + IPC_GLB_REPLY_PIPELINE_NOT_EXIST = 161, + IPC_GLB_REPLY_PIPELINE_SAVE_FAILED = 162, + IPC_GLB_REPLY_PIPELINE_RESTORE_FAILED = 163, + + IPC_MAX_STATUS = ((1<<IXC_STATUS_BITS)-1) +}; + +enum ssth_ipc_notification_type { + IPC_GLB_NOTIFCATION_GLITCH = 0, + IPC_GLB_NOTIFCATION_OVERRUN = 1, + IPC_GLB_NOTIFCATION_UNDERRUN = 2, + IPC_GLB_NOTIFCATION_END_STREAM = 3, + IPC_GLB_NOTIFCATION_PHRASE_DETECTED = 4, + IPC_GLB_NOTIFCATION_RESOURCE_EVENT = 5, + IPC_GLB_NOTIFCATION_LOG_BUFFER_STATUS = 6, + IPC_GLB_NOTIFCATION_TIMESTAMP_CAPTURED = 7, + IPC_GLB_NOTIFCATION_FW_READY = 8 +}; + +/* Module Message Types */ +enum ssth_ipc_module_msg { + IPC_MODULE_INIT_INSTANCE = 0, + IPC_MODULE_CONFIG_GET = 1, + IPC_MODULE_CONFIG_SET = 2, + IPC_MODULE_LARGE_CONFIG_GET = 3, + IPC_MODULE_LARGE_CONFIG_SET = 4, + IPC_MODULE_BIND = 5, + IPC_MODULE_UNBIND = 6, + IPC_MODULE_SET_DX = 7 +}; + +struct ssth_ipc_message { + struct list_head list; + struct ssth_ipc_header header; + char tx_data[WINDOW1_SIZE]; + size_t tx_size; + char rx_data[WINDOW0_UP_SIZE]; + size_t rx_size; + + wait_queue_head_t waitq; + bool complete; + bool wait; + int errno; +}; + +/* locks held by caller */ +static struct ssth_ipc_message *ssth_ipc_msg_get_empty(struct ssth_ipc *ipc) +{ + struct ssth_ipc_message *msg = NULL; + + if (!list_empty(&ipc->empty_list)) { + msg = list_first_entry(&ipc->empty_list, + struct ssth_ipc_message, list); + list_del(&msg->list); + } + + return msg; +} + +/* locks held by caller */ +static void ssth_ipc_msg_put_empty(struct ssth_ipc *ipc, + struct ssth_ipc_message *msg) +{ + list_add_tail(&msg->list, &ipc->empty_list); +} + +static int ssth_ipc_tx_wait_done(struct ssth_ipc *ipc, struct ssth_ipc_message *msg, + void *rx_data) +{ + int ret; + unsigned long irq_flags; + + /* wait for DSP completion (in all cases atm inc pending) */ + ret = wait_event_timeout(msg->waitq, msg->complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + + spin_lock_irqsave(&ipc->ipc_lock, irq_flags); + + if (ret == 0) + ret = -ETIMEDOUT; + else { + /* copy the data returned from DSP */ + if (msg->rx_size) { + if (rx_data) + memcpy(rx_data, msg->rx_data, msg->rx_size); + else + dev_err(ipc->dev, "error: no output buffer"); + } + ret = msg->errno; + } + + ssth_ipc_msg_put_empty(ipc, msg); + + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + return ret; +} + +static int ssth_ipc_tx_message(struct ssth_ipc *ipc, struct ssth_ipc_header header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes, + int wait) +{ + struct ssth_ipc_message *msg; + unsigned long irq_flags; + + spin_lock_irqsave(&ipc->ipc_lock, irq_flags); + + msg = ssth_ipc_msg_get_empty(ipc); + dev_dbg(ipc->dev, "msg pointer send %p", msg); + if (msg == NULL) { + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + return -EBUSY; + } + + if (tx_bytes) + memcpy(msg->tx_data, tx_data, tx_bytes); + + msg->header.primary = header.primary; + msg->header.extension = header.extension; + msg->tx_size = tx_bytes; + msg->rx_size = rx_bytes; + msg->wait = wait; + msg->errno = 0; + msg->complete = false; + + list_add_tail(&msg->list, &ipc->tx_list); + + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + + queue_kthread_work(&ipc->kworker, &ipc->kwork); + + if (wait) + return ssth_ipc_tx_wait_done(ipc, msg, rx_data); + else + return 0; +} + +int ssth_ipc_tx_message_wait(struct ssth_ipc *ipc, + struct ssth_ipc_header header, void *tx_data, size_t tx_bytes, void *rx_data, + size_t rx_bytes) +{ + return ssth_ipc_tx_message(ipc, header, tx_data, tx_bytes, rx_data, + rx_bytes, 1); +} + +static inline int ssth_ipc_tx_message_nowait(struct ssth_ipc *ipc, + struct ssth_ipc_header header, void *tx_data, size_t tx_bytes) +{ + return ssth_ipc_tx_message(ipc, header, tx_data, tx_bytes, NULL, 0, 0); +} + +static void ssth_ipc_tx_msgs(struct kthread_work *work) +{ + struct ssth_ipc *ipc = + container_of(work, struct ssth_ipc, kwork); + struct ssth_ipc_message *msg; + unsigned long irq_flags; + u32 hipci; + + + spin_lock_irqsave(&ipc->ipc_lock, irq_flags); + if (list_empty(&ipc->tx_list)) { + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + return; + } + + /* if the DSP is busy we will TX messages after IRQ */ + hipci = ssth_readl(ipc->dsp, HIPCI); + if (hipci & HDA_ADSP_REG_HIPCI_BUSY) { + dev_dbg(ipc->dev, "ipc_tx_msgs dsp busy HIPCI(48)=%x\n", hipci); + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + return; + } + + msg = list_first_entry(&ipc->tx_list, struct ssth_ipc_message, list); + + list_move(&msg->list, &ipc->rx_list); + + /* send the message */ + ssth_mailbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + ssth_writel(ipc->dsp, HIPCIE, + msg->header.extension); + ssth_writel(ipc->dsp, HIPCI, + msg->header.primary | HDA_ADSP_REG_HIPCI_BUSY); + + dev_dbg(ipc->dev, "sending msg HIPCI(48)=%x\n", + ssth_readl(ipc->dsp, HIPCI)); + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); +} + +static inline void ssth_ipc_tx_msg_reply_complete(struct ssth_ipc *ipc, + struct ssth_ipc_message *msg) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&ipc->ipc_lock, irq_flags); + msg->complete = true; + + if (!msg->wait) { + ssth_ipc_msg_put_empty(ipc, msg); + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + } else { + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + wake_up(&msg->waitq); + } +} + +static struct ssth_ipc_message *ssth_ipc_reply_find_msg(struct ssth_ipc *ipc, u32 header) +{ + struct ssth_ipc_message *msg = NULL; + unsigned long irq_flags; + + spin_lock_irqsave(&ipc->ipc_lock, irq_flags); + + if (list_empty(&ipc->rx_list)) { + dev_err(ipc->dev, "ipc: rx list is empty but received 0x%x\n", + header); + goto out; + } + + msg = list_first_entry(&ipc->rx_list, struct ssth_ipc_message, list); + +out: + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); + return msg; +} + +static void ssth_ipc_reply_remove(struct ssth_ipc *ipc, struct ssth_ipc_message *msg) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&ipc->ipc_lock, irq_flags); + if (list_empty(&ipc->rx_list)) { + dev_dbg(ipc->dev, "empty rx list"); + goto out; + } + list_del(&msg->list); + +out: + spin_unlock_irqrestore(&ipc->ipc_lock, irq_flags); +} + +static int ssth_ipc_process_notification(struct ssth_ipc *ipc, struct ssth_ipc_header header) +{ + if (IPC_GLB_NOTIFI_MSG_TYPE(header.primary)) { + switch (IPC_GLB_NOTIFI_TYPE(header.primary)) { + case IPC_GLB_NOTIFCATION_GLITCH: + break; + case IPC_GLB_NOTIFCATION_OVERRUN: + break; + case IPC_GLB_NOTIFCATION_UNDERRUN: + dev_dbg(ipc->dev, "FW UNDERRUN\n"); + break; + case IPC_GLB_NOTIFCATION_END_STREAM: + break; + case IPC_GLB_NOTIFCATION_PHRASE_DETECTED: + break; + case IPC_GLB_NOTIFCATION_RESOURCE_EVENT: + dev_dbg(ipc->dev, "MCPS Budget Violation\n"); + break; + case IPC_GLB_NOTIFCATION_LOG_BUFFER_STATUS: + break; + case IPC_GLB_NOTIFCATION_TIMESTAMP_CAPTURED: + break; + case IPC_GLB_NOTIFCATION_FW_READY: + ipc->boot_complete = true; + wake_up(&ipc->boot_wait); + break; + default: + dev_err(ipc->dev, "ipc: error received unexpected msg=%x", header.primary); + break; + } + } + return 0; +} + +static void ssth_ipc_process_reply(struct ssth_ipc *ipc, struct ssth_ipc_header header) +{ + struct ssth_ipc_message *msg; + u32 reply = header.primary & IPC_GLB_REPLY_STATUS_MASK; + + msg = ssth_ipc_reply_find_msg(ipc, header.primary); + if (msg == NULL) { + dev_dbg(ipc->dev, "ipc: rx list is empty\n"); + return; + } + + /* first process the header */ + switch (reply) { + case IPC_GLB_REPLY_SUCCESS: + dev_dbg(ipc->dev, "ipc FW reply: success\n"); + break; + case IPC_GLB_REPLY_OUT_OF_MEMORY: + msg->errno = -ENOMEM; + break; + case IPC_GLB_REPLY_BUSY: + msg->errno = -EBUSY; + break; + case IPC_GLB_REPLY_FAILURE: + case IPC_GLB_REPLY_INVALID_REQUEST: + case IPC_GLB_REPLY_ERROR_INVALID_PARAM: + case IPC_GLB_REPLY_PIPELINE_NOT_INITIALIZED: + msg->errno = -EINVAL; + break; + default: + dev_err(ipc->dev, "ipc reply: 0x%x", reply); + msg->errno = -EINVAL; + break; + } + + dev_err(ipc->dev, "********ipc FW reply: 0x%x", reply); + if (msg->errno != IPC_GLB_REPLY_SUCCESS) + dev_err(ipc->dev, "ipc FW reply: 0x%x reply=0x%x", msg->errno, reply); + + ssth_ipc_reply_remove(ipc, msg); + ssth_ipc_tx_msg_reply_complete(ipc, msg); +} + +void ssth_ipc_process_msg(struct work_struct *work) +{ + + struct ssth_lib *dsp = container_of(work, + struct ssth_lib, ipc_process_msg_work); + struct ssth_ipc *ipc = dsp->ipc; + struct ssth_ipc_header header = {0}; + u32 hipcie, hipct, hipcte; + int ipc_irq = 0; + + hipcie = ssth_readl(dsp, HIPCIE); + hipct = ssth_readl(dsp, HIPCT); + + /* reply message from DSP */ + if (hipcie & HDA_ADSP_REG_HIPCIE_DONE) { + ssth_updatel_bits(dsp, HDA_ADSP_REG_HIPCCTL, + HDA_ADSP_REG_HIPCCTL_DONE, 0); + /* clear DONE bit - tell DSP we have completed the operation */ + + ssth_updatel_bits(dsp, HDA_ADSP_REG_HIPCIE, + HDA_ADSP_REG_HIPCIE_DONE, HDA_ADSP_REG_HIPCIE_DONE); + + ipc_irq = 1; + + /* unmask Done interrupt */ + ssth_updatel_bits(dsp, HDA_ADSP_REG_HIPCCTL, + HDA_ADSP_REG_HIPCCTL_DONE, HDA_ADSP_REG_HIPCCTL_DONE); + } + + /* to do: new message from DSP */ + if (hipct & HDA_ADSP_REG_HIPCT_BUSY) { + dev_dbg(dsp->dev, "IPC irq: Firmware respond"); + hipcte = ssth_readl(dsp, HIPCTE); + header.primary = hipct; + header.extension = hipcte; + + if (header.primary & 0x20000000) { + /* Handle Immediate reply from DSP Core */ + ssth_ipc_process_reply(ipc, header); + } else { + trace_printk("IPC irq: Notification from firmware\n"); + ssth_ipc_process_notification(ipc, header); + } + /* clear busy interrupt */ + ssth_updatel_bits(dsp, HDA_ADSP_REG_HIPCT, + HDA_ADSP_REG_HIPCT_BUSY, HDA_ADSP_REG_HIPCT_BUSY); + ipc_irq = 1; + } + + if (ipc_irq == 0) + return; + + ssth_ipc_int_enable(dsp); + /* continue to send any remaining messages... */ + queue_kthread_work(&ipc->kworker, &ipc->kwork); +} + +static int ssth_ipc_msg_empty_list_init(struct ssth_ipc *ipc) +{ + struct ssth_ipc_message *msg; + int i; + + msg = kzalloc(sizeof(*msg) * IPC_EMPTY_LIST_SIZE, GFP_KERNEL); + if (msg == NULL) + return -ENOMEM; + + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + init_waitqueue_head(&msg[i].waitq); + list_add(&msg[i].list, &ipc->empty_list); + } + + return 0; +} + +void ssth_ipc_int_enable(struct ssth_lib *ctx) +{ + ssth_updatel_bits(ctx, HDA_ADSP_REG_ADSPIC, + ADSPIC_IPC, ADSPIC_IPC); +} + +void ssth_ipc_int_disable(struct ssth_lib *ctx) +{ + ssth_updatel_bits(ctx, HDA_ADSP_REG_ADSPIC, + ADSPIC_IPC, 0); +} + +void ssth_ipc_op_int_enable(struct ssth_lib *ctx) +{ + /* enable IPC DONE interrupt */ + ssth_updatel_bits(ctx, HDA_ADSP_REG_HIPCCTL, + HDA_ADSP_REG_HIPCCTL_DONE, HDA_ADSP_REG_HIPCCTL_DONE); + /* Enable IPC BUSY interrupt */ + ssth_updatel_bits(ctx, HDA_ADSP_REG_HIPCCTL, + HDA_ADSP_REG_HIPCCTL_BUSY, HDA_ADSP_REG_HIPCCTL_BUSY); +} + +bool ssth_ipc_int_status(struct ssth_lib *ctx) +{ + return ssth_readl(ctx, ADSPIS) & ADSPIS_IPC; +} + + +struct ssth_ipc *ssth_ipc_init(struct device *dev, + struct ssth_lib *dsp) +{ + struct ssth_ipc *ipc; + int err; + + dev_dbg(dev, "In %s\n", __func__); + + ipc = kzalloc(sizeof(*ipc), GFP_KERNEL); + if (ipc == NULL) + return NULL; + + ipc->dev = dev; + ipc->dsp = dsp; + + INIT_LIST_HEAD(&ipc->tx_list); + INIT_LIST_HEAD(&ipc->rx_list); + INIT_LIST_HEAD(&ipc->empty_list); + spin_lock_init(&ipc->ipc_lock); + init_waitqueue_head(&ipc->wait_txq); + init_waitqueue_head(&ipc->boot_wait); + + err = ssth_ipc_msg_empty_list_init(ipc); + if (err < 0) + goto list_err; + + /* start the IPC message thread */ + init_kthread_worker(&ipc->kworker); + ipc->tx_thread = kthread_run(kthread_worker_fn, + &ipc->kworker, + dev_name(ipc->dev)); + if (IS_ERR(ipc->tx_thread)) { + dev_err(ipc->dev, "error failed to create message TX task\n"); + goto list_err; + } + init_kthread_work(&ipc->kwork, ssth_ipc_tx_msgs); + + return ipc; + +list_err: + kfree(ipc); + return NULL; +} + +void ssth_ipc_free(struct ssth_ipc *ipc) +{ + /* Disable IPC DONE interrupt */ + ssth_updatel_bits(ipc->dsp, HDA_ADSP_REG_HIPCCTL, + HDA_ADSP_REG_HIPCCTL_DONE, 0); + /* Disable IPC BUSY interrupt */ + ssth_updatel_bits(ipc->dsp, HDA_ADSP_REG_HIPCCTL, + HDA_ADSP_REG_HIPCCTL_BUSY, 0); + + kfree(ipc); +} + +int ssth_ipc_create_pipeline(struct ssth_ipc *ipc, u16 ppl_mem_size, + u8 ppl_type, u8 instance_id) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_CREATE_PIPELINE); + header.primary |= IPC_INSTANCE_ID(instance_id); + header.primary |= IPC_PPL_TYPE(ppl_type); + header.primary |= IPC_PPL_MEM_SIZE(ppl_mem_size); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = ssth_ipc_tx_message_wait(ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: create pipeline failed, err: 0x%x\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_create_pipeline); + +int ssth_ipc_delete_pipeline(struct ssth_ipc *ipc, u8 instance_id) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_DELETE_PIPELINE); + header.primary |= IPC_INSTANCE_ID(instance_id); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = ssth_ipc_tx_message_wait(ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: delete pipeline failed\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ssth_ipc_delete_pipeline); + +int ssth_ipc_set_pipeline_state(struct ssth_ipc *ipc, u8 instance_id, + enum ssth_pipeline_state state) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_SET_PIPELINE_STATE); + header.primary |= IPC_INSTANCE_ID(instance_id); + header.primary |= IPC_PPL_STATE(state); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = ssth_ipc_tx_message_wait(ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: set pipeline state failed\n"); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_set_pipeline_state); + +int ssth_ipc_save_pipeline(struct ssth_ipc *ipc, u8 instance_id, int dma_id) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_SAVE_PIPELINE); + header.primary |= IPC_INSTANCE_ID(instance_id); + + header.extension = IPC_DMA_ID(dma_id); + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = ssth_ipc_tx_message_wait(ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: save pipeline failed, err: 0x%x\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_save_pipeline); + +int ssth_ipc_restore_pipeline(struct ssth_ipc *ipc, u8 instance_id) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_RESTORE_PIPELINE); + header.primary |= IPC_INSTANCE_ID(instance_id); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = ssth_ipc_tx_message_wait(ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: restore pipeline failed, err: 0x%x\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_restore_pipeline); + +int ssth_ipc_set_dx(struct ssth_ipc *ipc, u8 instance_id, u16 module_id, + struct ssth_dxstate_info *dx) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_MODULE_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MODULE_SET_DX); + header.primary |= IPC_MODULE_INSTANCE_ID(instance_id); + header.primary |= IPC_MODULE_ID(module_id); + + dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__, + header.primary, header.extension); + ret = ssth_ipc_tx_message_wait(ipc, header, (void *)dx, sizeof(dx), + NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: set dx failed\n"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_set_dx); + +int ssth_ipc_init_instance(struct ssth_ipc *ipc, struct ssth_init_instance_msg *msg, + void *param_data) +{ + struct ssth_ipc_header header = {0}; + /* param_block_size must be in dwords */ + u16 param_block_size = msg->param_data_size / sizeof(u32); + int ret = 0; + u32 *buffer = (u32 *)param_data; + int i = 0; + + dev_dbg(ipc->dev, "param size: %d bytes\n", msg->param_data_size); + for (i = 0; i < param_block_size; ++i) + dev_dbg(ipc->dev, "%x\n ", buffer[i]); + + header.primary = IPC_MSG_TARGET(IPC_MODULE_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MODULE_INIT_INSTANCE); + header.primary |= IPC_MODULE_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MODULE_ID(msg->module_id); + + header.extension = IPC_CORE_ID(msg->core_id); + header.extension |= IPC_PPL_INSTANCE_ID(msg->ppl_instance_id); + header.extension |= IPC_PARAM_BLOCK_SIZE(param_block_size); + + dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__, + header.primary, header.extension); + ret = ssth_ipc_tx_message_wait(ipc, header, param_data, msg->param_data_size, + NULL, 0); + + if (ret < 0) { + dev_err(ipc->dev, "ipc: init instance failed\n"); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_init_instance); + +int ssth_ipc_bind_unbind(struct ssth_ipc *ipc, struct ssth_bind_unbind_msg *msg) +{ + struct ssth_ipc_header header = {0}; + u8 bind_unbind = msg->bind ? IPC_MODULE_BIND : IPC_MODULE_UNBIND; + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_MODULE_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(bind_unbind); + header.primary |= IPC_MODULE_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MODULE_ID(msg->module_id); + + header.extension = IPC_DST_MODULE_ID(msg->dst_module_id); + header.extension |= IPC_DST_MODULE_INSTANCE_ID(msg->dst_instance_id); + header.extension |= IPC_DST_QUEUE(msg->dst_queue); + header.extension |= IPC_SRC_QUEUE(msg->src_queue); + + dev_dbg(ipc->dev, "In %s hdr=%x ext=%x\n", __func__, header.primary, + header.extension); + ret = ssth_ipc_tx_message_wait(ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: bind/unbind failed\n"); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_bind_unbind); + +int ssth_ipc_set_large_config(struct ssth_ipc *ipc, struct ssth_large_config_msg *msg, + u32 *param) +{ + struct ssth_ipc_header header = {0}; + int ret = 0; + size_t sz_remaining, tx_size, data_offset; + + header.primary = IPC_MSG_TARGET(IPC_MODULE_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MODULE_LARGE_CONFIG_SET); + header.primary |= IPC_MODULE_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MODULE_ID(msg->module_id); + + header.extension = IPC_DATA_OFFSET_SZ(msg->param_data_size); + header.extension |= IPC_LARGE_PARAM_ID(msg->large_param_id); + header.extension |= IPC_FINAL_BLOCK(0); + header.extension |= IPC_INITIAL_BLOCK(1); + + sz_remaining = msg->param_data_size; + data_offset = 0; + while (sz_remaining != 0) { + tx_size = sz_remaining > WINDOW1_SIZE + ? WINDOW1_SIZE : sz_remaining; + if (tx_size == sz_remaining) + header.extension |= IPC_FINAL_BLOCK(1); + + dev_dbg(ipc->dev, "In %s primary=%#x ext=%#x\n", __func__, + header.primary, header.extension); + dev_dbg(ipc->dev, "transmitting offset: %#x, size: %#x\n", + (unsigned)data_offset, (unsigned)tx_size); + ret = ssth_ipc_tx_message_wait(ipc, header, + ((char *)param) + data_offset, + tx_size, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: set large config failed\n"); + return ret; + } + sz_remaining -= tx_size; + data_offset = msg->param_data_size - sz_remaining; + + /* clear the fields */ + header.extension &= IPC_INITIAL_BLOCK_CLEAR; + header.extension &= IPC_DATA_OFFSET_SZ_CLEAR; + /* fill the fields */ + header.extension |= IPC_INITIAL_BLOCK(0); + header.extension |= IPC_DATA_OFFSET_SZ(data_offset); + } + return ret; +} +EXPORT_SYMBOL_GPL(ssth_ipc_set_large_config);