Jelajahi Sumber

Merge "asoc: wcd934x: add support for SSR handling using SND event FWK"

Linux Build Service Account 6 tahun lalu
induk
melakukan
98d3a76d95
7 mengubah file dengan 726 tambahan dan 18 penghapusan
  1. 40 2
      asoc/codecs/wcd934x/wcd934x.c
  2. 79 16
      dsp/q6core.c
  3. 79 0
      include/soc/snd_event.h
  4. 21 0
      ipc/apr.c
  5. 10 0
      soc/Android.mk
  6. 7 0
      soc/Kbuild
  7. 490 0
      soc/snd_event.c

+ 40 - 2
asoc/codecs/wcd934x/wcd934x.c

@@ -30,6 +30,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/mfd/wcd9xxx/wcd9xxx_registers.h>
 #include <soc/swr-wcd.h>
+#include <soc/snd_event.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
@@ -9979,6 +9980,7 @@ static int tavil_device_down(struct wcd9xxx *wcd9xxx)
 	priv = snd_soc_codec_get_drvdata(codec);
 	for (count = 0; count < NUM_CODEC_DAIS; count++)
 		priv->dai[count].bus_down_in_recovery = true;
+	snd_event_notify(priv->dev->parent, SND_EVENT_DOWN);
 
 	priv->mbhc->wcd_mbhc.deinit_in_progress = true;
 	if (delayed_work_pending(&priv->spk_anc_dwork.dwork))
@@ -10006,7 +10008,8 @@ static int tavil_device_down(struct wcd9xxx *wcd9xxx)
 		swrm_wcd_notify(priv->swr.ctrl_data[0].swr_pdev,
 				SWR_DEVICE_DOWN, NULL);
 	tavil_dsd_reset(priv->dsd_config);
-	snd_soc_card_change_online_state(codec->component.card, 0);
+	if (!is_snd_event_fwk_enabled())
+		snd_soc_card_change_online_state(codec->component.card, 0);
 	wcd_dsp_ssr_event(priv->wdsp_cntl, WCD_CDC_DOWN_EVENT);
 	wcd_resmgr_set_sido_input_src_locked(priv->resmgr,
 					     SIDO_SOURCE_INTERNAL);
@@ -10039,7 +10042,8 @@ static int tavil_post_reset_cb(struct wcd9xxx *wcd9xxx)
 	tavil_slimbus_slave_port_cfg.slave_dev_pgd_la =
 					control->slim->laddr;
 	tavil_init_slim_slave_cfg(codec);
-	snd_soc_card_change_online_state(codec->component.card, 1);
+	if (!is_snd_event_fwk_enabled())
+		snd_soc_card_change_online_state(codec->component.card, 1);
 
 	for (i = 0; i < TAVIL_MAX_MICBIAS; i++)
 		tavil->micb_ref[i] = 0;
@@ -10102,6 +10106,7 @@ static int tavil_post_reset_cb(struct wcd9xxx *wcd9xxx)
 	 */
 	tavil_vote_svs(tavil, false);
 	wcd_dsp_ssr_event(tavil->wdsp_cntl, WCD_CDC_UP_EVENT);
+	snd_event_notify(tavil->dev->parent, SND_EVENT_UP);
 
 done:
 	mutex_unlock(&tavil->codec_mutex);
@@ -10900,6 +10905,28 @@ struct wcd_dsp_cntl *tavil_get_wcd_dsp_cntl(struct device *dev)
 }
 EXPORT_SYMBOL(tavil_get_wcd_dsp_cntl);
 
+static void wcd934x_ssr_disable(struct device *dev, void *data)
+{
+	struct wcd9xxx *wcd9xxx = dev_get_drvdata(dev);
+	struct tavil_priv *tavil;
+	struct snd_soc_codec *codec;
+	int count = 0;
+
+	if (!wcd9xxx) {
+		dev_dbg(dev, "%s: wcd9xxx pointer NULL.\n", __func__);
+		return;
+	}
+	codec = (struct snd_soc_codec *)(wcd9xxx->ssr_priv);
+	tavil = snd_soc_codec_get_drvdata(codec);
+
+	for (count = 0; count < NUM_CODEC_DAIS; count++)
+		tavil->dai[count].bus_down_in_recovery = true;
+}
+
+static const struct snd_event_ops wcd934x_ssr_ops = {
+	.disable = wcd934x_ssr_disable,
+};
+
 static int tavil_probe(struct platform_device *pdev)
 {
 	int ret = 0;
@@ -11024,6 +11051,15 @@ static int tavil_probe(struct platform_device *pdev)
 	}
 	schedule_work(&tavil->tavil_add_child_devices_work);
 
