Browse Source

Merge "Add support for BT/FM SoundWire slave driver" into bt-kernel.lnx.14.0

CNSS_WLAN Service 1 year ago
parent
commit
77e8b20e22

+ 12 - 0
soundwire/Kconfig

@@ -0,0 +1,12 @@
+
+config BTFM_SWR
+	tristate "MSM Bluetooth/FM SoundWire Device"
+	depends on MSM_BT_POWER
+	help
+		This enables BT/FM soundwire driver to open/close ports.
+		This will make use of soundwire bus driver and soundwire
+		master driver to communicate with soundwire slave.
+
+		Say Y here to compile support for Bluetooth/FM soundwire slave driver
+		into the kernel or say M to compile as a module.
+

+ 3 - 0
soundwire/Makefile

@@ -0,0 +1,3 @@
+ccflags-y += -I$(BT_ROOT)/include
+bt_fm_swr-objs := btfm_swr.o btfm_swr_codec.o btfm_swr_slave.o
+obj-$(CONFIG_BTFM_SWR) += bt_fm_swr.o

+ 288 - 0
soundwire/btfm_swr.c

@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. 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 <linux/fs.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include "btpower.h"
+#include "btfm_swr.h"
+#include "btfm_swr_slave.h"
+
+struct class *btfm_swr_class;
+static int btfm_swr_major;
+struct btfmswr *pbtfmswr;
+static int btfm_num_ports_open;
+
+#define BT_CMD_SWR_TEST	0xbfac
+
+static int btfm_swr_probe(struct swr_device *pdev);
+
+int btfm_get_bt_soc_index(int chipset_ver)
+{
+	switch (chipset_ver) {
+	case QCA_GANGES_SOC_ID_0100:
+	case QCA_GANGES_SOC_ID_0200:
+		return GANGES;
+	case QCA_EVROS_SOC_ID_0100:
+	case QCA_EVROS_SOC_ID_0200:
+		return EVROS;
+	default:
+		BTFMSWR_ERR("no BT SOC id defined, returning EVROS");
+		return EVROS;
+	}
+}
+
+int btfm_swr_hw_init(void)
+{
+	uint8_t dev_num = 0;
+	int ret = 0;
+	int chipset_ver;
+
+	BTFMSWR_DBG("");
+
+	if (pbtfmswr->initialized)
+		BTFMSWR_INFO("Already initialized");
+
+	// get BT chipset version
+	chipset_ver = btpower_get_chipset_version();
+
+	// get BT/FM SOC slave port details
+	pbtfmswr->soc_index = btfm_get_bt_soc_index(chipset_ver);
+
+	BTFMSWR_INFO("chipset soc version:%x, soc index: %x", chipset_ver,
+							pbtfmswr->soc_index);
+
+	pbtfmswr->p_dai_port = &slave_port[pbtfmswr->soc_index];
+
+	// get logical address
+	/*
+	 * Add 5msec delay to provide sufficient time for
+	 * soundwire auto enumeration of slave devices as
+	 * per HW requirement.
+	 */
+	usleep_range(5000, 5010);
+	ret = swr_get_logical_dev_num(pbtfmswr->swr_slave, pbtfmswr->p_dai_port->ea,
+									&dev_num);
+	if (ret) {
+		BTFMSWR_ERR("error while getting logical device number");
+		goto err;
+	}
+
+	pbtfmswr->swr_slave->dev_num = dev_num;
+	pbtfmswr->initialized = true;
+
+err:
+	return ret;
+}
+
+int btfm_swr_enable_port(u8 port_num, u8 ch_count, u32 sample_rate, u8 usecase)
+{
+	int ret = 0;
+	u8 port_id[MAX_BT_PORTS];
+	u8 num_ch[MAX_BT_PORTS];
+	u8 ch_mask[MAX_BT_PORTS];
+	u32 ch_rate[MAX_BT_PORTS];
+	u8 port_type[MAX_BT_PORTS];
+	u8 num_port = 1;
+
+	// master expects port num -1 to be sent
+	port_id[0] = port_num-1;
+	num_ch[0] = ch_count;
+	ch_mask[0] = ch_count == 2 ? TWO_CHANNEL_MASK :	ONE_CHANNEL_MASK;
+	ch_rate[0] = sample_rate;
+	port_type[0] = usecase;
+
+	BTFMSWR_INFO("enabling port : %d\n", port_num);
+	ret = swr_connect_port(pbtfmswr->swr_slave, &port_id[0], num_port,
+							&ch_mask[0], &ch_rate[0], &num_ch[0],
+							&port_type[0]);
+
+	if (ret < 0) {
+		BTFMSWR_ERR("swr_connect_port failed, error %d", ret);
+		return ret;
+	}
+
+	BTFMSWR_INFO("calling swr_slvdev_datapath_control\n");
+	ret = swr_slvdev_datapath_control(pbtfmswr->swr_slave,
+							pbtfmswr->swr_slave->dev_num,
+							true);
+	if (ret < 0)
+		BTFMSWR_ERR("swr_slvdev_datapath_control failed");
+
+	if (ret == 0)
+		btfm_num_ports_open++;
+
+	BTFMSWR_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
+
+	return ret;
+}
+
+int btfm_swr_disable_port(u8 port_num, u8 ch_count, u8 usecase)
+{
+	int ret = 0;
+	u8 port_id[MAX_BT_PORTS];
+	u8 ch_mask[MAX_BT_PORTS];
+	u8 port_type[MAX_BT_PORTS];
+	u8 num_port = 1;
+
+	// master expects port num -1 to be sent
+	port_id[0] = port_num-1;
+	ch_mask[0] = ch_count == 2 ? TWO_CHANNEL_MASK :	ONE_CHANNEL_MASK;
+	port_type[0] = usecase;
+
+	BTFMSWR_INFO("disabling port : %d\n", port_num);
+	ret = swr_disconnect_port(pbtfmswr->swr_slave, &port_id[0], num_port,
+							&ch_mask[0], &port_type[0]);
+
+	if (ret < 0)
+		BTFMSWR_ERR("swr_disconnect_port port failed, error %d", ret);
+
+	BTFMSWR_INFO("calling swr_slvdev_datapath_control\n");
+	ret = swr_slvdev_datapath_control(pbtfmswr->swr_slave,
+							pbtfmswr->swr_slave->dev_num,
+							false);
+	if (ret < 0)
+		BTFMSWR_ERR("swr_slvdev_datapath_control failed");
+
+	if (btfm_num_ports_open > 0)
+		btfm_num_ports_open--;
+
+	BTFMSWR_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
+
+	return ret;
+}
+
+static long btfm_swr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+
+	BTFMSWR_INFO("");
+	switch (cmd) {
+	case BT_CMD_SWR_TEST:
+		BTFMSWR_INFO("cmd BT_CMD_SLIM_TEST, call btfm_swr_hw_init");
+		ret = btfm_swr_hw_init();
+		break;
+	}
+	return ret;
+}
+
+static const struct file_operations bt_dev_fops = {
+	.unlocked_ioctl = btfm_swr_ioctl,
+	.compat_ioctl = btfm_swr_ioctl,
+};
+
+static int btfm_swr_probe(struct swr_device *pdev)
+{
+	int ret = 0;
+
+	BTFMSWR_INFO("");
+
+	pbtfmswr = devm_kzalloc(&pdev->dev,
+					sizeof(struct btfmswr), GFP_KERNEL);
+	if (!pbtfmswr) {
+		BTFMSWR_ERR("memory allocation to driver failed");
+		return -ENOMEM;
+	}
+
+	swr_set_dev_data(pdev, pbtfmswr);
+	pbtfmswr->swr_slave = pdev;
+	pbtfmswr->dev = &pdev->dev;
+	pbtfmswr->initialized = false;
+
+	// register with ALSA
+	ret = btfm_swr_register_codec(pbtfmswr);
+	if (ret) {
+		BTFMSWR_ERR("registration with ALSA failed, returning");
+		goto dealloc;
+	}
+
+	btfm_swr_major = register_chrdev(0, "btfm_swr", &bt_dev_fops);
+	if (btfm_swr_major < 0) {
+		BTFMSWR_ERR("%s: failed to allocate char dev\n", __func__);
+		ret = -1;
+		goto register_err;
+	}
+
+	btfm_swr_class = class_create(THIS_MODULE, "btfmswr-dev");
+	if (IS_ERR(btfm_swr_class)) {
+		BTFMSWR_ERR("%s: coudn't create class\n", __func__);
+		ret = -1;
+		goto class_err;
+	}
+
+	if (device_create(btfm_swr_class, NULL, MKDEV(btfm_swr_major, 0),
+		NULL, "btfmswr") == NULL) {
+		BTFMSWR_ERR("%s: failed to allocate char dev\n", __func__);
+		ret = -1;
+		goto device_err;
+	}
+	return ret;
+
+device_err:
+	class_destroy(btfm_swr_class);
+class_err:
+	unregister_chrdev(btfm_swr_major, "btfm_swr");
+
+register_err:
+	btfm_swr_unregister_codec(pbtfmswr->dev);
+
+dealloc:
+	kfree(pbtfmswr);
+	return ret;
+}
+
+static const struct swr_device_id btfm_swr_id[] = {
+	{SWR_SLAVE_COMPATIBLE_STR, 0},
+	{}
+};
+
+static const struct of_device_id btfm_swr_dt_match[] = {
+	{
+		.compatible = "qcom,btfmswr_slave",
+	},
+	{}
+};
+
+static struct swr_driver btfm_swr_driver = {
+	.driver = {
+		.name = "btfmswr-driver",
+		.owner = THIS_MODULE,
+		.of_match_table = btfm_swr_dt_match,
+	},
+	.probe = btfm_swr_probe,
+	.id_table = btfm_swr_id,
+};
+
+static int __init btfm_swr_init(void)
+{
+	BTFMSWR_INFO("");
+	return swr_driver_register(&btfm_swr_driver);
+}
+
+static void __exit btfm_swr_exit(void)
+{
+	BTFMSWR_INFO("");
+	swr_driver_unregister(&btfm_swr_driver);
+}
+
+module_init(btfm_swr_init);
+module_exit(btfm_swr_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BTFM SoundWire Slave driver");
+

+ 103 - 0
soundwire/btfm_swr.h

@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+
+#ifndef BTFM_SWR_H
+#define BTFM_SWR_H
+
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <bindings/audio-codec-port-types.h>
+#include "soc/soundwire.h"
+
+#define SWR_SLAVE_COMPATIBLE_STR	"btfmswr_slave"
+
+
+#define BTFMSWR_DBG(fmt, arg...)  pr_debug("%s: " fmt "\n", __func__, ## arg)
+#define BTFMSWR_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg)
+#define BTFMSWR_ERR(fmt, arg...)  pr_err("%s: " fmt "\n", __func__, ## arg)
+
+extern struct btfmswr *pbtfmswr;
+
+// assumption is that we use adjacent channels
+#define ONE_CHANNEL_MASK 1
+#define TWO_CHANNEL_MASK 3
+
+#define MAX_BT_PORTS 1
+
+/* Codec driver defines */
+enum {
+	FMAUDIO_TX = 0,
+	BTAUDIO_TX,
+	BTAUDIO_RX,
+	BTAUDIO_A2DP_SINK_TX,
+	BTFM_NUM_CODEC_DAIS
+};
+
+enum {
+	EVROS = 0,
+	GANGES = 1,
+	MAX_SOC_ID = 0xFF
+};
+
+
+struct btfmswr_dai_port_info {
+	int dai_id;
+	char *dai_name;
+	uint8_t port;
+};
+
+struct soc_port_mapping {
+	// enumeration address of BT SOC
+	u64 ea;
+	struct btfmswr_dai_port_info port_info[BTFM_NUM_CODEC_DAIS];
+};
+
+
+struct btfmswr {
+	struct device *dev;
+	struct swr_device *swr_slave;
+	bool initialized;
+	uint32_t sample_rate;
+	uint32_t bps;
+	uint16_t direction;
+	uint8_t num_channels;
+	int soc_index;
+	struct soc_port_mapping *p_dai_port;
+};
+
+/**
+ * btfm_swr_hw_init: Initialize soundwire slave device
+ * Returns:
+ * 0: Success
+ * else: Fail
+ */
+int btfm_swr_hw_init(void);
+
+int btfm_get_bt_soc_index(int chipset_ver);
+
+int btfm_swr_enable_port(u8 port_num, u8 ch_count, u32 sample_rate,
+									u8 port_type);
+
+
+int btfm_swr_disable_port(u8 port_num, u8 ch_count, u8 usecase);
+
+/**
+ * btfm_swr_register_codec: Register codec driver with ALSA
+ * @btfmswr: swr slave device data pointer.
+ * Returns:
+ * -ENOMEM
+ * 0
+ */
+int btfm_swr_register_codec(struct btfmswr *btfmswr);
+
+/**
+ * btfm_swr_unregister_codec: Unregister codec driver with ALSA
+ * @dev: device node
+ * Returns:
+ * VOID
+ */
+void btfm_swr_unregister_codec(struct device *dev);
+#endif /* BTFM_SWR_H */

+ 341 - 0
soundwire/btfm_swr_codec.c

@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "btfm_swr.h"
+
+static int bt_soc_enable_status;
+int btfm_feedback_ch_setting;
+
+static int btfm_swr_codec_write(struct snd_soc_component *codec,
+			unsigned int reg, unsigned int value)
+{
+	BTFMSWR_DBG("");
+	return 0;
+}
+
+static unsigned int btfm_swr_codec_read(struct snd_soc_component *codec,
+				unsigned int reg)
+{
+	BTFMSWR_DBG("");
+	return 0;
+}
+
+static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSWR_DBG("");
+	ucontrol->value.integer.value[0] = bt_soc_enable_status;
+	return 1;
+}
+
+static int btfm_soc_status_put(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSWR_DBG("");
+	return 1;
+}
+
+static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSWR_DBG("");
+	ucontrol->value.integer.value[0] = btfm_feedback_ch_setting;
+	return 1;
+}
+
+static int btfm_put_feedback_ch_setting(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSWR_DBG("");
+	btfm_feedback_ch_setting = ucontrol->value.integer.value[0];
+	return 1;
+}
+
+static const struct snd_kcontrol_new status_controls[] = {
+	SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0,
+			btfm_soc_status_get,
+			btfm_soc_status_put),
+	SOC_SINGLE_EXT("BT set feedback channel", 0, 0, 1, 0,
+	btfm_get_feedback_ch_setting,
+	btfm_put_feedback_ch_setting)
+};
+
+
+static int btfm_swr_codec_probe(struct snd_soc_component *codec)
+{
+	BTFMSWR_DBG("");
+	snd_soc_add_component_controls(codec, status_controls,
+				   ARRAY_SIZE(status_controls));
+	return 0;
+}
+
+static void btfm_swr_codec_remove(struct snd_soc_component *codec)
+{
+	BTFMSWR_DBG("");
+}
+
+static int btfm_swr_dai_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	int ret = -1;
+
+	BTFMSWR_INFO("substream = %s  stream = %d dai->name = %s",
+		 substream->name, substream->stream, dai->name);
+	ret = btfm_swr_hw_init();
+	return ret;
+}
+
+static void btfm_swr_dai_shutdown(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	int ret = 0;
+	u8 port_type;
+
+	BTFMSWR_INFO("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
+		dai->id, dai->rate);
+
+	switch (dai->id) {
+	case FMAUDIO_TX:
+		port_type = FM_AUDIO_TX1;
+		break;
+	case BTAUDIO_TX:
+		port_type = BT_AUDIO_TX1;
+		break;
+	case BTAUDIO_RX:
+		port_type = BT_AUDIO_RX1;
+		break;
+	case BTAUDIO_A2DP_SINK_TX:
+		port_type = BT_AUDIO_TX2;
+		break;
+	case BTFM_NUM_CODEC_DAIS:
+	default:
+		BTFMSWR_ERR("dai->id is invalid:%d", dai->id);
+		return;
+	}
+
+	ret = btfm_swr_disable_port(pbtfmswr->p_dai_port->port_info[dai->id].port,
+					pbtfmswr->num_channels, port_type);
+
+}
+
+static int btfm_swr_dai_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	pbtfmswr->bps = params_width(params);
+	pbtfmswr->direction = substream->stream;
+	pbtfmswr->num_channels = params_channels(params);
+
+	BTFMSWR_INFO("dai->name = %s dai id %x rate %d bps %d num_ch %d",
+		dai->name, dai->id, params_rate(params), params_width(params),
+		params_channels(params));
+	return 0;
+}
+
+static int btfm_swr_dai_prepare(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	int ret = -EINVAL;
+	u8 port_type;
+
+	bt_soc_enable_status = 0;
+	BTFMSWR_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d", dai->name,
+		dai->id, dai->rate, pbtfmswr->direction);
+
+	/* save sample rate */
+	pbtfmswr->sample_rate = dai->rate;
+
+	switch (dai->id) {
+	case FMAUDIO_TX:
+		port_type = FM_AUDIO_TX1;
+		break;
+	case BTAUDIO_TX:
+		port_type = BT_AUDIO_TX1;
+		break;
+	case BTAUDIO_RX:
+		port_type = BT_AUDIO_RX1;
+		break;
+	case BTAUDIO_A2DP_SINK_TX:
+		port_type = BT_AUDIO_TX2;
+		break;
+	case BTFM_NUM_CODEC_DAIS:
+	default:
+		BTFMSWR_ERR("dai->id is invalid:%d", dai->id);
+		return -EINVAL;
+	}
+
+	ret = btfm_swr_enable_port(pbtfmswr->p_dai_port->port_info[dai->id].port,
+					pbtfmswr->num_channels, dai->rate, port_type);
+
+	/* save the enable channel status */
+	if (ret == 0)
+		bt_soc_enable_status = 1;
+
+	if (ret == -EISCONN) {
+		BTFMSWR_ERR("channel opened without closing, returning success");
+		ret = 0;
+	}
+
+	return ret;
+}
+
+/* This function will be called once during boot up */
+static int btfm_swr_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)
+{
+	BTFMSWR_DBG("");
+	return 0;
+}
+
+static int btfm_swr_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)
+{
+	*rx_slot = 0;
+	*tx_slot = 0;
+	*rx_num = 0;
+	*tx_num = 0;
+
+	switch (dai->id) {
+	case FMAUDIO_TX:
+	case BTAUDIO_TX:
+	case BTAUDIO_A2DP_SINK_TX:
+		*tx_num = pbtfmswr->num_channels;
+		*tx_slot = pbtfmswr->num_channels == 2 ? TWO_CHANNEL_MASK :
+							ONE_CHANNEL_MASK;
+		break;
+	case BTAUDIO_RX:
+		*rx_num = pbtfmswr->num_channels;
+		*rx_slot = pbtfmswr->num_channels == 2 ? TWO_CHANNEL_MASK :
+							ONE_CHANNEL_MASK;
+		break;
+
+	default:
+		BTFMSWR_ERR("Unsupported DAI %d", dai->id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static struct snd_soc_dai_ops btfmswr_dai_ops = {
+	.startup = btfm_swr_dai_startup,
+	.shutdown = btfm_swr_dai_shutdown,
+	.hw_params = btfm_swr_dai_hw_params,
+	.prepare = btfm_swr_dai_prepare,
+	.set_channel_map = btfm_swr_dai_set_channel_map,
+	.get_channel_map = btfm_swr_dai_get_channel_map,
+};
+
+static struct snd_soc_dai_driver btfmswr_dai[] = {
+	{	/* FM Audio data multiple channel  : FM -> lpass */
+		.name = "btfm_fm_swr_tx",
+		.id = FMAUDIO_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 = &btfmswr_dai_ops,
+	},
+	{	/* Bluetooth SCO voice uplink: bt -> lpass */
+		.name = "btfm_bt_sco_swr_tx",
+		.id = BTAUDIO_TX,
+		.capture = {
+			.stream_name = "SCO TX Capture",
+			/* 8/16/44.1/48/88.2/96/192 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
+				| SNDRV_PCM_RATE_192000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 192000,
+			.rate_min = 8000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmswr_dai_ops,
+	},
+	{	/* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
+		.name = "btfm_bt_sco_a2dp_swr_rx",
+		.id = BTAUDIO_RX,
+		.playback = {
+			.stream_name = "SCO A2DP RX Playback",
+			/* 8/16/44.1/48/88.2/96/192 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
+				| SNDRV_PCM_RATE_192000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 192000,
+			.rate_min = 8000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmswr_dai_ops,
+	},
+	{	/* Bluetooth A2DP sink: bt -> lpass */
+		.name = "btfm_a2dp_sink_swr_tx",
+		.id = BTAUDIO_A2DP_SINK_TX,
+		.capture = {
+			.stream_name = "A2DP sink TX Capture",
+			/* 8/16/44.1/48/88.2/96/192 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
+				| SNDRV_PCM_RATE_192000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 192000,
+			.rate_min = 8000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmswr_dai_ops,
+	},
+};
+
+static const struct snd_soc_component_driver btfmswr_codec = {
+	.probe	= btfm_swr_codec_probe,
+	.remove	= btfm_swr_codec_remove,
+	.read	= btfm_swr_codec_read,
+	.write	= btfm_swr_codec_write,
+};
+
+int btfm_swr_register_codec(struct btfmswr *btfm_swr)
+{
+	int ret = 0;
+	struct device *dev = btfm_swr->dev;
+
+	BTFMSWR_DBG("");
+	dev_err(dev, "\n");
+
+	/* Register Codec driver */
+	ret = snd_soc_register_component(dev, &btfmswr_codec,
+		btfmswr_dai, ARRAY_SIZE(btfmswr_dai));
+	if (ret)
+		BTFMSWR_ERR("failed to register codec (%d)", ret);
+	return ret;
+}
+
+void btfm_swr_unregister_codec(struct device *dev)
+{
+	BTFMSWR_DBG("");
+	/* Unregister Codec driver */
+	snd_soc_unregister_component(dev);
+}
+
+
+MODULE_DESCRIPTION("BTFM SoundWire Codec driver");
+MODULE_LICENSE("GPL v2");

+ 44 - 0
soundwire/btfm_swr_slave.c

@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+
+#include "btfm_swr.h"
+#include "btfm_swr_slave.h"
+
+struct soc_port_mapping slave_port[] = {
+	// Evros
+	{
+	.ea = EVROS_EA,
+	.port_info[0].dai_id = FMAUDIO_TX,
+	.port_info[0].port = 5,
+
+	.port_info[1].dai_id = BTAUDIO_TX,
+	.port_info[1].port = 3,
+
+	.port_info[2].dai_id = BTAUDIO_RX,
+	.port_info[2].port = 1,
+
+	.port_info[3].dai_id = BTAUDIO_A2DP_SINK_TX,
+	.port_info[3].port = 4,
+	},
+
+	// Ganges
+	{
+	.ea = GANGES_EA,
+	// FM is not supported on Ganges. populate with invalid port number
+	.port_info[0].dai_id = FMAUDIO_TX,
+	.port_info[0].port = BTFM_INVALID_PORT,
+
+	.port_info[1].dai_id = BTAUDIO_TX,
+	.port_info[1].port = 4,
+
+	.port_info[2].dai_id = BTAUDIO_RX,
+	.port_info[2].port = 1,
+
+	.port_info[3].dai_id = BTAUDIO_A2DP_SINK_TX,
+	.port_info[3].port = 5,
+	},
+};
+

+ 45 - 0
soundwire/btfm_swr_slave.h

@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+
+#ifndef BTFM_SWR_SLAVE_H
+#define BTFM_SWR_SLAVE_H
+
+#include "btfm_swr.h"
+
+/* Registers Address */
+
+/* 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 BTFM_INVALID_PORT 0xFF
+
+extern struct soc_port_mapping slave_port[];
+
+enum {
+	QCA_GANGES_SOC_ID_0100 = 0x40210100,
+	QCA_GANGES_SOC_ID_0200 = 0x40210200,
+};
+
+
+enum {
+	QCA_EVROS_SOC_ID_0100 = 0x40200100,
+	QCA_EVROS_SOC_ID_0200 = 0x40200200,
+};
+
+
+enum {
+	EVROS_EA = 0x0108170220,
+	GANGES_EA = 0x0208170220,
+};
+
+/* Specific defines for slave slimbus device */
+#define SLAVE_SWR_REG_OFFSET		0x0800
+
+#endif