Merge "bluetooth: Add bluetooth slimbus slave drivers"
diff --git a/Documentation/devicetree/bindings/bluetooth/btfm_slim.txt b/Documentation/devicetree/bindings/bluetooth/btfm_slim.txt
new file mode 100644
index 0000000..9e1524a
--- /dev/null
+++ b/Documentation/devicetree/bindings/bluetooth/btfm_slim.txt
@@ -0,0 +1,20 @@
+* BTFM Slimbus Slave Driver
+BTFM Slimbus Slave driver configure and initialize slimbus slave device.
+Bluetooth SCO and FM Audio data is transferred over slimbus interface.
+
+Required properties:
+	- compatible: Should be set to one of the following:
+		btfmslim_slave
+	- qcom,btfm-slim-ifd: BTFM slimbus slave device entry name
+
+Optional properties:
+	- qcom,btfm-slim-ifd-elemental-addr: BTFM slimbus slave device
+		enumeration address
+
+Example:
+	btfmslim_codec: qca6390 {
+		compatible = "qcom,btfmslim_slave";
+		elemental-addr = [00 01 20 02 17 02];
+		qcom,btfm-slim-ifd = "btfmslim_slave_ifd";
+		qcom,btfm-slim-ifd-elemental-addr = [00 00 20 02 17 02];
+	};
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 20cbc4c..3e5f9c8 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -421,4 +421,33 @@
 	  This provides a parameter to switch on/off power from PMIC
 	  to Bluetooth device. This will control LDOs/Clock/GPIOs to
 	  control Bluetooth Chipset based on power on/off sequence.
+
+config BTFM_SLIM
+	bool "MSM Bluetooth/FM Slimbus Driver"
+	select SLIMBUS
+	help
+	  This enables BT/FM slimbus driver to get multiple audio channel.
+	  This will make use of slimbus platform driver and slimbus codec
+	  driver to communicate with slimbus machine driver and LPSS which
+	  is Slimbus master.
+
+	  Slimbus slave initialization and configuration will be done through
+	  this driver.
+
+config BTFM_SLIM_WCN3990
+	bool "MSM Bluetooth/FM WCN3990 Device"
+	select BTFM_SLIM
+	help
+	  This enables specific driver handle for WCN3990 device.
+	  It is designed to adapt any future BT/FM device to implement a specific
+	  chip initialization process and control.
+
+config BT_SLIM_QCA6390
+	bool "MSM Bluetooth QCA6390 Device"
+	select BTFM_SLIM
+	help
+	  This enables specific driver handle for QCA6390 device.
+	  It is designed to adapt any future BT/FM device to implement a specific
+	  chip initialization process and control.
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 9617314..6354ccd 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -30,6 +30,11 @@
 
 obj-$(CONFIG_BT_HCIRSI)		+= btrsi.o
 obj-$(CONFIG_MSM_BT_POWER)	+= bluetooth-power.o
+obj-$(CONFIG_BTFM_SLIM)		+= btfm_slim.o
+obj-$(CONFIG_BTFM_SLIM)		+= btfm_slim_codec.o
+obj-$(CONFIG_BTFM_SLIM_WCN3990)	+= btfm_slim_slave.o
+obj-$(CONFIG_BT_SLIM_QCA6390)	+= btfm_slim_slave.o
+
 btmrvl-y			:= btmrvl_main.o
 btmrvl-$(CONFIG_DEBUG_FS)	+= btmrvl_debugfs.o
 
diff --git a/drivers/bluetooth/bluetooth-power.c b/drivers/bluetooth/bluetooth-power.c
index d6d9deb..58bea41b 100644
--- a/drivers/bluetooth/bluetooth-power.c
+++ b/drivers/bluetooth/bluetooth-power.c
@@ -26,7 +26,7 @@
 #include <net/cnss.h>
 #endif
 
-#ifdef CONFIG_BTFM_SLIM
+#if defined CONFIG_BT_SLIM_QCA6390 || defined CONFIG_BTFM_SLIM_WCN3990
 #include "btfm_slim.h"
 #endif
 #include <linux/fs.h>