+	ret = snd_event_client_register(pdev->dev.parent, &wcd934x_ssr_ops, NULL);
+	if (!ret) {
+		snd_event_notify(pdev->dev.parent, SND_EVENT_UP);
+	} else {
+		pr_err("%s: Registration with SND event fwk failed ret = %d\n",
+			__func__, ret);
+		ret = 0;
+	}
+
 	return ret;
 
 err_cdc_reg:
@@ -11057,6 +11093,8 @@ static int tavil_remove(struct platform_device *pdev)
 		tavil->dsd_config = NULL;
 	}
 
+	snd_event_client_deregister(pdev->dev.parent);
+
 	if (tavil->spi)
 		spi_unregister_device(tavil->spi);
 	for (count = 0; count < tavil->child_count &&

+ 79 - 16
dsp/q6core.c

@@ -26,6 +26,7 @@
 #include <dsp/q6core.h>
 #include <dsp/audio_cal_utils.h>
 #include <dsp/apr_audio-v2.h>
+#include <soc/snd_event.h>
 #include <ipc/apr.h>
 #include "adsp_err.h"
 
@@ -79,6 +80,7 @@ struct q6core_str {
 	struct cal_type_data *cal_data[CORE_MAX_CAL];
 	uint32_t mem_map_cal_handle;
 	int32_t adsp_status;
+	int32_t avs_state;
 	struct q6core_avcs_ver_info q6core_avcs_ver_info;
 };
 
@@ -1448,50 +1450,111 @@ err:
 	return ret;
 }
 
-static int q6core_probe(struct platform_device *pdev)
+static int q6core_is_avs_up(int32_t *avs_state)
 {
 	unsigned long timeout;
-	int adsp_ready = 0, rc;
+	int32_t adsp_ready = 0;
+	int ret = 0;
 
 	timeout = jiffies +
 		msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS);
 
 	do {
-		if (!adsp_ready) {
-			adsp_ready = q6core_is_adsp_ready();
-			dev_dbg(&pdev->dev, "%s: ADSP Audio is %s\n", __func__,
-				adsp_ready ? "ready" : "not ready");
-		}
+		adsp_ready = q6core_is_adsp_ready();
+		pr_debug("%s: ADSP Audio is %s\n", __func__,
+			 adsp_ready ? "ready" : "not ready");
 		if (adsp_ready)
 			break;
 
 		/*
-		 * ADSP will be coming up after loading (PD up event) and
-		 * it might not be fully up when the control reaches
-		 * here. So, wait for 50msec before checking ADSP state
+		 * ADSP will be coming up after boot up and AVS might
+		 * not be fully up when the control reaches here.
+		 * So, wait for 50msec before checking ADSP state again.
 		 */
 		msleep(50);
 	} while (time_after(timeout, jiffies));
 
