audio-lnx: Initial change for techpack of audio drivers.

Add snapshot for audio drivers for SDM targets. The code is
migrated from msm-4.9 kernel at the below cutoff -

(74ff856e8d6: "net: ipc_router: Add dynamic enable/disable
wakeup source feature")

This changes are done for new techpack addition
for audio kernel. Migrate all audio kernel drivers
to this techpack.

Change-Id: I33d580af3ba86a5cb777583efc5d4cdaf2882d93
Signed-off-by: Asish Bhattacharya <asishb@codeaurora.org>
diff --git a/sound/soc/codecs/wcd-dsp-mgr.c b/sound/soc/codecs/wcd-dsp-mgr.c
new file mode 100644
index 0000000..661db2b
--- /dev/null
+++ b/sound/soc/codecs/wcd-dsp-mgr.c
@@ -0,0 +1,1253 @@
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. 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 and
+ * only 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/stringify.h>
+#include <linux/of.h>
+#include <linux/debugfs.h>
+#include <linux/component.h>
+#include <linux/dma-mapping.h>
+#include <soc/qcom/ramdump.h>
+#include <sound/wcd-dsp-mgr.h>
+#include "wcd-dsp-utils.h"
+
+/* Forward declarations */
+static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type);
+
+/* Component related macros */
+#define WDSP_GET_COMPONENT(wdsp, x) (&(wdsp->cmpnts[x]))
+#define WDSP_GET_CMPNT_TYPE_STR(x) wdsp_get_cmpnt_type_string(x)
+
+/*
+ * These #defines indicate the bit number in status field
+ * for each of the status. If bit is set, it indicates
+ * the status as done, else if bit is not set, it indicates
+ * the status is either failed or not done.
+ */
+#define WDSP_STATUS_INITIALIZED   BIT(0)
+#define WDSP_STATUS_CODE_DLOADED  BIT(1)
+#define WDSP_STATUS_DATA_DLOADED  BIT(2)
+#define WDSP_STATUS_BOOTED        BIT(3)
+
+/* Helper macros for printing wdsp messages */
+#define WDSP_ERR(wdsp, fmt, ...)		\
+	dev_err(wdsp->mdev, "%s: " fmt "\n", __func__, ##__VA_ARGS__)
+#define WDSP_DBG(wdsp, fmt, ...)	\
+	dev_dbg(wdsp->mdev, "%s: " fmt "\n", __func__, ##__VA_ARGS__)
+
+/* Helper macros for locking */
+#define WDSP_MGR_MUTEX_LOCK(wdsp, lock)         \
+{                                               \
+	WDSP_DBG(wdsp, "mutex_lock(%s)",        \
+		 __stringify_1(lock));          \
+	mutex_lock(&lock);                      \
+}
+
+#define WDSP_MGR_MUTEX_UNLOCK(wdsp, lock)       \
+{                                               \
+	WDSP_DBG(wdsp, "mutex_unlock(%s)",      \
+		 __stringify_1(lock));          \
+	mutex_unlock(&lock);                    \
+}
+
+/* Helper macros for using status mask */
+#define WDSP_SET_STATUS(wdsp, state)                  \
+{                                                     \
+	wdsp->status |= state;                        \
+	WDSP_DBG(wdsp, "set 0x%lx, new_state = 0x%x", \
+		 state, wdsp->status);                \
+}
+
+#define WDSP_CLEAR_STATUS(wdsp, state)                  \
+{                                                       \
+	wdsp->status &= (~state);                       \
+	WDSP_DBG(wdsp, "clear 0x%lx, new_state = 0x%x", \
+		 state, wdsp->status);                  \
+}
+
+#define WDSP_STATUS_IS_SET(wdsp, state) (wdsp->status & state)
+
+/* SSR relate status macros */
+#define WDSP_SSR_STATUS_WDSP_READY    BIT(0)
+#define WDSP_SSR_STATUS_CDC_READY     BIT(1)
+#define WDSP_SSR_STATUS_READY         \
+	(WDSP_SSR_STATUS_WDSP_READY | WDSP_SSR_STATUS_CDC_READY)
+#define WDSP_SSR_READY_WAIT_TIMEOUT   (10 * HZ)
+
+enum wdsp_ssr_type {
+
+	/* Init value, indicates there is no SSR in progress */
+	WDSP_SSR_TYPE_NO_SSR = 0,
+
+	/*
+	 * Indicates WDSP crashed. The manager driver internally
+	 * decides when to perform WDSP restart based on the
+	 * users of wdsp. Hence there is no explicit WDSP_UP.
+	 */
+	WDSP_SSR_TYPE_WDSP_DOWN,
+
+	/* Indicates codec hardware is down */
+	WDSP_SSR_TYPE_CDC_DOWN,
+
+	/* Indicates codec hardware is up, trigger to restart WDSP */
+	WDSP_SSR_TYPE_CDC_UP,
+};
+
+struct wdsp_cmpnt {
+
+	/* OF node of the phandle */
+	struct device_node *np;
+
+	/*
+	 * Child component's dev_name, should be set in DT for the child's
+	 * phandle if child's dev->of_node does not match the phandle->of_node
+	 */
+	const char *cdev_name;
+
+	/* Child component's device node */
+	struct device *cdev;
+
+	/* Private data that component may want back on callbacks */
+	void *priv_data;
+
+	/* Child ops */
+	struct wdsp_cmpnt_ops *ops;
+};
+
+struct wdsp_ramdump_data {
+
+	/* Ramdump device */
+	void *rd_dev;
+
+	/* DMA address of the dump */
+	dma_addr_t rd_addr;
+
+	/* Virtual address of the dump */
+	void *rd_v_addr;
+
+	/* Data provided through error interrupt */
+	struct wdsp_err_signal_arg err_data;
+};
+
+struct wdsp_mgr_priv {
+
+	/* Manager driver's struct device pointer */
+	struct device *mdev;
+
+	/* Match struct for component framework */
+	struct component_match *match;
+
+	/* Manager's ops/function callbacks */
+	struct wdsp_mgr_ops *ops;
+
+	/* Array to store information for all expected components */
+	struct wdsp_cmpnt cmpnts[WDSP_CMPNT_TYPE_MAX];
+
+	/* The filename of image to be downloaded */
+	const char *img_fname;
+
+	/* Keeps track of current state of manager driver */
+	u32 status;
+
+	/* Work to load the firmware image after component binding */
+	struct work_struct load_fw_work;
+
+	/* List of segments in image to be downloaded */
+	struct list_head *seg_list;
+
+	/* Base address of the image in memory */
+	u32 base_addr;
+
+	/* Instances using dsp */
+	int dsp_users;
+
+	/* Lock for serializing ops called by components */
+	struct mutex api_mutex;
+
+	struct wdsp_ramdump_data dump_data;
+
+	/* SSR related */
+	enum wdsp_ssr_type ssr_type;
+	struct mutex ssr_mutex;
+	struct work_struct ssr_work;
+	u16 ready_status;
+	struct completion ready_compl;
+
+	/* Debugfs related */
+	struct dentry *entry;
+	bool panic_on_error;
+};
+
+static char *wdsp_get_ssr_type_string(enum wdsp_ssr_type type)
+{
+	switch (type) {
+	case WDSP_SSR_TYPE_NO_SSR:
+		return "NO_SSR";
+	case WDSP_SSR_TYPE_WDSP_DOWN:
+		return "WDSP_DOWN";
+	case WDSP_SSR_TYPE_CDC_DOWN:
+		return "CDC_DOWN";
+	case WDSP_SSR_TYPE_CDC_UP:
+		return "CDC_UP";
+	default:
+		pr_err("%s: Invalid ssr_type %d\n",
+			__func__, type);
+		return "Invalid";
+	}
+}
+
+static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type type)
+{
+	switch (type) {
+	case WDSP_CMPNT_CONTROL:
+		return "control";
+	case WDSP_CMPNT_IPC:
+		return "ipc";
+	case WDSP_CMPNT_TRANSPORT:
+		return "transport";
+	default:
+		pr_err("%s: Invalid component type %d\n",
+			__func__, type);
+		return "Invalid";
+	}
+}
+
+static void __wdsp_clr_ready_locked(struct wdsp_mgr_priv *wdsp,
+				    u16 value)
+{
+	wdsp->ready_status &= ~(value);
+	WDSP_DBG(wdsp, "ready_status = 0x%x", wdsp->ready_status);
+}
+
+static void __wdsp_set_ready_locked(struct wdsp_mgr_priv *wdsp,
+				    u16 value, bool mark_complete)
+{
+	wdsp->ready_status |= value;
+	WDSP_DBG(wdsp, "ready_status = 0x%x", wdsp->ready_status);
+
+	if (mark_complete &&
+	    wdsp->ready_status == WDSP_SSR_STATUS_READY) {
+		WDSP_DBG(wdsp, "marking ready completion");
+		complete(&wdsp->ready_compl);
+	}
+}
+
+static void wdsp_broadcast_event_upseq(struct wdsp_mgr_priv *wdsp,
+				       enum wdsp_event_type event,
+				       void *data)
+{
+	struct wdsp_cmpnt *cmpnt;
+	int i;
+
+	for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) {
+		cmpnt = WDSP_GET_COMPONENT(wdsp, i);
+		if (cmpnt && cmpnt->ops && cmpnt->ops->event_handler)
+			cmpnt->ops->event_handler(cmpnt->cdev, cmpnt->priv_data,
+						  event, data);
+	}
+}
+
+static void wdsp_broadcast_event_downseq(struct wdsp_mgr_priv *wdsp,
+					 enum wdsp_event_type event,
+					 void *data)
+{
+	struct wdsp_cmpnt *cmpnt;
+	int i;
+
+	for (i = WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) {
+		cmpnt = WDSP_GET_COMPONENT(wdsp, i);
+		if (cmpnt && cmpnt->ops && cmpnt->ops->event_handler)
+			cmpnt->ops->event_handler(cmpnt->cdev, cmpnt->priv_data,
+						  event, data);
+	}
+}
+
+static int wdsp_unicast_event(struct wdsp_mgr_priv *wdsp,
+			      enum wdsp_cmpnt_type type,
+			      enum wdsp_event_type event,
+			      void *data)
+{
+	struct wdsp_cmpnt *cmpnt;
+	int ret;
+
+	cmpnt = WDSP_GET_COMPONENT(wdsp, type);
+	if (cmpnt && cmpnt->ops && cmpnt->ops->event_handler) {
+		ret = cmpnt->ops->event_handler(cmpnt->cdev, cmpnt->priv_data,
+						event, data);
+	} else {
+		WDSP_ERR(wdsp, "not valid event_handler for %s",
+			 WDSP_GET_CMPNT_TYPE_STR(type));
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static void wdsp_deinit_components(struct wdsp_mgr_priv *wdsp)
+{
+	struct wdsp_cmpnt *cmpnt;
+	int i;
+
+	for (i = WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) {
+		cmpnt = WDSP_GET_COMPONENT(wdsp, i);
+		if (cmpnt && cmpnt->ops && cmpnt->ops->deinit)
+			cmpnt->ops->deinit(cmpnt->cdev, cmpnt->priv_data);
+	}
+}
+
+static int wdsp_init_components(struct wdsp_mgr_priv *wdsp)
+{
+	struct wdsp_cmpnt *cmpnt;
+	int fail_idx = WDSP_CMPNT_TYPE_MAX;
+	int i, ret = 0;
+
+	for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) {
+
+		cmpnt = WDSP_GET_COMPONENT(wdsp, i);
+
+		/* Init is allowed to be NULL */
+		if (!cmpnt->ops || !cmpnt->ops->init)
+			continue;
+		ret = cmpnt->ops->init(cmpnt->cdev, cmpnt->priv_data);
+		if (ret) {
+			WDSP_ERR(wdsp, "Init failed (%d) for component %s",
+				 ret, WDSP_GET_CMPNT_TYPE_STR(i));
+				fail_idx = i;
+				break;
+		}
+	}
+
+	if (fail_idx < WDSP_CMPNT_TYPE_MAX) {
+		/* Undo init for already initialized components */
+		for (i = fail_idx - 1; i >= 0; i--) {
+			struct wdsp_cmpnt *cmpnt = WDSP_GET_COMPONENT(wdsp, i);
+
+			if (cmpnt->ops && cmpnt->ops->deinit)
+				cmpnt->ops->deinit(cmpnt->cdev,
+						   cmpnt->priv_data);
+		}
+	} else {
+		wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_INIT, NULL);
+	}
+
+	return ret;
+}
+
+static int wdsp_load_each_segment(struct wdsp_mgr_priv *wdsp,
+				  struct wdsp_img_segment *seg)
+{
+	struct wdsp_img_section img_section;
+	int ret;
+
+	WDSP_DBG(wdsp,
+		 "base_addr 0x%x, split_fname %s, load_addr 0x%x, size 0x%zx",
+		 wdsp->base_addr, seg->split_fname, seg->load_addr, seg->size);
+
+	if (seg->load_addr < wdsp->base_addr) {
+		WDSP_ERR(wdsp, "Invalid addr 0x%x, base_addr = 0x%x",
+			 seg->load_addr, wdsp->base_addr);
+		return -EINVAL;
+	}
+
+	img_section.addr = seg->load_addr - wdsp->base_addr;
+	img_section.size = seg->size;
+	img_section.data = seg->data;
+
+	ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_TRANSPORT,
+				 WDSP_EVENT_DLOAD_SECTION,
+				 &img_section);
+	if (ret < 0)
+		WDSP_ERR(wdsp,
+			 "Failed, err = %d for base_addr = 0x%x split_fname = %s, load_addr = 0x%x, size = 0x%zx",
+			 ret, wdsp->base_addr, seg->split_fname,
+			 seg->load_addr, seg->size);
+	return ret;
+}
+
+static int wdsp_download_segments(struct wdsp_mgr_priv *wdsp,
+				  unsigned int type)
+{
+	struct wdsp_cmpnt *ctl;
+	struct wdsp_img_segment *seg = NULL;
+	enum wdsp_event_type pre, post;
+	long status;
+	int ret;
+
+	ctl = WDSP_GET_COMPONENT(wdsp, WDSP_CMPNT_CONTROL);
+
+	if (type == WDSP_ELF_FLAG_RE) {
+		pre = WDSP_EVENT_PRE_DLOAD_CODE;
+		post = WDSP_EVENT_POST_DLOAD_CODE;
+		status = WDSP_STATUS_CODE_DLOADED;
+	} else if (type == WDSP_ELF_FLAG_WRITE) {
+		pre = WDSP_EVENT_PRE_DLOAD_DATA;
+		post = WDSP_EVENT_POST_DLOAD_DATA;
+		status = WDSP_STATUS_DATA_DLOADED;
+	} else {
+		WDSP_ERR(wdsp, "Invalid type %u", type);
+		return -EINVAL;
+	}
+
+	ret = wdsp_get_segment_list(ctl->cdev, wdsp->img_fname,
+				    type, wdsp->seg_list, &wdsp->base_addr);
+	if (ret < 0 ||
+	    list_empty(wdsp->seg_list)) {
+		WDSP_ERR(wdsp, "Error %d to get image segments for type %d",
+			 ret, type);
+		wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_DLOAD_FAILED,
+					     NULL);
+		goto done;
+	}
+
+	/* Notify all components that image is about to be downloaded */
+	wdsp_broadcast_event_upseq(wdsp, pre, NULL);
+
+	/* Go through the list of segments and download one by one */
+	list_for_each_entry(seg, wdsp->seg_list, list) {
+		ret = wdsp_load_each_segment(wdsp, seg);
+		if (ret < 0) {
+			wdsp_broadcast_event_downseq(wdsp,
+						     WDSP_EVENT_DLOAD_FAILED,
+						     NULL);
+			goto dload_error;
+		}
+	}
+
+	WDSP_SET_STATUS(wdsp, status);
+
+	/* Notify all components that image is downloaded */
+	wdsp_broadcast_event_downseq(wdsp, post, NULL);
+
+dload_error:
+	wdsp_flush_segment_list(wdsp->seg_list);
+done:
+	return ret;
+}
+
+static int wdsp_init_and_dload_code_sections(struct wdsp_mgr_priv *wdsp)
+{
+	int ret;
+	bool is_initialized;
+
+	is_initialized = WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_INITIALIZED);
+
+	if (!is_initialized) {
+		/* Components are not initialized yet, initialize them */
+		ret = wdsp_init_components(wdsp);
+		if (ret < 0) {
+			WDSP_ERR(wdsp, "INIT failed, err = %d", ret);
+			goto done;
+		}
+		WDSP_SET_STATUS(wdsp, WDSP_STATUS_INITIALIZED);
+	}
+
+	/* Download the read-execute sections of image */
+	ret = wdsp_download_segments(wdsp, WDSP_ELF_FLAG_RE);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Error %d to download code sections", ret);
+		goto done;
+	}
+done:
+	return ret;
+}
+
+static void wdsp_load_fw_image(struct work_struct *work)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int ret;
+
+	wdsp = container_of(work, struct wdsp_mgr_priv, load_fw_work);
+	if (!wdsp) {
+		pr_err("%s: Invalid private_data\n", __func__);
+		return;
+	}
+
+	ret = wdsp_init_and_dload_code_sections(wdsp);
+	if (ret < 0)
+		WDSP_ERR(wdsp, "dload code sections failed, err = %d", ret);
+}
+
+static int wdsp_enable_dsp(struct wdsp_mgr_priv *wdsp)
+{
+	int ret;
+
+	/* Make sure wdsp is in good state */
+	if (!WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_CODE_DLOADED)) {
+		WDSP_ERR(wdsp, "WDSP in invalid state 0x%x", wdsp->status);
+		ret = -EINVAL;
+		goto done;
+	}
+
+	/* Download the read-write sections of image */
+	ret = wdsp_download_segments(wdsp, WDSP_ELF_FLAG_WRITE);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Data section download failed, err = %d", ret);
+		goto done;
+	}
+
+	wdsp_broadcast_event_upseq(wdsp, WDSP_EVENT_PRE_BOOTUP, NULL);
+
+	ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL,
+				 WDSP_EVENT_DO_BOOT, NULL);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Failed to boot dsp, err = %d", ret);
+		WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED);
+		goto done;
+	}
+
+	wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_BOOTUP, NULL);
+	WDSP_SET_STATUS(wdsp, WDSP_STATUS_BOOTED);
+done:
+	return ret;
+}
+
+static int wdsp_disable_dsp(struct wdsp_mgr_priv *wdsp)
+{
+	int ret;
+
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
+
+	/*
+	 * If Disable happened while SSR is in progress, then set the SSR
+	 * ready status indicating WDSP is now ready. Ignore the disable
+	 * event here and let the SSR handler go through shutdown.
+	 */
+	if (wdsp->ssr_type != WDSP_SSR_TYPE_NO_SSR) {
+		__wdsp_set_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY, true);
+		WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
+		return 0;
+	}
+
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
+
+	/* Make sure wdsp is in good state */
+	if (!WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) {
+		WDSP_ERR(wdsp, "wdsp in invalid state 0x%x", wdsp->status);
+		ret = -EINVAL;
+		goto done;
+	}
+
+	wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_PRE_SHUTDOWN, NULL);
+	ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL,
+				 WDSP_EVENT_DO_SHUTDOWN, NULL);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Failed to shutdown dsp, err = %d", ret);
+		goto done;
+	}
+
+	wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_SHUTDOWN, NULL);
+	WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_BOOTED);
+
+	/* Data sections are to be downloaded per boot */
+	WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED);
+done:
+	return ret;
+}
+
+static int wdsp_register_cmpnt_ops(struct device *wdsp_dev,
+				   struct device *cdev,
+				   void *priv_data,
+				   struct wdsp_cmpnt_ops *ops)
+{
+	struct wdsp_mgr_priv *wdsp;
+	struct wdsp_cmpnt *cmpnt;
+	int i, ret;
+
+	if (!wdsp_dev || !cdev || !ops)
+		return -EINVAL;
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->api_mutex);
+
+	for (i = 0; i < WDSP_CMPNT_TYPE_MAX; i++) {
+		cmpnt = WDSP_GET_COMPONENT(wdsp, i);
+		if ((cdev->of_node && cdev->of_node == cmpnt->np) ||
+		    (cmpnt->cdev_name &&
+		     !strcmp(dev_name(cdev), cmpnt->cdev_name))) {
+			break;
+		}
+	}
+
+	if (i == WDSP_CMPNT_TYPE_MAX) {
+		WDSP_ERR(wdsp, "Failed to register component dev %s",
+			 dev_name(cdev));
+		ret = -EINVAL;
+		goto done;
+	}
+
+	cmpnt->cdev = cdev;
+	cmpnt->ops = ops;
+	cmpnt->priv_data = priv_data;
+done:
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->api_mutex);
+	return 0;
+}
+
+static struct device *wdsp_get_dev_for_cmpnt(struct device *wdsp_dev,
+					     enum wdsp_cmpnt_type type)
+{
+	struct wdsp_mgr_priv *wdsp;
+	struct wdsp_cmpnt *cmpnt;
+
+	if (!wdsp_dev || type >= WDSP_CMPNT_TYPE_MAX)
+		return NULL;
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+	cmpnt = WDSP_GET_COMPONENT(wdsp, type);
+
+	return cmpnt->cdev;
+}
+
+static int wdsp_get_devops_for_cmpnt(struct device *wdsp_dev,
+				     enum wdsp_cmpnt_type type,
+				     void *data)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int ret = 0;
+
+	if (!wdsp_dev || type >= WDSP_CMPNT_TYPE_MAX)
+		return -EINVAL;
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+	ret = wdsp_unicast_event(wdsp, type,
+				 WDSP_EVENT_GET_DEVOPS, data);
+	if (ret)
+		WDSP_ERR(wdsp, "get_dev_ops failed for cmpnt type %d",
+			 type);
+	return ret;
+}
+
+static void wdsp_collect_ramdumps(struct wdsp_mgr_priv *wdsp)
+{
+	struct wdsp_img_section img_section;
+	struct wdsp_err_signal_arg *data = &wdsp->dump_data.err_data;
+	struct ramdump_segment rd_seg;
+	int ret = 0;
+
+	if (wdsp->ssr_type != WDSP_SSR_TYPE_WDSP_DOWN ||
+	    !data->mem_dumps_enabled) {
+		WDSP_DBG(wdsp, "cannot dump memory, ssr_type %s, dumps %s",
+			 wdsp_get_ssr_type_string(wdsp->ssr_type),
+			 !(data->mem_dumps_enabled) ? "disabled" : "enabled");
+		goto done;
+	}
+
+	if (data->dump_size == 0 ||
+	    data->remote_start_addr < wdsp->base_addr) {
+		WDSP_ERR(wdsp, "Invalid start addr 0x%x or dump_size 0x%zx",
+			 data->remote_start_addr, data->dump_size);
+		goto done;
+	}
+
+	if (!wdsp->dump_data.rd_dev) {
+		WDSP_ERR(wdsp, "Ramdump device is not setup");
+		goto done;
+	}
+
+	WDSP_DBG(wdsp, "base_addr 0x%x, dump_start_addr 0x%x, dump_size 0x%zx",
+		 wdsp->base_addr, data->remote_start_addr, data->dump_size);
+
+	/* Allocate memory for dumps */
+	wdsp->dump_data.rd_v_addr = dma_alloc_coherent(wdsp->mdev,
+						       data->dump_size,
+						       &wdsp->dump_data.rd_addr,
+						       GFP_KERNEL);
+	if (!wdsp->dump_data.rd_v_addr)
+		goto done;
+
+	img_section.addr = data->remote_start_addr - wdsp->base_addr;
+	img_section.size = data->dump_size;
+	img_section.data = wdsp->dump_data.rd_v_addr;
+
+	ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_TRANSPORT,
+				 WDSP_EVENT_READ_SECTION,
+				 &img_section);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Failed to read dumps, size 0x%zx at addr 0x%x",
+			 img_section.size, img_section.addr);
+		goto err_read_dumps;
+	}
+
+	/*
+	 * If panic_on_error flag is explicitly set through the debugfs,
+	 * then cause a BUG here to aid debugging.
+	 */
+	BUG_ON(wdsp->panic_on_error);
+
+	rd_seg.address = (unsigned long) wdsp->dump_data.rd_v_addr;
+	rd_seg.size = img_section.size;
+	rd_seg.v_address = wdsp->dump_data.rd_v_addr;
+
+	ret = do_ramdump(wdsp->dump_data.rd_dev, &rd_seg, 1);
+	if (ret < 0)
+		WDSP_ERR(wdsp, "do_ramdump failed with error %d", ret);
+
+err_read_dumps:
+	dma_free_coherent(wdsp->mdev, data->dump_size,
+			  wdsp->dump_data.rd_v_addr, wdsp->dump_data.rd_addr);
+done:
+	return;
+}
+
+static void wdsp_ssr_work_fn(struct work_struct *work)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int ret;
+
+	wdsp = container_of(work, struct wdsp_mgr_priv, ssr_work);
+	if (!wdsp) {
+		pr_err("%s: Invalid private_data\n", __func__);
+		return;
+	}
+
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
+
+	/* Issue ramdumps and shutdown only if DSP is currently booted */
+	if (WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) {
+		wdsp_collect_ramdumps(wdsp);
+		ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL,
+					 WDSP_EVENT_DO_SHUTDOWN, NULL);
+		if (ret < 0)
+			WDSP_ERR(wdsp, "Failed WDSP shutdown, err = %d", ret);
+
+		wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_SHUTDOWN,
+					     NULL);
+		WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_BOOTED);
+	}
+
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
+	ret = wait_for_completion_timeout(&wdsp->ready_compl,
+					  WDSP_SSR_READY_WAIT_TIMEOUT);
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
+	if (ret == 0) {
+		WDSP_ERR(wdsp, "wait_for_ready timed out, status = 0x%x",
+			 wdsp->ready_status);
+		goto done;
+	}
+
+	/* Data sections are to downloaded per WDSP boot */
+	WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED);
+
+	/*
+	 * Even though code section could possible be retained on DSP
+	 * crash, go ahead and still re-download just to avoid any
+	 * memory corruption from previous crash.
+	 */
+	WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_CODE_DLOADED);
+
+	/* If codec restarted, then all components must be re-initialized */
+	if (wdsp->ssr_type == WDSP_SSR_TYPE_CDC_UP) {
+		wdsp_deinit_components(wdsp);
+		WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_INITIALIZED);
+	}
+
+	ret = wdsp_init_and_dload_code_sections(wdsp);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Failed to dload code sections err = %d",
+			 ret);
+		goto done;
+	}
+
+	/* SSR handling is finished, mark SSR type as NO_SSR */
+	wdsp->ssr_type = WDSP_SSR_TYPE_NO_SSR;
+done:
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
+}
+
+static int wdsp_ssr_handler(struct wdsp_mgr_priv *wdsp, void *arg,
+			    enum wdsp_ssr_type ssr_type)
+{
+	enum wdsp_ssr_type current_ssr_type;
+	struct wdsp_err_signal_arg *err_data;
+
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
+
+	current_ssr_type = wdsp->ssr_type;
+	WDSP_DBG(wdsp, "Current ssr_type %s, handling ssr_type %s",
+		 wdsp_get_ssr_type_string(current_ssr_type),
+		 wdsp_get_ssr_type_string(ssr_type));
+	wdsp->ssr_type = ssr_type;
+
+	if (arg) {
+		err_data = (struct wdsp_err_signal_arg *) arg;
+		memcpy(&wdsp->dump_data.err_data, err_data,
+		       sizeof(*err_data));
+	} else {
+		memset(&wdsp->dump_data.err_data, 0,
+		       sizeof(wdsp->dump_data.err_data));
+	}
+
+	switch (ssr_type) {
+
+	case WDSP_SSR_TYPE_WDSP_DOWN:
+		__wdsp_clr_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY);
+		wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_PRE_SHUTDOWN,
+					     NULL);
+		schedule_work(&wdsp->ssr_work);
+		break;
+
+	case WDSP_SSR_TYPE_CDC_DOWN:
+		__wdsp_clr_ready_locked(wdsp, WDSP_SSR_STATUS_CDC_READY);
+		/*
+		 * If DSP is booted when CDC_DOWN is received, it needs
+		 * to be shutdown.
+		 */
+		if (WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) {
+			__wdsp_clr_ready_locked(wdsp,
+						WDSP_SSR_STATUS_WDSP_READY);
+			wdsp_broadcast_event_downseq(wdsp,
+						     WDSP_EVENT_PRE_SHUTDOWN,
+						     NULL);
+		}
+
+		schedule_work(&wdsp->ssr_work);
+		break;
+
+	case WDSP_SSR_TYPE_CDC_UP:
+		__wdsp_set_ready_locked(wdsp, WDSP_SSR_STATUS_CDC_READY, true);
+		break;
+
+	default:
+		WDSP_ERR(wdsp, "undefined ssr_type %d\n", ssr_type);
+		/* Revert back the ssr_type for undefined events */
+		wdsp->ssr_type = current_ssr_type;
+		break;
+	}
+
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
+
+	return 0;
+}
+
+static int wdsp_signal_handler(struct device *wdsp_dev,
+			       enum wdsp_signal signal, void *arg)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int ret;
+
+	if (!wdsp_dev)
+		return -EINVAL;
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->api_mutex);
+
+	WDSP_DBG(wdsp, "Raised signal %d", signal);
+
+	switch (signal) {
+	case WDSP_IPC1_INTR:
+		ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_IPC,
+					 WDSP_EVENT_IPC1_INTR, NULL);
+		break;
+	case WDSP_ERR_INTR:
+		ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_WDSP_DOWN);
+		break;
+	case WDSP_CDC_DOWN_SIGNAL:
+		ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_CDC_DOWN);
+		break;
+	case WDSP_CDC_UP_SIGNAL:
+		ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_CDC_UP);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	if (ret < 0)
+		WDSP_ERR(wdsp, "handling signal %d failed with error %d",
+			 signal, ret);
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->api_mutex);
+
+	return ret;
+}
+
+static int wdsp_vote_for_dsp(struct device *wdsp_dev,
+			     bool vote)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int ret = 0;
+
+	if (!wdsp_dev)
+		return -EINVAL;
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+
+	WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->api_mutex);
+	WDSP_DBG(wdsp, "request %s, current users = %d",
+		 vote ? "enable" : "disable", wdsp->dsp_users);
+
+	if (vote) {
+		wdsp->dsp_users++;
+		if (wdsp->dsp_users == 1)
+			ret = wdsp_enable_dsp(wdsp);
+	} else {
+		if (wdsp->dsp_users == 0)
+			goto done;
+
+		wdsp->dsp_users--;
+		if (wdsp->dsp_users == 0)
+			ret = wdsp_disable_dsp(wdsp);
+	}
+
+	if (ret < 0)
+		WDSP_DBG(wdsp, "wdsp %s failed, err = %d",
+			 vote ? "enable" : "disable", ret);
+
+done:
+	WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->api_mutex);
+	return ret;
+}
+
+static int wdsp_suspend(struct device *wdsp_dev)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int rc = 0, i;
+
+	if (!wdsp_dev) {
+		pr_err("%s: Invalid handle to device\n", __func__);
+		return -EINVAL;
+	}
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+
+	for (i =  WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) {
+		rc = wdsp_unicast_event(wdsp, i, WDSP_EVENT_SUSPEND, NULL);
+		if (rc < 0) {
+			WDSP_ERR(wdsp, "component %s failed to suspend\n",
+				WDSP_GET_CMPNT_TYPE_STR(i));
+			break;
+		}
+	}
+
+	return rc;
+}
+
+static int wdsp_resume(struct device *wdsp_dev)
+{
+	struct wdsp_mgr_priv *wdsp;
+	int rc = 0, i;
+
+	if (!wdsp_dev) {
+		pr_err("%s: Invalid handle to device\n", __func__);
+		return -EINVAL;
+	}
+
+	wdsp = dev_get_drvdata(wdsp_dev);
+
+	for (i =  0; i < WDSP_CMPNT_TYPE_MAX; i++) {
+		rc = wdsp_unicast_event(wdsp, i, WDSP_EVENT_RESUME, NULL);
+		if (rc < 0) {
+			WDSP_ERR(wdsp, "component %s failed to resume\n",
+				WDSP_GET_CMPNT_TYPE_STR(i));
+			break;
+		}
+	}
+
+	return rc;
+}
+
+static struct wdsp_mgr_ops wdsp_ops = {
+	.register_cmpnt_ops = wdsp_register_cmpnt_ops,
+	.get_dev_for_cmpnt = wdsp_get_dev_for_cmpnt,
+	.get_devops_for_cmpnt = wdsp_get_devops_for_cmpnt,
+	.signal_handler = wdsp_signal_handler,
+	.vote_for_dsp = wdsp_vote_for_dsp,
+	.suspend = wdsp_suspend,
+	.resume = wdsp_resume,
+};
+
+static int wdsp_mgr_compare_of(struct device *dev, void *data)
+{
+	struct wdsp_cmpnt *cmpnt = data;
+
+	/*
+	 * First try to match based on of_node, if of_node is not
+	 * present, try to match on the dev_name
+	 */
+	return ((dev->of_node && dev->of_node == cmpnt->np) ||
+		(cmpnt->cdev_name &&
+		 !strcmp(dev_name(dev), cmpnt->cdev_name)));
+}
+
+static void wdsp_mgr_debugfs_init(struct wdsp_mgr_priv *wdsp)
+{
+	wdsp->entry = debugfs_create_dir("wdsp_mgr", NULL);
+	if (IS_ERR_OR_NULL(wdsp->entry))
+		return;
+
+	debugfs_create_bool("panic_on_error", 0644,
+			    wdsp->entry, &wdsp->panic_on_error);
+}
+
+static void wdsp_mgr_debugfs_remove(struct wdsp_mgr_priv *wdsp)
+{
+	debugfs_remove_recursive(wdsp->entry);
+	wdsp->entry = NULL;
+}
+
+static int wdsp_mgr_bind(struct device *dev)
+{
+	struct wdsp_mgr_priv *wdsp = dev_get_drvdata(dev);
+	struct wdsp_cmpnt *cmpnt;
+	int ret, idx;
+
+	wdsp->ops = &wdsp_ops;
+
+	/* Setup ramdump device */
+	wdsp->dump_data.rd_dev = create_ramdump_device("wdsp", dev);
+	if (!wdsp->dump_data.rd_dev)
+		dev_info(dev, "%s: create_ramdump_device failed\n", __func__);
+
+	ret = component_bind_all(dev, wdsp->ops);
+	if (ret < 0)
+		WDSP_ERR(wdsp, "component_bind_all failed %d\n", ret);
+
+	/* Make sure all components registered ops */
+	for (idx = 0; idx < WDSP_CMPNT_TYPE_MAX; idx++) {
+		cmpnt = WDSP_GET_COMPONENT(wdsp, idx);
+		if (!cmpnt->cdev || !cmpnt->ops) {
+			WDSP_ERR(wdsp, "%s did not register ops\n",
+				 WDSP_GET_CMPNT_TYPE_STR(idx));
+			ret = -EINVAL;
+			component_unbind_all(dev, wdsp->ops);
+			break;
+		}
+	}
+
+	wdsp_mgr_debugfs_init(wdsp);
+
+	/* Schedule the work to download image if binding was successful. */
+	if (!ret)
+		schedule_work(&wdsp->load_fw_work);
+
+	return ret;
+}
+
+static void wdsp_mgr_unbind(struct device *dev)
+{
+	struct wdsp_mgr_priv *wdsp = dev_get_drvdata(dev);
+	struct wdsp_cmpnt *cmpnt;
+	int idx;
+
+	component_unbind_all(dev, wdsp->ops);
+
+	wdsp_mgr_debugfs_remove(wdsp);
+
+	if (wdsp->dump_data.rd_dev) {
+		destroy_ramdump_device(wdsp->dump_data.rd_dev);
+		wdsp->dump_data.rd_dev = NULL;
+	}
+
+	/* Clear all status bits */
+	wdsp->status = 0x00;
+
+	/* clean up the components */
+	for (idx = 0; idx < WDSP_CMPNT_TYPE_MAX; idx++) {
+		cmpnt = WDSP_GET_COMPONENT(wdsp, idx);
+		cmpnt->cdev = NULL;
+		cmpnt->ops = NULL;
+		cmpnt->priv_data = NULL;
+	}
+}
+
+static const struct component_master_ops wdsp_master_ops = {
+	.bind = wdsp_mgr_bind,
+	.unbind = wdsp_mgr_unbind,
+};
+
+static void *wdsp_mgr_parse_phandle(struct wdsp_mgr_priv *wdsp,
+				    int index)
+{
+	struct device *mdev = wdsp->mdev;
+	struct device_node *np;
+	struct wdsp_cmpnt *cmpnt = NULL;
+	struct of_phandle_args pargs;
+	u32 value;
+	int ret;
+
+	ret = of_parse_phandle_with_fixed_args(mdev->of_node,
+					      "qcom,wdsp-components", 1,
+					      index, &pargs);
+	if (ret) {
+		WDSP_ERR(wdsp, "parse_phandle at index %d failed %d",
+			 index, ret);
+		return NULL;
+	}
+
+	np = pargs.np;
+	value = pargs.args[0];
+
+	if (value >= WDSP_CMPNT_TYPE_MAX) {
+		WDSP_ERR(wdsp, "invalid phandle_arg to of_node %s", np->name);
+		goto done;
+	}
+
+	cmpnt = WDSP_GET_COMPONENT(wdsp, value);
+	if (cmpnt->np || cmpnt->cdev_name) {
+		WDSP_ERR(wdsp, "cmpnt %d already added", value);
+		cmpnt = NULL;
+		goto done;
+	}
+
+	cmpnt->np = np;
+	of_property_read_string(np, "qcom,wdsp-cmpnt-dev-name",
+				&cmpnt->cdev_name);
+done:
+	of_node_put(np);
+	return cmpnt;
+}
+
+static int wdsp_mgr_parse_dt_entries(struct wdsp_mgr_priv *wdsp)
+{
+	struct device *dev = wdsp->mdev;
+	void *match_data;
+	int ph_idx, ret;
+
+	ret = of_property_read_string(dev->of_node, "qcom,img-filename",
+				      &wdsp->img_fname);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Reading property %s failed, error = %d",
+			 "qcom,img-filename", ret);
+		return ret;
+	}
+
+	ret = of_count_phandle_with_args(dev->of_node,
+					 "qcom,wdsp-components",
+					 NULL);
+	if (ret == -ENOENT) {
+		WDSP_ERR(wdsp, "Property %s not defined in DT",
+			 "qcom,wdsp-components");
+		goto done;
+	} else if (ret != WDSP_CMPNT_TYPE_MAX * 2) {
+		WDSP_ERR(wdsp, "Invalid phandle + arg count %d, expected %d",
+			 ret, WDSP_CMPNT_TYPE_MAX * 2);
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = 0;
+
+	for (ph_idx = 0; ph_idx < WDSP_CMPNT_TYPE_MAX; ph_idx++) {
+
+		match_data = wdsp_mgr_parse_phandle(wdsp, ph_idx);
+		if (!match_data) {
+			WDSP_ERR(wdsp, "component not found at idx %d", ph_idx);
+			ret = -EINVAL;
+			goto done;
+		}
+
+		component_match_add(dev, &wdsp->match,
+				    wdsp_mgr_compare_of, match_data);
+	}
+
+done:
+	return ret;
+}
+
+static int wdsp_mgr_probe(struct platform_device *pdev)
+{
+	struct wdsp_mgr_priv *wdsp;
+	struct device *mdev = &pdev->dev;
+	int ret;
+
+	wdsp = devm_kzalloc(mdev, sizeof(*wdsp), GFP_KERNEL);
+	if (!wdsp)
+		return -ENOMEM;
+	wdsp->mdev = mdev;
+	wdsp->seg_list = devm_kzalloc(mdev, sizeof(struct list_head),
+				      GFP_KERNEL);
+	if (!wdsp->seg_list) {
+		devm_kfree(mdev, wdsp);
+		return -ENOMEM;
+	}
+
+	ret = wdsp_mgr_parse_dt_entries(wdsp);
+	if (ret)
+		goto err_dt_parse;
+
+	INIT_WORK(&wdsp->load_fw_work, wdsp_load_fw_image);
+	INIT_LIST_HEAD(wdsp->seg_list);
+	mutex_init(&wdsp->api_mutex);
+	mutex_init(&wdsp->ssr_mutex);
+	wdsp->ssr_type = WDSP_SSR_TYPE_NO_SSR;
+	wdsp->ready_status = WDSP_SSR_STATUS_READY;
+	INIT_WORK(&wdsp->ssr_work, wdsp_ssr_work_fn);
+	init_completion(&wdsp->ready_compl);
+	arch_setup_dma_ops(wdsp->mdev, 0, 0, NULL, 0);
+	dev_set_drvdata(mdev, wdsp);
+
+	ret = component_master_add_with_match(mdev, &wdsp_master_ops,
+					      wdsp->match);
+	if (ret < 0) {
+		WDSP_ERR(wdsp, "Failed to add master, err = %d", ret);
+		goto err_master_add;
+	}
+
+	return 0;
+
+err_master_add:
+	mutex_destroy(&wdsp->api_mutex);
+	mutex_destroy(&wdsp->ssr_mutex);
+err_dt_parse:
+	devm_kfree(mdev, wdsp->seg_list);
+	devm_kfree(mdev, wdsp);
+	dev_set_drvdata(mdev, NULL);
+
+	return ret;
+}
+
+static int wdsp_mgr_remove(struct platform_device *pdev)
+{
+	struct device *mdev = &pdev->dev;
+	struct wdsp_mgr_priv *wdsp = dev_get_drvdata(mdev);
+
+	component_master_del(mdev, &wdsp_master_ops);
+
+	mutex_destroy(&wdsp->api_mutex);
+	mutex_destroy(&wdsp->ssr_mutex);
+	devm_kfree(mdev, wdsp->seg_list);
+	devm_kfree(mdev, wdsp);
+	dev_set_drvdata(mdev, NULL);
+
+	return 0;
+};
+
+static const struct of_device_id wdsp_mgr_dt_match[] = {
+	{.compatible = "qcom,wcd-dsp-mgr" },
+	{ }
+};
+
+static struct platform_driver wdsp_mgr_driver = {
+	.driver = {
+		.name = "wcd-dsp-mgr",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(wdsp_mgr_dt_match),
+	},
+	.probe = wdsp_mgr_probe,
+	.remove = wdsp_mgr_remove,
+};
+
+int wcd_dsp_mgr_init(void)
+{
+	return platform_driver_register(&wdsp_mgr_driver);
+}
+
+void wcd_dsp_mgr_exit(void)
+{
+	platform_driver_unregister(&wdsp_mgr_driver);
+}
+
+MODULE_DESCRIPTION("WCD DSP manager driver");
+MODULE_DEVICE_TABLE(of, wdsp_mgr_dt_match);
+MODULE_LICENSE("GPL v2");