Browse Source

ASoC: Add WCD937X slave and core driver

Add wcd937x swr slave and codec driver.
This adds only basic support for codec registration.

Change-Id: I87519a234f14d34a019c8f66652b7224759e639c
Signed-off-by: Rohit kumar <[email protected]>
Rohit kumar 6 years ago
parent
commit
759426ec4b

+ 2 - 2
Android.mk

@@ -30,8 +30,8 @@ include $(MY_LOCAL_PATH)/asoc/codecs/aqt1000/Android.mk
 endif
 
 ifeq ($(call is-board-platform-in-list,$(MSMSTEPPE)),true)
-$(shell rm -rf $(PRODUCT_OUT)/obj/vendor/qcom/opensource/audio-kernel/asoc/codecs/bolero/Module.symvers)
-include $(MY_LOCAL_PATH)/asoc/codecs/bolero/Android.mk
+$(shell rm -rf $(PRODUCT_OUT)/obj/vendor/qcom/opensource/audio-kernel/asoc/codecs/wcd937x/Module.symvers)
+include $(MY_LOCAL_PATH)/asoc/codecs/wcd937x/Android.mk
 endif
 
 ifeq ($(call is-board-platform-in-list,msm8953 sdm670 qcs605),true)

+ 1 - 0
asoc/codecs/Kbuild

@@ -186,6 +186,7 @@ ifeq ($(KERNEL_BUILD), 1)
 	obj-y	+= sdm660_cdc/
 	obj-y	+= msm_sdw/
 	obj-y	+= wcd9360/
+	obj-y	+= wcd937x/
 endif
 # Module information used by KBuild framework
 obj-$(CONFIG_WCD9XXX_CODEC_CORE) += wcd_core_dlkm.o

+ 57 - 0
asoc/codecs/wcd937x/Android.mk

@@ -0,0 +1,57 @@
+# Android makefile for audio kernel modules
+
+# Assume no targets will be supported
+
+# Check if this driver needs be built for current target
+ifeq ($(call is-board-platform,$(MSMSTEPPE)),true)
+AUDIO_SELECT  := CONFIG_SND_SOC_SM6150=m
+endif
+
+AUDIO_CHIPSET := audio
+# Build/Package only in case of supported target
+ifeq ($(call is-board-platform-in-list,msm8953 sdm845 sdm670 qcs605 msmnile $(MSMSTEPPE)),true)
+
+LOCAL_PATH := $(call my-dir)
+
+# This makefile is only for DLKM
+ifneq ($(findstring vendor,$(LOCAL_PATH)),)
+
+ifneq ($(findstring opensource,$(LOCAL_PATH)),)
+	AUDIO_BLD_DIR := $(ANDROID_BUILD_TOP)/vendor/qcom/opensource/audio-kernel
+endif # opensource
+
+DLKM_DIR := $(TOP)/device/qcom/common/dlkm
+
+# Build audio.ko as $(AUDIO_CHIPSET)_audio.ko
+###########################################################
+# This is set once per LOCAL_PATH, not per (kernel) module
+KBUILD_OPTIONS := AUDIO_ROOT=$(AUDIO_BLD_DIR)
+
+# We are actually building audio.ko here, as per the
+# requirement we are specifying <chipset>_audio.ko as LOCAL_MODULE.
+# This means we need to rename the module to <chipset>_audio.ko
+# after audio.ko is built.
+KBUILD_OPTIONS += MODNAME=wcd937x_dlkm
+KBUILD_OPTIONS += BOARD_PLATFORM=$(TARGET_BOARD_PLATFORM)
+KBUILD_OPTIONS += $(AUDIO_SELECT)
+
+###########################################################
+include $(CLEAR_VARS)
+LOCAL_MODULE              := $(AUDIO_CHIPSET)_wcd937x.ko
+LOCAL_MODULE_KBUILD_NAME  := wcd937x_dlkm.ko
+LOCAL_MODULE_TAGS         := optional
+LOCAL_MODULE_DEBUG_ENABLE := true
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/AndroidKernelModule.mk
+###########################################################
+include $(CLEAR_VARS)
+LOCAL_MODULE              := $(AUDIO_CHIPSET)_wcd937x_slave.ko
+LOCAL_MODULE_KBUILD_NAME  := wcd937x_slave_dlkm.ko
+LOCAL_MODULE_TAGS         := optional
+LOCAL_MODULE_DEBUG_ENABLE := true
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/AndroidKernelModule.mk
+###########################################################
+
+endif # DLKM check
+endif # supported target check