+	*avs_state = adsp_ready;
+	pr_debug("%s: ADSP Audio is %s\n", __func__,
+	       adsp_ready ? "ready" : "not ready");
+
 	if (!adsp_ready) {
-		dev_err(&pdev->dev, "%s: Timeout. ADSP Audio is %s\n",
-		       __func__,
-		       adsp_ready ? "ready" : "not ready");
-		return -ETIMEDOUT;
+		pr_err_ratelimited("%s: Timeout. ADSP Audio is not ready\n",
+				   __func__);
+		ret = -ETIMEDOUT;
+	}
+
+	return ret;
+}
+
+static int q6core_ssr_enable(struct device *dev, void *data)
+{
+	int32_t avs_state = 0;
+	int ret = 0;
+
+	if (!dev) {
+		pr_err("%s: dev is NULL\n", __func__);
+		return -EINVAL;
 	}
+
+	if (!q6core_lcl.avs_state) {
+		ret = q6core_is_avs_up(&avs_state);
+		if (ret < 0)
+			goto err;
+		q6core_lcl.avs_state = avs_state;
+	}
+
+err:
+	return ret;
+}
+
+static void q6core_ssr_disable(struct device *dev, void *data)
+{
+	/* Reset AVS state to 0 */
+	q6core_lcl.avs_state = 0;
+}
+
+static const struct snd_event_ops q6core_ssr_ops = {
+	.enable = q6core_ssr_enable,
+	.disable = q6core_ssr_disable,
+};
+
+static int q6core_probe(struct platform_device *pdev)
+{
+	int32_t avs_state = 0;
+	int rc = 0;
+
+	rc = q6core_is_avs_up(&avs_state);
+	if (rc < 0)
+		goto err;
+	q6core_lcl.avs_state = avs_state;
+
 	rc = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
 	if (rc) {
 		dev_err(&pdev->dev, "%s: failed to add child nodes, rc=%d\n",
 			__func__, rc);
-		return -EINVAL;
+		rc = -EINVAL;
+		goto err;
 	}
 	dev_dbg(&pdev->dev, "%s: added child node\n", __func__);
 
-	return 0;
+	rc = snd_event_client_register(&pdev->dev, &q6core_ssr_ops, NULL);
+	if (!rc) {
+		snd_event_notify(&pdev->dev, SND_EVENT_UP);
+	} else {
+		dev_err(&pdev->dev,
+			"%s: Registration with SND event fwk failed rc = %d\n",
+			__func__, rc);
+		rc = 0;
+	}
+
+err:
+	return rc;
 }
 
 static int q6core_remove(struct platform_device *pdev)
 {
+	snd_event_client_deregister(&pdev->dev);
 	of_platform_depopulate(&pdev->dev);
 	return 0;
 }

+ 79 - 0
include/soc/snd_event.h

@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _SND_EVENT_H_
+#define _SND_EVENT_H_
+
+enum {
+	SND_EVENT_DOWN = 0,
+	SND_EVENT_UP,
+};
+
+struct snd_event_clients;
+
+struct snd_event_ops {
+	int (*enable)(struct device *dev, void *data);
+	void (*disable)(struct device *dev, void *data);
+};
+
+#ifdef CONFIG_SND_EVENT
+int snd_event_client_register(struct device *dev,
+			      const struct snd_event_ops *snd_ev_ops,
+			      void *data);
+int snd_event_client_deregister(struct device *dev);
+int snd_event_master_register(struct device *dev,
+			      const struct snd_event_ops *ops,
+			      struct snd_event_clients *clients,
+			      void *data);
+int snd_event_master_deregister(struct device *dev);
+int snd_event_notify(struct device *dev, unsigned int state);
+
+void snd_event_mstr_add_client(struct snd_event_clients **snd_clients,
+			    int (*compare)(struct device *, void *),
+			    void *data);
+inline bool is_snd_event_fwk_enabled(void)
+{
+	return 1;
+}
+#else
+int snd_event_client_register(struct device *dev,
+			      const struct snd_event_ops *snd_ev_ops,
+			      void *data)
+{
+	return 0;
+}
+int snd_event_client_deregister(struct device *dev)
+{
+	return 0;
+}
+int snd_event_master_register(struct device *dev,
+			      const struct snd_event_ops *ops,
+			      struct snd_event_clients *clients,
+			      void *data)
+{
+	return 0;
+}
+int snd_event_master_deregister(struct device *dev)
+{
+	return 0;
+}
+int snd_event_notify(struct device *dev, unsigned int state)
+{
+	return 0;
+}
+
+void snd_event_mstr_add_client(struct snd_event_clients **snd_clients,
+			    int (*compare)(struct device *, void *),
+			    void *data)
+{
+	return;
+}
+inline bool is_snd_event_fwk_enabled(void)
+{
+	return 0;
+}
+
+#endif /* CONFIG_SND_EVENT */
+#endif /* _SND_EVENT_H_ */

+ 21 - 0
ipc/apr.c

@@ -31,6 +31,7 @@
 #include <linux/of_platform.h>
 #include <soc/qcom/subsystem_restart.h>
 #include <soc/qcom/scm.h>
+#include <soc/snd_event.h>
 #include <dsp/apr_audio-v2.h>
 #include <dsp/audio_notifier.h>
 #include <ipc/apr.h>