@@ -686,8 +686,8 @@
 	int ret = 0, pwr_cntrl = 0;
 
 	switch (cmd) {
-#ifdef CONFIG_BTFM_SLIM
 	case BT_CMD_SLIM_TEST:
+#if defined CONFIG_BT_SLIM_QCA6390 || defined CONFIG_BTFM_SLIM_WCN3990
 		if (!bt_power_pdata->slim_dev) {
 			BT_PWR_ERR("slim_dev is null\n");
 			return -EINVAL;
@@ -695,8 +695,8 @@
 		ret = btfm_slim_hw_init(
 			bt_power_pdata->slim_dev->platform_data
 		);
-		break;
 #endif
+		break;
 	case BT_CMD_PWR_CTRL:
 		pwr_cntrl = (int)arg;
 		BT_PWR_ERR("BT_CMD_PWR_CTRL pwr_cntrl:%d", pwr_cntrl);
diff --git a/drivers/bluetooth/btfm_slim.c b/drivers/bluetooth/btfm_slim.c
new file mode 100644
index 0000000..22e96f2
--- /dev/null
+++ b/drivers/bluetooth/btfm_slim.c
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/debugfs.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <btfm_slim.h>
+#include <btfm_slim_slave.h>
+#include <linux/bluetooth-power.h>
+
+int btfm_slim_write(struct btfmslim *btfmslim,
+		uint16_t reg, int bytes, void *src, uint8_t pgd)
+{
+	int ret, i;
+	struct slim_ele_access msg;
+	int slim_write_tries = SLIM_SLAVE_RW_MAX_TRIES;
+
+	BTFMSLIM_DBG("Write to %s", pgd?"PGD":"IFD");
+	msg.start_offset = SLIM_SLAVE_REG_OFFSET + reg;
+	msg.num_bytes = bytes;
+	msg.comp = NULL;
+
+	for ( ; slim_write_tries != 0; slim_write_tries--) {
+		mutex_lock(&btfmslim->xfer_lock);
+		ret = slim_change_val_element(pgd ? btfmslim->slim_pgd :
+			&btfmslim->slim_ifd, &msg, src, bytes);
+		mutex_unlock(&btfmslim->xfer_lock);
+		if (ret == 0)
+			break;
+		usleep_range(5000, 5100);
+	}
+
+	if (ret) {
+		BTFMSLIM_ERR("failed (%d)", ret);
+		return ret;
+	}
+
+	for (i = 0; i < bytes; i++)
+		BTFMSLIM_DBG("Write 0x%02x to reg 0x%x", ((uint8_t *)src)[i],
+			reg + i);
+	return 0;
+}
+
+int btfm_slim_write_pgd(struct btfmslim *btfmslim,
+		uint16_t reg, int bytes, void *src)
+{
+	return btfm_slim_write(btfmslim, reg, bytes, src, PGD);
+}
+
+int btfm_slim_write_inf(struct btfmslim *btfmslim,
+		uint16_t reg, int bytes, void *src)
+{
+	return btfm_slim_write(btfmslim, reg, bytes, src, IFD);
+}
+
+int btfm_slim_read(struct btfmslim *btfmslim, unsigned short reg,
+				int bytes, void *dest, uint8_t pgd)
+{
+	int ret, i;
+	struct slim_ele_access msg;
+	int slim_read_tries = SLIM_SLAVE_RW_MAX_TRIES;
+
+	BTFMSLIM_DBG("Read from %s", pgd?"PGD":"IFD");
+	msg.start_offset = SLIM_SLAVE_REG_OFFSET + reg;
+	msg.num_bytes = bytes;
+	msg.comp = NULL;
+
+	for ( ; slim_read_tries != 0; slim_read_tries--) {
+		mutex_lock(&btfmslim->xfer_lock);
+		ret = slim_request_val_element(pgd ? btfmslim->slim_pgd :
+			&btfmslim->slim_ifd, &msg, dest, bytes);
+		mutex_unlock(&btfmslim->xfer_lock);
+		if (ret == 0)
+			break;
+		usleep_range(5000, 5100);
+	}
+
+	if (ret)
+		BTFMSLIM_ERR("failed (%d)", ret);
+
+	for (i = 0; i < bytes; i++)
+		BTFMSLIM_DBG("Read 0x%02x from reg 0x%x", ((uint8_t *)dest)[i],
+			reg + i);
+
+	return 0;
+}
+
+int btfm_slim_read_pgd(struct btfmslim *btfmslim,
+		uint16_t reg, int bytes, void *dest)
+{
+	return btfm_slim_read(btfmslim, reg, bytes, dest, PGD);
+}
+
+int btfm_slim_read_inf(struct btfmslim *btfmslim,
+		uint16_t reg, int bytes, void *dest)
+{
+	return btfm_slim_read(btfmslim, reg, bytes, dest, IFD);
+}
+
+int btfm_slim_enable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
+	uint8_t rxport, uint32_t rates, uint8_t grp, uint8_t nchan)
+{
+	int ret, i;
+	struct slim_ch prop;
+	struct btfmslim_ch *chan = ch;
+	uint16_t ch_h[2];
+
+	if (!btfmslim || !ch)
+		return -EINVAL;
+
+	BTFMSLIM_DBG("port: %d ch: %d", ch->port, ch->ch);
+
+	/* Define the channel with below parameters */
+	prop.prot =  ((rates == 44100) || (rates == 88200)) ?
+			SLIM_PUSH : SLIM_AUTO_ISO;
+	prop.baser = ((rates == 44100) || (rates == 88200)) ?
+			SLIM_RATE_11025HZ : SLIM_RATE_4000HZ;
+	prop.dataf = ((rates == 48000) || (rates == 44100) ||
+		(rates == 88200) || (rates == 96000)) ?
+			SLIM_CH_DATAF_NOT_DEFINED : SLIM_CH_DATAF_LPCM_AUDIO;
+	prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE;
+	prop.ratem = ((rates == 44100) || (rates == 88200)) ?
+		(rates/11025) : (rates/4000);
+	prop.sampleszbits = 16;
+
+	ch_h[0] = ch->ch_hdl;
+	ch_h[1] = (grp) ? (ch+1)->ch_hdl : 0;
+
+	BTFMSLIM_INFO("channel define - prot:%d, dataf:%d, auxf:%d",
+			prop.prot, prop.dataf, prop.auxf);
+	BTFMSLIM_INFO("channel define - rates:%d, baser:%d, ratem:%d",
+			rates, prop.baser, prop.ratem);
+
+	ret = slim_define_ch(btfmslim->slim_pgd, &prop, ch_h, nchan, grp,
+			&ch->grph);
+	if (ret < 0) {
+		BTFMSLIM_ERR("slim_define_ch failed ret[%d]", ret);
+		goto error;
+	}
+
+	for (i = 0; i < nchan; i++, ch++) {
+		/* Enable port through registration setting */
+		if (btfmslim->vendor_port_en) {
+			ret = btfmslim->vendor_port_en(btfmslim, ch->port,
+					rxport, 1);
+			if (ret < 0) {
+				BTFMSLIM_ERR("vendor_port_en failed ret[%d]",
+					ret);
+				goto error;
+			}
+		}
+
+		if (rxport) {
+			BTFMSLIM_INFO("slim_connect_sink(port: %d, ch: %d)",
+				ch->port, ch->ch);
+			/* Connect Port with channel given by Machine driver*/
+			ret = slim_connect_sink(btfmslim->slim_pgd,
+				&ch->port_hdl, 1, ch->ch_hdl);
+			if (ret < 0) {
+				BTFMSLIM_ERR("slim_connect_sink failed ret[%d]",
+					ret);
+				goto remove_channel;
+			}
+
+		} else {
+			BTFMSLIM_INFO("slim_connect_src(port: %d, ch: %d)",
+				ch->port, ch->ch);
+			/* Connect Port with channel given by Machine driver*/
+			ret = slim_connect_src(btfmslim->slim_pgd, ch->port_hdl,
+				ch->ch_hdl);
+			if (ret < 0) {
+				BTFMSLIM_ERR("slim_connect_src failed ret[%d]",
+					ret);
+				goto remove_channel;
+			}
+		}
+	}
+
+	/* Activate the channel immediately */
+	BTFMSLIM_INFO(
+		"port: %d, ch: %d, grp: %d, ch->grph: 0x%x, ch_hdl: 0x%x",
+		chan->port, chan->ch, grp, chan->grph, chan->ch_hdl);
+	ret = slim_control_ch(btfmslim->slim_pgd, (grp ? chan->grph :
+		chan->ch_hdl), SLIM_CH_ACTIVATE, true);
+	if (ret < 0) {
+		BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret);
+		goto remove_channel;
+	}
+
+error:
+	return ret;
+
+remove_channel:
+	/* Remove the channel immediately*/
+	ret = slim_control_ch(btfmslim->slim_pgd, (grp ? ch->grph : ch->ch_hdl),
+			SLIM_CH_REMOVE, true);
+	if (ret < 0)
+		BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret);
+
+	return ret;
+}
+
+int btfm_slim_disable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
+	uint8_t rxport, uint8_t grp, uint8_t nchan)
+{
+	int ret, i;
+
+	if (!btfmslim || !ch)
+		return -EINVAL;
+
+	BTFMSLIM_INFO("port:%d, grp: %d, ch->grph:0x%x, ch->ch_hdl:0x%x ",
+		ch->port, grp, ch->grph, ch->ch_hdl);
+
+	/* For 44.1/88.2 Khz A2DP Rx, disconnect the port first */
+	if (rxport &&
+		(btfmslim->sample_rate == 44100 ||
+		 btfmslim->sample_rate == 88200)) {
+		BTFMSLIM_DBG("disconnecting the ports, removing the channel");
+		ret = slim_disconnect_ports(btfmslim->slim_pgd,
+				&ch->port_hdl, 1);
+		if (ret < 0) {
+			BTFMSLIM_ERR("slim_disconnect_ports failed ret[%d]",
+				ret);
+		}
+	}
+
+	/* Remove the channel immediately*/
+	ret = slim_control_ch(btfmslim->slim_pgd, (grp ? ch->grph : ch->ch_hdl),
+			SLIM_CH_REMOVE, true);
+	if (ret < 0) {
+		BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret);
+		if (btfmslim->sample_rate != 44100 &&
+			btfmslim->sample_rate != 88200) {
+			ret = slim_disconnect_ports(btfmslim->slim_pgd,
+				&ch->port_hdl, 1);
+			if (ret < 0) {
+				BTFMSLIM_ERR("disconnect_ports failed ret[%d]",
+					 ret);
+				goto error;
+			}
+		}
+	}
+
+	/* Disable port through registration setting */
+	for (i = 0; i < nchan; i++, ch++) {
+		if (btfmslim->vendor_port_en) {
+			ret = btfmslim->vendor_port_en(btfmslim, ch->port,
+				rxport, 0);
+			if (ret < 0) {
+				BTFMSLIM_ERR("vendor_port_en failed ret[%d]",
+					ret);
+				break;
+			}
+		}
+	}
+error:
+	return ret;
+}
+static int btfm_slim_get_logical_addr(struct slim_device *slim)
+{
+	int ret = 0;
+	const unsigned long timeout = jiffies +
+			      msecs_to_jiffies(SLIM_SLAVE_PRESENT_TIMEOUT);
+
+	do {
+		ret = slim_get_logical_addr(slim, slim->e_addr,
+			ARRAY_SIZE(slim->e_addr), &slim->laddr);
+		if (!ret)  {
+			BTFMSLIM_DBG("Assigned l-addr: 0x%x", slim->laddr);
+			break;
+		}
+		/* Give SLIMBUS time to report present and be ready. */
+		usleep_range(1000, 1100);
+		BTFMSLIM_DBG("retyring get logical addr");
+	} while (time_before(jiffies, timeout));
+
+	return ret;
+}
+
+static int btfm_slim_alloc_port(struct btfmslim *btfmslim)
+{
+	int ret = -EINVAL, i;
+	struct btfmslim_ch *rx_chs;
+	struct btfmslim_ch *tx_chs;
+
+	if (!btfmslim)
+		return ret;
+
+	rx_chs = btfmslim->rx_chs;
+	tx_chs = btfmslim->tx_chs;
+
+	if (!rx_chs || !tx_chs)
+		return ret;
+
+	BTFMSLIM_DBG("Rx: id\tname\tport\thdl\tch\tch_hdl");
+	for (i = 0 ; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
+		(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, rx_chs++) {
+
+		/* Get Rx port handler from slimbus driver based
+		 * on port number
+		 */
+		ret = slim_get_slaveport(btfmslim->slim_pgd->laddr,
+			rx_chs->port, &rx_chs->port_hdl, SLIM_SINK);
+		if (ret < 0) {
+			BTFMSLIM_ERR("slave port failure port#%d - ret[%d]",
+				rx_chs->port, SLIM_SINK);
+			return ret;
+		}
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", rx_chs->id,
+			rx_chs->name, rx_chs->port, rx_chs->port_hdl,
+			rx_chs->ch, rx_chs->ch_hdl);
+	}
+
+	BTFMSLIM_DBG("Tx: id\tname\tport\thdl\tch\tch_hdl");
+	for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
+		(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) {
+
+		/* Get Tx port handler from slimbus driver based
+		 * on port number
+		 */
+		ret = slim_get_slaveport(btfmslim->slim_pgd->laddr,
+			tx_chs->port, &tx_chs->port_hdl, SLIM_SRC);
+		if (ret < 0) {
+			BTFMSLIM_ERR("slave port failure port#%d - ret[%d]",
+				tx_chs->port, SLIM_SRC);
+			return ret;
+		}
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id,
+			tx_chs->name, tx_chs->port, tx_chs->port_hdl,
+			tx_chs->ch, tx_chs->ch_hdl);
+	}
+	return ret;
+}
+
+int btfm_slim_hw_init(struct btfmslim *btfmslim)
+{
+	int ret;
+
+	BTFMSLIM_DBG("");
+	if (!btfmslim)
+		return -EINVAL;
+
+	if (btfmslim->enabled) {
+		BTFMSLIM_DBG("Already enabled");
+		return 0;
+	}
+	mutex_lock(&btfmslim->io_lock);
+
+	/* Assign Logical Address for PGD (Ported Generic Device)
+	 * enumeration address
+	 */
+	ret = btfm_slim_get_logical_addr(btfmslim->slim_pgd);
+	if (ret) {
+		BTFMSLIM_ERR("failed to get slimbus %s logical address: %d",
+		       btfmslim->slim_pgd->name, ret);
+		goto error;
+	}
+
+	/* Assign Logical Address for Ported Generic Device
+	 * enumeration address
+	 */
+	ret = btfm_slim_get_logical_addr(&btfmslim->slim_ifd);
+	if (ret) {
+		BTFMSLIM_ERR("failed to get slimbus %s logical address: %d",
+		       btfmslim->slim_ifd.name, ret);
+		goto error;
+	}
+
+	/* Allocate ports with logical address to get port handler from
+	 * slimbus driver
+	 */
+	ret = btfm_slim_alloc_port(btfmslim);
+	if (ret)
+		goto error;
+
+	/* Start vendor specific initialization and get port information */
+	if (btfmslim->vendor_init)
+		ret = btfmslim->vendor_init(btfmslim);
+
+	/* Only when all registers read/write successfully, it set to
+	 * enabled status
+	 */
+	btfmslim->enabled = 1;
+error:
+	mutex_unlock(&btfmslim->io_lock);
+	return ret;
+}
+
+
+int btfm_slim_hw_deinit(struct btfmslim *btfmslim)
+{
+	int ret = 0;
+
+	if (!btfmslim)
+		return -EINVAL;
+
+	if (!btfmslim->enabled) {
+		BTFMSLIM_DBG("Already disabled");
+		return 0;
+	}
+	mutex_lock(&btfmslim->io_lock);
+	btfmslim->enabled = 0;
+	mutex_unlock(&btfmslim->io_lock);
+	return ret;
+}
+
+static int btfm_slim_get_dt_info(struct btfmslim *btfmslim)
+{
+	int ret = 0;
+	struct slim_device *slim = btfmslim->slim_pgd;
+	struct slim_device *slim_ifd = &btfmslim->slim_ifd;
+	struct property *prop;
+
+	if (!slim || !slim_ifd)
+		return -EINVAL;
+
+	if (slim->dev.of_node) {
+		BTFMSLIM_DBG("Platform data from device tree (%s)",
+			slim->name);
+		ret = of_property_read_string(slim->dev.of_node,
+			"qcom,btfm-slim-ifd", &slim_ifd->name);
+		if (ret) {
+			BTFMSLIM_ERR("Looking up %s property in node %s failed",
+				"qcom,btfm-slim-ifd",
+				 slim->dev.of_node->full_name);
+			return -ENODEV;
+		}
+		BTFMSLIM_DBG("qcom,btfm-slim-ifd (%s)", slim_ifd->name);
+
+		prop = of_find_property(slim->dev.of_node,
+				"qcom,btfm-slim-ifd-elemental-addr", NULL);
+		if (!prop) {
+			BTFMSLIM_ERR("Looking up %s property in node %s failed",
+				"qcom,btfm-slim-ifd-elemental-addr",
+				slim->dev.of_node->full_name);
+			return -ENODEV;
+		} else if (prop->length != 6) {
+			BTFMSLIM_ERR(
+				"invalid codec slim ifd addr. addr length= %d",
+				prop->length);
+			return -ENODEV;
+		}
+		memcpy(slim_ifd->e_addr, prop->value, 6);
+		BTFMSLIM_DBG(
+			"PGD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
+			slim->e_addr[0], slim->e_addr[1], slim->e_addr[2],
+			slim->e_addr[3], slim->e_addr[4], slim->e_addr[5]);
+		BTFMSLIM_DBG(
+			"IFD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
+			slim_ifd->e_addr[0], slim_ifd->e_addr[1],
+			slim_ifd->e_addr[2], slim_ifd->e_addr[3],
+			slim_ifd->e_addr[4], slim_ifd->e_addr[5]);
+	} else {
+		BTFMSLIM_ERR("Platform data is not valid");
+	}
+
+	return ret;
+}
+
+static int btfm_slim_probe(struct slim_device *slim)
+{
+	int ret = 0;
+	struct btfmslim *btfm_slim;
+
+	BTFMSLIM_DBG("");
+	if (!slim->ctrl)
+		return -EINVAL;
+
+	/* Allocation btfmslim data pointer */
+	btfm_slim = kzalloc(sizeof(struct btfmslim), GFP_KERNEL);
+	if (btfm_slim == NULL) {
+		BTFMSLIM_ERR("error, allocation failed");
+		return -ENOMEM;
+	}
+	/* BTFM Slimbus driver control data configuration */
+	btfm_slim->slim_pgd = slim;
+
+	/* Assign vendor specific function */
+	btfm_slim->rx_chs = SLIM_SLAVE_RXPORT;
+	btfm_slim->tx_chs = SLIM_SLAVE_TXPORT;
+	btfm_slim->vendor_init = SLIM_SLAVE_INIT;
+	btfm_slim->vendor_port_en = SLIM_SLAVE_PORT_EN;
+
+	/* Created Mutex for slimbus data transfer */
+	mutex_init(&btfm_slim->io_lock);
+	mutex_init(&btfm_slim->xfer_lock);
+
+	/* Get Device tree node for Interface Device enumeration address */
+	ret = btfm_slim_get_dt_info(btfm_slim);
+	if (ret)
+		goto dealloc;
+
+	/* Add Interface Device for slimbus driver */
+	ret = slim_add_device(btfm_slim->slim_pgd->ctrl, &btfm_slim->slim_ifd);
+	if (ret) {
+		BTFMSLIM_ERR("error, adding SLIMBUS device failed");
+		goto dealloc;
+	}
+
+	/* Platform driver data allocation */
+	slim->dev.platform_data = btfm_slim;
+
+	/* Driver specific data allocation */
+	btfm_slim->dev = &slim->dev;
+	ret = btfm_slim_register_codec(&slim->dev);
+	if (ret) {
+		BTFMSLIM_ERR("error, registering slimbus codec failed");
+		goto free;
+	}
+	ret = bt_register_slimdev(&slim->dev);
+	if (ret < 0) {
+		btfm_slim_unregister_codec(&slim->dev);
+		goto free;
+	}
+	return ret;
+free:
+	slim_remove_device(&btfm_slim->slim_ifd);
+dealloc:
+	mutex_destroy(&btfm_slim->io_lock);
+	mutex_destroy(&btfm_slim->xfer_lock);
+	kfree(btfm_slim);
+	return ret;
+}
+static int btfm_slim_remove(struct slim_device *slim)
+{
+	struct btfmslim *btfm_slim = slim->dev.platform_data;
+
+	BTFMSLIM_DBG("");
+	mutex_destroy(&btfm_slim->io_lock);
+	mutex_destroy(&btfm_slim->xfer_lock);
+	snd_soc_unregister_component(&slim->dev);
+
+	BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_ifd");
+	slim_remove_device(&btfm_slim->slim_ifd);
+
+	kfree(btfm_slim);
+
+	BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_pgd");
+	slim_remove_device(slim);
+	return 0;
+}
+
+static const struct slim_device_id btfm_slim_id[] = {
+	{SLIM_SLAVE_COMPATIBLE_STR, 0},
+	{}
+};
+
+static struct slim_driver btfm_slim_driver = {
+	.driver = {
+		.name = "btfmslim-driver",
+		.owner = THIS_MODULE,
+	},
+	.probe = btfm_slim_probe,
+	.remove = btfm_slim_remove,
+	.id_table = btfm_slim_id
+};
+
+static int __init btfm_slim_init(void)
+{
+	int ret;
+
+	BTFMSLIM_DBG("");
+	ret = slim_driver_register(&btfm_slim_driver);
+	if (ret)
+		BTFMSLIM_ERR("Failed to register slimbus driver: %d", ret);
+	return ret;
+}
+
+static void __exit btfm_slim_exit(void)
+{
+	BTFMSLIM_DBG("");
+	slim_driver_unregister(&btfm_slim_driver);
+}
+
+module_init(btfm_slim_init);
+module_exit(btfm_slim_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BTFM Slimbus Slave driver");
diff --git a/drivers/bluetooth/btfm_slim.h b/drivers/bluetooth/btfm_slim.h
new file mode 100644
index 0000000..bdd286c
--- /dev/null
+++ b/drivers/bluetooth/btfm_slim.h
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef BTFM_SLIM_H
+#define BTFM_SLIM_H
+#include <linux/slimbus/slimbus.h>
+
+#define BTFMSLIM_DBG(fmt, arg...)  pr_debug("%s: " fmt "\n", __func__, ## arg)
+#define BTFMSLIM_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg)
+#define BTFMSLIM_ERR(fmt, arg...)  pr_err("%s: " fmt "\n", __func__, ## arg)
+
+/* Vendor specific defines
+ * This should redefines in slimbus slave specific header
+ */
+#define SLIM_SLAVE_COMPATIBLE_STR	"btfmslim_slave"
+#define SLIM_SLAVE_REG_OFFSET		0x0000
+#define SLIM_SLAVE_RXPORT		NULL
+#define SLIM_SLAVE_TXPORT		NULL
+#define SLIM_SLAVE_INIT			NULL
+#define SLIM_SLAVE_PORT_EN		NULL
+
+/* Misc defines */
+#define SLIM_SLAVE_RW_MAX_TRIES		3
+#define SLIM_SLAVE_PRESENT_TIMEOUT	100
+
+#define PGD	1
+#define IFD	0
+
+
+/* Codec driver defines */
+enum {
+	BTFM_FM_SLIM_TX = 0,
+	BTFM_BT_SCO_SLIM_TX,
+	BTFM_BT_SCO_A2DP_SLIM_RX,
+	BTFM_BT_SPLIT_A2DP_SLIM_RX,
+	BTFM_SLIM_NUM_CODEC_DAIS
+};
+
+/* Slimbus Port defines - This should be redefined in specific device file */
+#define BTFM_SLIM_PGD_PORT_LAST				0xFF
+
+struct btfmslim_ch {
+	int id;
+	char *name;
+	uint32_t port_hdl;	/* slimbus port handler */
+	uint16_t port;		/* slimbus port number */
+
+	uint8_t ch;		/* slimbus channel number */
+	uint16_t ch_hdl;	/* slimbus channel handler */
+	uint16_t grph;	/* slimbus group channel handler */
+};
+
+struct btfmslim {
+	struct device *dev;
+	struct slim_device *slim_pgd;
+	struct slim_device slim_ifd;
+	struct mutex io_lock;
+	struct mutex xfer_lock;
+	uint8_t enabled;
+
+	uint32_t num_rx_port;
+	uint32_t num_tx_port;
+	uint32_t sample_rate;
+
+	struct btfmslim_ch *rx_chs;
+	struct btfmslim_ch *tx_chs;
+
+	int (*vendor_init)(struct btfmslim *btfmslim);
+	int (*vendor_port_en)(struct btfmslim *btfmslim, uint8_t port_num,
+		uint8_t rxport, uint8_t enable);
+};
+
+/**
+ * btfm_slim_hw_init: Initialize slimbus slave device
+ * Returns:
+ * 0: Success
+ * else: Fail
+ */
+int btfm_slim_hw_init(struct btfmslim *btfmslim);
+
+/**
+ * btfm_slim_hw_deinit: Deinitialize slimbus slave device
+ * Returns:
+ * 0: Success
+ * else: Fail
+ */
+int btfm_slim_hw_deinit(struct btfmslim *btfmslim);
+
+/**
+ * btfm_slim_write: write value to pgd or ifd device
+ * @btfmslim: slimbus slave device data pointer.
+ * @reg: slimbus slave register address
+ * @bytes: length of data
+ * @src: data pointer to write
+ * @pgd: selection for device: either PGD or IFD
+ * Returns:
+ * -EINVAL
+ * -ETIMEDOUT
+ * -ENOMEM
+ */
+int btfm_slim_write(struct btfmslim *btfmslim,
+	uint16_t reg, int bytes, void *src, uint8_t pgd);
+
+
+
+/**
+ * btfm_slim_read: read value from pgd or ifd device
+ * @btfmslim: slimbus slave device data pointer.
+ * @reg: slimbus slave register address
+ * @bytes: length of data
+ * @dest: data pointer to read
+ * @pgd: selection for device: either PGD or IFD
+ * Returns:
+ * -EINVAL
+ * -ETIMEDOUT
+ * -ENOMEM
+ */
+int btfm_slim_read(struct btfmslim *btfmslim,
+	uint16_t reg, int bytes, void *dest, uint8_t pgd);
+
+
+/**
+ * btfm_slim_enable_ch: enable channel for slimbus slave port
+ * @btfmslim: slimbus slave device data pointer.
+ * @ch: slimbus slave channel pointer
+ * @rxport: rxport or txport
+ * Returns:
+ * -EINVAL
+ * -ETIMEDOUT
+ * -ENOMEM
+ */
+int btfm_slim_enable_ch(struct btfmslim *btfmslim,
+	struct btfmslim_ch *ch, uint8_t rxport, uint32_t rates,
+	uint8_t grp, uint8_t nchan);
+
+/**
+ * btfm_slim_disable_ch: disable channel for slimbus slave port
+ * @btfmslim: slimbus slave device data pointer.
+ * @ch: slimbus slave channel pointer
+ * @rxport: rxport or txport
+ * Returns:
+ * -EINVAL
+ * -ETIMEDOUT
+ * -ENOMEM
+ */
+int btfm_slim_disable_ch(struct btfmslim *btfmslim,
+	struct btfmslim_ch *ch, uint8_t rxport, uint8_t grp, uint8_t nchan);
+
+/**
+ * btfm_slim_register_codec: Register codec driver in slimbus device node
+ * @dev: device node
+ * Returns:
+ * -ENOMEM
+ * 0
+ */
+int btfm_slim_register_codec(struct device *dev);
+
+/**
+ * btfm_slim_unregister_codec: Unregister codec driver in slimbus device node
+ * @dev: device node
+ * Returns:
+ * VOID
+ */
+void btfm_slim_unregister_codec(struct device *dev);
+#endif /* BTFM_SLIM_H */
diff --git a/drivers/bluetooth/btfm_slim_codec.c b/drivers/bluetooth/btfm_slim_codec.c
new file mode 100644
index 0000000..649df86
--- /dev/null
+++ b/drivers/bluetooth/btfm_slim_codec.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/debugfs.h>
+#include <linux/slimbus/slimbus.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <btfm_slim.h>
+
+static int bt_soc_enable_status;
+
+
+static int btfm_slim_codec_write(struct snd_soc_component *codec,
+			unsigned int reg, unsigned int value)
+{
+	return 0;
+}
+
+static unsigned int btfm_slim_codec_read(struct snd_soc_component *codec,
+				unsigned int reg)
+{
+	return 0;
+}
+
+static int bt_soc_status_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = bt_soc_enable_status;
+	return 1;
+}
+
+static int bt_soc_status_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	return 1;
+}
+
+static const struct snd_kcontrol_new status_controls[] = {
+	SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0,
+			bt_soc_status_get,
+			bt_soc_status_put)
+
+};
+
+
+static int btfm_slim_codec_probe(struct snd_soc_component *codec)
+{
+	snd_soc_add_component_controls(codec, status_controls,
+				   ARRAY_SIZE(status_controls));
+	return 0;
+}
+
+static void btfm_slim_codec_remove(struct snd_soc_component *codec)
+{
+
+}
+
+static int btfm_slim_dai_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	int ret;
+	struct btfmslim *btfmslim = dai->dev->platform_data;
+
+	BTFMSLIM_DBG("substream = %s  stream = %d dai->name = %s",
+		 substream->name, substream->stream, dai->name);
+	ret = btfm_slim_hw_init(btfmslim);
+	return ret;
+}
+
+static void btfm_slim_dai_shutdown(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	int i;
+	struct btfmslim *btfmslim = dai->dev->platform_data;
+	struct btfmslim_ch *ch;
+	uint8_t rxport, grp = false, nchan = 1;
+
+	BTFMSLIM_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
+		dai->id, dai->rate);
+
+	switch (dai->id) {
+	case BTFM_FM_SLIM_TX:
+		grp = true; nchan = 2;
+		ch = btfmslim->tx_chs;
+		rxport = 0;
+		break;
+	case BTFM_BT_SCO_SLIM_TX:
+		ch = btfmslim->tx_chs;
+		rxport = 0;
+		break;
+	case BTFM_BT_SCO_A2DP_SLIM_RX:
+	case BTFM_BT_SPLIT_A2DP_SLIM_RX:
+		ch = btfmslim->rx_chs;
+		rxport = 1;
+		break;
+	case BTFM_SLIM_NUM_CODEC_DAIS:
+	default:
+		BTFMSLIM_ERR("dai->id is invalid:%d", dai->id);
+		return;
+	}
+
+	/* Search for dai->id matched port handler */
+	for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
+		(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
+		(ch->id != dai->id); ch++, i++)
+		;
+
+	if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
+		(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
+		BTFMSLIM_ERR("ch is invalid!!");
+		return;
+	}
+
+	btfm_slim_disable_ch(btfmslim, ch, rxport, grp, nchan);
+	btfm_slim_hw_deinit(btfmslim);
+}
+
+static int btfm_slim_dai_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	BTFMSLIM_DBG("dai->name = %s DAI-ID %x rate %d num_ch %d",
+		dai->name, dai->id, params_rate(params),
+		params_channels(params));
+
+	return 0;
+}
+
+static int btfm_slim_dai_prepare(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	int i, ret = -EINVAL;
+	struct btfmslim *btfmslim = dai->dev->platform_data;
+	struct btfmslim_ch *ch;
+	uint8_t rxport, grp = false, nchan = 1;
+
+	bt_soc_enable_status = 0;
+
+	BTFMSLIM_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
+		dai->id, dai->rate);
+
+	/* save sample rate */
+	btfmslim->sample_rate = dai->rate;
+
+	switch (dai->id) {
+	case BTFM_FM_SLIM_TX:
+		grp = true; nchan = 2;
+		ch = btfmslim->tx_chs;
+		rxport = 0;
+		break;
+	case BTFM_BT_SCO_SLIM_TX:
+		ch = btfmslim->tx_chs;
+		rxport = 0;
+		break;
+	case BTFM_BT_SCO_A2DP_SLIM_RX:
+	case BTFM_BT_SPLIT_A2DP_SLIM_RX:
+		ch = btfmslim->rx_chs;
+		rxport = 1;
+		break;
+	case BTFM_SLIM_NUM_CODEC_DAIS:
+	default:
+		BTFMSLIM_ERR("dai->id is invalid:%d", dai->id);
+		return ret;
+	}
+
+	/* Search for dai->id matched port handler */
+	for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
+		(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
+		(ch->id != dai->id); ch++, i++)
+		;
+
+	if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
+		(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
+		BTFMSLIM_ERR("ch is invalid!!");
+		return ret;
+	}
+
+	ret = btfm_slim_enable_ch(btfmslim, ch, rxport, dai->rate, grp, nchan);
+
+	/* save the enable channel status */
+	if (ret == 0)
+		bt_soc_enable_status = 1;
+	return ret;
+}
+
+/* This function will be called once during boot up */
+static int btfm_slim_dai_set_channel_map(struct snd_soc_dai *dai,
+				unsigned int tx_num, unsigned int *tx_slot,
+				unsigned int rx_num, unsigned int *rx_slot)
+{
+	int ret = -EINVAL, i;
+	struct btfmslim *btfmslim = dai->dev->platform_data;
+	struct btfmslim_ch *rx_chs;
+	struct btfmslim_ch *tx_chs;
+
+	BTFMSLIM_DBG("");
+
+	if (!btfmslim)
+		return ret;
+
+	rx_chs = btfmslim->rx_chs;
+	tx_chs = btfmslim->tx_chs;
+
+	if (!rx_chs || !tx_chs)
+		return ret;
+
+	BTFMSLIM_DBG("Rx: id\tname\tport\thdl\tch\tch_hdl");
+	for (i = 0; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < rx_num);
+		i++, rx_chs++) {
+		/* Set Rx Channel number from machine driver and
+		 * get channel handler from slimbus driver
+		 */
+		rx_chs->ch = *(uint8_t *)(rx_slot + i);
+		ret = slim_query_ch(btfmslim->slim_pgd, rx_chs->ch,
+			&rx_chs->ch_hdl);
+		if (ret < 0) {
+			BTFMSLIM_ERR("slim_query_ch failure ch#%d - ret[%d]",
+				rx_chs->ch, ret);
+			goto error;
+		}
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", rx_chs->id,
+			rx_chs->name, rx_chs->port, rx_chs->port_hdl,
+			rx_chs->ch, rx_chs->ch_hdl);
+	}
+
+	BTFMSLIM_DBG("Tx: id\tname\tport\thdl\tch\tch_hdl");
+	for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num);
+		i++, tx_chs++) {
+		/* Set Tx Channel number from machine driver and
+		 * get channel handler from slimbus driver
+		 */
+		tx_chs->ch = *(uint8_t *)(tx_slot + i);
+		ret = slim_query_ch(btfmslim->slim_pgd, tx_chs->ch,
+			&tx_chs->ch_hdl);
+		if (ret < 0) {
+			BTFMSLIM_ERR("slim_query_ch failure ch#%d - ret[%d]",
+				tx_chs->ch, ret);
+			goto error;
+		}
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id,
+			tx_chs->name, tx_chs->port, tx_chs->port_hdl,
+			tx_chs->ch, tx_chs->ch_hdl);
+	}
+
+error:
+	return ret;
+}
+
+static int btfm_slim_dai_get_channel_map(struct snd_soc_dai *dai,
+				 unsigned int *tx_num, unsigned int *tx_slot,
+				 unsigned int *rx_num, unsigned int *rx_slot)
+{
+	int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1;
+	struct btfmslim *btfmslim = dai->dev->platform_data;
+	struct btfmslim_ch *ch = NULL;
+
+	if (!btfmslim)
+		return ret;
+
+	switch (dai->id) {
+	case BTFM_FM_SLIM_TX:
+		num = 2;
+	case BTFM_BT_SCO_SLIM_TX:
+		if (!tx_slot || !tx_num) {
+			BTFMSLIM_ERR("Invalid tx_slot %p or tx_num %p",
+				tx_slot, tx_num);
+			return -EINVAL;
+		}
+		ch = btfmslim->tx_chs;
+		if (!ch)
+			return -EINVAL;
+		slot = tx_slot;
+		*rx_slot = 0;
+		*tx_num = num;
+		*rx_num = 0;
+		break;
+	case BTFM_BT_SCO_A2DP_SLIM_RX:
+	case BTFM_BT_SPLIT_A2DP_SLIM_RX:
+		if (!rx_slot || !rx_num) {
+			BTFMSLIM_ERR("Invalid rx_slot %p or rx_num %p",
+				 rx_slot, rx_num);
+			return -EINVAL;
+		}
+		ch = btfmslim->rx_chs;
+		if (!ch)
+			return -EINVAL;
+		slot = rx_slot;
+		*tx_slot = 0;
+		*tx_num = 0;
+		*rx_num = num;
+		break;
+	default:
+		BTFMSLIM_ERR("Unsupported DAI %d", dai->id);
+		return -EINVAL;
+	}
+
+	do {
+		if (!ch)
+			return -EINVAL;
+		for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id !=
+			BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != dai->id);
+			ch++, i++)
+			;
+
+		if (ch->id == BTFM_SLIM_NUM_CODEC_DAIS ||
+			i == BTFM_SLIM_NUM_CODEC_DAIS) {
+			BTFMSLIM_ERR(
+				"No channel has been allocated for dai (%d)",
+				dai->id);
+			return -EINVAL;
+		}
+		if (!slot)
+			return -EINVAL;
+		*(slot + j) = ch->ch;
+		BTFMSLIM_DBG("id:%d, port:%d, ch:%d, slot: %d", ch->id,
+			ch->port, ch->ch, *(slot + j));
+
+		/* In case it has mulitiple channels */
+		if (++j < num)
+			ch++;
+	} while (j < num);
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops btfmslim_dai_ops = {
+	.startup = btfm_slim_dai_startup,
+	.shutdown = btfm_slim_dai_shutdown,
+	.hw_params = btfm_slim_dai_hw_params,
+	.prepare = btfm_slim_dai_prepare,
+	.set_channel_map = btfm_slim_dai_set_channel_map,
+	.get_channel_map = btfm_slim_dai_get_channel_map,
+};
+
+static struct snd_soc_dai_driver btfmslim_dai[] = {
+	{	/* FM Audio data multiple channel  : FM -> qdsp */
+		.name = "btfm_fm_slim_tx",
+		.id = BTFM_FM_SLIM_TX,
+		.capture = {
+			.stream_name = "FM TX Capture",
+			.rates = SNDRV_PCM_RATE_48000, /* 48 KHz */
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 48000,
+			.rate_min = 48000,
+			.channels_min = 1,
+			.channels_max = 2,
+		},
+		.ops = &btfmslim_dai_ops,
+	},
+	{	/* Bluetooth SCO voice uplink: bt -> modem */
+		.name = "btfm_bt_sco_slim_tx",
+		.id = BTFM_BT_SCO_SLIM_TX,
+		.capture = {
+			.stream_name = "SCO TX Capture",
+			/* 8 KHz or 16 KHz */
+			.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 16000,
+			.rate_min = 8000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmslim_dai_ops,
+	},
+	{	/* Bluetooth SCO voice downlink: modem -> bt or A2DP Playback */
+		.name = "btfm_bt_sco_a2dp_slim_rx",
+		.id = BTFM_BT_SCO_A2DP_SLIM_RX,
+		.playback = {
+			.stream_name = "SCO A2DP RX Playback",
+			/* 8/16/44.1/48/88.2/96 Khz */
+			.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
+				| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
+				| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 96000,
+			.rate_min = 8000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmslim_dai_ops,
+	},
+	{	/* Bluetooth Split A2DP data: qdsp -> bt */
+		.name = "btfm_bt_split_a2dp_slim_rx",
+		.id = BTFM_BT_SPLIT_A2DP_SLIM_RX,
+		.playback = {
+			.stream_name = "SPLIT A2DP Playback",
+			.rates = SNDRV_PCM_RATE_48000, /* 48 KHz */
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 48000,
+			.rate_min = 48000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmslim_dai_ops,
+	},
+};
+
+static const struct snd_soc_component_driver btfmslim_codec = {
+	.probe	= btfm_slim_codec_probe,
+	.remove	= btfm_slim_codec_remove,
+	.read	= btfm_slim_codec_read,
+	.write	= btfm_slim_codec_write,
+};
+
+int btfm_slim_register_codec(struct device *dev)
+{
+	int ret = 0;
+
+	BTFMSLIM_DBG("");
+	/* Register Codec driver */
+	ret = snd_soc_register_component(dev, &btfmslim_codec,
+		btfmslim_dai, ARRAY_SIZE(btfmslim_dai));
+
+	if (ret)
+		BTFMSLIM_ERR("failed to register codec (%d)", ret);
+
+	return ret;
+}
+
+void btfm_slim_unregister_codec(struct device *dev)
+{
+	BTFMSLIM_DBG("");
+	/* Unregister Codec driver */
+	snd_soc_unregister_component(dev);
+}
+
+MODULE_DESCRIPTION("BTFM Slimbus Codec driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/bluetooth/btfm_slim_slave.c b/drivers/bluetooth/btfm_slim_slave.c
new file mode 100644
index 0000000..d0e19ca
--- /dev/null
+++ b/drivers/bluetooth/btfm_slim_slave.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/slimbus/slimbus.h>
+#include <btfm_slim.h>
+#include <btfm_slim_slave.h>
+
+/* SLAVE (WCN3990/QCA6390) Port assignment */
+struct btfmslim_ch slave_rxport[] = {
+	{.id = BTFM_BT_SCO_A2DP_SLIM_RX, .name = "SCO_A2P_Rx",
+	.port = SLAVE_SB_PGD_PORT_RX_SCO},
+	{.id = BTFM_BT_SPLIT_A2DP_SLIM_RX, .name = "A2P_Rx",
+	.port = SLAVE_SB_PGD_PORT_RX_A2P},
+	{.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "",
+	.port = BTFM_SLIM_PGD_PORT_LAST},
+};
+
+struct btfmslim_ch slave_txport[] = {
+	{.id = BTFM_FM_SLIM_TX, .name = "FM_Tx1",
+	.port = SLAVE_SB_PGD_PORT_TX1_FM},
+	{.id = BTFM_FM_SLIM_TX, .name = "FM_Tx2",
+	.port = SLAVE_SB_PGD_PORT_TX2_FM},
+	{.id = BTFM_BT_SCO_SLIM_TX, .name = "SCO_Tx",
+	.port = SLAVE_SB_PGD_PORT_TX_SCO},
+	{.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "",
+	.port = BTFM_SLIM_PGD_PORT_LAST},
+};
+
+/* Function description */
+int btfm_slim_slave_hw_init(struct btfmslim *btfmslim)
+{
+	int ret = 0;
+	uint8_t reg_val;
+	uint16_t reg;
+
+	BTFMSLIM_DBG("");
+
+	if (!btfmslim)
+		return -EINVAL;
+
+	/* Get SB_SLAVE_HW_REV_MSB value*/
+	reg = SLAVE_SB_SLAVE_HW_REV_MSB;
+	ret = btfm_slim_read(btfmslim, reg,  1, &reg_val, IFD);
+	if (ret) {
+		BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
+		goto error;
+	}
+	BTFMSLIM_DBG("Major Rev: 0x%x, Minor Rev: 0x%x",
+		(reg_val & 0xF0) >> 4, (reg_val & 0x0F));
+
+	/* Get SB_SLAVE_HW_REV_LSB value*/
+	reg = SLAVE_SB_SLAVE_HW_REV_LSB;
+	ret = btfm_slim_read(btfmslim, reg,  1, &reg_val, IFD);
+	if (ret) {
+		BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
+		goto error;
+	}
+	BTFMSLIM_DBG("Step Rev: 0x%x", reg_val);
+
+error:
+	return ret;
+}
+
+static inline int is_fm_port(uint8_t port_num)
+{
+	if (port_num == SLAVE_SB_PGD_PORT_TX1_FM ||
+		port_num == SLAVE_SB_PGD_PORT_TX2_FM)
+		return 1;
+	else
+		return 0;
+}
+
+int btfm_slim_slave_enable_port(struct btfmslim *btfmslim, uint8_t port_num,
+	uint8_t rxport, uint8_t enable)
+{
+	int ret = 0;
+	uint8_t reg_val = 0, en;
+	uint8_t rxport_num = 0;
+	uint16_t reg;
+
+	BTFMSLIM_DBG("port(%d) enable(%d)", port_num, enable);
+	if (rxport) {
+		BTFMSLIM_DBG("sample rate is %d", btfmslim->sample_rate);
+		if (enable &&
+			btfmslim->sample_rate != 44100 &&
+			btfmslim->sample_rate != 88200) {
+			BTFMSLIM_DBG("setting multichannel bit");
+			/* For SCO Rx, A2DP Rx other than 44.1 and 88.2Khz */
+			if (port_num < 24) {
+				rxport_num = port_num - 16;
+				reg_val = 0x01 << rxport_num;
+				reg = SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_0(
+					rxport_num);
+			} else {
+				rxport_num = port_num - 24;
+				reg_val = 0x01 << rxport_num;
+				reg = SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_1(
+					rxport_num);
+			}
+
+			BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
+				reg_val, reg);
+			ret = btfm_slim_write(btfmslim, reg, 1, &reg_val, IFD);
+			if (ret) {
+				BTFMSLIM_ERR("failed to write (%d) reg 0x%x",
+					ret, reg);
+				goto error;
+			}
+		}
+		/* Port enable */
+		reg = SLAVE_SB_PGD_PORT_RX_CFGN(port_num - 0x10);
+		goto enable_disable_rxport;
+	}
+	if (!enable)
+		goto enable_disable_txport;
+
+	/* txport */
+	/* Multiple Channel Setting */
+	if (is_fm_port(port_num)) {
+		reg_val = (0x1 << SLAVE_SB_PGD_PORT_TX1_FM) |
+				(0x1 << SLAVE_SB_PGD_PORT_TX2_FM);
+		reg = SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num);
+		ret = btfm_slim_write(btfmslim, reg, 1, &reg_val, IFD);
+		if (ret) {
+			BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
+			goto error;
+		}
+	} else if (port_num == SLAVE_SB_PGD_PORT_TX_SCO) {
+		/* SCO Tx */
+		reg_val = 0x1 << SLAVE_SB_PGD_PORT_TX_SCO;
+		reg = SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num);
+		BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
+				reg_val, reg);
+		ret = btfm_slim_write(btfmslim, reg, 1, &reg_val, IFD);
+		if (ret) {
+			BTFMSLIM_ERR("failed to write (%d) reg 0x%x",
+					ret, reg);
+			goto error;
+		}
+	}
+
+	/* Enable Tx port hw auto recovery for underrun or overrun error */
+	reg_val = (SLAVE_ENABLE_OVERRUN_AUTO_RECOVERY |
+				SLAVE_ENABLE_UNDERRUN_AUTO_RECOVERY);
+	reg = SLAVE_SB_PGD_PORT_TX_OR_UR_CFGN(port_num);
+	ret = btfm_slim_write(btfmslim, reg, 1, &reg_val, IFD);
+	if (ret) {
+		BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
+		goto error;
+	}
+
+enable_disable_txport:
+	/* Port enable */
+	reg = SLAVE_SB_PGD_PORT_TX_CFGN(port_num);
+
+enable_disable_rxport:
+	if (enable)
+		en = SLAVE_SB_PGD_PORT_ENABLE;
+	else
+		en = SLAVE_SB_PGD_PORT_DISABLE;
+
+	if (is_fm_port(port_num))
+		reg_val = en | SLAVE_SB_PGD_PORT_WM_L8;
+	else if (port_num == SLAVE_SB_PGD_PORT_TX_SCO)
+		reg_val = enable ? en | SLAVE_SB_PGD_PORT_WM_L1 : en;
+	else
+		reg_val = enable ? en | SLAVE_SB_PGD_PORT_WM_LB : en;
+
+	if (enable && port_num == SLAVE_SB_PGD_PORT_TX_SCO)
+		BTFMSLIM_INFO("programming SCO Tx with reg_val %d to reg 0x%x",
+				reg_val, reg);
+
+	ret = btfm_slim_write(btfmslim, reg, 1, &reg_val, IFD);
+	if (ret)
+		BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
+
+error:
+	return ret;
+}
diff --git a/drivers/bluetooth/btfm_slim_slave.h b/drivers/bluetooth/btfm_slim_slave.h
new file mode 100644
index 0000000..20857ad
--- /dev/null
+++ b/drivers/bluetooth/btfm_slim_slave.h
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef BTFM_SLIM_SLAVE_H
+#define BTFM_SLIM_SLAVE_H
+#include <btfm_slim.h>
+
+/* Registers Address */
+#define SLAVE_SB_COMP_TEST			0x00000000
+#define SLAVE_SB_SLAVE_HW_REV_MSB		0x00000001
+#define SLAVE_SB_SLAVE_HW_REV_LSB		0x00000002
+#define SLAVE_SB_DEBUG_FEATURES			0x00000005
+#define SLAVE_SB_INTF_INT_EN			0x00000010
+#define SLAVE_SB_INTF_INT_STATUS			0x00000011
+#define SLAVE_SB_INTF_INT_CLR			0x00000012
+#define SLAVE_SB_FRM_CFG				0x00000013
+#define SLAVE_SB_FRM_STATUS			0x00000014
+#define SLAVE_SB_FRM_INT_EN			0x00000015
+#define SLAVE_SB_FRM_INT_STATUS			0x00000016
+#define SLAVE_SB_FRM_INT_CLR			0x00000017
+#define SLAVE_SB_FRM_WAKEUP			0x00000018
+#define SLAVE_SB_FRM_CLKCTL_DONE			0x00000019
+#define SLAVE_SB_FRM_IE_STATUS			0x0000001A
+#define SLAVE_SB_FRM_VE_STATUS			0x0000001B
+#define SLAVE_SB_PGD_TX_CFG_STATUS		0x00000020
+#define SLAVE_SB_PGD_RX_CFG_STATUS		0x00000021
+#define SLAVE_SB_PGD_DEV_INT_EN			0x00000022
+#define SLAVE_SB_PGD_DEV_INT_STATUS		0x00000023
+#define SLAVE_SB_PGD_DEV_INT_CLR			0x00000024
+#define SLAVE_SB_PGD_PORT_INT_EN_RX_0		0x00000030
+#define SLAVE_SB_PGD_PORT_INT_EN_RX_1		0x00000031
+#define SLAVE_SB_PGD_PORT_INT_EN_TX_0		0x00000032
+#define SLAVE_SB_PGD_PORT_INT_EN_TX_1		0x00000033
+#define SLAVE_SB_PGD_PORT_INT_STATUS_RX_0	0x00000034
+#define SLAVE_SB_PGD_PORT_INT_STATUS_RX_1	0x00000035
+#define SLAVE_SB_PGD_PORT_INT_STATUS_TX_0	0x00000036
+#define SLAVE_SB_PGD_PORT_INT_STATUS_TX_1	0x00000037
+#define SLAVE_SB_PGD_PORT_INT_CLR_RX_0		0x00000038
+#define SLAVE_SB_PGD_PORT_INT_CLR_RX_1		0x00000039
+#define SLAVE_SB_PGD_PORT_INT_CLR_TX_0		0x0000003A
+#define SLAVE_SB_PGD_PORT_INT_CLR_TX_1		0x0000003B
+#define SLAVE_SB_PGD_PORT_RX_CFGN(n)		(0x00000040 + n)
+#define SLAVE_SB_PGD_PORT_TX_CFGN(n)		(0x00000050 + n)
+#define SLAVE_SB_PGD_PORT_INT_RX_SOURCEN(n)	(0x00000060 + n)
+#define SLAVE_SB_PGD_PORT_INT_TX_SOURCEN(n)	(0x00000070 + n)
+#define SLAVE_SB_PGD_PORT_RX_STATUSN(n)		(0x00000080 + n)
+#define SLAVE_SB_PGD_PORT_TX_STATUSN(n)		(0x00000090 + n)
+#define SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(n)	(0x00000100 + 0x4*n)
+#define SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_1(n)	(0x00000101 + 0x4*n)
+#define SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_0(n)	(0x00000180 + 0x4*n)
+#define SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_1(n)	(0x00000181 + 0x4*n)
+#define SLAVE_SB_PGD_PORT_TX_OR_UR_CFGN(n)	(0x000001F0 + n)
+
+/* Register Bit Setting */
+#define SLAVE_ENABLE_OVERRUN_AUTO_RECOVERY	(0x1 << 1)
+#define SLAVE_ENABLE_UNDERRUN_AUTO_RECOVERY	(0x1 << 0)
+#define SLAVE_SB_PGD_PORT_ENABLE			(0x1 << 0)
+#define SLAVE_SB_PGD_PORT_DISABLE		(0x0 << 0)
+#define SLAVE_SB_PGD_PORT_WM_L1			(0x1 << 1)
+#define SLAVE_SB_PGD_PORT_WM_L2			(0x2 << 1)
+#define SLAVE_SB_PGD_PORT_WM_L3			(0x3 << 1)
+#define SLAVE_SB_PGD_PORT_WM_L8			(0x8 << 1)
+#define SLAVE_SB_PGD_PORT_WM_LB			(0xB << 1)
+
+#define SLAVE_SB_PGD_PORT_RX_NUM			16
+#define SLAVE_SB_PGD_PORT_TX_NUM			16
+
+/* PGD Port Map */
+#define SLAVE_SB_PGD_PORT_TX_SCO			0
+#define SLAVE_SB_PGD_PORT_TX1_FM			1
+#define SLAVE_SB_PGD_PORT_TX2_FM			2
+#define SLAVE_SB_PGD_PORT_RX_SCO			16
+#define SLAVE_SB_PGD_PORT_RX_A2P			17
+
+
+/* Function Prototype */
+
+/*
+ * btfm_slim_slave_hw_init: Initialize slave specific slimbus slave device
+ * @btfmslim: slimbus slave device data pointer.
+ * Returns:
+ * 0: Success
+ * else: Fail
+ */
+int btfm_slim_slave_hw_init(struct btfmslim *btfmslim);
+
+/*
+ * btfm_slim_slave_enable_rxport: Enable slave Rx port by given port number
+ * @btfmslim: slimbus slave device data pointer.
+ * @portNum: slimbus slave port number to enable
+ * @rxport: rxport or txport
+ * @enable: enable port or disable port
+ * Returns:
+ * 0: Success
+ * else: Fail
+ */
+int btfm_slim_slave_enable_port(struct btfmslim *btfmslim, uint8_t portNum,
+	uint8_t rxport, uint8_t enable);
+
+/* Specific defines for slave slimbus device */
+#define SLAVE_SLIM_REG_OFFSET		0x0800
+
+#ifdef SLIM_SLAVE_REG_OFFSET
+#undef SLIM_SLAVE_REG_OFFSET
+#define SLIM_SLAVE_REG_OFFSET		SLAVE_SLIM_REG_OFFSET
+#endif
+
+/* Assign vendor specific function */
+extern struct btfmslim_ch slave_txport[];
+extern struct btfmslim_ch slave_rxport[];
+
+#ifdef SLIM_SLAVE_RXPORT
+#undef SLIM_SLAVE_RXPORT
+#define SLIM_SLAVE_RXPORT (&slave_rxport[0])
+#endif
+
+#ifdef SLIM_SLAVE_TXPORT
+#undef SLIM_SLAVE_TXPORT
+#define SLIM_SLAVE_TXPORT (&slave_txport[0])
+#endif
+
+#ifdef SLIM_SLAVE_INIT
+#undef SLIM_SLAVE_INIT
+#define SLIM_SLAVE_INIT btfm_slim_slave_hw_init
+#endif
+
+#ifdef SLIM_SLAVE_PORT_EN
+#undef SLIM_SLAVE_PORT_EN
+#define SLIM_SLAVE_PORT_EN btfm_slim_slave_enable_port
+#endif
+#endif