+ 110 - 0
asoc/codecs/wcd937x/Kbuild

@@ -0,0 +1,110 @@
+# We can build either as part of a standalone Kernel build or as
+# an external module.  Determine which mechanism is being used
+ifeq ($(MODNAME),)
+	KERNEL_BUILD := 1
+else
+	KERNEL_BUILD := 0
+endif
+
+
+
+ifeq ($(KERNEL_BUILD), 1)
+	# These are configurable via Kconfig for kernel-based builds
+	# Need to explicitly configure for Android-based builds
+	AUDIO_BLD_DIR := $(ANDROID_BUILD_TOP)/kernel/msm-4.9
+	AUDIO_ROOT := $(AUDIO_BLD_DIR)/techpack/audio
+endif
+
+ifeq ($(KERNEL_BUILD), 0)
+	ifeq ($(CONFIG_ARCH_SM6150), y)
+		include $(AUDIO_ROOT)/config/sm6150auto.conf
+		export
+		INCS    +=  -include $(AUDIO_ROOT)/config/sm6150autoconf.h
+	endif
+endif
+
+# As per target team, build is done as follows:
+# Defconfig : build with default flags
+# Slub      : defconfig  + CONFIG_SLUB_DEBUG := y +
+#	      CONFIG_SLUB_DEBUG_ON := y + CONFIG_PAGE_POISONING := y
+# Perf      : Using appropriate msmXXXX-perf_defconfig
+#
+# Shipment builds (user variants) should not have any debug feature
+# enabled. This is identified using 'TARGET_BUILD_VARIANT'. Slub builds
+# are identified using the CONFIG_SLUB_DEBUG_ON configuration. Since
+# there is no other way to identify defconfig builds, QTI internal
+# representation of perf builds (identified using the string 'perf'),
+# is used to identify if the build is a slub or defconfig one. This
+# way no critical debug feature will be enabled for perf and shipment
+# builds. Other OEMs are also protected using the TARGET_BUILD_VARIANT
+# config.
+
+############ UAPI ############
+UAPI_DIR :=	uapi
+UAPI_INC :=	-I$(AUDIO_ROOT)/include/$(UAPI_DIR)
+
+############ COMMON ############
+COMMON_DIR :=	include
+COMMON_INC :=	-I$(AUDIO_ROOT)/$(COMMON_DIR)
+
+############ WCD937X ############
+
+# for WCD937X Codec
+ifdef CONFIG_SND_SOC_WCD937X
+	WCD937X_OBJS += wcd937x.o
+endif
+
+ifdef CONFIG_SND_SOC_WCD937X_SLAVE
+	WCD937X_SLAVE_OBJS += wcd937x_slave.o
+endif
+
+LINUX_INC +=	-Iinclude/linux
+
+INCS +=		$(COMMON_INC) \
+		$(UAPI_INC)
+
+EXTRA_CFLAGS += $(INCS)
+
+
+CDEFINES +=	-DANI_LITTLE_BYTE_ENDIAN \
+		-DANI_LITTLE_BIT_ENDIAN \
+		-DDOT11F_LITTLE_ENDIAN_HOST \
+		-DANI_COMPILER_TYPE_GCC \
+		-DANI_OS_TYPE_ANDROID=6 \
+		-DPTT_SOCK_SVC_ENABLE \
+		-Wall\
+		-Werror\
+		-D__linux__
+
+KBUILD_CPPFLAGS += $(CDEFINES)
+
+# Currently, for versions of gcc which support it, the kernel Makefile
+# is disabling the maybe-uninitialized warning.  Re-enable it for the
+# AUDIO driver.  Note that we must use EXTRA_CFLAGS here so that it
+# will override the kernel settings.
+ifeq ($(call cc-option-yn, -Wmaybe-uninitialized),y)
+EXTRA_CFLAGS += -Wmaybe-uninitialized
+endif
+#EXTRA_CFLAGS += -Wmissing-prototypes
+
+ifeq ($(call cc-option-yn, -Wheader-guard),y)
+EXTRA_CFLAGS += -Wheader-guard
+endif
+
+ifeq ($(KERNEL_BUILD), 0)
+KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/ipc/Module.symvers
+KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/dsp/Module.symvers
+KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/asoc/Module.symvers
+KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/asoc/codecs/Module.symvers
+KBUILD_EXTRA_SYMBOLS +=$(OUT)/obj/vendor/qcom/opensource/audio-kernel/soc/Module.symvers
+endif
+
+# Module information used by KBuild framework
+obj-$(CONFIG_SND_SOC_WCD937X) += wcd937x_dlkm.o
+wcd937x_dlkm-y := $(WCD937X_OBJS)
+
+obj-$(CONFIG_SND_SOC_WCD937X_SLAVE) += wcd937x_slave_dlkm.o
+wcd937x_slave_dlkm-y := $(WCD937X_SLAVE_OBJS)
+
+# inject some build related information
+DEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\"