@@ -281,9 +282,19 @@ int apr_set_q6_state(enum apr_subsys_state state)
 }
 EXPORT_SYMBOL(apr_set_q6_state);
 
+static void apr_ssr_disable(struct device *dev, void *data)
+{
+	apr_set_q6_state(APR_SUBSYS_DOWN);
+}
+
+static const struct snd_event_ops apr_ssr_ops = {
+	.disable = apr_ssr_disable,
+};
+
 static void apr_adsp_down(unsigned long opcode)
 {
 	pr_info("%s: Q6 is Down\n", __func__);
+	snd_event_notify(apr_priv->dev, SND_EVENT_DOWN);
 	apr_set_q6_state(APR_SUBSYS_DOWN);
 	dispatch_event(opcode, APR_DEST_QDSP6);
 }
@@ -308,6 +319,7 @@ static void apr_adsp_up(void)
 	if (apr_priv->is_initial_boot)
 		schedule_work(&apr_priv->add_chld_dev_work);
 	spin_unlock(&apr_priv->apr_lock);
+	snd_event_notify(apr_priv->dev, SND_EVENT_UP);
 }
 
 int apr_load_adsp_image(void)
@@ -1186,11 +1198,20 @@ static int apr_probe(struct platform_device *pdev)
 	}
 
 	apr_tal_init();
