Ver Fonte

btfmcodec: Create btfmcodec driver

This changes handles below.

1. Create btfm codec driver.
2. add support to enumerate char device.
3. create hardware endpoint abstract layers.
4. Register with ALSA driver

CRs-Fixed: 3298745
Change-Id: Id75dad16de8414b3b6a24d265c8acb54eca4d5dc
Balakrishna Godavarthi há 3 anos atrás
pai
commit
d22c687077

+ 7 - 0
Kbuild

@@ -10,6 +10,13 @@ ifeq ($(CONFIG_I2C_RTC6226_QCA),m)
 KBUILD_CPPFLAGS += -DCONFIG_I2C_RTC6226_QCA
 endif
 
+
+ifeq ($(CONFIG_SLIM_BTFM_CODEC), m)
+KBUILD_CPPFLAGS += -DCONFIG_SLIM_BTFM_CODEC
+endif
+
 obj-$(CONFIG_MSM_BT_POWER) += pwr/
 obj-$(CONFIG_BTFM_SLIM) += slimbus/
 obj-$(CONFIG_I2C_RTC6226_QCA) += rtc6226/
+obj-$(CONFIG_BTFM_CODEC) += btfmcodec/
+obj-$(CONFIG_SLIM_BTFM_CODEC) += slimbus/

+ 10 - 0
btfmcodec/Kconfig

@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config BTFM_CODEC
+	tristate "MSM Bluetooth/FM CODEC Driver"
+	help
+		This will enables BT/FM Codec driver. Hardware endpoint
+		drivers will register to this driver to communicates with ALSA codec
+		driver.
+
+		Say Y here to compile support for BT/FM Codec driver
+		into the kernel or say M to compile as a module.

+ 4 - 0
btfmcodec/Makefile

@@ -0,0 +1,4 @@
+ccflags-y += -I$(BT_ROOT)/include
+ccflags-y += -I$(BT_ROOT)/btfmcodec/include
+btfmcodec-objs := btfm_codec.o btfm_codec_hw_interface.o btfm_codec_interface.o
+obj-$(CONFIG_BTFM_CODEC) += btfmcodec.o

+ 239 - 0
btfmcodec/btfm_codec.c