+ 310 - 0
asoc/codecs/wcd937x/wcd937x.c

@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2018, 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/slab.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/component.h>
+#include <sound/soc.h>
+#include <soc/soundwire.h>
+#include "../msm-cdc-pinctrl.h"
+
+struct wcd937x_priv {
+	struct device *dev;
+	struct snd_soc_codec *codec;
+	struct device_node *rst_np;
+	struct swr_device *rx_swr_dev;
+	struct swr_device *tx_swr_dev;
+};
+
+struct wcd937x_pdata {
+	struct device_node *rst_np;
+	struct device_node *rx_slave;
+	struct device_node *tx_slave;
+};
+
+static int wcd937x_soc_codec_probe(struct snd_soc_codec *codec)
+{
+	struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec);
+
+	if (!wcd937x)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int wcd937x_soc_codec_remove(struct snd_soc_codec *codec)
+{
+	struct wcd937x_priv *wcd937x = snd_soc_codec_get_drvdata(codec);
+
+	if (!wcd937x)
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct regmap *wcd937x_get_regmap(struct device *dev)
+{
+	struct wcd937x_priv *wcd937x = dev_get_drvdata(dev);
+
+	return wcd937x->regmap;
+}
+
+static const struct snd_kcontrol_new wcd937x_snd_controls[] = {
+};
+
+static const struct snd_soc_dapm_widget wcd937x_dapm_widgets[] = {
+};
+
+static const struct snd_soc_dapm_route wcd937x_audio_map[] = {
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wcd937x = {
+	.probe = wcd937x_soc_codec_probe,
+	.remove = wcd937x_soc_codec_remove,
+	.get_regmap = wcd937x_get_regmap,
+	.component_driver = {
+		.controls = wcd937x_snd_controls,
+		.num_controls = ARRAY_SIZE(wcd937x_snd_controls),
+		.dapm_widgets = wcd937x_dapm_widgets,
+		.num_dapm_widgets = ARRAY_SIZE(wcd937x_dapm_widgets),
+		.dapm_routes = wcd937x_audio_map,
+		.num_dapm_routes = ARRAY_SIZE(wcd937x_audio_map),
+	},
+};
+
+int wcd937x_reset(struct device *dev)
+{
+	struct wcd937x_priv *wcd937x = NULL;
+	int rc = 0;
+	int value = 0;
+
+	if (!dev)
+		return -ENODEV;
+
+	wcd937x = dev_get_drvdata(dev);
+	if (!wcd937x)
+		return -EINVAL;
+
+	if (!wcd937x->rst_np) {
+		dev_err(dev, "%s: reset gpio device node not specified\n",
+				__func__);
+		return -EINVAL;
+	}
+
+	value = msm_cdc_pinctrl_get_state(wcd937x->rst_np);
+	if (value > 0)
+		return 0;
+
+	rc = msm_cdc_pinctrl_select_sleep_state(wcd937x->rst_np);
+	if (rc) {
+		dev_err(dev, "%s: wcd sleep state request fail!\n",
+				__func__);
+		return rc;
+	}
+
+	/* 20ms sleep required after pulling the reset gpio to LOW */
+	msleep(20);
+
+	rc = msm_cdc_pinctrl_select_active_state(wcd937x->rst_np);
+	if (rc) {
+		dev_err(dev, "%s: wcd active state request fail!\n",
+				__func__);
+		return rc;
+	}
+	msleep(20);
+
+	return rc;
+}
+
+struct wcd937x_pdata *wcd937x_populate_dt_data(struct device *dev)
+{
+	struct wcd937x_pdata *pdata = NULL;
+
+	pdata = devm_kzalloc(dev, sizeof(struct wcd937x_pdata),
+				GFP_KERNEL);
+	if (!pdata)
+		return NULL;
+
+	pdata->rst_np = of_parse_phandle(dev->of_node,
+			"qcom,wcd937x-reset-node", 0);
+	if (!pdata->rst_np) {
+		dev_err(dev, "%s: Looking up %s property in node %s failed\n",
+				__func__, "qcom,wcd937x-reset-node",
+				dev->of_node->full_name);
+		return NULL;
+	}
+
+	pdata->rx_slave = of_parse_phandle(dev->of_node, "qcom,rx-slave", 0);
+	pdata->tx_slave = of_parse_phandle(dev->of_node, "qcom,tx-slave", 0);
+
+	return pdata;
+}
+
+static int wcd937x_bind(struct device *dev)
+{
+	int ret = 0;
+	struct wcd937x_priv *wcd937x = NULL;
+	struct wcd937x_pdata *pdata = NULL;
+
+	wcd937x = devm_kzalloc(dev, sizeof(struct wcd937x_priv), GFP_KERNEL);
+	if (!wcd937x)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, wcd937x);
+
+	pdata = wcd937x_populate_dt_data(dev);
+	if (!pdata) {
+		dev_err(dev, "%s: Fail to obtain platform data\n", __func__);
+		return -EINVAL;
+	}
+
+	wcd937x->rst_np = pdata->rst_np;
+	wcd937x_reset(dev);
+	/*
+	 * Add 5msec delay to provide sufficient time for
+	 * soundwire auto enumeration of slave devices as
+	 * as per HW requirement.
+	 */
+	usleep_range(5000, 5010);
+	ret = component_bind_all(dev, wcd937x);
+	if (ret) {
+		dev_err(dev, "%s: Slave bind failed, ret = %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	wcd937x->rx_swr_dev = get_matching_swr_slave_device(pdata->rx_slave);
+	if (!wcd937x->rx_swr_dev) {
+		dev_err(dev, "%s: Could not find RX swr slave device\n",
+			 __func__);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	wcd937x->tx_swr_dev = get_matching_swr_slave_device(pdata->tx_slave);
+	if (!wcd937x->tx_swr_dev) {
+		dev_err(dev, "%s: Could not find TX swr slave device\n",
+			__func__);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ret = snd_soc_register_codec(dev, &soc_codec_dev_wcd937x,
+			NULL, 0);
+	if (ret) {
+		dev_err(dev, "%s: Codec registration failed\n",
+				__func__);
+		goto err;
+	}
+
+	return ret;
+err:
+	component_unbind_all(dev, wcd937x);
+	return ret;
+}
+
+static void wcd937x_unbind(struct device *dev)
+{
+	struct wcd937x_priv *wcd937x = dev_get_drvdata(dev);
+
+	snd_soc_unregister_codec(dev);
+	component_unbind_all(dev, wcd937x);
+}
+
+static const struct of_device_id wcd937x_dt_match[] = {
+	{ .compatible = "qcom,wcd937x-codec" },
+	{}
+};
+
+static const struct component_master_ops wcd937x_comp_ops = {
+	.bind   = wcd937x_bind,
+	.unbind = wcd937x_unbind,
+};
+
+static int wcd937x_compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static void wcd937x_release_of(struct device *dev, void *data)
+{
+	of_node_put(data);
+}
+
+static int wcd937x_add_slave_components(struct device *dev,
+				struct component_match **matchptr)
+{
+	struct device_node *np, *rx_node, *tx_node;
+
+	np = dev->of_node;
+
+	rx_node = of_parse_phandle(np, "qcom,rx-slave", 0);
+	if (!rx_node) {
+		dev_err(dev, "%s: Rx-slave node not defined\n", __func__);
+		return -ENODEV;
+	}
+	of_node_get(rx_node);
+	component_match_add_release(dev, matchptr,
+			wcd937x_release_of,
+			wcd937x_compare_of,
+			rx_node);
+
+	tx_node = of_parse_phandle(np, "qcom,tx-slave", 0);
+	if (!tx_node) {
+		dev_err(dev, "%s: Tx-slave node not defined\n", __func__);
+			return -ENODEV;
+	}
+	of_node_get(tx_node);
+	component_match_add_release(dev, matchptr,
+			wcd937x_release_of,
+			wcd937x_compare_of,
+			tx_node);
+	return 0;
+}
+
+static int wcd937x_probe(struct platform_device *pdev)
+{
+	struct component_match *match = NULL;
+	int ret;
+
+	ret = wcd937x_add_slave_components(&pdev->dev, &match);
+	if (ret)
+		return ret;
+
+	return component_master_add_with_match(&pdev->dev,
+					&wcd937x_comp_ops, match);
+}
+
+static int wcd937x_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &wcd937x_comp_ops);
+	return 0;
+}
+
+static struct platform_driver wcd937x_codec_driver = {
+	.probe = wcd937x_probe,
+	.remove = wcd937x_remove,
+	.driver = {
+		.name = "wcd937x_codec",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(wcd937x_dt_match),
+	},
+};
+
+module_platform_driver(wcd937x_codec_driver);
+MODULE_DESCRIPTION("WCD937X Codec driver");
+MODULE_LICENSE("GPL v2");

+ 124 - 0
asoc/codecs/wcd937x/wcd937x_slave.c

@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018, 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/slab.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/component.h>
+#include <soc/soundwire.h>
+
+struct wcd937x_slave_priv {
+	struct swr_device *swr_slave;
+};
+
+static int wcd937x_slave_bind(struct device *dev,
+				struct device *master, void *data)
+{
+	int ret = 0;
+	struct wcd937x_slave_priv *wcd937x_slave = NULL;
+	uint8_t devnum = 0;
+	struct swr_device *pdev = to_swr_device(dev);
+
+	wcd937x_slave = devm_kzalloc(&pdev->dev,
+				sizeof(struct wcd937x_slave_priv), GFP_KERNEL);
+	if (!wcd937x_slave)
+		return -ENOMEM;
+
+	swr_set_dev_data(pdev, wcd937x_slave);
+
+	wcd937x_slave->swr_slave = pdev;
+
+	ret = swr_get_logical_dev_num(pdev, pdev->addr, &devnum);
+	if (ret) {
+		dev_dbg(&pdev->dev,
+				"%s get devnum %d for dev addr %lx failed\n",
+				__func__, devnum, pdev->addr);
+		swr_remove_device(pdev);
+		return ret;
+	}
+	pdev->dev_num = devnum;
+
+	return ret;
+}
+
+static void wcd937x_slave_unbind(struct device *dev,
+				struct device *master, void *data)
+{
+	struct wcd937x_slave_priv *wcd937x_slave = NULL;
+	struct swr_device *pdev = to_swr_device(dev);
+
+	wcd937x_slave = swr_get_dev_data(pdev);
+	if (!wcd937x_slave) {
+		dev_err(&pdev->dev, "%s: wcd937x_slave is NULL\n", __func__);
+		return;
+	}
+
+	swr_set_dev_data(pdev, NULL);
+}
+
+static const struct swr_device_id wcd937x_swr_id[] = {
+	{"wcd937x-slave", 0},
+	{}
+};
+
+static const struct of_device_id wcd937x_swr_dt_match[] = {
+	{
+		.compatible = "qcom,wcd937x-slave",
+	},
+	{}
+};
+
+static const struct component_ops wcd937x_slave_comp_ops = {
+	.bind   = wcd937x_slave_bind,
+	.unbind = wcd937x_slave_unbind,
+};
+
+static int wcd937x_swr_probe(struct swr_device *pdev)
+{
+	return component_add(&pdev->dev, &wcd937x_slave_comp_ops);
+}
+
+static int wcd937x_swr_remove(struct swr_device *pdev)
+{
+	component_del(&pdev->dev, &wcd937x_slave_comp_ops);
+	return 0;
+}
+
+static struct swr_driver wcd937x_slave_driver = {
+	.driver = {
+		.name = "wcd937x-slave",
+		.owner = THIS_MODULE,
+		.of_match_table = wcd937x_swr_dt_match,
+	},
+	.probe = wcd937x_swr_probe,
+	.remove = wcd937x_swr_remove,
+	.id_table = wcd937x_swr_id,
+};
+
+static int __init wcd937x_slave_init(void)
+{
+	return swr_driver_register(&wcd937x_slave_driver);
+}
+
+static void __exit wcd937x_slave_exit(void)
+{
+	swr_driver_unregister(&wcd937x_slave_driver);
+}
+
+module_init(wcd937x_slave_init);
+module_exit(wcd937x_slave_exit);
+
+MODULE_DESCRIPTION("WCD937X Swr Slave driver");
+MODULE_LICENSE("GPL v2");

+ 3 - 0
include/soc/soundwire.h

@@ -334,4 +334,7 @@ extern int swr_slvdev_datapath_control(struct swr_device *swr_dev, u8 dev_num,
 extern int swr_remove_from_group(struct swr_device *dev, u8 dev_num);
 
 extern void swr_remove_device(struct swr_device *swr_dev);
+
+extern struct swr_device *get_matching_swr_slave_device(struct device_node *np);
+
 #endif /* _LINUX_SOUNDWIRE_H */

+ 20 - 0
soc/soundwire.c

@@ -792,6 +792,26 @@ void swr_master_add_boarddevices(struct swr_master *master)
 }
 EXPORT_SYMBOL(swr_master_add_boarddevices);
 
+struct swr_device *get_matching_swr_slave_device(struct device_node *np)
+{
+	struct swr_device *swr = NULL;
+	struct swr_master *master;
+
+	mutex_lock(&board_lock);
+	list_for_each_entry(master, &swr_master_list, list) {
+		mutex_lock(&master->mlock);
+		list_for_each_entry(swr, &master->devices, dev_list) {
+			if (swr->dev.of_node == np)
+				break;
+		}
+		mutex_unlock(&master->mlock);
+	}
+	mutex_unlock(&board_lock);
+
+	return swr;
+}
+EXPORT_SYMBOL(get_matching_swr_slave_device);
+
 static void swr_unregister_device(struct swr_device *swr)
 {
 	if (swr)