+
+	ret = snd_event_client_register(&pdev->dev, &apr_ssr_ops, NULL);
+	if (ret) {
+		pr_err("%s: Registration with Audio SSR FW failed ret = %d\n",
+			__func__, ret);
+		ret = 0;
+	}
+
 	return apr_debug_init();
 }
 
 static int apr_remove(struct platform_device *pdev)
 {
+	snd_event_client_deregister(&pdev->dev);
 	apr_cleanup();
 	apr_tal_exit();
 	apr_priv = NULL;

+ 10 - 0
soc/Android.mk

@@ -82,6 +82,16 @@ LOCAL_MODULE_DEBUG_ENABLE := true
 LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
 include $(DLKM_DIR)/AndroidKernelModule.mk
 ###########################################################
+ifeq ($(call is-board-platform-in-list, ),true)
+include $(CLEAR_VARS)
+LOCAL_MODULE              := $(AUDIO_CHIPSET)_snd_event.ko
+LOCAL_MODULE_KBUILD_NAME  := snd_event_dlkm.ko
+LOCAL_MODULE_TAGS         := optional
+LOCAL_MODULE_DEBUG_ENABLE := true
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/AndroidKernelModule.mk
+endif
+###########################################################
 
 endif # DLKM check
 endif # supported target check

+ 7 - 0
soc/Kbuild

@@ -102,6 +102,10 @@ ifdef CONFIG_SOUNDWIRE
 	SWR_OBJS += soundwire.o
 endif
 
+ifdef CONFIG_SND_EVENT
+	SND_EVENT_OBJS += snd_event.o
+endif
+
 LINUX_INC +=	-Iinclude/linux
 
 INCS +=		$(COMMON_INC) \
@@ -149,6 +153,9 @@ pinctrl_lpi_dlkm-y := $(PINCTRL_LPI_OBJS)
 obj-$(CONFIG_SOUNDWIRE) += swr_dlkm.o
 swr_dlkm-y := $(SWR_OBJS)
 
+obj-$(CONFIG_SND_EVENT) += snd_event_dlkm.o
+snd_event_dlkm-y := $(SND_EVENT_OBJS)
+
 obj-$(CONFIG_SOUNDWIRE_WCD_CTRL) += swr_ctrl_dlkm.o
 obj-$(CONFIG_SOUNDWIRE_MSTR_CTRL) += swr_ctrl_dlkm.o
 swr_ctrl_dlkm-y := $(SWR_CTRL_OBJS)

+ 490 - 0
soc/snd_event.c

@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <soc/snd_event.h>
+
+struct snd_event_client {
+	struct list_head node;
+
+	struct device *dev;
+	const struct snd_event_ops *ops;
+	void *data;
+
+	bool attached;
+	bool state;
+};
+
+struct snd_event_client_array {
+	struct device *dev;
+	struct snd_event_client *clnt;
+	void *data;
+	int (*compare)(struct device *, void *);
+};
+
+struct snd_event_clients {
+	size_t num_clients;
+	struct snd_event_client_array *cl_arr;
+};
+
+struct snd_master {
+	struct device *dev;
+	const struct snd_event_ops *ops;
+	void *data;
+
+	bool state;
+	bool fwk_state;
+	bool clients_found;
+	struct snd_event_clients *clients;
+};
+
+static DEFINE_MUTEX(snd_event_mutex);
+static LIST_HEAD(snd_event_client_list);
+static struct snd_master *master;
+
+static struct snd_event_client *find_snd_event_client(struct device *dev)
+{
+	struct snd_event_client *c;
+
+	list_for_each_entry(c, &snd_event_client_list, node)
+		if ((c->dev == dev) && c->ops)
+			return c;
+
+	return NULL;
+}
+
+static int check_and_update_fwk_state(void)
+{
+	bool new_fwk_state = true;
+	struct snd_event_client *c;
+	int ret = 0;
+	int i = 0;
+
+	for (i = 0; i < master->clients->num_clients; i++) {
+		c = master->clients->cl_arr[i].clnt;
+		new_fwk_state &= c->state;
+	}
+	new_fwk_state &= master->state;
+
+	if (master->fwk_state ^ new_fwk_state) {
+		if (new_fwk_state) {
+			for (i = 0; i < master->clients->num_clients; i++) {
+				c = master->clients->cl_arr[i].clnt;
+				if (c->ops->enable) {
+					ret = c->ops->enable(c->dev, c->data);
+					if (ret) {
+						dev_err(c->dev,
+							"%s: enable failed\n",
+							__func__);
+						goto dev_en_failed;
+					}
+				}
+			}
+			if (master->ops->enable) {
+				ret = master->ops->enable(master->dev,
+							  master->data);
+				if (ret) {
+					dev_err(master->dev,
+						"%s: enable failed\n",
+						__func__);
+					goto mstr_en_failed;
+				}
+			}
+		} else {
+			if (master->ops->disable)
+				master->ops->disable(master->dev,
+						     master->data);
+			for (i = 0; i < master->clients->num_clients; i++) {
+				c = master->clients->cl_arr[i].clnt;
+				if (c->ops->disable)
+					c->ops->disable(c->dev, c->data);
+			}
+		}
+		master->fwk_state = new_fwk_state;
+	}
+	goto exit;
+
+mstr_en_failed:
+	i = master->clients->num_clients;
+dev_en_failed:
+	for (; i > 0; i--) {
+		c = master->clients->cl_arr[i - 1].clnt;
+		if (c->ops->disable)
+			c->ops->disable(c->dev, c->data);
+	}
+exit:
+	return ret;
+}
+
+static int snd_event_find_clients(struct snd_master *master)
+{
+	struct snd_event_clients *clients = master->clients;
+	int i = 0;
+	int ret = 0;
+
+	for (i = 0; i < clients->num_clients; i++) {
+		struct snd_event_client_array *c_arr = &clients->cl_arr[i];
+		struct snd_event_client *c;
+
+		if (c_arr->dev) {
+			pr_err("%s: client already present dev=%pK\n",
+				 __func__, c_arr->dev);
+			continue;
+		}
+
+		list_for_each_entry(c, &snd_event_client_list, node) {
+			if (c->attached)
+				continue;
+
+			if (c_arr->compare(c->dev, c_arr->data)) {
+				dev_dbg(master->dev,
+					"%s: found client, dev=%pK\n",
+					__func__, c->dev);
+				c_arr->dev = c->dev;
+				c_arr->clnt = c;
+				c->attached = true;
+				break;
+			}
+		}
+		if (!c_arr->dev) {
+			dev_dbg(master->dev,
+				"%s: failed to find some client\n",
+				__func__);
+			ret = -ENXIO;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * snd_event_client_register - Register a client with the SND event FW
+ *
+ * @dev: Pointer to the "struct device" associated with the client
+ * @snd_ev_ops: Pointer to the snd_event_ops struct for the client containing
+ *              callback functions
+ * @data: Pointer to any additional data that the caller wants to get back
+ *        with callback functions
+ *
+ * Returns 0 on success or error on failure.
+ */
+int snd_event_client_register(struct device *dev,
+			      const struct snd_event_ops *snd_ev_ops,
+			      void *data)
+{
+	struct snd_event_client *c;
+
+	if (!dev) {
+		pr_err("%s: dev is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	c = kzalloc(sizeof(*c), GFP_KERNEL);
+	if (!c)
+		return -ENOMEM;
+
+	c->dev = dev;
+	c->ops = snd_ev_ops;
+	c->data = data;
+
+	dev_dbg(dev, "%s: adding client to SND event FW (ops %pK)\n",
+		__func__, snd_ev_ops);
+
+	mutex_lock(&snd_event_mutex);
+	list_add_tail(&c->node, &snd_event_client_list);
+
+	if (master && !master->clients_found) {
+		if (snd_event_find_clients(master)) {
+			dev_dbg(dev, "%s: Failed to find all clients\n",
+				__func__);
+			goto exit;
+		}
+		master->clients_found = true;
+	}
+
+exit:
+	mutex_unlock(&snd_event_mutex);
+	return 0;
+}
+EXPORT_SYMBOL(snd_event_client_register);
+
+/*
+ * snd_event_client_deregister - Remove a client from the SND event FW
+ *
+ * @dev: Pointer to the "struct device" associated with the client
+ *
+ * Returns 0 on success or error on failure.
+ */
+int snd_event_client_deregister(struct device *dev)
+{
+	struct snd_event_client *c;
+	int ret = 0;
+	int i = 0;
+
+	if (!dev) {
+		pr_err("%s: dev is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&snd_event_mutex);
+	if (list_empty(&snd_event_client_list)) {
+		dev_dbg(dev, "%s: No SND client registered\n", __func__);
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	c = find_snd_event_client(dev);
+	if (!c || (c->dev != dev)) {
+		dev_dbg(dev, "%s: No matching snd dev found\n", __func__);
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	c->state = false;
+
+	if (master && master->clients_found) {
+		struct snd_event_client *d;
+		bool dev_found = false;
+
+		for (i = 0; i < master->clients->num_clients; i++) {
+			d = master->clients->cl_arr[i].clnt;
+			if (c->dev == d->dev) {
+				dev_found = true;
+				break;
+			}
+		}
+		if (dev_found) {
+			ret = check_and_update_fwk_state();
+			master->clients_found = false;
+		}
+	}
+
+	list_del(&c->node);
+	kfree(c);
+exit:
+	mutex_unlock(&snd_event_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(snd_event_client_deregister);
+
+/*
+ * snd_event_mstr_add_client - Add a client to the master's list of clients
+ *
+ * @snd_clients: list of clients associated with this master
+ * @compare: Pointer to the compare callback function that master will use to
+ *           confirm the clients
+ * @data: Address to any additional data that the master wants to get back with
+ *        compare callback functions
+ */
+void snd_event_mstr_add_client(struct snd_event_clients **snd_clients,
+			       int (*compare)(struct device *, void *),
+			       void *data)
+{
+	struct snd_event_clients *client = *snd_clients;
+
+	if (IS_ERR(client)) {
+		pr_err("%s: snd_clients is invalid\n", __func__);
+		return;
+	}
+
+	if (!client) {
+		client = kzalloc(sizeof(*client), GFP_KERNEL);
+		if (!client) {
+			*snd_clients = ERR_PTR(-ENOMEM);
+			return;
+		}
+		client->cl_arr = kzalloc(sizeof(struct snd_event_client_array),
+					 GFP_KERNEL);
+		*snd_clients = client;
+	} else {
+		struct snd_event_client_array *new;
+
+		new = krealloc(client->cl_arr,
+			       (client->num_clients + 1) * sizeof(*new),
+			       GFP_KERNEL | __GFP_ZERO);
+		if (!new) {
+			*snd_clients = ERR_PTR(-ENOMEM);
+			return;
+		}
+		client->cl_arr = new;
+	}
+
+	client->cl_arr[client->num_clients].dev = NULL;
+	client->cl_arr[client->num_clients].data = data;
+	client->cl_arr[client->num_clients].compare = compare;
+	client->num_clients++;
+}
+EXPORT_SYMBOL(snd_event_mstr_add_client);
+
+/*
+ * snd_event_master_register - Register a master with the SND event FW
+ *
+ * @dev: Pointer to the "struct device" associated with the master
+ * @ops: Pointer to the snd_event_ops struct for the master containing
+ *       callback functions
+ * @clients: List of clients for the master
+ * @data: Pointer to any additional data that the caller wants to get back
+ *        with callback functions
+ *
+ * Returns 0 on success or error on failure.
+ *
+ * Prerequisite:
+ *  clients list must not be empty.
+ *  All clients for the master must have to be registered by calling
+ *  snd_event_mstr_add_client() before calling this API to register a
+ *  master with SND event fwk.
+ */
+int snd_event_master_register(struct device *dev,
+			      const struct snd_event_ops *ops,
+			      struct snd_event_clients *clients,
+			      void *data)
+{
+	struct snd_master *new_master;
+	int ret = 0;
+
+	if (!dev) {
+		pr_err("%s: dev is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&snd_event_mutex);
+	if (master) {
+		dev_err(dev, "%s: master already allocated with %pK\n",
+			__func__, master->dev);
+		ret = -EALREADY;
+		goto exit;
+	}
+	mutex_unlock(&snd_event_mutex);
+
+	if (!clients || IS_ERR(clients)) {
+		dev_err(dev, "%s: Invalid clients ptr\n", __func__);
+		return -EINVAL;
+	}
+
+	new_master = kzalloc(sizeof(*new_master), GFP_KERNEL);
+	if (!new_master)
+		return -ENOMEM;
+
+	new_master->dev = dev;
+	new_master->ops = ops;
+	new_master->data = data;
+	new_master->clients = clients;
+
+	dev_dbg(dev, "adding master to SND event FW (ops %pK)\n", ops);
+
+	mutex_lock(&snd_event_mutex);
+
+	master = new_master;
+
+	ret = snd_event_find_clients(master);
+	if (ret) {
+		dev_dbg(dev, "%s: Failed to find all clients\n", __func__);
+		ret = 0;
+		goto exit;
+	}
+	master->clients_found = true;
+
+exit:
+	mutex_unlock(&snd_event_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(snd_event_master_register);
+
+/*
+ * snd_event_master_deregister - Remove a master from the SND event FW
+ *
+ * @dev: Pointer to the "struct device" associated with the master
+ *
+ * Returns 0 on success or error on failure.
+ */
+int snd_event_master_deregister(struct device *dev)
+{
+	int ret = 0;
+
+	if (!dev) {
+		pr_err("%s: dev is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&snd_event_mutex);
+	if (!master) {
+		dev_dbg(dev, "%s: No master found\n", __func__);
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	if (master->dev != dev) {
+		dev_dbg(dev, "%s: device is not a Master\n", __func__);
+		ret = -ENXIO;
+		goto exit;
+	}
+
+	master->state = false;
+
+	if (master && master->clients_found)
+		ret = check_and_update_fwk_state();
+
+	kfree(master->clients->cl_arr);
+	kfree(master->clients);
+	kfree(master);
+	master = NULL;
+exit:
+	mutex_unlock(&snd_event_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(snd_event_master_deregister);
+
+/*
+ * snd_event_notify - Update the state of the Master/client in the SND event FW
+ *
+ * @dev: Pointer to the "struct device" associated with the master/client
+ * @state: UP/DOWN state of the caller (master/client)
+ *
+ * Returns 0 on success or error on failure.
+ */
+int snd_event_notify(struct device *dev, unsigned int state)
+{
+	struct snd_event_client *c;
+	int ret = 0;
+
+	if (!dev) {
+		pr_err("%s: dev is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&snd_event_mutex);
+	if (list_empty(&snd_event_client_list) && !master) {
+		dev_err(dev, "%s: No device registered\n", __func__);
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	c = find_snd_event_client(dev);
+	if (!c && (!master || (master->dev != dev))) {
+		dev_err(dev, "%s: No snd dev entry found\n", __func__);
+		ret = -ENXIO;
+		goto exit;
+	}
+
+	if (c)
+		c->state = !!state;
+	else
+		master->state = !!state;
+
+	if (master && master->clients_found)
+		ret = check_and_update_fwk_state();
+
+exit:
+	mutex_unlock(&snd_event_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(snd_event_notify);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SND event module");