@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/slab.h>
+#include <linux/kdev_t.h>
+#include <linux/refcount.h>
+#include <linux/idr.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include "btfm_codec.h"
+//#include "btfm_codec_hw_interface.h"
+
+#define dev_to_btfmcodec(_dev) container_of(_dev, struct btfmcodec_data, dev)
+
+static DEFINE_IDR(dev_minor);
+static struct class *dev_class;
+static dev_t dev_major;
+struct btfmcodec_data *btfmcodec;
+struct device_driver driver = {.name = "btfmcodec-driver", .owner = THIS_MODULE};
+struct btfmcodec_char_device *btfmcodec_dev;
+
+char *coverttostring(enum btfmcodec_states state) {
+	switch (state) {
+	case IDLE:
+		return "IDLE";
+		break;
+	case BT_Connected:
+		return "BT_CONNECTED";
+		break;
+	case BT_Connecting:
+		return "BT_CONNECTING";
+		break;
+	case BTADV_AUDIO_Connected:
+		return "BTADV_AUDIO_CONNECTED";
+		break;
+	case BTADV_AUDIO_Connecting:
+		return "BTADV_AUDIO_CONNECTING";
+		break;
+	default:
+		return "INVALID_STATE";
+		break;
+	}
+}
+
+static const struct file_operations btfmcodec_fops = {
+	.owner = THIS_MODULE,
+/*	.open = glink_pkt_open,
+	.release = glink_pkt_release,
+	.read = glink_pkt_read,
+	.write = glink_pkt_write,
+	.poll = glink_pkt_poll,
+	.unlocked_ioctl = glink_pkt_ioctl,
+	.compat_ioctl = glink_pkt_ioctl,
+*/};
+
+static ssize_t btfmcodec_attributes_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t n)
+{
+	struct btfmcodec_data *btfmcodec = dev_to_btfmcodec(dev);
+	struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	long tmp;
+
+	mutex_lock(&btfmcodec_dev->lock);
+	if (kstrtol(buf, 0, &tmp)) {
+		mutex_unlock(&btfmcodec_dev->lock);
+/*		BTFMCODEC_ERR("unable to convert string to int for /dev/%s\n",
+			      btfmcodec->dev->name);
+*/		return -EINVAL;
+	}
+	mutex_unlock(&btfmcodec_dev->lock);
+
+	return n;
+}
+
+static ssize_t btfmcodec_attributes_show(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+//	struct btfmcodec_char_device *btfmcodec_dev = dev_to_btfmcodec(dev);
+
+	return 0;
+}
+
+struct btfmcodec_data* btfm_get_btfmcodec(void)
+{
+	return btfmcodec;
+}
+EXPORT_SYMBOL(btfm_get_btfmcodec);
+
+static DEVICE_ATTR_RW(btfmcodec_attributes);
+
+static int __init btfmcodec_init(void)
+{
+	struct btfmcodec_state_machine *states;
+	struct btfmcodec_char_device *btfmcodec_dev;
+	struct device *dev;
+	int ret;
+
+	BTFMCODEC_INFO("starting up the module");
+	btfmcodec = kzalloc(sizeof(struct btfmcodec_data), GFP_KERNEL);
+	if (!btfmcodec) {
+		BTFMCODEC_ERR("failed to allocate memory");
+		return -ENOMEM;
+	}
+
+	states = &btfmcodec->states;
+	states->current_state = IDLE;
+	states->next_state = IDLE;
+
+	BTFMCODEC_INFO("creating device node");
+	/* create device node for communication between userspace and kernel */
+	btfmcodec_dev = kzalloc(sizeof(struct btfmcodec_char_device), GFP_KERNEL);
+	if (!btfmcodec_dev) {
+		BTFMCODEC_ERR("failed to allocate memory");
+		ret = -ENOMEM;
+		goto info_cleanup;
+	}
+
+	BTFMCODEC_INFO("trying to get major number\n");
+	ret = alloc_chrdev_region(&dev_major, 0, 0, "btfmcodec");
+	if (ret < 0) {
+		BTFMCODEC_ERR("failed to allocate character device region");
+		goto dev_cleanup;
+	}
+
+	BTFMCODEC_INFO("creating btfm codec class");
+	dev_class = class_create(THIS_MODULE, "btfmcodec");
+	if (IS_ERR(dev_class)) {
+		ret = PTR_ERR(dev_class);
+		BTFMCODEC_ERR("class_create failed ret:%d\n", ret);
+		goto deinit_chrdev;
+	}
+
+	btfmcodec_dev->reuse_minor = idr_alloc(&dev_minor, btfmcodec, 1, 0, GFP_KERNEL);
+	if (ret < 0) {
+		BTFMCODEC_ERR("failed to allocated minor number");
+		goto deinit_class;
+	}
+
+	dev = &btfmcodec->dev;
+	dev->driver = &driver;
+
+	// ToDo Rethink of having btfmcodec alone instead of btfmcodec
+	btfmcodec->btfmcodec_dev = btfmcodec_dev;
+	refcount_set(&btfmcodec_dev->active_clients, 1);
+	mutex_init(&btfmcodec_dev->lock);
+	strlcpy(btfmcodec_dev->dev_name, "btfmcodec_dev", DEVICE_NAME_MAX_LEN);
+	device_initialize(dev);
+	dev->class = dev_class;
+	dev->devt = MKDEV(MAJOR(dev_major), btfmcodec_dev->reuse_minor);
+	dev_set_drvdata(dev, btfmcodec);
+
+	cdev_init(&btfmcodec_dev->cdev, &btfmcodec_fops);
+	btfmcodec_dev->cdev.owner = THIS_MODULE;
+	dev_set_name(dev, btfmcodec_dev->dev_name, btfmcodec_dev->reuse_minor);
+	ret = cdev_add(&btfmcodec_dev->cdev, dev->devt, 1);
+	if (ret) {
+		BTFMCODEC_ERR("cdev_add failed with error no %d", ret);
+		goto idr_cleanup;
+	}
+
+	// ToDo to handler HIDL abrupt kill
+	dev->release = NULL;
+	ret = device_add(dev);
+	if (ret) {
+		BTFMCODEC_ERR("Failed to add device error no %d", ret);
+		goto free_device;
+	}
+
+	BTFMCODEC_ERR("Creating a sysfs entry with name: %s", btfmcodec_dev->dev_name);
+	ret = device_create_file(dev, &dev_attr_btfmcodec_attributes);
+	if (ret) {
+		BTFMCODEC_ERR("Failed to create a devicd node: %s", btfmcodec_dev->dev_name);
+		goto free_device;
+	}
+
+	BTFMCODEC_INFO("created a node at /dev/%s with %u:%u\n",
+		btfmcodec_dev->dev_name, dev_major, btfmcodec_dev->reuse_minor);
+
+	return ret;
+
+free_device:
+	put_device(dev);
+idr_cleanup:
+	idr_remove(&dev_minor, btfmcodec_dev->reuse_minor);
+deinit_class:
+	class_destroy(dev_class);
+deinit_chrdev:
+	unregister_chrdev_region(MAJOR(dev_major), 0);
+dev_cleanup:
+	kfree(btfmcodec_dev);
+info_cleanup:
+	kfree(btfmcodec);
+
+	return ret;
+}
+
+static void __exit btfmcodec_deinit(void)
+{
+	struct btfmcodec_char_device *btfmcodec_dev;
+	struct device *dev;
+	BTFMCODEC_INFO("cleaning up btfm codec driver", __func__);
+	if (!btfmcodec) {
+		BTFMCODEC_ERR("skiping driver cleanup", __func__);
+		goto info_cleanup;
+	}
+
+	dev = &btfmcodec->dev;
+
+	device_remove_file(dev, &dev_attr_btfmcodec_attributes);
+	put_device(dev);
+
+	if (!btfmcodec->btfmcodec_dev) {
+		BTFMCODEC_ERR("skiping device node cleanup", __func__);
+		goto info_cleanup;
+	}
+
+	btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	idr_remove(&dev_minor, btfmcodec_dev->reuse_minor);
+	class_destroy(dev_class);
+	unregister_chrdev_region(MAJOR(dev_major), 0);
+	kfree(btfmcodec_dev);
+info_cleanup:
+	kfree(btfmcodec);
+	BTFMCODEC_INFO("btfm codec driver cleanup completed", __func__);
+	return;
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM Bluetooth FM CODEC driver");
+
+module_init(btfmcodec_init);
+module_exit(btfmcodec_deinit);

+ 99 - 0
btfmcodec/btfm_codec_hw_interface.c

@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include "btfm_codec.h"
+#include "btfm_codec_hw_interface.h"
+#include "btfm_codec_interface.h"
+
+struct mutex hwep_drv_lock;
+
+int btfmcodec_register_hw_ep (struct hwep_data *ep_info)
+{
+	struct btfmcodec_data *btfmcodec;
+	struct hwep_data *hwep_info;
+	int ret = 0;
+
+	// ToDo Check wether we need mutex_init api
+	mutex_lock(&hwep_drv_lock);
+	btfmcodec = btfm_get_btfmcodec();
+	if (!btfmcodec) {
+		BTFMCODEC_ERR("btfm codec driver it not initialized");
+		ret = -EPERM;
+		goto end;
+	}
+
+	if (ep_info->num_dai == 0) {
+		BTFMCODEC_ERR("no active information provided by hw ep interface");
+		ret = -EPERM;
+		goto end;
+
+	}
+
+	hwep_info = btfmcodec->hwep_info;
+	if (hwep_info) {
+		BTFMCODEC_ERR("driver already holds hardware endpoint info");
+		ret = -EPERM;
+		goto end;
+	}
+
+	hwep_info = kzalloc(sizeof(struct hwep_data), GFP_KERNEL);
+	if (!hwep_info) {
+		BTFMCODEC_ERR("%s: failed to allocate memory\n", __func__);
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	btfmcodec->hwep_info = hwep_info;
+	memcpy(hwep_info, ep_info, sizeof(struct hwep_data));
+
+	BTFMCODEC_INFO("Below driver registered with btfm codec\n");
+	BTFMCODEC_INFO("Driver name: %s\n", hwep_info->driver_name);
+	BTFMCODEC_INFO("Num of dai: %d supported", hwep_info->num_dai);
+	BTFMCODEC_INFO("Master config enabled: %u\n", test_bit(BTADV_AUDIO_MASTER_CONFIG,
+		&hwep_info->flags));
+	ret = btfm_register_codec(hwep_info);
+end:
+	mutex_unlock(&hwep_drv_lock);
+	return ret;
+}
+
+int btfmcodec_unregister_hw_ep (char *driver_name)
+{
+	struct btfmcodec_data *btfmcodec;
+	struct hwep_data *hwep_info;
+	int ret;
+
+	mutex_lock(&hwep_drv_lock);
+	btfmcodec = btfm_get_btfmcodec();
+	if (!btfmcodec) {
+		BTFMCODEC_ERR("btfm codec driver it not initialized");
+		ret = -EPERM;
+		goto end;
+	}
+
+	hwep_info = btfmcodec->hwep_info;
+	if (!hwep_info) {
+		BTFMCODEC_ERR("%s: no active hardware endpoint registered\n", __func__);
+		ret = -EPERM;
+		goto end;
+	}
+
+	if(!strncmp(hwep_info->driver_name, driver_name, DEVICE_NAME_MAX_LEN)) {
+		btfm_unregister_codec();
+		kfree(hwep_info);
+		BTFMCODEC_INFO("%s: deleted %s hardware endpoint\n", __func__, driver_name);
+		ret = -1;
+		goto end;
+	} else {
+		BTFMCODEC_ERR("%s: No hardware endpoint registered with %s\n", __func__, driver_name);
+		ret = -1;
+		goto end;
+	}
+end:
+	mutex_unlock(&hwep_drv_lock);
+	return ret;
+}
+
+EXPORT_SYMBOL(btfmcodec_register_hw_ep);
+EXPORT_SYMBOL(btfmcodec_unregister_hw_ep);

+ 278 - 0
btfmcodec/btfm_codec_interface.c

@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include "btfm_codec.h"
+#include "btfm_codec_interface.h"
+
+static struct snd_soc_dai_driver *btfmcodec_dai_info;
+
+static int btfmcodec_codec_probe(struct snd_soc_component *codec)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check weather probe has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (hwep_info->drv && hwep_info->drv->hwep_probe) {
+		return hwep_info->drv->hwep_probe(codec);
+	}
+
+	// ToDo to add mixer control.
+	return 0;
+}
+
+static void btfmcodec_codec_remove(struct snd_soc_component *codec)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check whether remove has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (hwep_info->drv && hwep_info->drv->hwep_remove) {
+		hwep_info->drv->hwep_remove(codec);
+	}
+}
+
+static int btfmcodec_codec_write(struct snd_soc_component *codec,
+			unsigned int reg, unsigned int value)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check whether remove has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (hwep_info->drv && hwep_info->drv->hwep_remove) {
+		return hwep_info->drv->hwep_write(codec, reg, value);
+	}
+
+	return 0;
+}
+
+static unsigned int btfmcodec_codec_read(struct snd_soc_component *codec,
+				unsigned int reg)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check whether remove has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (hwep_info->drv && hwep_info->drv->hwep_read) {
+		return hwep_info->drv->hwep_read(codec, reg);
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver btfmcodec_codec_component = {
+	.probe	= btfmcodec_codec_probe,
+	.remove	= btfmcodec_codec_remove,
+	.read	= btfmcodec_codec_read,
+	.write	= btfmcodec_codec_write,
+};
+
+
+static inline void * btfmcodec_get_dai_drvdata(struct hwep_data *hwep_info)
+{
+	if (!hwep_info || !hwep_info->dai_drv) return NULL;
+	return hwep_info->dai_drv;
+}
+
+static int btfmcodec_dai_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(dai->component);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)btfmcodec_get_dai_drvdata(btfmcodec->hwep_info);
+
+	BTFMCODEC_DBG("substream = %s  stream = %d dai->name = %s",
+		 substream->name, substream->stream, dai->name);
+
+	// ToDo: check whether statup has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (dai && dai_drv->dai_ops && dai_drv->dai_ops->hwep_startup) {
+		return dai_drv->dai_ops->hwep_startup((void *)btfmcodec->hwep_info);
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static void btfmcodec_dai_shutdown(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(dai->component);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)btfmcodec_get_dai_drvdata(btfmcodec->hwep_info);
+
+	BTFMCODEC_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
+		dai->id, dai->rate);
+
+	// ToDo: check whether statup has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (dai && dai_drv->dai_ops && dai_drv->dai_ops->hwep_shutdown) {
+		dai_drv->dai_ops->hwep_shutdown((void *)btfmcodec->hwep_info, dai->id);
+	}
+}
+
+static int btfmcodec_dai_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(dai->component);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)btfmcodec_get_dai_drvdata(btfmcodec->hwep_info);
+	uint32_t bps = params_width(params);
+	uint32_t direction = substream->stream;
+
+	BTFMCODEC_DBG("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));
+	// ToDo: check whether hw_params has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_hw_params) {
+		return dai_drv->dai_ops->hwep_hw_params((void *)btfmcodec->hwep_info, bps, direction);
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int btfmcodec_dai_prepare(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(dai->component);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)btfmcodec_get_dai_drvdata(btfmcodec->hwep_info);
+	uint32_t sampling_rate = dai->rate;
+	uint32_t direction = substream->stream;
+	int id = dai->id;
+
+
+	BTFMCODEC_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d", dai->name,
+		id, sampling_rate, direction);
+	// ToDo: check whether hw_params has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_prepare) {
+		return dai_drv->dai_ops->hwep_prepare((void *)btfmcodec->hwep_info, sampling_rate,
+							direction, id);
+	} else {
+		return -1;
+	}
+
+	return 0;
+	return 0;
+}
+
+static int btfmcodec_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)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(dai->component);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)btfmcodec_get_dai_drvdata(btfmcodec->hwep_info);
+
+	BTFMCODEC_DBG("");
+	// ToDo: check whether hw_params has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_set_channel_map) {
+		return dai_drv->dai_ops->hwep_set_channel_map((void *)btfmcodec->hwep_info, tx_num,
+								tx_slot, rx_num, rx_slot);
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int btfmcodec_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)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(dai->component);
+	struct btfmcodec_state_machine states = btfmcodec->states;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)btfmcodec_get_dai_drvdata(btfmcodec->hwep_info);
+
+	BTFMCODEC_DBG("");
+	// ToDo: check whether hw_params has to allowed when state if different
+	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_get_channel_map) {
+		return dai_drv->dai_ops->hwep_get_channel_map((void *)btfmcodec->hwep_info, tx_num,
+								tx_slot, rx_num, rx_slot, dai->id);
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops btfmcodec_dai_ops = {
+	.startup = btfmcodec_dai_startup,
+	.shutdown = btfmcodec_dai_shutdown,
+	.hw_params = btfmcodec_dai_hw_params,
+	.prepare = btfmcodec_dai_prepare,
+	.set_channel_map = btfmcodec_dai_set_channel_map,
+	.get_channel_map = btfmcodec_dai_get_channel_map,
+};
+
+int btfm_register_codec(struct hwep_data *hwep_info)
+{
+	struct btfmcodec_data *btfmcodec;
+	struct device *dev;
+	struct hwep_dai_driver *dai_drv;
+	int i, ret;
+
+	btfmcodec = btfm_get_btfmcodec();
+	dev = &btfmcodec->dev;
+	btfmcodec_dai_info = kzalloc((sizeof(struct snd_soc_dai_driver) * hwep_info->num_dai), GFP_KERNEL);
+	if (!btfmcodec_dai_info) {
+		BTFMCODEC_ERR("failed to allocate memory");
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < hwep_info->num_dai; i++) {
+		dai_drv = &hwep_info->dai_drv[i];
+		btfmcodec_dai_info[i].name = dai_drv->dai_name;
+		btfmcodec_dai_info[i].id = dai_drv->id;
+		btfmcodec_dai_info[i].capture = dai_drv->capture;
+		btfmcodec_dai_info[i].playback = dai_drv->playback;
+		btfmcodec_dai_info[i].ops = &btfmcodec_dai_ops;
+	}
+
+	BTFMCODEC_INFO("Adding %d dai support to codec", hwep_info->num_dai);
+	BTFMCODEC_INFO("slim bus driver name:%s", dev->driver->name);
+	ret = snd_soc_register_component(dev, &btfmcodec_codec_component,
+					btfmcodec_dai_info, hwep_info->num_dai);
+
+	return ret;
+}
+
+void btfm_unregister_codec(void)
+{
+	struct btfmcodec_data *btfmcodec;
+
+	btfmcodec = btfm_get_btfmcodec();
+	snd_soc_unregister_component(&btfmcodec->dev);
+}

+ 58 - 0
btfmcodec/include/btfm_codec.h

@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_CODEC_H
+#define __LINUX_BTFM_CODEC_H
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/printk.h>
+#include <linux/cdev.h>
+#include "btfm_codec_hw_interface.h"
+
+#define BTFMCODEC_DBG(fmt, arg...)  pr_err("%s: " fmt "\n", __func__, ## arg)
+#define BTFMCODEC_INFO(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg)
+#define BTFMCODEC_ERR(fmt, arg...)  pr_err("%s: " fmt "\n", __func__, ## arg)
+#define BTFMCODEC_WARN(fmt, arg...) pr_warn("%s: " fmt "\n", __func__, ## arg)
+
+#define DEVICE_NAME_MAX_LEN	64
+
+enum btfmcodec_states {
+	/*Default state of btfm codec driver */
+	IDLE = 0,
+	/* When BT is active transport */
+	BT_Connected = 1,
+	/* Waiting for BT bearer indication after configuring HW ports */
+	BT_Connecting = 2,
+	/* When BTADV_AUDIO is active transport */
+	BTADV_AUDIO_Connected = 3,
+	/* Waiting for BTADV_AUDIO bearer switch indications */
+	BTADV_AUDIO_Connecting = 4
+};
+
+char *coverttostring(enum btfmcodec_states);
+struct btfmcodec_state_machine {
+	enum btfmcodec_states prev_state;
+	enum btfmcodec_states current_state;
+	enum btfmcodec_states next_state;
+};
+
+struct btfmcodec_char_device {
+	struct cdev cdev;
+	refcount_t active_clients;
+	struct mutex lock;
+	int reuse_minor;
+	char dev_name[DEVICE_NAME_MAX_LEN];
+};
+
+struct btfmcodec_data {
+	struct device dev;
+	struct btfmcodec_state_machine states;
+	struct btfmcodec_char_device *btfmcodec_dev;
+	struct hwep_data *hwep_info;
+};
+
+struct btfmcodec_data *btfm_get_btfmcodec(void);
+#endif /*__LINUX_BTFM_CODEC_H */

+ 77 - 0
btfmcodec/include/btfm_codec_hw_interface.h

@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_CODEC_HW_INTERFACE_H
+#define __LINUX_BTFM_CODEC_HW_INTERFACE_H
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+/* This flag is set to indicate btfm codec driver is
+ * responsible to configure master.
+ */
+#define BTADV_AUDIO_MASTER_CONFIG	0
+#define DEVICE_NAME_MAX_LEN	64
+
+struct hwep_comp_drv {
+	int  (*hwep_probe)  (struct snd_soc_component *component);
+	void (*hwep_remove) (struct snd_soc_component *component);
+	unsigned int  (*hwep_read)(struct snd_soc_component *component, unsigned int reg);
+	int  (*hwep_write)(struct snd_soc_component *componentm, unsigned int reg, unsigned int value);
+};
+
+struct hwep_dai_ops {
+	int (*hwep_startup)(void *);
+	void (*hwep_shutdown)(void *, int);
+	int (*hwep_hw_params)(void *, uint32_t, uint32_t);
+	int (*hwep_prepare)(void *, uint32_t, uint32_t, int);
+	int (*hwep_set_channel_map)(void *, unsigned int, unsigned int *,
+				unsigned int, unsigned int *);
+	int (*hwep_get_channel_map)(void *, unsigned int *, unsigned int *,
+				unsigned int *, unsigned int *, int);
+};
+
+struct hwep_dai_driver {
+	const char *dai_name;
+	unsigned int id;
+	struct snd_soc_pcm_stream capture;
+	struct snd_soc_pcm_stream playback;
+        struct hwep_dai_ops *dai_ops;
+};
+
+struct hwep_data {
+	struct device *dev;
+	char driver_name [DEVICE_NAME_MAX_LEN];
+        struct hwep_comp_drv *drv;
+        struct hwep_dai_driver *dai_drv;
+	int num_dai;
+	unsigned long flags;
+};
+
+int btfmcodec_register_hw_ep(struct hwep_data *);
+int btfmcodec_unregister_hw_ep(char *);
+// ToDo below.
+/*
+#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC_DRV)
+int btfmcodec_register_hw_ep(struct hwep_data *);
+int btfmcodec_unregister_hw_ep(char *);
+#else
+static inline int btfmcodec_register_hw_ep(struct hwep_data *hwep_info)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int btfmcodec_unregister_hw_ep(char *dev_name)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+*/
+#endif /*__LINUX_BTFM_CODEC_HW_INTERFACE_H */

+ 12 - 0
btfmcodec/include/btfm_codec_interface.h

@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_CODEC_INTERFACE
+#define __LINUX_BTFM_CODEC_INTERFACE
+
+#include "btfm_codec_hw_interface.h"
+int btfm_register_codec(struct hwep_data *hwep_info);
+void btfm_unregister_codec(void);
+#endif /*__LINUX_BTFM_CODEC_INTERFACE */

+ 10 - 0
slimbus/Kconfig

@@ -13,3 +13,13 @@ config BTFM_SLIM
 		Say Y here to compile support for Bluetooth slimbus driver
 		into the kernel or say M to compile as a module.
 
+config SLIM_BTFM_CODEC
+	tristate "MSM Bluetooth/FM Slimbus Device using BTFM codec driver"
+	depends on MSM_BT_POWER
+	depends on BTFM_CODEC
+	help
+		This enables BT/FM slimbus driver to use btfm codec driver as
+		interface to interacts with codec driver.
+
+		Say Y here to compile support for Bluetooth slimbus driver
+		into the kernel or say M to compile as a module.

+ 5 - 0
slimbus/Makefile

@@ -1,3 +1,8 @@
 ccflags-y += -I$(BT_ROOT)/include
+ccflags-y += -I$(BT_ROOT)/btfmcodec/include
+#Below src is for BTFM SLAVE CODEC Driver support on LE platform.
 bt_fm_slim-objs := btfm_slim.o btfm_slim_codec.o btfm_slim_slave.o
 obj-$(CONFIG_BTFM_SLIM) += bt_fm_slim.o
+# Below src is for BTFM Driver support based on btfm codec
+btfm_slim_codec-objs := btfm_slim.o btfm_slim_hw_interface.o btfm_slim_slave.o
+obj-$(CONFIG_SLIM_BTFM_CODEC) += btfm_slim_codec.o

+ 13 - 1
slimbus/btfm_slim.c

@@ -21,6 +21,8 @@
 #include "btpower.h"
 #include "btfm_slim.h"
 #include "btfm_slim_slave.h"
+#include "btfm_slim_hw_interface.h"
+
 #define DELAY_FOR_PORT_OPEN_MS (200)
 #define SLIM_MANF_ID_QCOM	0x217
 #define SLIM_PROD_CODE		0x221
@@ -520,7 +522,12 @@ static int btfm_slim_status(struct slim_device *sdev,
 	struct device *dev = &sdev->dev;
 	struct btfmslim *btfm_slim;
 	btfm_slim = dev_get_drvdata(dev);
+
+#if IS_ENABLED(CONFIG_BTFM_SLIM)
 	ret = btfm_slim_register_codec(btfm_slim);
+#else
+	ret = btfm_slim_register_hw_ep(btfm_slim);
+#endif
 	if (ret)
 		BTFMSLIM_ERR("error, registering slimbus codec failed");
 	return ret;
@@ -534,7 +541,8 @@ static int btfm_slim_probe(struct slim_device *slim)
 	pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
 	/*this as true during the probe then slimbus won't check for logical address*/
 	slim->is_laddr_valid = true;
-	dev_set_name(&slim->dev, "%s", "btfmslim_slave");
+
+	dev_set_name(&slim->dev, "%s", BTFMSLIM_DEV_NAME);
 	pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
 
 	BTFMSLIM_DBG("");
@@ -565,7 +573,11 @@ static int btfm_slim_probe(struct slim_device *slim)
 	btfm_slim->dev = &slim->dev;
 	ret = btpower_register_slimdev(&slim->dev);
 	if (ret < 0) {
+#if IS_ENABLED(CONFIG_BTFM_SLIM)
 		btfm_slim_unregister_codec(&slim->dev);
+#else
+		btfm_slim_unregister_hwep();
+#endif
 		ret = -EPROBE_DEFER;
 		goto dealloc;
 	}

+ 457 - 0
slimbus/btfm_slim_hw_interface.c

@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021, 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/slimbus.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+#include <linux/errno.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_hw_interface.h"
+#include "btfm_codec_hw_interface.h"
+
+static int bt_soc_enable_status;
+int btfm_feedback_ch_setting;
+
+static int btfm_slim_codec_write(struct snd_soc_component *codec,
+			unsigned int reg, unsigned int value)
+{
+	BTFMSLIM_DBG("");
+	return 0;
+}
+
+static unsigned int btfm_slim_codec_read(struct snd_soc_component *codec,
+				unsigned int reg)
+{
+	BTFMSLIM_DBG("");
+	return 0;
+}
+
+static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSLIM_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)
+{
+	BTFMSLIM_DBG("");
+	return 1;
+}
+
+static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSLIM_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)
+{
+	BTFMSLIM_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_slim_codec_probe(struct snd_soc_component *codec)
+{
+	BTFMSLIM_DBG("");
+	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)
+{
+	BTFMSLIM_DBG("");
+}
+
+static int btfm_slim_dai_startup(void *dai)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+	int ret = -1;
+
+	BTFMSLIM_DBG("");
+	ret = btfm_slim_hw_init(btfmslim);
+	return ret;
+}
+
+static void btfm_slim_dai_shutdown(void *dai, int id)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+	struct btfmslim_ch *ch;
+	int i;
+	uint8_t rxport, nchan = 1;
+
+	BTFMSLIM_DBG("");
+	switch (id) {
+	case BTFM_FM_SLIM_TX:
+		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("id is invalid:%d", 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 != 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, nchan);
+	btfm_slim_hw_deinit(btfmslim);
+}
+
+static int btfm_slim_dai_hw_params(void *dai, uint32_t bps,
+				   uint32_t direction) {
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+
+	BTFMSLIM_DBG("");
+	btfmslim->bps = bps;
+	btfmslim->direction = direction;
+
+	return 0;
+}
+
+static int btfm_slim_dai_prepare(void *dai, uint32_t sampling_rate, uint32_t direction, int id)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+	struct btfmslim_ch *ch;
+	int ret = -EINVAL;
+	int i = 0;
+	uint8_t rxport, nchan = 1;
+
+	btfmslim->direction = direction;
+	bt_soc_enable_status = 0;
+
+	/* save sample rate */
+	btfmslim->sample_rate = sampling_rate;
+
+	switch (id) {
+	case BTFM_FM_SLIM_TX:
+		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("id is invalid:%d", 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 != 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, sampling_rate, nchan);
+
+	/* save the enable channel status */
+	if (ret == 0)
+		bt_soc_enable_status = 1;
+
+	if (ret == -EISCONN) {
+		BTFMSLIM_ERR("channel opened without closing, returning success");
+		ret = 0;
+	}
+	return ret;
+}
+
+/* This function will be called once during boot up */
+static int btfm_slim_dai_set_channel_map(void *dai,
+				unsigned int tx_num, unsigned int *tx_slot,
+				unsigned int rx_num, unsigned int *rx_slot)
+{
+
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+	struct btfmslim_ch *rx_chs;
+	struct btfmslim_ch *tx_chs;
+	int ret = 0, i;
+
+	BTFMSLIM_DBG("");
+
+	if (!btfmslim)
+		return -EINVAL;
+
+	rx_chs = btfmslim->rx_chs;
+	tx_chs = btfmslim->tx_chs;
+
+	if (!rx_chs || !tx_chs)
+		return ret;
+
+	BTFMSLIM_DBG("Rx: id\tname\tport\tch");
+	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);
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", rx_chs->id,
+			rx_chs->name, rx_chs->port, rx_chs->ch);
+	}
+
+	BTFMSLIM_DBG("Tx: id\tname\tport\tch");
+	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);
+	BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id,
+			tx_chs->name, tx_chs->port, tx_chs->ch);
+	}
+
+	return ret;
+}
+
+static int btfm_slim_dai_get_channel_map(void *dai,
+				 unsigned int *tx_num, unsigned int *tx_slot,
+				 unsigned int *rx_num, unsigned int *rx_slot, int id)
+{
+
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+	int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1;
+	struct btfmslim_ch *ch = NULL;
+
+	BTFMSLIM_DBG("");
+	if (!btfmslim)
+		return ret;
+
+	switch (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", 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 != 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)",
+				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 hwep_dai_ops  btfmslim_hw_dai_ops = {
+	.hwep_startup = btfm_slim_dai_startup,
+	.hwep_shutdown = btfm_slim_dai_shutdown,
+	.hwep_hw_params = btfm_slim_dai_hw_params,
+	.hwep_prepare = btfm_slim_dai_prepare,
+	.hwep_set_channel_map = btfm_slim_dai_set_channel_map,
+	.hwep_get_channel_map = btfm_slim_dai_get_channel_map,
+};
+
+static struct hwep_dai_driver btfmslim_dai_driver[] = {
+	{	/* Bluetooth SCO voice uplink: bt -> lpass */
+		.dai_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
+				| SNDRV_PCM_RATE_8000_192000
+				| 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,
+		},
+		.dai_ops = &btfmslim_hw_dai_ops,
+	},
+	{	/* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
+		.dai_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_8000_192000
+				| 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,
+		},
+		.dai_ops = &btfmslim_hw_dai_ops,
+	},
+};
+
+static struct hwep_comp_drv btfmslim_hw_driver = {
+	.hwep_probe	= btfm_slim_codec_probe,
+	.hwep_remove	= btfm_slim_codec_remove,
+	.hwep_read	= btfm_slim_codec_read,
+	.hwep_write	= btfm_slim_codec_write,
+};
+
+int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim)
+{
+	struct device *dev = btfm_slim->dev;
+	struct hwep_data *hwep_info;
+	int ret = 0;
+
+	BTFMSLIM_INFO("Registering with BTFMCODEC HWEP interface\n");
+	hwep_info = kzalloc(sizeof(struct hwep_data), GFP_KERNEL);
+
+	if (!hwep_info) {
+		BTFMSLIM_ERR("%s: failed to allocate memory\n", __func__);
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	/* Copy EP device parameters as intercations will be on the same device */
+	hwep_info->dev = dev;
+	strlcpy(hwep_info->driver_name, BTFMSLIM_DEV_NAME, DEVICE_NAME_MAX_LEN);
+	hwep_info->drv = &btfmslim_hw_driver;
+	hwep_info->dai_drv = btfmslim_dai_driver;
+	hwep_info->num_dai = ARRAY_SIZE(btfmslim_dai_driver);
+	hwep_info->num_dai = 2;
+	set_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags);
+	/* Register to hardware endpoint */
+	ret = btfmcodec_register_hw_ep(hwep_info);
+	if (ret) {
+		BTFMSLIM_ERR("failed to register with btfmcodec driver hw interface (%d)", ret);
+		goto end;
+	}
+
+	BTFMSLIM_INFO("Registered succesfull with BTFMCODEC HWEP interface\n");
+	return ret;
+end:
+	return ret;
+}
+
+void btfm_slim_unregister_hwep(void)
+{
+	BTFMSLIM_INFO("Unregistered with BTFMCODEC HWEP	interface");
+	/* Unregister with BTFMCODEC HWEP	driver */
+	btfmcodec_unregister_hw_ep(BTFMSLIM_DEV_NAME);
+
+}
+
+MODULE_DESCRIPTION("BTFM Slimbus driver");
+MODULE_LICENSE("GPL v2");

+ 18 - 0
slimbus/btfm_slim_hw_interface.h

@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_SLIM_HW_INTERFACE_H
+#define __LINUX_BTFM_SLIM_HW_INTERFACE_H
+
+#if IS_ENABLED(CONFIG_BTFM_SLIM)
+#define BTFMSLIM_DEV_NAME "btfmslim_slave"
+#else
+#define BTFMSLIM_DEV_NAME "btfmslim"
+#endif
+
+// Todo protect with flags
+int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim);
+void btfm_slim_unregister_hwep(void);
+#endif /*__LINUX_BTFM_SLIM_HW_INTERFACE_H*/