Ver código fonte

Add 'qcom/opensource/bt-kernel/' from commit 'abeb53d57fb210fc51839143b6ce9292c595c424'

git-subtree-dir: qcom/opensource/bt-kernel
git-subtree-mainline: 91a891006189d1ffd4848c0827d60f5ba9f280ed
git-subtree-split: abeb53d57fb210fc51839143b6ce9292c595c424
Change-Id:
repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom-opensource/bt-kernel
tag: LA.VENDOR.14.3.0.r1-17300-lanai.QSSI15.0
David Wronek 5 meses atrás
pai
commit
7870029999
46 arquivos alterados com 13888 adições e 0 exclusões
  1. 103 0
      qcom/opensource/bt-kernel/Android.mk
  2. 17 0
      qcom/opensource/bt-kernel/BUILD.bazel
  3. 25 0
      qcom/opensource/bt-kernel/Kbuild
  4. 16 0
      qcom/opensource/bt-kernel/Makefile
  5. 95 0
      qcom/opensource/bt-kernel/bt_kernel.bzl
  6. 4 0
      qcom/opensource/bt-kernel/bt_kernel_product_board.mk
  7. 20 0
      qcom/opensource/bt-kernel/bt_kernel_vendor_board.mk
  8. 116 0
      qcom/opensource/bt-kernel/bt_modules.bzl
  9. 10 0
      qcom/opensource/bt-kernel/btfmcodec/Kconfig
  10. 4 0
      qcom/opensource/bt-kernel/btfmcodec/Makefile
  11. 731 0
      qcom/opensource/bt-kernel/btfmcodec/btfm_codec.c
  12. 326 0
      qcom/opensource/bt-kernel/btfmcodec/btfm_codec_btadv_interface.c
  13. 96 0
      qcom/opensource/bt-kernel/btfmcodec/btfm_codec_hw_interface.c
  14. 881 0
      qcom/opensource/bt-kernel/btfmcodec/btfm_codec_interface.c
  15. 108 0
      qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec.h
  16. 22 0
      qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_btadv_interface.h
  17. 116 0
      qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_hw_interface.h
  18. 12 0
      qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_interface.h
  19. 136 0
      qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_pkt.h
  20. 721 0
      qcom/opensource/bt-kernel/include/btpower.h
  21. 13 0
      qcom/opensource/bt-kernel/pwr/Kconfig
  22. 3 0
      qcom/opensource/bt-kernel/pwr/Makefile
  23. 2779 0
      qcom/opensource/bt-kernel/pwr/btpower.c
  24. 14 0
      qcom/opensource/bt-kernel/rtc6226/Kconfig
  25. 3 0
      qcom/opensource/bt-kernel/rtc6226/Makefile
  26. 2375 0
      qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-common.c
  27. 1016 0
      qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-i2c.c
  28. 700 0
      qcom/opensource/bt-kernel/rtc6226/radio-rtc6226.h
  29. 25 0
      qcom/opensource/bt-kernel/slimbus/Kconfig
  30. 8 0
      qcom/opensource/bt-kernel/slimbus/Makefile
  31. 750 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim.c
  32. 176 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim.h
  33. 466 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim_codec.c
  34. 546 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.c
  35. 43 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.h
  36. 187 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.c
  37. 187 0
      qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.h
  38. 12 0
      qcom/opensource/bt-kernel/soundwire/Kconfig
  39. 3 0
      qcom/opensource/bt-kernel/soundwire/Makefile
  40. 288 0
      qcom/opensource/bt-kernel/soundwire/btfm_swr.c
  41. 86 0
      qcom/opensource/bt-kernel/soundwire/btfm_swr.h
  42. 442 0
      qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.c
  43. 35 0
      qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.h
  44. 44 0
      qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.c
  45. 45 0
      qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.h
  46. 83 0
      qcom/opensource/bt-kernel/target.bzl

+ 103 - 0
qcom/opensource/bt-kernel/Android.mk

@@ -0,0 +1,103 @@
+# Android makefile for BT kernel modules
+
+LOCAL_PATH := $(call my-dir)
+
+# Build/Package only in case of supported target
+ifeq ($(call is-board-platform-in-list,taro kalama pineapple blair pitti volcano niobe anorak61), true)
+
+BT_SELECT := CONFIG_MSM_BT_POWER=m
+#ifdef CONFIG_SLIMBUS
+BT_SELECT += CONFIG_BTFM_SLIM=m
+#endif
+BT_SELECT += CONFIG_I2C_RTC6226_QCA=m
+
+ifeq ($(TARGET_KERNEL_DLKM_SECURE_MSM_OVERRIDE), true)
+ifeq ($(ENABLE_PERIPHERAL_STATE_UTILS), true)
+BT_SELECT += CONFIG_BT_HW_SECURE_DISABLE=y
+endif
+endif
+
+LOCAL_PATH := $(call my-dir)
+LOCAL_MODULE_DDK_BUILD := true
+LOCAL_MODULE_KO_DIRS := pwr/btpower.ko
+LOCAL_MODULE_KO_DIRS += slimbus/bt_fm_slim.ko
+LOCAL_MODULE_KO_DIRS += rtc6226/radio-i2c-rtc6226-qca.ko
+
+
+# This makefile is only for DLKM
+ifneq ($(findstring vendor,$(LOCAL_PATH)),)
+
+ifneq ($(findstring opensource,$(LOCAL_PATH)),)
+	BT_BLD_DIR := $(abspath .)/vendor/qcom/opensource/bt-kernel
+endif # opensource
+
+DLKM_DIR := $(TOP)/device/qcom/common/dlkm
+
+
+###########################################################
+# This is set once per LOCAL_PATH, not per (kernel) module
+KBUILD_OPTIONS := BT_KERNEL_ROOT=$(BT_BLD_DIR)
+KBUILD_OPTIONS += $(foreach bt_select, \
+       $(BT_SELECT), \
+       $(bt_select))
+BT_SRC_FILES := \
+	$(wildcard $(LOCAL_PATH)/*) \
+	$(wildcard $(LOCAL_PATH)/*/*) \
+
+ifeq ($(TARGET_KERNEL_DLKM_SECURE_MSM_OVERRIDE), true)
+ifeq ($(ENABLE_PERIPHERAL_STATE_UTILS), true)
+KBUILD_REQUIRED_KOS := smcinvoke_dlkm.ko
+endif
+endif
+
+
+# Module.symvers needs to be generated as a intermediate module so that
+# other modules which depend on BT platform modules can set local
+# dependencies to it.
+
+########################### Module.symvers ############################
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES           := $(BT_SRC_FILES)
+LOCAL_MODULE              := bt-kernel-module-symvers
+LOCAL_MODULE_STEM         := Module.symvers
+LOCAL_MODULE_KBUILD_NAME  := Module.symvers
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/Build_external_kernelmodule.mk
+
+# Below are for Android build system to recognize each module name, so
+# they can be installed properly. Since Kbuild is used to compile these
+# modules, invoking any of them will cause other modules to be compiled
+# as well if corresponding flags are added in KBUILD_OPTIONS from upper
+# level Makefiles.
+
+################################ pwr ################################
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES           := $(BT_SRC_FILES)
+LOCAL_MODULE              := btpower.ko
+LOCAL_MODULE_KBUILD_NAME  := pwr/btpower.ko
+LOCAL_MODULE_TAGS         := optional
+LOCAL_MODULE_DEBUG_ENABLE := true
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/Build_external_kernelmodule.mk
+################################ slimbus ################################
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES           := $(BT_SRC_FILES)
+LOCAL_MODULE              := bt_fm_slim.ko
+LOCAL_MODULE_KBUILD_NAME  := slimbus/bt_fm_slim.ko
+LOCAL_MODULE_TAGS         := optional
+LOCAL_MODULE_DEBUG_ENABLE := true
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/Build_external_kernelmodule.mk
+################################ rtc6226 ################################
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES           := $(BT_SRC_FILES)
+LOCAL_MODULE              := radio-i2c-rtc6226-qca.ko
+LOCAL_MODULE_KBUILD_NAME  := rtc6226/radio-i2c-rtc6226-qca.ko
+LOCAL_MODULE_TAGS         := optional
+LOCAL_MODULE_DEBUG_ENABLE := true
+LOCAL_MODULE_PATH         := $(KERNEL_MODULES_OUT)
+include $(DLKM_DIR)/Build_external_kernelmodule.mk
+###########################################################
+
+endif # DLKM check
+endif # supported target check

+ 17 - 0
qcom/opensource/bt-kernel/BUILD.bazel

@@ -0,0 +1,17 @@
+load("//build/kernel/kleaf:kernel.bzl", "ddk_headers")
+
+ddk_headers(
+    name = "btfmcodec_headers",
+    hdrs = glob([
+        "btfmcodec/include/*.h"
+    ]),
+    includes = ["btfmcodec/include"]
+)
+
+load(":target.bzl", "define_pineapple")
+
+define_pineapple()
+
+load(":target.bzl", "define_anorak61")
+
+define_anorak61()

+ 25 - 0
qcom/opensource/bt-kernel/Kbuild

@@ -0,0 +1,25 @@
+ifeq ($(CONFIG_MSM_BT_POWER),m)
+KBUILD_CPPFLAGS += -DCONFIG_MSM_BT_POWER
+endif
+
+ifeq ($(CONFIG_BTFM_SLIM),m)
+KBUILD_CPPFLAGS += -DCONFIG_BTFM_SLIM
+endif
+
+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
+
+ifeq ($(CONFIG_BT_HW_SECURE_DISABLE), y)
+KBUILD_CPPFLAGS += -DCONFIG_BT_HW_SECURE_DISABLE
+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/

+ 16 - 0
qcom/opensource/bt-kernel/Makefile

@@ -0,0 +1,16 @@
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+M=$(PWD)
+BT_ROOT=$(KERNEL_SRC)/$(M)
+
+KBUILD_OPTIONS+=  BT_ROOT=$(BT_ROOT)
+
+all:
+	$(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS)
+
+modules_install:
+	$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install
+
+clean:
+	$(MAKE) -C $(KERNEL_SRC) M=$(M) clean

+ 95 - 0
qcom/opensource/bt-kernel/bt_kernel.bzl

@@ -0,0 +1,95 @@
+load("//msm-kernel:target_variants.bzl", "get_all_variants")
+load("//build/kernel/kleaf:kernel.bzl", "ddk_module")
+load("//build/bazel_common_rules/dist:dist.bzl", "copy_to_dist_dir")
+load(":bt_modules.bzl", "bt_modules")
+
+def _get_config_choices(config_srcs, options):
+    choices = []
+
+    for option in config_srcs:
+        choices.extend(config_srcs[option].get(option in options, []))
+
+    return choices
+
+def _get_module_srcs(module, options):
+    """
+    Gets all the module sources, formats them with the path for that module
+    and then groups them together
+    It also includes all the headers within the `include` directory
+    `native.glob()` returns a new list with every file need for the current package
+    """
+    srcs = module.srcs + _get_config_choices(module.config_srcs, options)
+    return native.glob(
+        ["{}/{}".format(module.path, src) for src in srcs] + ["include/*.h"]
+    )
+
+def _get_module_deps(module, options, formatter):
+    """
+    Formats the dependent targets with the necessary prefix
+    Args:
+        module: kernel module
+        options: dependencies that rely on a config option
+        formatter: function that will replace the format string within `deps`
+    Example:
+        kernel build = "pineapple_gki"
+        dep = "%b_btpower"
+        The formatted string will look as follow
+        formatted_dep = formatter(dep) = "pineapple_gki_btpower"
+    """
+    deps = module.deps + _get_config_choices(module.config_deps, options)
+    return [formatter(dep) for dep in deps]
+
+def _get_build_options(modules, config_options):
+    all_options = {option: True for option in config_options}
+    all_options = all_options | {module.config_opt: True for module in modules if module.config_opt}
+
+    return all_options
+
+def define_target_variant_modules(target, variant, modules, config_options = []):
+    """
+    Generates the ddk_module for each of our kernel modules
+    Args:
+        target: either `pineapple` or `kalama`
+        variant: either `gki` or `consolidate`
+        modules: bt_modules dictionary defined in `bt_modules.bzl`
+        config_options: decides which kernel modules to build
+    """
+    kernel_build = "{}_{}".format(target, variant)
+    kernel_build_label = "//msm-kernel:{}".format(kernel_build)
+    modules = [bt_modules.get(module_name) for module_name in modules]
+    options = _get_build_options(modules, config_options)
+    formatter = lambda s : s.replace("%b", kernel_build)
+
+    all_modules = []
+    for module in modules:
+        rule_name = "{}_{}".format(kernel_build, module.name)
+        module_srcs = _get_module_srcs(module, options)
+
+        ddk_module(
+            name = rule_name,
+            kernel_build = kernel_build_label,
+            srcs = module_srcs,
+            out = "{}.ko".format(module.name),
+            deps = ["//msm-kernel:all_headers"] + _get_module_deps(module, options, formatter),
+            includes = ["include"],
+            local_defines = options.keys(),
+            visibility = ["//visibility:public"],
+        )
+
+        all_modules.append(rule_name)
+
+    copy_to_dist_dir(
+        name = "{}_bt-kernel_dist".format(kernel_build),
+        data = all_modules,
+        dist_dir = "out/target/product/{}/dlkm/lib/modules".format(target),
+        flat = True,
+        wipe_dist_dir = False,
+        allow_duplicate_filenames = False,
+        mode_overrides = {"**/*": "644"},
+        log = "info",
+    )
+
+def define_bt_modules(target, modules, config_options = []):
+    for (t, v) in get_all_variants():
+        if t == target:
+            define_target_variant_modules(t, v, modules, config_options)

+ 4 - 0
qcom/opensource/bt-kernel/bt_kernel_product_board.mk

@@ -0,0 +1,4 @@
+# Build BT kernel drivers
+PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/btpower.ko\
+	$(KERNEL_MODULES_OUT)/bt_fm_slim.ko \
+	$(KERNEL_MODULES_OUT)/radio-i2c-rtc6226-qca.ko

+ 20 - 0
qcom/opensource/bt-kernel/bt_kernel_vendor_board.mk

@@ -0,0 +1,20 @@
+# Build audio kernel driver
+ifneq ($(TARGET_BOARD_AUTO),true)
+ifeq ($(TARGET_USES_QMAA),true)
+  ifeq ($(TARGET_USES_QMAA_OVERRIDE_BLUETOOTH), true)
+     ifeq ($(call is-board-platform-in-list,$(TARGET_BOARD_PLATFORM)),true)
+           BT_KERNEL_DRIVER := $(KERNEL_MODULES_OUT)/btpower.ko\
+             $(KERNEL_MODULES_OUT)/bt_fm_slim.ko \
+             $(KERNEL_MODULES_OUT)/radio-i2c-rtc6226-qca.ko
+           BOARD_VENDOR_KERNEL_MODULES += $(BT_KERNEL_DRIVER)
+     endif
+  endif
+else
+  ifeq ($(call is-board-platform-in-list,$(TARGET_BOARD_PLATFORM)),true)
+        BT_KERNEL_DRIVER := $(KERNEL_MODULES_OUT)/btpower.ko\
+            $(KERNEL_MODULES_OUT)/bt_fm_slim.ko \
+            $(KERNEL_MODULES_OUT)/radio-i2c-rtc6226-qca.ko
+        BOARD_VENDOR_KERNEL_MODULES += $(BT_KERNEL_DRIVER)
+  endif
+endif
+endif

+ 116 - 0
qcom/opensource/bt-kernel/bt_modules.bzl

@@ -0,0 +1,116 @@
+PWR_PATH = "pwr"
+SLIMBUS_PATH = "slimbus"
+FMRTC_PATH = "rtc6226"
+BTFMCODEC_PATH = "btfmcodec"
+
+# This dictionary holds all the BT modules included in the bt-kernel
+bt_modules = {}
+
+def register_bt_modules(name, path = None, config_opt = None, srcs = [], config_srcs = {}, deps = [], config_deps = {}):
+    """
+    Register modules
+    Args:
+        name: Name of the module (which will be used to generate the name of the .ko file)
+        path: Path in which the source files can be found
+        config_opt: Config name used in Kconfig (not needed currently)
+        srcs: source files and local headers
+        config_srcs: source files and local headers that depend on a config define being enabled.
+        deps: a list of dependent targets
+        config_deps: a list of dependent targets that depend on a config define being enabled.
+    """
+    processed_config_srcs = {}
+    processed_config_deps = {}
+
+    for config_src_name in config_srcs:
+        config_src = config_srcs[config_src_name]
+
+        if type(config_src) == "list":
+            processed_config_srcs[config_src_name] = {True: config_src}
+        else:
+            processed_config_srcs[config_src_name] = config_src
+
+    for config_deps_name in config_deps:
+        config_dep = config_deps[config_deps_name]
+
+        if type(config_dep) == "list":
+            processed_config_deps[config_deps_name] = {True: config_dep}
+        else:
+            processed_config_deps[config_deps_name] = config_dep
+
+    module = struct(
+        name = name,
+        path = path,
+        srcs = srcs,
+        config_srcs = processed_config_srcs,
+        config_opt = config_opt,
+        deps = deps,
+        config_deps = processed_config_deps,
+    )
+    bt_modules[name] = module
+
+# --- BT Modules ---
+
+register_bt_modules(
+    name = "btpower",
+    path = PWR_PATH,
+    config_opt = "CONFIG_MSM_BT_POWER",
+    srcs = ["btpower.c"],
+    config_deps = {
+        "CONFIG_BT_HW_SECURE_DISABLE": [
+            "//vendor/qcom/opensource/securemsm-kernel:%b_smcinvoke_dlkm",
+        ]
+    },
+)
+
+register_bt_modules(
+    name = "bt_fm_slim",
+    path = SLIMBUS_PATH,
+    # config_opt = "CONFIG_BTFM_SLIM",
+    srcs = [
+        "btfm_slim.c",
+        "btfm_slim.h",
+        "btfm_slim_slave.c",
+        "btfm_slim_slave.h",
+        "btfm_slim_codec.c",
+    ],
+    deps = [":%b_btpower"],
+)
+
+register_bt_modules(
+    name = "btfm_slim_codec",
+    path = SLIMBUS_PATH,
+    config_opt = "CONFIG_SLIM_BTFM_CODEC",
+    srcs = [
+        "btfm_slim.c",
+        "btfm_slim.h",
+        "btfm_slim_slave.c",
+        "btfm_slim_slave.h",
+        "btfm_slim_hw_interface.c",
+        "btfm_slim_hw_interface.h",
+    ],
+    deps = [":%b_btpower", ":%b_btfmcodec", ":btfmcodec_headers"],
+)
+
+register_bt_modules(
+   name = "btfmcodec",
+   path = BTFMCODEC_PATH,
+   config_opt = "CONFIG_BTFM_CODEC",
+   srcs = [
+        "btfm_codec.c",
+        "btfm_codec_btadv_interface.c",
+	"btfm_codec_hw_interface.c",
+	"btfm_codec_interface.c",
+	],
+   deps = [":btfmcodec_headers"],
+)
+
+register_bt_modules(
+    name = "radio-i2c-rtc6226-qca",
+    path = FMRTC_PATH,
+    config_opt = "CONFIG_I2C_RTC6226_QCA",
+    srcs = [
+        "radio-rtc6226-common.c",
+        "radio-rtc6226-i2c.c",
+        "radio-rtc6226.h",
+    ],
+)

+ 10 - 0
qcom/opensource/bt-kernel/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
qcom/opensource/bt-kernel/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 btfm_codec_btadv_interface.o
+obj-$(CONFIG_BTFM_CODEC) += btfmcodec.o

+ 731 - 0
qcom/opensource/bt-kernel/btfmcodec/btfm_codec.c

@@ -0,0 +1,731 @@
+// 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_pkt.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;
+bool is_cp_supported = true;
+
+#define cdev_to_btfmchardev(_cdev) container_of(_cdev, struct btfmcodec_char_device, cdev)
+#define MIN_PKT_LEN  0x9
+
+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;
+	}
+}
+
+/*
+ * btfmcodec_dev_open() - open() syscall for the btfmcodec dev node
+ * inode:	Pointer to the inode structure.
+ * file:	Pointer to the file structure.
+ *
+ * This function is used to open the btfmcodec char device when
+ * userspace client do a open() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ * Note: btfmcodec dev node works on nonblocking mode.
+ */
+static int btfmcodec_dev_open(struct inode *inode, struct file *file)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = cdev_to_btfmchardev(inode->i_cdev);
+	struct btfmcodec_data *btfmcodec = (struct btfmcodec_data *)btfmcodec_dev->btfmcodec;
+	unsigned int active_clients = refcount_read(&btfmcodec_dev->active_clients);
+
+	btfmcodec->states.current_state = IDLE; /* Just a temp*/
+	btfmcodec->states.next_state = IDLE;
+	BTFMCODEC_INFO("for %s by %s:%d active_clients[%d]\n",
+		       btfmcodec_dev->dev_name, current->comm,
+		       task_pid_nr(current), refcount_read(&btfmcodec_dev->active_clients));
+	/* Don't allow a new client if already one is active. */
+	if (active_clients > 1) {
+		BTFMCODEC_WARN("%s: Not honoring open as other client is active", __func__);
+		return EACCES;
+	}
+	/* for now have btfmcodec and later we can think of having it btfmcodec_dev */
+	file->private_data = btfmcodec;
+	refcount_inc(&btfmcodec_dev->active_clients);
+	return 0;
+}
+
+/*
+ * btfmcodec_pkt_release() - release operation on btfmcodec device
+ * inode:	Pointer to the inode structure.
+ * file:	Pointer to the file structure.
+ *
+ * This function is used to release the btfmcodec dev node when
+ * userspace client do a close() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+static int btfmcodec_dev_release(struct inode *inode, struct file *file)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = cdev_to_btfmchardev(inode->i_cdev);
+	unsigned long flags;
+	int idx;
+
+	BTFMCODEC_INFO("for %s by %s:%d active_clients[%u]\n",
+		       btfmcodec_dev->dev_name, current->comm,
+		       task_pid_nr(current), refcount_read(&btfmcodec_dev->active_clients));
+
+	refcount_dec(&btfmcodec_dev->active_clients);
+	if (refcount_read(&btfmcodec_dev->active_clients) == 1) {
+		spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
+		skb_queue_purge(&btfmcodec_dev->txq);
+		/* Wakeup the device if waiting for the data */
+		wake_up_interruptible(&btfmcodec_dev->readq);
+		spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+		/* we need to have separte rx lock for below buff */
+		skb_queue_purge(&btfmcodec_dev->rxq);
+	}
+
+	/* Notify waiting clients that client is closed or killed */
+	for (idx = 0; idx < BTM_PKT_TYPE_MAX; idx++) {
+		btfmcodec_dev->status[idx] = BTM_RSP_NOT_RECV_CLIENT_KILLED;
+		wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+	}
+
+	if (btfmcodec_dev->wq_hwep_shutdown.func)
+		cancel_work_sync(&btfmcodec_dev->wq_hwep_shutdown);
+	if (btfmcodec_dev->wq_hwep_configure.func)
+		cancel_work_sync(&btfmcodec_dev->wq_hwep_configure);
+	if (btfmcodec_dev->wq_prepare_bearer.func)
+		cancel_work_sync(&btfmcodec_dev->wq_prepare_bearer);
+
+	btfmcodec->states.current_state = IDLE;
+	btfmcodec->states.next_state = IDLE;
+	return 0;
+}
+
+btm_opcode STREAM_TO_UINT32 (struct sk_buff *skb)
+{
+	return (skb->data[0] | (skb->data[1] << 8) |
+		(skb->data[2] << 16) | (skb->data[3] << 24));
+}
+
+static void btfmcodec_dev_rxwork(struct work_struct *work)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = container_of(work, struct btfmcodec_char_device, rx_work);
+	struct sk_buff *skb;
+	uint32_t len;
+	uint8_t status;
+	int idx;
+	uint8_t *bearer_switch_ind;
+
+	BTFMCODEC_DBG("start");
+	while ((skb = skb_dequeue(&btfmcodec_dev->rxq))) {
+		btm_opcode opcode = STREAM_TO_UINT32(skb);
+		skb_pull(skb, sizeof(btm_opcode));
+		len = STREAM_TO_UINT32(skb);
+		skb_pull(skb, sizeof(len));
+		switch (opcode) {
+		case BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_REQ:
+			idx = BTM_PKT_TYPE_PREPARE_REQ;
+			BTFMCODEC_DBG("BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_REQ");
+			if (len == BTM_PREPARE_AUDIO_BEARER_SWITCH_REQ_LEN) {
+				/* there are chances where bearer indication is not recevied,
+				 * So inform waiting thread to unblock itself and move to
+				 * previous state.
+				 */
+				if (btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND] == BTM_WAITING_RSP) {
+				  BTFMCODEC_DBG("Notifying waiting beare indications");
+				  btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND] = BTM_FAIL_RESP_RECV;
+				  wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_BEARER_SWITCH_IND]);
+				}
+				btfmcodec_dev->status[idx] = skb->data[0];
+				/* Reset bearer switch ind flag */
+				bearer_switch_ind =
+					&btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND];
+				*bearer_switch_ind = BTM_WAITING_RSP;
+				queue_work(btfmcodec_dev->workqueue, &btfmcodec_dev->wq_prepare_bearer);
+			} else {
+				BTFMCODEC_ERR("wrong packet format with len:%d", len);
+			}
+			break;
+		case BTM_BTFMCODEC_MASTER_CONFIG_RSP:
+			idx = BTM_PKT_TYPE_MASTER_CONFIG_RSP;
+			if (len == BTM_MASTER_CONFIG_RSP_LEN) {
+				status = skb->data[1];
+				if (status == MSG_SUCCESS)
+				  btfmcodec_dev->status[idx] = BTM_RSP_RECV;
+				else
+				  btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			} else {
+				BTFMCODEC_ERR("wrong packet format with len:%d", len);
+				btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			}
+			BTFMCODEC_INFO("Rx BTM_BTFMCODEC_MASTER_CONFIG_RSP status:%d",
+				status);
+			wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+			break;
+		case BTM_BTFMCODEC_CODEC_CONFIG_DMA_RSP:
+			idx = BTM_PKT_TYPE_DMA_CONFIG_RSP;
+			if (len == BTM_CODEC_CONFIG_DMA_RSP_LEN) {
+				status = skb->data[1];
+				if (status == MSG_SUCCESS)
+					btfmcodec_dev->status[idx] = BTM_RSP_RECV;
+				else
+					btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			} else {
+				BTFMCODEC_ERR("wrong packet format with len:%d", len);
+				btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			}
+			BTFMCODEC_INFO("Rx BTM_BTFMCODEC_CODEC_CONFIG_DMA_RSP status:%d",
+				status);
+			wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+			break;
+		case BTM_BTFMCODEC_CTRL_MASTER_SHUTDOWN_RSP:
+			idx = BTM_PKT_TYPE_MASTER_SHUTDOWN_RSP;
+			if (len == BTM_MASTER_CONFIG_RSP_LEN) {
+				status = skb->data[1];
+				if (status == MSG_SUCCESS)
+				  btfmcodec_dev->status[idx] = BTM_RSP_RECV;
+				else
+				  btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			} else {
+				BTFMCODEC_ERR("wrong packet format with len:%d", len);
+				btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			}
+			BTFMCODEC_INFO("Rx BTM_BTFMCODEC_CTRL_MASTER_SHUTDOWN_RSP status:%d",
+				status);
+			wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+			BTFMCODEC_INFO("%s: waiting to cancel prepare bearer wq", __func__);
+			cancel_work_sync(&btfmcodec_dev->wq_prepare_bearer);
+			BTFMCODEC_INFO("%s: prepare bearer wq canceled", __func__);
+			break;
+		case BTM_BTFMCODEC_BEARER_SWITCH_IND:
+			idx = BTM_PKT_TYPE_BEARER_SWITCH_IND;
+			if (len == BTM_BEARER_SWITCH_IND_LEN) {
+				status = skb->data[0];
+				if (status == MSG_SUCCESS)
+				  btfmcodec_dev->status[idx] = BTM_RSP_RECV;
+				else
+				  btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			} else {
+				BTFMCODEC_ERR("wrong packet format with len:%d", len);
+				btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+			}
+			BTFMCODEC_INFO("Rx BTM_BTFMCODEC_BEARER_SWITCH_IND status:%d",
+				status);
+			wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+			break;
+		case BTM_BTFMCODEC_CTRL_LOG_LVL_IND:
+			if (len == BTM_LOG_LVL_IND_LEN) {
+				log_lvl = skb->data[0];
+			} else {
+				BTFMCODEC_ERR("wrong packet format with len:%d", len);
+			}
+			BTFMCODEC_INFO("Rx BTM_BTFMCODEC_CTRL_LOG_LVL_IND status:%d",
+					log_lvl);
+			wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+			break;
+		default:
+			BTFMCODEC_ERR("wrong opcode:%08x", opcode);
+		}
+		kfree_skb(skb);
+	}
+	BTFMCODEC_DBG("end");
+}
+
+/*
+ * btfmcodec_pkt_write() - write() syscall for the btfmcodec_pkt device
+ * file:	Pointer to the file structure.
+ * buf:		Pointer to the userspace buffer.
+ * count:	Number bytes to read from the file.
+ * ppos:	Pointer to the position into the file.
+ *
+ * This function is used to write the data to btfmcodec dev node when
+ * userspace client do a write() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+static ssize_t btfmcodec_dev_write(struct file *file,
+			const char __user *buf, size_t count, loff_t *ppos)
+{
+	struct btfmcodec_data *btfmcodec = file->private_data;
+	struct btfmcodec_char_device *btfmcodec_dev= NULL;
+	struct sk_buff *skb;
+	void *kbuf;
+	int ret =  0;
+
+	if (!btfmcodec || !btfmcodec->btfmcodec_dev || refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1) {
+		BTFMCODEC_INFO("%s: %d\n", current->comm, task_pid_nr(current));
+		return -EINVAL;
+	} else {
+		btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	}
+
+	if (mutex_lock_interruptible(&btfmcodec_dev->lock)) {
+		ret = -ERESTARTSYS;
+		goto free_kbuf;
+	}
+
+	/* Hack for Now */
+	if (count < MIN_PKT_LEN) {
+		BTFMCODEC_ERR("minimum packet len should be greater than 3 bytes");
+		goto free_kbuf;
+	}
+	if (refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 0) {
+		BTFMCODEC_WARN("Client disconnected");
+		ret = -ENETRESET;
+		goto free_kbuf;
+	}
+
+	BTFMCODEC_DBG("begin to %s buffer_size %zu\n", btfmcodec_dev->dev_name, count);
+	kbuf = memdup_user(buf, count);
+	if (IS_ERR(kbuf)) {
+		ret = PTR_ERR(kbuf);
+		goto free_kbuf;
+	}
+
+	/* Check whether we need a dedicated chunk of memory for this driver */
+	skb = alloc_skb(count* sizeof(size_t), GFP_KERNEL);
+	if (!skb) {
+		BTFMCODEC_ERR("failed to allocate memory for recevied packet");
+		ret = -ENOMEM;
+		goto free_kbuf;
+	}
+
+	skb_put_data(skb, (uint8_t *)kbuf, count);
+	skb_queue_tail(&btfmcodec_dev->rxq, skb);
+	schedule_work(&btfmcodec_dev->rx_work);
+
+	kfree(kbuf);
+
+free_kbuf:
+	mutex_unlock(&btfmcodec_dev->lock);
+	BTFMCODEC_DBG("finish to %s ret %d\n", btfmcodec_dev->dev_name, ret);
+	return ret < 0 ? ret : count;
+}
+
+int btfmcodec_dev_enqueue_pkt(struct btfmcodec_char_device *btfmcodec_dev, void *buf, int len)
+{
+	struct sk_buff *skb;
+	unsigned long flags;
+	uint8_t *cmd = buf;
+
+	BTFMCODEC_DBG("start");
+	spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
+	if (refcount_read(&btfmcodec_dev->active_clients) == 1) {
+		BTFMCODEC_WARN("no active clients discarding the packet");
+		spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+		return -EINVAL;
+	}
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb) {
+		BTFMCODEC_ERR("failed to allocate memory");
+		spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+		return -ENOMEM;
+	}
+
+	skb_put_data(skb, cmd, len);
+	skb_queue_tail(&btfmcodec_dev->txq, skb);
+	wake_up_interruptible(&btfmcodec_dev->readq);
+	spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+	BTFMCODEC_DBG("end");
+	return 0;
+}
+
+/*
+ * btfmcodec_pkt_poll() - poll() syscall for the btfmcodec device
+ * file:	Pointer to the file structure.
+ * wait:	pointer to Poll table.
+ *
+ * This function is used to poll on the btfmcodec dev node when
+ * userspace client do a poll() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+static __poll_t btfmcodec_dev_poll(struct file *file, poll_table *wait)
+{
+	struct btfmcodec_data *btfmcodec = file->private_data;
+	struct btfmcodec_char_device *btfmcodec_dev= NULL;
+	__poll_t mask = 0;
+	unsigned long flags;
+
+	BTFMCODEC_DBG("start");
+	if (!btfmcodec || !btfmcodec->btfmcodec_dev || refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1) {
+		BTFMCODEC_INFO("%s: %d\n", current->comm, task_pid_nr(current));
+		return -EINVAL;
+	} else {
+		btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	}
+
+	/* Wait here for timeout or for a wakeup signal on readq */
+	poll_wait(file, &btfmcodec_dev->readq, wait);
+
+	mutex_lock(&btfmcodec_dev->lock);
+	/* recheck if the client has released by the driver */
+	if (refcount_read(&btfmcodec_dev->active_clients) == 1) {
+		BTFMCODEC_WARN("port has been closed already");
+		mutex_unlock(&btfmcodec_dev->lock);
+		return POLLHUP;
+	}
+
+	spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
+	/* Set flags if data is avilable to read */
+	if (!skb_queue_empty(&btfmcodec_dev->txq))
+		mask |= POLLIN | POLLRDNORM;
+
+	spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+	mutex_unlock(&btfmcodec_dev->lock);
+
+	BTFMCODEC_DBG("end with reason %d", mask);
+	return mask;
+}
+
+/*
+ * btfmcodec_dev_read() - read() syscall for the btfmcodec dev node
+ * file:	Pointer to the file structure.
+ * buf:		Pointer to the userspace buffer.
+ * count:	Number bytes to read from the file.
+ * ppos:	Pointer to the position into the file.
+ *
+ * This function is used to Read the data from btfmcodec pkt device when
+ * userspace client do a read() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+static ssize_t btfmcodec_dev_read(struct file *file,
+			char __user *buf, size_t count, loff_t *ppos)
+{
+	struct btfmcodec_data *btfmcodec = file->private_data;
+	struct btfmcodec_char_device *btfmcodec_dev= NULL;
+	unsigned long flags;
+	struct sk_buff *skb;
+	int use;
+
+	BTFMCODEC_DBG("start");
+	if (!btfmcodec || !btfmcodec->btfmcodec_dev || refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1) {
+		BTFMCODEC_INFO("%s: %d\n", current->comm, task_pid_nr(current));
+		return -EINVAL;
+	} else {
+		btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	}
+
+	spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
+	/* Wait for data in the queue */
+	if (skb_queue_empty(&btfmcodec_dev->txq)) {
+		spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		/* Wait until we get data*/
+		if (wait_event_interruptible(btfmcodec_dev->readq,
+					     !skb_queue_empty(&btfmcodec_dev->txq)))
+			return -ERESTARTSYS;
+
+		/* We lost the client while waiting */
+		if (refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1)
+			return -ENETRESET;
+
+		spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
+	}
+
+	skb = skb_dequeue(&btfmcodec_dev->txq);
+	spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
+	if (!skb)
+		return -EFAULT;
+
+	use = min_t(size_t, count, skb->len);
+	if (copy_to_user(buf, skb->data, use))
+		use = -EFAULT;
+
+	kfree_skb(skb);
+
+	BTFMCODEC_DBG("end for %s by %s:%d ret[%d]\n", btfmcodec_dev->dev_name,
+		       current->comm, task_pid_nr(current), use);
+
+	return use;
+}
+
+bool isCpSupported(void)
+{
+	return is_cp_supported;
+}
+
+static long btfmcodec_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct btfmcodec_data *btfmcodec = file->private_data;
+	struct hwep_data *hwep_info;
+
+	BTFMCODEC_INFO("%s: command %04x", __func__, cmd);
+
+	mutex_lock(&btfmcodec->hwep_drv_lock);
+	hwep_info = btfmcodec->hwep_info;
+	if (!hwep_info) {
+		BTFMCODEC_WARN("%s: HWEP is not registered with btfmcodec", __func__);
+		BTFMCODEC_WARN("%s: caching required info", __func__);
+		is_cp_supported = ((int)arg == 1) ? true : false;
+		mutex_unlock(&btfmcodec->hwep_drv_lock);
+		return 0;
+	}
+
+	mutex_unlock(&btfmcodec->hwep_drv_lock);
+	switch (cmd) {
+	case BTM_CP_UPDATE: {
+		if ((int)arg == 1) {
+			if (!strcmp(hwep_info->driver_name, "btfmslim"))
+				set_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags);
+			else if (!strcmp(hwep_info->driver_name, "btfmswr_slave"))
+				set_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags);
+			BTFMCODEC_INFO("%s: This target support CP hwep %s",
+					__func__, hwep_info->driver_name);
+		} else {
+			clear_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags);
+			clear_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags);
+			BTFMCODEC_INFO("%s: This target support doesn't CP", __func__);
+		}
+
+	BTFMCODEC_INFO("%s: mastr %d dma codec %d", __func__,
+			(int)test_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags),
+			(int)test_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags));
+		break;
+	} default: {
+		BTFMCODEC_ERR("%s unhandled cmd %04x", __func__, cmd);
+	}
+	}
+
+	return 0;
+}
+static const struct file_operations btfmcodec_fops = {
+	.owner = THIS_MODULE,
+	.open = btfmcodec_dev_open,
+	.release = btfmcodec_dev_release,
+	.write = btfmcodec_dev_write,
+	.poll = btfmcodec_dev_poll,
+	.read = btfmcodec_dev_read,
+	/* For Now add no hookups for below callbacks */
+	.unlocked_ioctl = btfmcodec_ioctl,
+	.compat_ioctl = btfmcodec_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);
+		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_get_current_transport *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, i;
+
+	BTFMCODEC_INFO("starting up the module");
+	btfmcodec = kzalloc(sizeof(struct btfmcodec_data), GFP_KERNEL);
+	if (!btfmcodec) {
+		BTFMCODEC_ERR("failed to allocate memory");
+		return -ENOMEM;
+	}
+
+	mutex_init(&btfmcodec->hwep_drv_lock);
+	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;
+	btfmcodec_dev->btfmcodec = (struct btfmcodec_data *)btfmcodec;
+	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);
+
+	skb_queue_head_init(&btfmcodec_dev->rxq);
+	mutex_init(&btfmcodec_dev->lock);
+	INIT_WORK(&btfmcodec_dev->rx_work, btfmcodec_dev_rxwork);
+	init_waitqueue_head(&btfmcodec_dev->readq);
+	spin_lock_init(&btfmcodec_dev->tx_queue_lock);
+	skb_queue_head_init(&btfmcodec_dev->txq);
+	INIT_LIST_HEAD(&btfmcodec->config_head);
+	for (i = 0; i < BTM_PKT_TYPE_MAX; i++) {
+		init_waitqueue_head(&btfmcodec_dev->rsp_wait_q[i]);
+	}
+	mutex_init(&states->state_machine_lock);
+	btfmcodec_dev->workqueue = alloc_ordered_workqueue("btfmcodec_wq", 0);
+	if (!btfmcodec_dev->workqueue) {
+		BTFMCODEC_ERR("btfmcodec_dev Workqueue not initialized properly");
+		ret = -ENOMEM;
+		goto free_device;
+	}
+	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("%s: cleaning up btfm codec driver", __func__);
+	if (!btfmcodec) {
+		BTFMCODEC_ERR("%s: 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("%s: skiping device node cleanup", __func__);
+		goto info_cleanup;
+	}
+
+	btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	skb_queue_purge(&btfmcodec_dev->rxq);
+	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("%s: 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);

+ 326 - 0
qcom/opensource/bt-kernel/btfmcodec/btfm_codec_btadv_interface.c

@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include "btfm_codec.h"
+#include "btfm_codec_pkt.h"
+#include "btfm_codec_btadv_interface.h"
+
+void btfmcodec_initiate_hwep_shutdown(struct btfmcodec_char_device *btfmcodec_dev)
+{
+	wait_queue_head_t *rsp_wait_q =
+		&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_HWEP_SHUTDOWN];
+	int ret;
+	uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_HWEP_SHUTDOWN];
+
+	*status = BTM_WAITING_RSP;
+	BTFMCODEC_INFO("queuing work to shutdown");
+	schedule_work(&btfmcodec_dev->wq_hwep_shutdown);
+	BTFMCODEC_INFO("waiting here for shutdown");
+	ret = wait_event_interruptible_timeout(*rsp_wait_q,
+		*status != BTM_WAITING_RSP,
+		msecs_to_jiffies(BTM_MASTER_CONFIG_RSP_TIMEOUT));
+
+	/* Rethink of having a new packet to notify transport switch error */
+	if (ret == 0) {
+		BTFMCODEC_ERR("failed to recevie to complete hwep_shutdown");
+		flush_work(&btfmcodec_dev->wq_hwep_shutdown);
+	} else {
+		if (*status == BTM_RSP_RECV) {
+			BTFMCODEC_ERR("sucessfully closed hwep");
+			return;
+		} else if (*status == BTM_FAIL_RESP_RECV ||
+			   *status == BTM_RSP_NOT_RECV_CLIENT_KILLED) {
+			BTFMCODEC_ERR("Failed to close hwep");
+			return;
+		}
+	}
+}
+
+void btfmcodec_move_to_next_state(struct btfmcodec_state_machine *state)
+{
+	mutex_lock(&state->state_machine_lock);
+	if (state->current_state == BT_Connecting ||
+	    state->current_state == BTADV_AUDIO_Connecting) {
+		state->current_state += 1;
+		BTFMCODEC_INFO("moving from %s to %s",
+				coverttostring(state->current_state -1 ),
+				coverttostring(state->current_state));
+	} else {
+		BTFMCODEC_ERR("State machine might have gone bad. reseting to default");
+		state->current_state = IDLE;
+	}
+
+	state->prev_state = IDLE;
+	mutex_unlock(&state->state_machine_lock);
+}
+
+void btfmcodec_revert_current_state(struct btfmcodec_state_machine *state)
+{
+	mutex_lock(&state->state_machine_lock);
+	BTFMCODEC_INFO("reverting from %s to %s", coverttostring(state->current_state),
+					coverttostring(state->prev_state));
+	state->current_state = state->prev_state;
+	state->prev_state = IDLE;
+	mutex_unlock(&state->state_machine_lock);
+}
+
+void btfmcodec_set_current_state(struct btfmcodec_state_machine *state,
+		btfmcodec_state current_state)
+{
+	mutex_lock(&state->state_machine_lock);
+	BTFMCODEC_INFO("moving from %s to %s", coverttostring(state->current_state),
+					coverttostring(current_state));
+	state->prev_state = state->current_state;
+	state->current_state = current_state;
+	mutex_unlock(&state->state_machine_lock);
+}
+
+btfmcodec_state btfmcodec_get_current_transport(struct
+					btfmcodec_state_machine *state)
+{
+	btfmcodec_state current_state;
+
+	mutex_lock(&state->state_machine_lock);
+	current_state = state->current_state;
+	mutex_unlock(&state->state_machine_lock);
+	return current_state;
+}
+
+int btfmcodec_frame_transport_switch_ind_pkt(struct btfmcodec_char_device *btfmcodec_dev,
+					uint8_t active_transport,
+					uint8_t status)
+{
+	struct btm_ctrl_pkt rsp;
+
+	rsp.opcode = BTM_BTFMCODEC_TRANSPORT_SWITCH_FAILED_IND;
+	rsp.len = BTM_PREPARE_AUDIO_BEARER_SWITCH_RSP_LEN;
+	rsp.active_transport = active_transport;
+	rsp.status = status;
+	return btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &rsp, (rsp.len +
+					BTM_HEADER_LEN));
+}
+
+int btfmcodec_frame_prepare_bearer_rsp_pkt(struct btfmcodec_char_device *btfmcodec_dev,
+					uint8_t active_transport,
+					uint8_t status)
+{
+	struct btm_ctrl_pkt rsp;
+
+	rsp.opcode = BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_RSP;
+	rsp.len =BTM_PREPARE_AUDIO_BEARER_SWITCH_RSP_LEN;
+	rsp.active_transport = active_transport;
+	rsp.status = status;
+	return btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &rsp, (rsp.len +
+					BTM_HEADER_LEN));
+}
+
+int btfmcodec_wait_for_bearer_ind(struct btfmcodec_char_device *btfmcodec_dev)
+{
+	wait_queue_head_t *rsp_wait_q =
+		&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_BEARER_SWITCH_IND];
+	int ret;
+	uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND];
+
+	ret = wait_event_interruptible_timeout(*rsp_wait_q,
+		*status != BTM_WAITING_RSP,
+		msecs_to_jiffies(BTM_MASTER_CONFIG_RSP_TIMEOUT));
+
+	if (ret == 0) {
+		BTFMCODEC_ERR("failed to recevie BTM_BEARER_SWITCH_IND");
+		ret = -MSG_INTERNAL_TIMEOUT;
+	} else {
+		if (*status == BTM_RSP_RECV) {
+			ret = 0;
+		} else if (*status == BTM_FAIL_RESP_RECV) {
+			BTFMCODEC_ERR("Rx BTM_BEARER_SWITCH_IND with failure status");
+			ret = -1;
+		} else if (*status == BTM_RSP_NOT_RECV_CLIENT_KILLED) {
+			BTFMCODEC_ERR("client killed so moving further");
+			ret = -1;
+		}
+	}
+
+	return ret;
+}
+
+int btfmcodec_initiate_hwep_configuration(struct btfmcodec_char_device *btfmcodec_dev)
+{
+	wait_queue_head_t *rsp_wait_q =
+		&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_HWEP_CONFIG];
+	uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_HWEP_CONFIG];
+	int ret;
+
+	schedule_work(&btfmcodec_dev->wq_hwep_configure);
+
+	*status = BTM_WAITING_RSP;
+	ret = wait_event_interruptible_timeout(*rsp_wait_q,
+		*status != BTM_WAITING_RSP,
+		msecs_to_jiffies(BTM_MASTER_CONFIG_RSP_TIMEOUT));
+
+	if (ret == 0) {
+		BTFMCODEC_ERR("failed to recevie complete hwep configure");
+		flush_work(&btfmcodec_dev->wq_hwep_configure);
+		ret = -1;
+	} else {
+		if (*status == BTM_RSP_RECV) {
+			ret = 0;
+		} else if (*status == BTM_FAIL_RESP_RECV ||
+			   *status == BTM_RSP_NOT_RECV_CLIENT_KILLED) {
+			BTFMCODEC_ERR("Failed to close hwep moving back to previous state");
+			ret = -1;
+		}
+	}
+
+	return ret;
+}
+
+void btfmcodec_configure_hwep(struct btfmcodec_char_device *btfmcodec_dev)
+{
+	struct btfmcodec_data *btfmcodec = (struct btfmcodec_data *)btfmcodec_dev->btfmcodec;
+	struct btfmcodec_state_machine *state = &btfmcodec->states;
+	int ret;
+	uint8_t status = MSG_SUCCESS;
+
+	ret = btfmcodec_initiate_hwep_configuration(btfmcodec_dev);
+	if (ret < 0) {
+		status = MSG_FAILED_TO_CONFIGURE_HWEP;
+		/* Move back to BTADV_AUDIO_Connected  from BT_Connecting */
+		btfmcodec_revert_current_state(state);
+	}
+
+	ret = btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev,
+		btfmcodec_get_current_transport(state), status);
+
+	if (status != MSG_SUCCESS)
+		return;
+
+	if (ret < 0) {
+		ret = btfmcodec_wait_for_bearer_ind(btfmcodec_dev);
+		if (ret < 0) {
+			/* Move back to BTADV_AUDIO_Connected for failure cases*/
+			BTFMCODEC_ERR("moving back to previous state");
+			btfmcodec_revert_current_state(state);
+			/* close HWEP */
+			btfmcodec_initiate_hwep_shutdown(btfmcodec_dev);
+			if (ret == -MSG_INTERNAL_TIMEOUT) {
+				btfmcodec_frame_transport_switch_ind_pkt(btfmcodec_dev, BT,
+							MSG_INTERNAL_TIMEOUT);
+			}
+		} else {
+			/* move from BT_Connecting to BT_Connected */
+			btfmcodec_move_to_next_state(state);
+		}
+	} else {
+		/* add code to handle packet errors */
+	}
+}
+
+void btfmcodec_prepare_bearer(struct btfmcodec_char_device *btfmcodec_dev,
+		enum transport_type new_transport)
+{
+	struct btfmcodec_data *btfmcodec = (struct btfmcodec_data *)btfmcodec_dev->btfmcodec;
+	struct btfmcodec_state_machine *state = &btfmcodec->states;
+	btfmcodec_state current_state;
+	int ret = -1;
+
+	if (new_transport > (ARRAY_SIZE(transport_type_text))) {
+		btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev,
+			(uint8_t) btfmcodec_get_current_transport(state),
+			MSG_WRONG_TRANSPORT_TYPE);
+		return;
+	}
+
+	BTFMCODEC_INFO("Rx to switch from transport type %s to %s",
+		coverttostring(btfmcodec_get_current_transport(state)),
+		transport_type_text[new_transport - 1]);
+
+	current_state = btfmcodec_get_current_transport(state);
+	if (new_transport == BT) {
+		/* If BT is already active. send +ve ack to BTADV Audio Manager */
+		if (current_state == BT_Connected ||
+		    current_state == BT_Connecting) {
+			btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev,
+				(uint8_t)current_state, MSG_SUCCESS);
+			return;
+		} else if (current_state == BTADV_AUDIO_Connected ||
+			   current_state == BTADV_AUDIO_Connecting||
+			   current_state == IDLE) {
+			if (btfmcodec_is_valid_cache_avb(btfmcodec)) {
+				BTFMCODEC_INFO("detected BTADV audio Gaming usecase to BT usecase");
+				btfmcodec_set_current_state(state, BT_Connecting);
+				btfmcodec_configure_hwep(btfmcodec_dev);
+			} else {
+				if (current_state != IDLE)
+					BTFMCODEC_INFO("detected BTADV Audio lossless to IDLE");
+				BTFMCODEC_INFO("moving to IDLE as no config available");
+				btfmcodec_set_current_state(state, IDLE);
+				btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev,
+					btfmcodec_get_current_transport(state),
+					MSG_SUCCESS);
+				/* No need wait for bearer switch indications as BTFMCODEC
+				 * driver doesn't have configs to configure
+				 */
+			}
+		}
+	} else if(new_transport == BTADV) {
+		/* If BTADV audio is already active. send +ve ack to BTADV audio Manager */
+		if (current_state == BTADV_AUDIO_Connecting ||
+		    current_state == BTADV_AUDIO_Connected) {
+			btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev,
+				(uint8_t)current_state, MSG_SUCCESS);
+			return;
+		} else {
+			btfmcodec_set_current_state(state, BTADV_AUDIO_Connecting);
+			if (btfmcodec_is_valid_cache_avb(btfmcodec)) {
+				BTFMCODEC_INFO("detected BT to BTADV audio Gaming usecase");
+			} else {
+				BTFMCODEC_INFO("detected IDLE to BTADV audio lossless usecase");
+			}
+
+			ret = btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev,
+						BTADV_AUDIO_Connecting, MSG_SUCCESS);
+			if (ret < 0)
+				return;
+
+			/* Wait here to get Bearer switch indication */
+			ret = btfmcodec_wait_for_bearer_ind(btfmcodec_dev);
+			if (ret < 0) {
+				BTFMCODEC_ERR("moving back to previous state");
+				btfmcodec_revert_current_state(state);
+				if (ret == -MSG_INTERNAL_TIMEOUT) {
+					btfmcodec_frame_transport_switch_ind_pkt(
+							btfmcodec_dev, BTADV,
+							MSG_INTERNAL_TIMEOUT);
+				}
+			} else {
+				btfmcodec_move_to_next_state(state);
+			}
+			if (ret < 0)
+				return;
+
+			if (btfmcodec_is_valid_cache_avb(btfmcodec)) {
+				BTFMCODEC_INFO("Initiating BT port close...");
+				btfmcodec_initiate_hwep_shutdown(btfmcodec_dev);
+			}
+		}
+	} else if (new_transport == NONE) {
+		/* Let ALSA handles the transport close  for BT */
+		if (current_state != BT_Connecting && current_state != BT_Connected)
+			btfmcodec_set_current_state(state, IDLE);
+		btfmcodec_frame_prepare_bearer_rsp_pkt(btfmcodec_dev, (uint8_t)current_state,
+							MSG_SUCCESS);
+		return;
+	}
+}
+
+void btfmcodec_wq_prepare_bearer(struct work_struct *work)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = container_of(work,
+						struct btfmcodec_char_device,
+						wq_prepare_bearer);
+	int idx = BTM_PKT_TYPE_PREPARE_REQ;
+	BTFMCODEC_INFO("with new transport:%d", btfmcodec_dev->status[idx]);
+	btfmcodec_prepare_bearer(btfmcodec_dev, btfmcodec_dev->status[idx]);
+}

+ 96 - 0
qcom/opensource/bt-kernel/btfmcodec/btfm_codec_hw_interface.c

@@ -0,0 +1,96 @@
+// 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"
+
+int btfmcodec_register_hw_ep (struct hwep_data *ep_info)
+{
+	struct btfmcodec_data *btfmcodec;
+	struct hwep_data *hwep_info;
+	int ret = 0;
+
+	btfmcodec = btfm_get_btfmcodec();
+	mutex_lock(&btfmcodec->hwep_drv_lock);
+	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(&btfmcodec->hwep_drv_lock);
+	return ret;
+}
+
+int btfmcodec_unregister_hw_ep (char *driver_name)
+{
+	struct btfmcodec_data *btfmcodec;
+	struct hwep_data *hwep_info;
+	int ret;
+
+	btfmcodec = btfm_get_btfmcodec();
+	mutex_lock(&btfmcodec->hwep_drv_lock);
+	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(&btfmcodec->hwep_drv_lock);
+	return ret;
+}
+
+EXPORT_SYMBOL(btfmcodec_register_hw_ep);
+EXPORT_SYMBOL(btfmcodec_unregister_hw_ep);

+ 881 - 0
qcom/opensource/bt-kernel/btfmcodec/btfm_codec_interface.c

@@ -0,0 +1,881 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/remoteproc.h>
+#include <linux/remoteproc/qcom_rproc.h>
+#include "btfm_codec.h"
+#include "btfm_codec_interface.h"
+#include "btfm_codec_pkt.h"
+#include "btfm_codec_btadv_interface.h"
+
+static struct snd_soc_dai_driver *btfmcodec_dai_info;
+uint32_t bits_per_second;
+uint8_t num_channels;
+
+static int btfm_codec_get_mixer_control(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec = kcontrol->private_data;
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct hwep_data *hwepinfo = btfmcodec->hwep_info;
+	struct snd_kcontrol_new *mixer_ctrl = hwepinfo->mixer_ctrl;
+	struct snd_ctl_elem_id id = kcontrol->id;
+	int num_mixer_ctrl = hwepinfo->num_mixer_ctrl;
+	int i = 0;
+
+	BTFMCODEC_DBG("");
+	for (; i < num_mixer_ctrl ; i++) {
+		BTFMCODEC_DBG("checking mixer_ctrl:%s and current mixer:%s",
+			id.name, mixer_ctrl[i].name);
+		if (!strncmp(id.name, mixer_ctrl[i].name, 64)) {
+			BTFMCODEC_DBG("Matched");
+			mixer_ctrl[i].get(kcontrol, ucontrol);
+			break;
+		}
+	}
+	if (num_mixer_ctrl == i)
+		return 0;
+	return 1;
+}
+
+
+static int btfmcodec_put_mixer_control(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *codec = kcontrol->private_data;
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct hwep_data *hwepinfo = btfmcodec->hwep_info;
+	struct snd_kcontrol_new *mixer_ctrl = hwepinfo->mixer_ctrl;
+	struct snd_ctl_elem_id id = kcontrol->id;
+	int num_mixer_ctrl = hwepinfo->num_mixer_ctrl;
+	int i = 0;
+
+	BTFMCODEC_DBG("");
+	for (; i < num_mixer_ctrl ; i++) {
+		BTFMCODEC_DBG("checking mixer_ctrl:%s and current mixer:%s",
+			id.name, mixer_ctrl[i].name);
+		if (!strncmp(id.name, mixer_ctrl[i].name, 64)) {
+			BTFMCODEC_DBG("Matched");
+			mixer_ctrl[i].put(kcontrol, ucontrol);
+			break;
+		}
+	}
+	if (num_mixer_ctrl == i)
+		return 0;
+	return 1;
+}
+
+static int btfmcodec_codec_probe(struct snd_soc_component *codec)
+{
+	struct btfmcodec_data *btfmcodec = snd_soc_component_get_drvdata(codec);
+	struct btfmcodec_state_machine *state = &btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	int num_mixer_ctrl = hwep_info->num_mixer_ctrl;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check Whether probe has to allowed when state if different
+	if (btfmcodec_get_current_transport(state)!= IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s",
+			coverttostring(btfmcodec_get_current_transport(state)));
+	} else if (hwep_info->drv && hwep_info->drv->hwep_probe) {
+		hwep_info->drv->hwep_probe(codec);
+		/* Register mixer control */
+		if (hwep_info->mixer_ctrl && num_mixer_ctrl >= 1) {
+			struct snd_kcontrol_new *mixer_ctrl;
+			int i = 0;
+			mixer_ctrl = (struct snd_kcontrol_new *)
+				      kzalloc(num_mixer_ctrl *
+				      sizeof(struct snd_kcontrol_new), GFP_KERNEL);
+			if (!mixer_ctrl) {
+				BTFMCODEC_ERR("failed to register mixer controls");
+				goto end;
+			}
+
+			BTFMCODEC_INFO("Registering %d mixer controls", num_mixer_ctrl);
+			memcpy(mixer_ctrl, hwep_info->mixer_ctrl, num_mixer_ctrl * sizeof(struct snd_kcontrol_new));
+			for (; i< num_mixer_ctrl; i++) {
+				BTFMCODEC_INFO("name of control:%s", mixer_ctrl[i].name);
+				mixer_ctrl[i].get = btfm_codec_get_mixer_control;
+			        mixer_ctrl[i].put = btfmcodec_put_mixer_control;
+			}
+			snd_soc_add_component_controls(codec, mixer_ctrl, num_mixer_ctrl);
+			BTFMCODEC_INFO("CODEC address while registering mixer ctrl:%p", codec);
+		}
+	}
+
+end:
+	// 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 *state = &btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check whether remove has to allowed when state if different
+	if (btfmcodec_get_current_transport(state)!= IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s",
+			coverttostring(btfmcodec_get_current_transport(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 *state = &btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check whether write has to allowed when state if different
+	if (btfmcodec_get_current_transport(state)!= IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s",
+			coverttostring(btfmcodec_get_current_transport(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 *state = &btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	BTFMCODEC_DBG("");
+
+	// ToDo: check whether read has to allowed when state if different
+	if (btfmcodec_get_current_transport(state)!= IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s",
+			coverttostring(btfmcodec_get_current_transport(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;
+}
+
+int btfmcodec_hwep_startup(struct btfmcodec_data *btfmcodec)
+{
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_startup) {
+		return dai_drv->dai_ops->hwep_startup((void *)btfmcodec->hwep_info);
+	} else {
+		return -1;
+	}
+}
+
+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 *state = &btfmcodec->states;
+
+	BTFMCODEC_DBG("substream = %s  stream = %d dai->name = %s",
+		 substream->name, substream->stream, dai->name);
+
+	if (btfmcodec_get_current_transport(state) != IDLE &&
+		btfmcodec_get_current_transport(state) != BT_Connected) {
+		BTFMCODEC_DBG("Not allowing as state is:%s",
+			coverttostring(btfmcodec_get_current_transport(state)));
+	} else {
+		return btfmcodec_hwep_startup(btfmcodec);
+	}
+
+	return 0;
+}
+
+int btfmcodec_hwep_shutdown(struct btfmcodec_data *btfmcodec, int id,
+			    bool disable_master)
+{
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+	struct btfmcodec_state_machine *state = &btfmcodec->states;
+	struct btm_master_shutdown_req shutdown_req;
+	wait_queue_head_t *rsp_wait_q =
+			&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_MASTER_SHUTDOWN_RSP];
+	uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_MASTER_SHUTDOWN_RSP];
+	int ret = 0;
+
+	/* for master configurations failure cases, we don't need to send
+	 * shutdown request
+	 */
+	if (btfmcodec_get_current_transport(state) == BT_Connected && disable_master) {
+		BTFMCODEC_DBG("sending master shutdown request..");
+		shutdown_req.opcode = BTM_BTFMCODEC_MASTER_SHUTDOWN_REQ;
+		shutdown_req.len = BTM_MASTER_SHUTDOWN_REQ_LEN;
+		shutdown_req.stream_id = id;
+		/* See if we need to protect below with lock */
+		*status = BTM_WAITING_RSP;
+		btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &shutdown_req, (shutdown_req.len +
+					BTM_HEADER_LEN));
+
+		ret = wait_event_interruptible_timeout(*rsp_wait_q,
+			(*status) != BTM_WAITING_RSP,
+			msecs_to_jiffies(BTM_MASTER_CONFIG_RSP_TIMEOUT));
+		if (ret == 0) {
+			BTFMCODEC_ERR("failed to recevie response from BTADV audio Manager");
+		} else {
+			if (*status == BTM_RSP_RECV)
+				ret = 0;
+			else if (*status == BTM_FAIL_RESP_RECV ||
+				 *status == BTM_RSP_NOT_RECV_CLIENT_KILLED)
+				ret = -1;
+		}
+	} else {
+		if (!disable_master)
+		BTFMCODEC_WARN("Not sending master shutdown request as graph might have closed");
+		else	
+		BTFMCODEC_WARN("Not sending master shutdown request as state is:%s",
+			coverttostring(btfmcodec_get_current_transport(state)));
+	}
+
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_shutdown) {
+		dai_drv->dai_ops->hwep_shutdown((void *)btfmcodec->hwep_info, id);
+	}
+
+	return ret;
+}
+
+void btfmcodec_wq_hwep_shutdown(struct work_struct *work)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = container_of(work,
+						struct btfmcodec_char_device,
+						wq_hwep_shutdown);
+	struct btfmcodec_data *btfmcodec = (struct btfmcodec_data *)btfmcodec_dev->btfmcodec;
+	struct list_head *head = &btfmcodec->config_head;
+	struct hwep_configurations *hwep_configs = NULL, *tmp;
+	int ret = -1;
+	int idx = BTM_PKT_TYPE_HWEP_SHUTDOWN;
+
+	BTFMCODEC_INFO(" starting shutdown");
+	/* Just check if first Rx has to be closed first or
+	 * any order should be ok.
+	 */
+	list_for_each_entry_safe(hwep_configs, tmp, head, dai_list) {
+		BTFMCODEC_INFO("shuting down dai id:%d", hwep_configs->stream_id);
+		ret = btfmcodec_hwep_shutdown(btfmcodec, hwep_configs->stream_id, true);
+		if (ret < 0) {
+			BTFMCODEC_ERR("failed to shutdown master with id %d", hwep_configs->stream_id);
+			break;
+		}
+	}
+
+	if (ret < 0)
+		btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+	else
+		btfmcodec_dev->status[idx] = BTM_RSP_RECV;
+
+	wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+}
+
+static int btfmcodec_delete_configs(struct btfmcodec_data *btfmcodec, uint8_t id)
+{
+	struct list_head *head = &btfmcodec->config_head;
+	struct hwep_configurations *hwep_configs, *tmp;
+	int ret = -1;
+
+	list_for_each_entry_safe(hwep_configs, tmp, head, dai_list) {
+		if (hwep_configs->stream_id == id) {
+			BTFMCODEC_INFO("deleting configs with id %d", id);
+			list_del(&hwep_configs->dai_list);
+			ret = 1;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+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 *state = &btfmcodec->states;
+
+	BTFMCODEC_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
+		dai->id, dai->rate);
+
+	if (btfmcodec_get_current_transport(state) != IDLE &&
+	    btfmcodec_get_current_transport(state) != BT_Connected) {
+		BTFMCODEC_WARN("not allowing shutdown as state is:%s",
+			coverttostring(btfmcodec_get_current_transport(state)));
+		/* Delete stored configs */
+		btfmcodec_delete_configs(btfmcodec, dai->id);
+	} else {
+		/* first master shutdown has to done */
+		btfmcodec_hwep_shutdown(btfmcodec, dai->id, false);
+		btfmcodec_delete_configs(btfmcodec, dai->id);
+		if (!btfmcodec_is_valid_cache_avb(btfmcodec))
+			btfmcodec_set_current_state(state, IDLE);
+		else {
+			BTFMCODEC_WARN("valid stream id is available not updating state\n");
+			btfmcodec_set_current_state(state, BT_Connected);
+		}
+	}
+}
+
+int btfmcodec_hwep_hw_params (struct btfmcodec_data *btfmcodec, uint32_t bps,
+			      uint32_t direction, uint8_t num_channels)
+{
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+
+	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,
+							num_channels);
+	} else {
+		return -1;
+	}
+}
+
+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 *state = &btfmcodec->states;
+	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));
+
+	bits_per_second = params_width(params);
+	num_channels = params_channels(params);
+	if (btfmcodec_get_current_transport(state) != IDLE &&
+		btfmcodec_get_current_transport(state) != BT_Connected) {
+		BTFMCODEC_WARN("caching bps and num_channels as state is :%s",
+			coverttostring(btfmcodec_get_current_transport(state)));
+	} else {
+		return btfmcodec_hwep_hw_params(btfmcodec, bits_per_second,
+						direction, num_channels);
+	}
+
+	return 0;
+}
+
+bool btfmcodec_is_valid_cache_avb(struct btfmcodec_data *btfmcodec)
+{
+	struct list_head *head = &btfmcodec->config_head;
+	struct hwep_configurations *hwep_configs, *tmp;
+	bool cache_avb = false;
+
+	list_for_each_entry_safe(hwep_configs, tmp, head, dai_list) {
+		cache_avb = true;
+		break;
+	}
+
+	return cache_avb;
+}
+
+static int btfmcodec_check_and_cache_configs(struct btfmcodec_data *btfmcodec,
+				   uint32_t sampling_rate, uint32_t direction,
+				   int id, uint8_t codectype)
+{
+	struct list_head *head = &btfmcodec->config_head;
+	struct hwep_configurations *hwep_configs, *tmp;
+
+	list_for_each_entry_safe(hwep_configs, tmp, head, dai_list) {
+		if (hwep_configs->stream_id == id) {
+			BTFMCODEC_WARN("previous entry for %d is already available",
+				id);
+			list_del(&hwep_configs->dai_list);
+		}
+	}
+
+	hwep_configs = kzalloc(sizeof(struct hwep_configurations),
+				GFP_KERNEL);
+	if (!hwep_configs) {
+		BTFMCODEC_ERR("failed to allocate memory");
+		return -ENOMEM;
+	}
+
+	hwep_configs->stream_id = id; /* Stream identifier */
+	hwep_configs->sample_rate = sampling_rate;
+	hwep_configs->bit_width = bits_per_second;
+	hwep_configs->codectype = codectype;
+	hwep_configs->direction = direction;
+	hwep_configs->num_channels = num_channels;
+
+	list_add(&hwep_configs->dai_list, head);
+	BTFMCODEC_INFO("added dai id:%d to list with sampling_rate :%u, direction:%u", id, sampling_rate, direction);
+	return 1;
+}
+
+static int btfmcodec_configure_master(struct btfmcodec_data *btfmcodec, uint8_t id)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct master_hwep_configurations hwep_configs;
+	struct btm_master_config_req config_req;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+	wait_queue_head_t *rsp_wait_q =
+		&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_MASTER_CONFIG_RSP];
+	uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_MASTER_CONFIG_RSP];
+	int ret = 0;
+
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_get_configs) {
+		dai_drv->dai_ops->hwep_get_configs((void *)btfmcodec->hwep_info,
+						   &hwep_configs, id);
+	} else {
+		BTFMCODEC_ERR("No hwep_get_configs is set by hw ep driver");
+		return -1;
+	}
+
+	BTFMCODEC_INFO("framing packet for %d", id);
+	config_req.opcode = BTM_BTFMCODEC_MASTER_CONFIG_REQ;
+	config_req.len = BTM_MASTER_CONFIG_REQ_LEN;
+	config_req.stream_id = hwep_configs.stream_id;
+	config_req.device_id = hwep_configs.device_id;
+	config_req.sample_rate = hwep_configs.sample_rate;
+	config_req.bit_width = hwep_configs.bit_width;
+	config_req.num_channels = hwep_configs.num_channels;
+	config_req.channel_num = hwep_configs.chan_num;
+	config_req.codec_id = hwep_configs.codectype;
+	BTFMCODEC_DBG("================================================\n");
+	BTFMCODEC_DBG("dma_config_req.len :%d", config_req.len);
+	BTFMCODEC_DBG("dma_config_req.stream_id :%d", config_req.stream_id);
+	BTFMCODEC_DBG("dma_config_req.device_id :%d", config_req.device_id);
+	BTFMCODEC_DBG("dma_config_req.sample_rate :%d", config_req.sample_rate);
+	BTFMCODEC_DBG("dma_config_req.bit_width :%d", config_req.bit_width);
+	BTFMCODEC_DBG("dma_config_req.num_channels :%d", config_req.num_channels);
+	BTFMCODEC_DBG("dma_config_req.channel_num :%d", config_req.channel_num);
+	BTFMCODEC_DBG("dma_config_req.codec_id :%d", config_req.codec_id);
+	BTFMCODEC_DBG("================================================\n");
+	/* See if we need to protect below with lock */
+	*status = BTM_WAITING_RSP;
+	btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &config_req, (config_req.len +
+				BTM_HEADER_LEN));
+	ret = wait_event_interruptible_timeout(*rsp_wait_q,
+		(*status) != BTM_WAITING_RSP,
+		msecs_to_jiffies(BTM_MASTER_CONFIG_RSP_TIMEOUT));
+	if (ret == 0) {
+		BTFMCODEC_ERR("failed to recevie response from BTADV audio Manager");
+		ret = -ETIMEDOUT;
+	} else {
+		if (*status == BTM_RSP_RECV)
+			return 0;
+		else if (*status == BTM_FAIL_RESP_RECV ||
+			 *status == BTM_RSP_NOT_RECV_CLIENT_KILLED)
+			return -1;
+	}
+
+	return ret;
+}
+
+static int btfmcodec_configure_dma(struct btfmcodec_data *btfmcodec, uint8_t id)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct hwep_dma_configurations dma_config;
+	struct btm_dma_config_req dma_config_req;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+	wait_queue_head_t *rsp_wait_q =
+		&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_DMA_CONFIG_RSP];
+	uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_DMA_CONFIG_RSP];
+	int ret = 0;
+
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_get_configs) {
+		dai_drv->dai_ops->hwep_get_configs((void *)btfmcodec->hwep_info,
+						   &dma_config, id);
+	} else {
+		BTFMCODEC_ERR("No hwep_get_configs is set by hw ep driver");
+		return -1;
+	}
+
+	BTFMCODEC_INFO("framing packet for %d", id);
+	dma_config_req.opcode = BTM_BTFMCODEC_CODEC_CONFIG_DMA_REQ;
+	dma_config_req.len = BTM_CODEC_CONFIG_DMA_REQ_LEN;
+	dma_config_req.stream_id = dma_config.stream_id;
+	dma_config_req.sample_rate = dma_config.sample_rate;
+	dma_config_req.bit_width = dma_config.bit_width;
+	dma_config_req.num_channels = dma_config.num_channels;
+	dma_config_req.codec_id = dma_config.codectype;
+	dma_config_req.lpaif = dma_config.lpaif;
+	dma_config_req.inf_index = dma_config.inf_index;
+	dma_config_req.active_channel_mask = dma_config.active_channel_mask;
+
+	BTFMCODEC_DBG("================================================\n");
+	BTFMCODEC_DBG("dma_config_req.len :%d", dma_config_req.len);
+	BTFMCODEC_DBG("dma_config_req.stream_id :%d", dma_config_req.stream_id);
+	BTFMCODEC_DBG("dma_config_req.sample_rate :%d", dma_config_req.sample_rate);
+	BTFMCODEC_DBG("dma_config_req.bit_width :%d", dma_config_req.bit_width);
+	BTFMCODEC_DBG("dma_config_req.num_channels :%d", dma_config_req.num_channels);
+	BTFMCODEC_DBG("dma_config_req.codec_id :%d", dma_config_req.codec_id);
+	BTFMCODEC_DBG("dma_config_req.lpaif :%d", dma_config_req.lpaif);
+	BTFMCODEC_DBG("dma_config_req.inf_index :%d", dma_config_req.inf_index);
+	BTFMCODEC_DBG("dma_config_req.active_channel_mask :%d", dma_config_req.active_channel_mask);
+	BTFMCODEC_DBG("================================================\n");
+
+	*status = BTM_WAITING_RSP;
+	btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &dma_config_req, (dma_config_req.len +
+				BTM_HEADER_LEN));
+
+	ret = wait_event_interruptible_timeout(*rsp_wait_q,
+		(*status) != BTM_WAITING_RSP,
+		msecs_to_jiffies(BTM_MASTER_DMA_CONFIG_RSP_TIMEOUT));
+
+	if (ret == 0) {
+		BTFMCODEC_ERR("failed to recevie response from BTADV audio Manager");
+		ret = -ETIMEDOUT;
+	} else {
+		if (*status == BTM_RSP_RECV)
+			return 0;
+		else if (*status == BTM_FAIL_RESP_RECV ||
+			 *status == BTM_RSP_NOT_RECV_CLIENT_KILLED)
+			return -1;
+	}
+
+	return ret;
+}
+
+int btfmcodec_hwep_prepare(struct btfmcodec_data *btfmcodec, uint32_t sampling_rate,
+			uint32_t direction, int id)
+{
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+	struct btfmcodec_state_machine *state = &btfmcodec->states;
+
+	int ret;
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_prepare) {
+		ret = dai_drv->dai_ops->hwep_prepare((void *)hwep_info, sampling_rate,
+						      direction, id);
+		BTFMCODEC_ERR("%s: hwep info %d", __func__, hwep_info->flags);
+		if (ret == 0 && test_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags)) {
+			ret = btfmcodec_configure_master(btfmcodec, (uint8_t)id);
+			if (ret < 0) {
+				BTFMCODEC_ERR("failed to configure master error %d", ret);
+				btfmcodec_set_current_state(state, IDLE);
+			} else {
+				btfmcodec_set_current_state(state, BT_Connected);
+			}
+		} else if (ret == 0 && test_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags)) {
+			ret  = btfmcodec_configure_dma(btfmcodec, (uint8_t)id);
+			if (ret < 0) {
+				BTFMCODEC_ERR("failed to configure Codec DMA %d", ret);
+				btfmcodec_set_current_state(state, IDLE);
+			} else {
+				btfmcodec_set_current_state(state, BT_Connected);
+			}
+		}
+	} else {
+		return -1;
+	}
+
+	return ret;
+}
+
+static int btfmcodec_notify_usecase_start(struct btfmcodec_data *btfmcodec,
+					  uint8_t transport)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	struct btm_usecase_start_ind ind;
+
+	ind.opcode = BTM_BTFMCODEC_USECASE_START_IND;
+	ind.len = BTM_USECASE_START_IND_LEN;
+	ind.transport = transport;
+	return btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &ind, (ind.len +
+					 BTM_HEADER_LEN));
+}
+
+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 *state = &btfmcodec->states;
+	struct hwep_data *hwep_info = btfmcodec->hwep_info;
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+	uint8_t *codectype = dai_drv->dai_ops->hwep_codectype;
+	uint32_t sampling_rate = dai->rate;
+	uint32_t direction = substream->stream;
+	int id = dai->id;
+	int ret ;
+
+	BTFMCODEC_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d",
+		dai->name, id, sampling_rate, direction);
+
+	ret = btfmcodec_check_and_cache_configs(btfmcodec, sampling_rate,
+						direction, id, *codectype);
+	if (btfmcodec_get_current_transport(state) != IDLE &&
+	    btfmcodec_get_current_transport(state) != BT_Connected) {
+		BTFMCODEC_WARN("cached required info as state is:%s",
+			coverttostring(btfmcodec_get_current_transport(state)));
+		btfmcodec_notify_usecase_start(btfmcodec, BTADV);
+	} else {
+	        ret = btfmcodec_hwep_prepare(btfmcodec, sampling_rate, direction, id);
+/*		if (ret >= 0) {
+			btfmcodec_check_and_cache_configs(btfmcodec,  sampling_rate, direction,
+						id, *codectype);
+		}
+*/	}
+
+	return ret;
+}
+
+int btfmcodec_hwep_set_channel_map(void *hwep_info, unsigned int tx_num,
+				unsigned int *tx_slot, unsigned int rx_num,
+				unsigned int *rx_slot)
+{
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_set_channel_map) {
+		return dai_drv->dai_ops->hwep_set_channel_map(hwep_info, tx_num,
+							      tx_slot, rx_num,
+							      rx_slot);
+	} else {
+		return -1;
+	}
+
+}
+
+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;
+
+	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 {
+		return btfmcodec_hwep_set_channel_map((void *)btfmcodec->hwep_info, tx_num,
+						   tx_slot, rx_num, rx_slot);
+	}
+
+	return 0;
+}
+
+
+int btfmcodec_hwep_get_channel_map(void *hwep_info, unsigned int *tx_num,
+				unsigned int *tx_slot, unsigned int *rx_num,
+				unsigned int *rx_slot, int id)
+{
+	struct hwep_dai_driver *dai_drv = (struct hwep_dai_driver *)
+					      btfmcodec_get_dai_drvdata(hwep_info);
+
+	if (dai_drv && dai_drv->dai_ops && dai_drv->dai_ops->hwep_get_channel_map) {
+		return dai_drv->dai_ops->hwep_get_channel_map(hwep_info, tx_num,
+							      tx_slot, rx_num,
+							      rx_slot, id);
+	} else {
+		return -1;
+	}
+}
+
+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;
+
+	BTFMCODEC_DBG("");
+	// ToDo: get_channel_map is not needed for new driver
+/*	if (states.current_state != IDLE) {
+		BTFMCODEC_WARN("Received probe when state is :%s", coverttostring(states.current_state));
+	} else {
+*/		return btfmcodec_hwep_get_channel_map((void *)btfmcodec->hwep_info,
+						   tx_num, tx_slot, rx_num,
+						   rx_slot, dai->id);
+//	}
+
+	return 0;
+}
+
+void btfmcodec_wq_hwep_configure(struct work_struct *work)
+{
+	struct btfmcodec_char_device *btfmcodec_dev = container_of(work,
+						struct btfmcodec_char_device,
+						wq_hwep_configure);
+	struct btfmcodec_data *btfmcodec = (struct btfmcodec_data *)btfmcodec_dev->btfmcodec;
+	struct list_head *head = &btfmcodec->config_head;
+	struct hwep_configurations *hwep_configs = NULL, *tmp;
+	int ret;
+	int idx = BTM_PKT_TYPE_HWEP_CONFIG;
+	uint32_t sample_rate, direction;
+	uint8_t id, bit_width, codectype, num_channels;
+
+	list_for_each_entry_safe(hwep_configs, tmp, head, dai_list) {
+		id = hwep_configs->stream_id;
+		sample_rate = hwep_configs->sample_rate;
+		bit_width = hwep_configs->bit_width;
+		codectype = hwep_configs->codectype;
+		direction = hwep_configs->direction;
+		num_channels = hwep_configs->num_channels;
+
+		BTFMCODEC_INFO("configuring dai id:%d with sampling rate:%d bit_width:%d", id, sample_rate, bit_width);
+		ret = btfmcodec_hwep_startup(btfmcodec);
+		if (ret >= 0)
+			ret = btfmcodec_hwep_hw_params(btfmcodec, bit_width, direction, num_channels);
+		if (ret >= 0)
+			ret = btfmcodec_hwep_prepare(btfmcodec, sample_rate, direction, id);
+		if (ret < 0) {
+			BTFMCODEC_ERR("failed to configure hwep %d", hwep_configs->stream_id);
+			break;
+		}
+	}
+
+	if (ret < 0)
+		btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
+	else
+		btfmcodec_dev->status[idx] = BTM_RSP_RECV;
+
+	wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
+}
+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,
+};
+
+static int btfmcodec_adsp_ssr_notify(struct notifier_block *nb,
+				    unsigned long action, void *data)
+{
+	struct btfmcodec_data *btfmcodec  = container_of(nb,
+					    struct btfmcodec_data, notifier.nb);
+	struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	struct btm_adsp_state_ind state_ind;
+
+	switch (action) {
+	case QCOM_SSR_BEFORE_SHUTDOWN: {
+		BTFMCODEC_WARN("LPASS SSR triggered");
+		break;
+	} case QCOM_SSR_AFTER_SHUTDOWN: {
+		BTFMCODEC_WARN("LPASS SSR Completed");
+		break;
+	} case QCOM_SSR_BEFORE_POWERUP: {
+		BTFMCODEC_WARN("LPASS booted up after SSR");
+		break;
+	} case QCOM_SSR_AFTER_POWERUP: {
+		BTFMCODEC_WARN("LPASS booted up completely");
+		state_ind.opcode = BTM_BTFMCODEC_ADSP_STATE_IND;
+		state_ind.len  = BTM_ADSP_STATE_IND_LEN;
+		state_ind.action = (uint32_t)action;
+		btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &state_ind,
+				(state_ind.len +
+				BTM_HEADER_LEN));
+		break;
+	} default:
+		BTFMCODEC_WARN("unhandled action id %lu", action);
+		break;
+	}
+	return 0;
+}
+
+int btfm_register_codec(struct hwep_data *hwep_info)
+{
+	struct btfmcodec_data *btfmcodec;
+	struct btfmcodec_char_device *btfmcodec_dev;
+	struct device *dev;
+	struct hwep_dai_driver *dai_drv;
+	int i, ret;
+
+	btfmcodec = btfm_get_btfmcodec();
+	btfmcodec_dev = btfmcodec->btfmcodec_dev;
+	dev = &btfmcodec->dev;
+
+	btfmcodec->notifier.nb.notifier_call = btfmcodec_adsp_ssr_notify;
+	btfmcodec->notifier.notifier = qcom_register_ssr_notifier("lpass",
+					&btfmcodec->notifier.nb);
+	if (IS_ERR(btfmcodec->notifier.notifier)) {
+		ret = PTR_ERR(btfmcodec->notifier.notifier);
+		BTFMCODEC_ERR("Failed to register SSR notification: %d\n", ret);
+		return ret;
+	}
+
+	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);
+	BTFMCODEC_INFO("Dev node address: %p", dev);
+	BTFMCODEC_INFO("btfmcodec address :%p", btfmcodec);
+	BTFMCODEC_INFO("HWEPINFO address:%p", hwep_info);
+	BTFMCODEC_INFO("btfmcodec_dev INFO address:%p", btfmcodec->btfmcodec_dev);
+	BTFMCODEC_INFO("before wq_hwep_shutdown:%p", btfmcodec_dev->wq_hwep_shutdown);
+	BTFMCODEC_INFO("before wq_prepare_bearer:%p", btfmcodec_dev->wq_prepare_bearer);
+	INIT_WORK(&btfmcodec_dev->wq_hwep_shutdown, btfmcodec_wq_hwep_shutdown);
+	INIT_WORK(&btfmcodec_dev->wq_prepare_bearer, btfmcodec_wq_prepare_bearer);
+	INIT_WORK(&btfmcodec_dev->wq_hwep_configure, btfmcodec_wq_hwep_configure);
+	BTFMCODEC_INFO("after wq_hwep_shutdown:%p", btfmcodec_dev->wq_hwep_shutdown);
+	BTFMCODEC_INFO("after wq_prepare_bearer:%p", btfmcodec_dev->wq_prepare_bearer);
+	BTFMCODEC_INFO("btfmcodec_wq_prepare_bearer:%p", btfmcodec_wq_prepare_bearer);
+	BTFMCODEC_INFO("btfmcodec_wq_hwep_shutdown:%p", btfmcodec_wq_hwep_shutdown);
+
+	if (isCpSupported()) {
+		if (!strcmp(hwep_info->driver_name, "btfmslim"))
+			set_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags);
+		else if (!strcmp(hwep_info->driver_name, "btfmswr_slave"))
+			set_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags);
+
+	BTFMCODEC_INFO("%s: master %d dma codec %d", __func__,
+			(int)test_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags),
+			(int)test_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags));
+	}
+
+	return ret;
+}
+
+void btfm_unregister_codec(void)
+{
+	struct btfmcodec_data *btfmcodec;
+
+	btfmcodec = btfm_get_btfmcodec();
+	snd_soc_unregister_component(&btfmcodec->dev);
+}

+ 108 - 0
qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec.h

@@ -0,0 +1,108 @@
+// 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 <linux/skbuff.h>
+#include "btfm_codec_hw_interface.h"
+
+#define BTM_BTFMCODEC_DEFAULT_LOG_LVL        0x03
+#define BTM_BTFMCODEC_DEBUG_LOG_LVL          0x04
+#define BTM_BTFMCODEC_INFO_LOG_LVL           0x08
+
+static uint8_t log_lvl = BTM_BTFMCODEC_DEFAULT_LOG_LVL;
+
+#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 BTFMCODEC_DBG(fmt, arg...)  { if(log_lvl >= BTM_BTFMCODEC_DEBUG_LOG_LVL) \
+	                                pr_err("%s: " fmt "\n", __func__, ## arg); \
+				      else \
+	                                pr_debug("%s: " fmt "\n", __func__, ## arg); \
+	                            }
+#define BTFMCODEC_INFO(fmt, arg...) { if(log_lvl >= BTM_BTFMCODEC_INFO_LOG_LVL) \
+					pr_err("%s: " fmt "\n", __func__, ## arg);\
+				      else \
+					pr_info("%s: " fmt "\n", __func__, ## arg);\
+				    }
+
+#define DEVICE_NAME_MAX_LEN	64
+#define BTM_CP_UPDATE           0xbfaf
+
+typedef enum btfmcodec_states {
+	/*Default state of kernel proxy driver */
+	IDLE = 0,
+	/* Waiting for BT bearer indication after configuring HW ports */
+	BT_Connecting = 1,
+	/* When BT is active transport */
+	BT_Connected = 2,
+	/* Waiting for BTADV Audio bearer switch indications */
+	BTADV_AUDIO_Connecting = 3,
+	/* When BTADV audio is active transport */
+	BTADV_AUDIO_Connected = 4
+} btfmcodec_state;
+
+enum btfm_pkt_type {
+	BTM_PKT_TYPE_PREPARE_REQ = 0,
+	BTM_PKT_TYPE_MASTER_CONFIG_RSP,
+	BTM_PKT_TYPE_MASTER_SHUTDOWN_RSP,
+	BTM_PKT_TYPE_BEARER_SWITCH_IND,
+	BTM_PKT_TYPE_HWEP_SHUTDOWN,
+	BTM_PKT_TYPE_HWEP_CONFIG,
+	BTM_PKT_TYPE_DMA_CONFIG_RSP,
+	BTM_PKT_TYPE_MAX,
+};
+
+
+char *coverttostring(enum btfmcodec_states);
+struct btfmcodec_state_machine {
+	struct mutex state_machine_lock;
+	btfmcodec_state prev_state;
+	btfmcodec_state current_state;
+	btfmcodec_state 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 workqueue_struct *workqueue;
+	struct sk_buff_head rxq;
+	struct work_struct rx_work;
+	struct work_struct wq_hwep_shutdown;
+	struct work_struct wq_prepare_bearer;
+	struct work_struct wq_hwep_configure;
+	wait_queue_head_t readq;
+	spinlock_t tx_queue_lock;
+	struct sk_buff_head txq;
+	wait_queue_head_t rsp_wait_q[BTM_PKT_TYPE_MAX];
+	uint8_t status[BTM_PKT_TYPE_MAX];
+	void *btfmcodec;
+};
+
+struct adsp_notifier {
+	void *notifier;
+	struct notifier_block nb;
+};
+
+struct btfmcodec_data {
+	struct device dev;
+	struct btfmcodec_state_machine states;
+	struct btfmcodec_char_device *btfmcodec_dev;
+	struct hwep_data *hwep_info;
+	struct list_head config_head;
+	struct adsp_notifier notifier;
+	struct mutex hwep_drv_lock;
+};
+
+struct btfmcodec_data *btfm_get_btfmcodec(void);
+bool isCpSupported(void);
+#endif /*__LINUX_BTFM_CODEC_H */

+ 22 - 0
qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_btadv_interface.h

@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_CODEC_BTADV_INTERFACE_H
+#define __LINUX_BTFM_CODEC_BTADV_INTERFACE_H
+
+enum transport_type {
+	BT = 1,
+	BTADV,
+	NONE,
+};
+
+static char *transport_type_text[] = {"BT", "BTADV", "NONE"};
+
+void btfmcodec_set_current_state(struct btfmcodec_state_machine *, btfmcodec_state);
+void btfmcodec_wq_prepare_bearer(struct work_struct *);
+void btfmcodec_wq_hwep_shutdown(struct work_struct *);
+void btfmcodec_initiate_hwep_shutdown(struct btfmcodec_char_device *btfmcodec_dev);
+btfmcodec_state btfmcodec_get_current_transport(struct btfmcodec_state_machine *state);
+#endif /* __LINUX_BTFM_CODEC_BTADV_INTERFACE_H */

+ 116 - 0
qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_hw_interface.h

@@ -0,0 +1,116 @@
+// 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 BTADV_CONFIGURE_DMA             1
+#define DEVICE_NAME_MAX_LEN	64
+
+struct hwep_configurations {
+	void *btfmcodec;
+	uint8_t stream_id;
+	uint32_t sample_rate;
+	uint8_t bit_width;
+	uint8_t codectype;
+	uint32_t direction;
+	uint8_t num_channels;
+	struct list_head dai_list;
+};
+
+struct master_hwep_configurations {
+	uint8_t stream_id;
+	uint32_t device_id;
+	uint32_t sample_rate;
+	uint8_t bit_width;
+	uint8_t num_channels;
+	uint8_t chan_num;
+	uint8_t codectype;
+	uint16_t direction;
+};
+
+struct hwep_dma_configurations {
+	uint8_t stream_id;
+	uint32_t sample_rate;
+	uint8_t bit_width;
+	uint8_t num_channels;
+	uint8_t codectype;
+	uint8_t lpaif; // Low power audio interface
+	uint8_t inf_index; // interface index
+	uint8_t active_channel_mask;
+};
+
+struct hwep_comp_drv {
+	int  (*hwep_probe)  (struct snd_soc_component *);
+	void (*hwep_remove) (struct snd_soc_component *);
+	unsigned int  (*hwep_read)(struct snd_soc_component *, unsigned int );
+	int  (*hwep_write)(struct snd_soc_component *, unsigned int,
+			   unsigned int);
+};
+
+struct hwep_dai_ops {
+	int (*hwep_startup)(void *);
+	void (*hwep_shutdown)(void *, int);
+	int (*hwep_hw_params)(void *, uint32_t, uint32_t, uint8_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);
+	int (*hwep_get_configs)(void *a, void *b, uint8_t c);
+	uint8_t *hwep_codectype;
+};
+
+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;
+	struct snd_kcontrol_new *mixer_ctrl;
+	int num_dai;
+	int num_mixer_ctrl;
+	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
qcom/opensource/bt-kernel/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 */

+ 136 - 0
qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_pkt.h

@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_CODEC_PKT_H
+#define __LINUX_BTFM_CODEC_PKT_H
+
+typedef uint32_t btm_opcode;
+
+struct btm_req {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t *data;
+}__attribute__((packed));
+
+struct btm_rsp {
+	btm_opcode opcode;
+	uint8_t status;
+}__attribute__((packed));
+
+struct btm_ind {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t *data;
+}__attribute__((packed));
+
+struct btm_ctrl_pkt {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t active_transport;
+	uint8_t status;
+}__attribute__((packed));
+
+#define BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_REQ           0x50000000
+#define BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_RSP           0x50000001
+#define BTM_BTFMCODEC_MASTER_CONFIG_REQ                         0x50000002
+#define BTM_BTFMCODEC_MASTER_CONFIG_RSP                         0x50000003
+#define BTM_BTFMCODEC_MASTER_SHUTDOWN_REQ                       0x50000004
+#define BTM_BTFMCODEC_CTRL_MASTER_SHUTDOWN_RSP                  0x50000005
+#define BTM_BTFMCODEC_CODEC_CONFIG_DMA_REQ                      0x58000006
+#define BTM_BTFMCODEC_CODEC_CONFIG_DMA_RSP                      0x58000007
+
+#define BTM_BTFMCODEC_BEARER_SWITCH_IND                         0x58000001
+#define BTM_BTFMCODEC_TRANSPORT_SWITCH_FAILED_IND               0x58000002
+#define BTM_BTFMCODEC_ADSP_STATE_IND                            0x58000003
+#define BTM_BTFMCODEC_CTRL_LOG_LVL_IND                          0x58000004
+
+#define BTM_MASTER_CONFIG_REQ_LEN			13
+#define BTM_MASTER_CONFIG_RSP_TIMEOUT			5000
+#define BTM_MASTER_DMA_CONFIG_RSP_TIMEOUT		5000
+#define BTM_HEADER_LEN					8
+#define BTM_PREPARE_AUDIO_BEARER_SWITCH_RSP_LEN		2
+#define BTM_MASTER_CONFIG_RSP_LEN			2
+#define BTM_CODEC_CONFIG_DMA_RSP_LEN			2
+#define BTM_MASTER_SHUTDOWN_REQ_LEN			1
+#define BTM_PREPARE_AUDIO_BEARER_SWITCH_REQ_LEN		1
+#define BTM_BEARER_SWITCH_IND_LEN			1
+#define BTM_LOG_LVL_IND_LEN                             1
+#define BTM_ADSP_STATE_IND_LEN				4
+#define BTM_CODEC_CONFIG_DMA_REQ_LEN			11
+
+#define BTM_BTFMCODEC_USECASE_START_IND			0x58000008
+#define BTM_USECASE_START_IND_LEN                       1
+
+enum rx_status {
+	/* Waiting for response */
+	BTM_WAITING_RSP,
+	/* Response recevied */
+	BTM_RSP_RECV,
+	/* Response recevied with failure status*/
+	BTM_FAIL_RESP_RECV,
+	/* Response not recevied, but client killed */
+        BTM_RSP_NOT_RECV_CLIENT_KILLED,
+};
+
+enum btfm_kp_status {
+	/* KP processed message succesfully */
+	MSG_SUCCESS = 0,
+	/* Error while processing the message */
+	MSG_FAILED,
+	/* Wrong transport type selected by BTADV audio manager */
+	MSG_WRONG_TRANSPORT_TYPE,
+	/* Timeout triggered to receive bearer switch indications*/
+	MSG_INTERNAL_TIMEOUT,
+	MSG_FAILED_TO_CONFIGURE_HWEP,
+	MSG_FAILED_TO_SHUTDOWN_HWEP,
+	MSG_ERR_WHILE_SHUTING_DOWN_HWEP,
+};
+
+struct btm_master_config_req {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t stream_id;
+	uint32_t device_id;
+	uint32_t sample_rate;
+	uint8_t bit_width;
+	uint8_t num_channels;
+	uint8_t channel_num;
+	uint8_t codec_id;
+}__attribute__((packed));
+
+struct btm_dma_config_req {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t stream_id;
+	uint32_t sample_rate;
+	uint8_t bit_width;
+	uint8_t num_channels;
+	uint8_t codec_id;
+	uint8_t lpaif;     // Low power audio interface
+	uint8_t inf_index; // interface index
+	uint8_t active_channel_mask;
+} __packed;
+
+struct btm_usecase_start_ind {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t transport;
+} __packed;
+
+struct btm_master_shutdown_req {
+	btm_opcode opcode;
+	uint32_t len;
+	uint8_t stream_id;
+}__attribute__((packed));
+
+struct btm_adsp_state_ind {
+	btm_opcode opcode;
+	uint32_t len;
+	uint32_t action;
+} __attribute__((packed));
+
+int btfmcodec_dev_enqueue_pkt(struct btfmcodec_char_device *, void *, int);
+bool btfmcodec_is_valid_cache_avb(struct btfmcodec_data *);
+#endif /* __LINUX_BTFM_CODEC_PKT_H*/

+ 721 - 0
qcom/opensource/bt-kernel/include/btpower.h

@@ -0,0 +1,721 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BLUETOOTH_POWER_H
+#define __LINUX_BLUETOOTH_POWER_H
+
+#include <linux/cdev.h>
+#include <linux/types.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox/qmp.h>
+#include <linux/workqueue.h>
+#include <linux/skbuff.h>
+
+/*
+ * voltage regulator information required for configuring the
+ * bluetooth chipset
+ */
+
+enum power_modes {
+	POWER_DISABLE = 0,
+	POWER_ENABLE,
+	POWER_RETENTION,
+	POWER_DISABLE_RETENTION,
+};
+
+enum SubSystem {
+	BLUETOOTH = 1,
+	UWB,
+};
+
+enum power_states {
+	IDLE = 0,
+	BT_ON,
+	UWB_ON,
+	ALL_CLIENTS_ON,
+};
+
+enum retention_states {
+	/* Default state */
+	RETENTION_IDLE = 0,
+	/* When BT is only client and it is in retention_state */
+	BT_IN_RETENTION,
+	/* BT is retention mode and UWB powered ON triggered */
+	BT_OUT_OF_RETENTION,
+	/* When UWB is only client and it is in retention_state */
+	UWB_IN_RETENTION,
+	/* UWB is retention mode and BT powered ON triggered */
+	UWB_OUT_OF_RETENTION,
+	/* Both clients are voted for retention */
+	BOTH_CLIENTS_IN_RETENTION,
+};
+
+enum grant_return_values {
+	ACCESS_GRANTED = 0,
+	ACCESS_DENIED  = 1,
+	ACCESS_RELEASED = 2,
+	ACCESS_DISALLOWED = -1,
+};
+
+enum grant_states {
+	/* Default state */
+	NO_GRANT_FOR_ANY_SS = 0,
+	NO_OTHER_CLIENT_WAITING_FOR_GRANT,
+	BT_HAS_GRANT,
+	UWB_HAS_GRANT,
+	BT_WAITING_FOR_GRANT,
+	UWB_WAITING_FOR_GRANT,
+};
+
+enum cores {
+	BT_CORE = 0,
+	UWB_CORE,
+	PLATFORM_CORE
+};
+
+enum ssr_states {
+	SUB_STATE_IDLE = 0,
+	SSR_ON_BT,
+	BT_SSR_COMPLETED,
+	SSR_ON_UWB,
+	UWB_SSR_COMPLETED,
+	REG_BT_PID,
+	REG_UWB_PID,
+};
+
+enum plt_pwr_state {
+	POWER_ON_BT = 0,
+	POWER_OFF_BT,
+	POWER_ON_UWB,
+	POWER_OFF_UWB,
+	POWER_ON_BT_RETENION,
+	POWER_ON_UWB_RETENION,
+	BT_ACCESS_REQ,
+	UWB_ACCESS_REQ,
+	BT_RELEASE_ACCESS,
+	UWB_RELEASE_ACCESS,
+	BT_MAX_PWR_STATE,
+};
+
+enum {
+	PWR_WAITING_RSP = -2,
+	PWR_RSP_RECV = 0,
+	PWR_FAIL_RSP_RECV = -1,
+	PWR_CLIENT_KILLED,
+};
+
+static inline char *ConvertGrantRetToString(enum grant_return_values state) {
+
+	switch (state) {
+	case ACCESS_GRANTED:
+		return "ACCESS_GRANTED";
+	case ACCESS_DENIED:
+		return "ACCESS_DENIED";
+	case ACCESS_RELEASED:
+		return "ACCESS_RELEASED";
+	case ACCESS_DISALLOWED:
+		return "ACCESS_DISALLOWED";
+	default:
+		return "INVALID STATE";
+	}
+}
+
+static inline char *ConvertGrantToString(enum grant_states state) {
+
+	switch (state) {
+	case NO_GRANT_FOR_ANY_SS:
+		return "NO_GRANT_FOR_ANY_SS";
+	case NO_OTHER_CLIENT_WAITING_FOR_GRANT:
+		return "NO_OTHER_CLIENT_WAITING_FOR_GRANT";
+	case BT_HAS_GRANT:
+		return "BT_HAS_GRANT";
+	case UWB_HAS_GRANT:
+		return "UWB_HAS_GRANT";
+	case BT_WAITING_FOR_GRANT:
+		return "BT_WAITING_FOR_GRANT";
+	case UWB_WAITING_FOR_GRANT:
+		return "UWB_WAITING_FOR_GRANT";
+	default:
+		return "INVALID STATE";
+	}
+}
+
+static inline char *ConvertRetentionModeToString(int state) {
+
+	switch (state) {
+	case IDLE:
+		return "Both client not in Retention";
+	case BT_IN_RETENTION:
+		return "BT in Retention";
+	case BT_OUT_OF_RETENTION:
+		return "BT is out off Retention";
+	case UWB_IN_RETENTION:
+		return "UWB in Retention";
+	case UWB_OUT_OF_RETENTION:
+		return "UWB is out off Retention";
+	case BOTH_CLIENTS_IN_RETENTION:
+		return "Both client in Retention";
+	default:
+		return "Retention state = INVALID STATE";
+	}
+}
+
+static inline char *ConvertClientReqToString(int arg) {
+
+	switch (arg) {
+	case POWER_DISABLE:
+		return "Power OFF";
+	case POWER_ENABLE:
+		return "Power ON";
+	case POWER_RETENTION:
+		return "Power Retention";
+	default:
+		return "INVALID STATE";
+	}
+}
+
+static inline char *ConvertPowerStatusToString(int state) {
+
+	switch (state) {
+	case IDLE:
+		return "Current state is ALL Client OFF";
+	case BT_ON:
+		return "Current state is BT powered ON";
+	case UWB_ON:
+		return "Current state is UWB powered ON";
+	case ALL_CLIENTS_ON:
+		return "Current state is ALL Client ON";
+	default:
+		return "Current state is = INVALID STATE";
+	}
+}
+
+static inline char *ConvertSsrStatusToString(int state) {
+
+	switch (state) {
+	case SUB_STATE_IDLE:
+		return "and No SSR";
+	case SSR_ON_BT:
+		return "and SSR on BT";
+	case BT_SSR_COMPLETED:
+		return "and BT SSR completed";
+	case SSR_ON_UWB:
+		return "and SSR on UWB";
+	case UWB_SSR_COMPLETED:
+		return "and UWB SSR completed";
+	default:
+		return "SSR STATE = INVALID STATE";
+	}
+}
+
+static inline char *ConvertPowerReqToString(int arg) {
+
+	switch (arg) {
+	case POWER_ON_BT:
+		return "POWER_ON_BT";
+	case POWER_OFF_BT:
+		return "POWER_OFF_BT";
+	case POWER_ON_UWB:
+		return "POWER_ON_UWB";
+	case POWER_OFF_UWB:
+		return "POWER_OFF_UWB";
+	case POWER_ON_BT_RETENION:
+		return "POWER_ON_BT_RETENION";
+	case POWER_ON_UWB_RETENION:
+		return "POWER_ON_UWB_RETENION";
+	case BT_ACCESS_REQ:
+		return "BT_ACCESS_REQ";
+	case UWB_ACCESS_REQ:
+		return "UWB_ACCESS_REQ";
+	case BT_RELEASE_ACCESS:
+		return "BT_RELEASE_ACCESS";
+	case UWB_RELEASE_ACCESS:
+		return "UWB_RELEASE_ACCESS";
+	case BT_MAX_PWR_STATE:
+		return "BT_MAX_PWR_STATE";
+	default:
+		return "INVALID STATE";
+	}
+};
+
+static inline char *ConvertRegisterModeToString(int reg_mode) {
+
+	switch (reg_mode) {
+	case POWER_DISABLE:
+		return "vote off";
+	case POWER_ENABLE:
+		return "vote on";
+	case POWER_RETENTION:
+		return "vote for retention";
+	case POWER_DISABLE_RETENTION:
+		return "vote offretention";
+	default:
+		return "INVALID STATE";
+	}
+}
+
+enum UwbPrimaryReasonCode{
+	UWB_HOST_REASON_DEFAULT_NONE  = 0x00,                         //INVALID REASON
+	UWB_HOST_REASON_PERI_SOC_CRASHED = 0x01,                      //PERI SOC WAS CRASHED
+	UWB_HOST_REASON_PERI_SOC_CRASHED_DIAG_SSR = 0x02,             //PERI SOC CRASHED DIAG INITIATED SSR
+	UWB_HOST_REASON_INIT_FAILED = 0x03,                           //HOST INITIALIZATION FAILED
+	UWB_HOST_REASON_CLOSE_RCVD_DURING_INIT = 0x04,                //CLOSE RECEIVED FROM STACK DURING SOC INIT
+	UWB_HOST_REASON_ERROR_READING_DATA_FROM_Q2SPI = 0x05,         //ERROR READING DATA FROM Q2SPI
+	UWB_HOST_REASON_WRITE_FAIL_SPCL_BUFF_CRASH_SOC = 0x06,        //FAILED TO WRITE SPECIAL BYTES TO CRASH SOC
+	UWB_HOST_REASON_RX_THREAD_STUCK = 0x07,                       //RX THREAD STUCK
+	UWB_HOST_REASON_SSR_CMD_TIMEDOUT = 0x08,                      //SSR DUE TO CMD TIMED OUT
+	UWB_HOST_REASON_SSR_INVALID_BYTES_RCVD = 0x0A,                //INVALID HCI CMD TYPE RECEIVED
+	UWB_HOST_REASON_SSR_RCVD_LARGE_PKT_FROM_SOC = 0x0B,           //SSR DUE TO LARGE PKT RECVIVED FROM SOC
+	UWB_HOST_REASON_SSR_UNABLE_TO_WAKEUP_SOC = 0x0C,              //UNABLE TO WAKE UP SOC
+	UWB_HOST_REASON_CMD_TIMEDOUT_SOC_WAIT_TIMEOUT = 0x0D,         //COMMAND TIMEOUT AND SOC CRASH WAIT TIMEOUT
+	UWB_HOST_REASON_INV_BYTES_SOC_WAIT_TIMEOUT = 0x0F,            //INVALID BYTES AND SOC CRASH WAIT TIMEOUT
+	UWB_HOST_REASON_SOC_WAKEUP_FAILED_SOC_WAIT_TIMEOUT = 0x10,    //SOC WAKEUP FAILURE AND SOC CRASH WAIT TIMEOUT
+	UWB_HOST_REASON_SOC_CRASHED_DIAG_SSR_SOC_WAIT_TIMEOUT = 0x11, //SOC CRASHED DIAG INITIATED SSR CRASH WAIT TIMEOUT
+	UWB_HOST_REASON_NONE_SOC_WAIT_TIMEOUT = 0x12,                 //INVALID FAILURE AND SOC CRASH WAIT TIMEOUT
+	UWB_HOST_REASON_SOC_DEINIT_STUCK = 0x13,                      //SOC DEINIT STUCK
+	UWB_HOST_REASON_SSR_INTERNAL_CMD_TIMEDOUT = 0x14,             //SSR DUE TO CMD INTERNAL TIMED OUT
+	UWB_HOST_REASON_FAILED_TO_SEND_INTERNAL_CMD = 0x15,           //FAILED TO SEND INTERNAL CMD
+	UWB_HOST_REASON_SSR_SLEEP_IND_NOT_RCVD = 0x16,                //SOC DID NOT RCVD SLEEP IND DURING CLOSE
+	UWB_HOST_REASON_UWB_SOC_CRASHED = 0xC1,                       //UWB SOC WAS CRASHED
+	UWB_HOST_REASON_UWB_SOC_CRASHED_DIAG_SSR = 0xC2,              //UWB SOC CRASHED DIAG INITIATED SSR
+	UWB_HOST_REASON_DIAG_LOG_API_STUCK = 0x39,                    //DIAG log API stuck.
+	UWB_HOST_REASON_PERI_CRASH_ON_OTHER_SS = 0x3A,                //Peripheral core crash detected in BT SS
+	UWB_HOST_REASON_CRASH_EVT_INDUCED = 0x60,                     //Packet Type from SoC for inducing crash
+};
+
+enum UwbSecondaryReasonCode{
+	UWB_SOC_REASON_DEFAULT                 =  0x00,
+	UWB_SOC_REASON_TX_RX_INVALID_PKT       =  0x40,
+	UWB_SOC_REASON_TX_RX_INVALID_PKT_LENE  =  0x41,
+	UWB_SOC_REASON_TX_RX_OVERFLOW_BUFF     =  0x42,
+	UWB_SOC_REASON_UNKNOWN                 =  0x81,
+	UWB_SOC_REASON_SW_REQUESTED            =  0x82,
+	UWB_SOC_REASON_STACK_OVERFLOW          =  0x83,
+	UWB_SOC_REASON_EXCEPTION               =  0x84,
+	UWB_SOC_REASON_ASSERT                  =  0x85,
+	UWB_SOC_REASON_TRAP                    =  0x86,
+	UWB_SOC_REASON_OS_FATAL                =  0x87,
+	UWB_SOC_REASON_HCI_RESET               =  0x88,
+	UWB_SOC_REASON_PATCH_RESET             =  0x89,
+	UWB_SOC_REASON_ABT                     =  0x8A,
+	UWB_SOC_REASON_RAMMASK                 =  0x8B,
+	UWB_SOC_REASON_PREBARK                 =  0x8C,
+	UWB_SOC_REASON_BUSERROR                =  0x8D,
+	UWB_SOC_REASON_IO_FATAL                =  0x8E,
+	UWB_SOC_REASON_SSR_CMD                 =  0x8F,
+	UWB_SOC_REASON_POWERON                 =  0x90,
+	UWB_SOC_REASON_WATCHDOG                =  0x91,
+	UWB_SOC_REASON_RAMMASK_RGN1            =  0x92,
+	UWB_SOC_REASON_RAMMASK_RGN0            =  0x93,
+	UWB_SOC_REASON_Q6_WATCHDOG             =  0x94,
+	UWB_SOC_REASON_ZEALIS_RAM_MASK_RGN0    =  0x95,
+	UWB_SOC_REASON_ZEALIS_RAM_MASK_RGN1    =  0x96,
+	UWB_SOC_REASON_APSS_RESET              =  0x97,
+	UWB_SOC_REASON_TIME_RESET              =  0x98,
+	UWB_SOC_REASON_AUDIOSS_RESET           =  0x99,
+	UWB_SOC_REASON_HOST_WARMRESET          =  0x9A,
+	UWB_SOC_REASON_HOST_NMI_INIT           =  0x9B,
+	UWB_SOC_REASON_PANIC_FAULT             =  0x9C,
+	UWB_SOC_REASON_EARLY_TRAP              =  0x9D,
+	UWB_SOC_REASON_INSTR_ADDR_MISALGIN     =  0x9E,
+	UWB_SOC_REASON_INSTR_ACCESS_FAULT      =  0x9F,
+	UWB_SOC_REASON_ILLEGAL_INSTR           =  0xA0,
+	UWB_SOC_REASON_BREAKPOINT_EXCEPTION    =  0xA1,
+	UWB_SOC_REASON_LOAD_ADDR_MISALIGN      =  0xA2,
+	UWB_SOC_REASON_LOAD_ACCESS_FAULT       =  0xA3,
+	UWB_SOC_REASON_STORE_ADDR_MISALGN      =  0xA4,
+	UWB_SOC_REASON_STORE_ACCESS_FAULT      =  0xA5,
+	UWB_SOC_REASON_ECALL_UMODE             =  0xA6,
+	UWB_SOC_REASON_ECALL_MMODE             =  0xA7,
+	UWB_SOC_REASON_STACK_UNDERFLOW         =  0xA8,
+	UWB_SOC_REASON_MACHINE_EXIT_INT        =  0xA9,
+	UWB_SOC_REASON_PERF_MONITOR_OVERFLOW   =  0xAA,
+	UWB_SOC_REASON_EXT_SUBSYS_RESET        =  0xAB,
+	UWB_SOC_REASON_IPC_STALL               =  0xAC,
+	UWB_SOC_REASON_PEER_CPU0_NMI           =  0xAD,
+	UWB_SOC_REASON_PEER_CPU1_NMI           =  0xAE,
+	UWB_SOC_REASON_PEER_CPU2_NMI           =  0xAF,
+	UWB_SOC_REASON_TX_RX_INVALID_PKT_FATAL =  0xC0,
+	UWB_SOC_REASON_TX_RX_INVALID_LEN_FATAL =  0xC1,
+	UWB_SOC_REASON_TX_RX_OVERFLOW_FATAL    =  0xC2,
+	UWB_SOC_REASON_INVALID_STACK           =  0xF0,
+	UWB_SOC_REASON_INVALID_MCI_MSG_RCVD    =  0xF1,
+	UWB_HOST_REASON_PERI_GETVER_SEND_STUCK          =  0x18,
+	UWB_HOST_REASON_PERI_GETVER_NO_RSP_RCVD         =  0x19,
+	UWB_HOST_REASON_PERI_PATCH_DNLD_STUCK           =  0x1B,
+	UWB_HOST_REASON_PERI_GETBOARDID_CMD_STUCK       =  0x1C,
+	UWB_HOST_REASON_PERI_NVM_DNLD_STUCK             =  0x1D,
+	UWB_HOST_REASON_PERI_RESET_STUCK                =  0x1E,
+	UWB_HOST_REASON_PERI_GETBLDINFO_CMD_STUCK       =  0x1F,
+	UWB_HOST_REASON_PERI_ENHLOG_CMD_STUCK           =  0x21,
+	UWB_HOST_REASON_DIAGINIT_STUCK                  =  0x22,
+	UWB_HOST_REASON_DIAGDEINIT_STUCK                =  0x23,
+	UWB_HOST_REASON_SECURE_BRIDGE_CMD_STUCK         =  0x26,
+	UWB_HOST_REASON_FAILED_TO_SEND_CMD              =  0x27,
+	UWB_HOST_REASON_PERI_RESET_CC_NOT_RCVD          =  0x28,
+	UWB_HOST_REASON_HCI_PRE_SHUTDOWN_CC_NOT_RCVD    =  0x29,
+	UWB_HOST_REASON_FAILED_TO_RECEIVE_SLEEP_IND     =  0x2B,
+	UWB_HOST_REASON_POWER_ON_REGS_STUCK             =  0x2C,
+	UWB_HOST_REASON_RX_THREAD_START_STUCK           =  0x2D,
+	UWB_HOST_REASON_GET_LOCALADDR_STUCK             =  0x2E,
+	UWB_HOST_REASON_OTP_INFO_GET_CMD_STUCK          =  0x2F,
+	UWB_HOST_REASON_FILE_SYSTEM_CALL_STUCK          =  0x30,
+	UWB_HOST_REASON_PROPERTY_GET_STUCK              =  0x31,
+	UWB_HOST_REASON_PROPERTY_SET_STUCK              =  0x32,
+	UWB_HOST_REASON_PERI_RAM_PATCH_READ_STUCK       =  0x33,
+	UWB_HOST_REASON_PERI_NVM_PATCH_READ_STUCK       =  0x34,
+	UWB_HOST_REASON_POWER_IOCTL_STUCK               =  0x36,
+	UWB_HOST_REASON_PERI_PATCH_CONFIG_CMD_STUCK     =  0x37,
+	UWB_HOST_REASON_PERI_PATCH_CONFIG_FAILED        =  0x38,
+	UWB_HOST_REASON_UWB_GETVER_SEND_STUCK           =  0x39,
+	UWB_HOST_REASON_UWB_GETVER_NO_RSP_RCVD          =  0x3A,
+	UWB_HOST_REASON_SOC_NAME_UNKOWN                 =  0x3B,
+	UWB_HOST_REASON_PERI_GETVER_CMD_FAILED          =  0x3C,
+	UWB_HOST_REASON_BAUDRATE_CHANGE_FAILED          =  0x3D,
+	UWB_HOST_REASON_PERI_TLV_DOWNLOAD_FAILED        =  0x3E,
+	UWB_HOST_REASON_PERI_GETBLDINFO_CMD_FAILED      =  0x3F,
+	UWB_HOST_REASON_PERI_RESET_CMD_FAILED           =  0x40,
+	UWB_HOST_REASON_MEMORY_ALLOCATION_FAILED        =  0x42,
+	UWB_HOST_REASON_READ_THREAD_START_FAILED        =  0x43,
+	UWB_HOST_REASON_HW_FLOW_ON_FAILED               =  0x44,
+	UWB_HOST_REASON_PERI_NVM_FILE_NOT_FOUND         =  0x45,
+	UWB_HOST_REASON_UWB_RAM_PATCH_READ_STUCK        =  0x48,
+	UWB_HOST_REASON_UWB_NVM_PATCH_READ_STUCK        =  0x49,
+	UWB_HOST_REASON_UWB_NVM_FILE_NOT_FOUND          =  0x4A,
+	UWB_HOST_REASON_UWB_GETBLDINFO_CMD_FAILED       =  0x4B,
+	UWB_HOST_REASON_UWB_PATCH_DNLD_STUCK            =  0x4C,
+	UWB_HOST_REASON_UWB_NVM_DNLD_STUCK              =  0x4D,
+	UWB_HOST_REASON_UWB_GETBLDINFO_CMD_STUCK        =  0x4E,
+	UWB_HOST_REASON_PERI_ACTIVATE_CMD_STUCK         =  0x4F,
+	UWB_HOST_REASON_PERI_ARBITRATION_CMD_STUCK      =  0x50,
+	UWB_HOST_REASON_PERI_ARBITRATION_NTF_STUCK      =  0x51,
+	UWB_HOST_REASON_INITIALIZATION_FAILED           =  0x52,
+	UWB_HOST_REASON_UWB_RESET_CC_NOT_RCVD           =  0x53,
+	UWB_HOST_REASON_UWB_ACTIVATE_CC_NOT_RCVD        =  0x54,
+	UWB_HOST_REASON_TME_ACTIVATE_CC_NOT_RCVD        =  0x55,
+	UWB_HOST_REASON_Q2SPI_INIT_STUCK                =  0x56,
+	UWB_HOST_REASON_Q2SPI_INIT_FAILED               =  0x57,
+	UWB_HOST_REASON_UWB_TLV_DOWNLOAD_FAILED         =  0x58,
+	UWB_HOST_REASON_UWB_ENHLOG_CMD_STUCK            =  0x59,
+	UWB_HOST_REASON_UWB_GETVER_CMD_FAILED           =  0x5A,
+	UWB_HOST_REASON_UWB_PATCH_CONFIG_CMD_STUCK      =  0x5B,
+	UWB_HOST_REASON_UWB_PATCH_CONFIG_CMD_FAILED     =  0x5C,
+	UWB_HOST_REASON_UWB_RESET_STUCK                 =  0x5D,
+	UWB_HOST_REASON_PERI_ACTIVATE_NTF_STUCK         =  0x5E,
+	UWB_HOST_REASON_UWB_CORE_RESET_CMD_FAILED       =  0x5F,
+	UWB_HOST_REASON_TME_ARBITRATION_CMD_STUCK       =  0x60,
+	UWB_HOST_REASON_TME_ARBITRATION_NTF_STUCK       =  0x61,
+	UWB_HOST_REASON_TME_GETVER_SEND_STUCK           =  0x62,
+	UWB_HOST_REASON_TME_GETVER_NO_RSP_RCVD          =  0x63,
+	UWB_HOST_REASON_TME_GETVER_CMD_FAILED           =  0x64,
+	UWB_HOST_REASON_TME_PATCH_DNLD_STUCK            =  0x65,
+	UWB_HOST_REASON_TME_RESET_STUCK                 =  0x66,
+	UWB_HOST_REASON_TME_GETBLDINFO_CMD_STUCK        =  0x67,
+	UWB_HOST_REASON_TME_GETBLDINFO_CMD_FAILED       =  0x68,
+	UWB_HOST_REASON_TME_RAM_PATCH_READ_STUCK        =  0x69,
+	Q2SPI_REASON_DEFAULT                            =  0xFF
+};
+
+typedef struct {
+  enum UwbSecondaryReasonCode reason;
+  char reasonstr[50];
+} UwbSecondaryReasonMap;
+
+typedef struct {
+  enum UwbPrimaryReasonCode reason;
+  char reasonstr[100];
+} UwbPrimaryReasonMap;
+
+static UwbPrimaryReasonMap uwbPriReasonMap[] = {
+	{UWB_HOST_REASON_DEFAULT_NONE, "Invalid reason"},
+	{UWB_HOST_REASON_PERI_SOC_CRASHED, "Peri SOC crashed"},
+	{UWB_HOST_REASON_UWB_SOC_CRASHED, "UWB SOC crashed"},
+	{UWB_HOST_REASON_PERI_SOC_CRASHED_DIAG_SSR, "Peri SOC crashed with diag initiated SSR"},
+	{UWB_HOST_REASON_UWB_SOC_CRASHED_DIAG_SSR, "UWB SOC crashed with diag initiated SSR"},
+	{UWB_HOST_REASON_INIT_FAILED, "Init failed"},
+	{UWB_HOST_REASON_CLOSE_RCVD_DURING_INIT, "Close received from stack during SOC init"},
+	{UWB_HOST_REASON_ERROR_READING_DATA_FROM_Q2SPI, "Error reading data from Q2SPI"},
+	{UWB_HOST_REASON_WRITE_FAIL_SPCL_BUFF_CRASH_SOC, "Failed to write special bytes to crash SOC"},
+	{UWB_HOST_REASON_RX_THREAD_STUCK, "Rx Thread Stuck"},
+	{UWB_HOST_REASON_SSR_CMD_TIMEDOUT, "SSR due to command timed out"},
+	{UWB_HOST_REASON_SSR_RCVD_LARGE_PKT_FROM_SOC, "Large packet received from SOC"},
+	{UWB_HOST_REASON_SSR_UNABLE_TO_WAKEUP_SOC, "Unable to wake SOC"},
+	{UWB_HOST_REASON_CMD_TIMEDOUT_SOC_WAIT_TIMEOUT, "Command timedout and SOC crash wait timeout"},
+	{UWB_HOST_REASON_INV_BYTES_SOC_WAIT_TIMEOUT,
+		"Invalid bytes received and SOC crash wait timeout"},
+	{UWB_HOST_REASON_SOC_WAKEUP_FAILED_SOC_WAIT_TIMEOUT,
+		"SOC Wakeup failed and SOC crash wait timeout"},
+	{UWB_HOST_REASON_SOC_CRASHED_DIAG_SSR_SOC_WAIT_TIMEOUT,
+		"SOC crashed with diag initiated SSR and SOC wait timeout"},
+	{UWB_HOST_REASON_NONE_SOC_WAIT_TIMEOUT, "Invalid Reason and SOC crash wait timeout"},
+	{UWB_HOST_REASON_SOC_DEINIT_STUCK, "SOC Deinit Stuck"},
+	{UWB_HOST_REASON_SSR_INTERNAL_CMD_TIMEDOUT, "SSR due to internal Command timeout"},
+	{UWB_HOST_REASON_FAILED_TO_SEND_INTERNAL_CMD, "Failed to send internal command"},
+	{UWB_HOST_REASON_SSR_SLEEP_IND_NOT_RCVD, "Failed to receive SLEEP IND during close"},
+	{UWB_HOST_REASON_PERI_CRASH_ON_OTHER_SS, "Peri SOC crashed detected on BT SS"},
+	{UWB_HOST_REASON_DIAG_LOG_API_STUCK, "DIAG log API stuck"}
+};
+
+static UwbSecondaryReasonMap uwbSecReasonMap[] = {
+	{ UWB_SOC_REASON_DEFAULT, "Default"},
+	{ UWB_SOC_REASON_TX_RX_INVALID_PKT, "Tx/Rx Inavlid Packet"},
+	{ UWB_SOC_REASON_TX_RX_INVALID_PKT_LENE, "Tx/Rx Invalid Pkt Len"},
+	{ UWB_SOC_REASON_TX_RX_OVERFLOW_BUFF, "Tx/Rx Overflow Buffer"},
+	{ UWB_SOC_REASON_UNKNOWN, "Unknown"},
+	{ UWB_SOC_REASON_TX_RX_INVALID_PKT_FATAL, "Tx/Rx invalid packet fatal error"},
+	{ UWB_SOC_REASON_TX_RX_INVALID_LEN_FATAL, "Tx/Rx invalid length fatal error"},
+	{ UWB_SOC_REASON_TX_RX_OVERFLOW_BUFF, "Tx/Rx Overflow Buffer"},
+	{ UWB_SOC_REASON_SW_REQUESTED, "SW Requested"},
+	{ UWB_SOC_REASON_STACK_OVERFLOW, "Stack Overflow"},
+	{ UWB_SOC_REASON_EXCEPTION, "Exception"},
+	{ UWB_SOC_REASON_ASSERT, "Assert"},
+	{ UWB_SOC_REASON_TRAP, "Trap"},
+	{ UWB_SOC_REASON_OS_FATAL, "OS Fatal"},
+	{ UWB_SOC_REASON_HCI_RESET, "HCI Reset"},
+	{ UWB_SOC_REASON_PATCH_RESET, "Patch Reset"},
+	{ UWB_SOC_REASON_ABT, "SoC Abort"},
+	{ UWB_SOC_REASON_RAMMASK, "RAM MASK"},
+	{ UWB_SOC_REASON_PREBARK, "PREBARK"},
+	{ UWB_SOC_REASON_BUSERROR, "Bus error"},
+	{ UWB_SOC_REASON_IO_FATAL, "IO fatal eror"},
+	{ UWB_SOC_REASON_SSR_CMD, "SSR CMD"},
+	{ UWB_SOC_REASON_POWERON, "Power ON"},
+	{ UWB_SOC_REASON_WATCHDOG, "Watchdog"},
+	{ UWB_SOC_REASON_RAMMASK_RGN1, "RAMMASK RGN1"},
+	{ UWB_SOC_REASON_RAMMASK_RGN0, "RAMMASK RGN0"},
+	{ UWB_SOC_REASON_Q6_WATCHDOG, "Q6 Watchdog"},
+	{ UWB_SOC_REASON_ZEALIS_RAM_MASK_RGN0, "ZEALIS RAM MASK RGN0"},
+	{ UWB_SOC_REASON_ZEALIS_RAM_MASK_RGN1, "ZEALIS RAM MASK RGN1"},
+	{ UWB_SOC_REASON_APSS_RESET, "APSS reset"},
+	{ UWB_SOC_REASON_TIME_RESET, "Time reset"},
+	{ UWB_SOC_REASON_AUDIOSS_RESET, "Audioss reset"},
+	{ UWB_SOC_REASON_HOST_WARMRESET, "Host warm reset"},
+	{ UWB_SOC_REASON_HOST_NMI_INIT, "Host NMI init"},
+	{ UWB_SOC_REASON_PANIC_FAULT, "Panic Fault"},
+	{ UWB_SOC_REASON_EARLY_TRAP, "Early Trap"},
+	{ UWB_SOC_REASON_INSTR_ADDR_MISALGIN, "Instruction Address Misalign"},
+	{ UWB_SOC_REASON_INSTR_ACCESS_FAULT, "Instruction Access Fault"},
+	{ UWB_SOC_REASON_ILLEGAL_INSTR, "Illegal Instruction"},
+	{ UWB_SOC_REASON_BREAKPOINT_EXCEPTION, "Breakpoint Exception"},
+	{ UWB_SOC_REASON_LOAD_ADDR_MISALIGN, "Load Address Misalign"},
+	{ UWB_SOC_REASON_LOAD_ACCESS_FAULT, "Load Access Fault"},
+	{ UWB_SOC_REASON_STORE_ADDR_MISALGN, "Store Address Misalign"},
+	{ UWB_SOC_REASON_STORE_ACCESS_FAULT, "Store Access Fault"},
+	{ UWB_SOC_REASON_ECALL_UMODE, "Ecall Umode"},
+	{ UWB_SOC_REASON_ECALL_MMODE, "Ecall Mmode"},
+	{ UWB_SOC_REASON_STACK_UNDERFLOW, "Stack Underflow"},
+	{ UWB_SOC_REASON_MACHINE_EXIT_INT, "Machine Exit Int"},
+	{ UWB_SOC_REASON_PERF_MONITOR_OVERFLOW, "Perf Monitor Overflow"},
+	{ UWB_SOC_REASON_EXT_SUBSYS_RESET, "Ext Subsystem Reset"},
+	{ UWB_SOC_REASON_IPC_STALL, "IPC Stall"},
+	{ UWB_SOC_REASON_PEER_CPU0_NMI, "Crash in Peri CPU"},
+	{ UWB_SOC_REASON_PEER_CPU1_NMI, "Crash in BT CPU"},
+	{ UWB_SOC_REASON_PEER_CPU2_NMI, "Crash in UWB CPU"},
+	{ UWB_SOC_REASON_INVALID_STACK, "Invalid Stack"},
+	{ UWB_SOC_REASON_INVALID_MCI_MSG_RCVD, "Invalid MCI message received"},
+	{ UWB_HOST_REASON_PERI_GETVER_SEND_STUCK, "PeriGetVerSendStuck"},
+	{ UWB_HOST_REASON_UWB_GETVER_SEND_STUCK, "UwbGetVerSendStuck"},
+	{ UWB_HOST_REASON_TME_GETVER_SEND_STUCK, "TmeGetVerSendStuck"},
+	{ UWB_HOST_REASON_PERI_GETVER_NO_RSP_RCVD, "PeriGetVerNoRspRcvd"},
+	{ UWB_HOST_REASON_UWB_GETVER_NO_RSP_RCVD, "UwbGetVerNoRspRcvd"},
+	{ UWB_HOST_REASON_TME_GETVER_NO_RSP_RCVD, "TmeGetVerNoRspRcvd"},
+	{ UWB_HOST_REASON_PERI_PATCH_DNLD_STUCK, "PeriPatchDnldStuck"},
+	{ UWB_HOST_REASON_UWB_PATCH_DNLD_STUCK, "UwbPatchDnldStuck"},
+	{ UWB_HOST_REASON_TME_PATCH_DNLD_STUCK, "TmePatchDnldStuck"},
+	{ UWB_HOST_REASON_PERI_GETBOARDID_CMD_STUCK, "PeriGetBoardIdStuck"},
+	{ UWB_HOST_REASON_PERI_NVM_DNLD_STUCK, "PeriNvmDnldStuck"},
+	{ UWB_HOST_REASON_UWB_NVM_DNLD_STUCK, "UwbNvmDnldStuck"},
+	{ UWB_HOST_REASON_PERI_RESET_STUCK, "PeriResetStuck"},
+	{ UWB_HOST_REASON_UWB_RESET_STUCK, "UwbResetStuck"},
+	{ UWB_HOST_REASON_TME_RESET_STUCK, "TmeResetStuck"},
+	{ UWB_HOST_REASON_PERI_GETBLDINFO_CMD_STUCK, "PeriGetBldInfoCmdStuck"},
+	{ UWB_HOST_REASON_UWB_GETBLDINFO_CMD_STUCK, "UwbGetBldInfoCmdStuck"},
+	{ UWB_HOST_REASON_TME_GETBLDINFO_CMD_STUCK, "TmeGetBldInfoCmdStuck"},
+	{ UWB_HOST_REASON_PERI_ENHLOG_CMD_STUCK, "Peri EnhLogCmdStuck"},
+	{ UWB_HOST_REASON_UWB_ENHLOG_CMD_STUCK, "Uwb EnhLogCmdStuck"},
+	{ UWB_HOST_REASON_DIAGINIT_STUCK, "DiagInitStuck"},
+	{ UWB_HOST_REASON_DIAGDEINIT_STUCK, "DiagDeinitStuck"},
+	{ UWB_HOST_REASON_FAILED_TO_SEND_CMD, "Failed to send internal cmd"},
+	{ UWB_HOST_REASON_PERI_RESET_CC_NOT_RCVD, "Peri Reset Cmd CC Not Rcvd"},
+	{ UWB_HOST_REASON_UWB_RESET_CC_NOT_RCVD, "UWB Reset Cmd CC Not Rcvd"},
+	{ UWB_HOST_REASON_UWB_ACTIVATE_CC_NOT_RCVD, "UWB Activate Cmd CC Not Rcvd"},
+	{ UWB_HOST_REASON_TME_ACTIVATE_CC_NOT_RCVD, "TME DeActivate Cmd CC Not Rcvd"},
+	{ UWB_HOST_REASON_POWER_ON_REGS_STUCK, "SoC Power ON Sequence stuck"},
+	{ UWB_HOST_REASON_POWER_IOCTL_STUCK, "Power driver IOCTL stuck"},
+	{ UWB_HOST_REASON_RX_THREAD_START_STUCK, "RX thread start stuck"},
+	{ UWB_HOST_REASON_OTP_INFO_GET_CMD_STUCK, "Get OTP info. cmd stuck"},
+	{ UWB_HOST_REASON_FILE_SYSTEM_CALL_STUCK, "FILE system call stuck"},
+	{ UWB_HOST_REASON_PROPERTY_GET_STUCK, "Property get call stuck"},
+	{ UWB_HOST_REASON_PROPERTY_SET_STUCK, "Property set call stuck"},
+	{ UWB_HOST_REASON_PERI_RAM_PATCH_READ_STUCK, "Peri RAM patch open/read stuck"},
+	{ UWB_HOST_REASON_UWB_RAM_PATCH_READ_STUCK, "UWB RAM patch open/read stuck"},
+	{ UWB_HOST_REASON_PERI_NVM_PATCH_READ_STUCK, "Peri NVM file open/read stuck"},
+	{ UWB_HOST_REASON_UWB_NVM_PATCH_READ_STUCK, "UWB NVM file open/read stuck"},
+	{ UWB_HOST_REASON_PERI_PATCH_CONFIG_CMD_STUCK, "Peri Patch config cmd stuck"},
+	{ UWB_HOST_REASON_PERI_PATCH_CONFIG_FAILED, "Peri Patch config cmd failed"},
+	{ UWB_HOST_REASON_UWB_PATCH_CONFIG_CMD_STUCK, "Uwb Patch config cmd stuck"},
+	{ UWB_HOST_REASON_UWB_PATCH_CONFIG_CMD_FAILED, "Uwb Patch config cmd stuck"},
+	{ UWB_HOST_REASON_SOC_NAME_UNKOWN, "SoC name unkown"},
+	{ UWB_HOST_REASON_PERI_TLV_DOWNLOAD_FAILED, "Peri TLV/NVM download failed"},
+	{ UWB_HOST_REASON_PERI_GETBLDINFO_CMD_FAILED, "Peri FW build info. cmd failed"},
+	{ UWB_HOST_REASON_UWB_GETBLDINFO_CMD_FAILED, "UWB build info. cmd failed"},
+	{ UWB_HOST_REASON_PERI_RESET_CMD_FAILED, "HCI Peri RESET cmd failed"},
+	{ UWB_HOST_REASON_UWB_CORE_RESET_CMD_FAILED, "UWB Core RESET cmd failed"},
+	{ UWB_HOST_REASON_MEMORY_ALLOCATION_FAILED, "Memory allocation failed"},
+	{ UWB_HOST_REASON_READ_THREAD_START_FAILED, "Read thread start failed"},
+	{ UWB_HOST_REASON_HW_FLOW_ON_FAILED, "HW Flow ON failed"},
+	{ UWB_HOST_REASON_PERI_ACTIVATE_CMD_STUCK, "Peri actvate cmd stuck"},
+	{ UWB_HOST_REASON_PERI_ACTIVATE_NTF_STUCK, "Peri activate ntf stuck"},
+	{ UWB_HOST_REASON_PERI_ARBITRATION_CMD_STUCK, "Peri arbitration cmd stuck"},
+	{ UWB_HOST_REASON_PERI_ARBITRATION_NTF_STUCK, "Peri arbitration ntf stuck"},
+	{ UWB_HOST_REASON_INITIALIZATION_FAILED, "Initialization Failed"},
+	{ UWB_HOST_REASON_Q2SPI_INIT_STUCK, "Q2SPI Init stuck"},
+	{ UWB_HOST_REASON_Q2SPI_INIT_FAILED, "Q2SPI Init Failed"},
+	{ UWB_HOST_REASON_UWB_TLV_DOWNLOAD_FAILED, "Uwb TLV/NVM download failed"},
+	{ Q2SPI_REASON_DEFAULT, "Q2SPI reason Default"},
+};
+
+struct log_index {
+	int init;
+	int crash;
+};
+
+struct vreg_data {
+	struct regulator *reg;  /* voltage regulator handle */
+	const char *name;       /* regulator name */
+	u32 min_vol;            /* min voltage level */
+	u32 max_vol;            /* max voltage level */
+	u32 load_curr;          /* current */
+	bool is_enabled;        /* is this regulator enabled? */
+	bool is_retention_supp; /* does this regulator support retention mode */
+	struct log_index indx;  /* Index for reg. w.r.t init & crash */
+};
+
+struct pwr_data {
+	char compatible[32];
+	struct vreg_data *bt_vregs;
+	int bt_num_vregs;
+	struct vreg_data *uwb_vregs;
+	int uwb_num_vregs;
+	struct vreg_data *platform_vregs;
+	int platform_num_vregs;
+};
+
+struct bt_power_clk_data {
+	struct clk *clk;  /* clock regulator handle */
+	const char *name; /* clock name */
+	bool is_enabled;  /* is this clock enabled? */
+};
+
+struct btpower_state_machine {
+	struct mutex state_machine_lock;
+	enum power_states power_state;
+	enum retention_states retention_mode;
+	enum grant_states grant_state;
+	enum grant_states grant_pending;
+};
+
+#define BTPWR_MAX_REQ         BT_MAX_PWR_STATE
+
+/*
+ * Platform data for the bluetooth power driver.
+ */
+struct platform_pwr_data {
+	struct platform_device *pdev;
+	int bt_gpio_sys_rst;                   /* Bluetooth reset gpio */
+	int wl_gpio_sys_rst;                   /* Wlan reset gpio */
+	int bt_gpio_sw_ctrl;                   /* Bluetooth sw_ctrl gpio */
+	int bt_gpio_debug;                     /* Bluetooth debug gpio */
+	unsigned int wlan_sw_ctrl_gpio;        /* Wlan switch control gpio*/
+#ifdef CONFIG_MSM_BT_OOBS
+	int bt_gpio_dev_wake;                  /* Bluetooth bt_wake */
+	int bt_gpio_host_wake;                 /* Bluetooth bt_host_wake */
+	int irq;                               /* Bluetooth host_wake IRQ */
+#endif
+	int sw_cntrl_gpio;
+	int xo_gpio_clk;                       /* XO clock gpio*/
+	struct device *slim_dev;
+	struct vreg_data *bt_vregs;
+	struct vreg_data *uwb_vregs;
+	struct vreg_data *platform_vregs;
+	struct bt_power_clk_data *bt_chip_clk; /* bluetooth reference clock */
+	int (*power_setup)(int core, int id); /* Bluetooth power setup function */
+	char compatible[32]; /*Bluetooth SoC name */
+	int bt_num_vregs;
+	int uwb_num_vregs;
+	int platform_num_vregs;
+	struct mbox_client mbox_client_data;
+	struct mbox_chan *mbox_chan;
+	const char *vreg_ipa;
+	bool is_ganges_dt;
+	int pdc_init_table_len;
+	const char **pdc_init_table;
+	int bt_device_type;
+	bool sec_peri_feature_disable;
+	int bt_sec_hw_disable;
+#ifdef CONFIG_MSM_BT_OOBS
+	struct file *reffilp_obs;
+	struct task_struct *reftask_obs;
+#endif
+	struct task_struct *reftask;
+	struct task_struct *reftask_bt;
+	struct task_struct *reftask_uwb;
+	struct btpower_state_machine btpower_state;
+	enum ssr_states sub_state;
+	enum ssr_states wrkq_signal_state;
+	struct workqueue_struct *workq;
+	struct device_node *bt_of_node;
+	struct device_node *uwb_of_node;
+	struct work_struct bt_wq;
+	struct work_struct uwb_wq;
+	wait_queue_head_t rsp_wait_q[BTPWR_MAX_REQ];
+	int wait_status[BTPWR_MAX_REQ];
+	struct work_struct wq_pwr_voting;
+	struct sk_buff_head rxq;
+	struct mutex pwr_mtx;
+};
+
+int btpower_register_slimdev(struct device *dev);
+int btpower_get_chipset_version(void);
+int btpower_aop_mbox_init(struct platform_pwr_data *pdata);
+int bt_aop_pdc_reconfig(struct platform_pwr_data *pdata);
+
+#define WLAN_SW_CTRL_GPIO       "qcom,wlan-sw-ctrl-gpio"
+#define BT_CMD_SLIM_TEST            0xbfac
+#define BT_CMD_PWR_CTRL             0xbfad
+#define BT_CMD_CHIPSET_VERS         0xbfae
+#define BT_CMD_GET_CHIPSET_ID       0xbfaf
+#define BT_CMD_CHECK_SW_CTRL        0xbfb0
+#define BT_CMD_GETVAL_POWER_SRCS    0xbfb1
+#define BT_CMD_SET_IPA_TCS_INFO     0xbfc0
+#define BT_CMD_KERNEL_PANIC         0xbfc1
+#define UWB_CMD_KERNEL_PANIC        0xbfc2
+#define UWB_CMD_PWR_CTRL            0xbfe1
+#define BT_CMD_REGISTRATION	        0xbfe2
+#define UWB_CMD_REGISTRATION        0xbfe3
+#define BT_CMD_ACCESS_CTRL          0xbfe4
+#define UWB_CMD_ACCESS_CTRL         0xbfe5
+
+#ifdef CONFIG_MSM_BT_OOBS
+#define BT_CMD_OBS_VOTE_CLOCK		0xbfd1
+
+
+/**
+ * enum btpower_obs_param: OOBS low power param
+ * @BTPOWER_OBS_CLK_OFF: Transport bus is no longer acquired
+ * @BTPOWER_OBS_CLK_ON: Acquire transport bus for either transmitting or receiving
+ * @BTPOWER_OBS_DEV_OFF: Bluetooth is released because of no more transmission
+ * @BTPOWER_OBS_DEV_ON: Wake up the Bluetooth controller for transmission
+ */
+enum btpower_obs_param {
+	BTPOWER_OBS_CLK_OFF = 0,
+	BTPOWER_OBS_CLK_ON,
+	BTPOWER_OBS_DEV_OFF,
+	BTPOWER_OBS_DEV_ON,
+};
+#endif
+
+#endif /* __LINUX_BLUETOOTH_POWER_H */

+ 13 - 0
qcom/opensource/bt-kernel/pwr/Kconfig

@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config MSM_BT_POWER
+	tristate "MSM Bluetooth Power Control"
+	depends on ARCH_QCOM
+	help
+		MSM Bluetooth Power control driver.
+		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.
+
+		Say Y here to compile support for Bluetooth Power driver
+		into the kernel or say M to compile as a module.

+ 3 - 0
qcom/opensource/bt-kernel/pwr/Makefile

@@ -0,0 +1,3 @@
+ccflags-y += -I$(BT_ROOT)/include
+
+obj-$(CONFIG_MSM_BT_POWER)  += btpower.o

+ 2779 - 0
qcom/opensource/bt-kernel/pwr/btpower.c

@@ -0,0 +1,2779 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/*
+ * Bluetooth Power Switch Module
+ * controls power to external Bluetooth device
+ * with interface to power management device
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/skbuff.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/of_device.h>
+#include <soc/qcom/cmd-db.h>
+#include <linux/kdev_t.h>
+#include <linux/refcount.h>
+#include <linux/idr.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/pinctrl/qcom-pinctrl.h>
+#include "btpower.h"
+#if (defined CONFIG_BT_SLIM)
+#include "btfm_slim.h"
+#endif
+#include <linux/fs.h>
+
+#ifdef CONFIG_BT_HW_SECURE_DISABLE
+#include "linux/smcinvoke_object.h"
+#include "linux/IClientEnv.h"
+
+#define PERISEC_HW_STATE_UID 0x108
+#define PERISEC_HW_OP_GET_STATE 1
+#define PERISEC_HW_BLUETOOTH_UID 0x502
+#define PERISEC_FEATURE_NOT_SUPPORTED 12
+#define PERISEC_PERIPHERAL_NOT_FOUND 10
+#endif
+
+#define PWR_SRC_NOT_AVAILABLE -2
+#define DEFAULT_INVALID_VALUE -1
+#define PWR_SRC_INIT_STATE_IDX 0
+#define BTPOWER_MBOX_MSG_MAX_LEN 64
+#define BTPOWER_MBOX_TIMEOUT_MS 1000
+#define XO_CLK_RETRY_COUNT_MAX 5
+#define MAX_PROP_SIZE 32
+#define BTPOWER_CONFIG_MAX_TIMEOUT 600
+
+#define SIGIO_OOBS_SINGAL        0x00010000
+#define SIGIO_INTERACTION_SIGNAL 0x00020000
+#define SIGIO_SOC_ACCESS_SIGNAL  0x00040000
+
+#define SIGIO_GPIO_HIGH          0x00000001
+#define SIGIO_GPIO_LOW           0x00000000
+#define SIGIO_SSR_ON_UWB         0x00000001
+#define SIGIO_UWB_SSR_COMPLETED  0x00000002
+
+#define CRASH_REASON_NOT_FOUND  ((char *)"Crash reason not found")
+
+#define PERI_SS	(0x00)
+#define BT_SS	(0x01)
+#define UWB_SS	(0x02)
+#define TME_SS	(0x03)
+
+/**
+ * enum btpower_vreg_param: Voltage regulator TCS param
+ * @BTPOWER_VREG_VOLTAGE: Provides voltage level to be configured in TCS
+ * @BTPOWER_VREG_MODE: Regulator mode
+ * @BTPOWER_VREG_TCS_ENABLE: Set Voltage regulator enable config in TCS
+ */
+enum btpower_vreg_param {
+	BTPOWER_VREG_VOLTAGE,
+	BTPOWER_VREG_MODE,
+	BTPOWER_VREG_ENABLE,
+};
+
+/**
+ * enum btpower_tcs_seq: TCS sequence ID for trigger
+ * BTPOWER_TCS_UP_SEQ: TCS Sequence based on up trigger / Wake TCS
+ * BTPOWER_TCS_DOWN_SEQ: TCS Sequence based on down trigger / Sleep TCS
+ * BTPOWER_TCS_ALL_SEQ: Update for both up and down triggers
+ */
+enum btpower_tcs_seq {
+	BTPOWER_TCS_UP_SEQ,
+	BTPOWER_TCS_DOWN_SEQ,
+	BTPOWER_TCS_ALL_SEQ,
+};
+
+enum power_src_pos {
+	BT_RESET_GPIO = PWR_SRC_INIT_STATE_IDX,
+	BT_SW_CTRL_GPIO,
+	BT_VDD_AON_LDO,
+	BT_VDD_DIG_LDO,
+	BT_VDD_RFA1_LDO,
+	BT_VDD_RFA2_LDO,
+	BT_VDD_ASD_LDO,
+	BT_VDD_XTAL_LDO,
+	BT_VDD_PA_LDO,
+	BT_VDD_CORE_LDO,
+	BT_VDD_IO_LDO,
+	BT_VDD_LDO,
+	BT_VDD_RFA_0p8,
+	BT_VDD_RFACMN,
+	BT_VDD_ANT_LDO,
+	// these indexes GPIOs/regs value are fetched during crash.
+	BT_RESET_GPIO_CURRENT,
+	BT_SW_CTRL_GPIO_CURRENT,
+	BT_VDD_AON_LDO_CURRENT,
+	BT_VDD_DIG_LDO_CURRENT,
+	BT_VDD_RFA1_LDO_CURRENT,
+	BT_VDD_RFA2_LDO_CURRENT,
+	BT_VDD_ASD_LDO_CURRENT,
+	BT_VDD_XTAL_LDO_CURRENT,
+	BT_VDD_PA_LDO_CURRENT,
+	BT_VDD_CORE_LDO_CURRENT,
+	BT_VDD_IO_LDO_CURRENT,
+	BT_VDD_LDO_CURRENT,
+	BT_VDD_RFA_0p8_CURRENT,
+	BT_VDD_RFACMN_CURRENT,
+	BT_VDD_IPA_2p2,
+	BT_VDD_IPA_2p2_CURRENT,
+	BT_VDD_ANT_LDO_CURRENT,
+	/* The below bucks are voted for HW WAR on some platform which supports
+	 * WNC39xx.
+	 */
+	BT_VDD_SMPS,
+	BT_VDD_SMPS_CURRENT,
+	/* New entries need to be added before PWR_SRC_SIZE.
+	 * Its hold the max size of power sources states.
+	 */
+	BT_POWER_SRC_SIZE,
+};
+
+// Regulator structure for QCA6174/QCA9377/QCA9379 BT SoC series
+static struct vreg_data bt_vregs_info_qca61x4_937x[] = {
+	{NULL, "qcom,bt-vdd-aon", 928000, 928000, 0, false, false,
+		{BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-io", 1710000, 3460000, 0, false, false,
+		{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-core", 3135000, 3465000, 0, false, false,
+		{BT_VDD_CORE_LDO, BT_VDD_CORE_LDO_CURRENT}},
+};
+
+// Regulator structure for QCA6390,QCA6490 and WCN6750 BT SoC series
+static struct vreg_data bt_vregs_info_qca6xx0[] = {
+	{NULL, "qcom,bt-vdd-io",      1800000, 1800000, 0, false, true,
+		{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-aon",     966000,  966000,  0, false, true,
+		{BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfacmn",  950000,  950000,  0, false, true,
+		{BT_VDD_RFACMN, BT_VDD_RFACMN_CURRENT}},
+	/* BT_CX_MX */
+	{NULL, "qcom,bt-vdd-dig",      966000,  966000,  0, false, true,
+		{BT_VDD_DIG_LDO, BT_VDD_DIG_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa-0p8",  950000,  952000,  0, false, true,
+		{BT_VDD_RFA_0p8, BT_VDD_RFA_0p8_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa1",     1900000, 1900000, 0, false, true,
+		{BT_VDD_RFA1_LDO, BT_VDD_RFA1_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa2",     1900000, 1900000, 0, false, true,
+		{BT_VDD_RFA2_LDO, BT_VDD_RFA2_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-asd",      2800000, 2800000, 0, false, true,
+		{BT_VDD_ASD_LDO, BT_VDD_ASD_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-ipa-2p2",  2200000, 2210000, 0, false, true,
+		{BT_VDD_IPA_2p2, BT_VDD_IPA_2p2_CURRENT}},
+};
+
+// Regulator structure for kiwi BT SoC series
+static struct vreg_data bt_vregs_info_kiwi[] = {
+	{NULL, "qcom,bt-vdd18-aon",      1800000, 1800000, 0, false, true,
+		{BT_VDD_LDO, BT_VDD_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd12-io",      1200000, 1200000, 0, false, true,
+		{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+	{NULL, "qcom,bt-ant-ldo",  1776000, 1776000, 0, false, true,
+		{BT_VDD_ANT_LDO, BT_VDD_ANT_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-dig",      950000,  950000,  0, false, true,
+		{BT_VDD_DIG_LDO, BT_VDD_DIG_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-aon",     950000,  950000,  0, false, true,
+		{BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfaOp8",  950000,  952000,  0, false, true,
+		{BT_VDD_RFA_0p8, BT_VDD_RFA_0p8_CURRENT}},
+	/* BT_CX_MX */
+	{NULL, "qcom,bt-vdd-rfa2",     1900000, 1900000, 0, false, true,
+		{BT_VDD_RFA2_LDO, BT_VDD_RFA2_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa1",     1350000, 1350000, 0, false, true,
+		{BT_VDD_RFA1_LDO, BT_VDD_RFA1_LDO_CURRENT}},
+};
+
+// Regulator structure for kiwi BT SoC series
+static struct vreg_data bt_vregs_info_peach[] = {
+	{NULL, "qcom,bt-vdd18-aon",      1800000, 1800000, 0, false, true,
+		{BT_VDD_LDO, BT_VDD_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd12-io",      1200000, 1200000, 0, false, true,
+		{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+	{NULL, "qcom,bt-ant-ldo",  1776000, 1776000, 0, false, true,
+		{BT_VDD_ANT_LDO, BT_VDD_ANT_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-dig",      950000,  950000,  0, false, true,
+		{BT_VDD_DIG_LDO, BT_VDD_DIG_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-aon",     950000,  950000,  0, false, true,
+		{BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfaOp8",  950000,  952000,  0, false, true,
+		{BT_VDD_RFA_0p8, BT_VDD_RFA_0p8_CURRENT}},
+	/* BT_CX_MX */
+	{NULL, "qcom,bt-vdd-rfa2",     1900000, 1900000, 0, false, true,
+		{BT_VDD_RFA2_LDO, BT_VDD_RFA2_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa1",     1350000, 1350000, 0, false, true,
+		{BT_VDD_RFA1_LDO, BT_VDD_RFA1_LDO_CURRENT}},
+};
+
+// Regulator structure for WCN399x BT SoC series
+static struct pwr_data bt_vreg_info_wcn399x = {
+	.compatible = "qcom,wcn3990",
+	.bt_vregs = (struct vreg_data []) {
+		{NULL, "qcom,bt-vdd-smps", 984000,  984000, 0, false, false,
+			{BT_VDD_SMPS, BT_VDD_SMPS_CURRENT}},
+		{NULL, "qcom,bt-vdd-io",   1700000, 1900000, 0, false, false,
+			{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+		{NULL, "qcom,bt-vdd-core", 1304000, 1304000, 0, false, false,
+			{BT_VDD_CORE_LDO, BT_VDD_CORE_LDO_CURRENT}},
+		{NULL, "qcom,bt-vdd-pa",   3000000, 3312000, 0, false, false,
+			{BT_VDD_PA_LDO, BT_VDD_PA_LDO_CURRENT}},
+		{NULL, "qcom,bt-vdd-xtal", 1700000, 1900000, 0, false, false,
+			{BT_VDD_XTAL_LDO, BT_VDD_XTAL_LDO_CURRENT}},
+	},
+	.bt_num_vregs = 5,
+};
+
+static struct pwr_data bt_vreg_info_qca6174 = {
+	.compatible = "qcom,qca6174",
+	.bt_vregs = bt_vregs_info_qca61x4_937x,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_qca61x4_937x),
+};
+
+static struct pwr_data bt_vreg_info_qca6390 = {
+	.compatible = "qcom,qca6390",
+	.bt_vregs = bt_vregs_info_qca6xx0,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_qca6xx0),
+};
+
+static struct pwr_data bt_vreg_info_qca6490 = {
+	.compatible = "qcom,qca6490",
+	.bt_vregs = bt_vregs_info_qca6xx0,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_qca6xx0),
+};
+
+static struct pwr_data bt_vreg_info_kiwi = {
+	.compatible = "qcom,kiwi",
+	.bt_vregs = bt_vregs_info_kiwi,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_kiwi),
+};
+
+static struct pwr_data bt_vreg_info_kiwi_no_share_ant_power = {
+	.compatible = "qcom,kiwi-no-share-ant-power",
+	.bt_vregs = bt_vregs_info_kiwi,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_kiwi),
+};
+
+static struct pwr_data bt_vreg_info_converged = {
+	.compatible = "qcom,bt-qca-converged",
+	.bt_vregs = bt_vregs_info_kiwi,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_kiwi),
+};
+
+static struct pwr_data bt_vreg_info_wcn6750 = {
+	.compatible = "qcom,wcn6750-bt",
+	.bt_vregs = bt_vregs_info_qca6xx0,
+	.bt_num_vregs = ARRAY_SIZE(bt_vregs_info_qca6xx0),
+};
+
+static struct pwr_data bt_vreg_info_peach = {
+	.compatible = "qcom,peach-bt",
+	.platform_vregs = bt_vregs_info_peach,
+	.platform_num_vregs = ARRAY_SIZE(bt_vregs_info_peach),
+	//.uwb_vregs = uwb_vregs_info,
+	//.bt_vregs = platform_vregs_info,
+	//.uwb_num_vregs = ARRAY_SIZE(uwb_vregs_info),
+	//.bt_num_vregs = ARRAY_SIZE(platform_vregs_info),
+};
+
+static const struct of_device_id bt_power_match_table[] = {
+	{	.compatible = "qcom,qca6174", .data = &bt_vreg_info_qca6174},
+	{	.compatible = "qcom,wcn3990", .data = &bt_vreg_info_wcn399x},
+	{	.compatible = "qcom,qca6390", .data = &bt_vreg_info_qca6390},
+	{	.compatible = "qcom,qca6490", .data = &bt_vreg_info_qca6490},
+	{	.compatible = "qcom,kiwi",    .data = &bt_vreg_info_kiwi},
+	{	.compatible = "qcom,kiwi-no-share-ant-power",
+			.data = &bt_vreg_info_kiwi_no_share_ant_power},
+	{	.compatible = "qcom,wcn6750-bt", .data = &bt_vreg_info_wcn6750},
+	{	.compatible = "qcom,bt-qca-converged", .data = &bt_vreg_info_converged},
+	{	.compatible = "qcom,peach-bt", .data = &bt_vreg_info_peach},
+	{},
+};
+
+static int btpower_enable_ipa_vreg(struct platform_pwr_data *pdata);
+static struct platform_pwr_data *pwr_data;
+static bool previous;
+static struct class *bt_class;
+static int bt_major;
+static int soc_id;
+static bool probe_finished;
+struct mutex pwr_release;
+
+static void bt_power_vote(struct work_struct *work);
+
+static struct {
+	int platform_state[BT_POWER_SRC_SIZE];
+	int bt_state[BT_POWER_SRC_SIZE];
+	int uwb_state[BT_POWER_SRC_SIZE];
+} power_src;
+
+struct Crash_struct {
+//	char SubSystem[10];
+	char PrimaryReason[50];
+	char SecondaryReason[100];
+} CrashInfo;
+
+#ifdef CONFIG_BT_HW_SECURE_DISABLE
+int perisec_cnss_bt_hw_disable_check(struct platform_pwr_data *plat_priv)
+{
+	struct Object client_env;
+	struct Object app_object;
+	int bt_uid = PERISEC_HW_BLUETOOTH_UID;
+	union ObjectArg obj_arg[2] = {{{0, 0}}};
+	int ret;
+	u8 state = 0;
+
+	/* Once this flag is set, secure peripheral feature
+	 * will not be supported till next reboot
+	 */
+	if (plat_priv->sec_peri_feature_disable)
+		return 0;
+
+	/* get rootObj */
+	ret = get_client_env_object(&client_env);
+	if (ret) {
+		pr_err("Failed to get client_env_object, ret: %d\n", ret);
+		goto end;
+	}
+	ret = IClientEnv_open(client_env, PERISEC_HW_STATE_UID, &app_object);
+	if (ret) {
+		pr_err("Failed to get app_object, ret: %d\n",  ret);
+		if (ret == PERISEC_FEATURE_NOT_SUPPORTED) {
+			ret = 0; /* Do not Assert */
+			plat_priv->sec_peri_feature_disable = true;
+			pr_err("Secure HW feature not supported\n");
+		}
+		goto exit_release_clientenv;
+	}
+
+	obj_arg[0].b = (struct ObjectBuf) {&bt_uid, sizeof(u32)};
+	obj_arg[1].b = (struct ObjectBuf) {&state, sizeof(u8)};
+	ret = Object_invoke(app_object, PERISEC_HW_OP_GET_STATE, obj_arg,
+			    ObjectCounts_pack(1, 1, 0, 0));
+
+	pr_err("SMC invoke ret: %d state: %d\n", ret, state);
+	if (ret) {
+		if (ret == PERISEC_PERIPHERAL_NOT_FOUND) {
+			ret = 0; /* Do not Assert */
+			plat_priv->sec_peri_feature_disable = true;
+			pr_err("Secure HW mode is not updated. Peripheral not found\n");
+		}
+	} else {
+		if (state == 1)
+			plat_priv->bt_sec_hw_disable = 1;
+		else
+			plat_priv->bt_sec_hw_disable = 0;
+	}
+	Object_release(app_object);
+
+exit_release_clientenv:
+	Object_release(client_env);
+end:
+	if (ret) {
+		pr_err("SecMode:Unable to get sec mode BT Hardware status\n");
+	}
+	return ret;
+}
+#else
+int perisec_cnss_bt_hw_disable_check(struct platform_pwr_data *plat_priv)
+{
+	return 0;
+}
+#endif
+
+
+#ifdef CONFIG_MSM_BT_OOBS
+static void btpower_uart_transport_locked(struct platform_pwr_data *drvdata,
+					bool locked)
+{
+	pr_err("%s: %s\n", __func__, (locked ? "busy" : "idle"));
+}
+
+static irqreturn_t btpower_host_wake_isr(int irq, void *data)
+{
+	struct platform_pwr_data *drvdata = data;
+	struct kernel_siginfo siginfo;
+	int rc = 0;
+	int host_waking = SIGIO_OOBS_SINGAL;
+
+	if (gpio_get_value(drvdata->bt_gpio_host_wake))
+		host_waking |= SIGIO_GPIO_HIGH;
+	else
+		host_waking |= SIGIO_GPIO_LOW;
+
+	pr_err("%s: bt-hostwake-gpio(%d) IRQ(%d) value(%d)\n", __func__,
+		drvdata->bt_gpio_host_wake, drvdata->irq, host_waking);
+
+	if (drvdata->reftask_bt == NULL) {
+		pr_err("%s: ignore BT-HOSTWAKE IRQ\n", __func__);
+		return IRQ_HANDLED;
+	}
+
+	// Sending signal to HAL layer
+	memset(&siginfo, 0, sizeof(siginfo));
+	siginfo.si_signo = SIGIO;
+	siginfo.si_code = SI_QUEUE;
+	siginfo.si_int = host_waking;
+	rc = send_sig_info(siginfo.si_signo, &siginfo, drvdata->reftask_bt);
+	if (rc < 0) {
+		pr_err("%s: failed (%d) to send SIG to HAL(%d)\n", __func__,
+			rc, drvdata->reftask_bt->pid);
+	}
+	return IRQ_HANDLED;
+}
+#endif
+
+static int vreg_configure(struct vreg_data *vreg, bool retention)
+{
+	int rc = 0;
+
+	if ((vreg->min_vol != 0) && (vreg->max_vol != 0)) {
+		rc = regulator_set_voltage(vreg->reg,
+						(retention ? 0: vreg->min_vol),
+						vreg->max_vol);
+		if (rc < 0) {
+			pr_err("%s: regulator_set_voltage(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+			return rc;
+		}
+	}
+
+	if (vreg->load_curr >= 0) {
+		rc = regulator_set_load(vreg->reg,
+				(retention ? 0 : vreg->load_curr));
+		if (rc < 0) {
+			pr_err("%s: regulator_set_load(%s) failed rc=%d\n",
+			__func__, vreg->name, rc);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int vreg_enable(struct vreg_data *vreg)
+{
+	int rc = 0;
+
+	pr_debug("%s: vreg_en for : %s\n", __func__, vreg->name);
+
+	if (!vreg->is_enabled) {
+		if (vreg_configure(vreg, false) < 0)
+			return rc;
+		rc = regulator_enable(vreg->reg);
+		if (rc < 0) {
+			pr_err("%s: regulator_enable(%s) failed. rc=%d\n",
+					__func__, vreg->name, rc);
+			return rc;
+		}
+		vreg->is_enabled = true;
+	}
+
+	return rc;
+}
+
+static int vreg_disable_retention(struct vreg_data *vreg)
+{
+	int rc = 0;
+
+	if (!vreg)
+		return rc;
+
+	pr_debug("%s: disable_retention for : %s\n", __func__, vreg->name);
+
+	if ((vreg->is_enabled) && (vreg->is_retention_supp))
+		rc = vreg_configure(vreg, false);
+
+	return rc;
+}
+
+static int vreg_enable_retention(struct vreg_data *vreg)
+{
+	int rc = 0;
+
+	if (!vreg)
+		return rc;
+
+	pr_debug("%s: enable_retention for : %s\n", __func__, vreg->name);
+
+	if ((vreg->is_enabled) && (vreg->is_retention_supp))
+		if ((vreg->min_vol != 0) && (vreg->max_vol != 0))
+			rc = vreg_configure(vreg, true);
+
+	return rc;
+}
+
+static int vreg_disable(struct vreg_data *vreg)
+{
+	int rc = 0;
+
+	if (!vreg)
+		return rc;
+
+	pr_debug("%s for : %s\n", __func__, vreg->name);
+
+	if (vreg->is_enabled) {
+		rc = regulator_disable(vreg->reg);
+		if (rc < 0) {
+			pr_err("%s, regulator_disable(%s) failed. rc=%d\n",
+					__func__, vreg->name, rc);
+			goto out;
+		}
+		vreg->is_enabled = false;
+
+		if ((vreg->min_vol != 0) && (vreg->max_vol != 0)) {
+			/* Set the min voltage to 0 */
+			rc = regulator_set_voltage(vreg->reg, 0,
+					vreg->max_vol);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_voltage(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+				goto out;
+			}
+		}
+		if (vreg->load_curr >= 0) {
+			rc = regulator_set_load(vreg->reg, 0);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_load(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+			}
+		}
+	}
+out:
+	return rc;
+}
+
+static int bt_clk_enable(struct bt_power_clk_data *clk)
+{
+	int rc = 0;
+
+	pr_err("%s: %s\n", __func__, clk->name);
+
+	/* Get the clock handle for vreg */
+	if (!clk->clk || clk->is_enabled) {
+		pr_err("%s: error - node: %p, clk->is_enabled:%d\n",
+			__func__, clk->clk, clk->is_enabled);
+		return -EINVAL;
+	}
+
+	rc = clk_prepare_enable(clk->clk);
+	if (rc) {
+		pr_err("%s: failed to enable %s, rc(%d)\n",
+				__func__, clk->name, rc);
+		return rc;
+	}
+
+	clk->is_enabled = true;
+	return rc;
+}
+
+static int bt_clk_disable(struct bt_power_clk_data *clk)
+{
+	int rc = 0;
+
+	pr_err("%s: %s\n", __func__, clk->name);
+
+	/* Get the clock handle for vreg */
+	if (!clk->clk || !clk->is_enabled) {
+		pr_err("%s: error - node: %p, clk->is_enabled:%d\n",
+			__func__, clk->clk, clk->is_enabled);
+		return -EINVAL;
+	}
+	clk_disable_unprepare(clk->clk);
+
+	clk->is_enabled = false;
+	return rc;
+}
+
+static void btpower_set_xo_clk_gpio_state(bool enable)
+{
+	int xo_clk_gpio =  pwr_data->xo_gpio_clk;
+	int retry = 0;
+	int rc = 0;
+
+	if (xo_clk_gpio < 0)
+		return;
+
+retry_gpio_req:
+	rc = gpio_request(xo_clk_gpio, "bt_xo_clk_gpio");
+	if (rc) {
+		if (retry++ < XO_CLK_RETRY_COUNT_MAX) {
+			/* wait for ~(10 - 20) ms */
+			usleep_range(10000, 20000);
+			goto retry_gpio_req;
+		}
+	}
+
+	if (rc) {
+		pr_err("%s: unable to request XO clk gpio %d (%d)\n",
+			__func__, xo_clk_gpio, rc);
+		return;
+	}
+
+	if (enable) {
+		gpio_direction_output(xo_clk_gpio, 1);
+		/*XO CLK must be asserted for some time before BT_EN */
+		usleep_range(100, 200);
+	} else {
+		/* Assert XO CLK ~(2-5)ms before off for valid latch in HW */
+		usleep_range(4000, 6000);
+		gpio_direction_output(xo_clk_gpio, 0);
+	}
+
+	pr_err("%s:gpio(%d) success\n", __func__, xo_clk_gpio);
+
+	gpio_free(xo_clk_gpio);
+}
+
+#ifdef CONFIG_MSM_BT_OOBS
+void bt_configure_wakeup_gpios(int on)
+{
+	int bt_gpio_dev_wake = pwr_data->bt_gpio_dev_wake;
+	int bt_host_wake_gpio = pwr_data->bt_gpio_host_wake;
+	int rc;
+
+	if (on) {
+		if (gpio_is_valid(bt_gpio_dev_wake)) {
+			gpio_set_value(bt_gpio_dev_wake, 1);
+			pr_err("%s: BT-ON asserting BT_WAKE(%d)\n", __func__,
+				 bt_gpio_dev_wake);
+		}
+
+		if (gpio_is_valid(bt_host_wake_gpio)) {
+			pwr_data->irq = gpio_to_irq(bt_host_wake_gpio);
+			pr_err("%s: BT-ON bt-host_wake-gpio(%d) IRQ(%d)\n",
+				__func__, bt_host_wake_gpio, pwr_data->irq);
+			rc = request_irq(pwr_data->irq,
+					 btpower_host_wake_isr,
+					 IRQF_TRIGGER_FALLING |
+					 IRQF_TRIGGER_RISING,
+					 "btpower_hostwake_isr", pwr_data);
+			if (rc)
+				pr_err("%s: unable to request IRQ %d (%d)\n",
+				__func__, bt_host_wake_gpio, rc);
+		}
+	} else {
+		if (gpio_is_valid(bt_host_wake_gpio)) {
+			pr_err("%s: BT-OFF bt-hostwake-gpio(%d) IRQ(%d) value(%d)\n",
+				 __func__, bt_host_wake_gpio, pwr_data->irq,
+				 gpio_get_value(bt_host_wake_gpio));
+			free_irq(pwr_data->irq, pwr_data);
+		}
+
+		if (gpio_is_valid(bt_gpio_dev_wake))
+			gpio_set_value(bt_gpio_dev_wake, 0);
+	}
+}
+#endif
+
+static int bt_configure_gpios(int on)
+{
+	int rc = 0;
+	int bt_reset_gpio = pwr_data->bt_gpio_sys_rst;
+	int wl_reset_gpio = pwr_data->wl_gpio_sys_rst;
+	int bt_sw_ctrl_gpio  =  pwr_data->bt_gpio_sw_ctrl;
+	int bt_debug_gpio  =  pwr_data->bt_gpio_debug;
+	int assert_dbg_gpio = 0;
+
+	if (on) {
+		rc = gpio_request(bt_reset_gpio, "bt_sys_rst_n");
+		if (rc) {
+			pr_err("%s: unable to request gpio %d (%d)\n",
+					__func__, bt_reset_gpio, rc);
+			return rc;
+		}
+		pr_err("BTON:Turn Bt OFF asserting BT_EN to low\n");
+		pr_err("bt-reset-gpio(%d) value(%d)\n", bt_reset_gpio,
+			gpio_get_value(bt_reset_gpio));
+		rc = gpio_direction_output(bt_reset_gpio, 0);
+		if (rc) {
+			pr_err("%s: Unable to set direction\n", __func__);
+			return rc;
+		}
+		power_src.platform_state[BT_RESET_GPIO] =
+			gpio_get_value(bt_reset_gpio);
+		msleep(50);
+		pr_err("BTON:Turn Bt OFF post asserting BT_EN to low\n");
+		pr_err("bt-reset-gpio(%d) value(%d)\n", bt_reset_gpio,
+			gpio_get_value(bt_reset_gpio));
+
+		if (bt_sw_ctrl_gpio >= 0) {
+			power_src.platform_state[BT_SW_CTRL_GPIO] =
+			gpio_get_value(bt_sw_ctrl_gpio);
+			if (pwr_data->sw_cntrl_gpio > 0) {
+				rc = msm_gpio_mpm_wake_set(pwr_data->sw_cntrl_gpio, 1);
+				if (rc < 0) {
+					pr_err("Failed to set msm_gpio_mpm_wake_set for sw_cntrl gpio, ret: %d\n",
+						rc);
+					return rc;
+				}
+				pr_err("Set msm_gpio_mpm_wake_set for sw_cntrl gpio successful\n");
+			}
+			pr_err("BTON:Turn Bt OFF bt-sw-ctrl-gpio(%d) value(%d)\n",
+				bt_sw_ctrl_gpio,
+				power_src.platform_state[BT_SW_CTRL_GPIO]);
+		}
+		if (wl_reset_gpio >= 0)
+			pr_err("BTON:Turn Bt ON wl-reset-gpio(%d) value(%d)\n",
+				wl_reset_gpio, gpio_get_value(wl_reset_gpio));
+
+		if ((wl_reset_gpio < 0) ||
+			((wl_reset_gpio >= 0) && gpio_get_value(wl_reset_gpio))) {
+
+			btpower_set_xo_clk_gpio_state(true);
+			pr_err("BTON: WLAN ON Asserting BT_EN to high\n");
+			rc = gpio_direction_output(bt_reset_gpio, 1);
+			if (rc) {
+				pr_err("%s: Unable to set direction\n", __func__);
+				return rc;
+			}
+			power_src.platform_state[BT_RESET_GPIO] =
+				gpio_get_value(bt_reset_gpio);
+			btpower_set_xo_clk_gpio_state(false);
+		}
+		if ((wl_reset_gpio >= 0) && (gpio_get_value(wl_reset_gpio) == 0)) {
+			if (gpio_get_value(bt_reset_gpio)) {
+				pr_err("BTON: WLAN OFF and BT ON are too close\n");
+				pr_err("reset BT_EN, enable it after delay\n");
+				rc = gpio_direction_output(bt_reset_gpio, 0);
+				if (rc) {
+					pr_err("%s: Unable to set direction\n",
+						 __func__);
+					return rc;
+				}
+				power_src.platform_state[BT_RESET_GPIO] =
+					gpio_get_value(bt_reset_gpio);
+			}
+			pr_err("BTON: WLAN OFF waiting for 100ms delay\n");
+			pr_err("for AON output to fully discharge\n");
+			msleep(100);
+			pr_err("BTON: WLAN OFF Asserting BT_EN to high\n");
+			btpower_set_xo_clk_gpio_state(true);
+			rc = gpio_direction_output(bt_reset_gpio, 1);
+			if (rc) {
+				pr_err("%s: Unable to set direction\n", __func__);
+				return rc;
+			}
+			power_src.platform_state[BT_RESET_GPIO] =
+				gpio_get_value(bt_reset_gpio);
+			btpower_set_xo_clk_gpio_state(false);
+		}
+		/* Below block of code executes if WL_EN is pulled high when
+		 * BT_EN is about to pull high. so above two if conditions are
+		 * not executed.
+		 */
+		if (!gpio_get_value(bt_reset_gpio)) {
+			btpower_set_xo_clk_gpio_state(true);
+			pr_err("BTON: WLAN ON and BT ON are too close\n");
+			pr_err("Asserting BT_EN to high\n");
+			rc = gpio_direction_output(bt_reset_gpio, 1);
+			if (rc) {
+				pr_err("%s: Unable to set direction\n", __func__);
+				return rc;
+			}
+			power_src.platform_state[BT_RESET_GPIO] =
+				gpio_get_value(bt_reset_gpio);
+			btpower_set_xo_clk_gpio_state(false);
+		}
+		msleep(50);
+#ifdef CONFIG_MSM_BT_OOBS
+		bt_configure_wakeup_gpios(on);
+#endif
+		/*  Check  if  SW_CTRL  is  asserted  */
+		if  (bt_sw_ctrl_gpio  >=  0)  {
+			rc  =  gpio_direction_input(bt_sw_ctrl_gpio);
+			if  (rc)  {
+				pr_err("%s:SWCTRL Dir Set Problem:%d\n",
+					__func__, rc);
+			}  else  if  (!gpio_get_value(bt_sw_ctrl_gpio))  {
+				/* SW_CTRL not asserted, assert debug GPIO */
+				if  (bt_debug_gpio  >=  0)
+					assert_dbg_gpio = 1;
+			}
+		}
+		if (assert_dbg_gpio) {
+			rc  =  gpio_request(bt_debug_gpio, "bt_debug_n");
+			if  (rc)  {
+				pr_err("unable to request Debug Gpio\n");
+			}  else  {
+				rc = gpio_direction_output(bt_debug_gpio,  1);
+				if (rc)
+					pr_err("%s:Prob Set Debug-Gpio\n",
+						__func__);
+			}
+		}
+		pr_err("BTON:Turn Bt On bt-reset-gpio(%d) value(%d)\n",
+			bt_reset_gpio, gpio_get_value(bt_reset_gpio));
+		if (bt_sw_ctrl_gpio >= 0) {
+			power_src.platform_state[BT_SW_CTRL_GPIO] =
+			gpio_get_value(bt_sw_ctrl_gpio);
+			pr_err("BTON: Turn BT ON bt-sw-ctrl-gpio(%d) value(%d)\n",
+				bt_sw_ctrl_gpio,
+				power_src.platform_state[BT_SW_CTRL_GPIO]);
+		}
+	} else {
+#ifdef CONFIG_MSM_BT_OOBS
+		bt_configure_wakeup_gpios(on);
+#endif
+		gpio_set_value(bt_reset_gpio, 0);
+		msleep(100);
+		pr_err("BT-OFF:bt-reset-gpio(%d) value(%d)\n",
+			bt_reset_gpio, gpio_get_value(bt_reset_gpio));
+		if (bt_sw_ctrl_gpio >= 0) {
+			pr_err("BT-OFF:bt-sw-ctrl-gpio(%d) value(%d)\n",
+				bt_sw_ctrl_gpio,
+				gpio_get_value(bt_sw_ctrl_gpio));
+		}
+	}
+
+	pr_err("%s: bt_gpio= %d on: %d\n", __func__, bt_reset_gpio, on);
+
+	return rc;
+}
+
+static int bt_regulators_pwr(int pwr_state)
+{
+	int i, log_indx, bt_num_vregs, rc = 0;
+	struct vreg_data *bt_vregs = NULL;
+
+	rc = perisec_cnss_bt_hw_disable_check(pwr_data);
+
+	bt_num_vregs =  pwr_data->bt_num_vregs;
+
+	if (!bt_num_vregs) {
+		pr_warn("%s: not avilable to %s\n",
+			__func__, ConvertRegisterModeToString(pwr_state));
+		return 0;
+	}
+
+	pr_info("%s: %s\n", __func__, ConvertRegisterModeToString(pwr_state));
+
+	if (pwr_state == POWER_ENABLE) {
+		/* Power On */
+		if (pwr_data->bt_sec_hw_disable) {
+			pr_err("%s:secure hw mode on,BT ON not allowed",
+				 __func__);
+			return -EINVAL;
+		}
+
+		for (i = 0; i < bt_num_vregs; i++) {
+			bt_vregs = &pwr_data->bt_vregs[i];
+			log_indx = bt_vregs->indx.init;
+			if (bt_vregs->reg) {
+				power_src.bt_state[log_indx] = DEFAULT_INVALID_VALUE;
+				rc = vreg_enable(bt_vregs);
+				if (rc < 0) {
+					pr_err("%s: bt_power regulators config failed\n",
+						__func__);
+					goto regulator_fail;
+				}
+				if (bt_vregs->is_enabled)
+					power_src.bt_state[log_indx] =
+						regulator_get_voltage(bt_vregs->reg);
+			}
+		}
+
+		/* Parse dt_info and check if a target requires clock voting.
+		 * Enable BT clock when BT is on and disable it when BT is off
+		 */
+		if (pwr_data->bt_chip_clk) {
+			rc = bt_clk_enable(pwr_data->bt_chip_clk);
+			if (rc < 0) {
+				pr_err("%s: bt_power gpio config failed\n",
+					__func__);
+				goto clk_fail;
+			}
+		}
+		if (pwr_data->bt_gpio_sys_rst > 0) {
+			power_src.bt_state[BT_RESET_GPIO] = DEFAULT_INVALID_VALUE;
+			power_src.bt_state[BT_SW_CTRL_GPIO] = DEFAULT_INVALID_VALUE;
+			rc = bt_configure_gpios(POWER_ENABLE);
+			if (rc < 0) {
+				pr_err("%s: bt_power gpio config failed\n",
+					__func__);
+				goto gpio_fail;
+			}
+		}
+	} else if (pwr_state == POWER_DISABLE) {
+		/* Power Off */
+		if (pwr_data->bt_gpio_sys_rst > 0) {
+			if (pwr_data->bt_sec_hw_disable) {
+				pr_err("%s: secure hw mode on, not allowed to access gpio",
+					__func__);
+			}else {
+				bt_configure_gpios(POWER_DISABLE);
+			}
+		}
+gpio_fail:
+		if (pwr_data->bt_gpio_sys_rst > 0)
+			gpio_free(pwr_data->bt_gpio_sys_rst);
+		if (pwr_data->bt_gpio_debug  >  0)
+			gpio_free(pwr_data->bt_gpio_debug);
+		if (pwr_data->bt_chip_clk)
+			bt_clk_disable(pwr_data->bt_chip_clk);
+clk_fail:
+regulator_fail:
+		for (i = 0; i < bt_num_vregs; i++) {
+			bt_vregs = &pwr_data->bt_vregs[i];
+			rc = vreg_disable(bt_vregs);
+		}
+	} else if (pwr_state == POWER_RETENTION) {
+		/* Retention mode */
+		for (i = 0; i < bt_num_vregs; i++) {
+			bt_vregs = &pwr_data->bt_vregs[i];
+			rc = vreg_enable_retention(bt_vregs);
+		}
+	} else {
+		pr_err("%s: Invalid power mode: %d\n", __func__, pwr_state);
+		rc = -1;
+	}
+	return rc;
+}
+
+static int uwb_regulators_pwr(int pwr_state)
+{
+	int i, log_indx, uwb_num_vregs, rc = 0;
+	struct vreg_data *uwb_vregs = NULL;
+
+	rc = perisec_cnss_bt_hw_disable_check(pwr_data);
+
+	uwb_num_vregs =  pwr_data->uwb_num_vregs;
+
+	if (!uwb_num_vregs) {
+		pr_warn("%s: not avilable to %s\n",
+			__func__, ConvertRegisterModeToString(pwr_state));
+		return 0;
+	}
+
+	pr_info("%s: %s\n", __func__, ConvertRegisterModeToString(pwr_state));
+
+	switch (pwr_state) {
+	case POWER_ENABLE:
+		for (i = 0; i < uwb_num_vregs; i++) {
+			uwb_vregs = &pwr_data->uwb_vregs[i];
+			log_indx = uwb_vregs->indx.init;
+			if (uwb_vregs->reg) {
+				power_src.uwb_state[log_indx] = DEFAULT_INVALID_VALUE;
+				rc = vreg_enable(uwb_vregs);
+				if (rc < 0) {
+					pr_err("%s: UWB regulators config failed\n",
+						__func__);
+					goto UWB_regulator_fail;
+				}
+				if (uwb_vregs->is_enabled)
+					power_src.uwb_state[log_indx] =
+						regulator_get_voltage(uwb_vregs->reg);
+			}
+		}
+
+		rc = bt_configure_gpios(POWER_ENABLE);
+		if (rc < 0) {
+			pr_err("%s: bt_power gpio config failed\n",
+				__func__);
+			goto UWB_gpio_fail;
+		}
+
+		break;
+	case POWER_DISABLE:
+		rc = bt_configure_gpios(POWER_DISABLE);
+		if (rc < 0) {
+			pr_err("%s: bt_power gpio config failed\n",
+				__func__);
+			goto UWB_gpio_fail;
+		}
+UWB_gpio_fail:
+
+
+UWB_regulator_fail:
+		for (i = 0; i < uwb_num_vregs; i++) {
+			uwb_vregs = &pwr_data->uwb_vregs[i];
+			rc = vreg_disable(uwb_vregs);
+		}
+		break;
+	case POWER_RETENTION:
+		for (i = 0; i < uwb_num_vregs; i++) {
+			uwb_vregs = &pwr_data->uwb_vregs[i];
+			rc = vreg_enable_retention(uwb_vregs);
+		}
+		break;
+	}
+	return rc;
+}
+
+static int platform_regulators_pwr(int pwr_state)
+{
+	int i, log_indx, platform_num_vregs, rc = 0;
+	struct vreg_data *platform_vregs = NULL;
+
+	rc = perisec_cnss_bt_hw_disable_check(pwr_data);
+
+	platform_num_vregs =  pwr_data->platform_num_vregs;
+
+	if (!platform_num_vregs) {
+		pr_warn("%s: not avilable to %s\n",
+			__func__, ConvertRegisterModeToString(pwr_state));
+		return 0;
+	}
+
+	pr_info("%s: %s\n", __func__, ConvertRegisterModeToString(pwr_state));
+
+	switch (pwr_state) {
+	case POWER_ENABLE:
+		for (i = 0; i < platform_num_vregs; i++) {
+			platform_vregs = &pwr_data->platform_vregs[i];
+			log_indx = platform_vregs->indx.init;
+			if (platform_vregs->reg) {
+				power_src.platform_state[log_indx] = DEFAULT_INVALID_VALUE;
+				rc = vreg_enable(platform_vregs);
+				if (rc < 0) {
+					pr_err("%s: Platform regulators config failed\n",
+						__func__);
+					goto Platform_regulator_fail;
+				}
+				if (platform_vregs->is_enabled) {
+					power_src.platform_state[log_indx] = regulator_get_voltage(platform_vregs->reg);
+				}
+			}
+		}
+
+		rc = bt_configure_gpios(POWER_ENABLE);
+		if (rc < 0) {
+			pr_err("%s: bt_power gpio config failed\n",
+				__func__);
+			goto Platform_gpio_fail;
+		}
+
+		break;
+	case POWER_DISABLE:
+		rc = bt_configure_gpios(POWER_DISABLE);
+		if (rc < 0) {
+			pr_err("%s: bt_power gpio config failed\n",
+				__func__);
+		}
+
+Platform_gpio_fail:
+		if (pwr_data->bt_gpio_sys_rst > 0)
+			gpio_free(pwr_data->bt_gpio_sys_rst);
+		if (pwr_data->bt_gpio_debug  >  0)
+			gpio_free(pwr_data->bt_gpio_debug);
+
+Platform_regulator_fail:
+		for (i = 0; i < platform_num_vregs; i++) {
+			platform_vregs = &pwr_data->platform_vregs[i];
+			rc = vreg_disable(platform_vregs);
+		}
+		break;
+	case POWER_RETENTION:
+		for (i=0; i < platform_num_vregs; i++) {
+			platform_vregs = &pwr_data->platform_vregs[i];
+			rc = vreg_enable_retention(platform_vregs);
+		}
+		break;
+	case POWER_DISABLE_RETENTION:
+		for (i = 0; i < platform_num_vregs; i++) {
+			platform_vregs = &pwr_data->platform_vregs[i];
+			rc = vreg_disable_retention(platform_vregs);
+		}
+		break;
+	}
+	return rc;
+}
+
+static int power_regulators(int core_type, int mode) {
+
+	int ret = 0;
+
+	if ((mode != POWER_DISABLE) && (mode != POWER_ENABLE) &&
+		(mode != POWER_RETENTION)) {
+		pr_err("%s: Received wrong Mode to do regulator operation\n",
+			__func__);
+		return -1;
+	}
+
+	switch (core_type) {
+	case BT_CORE:
+		ret = bt_regulators_pwr(mode);
+		if (ret)
+			pr_err("%s: Failed to configure BT regulators to mode(%d)\n",
+			__func__, mode);
+		break;
+	case UWB_CORE:
+		ret = uwb_regulators_pwr(mode);
+		if (ret)
+			pr_err("%s: Failed to configure UWB regulators to mode(%d)\n",
+			__func__, mode);
+		break;
+	case PLATFORM_CORE:
+		ret = platform_regulators_pwr(mode);
+		if (ret)
+			pr_err("%s: Failed to configure platform regulators to mode(%d)\n",
+			__func__, mode);
+		break;
+	default:
+		pr_err("%s: Received wrong Core Type to do regulator operation\n",
+			__func__);
+		return -1;
+	}
+	return ret;
+}
+
+static int btpower_toggle_radio(void *data, bool blocked)
+{
+	int ret = 0;
+	int (*power_control)(int Core, int enable);
+
+	power_control =
+		((struct platform_pwr_data *)data)->power_setup;
+
+	if (previous != blocked)
+		ret = (*power_control)(BT_CORE, !blocked);
+	if (!ret)
+		previous = blocked;
+	return ret;
+}
+
+static const struct rfkill_ops btpower_rfkill_ops = {
+	.set_block = btpower_toggle_radio,
+};
+
+static ssize_t extldo_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	return scnprintf(buf, 6, "false\n");
+}
+
+static DEVICE_ATTR_RO(extldo);
+
+static int btpower_rfkill_probe(struct platform_device *pdev)
+{
+	struct rfkill *rfkill;
+	int ret;
+
+	rfkill = rfkill_alloc("bt_power", &pdev->dev, RFKILL_TYPE_BLUETOOTH,
+						&btpower_rfkill_ops,
+						pdev->dev.platform_data);
+
+	if (!rfkill) {
+		dev_err(&pdev->dev, "rfkill allocate failed\n");
+		return -ENOMEM;
+	}
+
+	/* add file into rfkill0 to handle LDO27 */
+	ret = device_create_file(&pdev->dev, &dev_attr_extldo);
+	if (ret < 0)
+		pr_err("%s: device create file error\n", __func__);
+
+	/* force Bluetooth off during init to allow for user control */
+	rfkill_init_sw_state(rfkill, 1);
+	previous = true;
+
+	ret = rfkill_register(rfkill);
+	if (ret) {
+		dev_err(&pdev->dev, "rfkill register failed=%d\n", ret);
+		rfkill_destroy(rfkill);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, rfkill);
+
+	return 0;
+}
+
+static void btpower_rfkill_remove(struct platform_device *pdev)
+{
+	struct rfkill *rfkill;
+
+	pr_info("%s\n", __func__);
+
+	rfkill = platform_get_drvdata(pdev);
+	if (rfkill)
+		rfkill_unregister(rfkill);
+	rfkill_destroy(rfkill);
+	platform_set_drvdata(pdev, NULL);
+}
+
+static int dt_parse_vreg_info(struct device *dev, struct device_node *child,
+		struct vreg_data *vreg_data)
+{
+	int len, ret = 0;
+	const __be32 *prop;
+	char prop_name[MAX_PROP_SIZE];
+	struct vreg_data *vreg = vreg_data;
+	struct device_node *np = child;
+	const char *vreg_name = vreg_data->name;
+
+	if (!child)
+		np = dev->of_node;
+
+	snprintf(prop_name, sizeof(prop_name), "%s-supply", vreg_name);
+	if (of_parse_phandle(np, prop_name, 0)) {
+		vreg->reg = regulator_get(dev, vreg_name);
+		if (IS_ERR(vreg->reg)) {
+			ret = PTR_ERR(vreg->reg);
+			vreg->reg = NULL;
+			pr_warn("%s: failed to get: %s error:%d\n", __func__,
+				vreg_name, ret);
+			return ret;
+		}
+
+		snprintf(prop_name, sizeof(prop_name), "%s-config", vreg->name);
+		prop = of_get_property(np, prop_name, &len);
+		if (!prop || len != (4 * sizeof(__be32))) {
+			pr_err("%s: Property %s %s, use default\n",
+				__func__, prop_name,
+				prop ? "invalid format" : "doesn't exist");
+		} else {
+			vreg->min_vol = be32_to_cpup(&prop[0]);
+			vreg->max_vol = be32_to_cpup(&prop[1]);
+			vreg->load_curr = be32_to_cpup(&prop[2]);
+			vreg->is_retention_supp = be32_to_cpup(&prop[3]);
+		}
+
+		pr_err("%s: Got regulator: %s, min_vol: %u, max_vol: %u, load_curr: %u, is_retention_supp: %u\n",
+			__func__, vreg->name, vreg->min_vol, vreg->max_vol,
+			vreg->load_curr, vreg->is_retention_supp);
+	} else {
+		pr_err("%s: %s is not provided in device tree\n", __func__,
+			vreg_name);
+	}
+	return ret;
+}
+
+static int bt_dt_parse_clk_info(struct device *dev,
+		struct bt_power_clk_data **clk_data)
+{
+	int ret = -EINVAL;
+	struct bt_power_clk_data *clk = NULL;
+	struct device_node *np = dev->of_node;
+
+	pr_info("%s\n", __func__);
+
+	*clk_data = NULL;
+	if (of_parse_phandle(np, "clocks", 0)) {
+		clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+		if (!clk) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		/* Allocated 20 bytes size buffer for clock name string */
+		clk->name = devm_kzalloc(dev, 20, GFP_KERNEL);
+
+		/* Parse clock name from node */
+		ret = of_property_read_string_index(np, "clock-names", 0,
+				&(clk->name));
+		if (ret < 0) {
+			pr_err("%s: reading \"clock-names\" failed\n",
+				__func__);
+			return ret;
+		}
+
+		clk->clk = devm_clk_get(dev, clk->name);
+		if (IS_ERR(clk->clk)) {
+			ret = PTR_ERR(clk->clk);
+			pr_err("%s: failed to get %s, ret (%d)\n",
+				__func__, clk->name, ret);
+			clk->clk = NULL;
+			return ret;
+		}
+
+		*clk_data = clk;
+	} else {
+		pr_err("%s: clocks is not provided in device tree\n", __func__);
+	}
+
+err:
+	return ret;
+}
+
+static void bt_power_vreg_put(void)
+{
+	int i = 0;
+	struct vreg_data *bt_vregs = NULL;
+	int bt_num_vregs = pwr_data->bt_num_vregs;
+
+	for (; i < bt_num_vregs; i++) {
+		bt_vregs = &pwr_data->bt_vregs[i];
+		if (bt_vregs->reg)
+			regulator_put(bt_vregs->reg);
+	}
+}
+
+static int get_gpio_dt_pinfo(struct platform_device *pdev) {
+
+	int ret;
+	struct device_node *child;
+
+	child = pdev->dev.of_node;
+
+	pwr_data->bt_gpio_sys_rst =
+		of_get_named_gpio(child,
+					"qcom,bt-reset-gpio", 0);
+	if (pwr_data->bt_gpio_sys_rst < 0)
+		pr_err("bt-reset-gpio not provided in devicetree\n");
+
+	pwr_data->wl_gpio_sys_rst =
+		of_get_named_gpio(child,
+					"qcom,wl-reset-gpio", 0);
+	if (pwr_data->wl_gpio_sys_rst < 0)
+		pr_err("%s: wl-reset-gpio not provided in device tree\n",
+			__func__);
+
+	ret = of_property_read_u32(child, "mpm_wake_set_gpios",
+				  &pwr_data->sw_cntrl_gpio);
+	if (ret)
+		pr_warn("sw_cntrl-gpio not provided in devicetree\n");
+
+	pwr_data->bt_gpio_sw_ctrl  =
+		of_get_named_gpio(child,
+					"qcom,bt-sw-ctrl-gpio",  0);
+	if (pwr_data->bt_gpio_sw_ctrl < 0)
+		pr_err("bt-sw-ctrl-gpio not provided in devicetree\n");
+
+	pwr_data->bt_gpio_debug  =
+		of_get_named_gpio(child,
+					"qcom,bt-debug-gpio",  0);
+	if (pwr_data->bt_gpio_debug < 0)
+		pr_warn("bt-debug-gpio not provided in devicetree\n");
+
+	pwr_data->xo_gpio_clk =
+		of_get_named_gpio(child,
+					"qcom,xo-clk-gpio", 0);
+	if (pwr_data->xo_gpio_clk < 0)
+		pr_warn("xo-clk-gpio not provided in devicetree\n");
+
+#ifdef CONFIG_MSM_BT_OOBS
+	pwr_data->bt_gpio_dev_wake =
+		of_get_named_gpio(child,
+				  "qcom,btwake_gpio", 0);
+	if (pwr_data->bt_gpio_dev_wake < 0)
+		pr_warn("%s: btwake-gpio not provided in device tree\n",
+			__func__);
+
+	pwr_data->bt_gpio_host_wake =
+		of_get_named_gpio(child,
+				  "qcom,bthostwake_gpio", 0);
+	if (pwr_data->bt_gpio_host_wake < 0)
+		pr_warn("%s: bthostwake_gpio not provided in device tree\n",
+			__func__);
+#endif
+	return true;
+}
+
+static int get_power_dt_pinfo(struct platform_device *pdev)
+{
+	int rc, i;
+	const struct pwr_data *data;
+
+	data = of_device_get_match_data(&pdev->dev);
+	if (!data) {
+		pr_err("%s: failed to get dev node\n", __func__);
+		return -EINVAL;
+	}
+
+	memcpy(&pwr_data->compatible, &data->compatible, MAX_PROP_SIZE);
+
+	pwr_data->bt_vregs = data->bt_vregs;
+	pwr_data->bt_num_vregs = data->bt_num_vregs;
+
+	if (pwr_data->is_ganges_dt) {
+		pwr_data->uwb_vregs = data->uwb_vregs;
+		pwr_data->platform_vregs = data->platform_vregs;
+		pwr_data->uwb_num_vregs = data->uwb_num_vregs;
+		pwr_data->platform_num_vregs = data->platform_num_vregs;
+
+		pr_err("%s: bt_num_vregs =%d uwb_num_vregs =%d platform_num_vregs=%d\n",
+			__func__, pwr_data->bt_num_vregs, pwr_data->uwb_num_vregs,
+			pwr_data->platform_num_vregs);
+	} else {
+		pr_err("%s: bt_num_vregs =%d\n", __func__, pwr_data->bt_num_vregs);
+	}
+
+
+	for (i = 0; i < pwr_data->bt_num_vregs; i++) {
+		rc = dt_parse_vreg_info(&(pdev->dev), pwr_data->bt_of_node,
+			&pwr_data->bt_vregs[i]);
+		/* No point to go further if failed to get regulator handler */
+		if (rc)
+			return rc;
+	}
+
+	if(pwr_data->is_ganges_dt) {
+		for (i = 0; i < pwr_data->platform_num_vregs; i++) {
+			rc = dt_parse_vreg_info(&(pdev->dev), NULL,
+				&pwr_data->platform_vregs[i]);
+			/* No point to go further if failed to get regulator handler */
+			if (rc)
+				return rc;
+		}
+
+		for (i = 0; i < pwr_data->uwb_num_vregs; i++) {
+			rc = dt_parse_vreg_info(&(pdev->dev), pwr_data->uwb_of_node,
+				&pwr_data->uwb_vregs[i]);
+			/* No point to go further if failed to get regulator handler */
+			if (rc)
+				return rc;
+		}
+	}
+	return rc;
+}
+
+static int bt_power_populate_dt_pinfo(struct platform_device *pdev)
+{
+	struct device_node *of_node;
+	int rc;
+
+	pr_info("%s\n", __func__);
+
+	if (!pwr_data)
+		return -ENOMEM;
+
+	if (pwr_data->is_ganges_dt) {
+		for_each_available_child_of_node(pdev->dev.of_node, of_node) {
+			if (!strcmp(of_node->name, "bt_ganges")) {
+				pwr_data->bt_of_node = of_node;
+				pr_err("%s: %s device node found\n", __func__,
+					pwr_data->bt_of_node->name);
+			} else if (!strcmp(of_node->name, "uwb_ganges")) {
+				pwr_data->uwb_of_node = of_node;
+				pr_err("%s: %s device node found\n", __func__,
+					pwr_data->uwb_of_node->name);
+			}
+		}
+	}
+
+	rc = get_power_dt_pinfo(pdev);
+
+	if (rc < 0)
+		pr_err("%s: failed to get the pin info from the DTSI\n",
+			__func__);
+
+	rc = get_gpio_dt_pinfo(pdev);
+
+	if (rc < 0)
+		pr_err("%s: failed to get the gpio info from the DTSI\n",
+			__func__);
+
+	bt_dt_parse_clk_info(&pdev->dev,
+				&pwr_data->bt_chip_clk);
+
+	pwr_data->power_setup = power_regulators;
+	return 0;
+}
+
+static inline bool bt_is_ganges_dt(struct platform_device *plat_dev)
+{
+	return of_property_read_bool(plat_dev->dev.of_node, "qcom,peach-bt");
+}
+
+static void bt_power_pdc_init_params(struct platform_pwr_data *pdata) {
+	int ret;
+	struct device *dev = &pdata->pdev->dev;
+
+	pdata->pdc_init_table_len = of_property_count_strings(dev->of_node,
+				"qcom,pdc_init_table");
+	if (pdata->pdc_init_table_len > 0) {
+		pdata->pdc_init_table = kcalloc(pdata->pdc_init_table_len,
+				sizeof(char *), GFP_KERNEL);
+		ret = of_property_read_string_array(dev->of_node, "qcom,pdc_init_table",
+			pdata->pdc_init_table, pdata->pdc_init_table_len);
+		if (ret < 0)
+			pr_err("Failed to get PDC Init Table\n");
+		else
+			pr_err("PDC Init table configured\n");
+	} else {
+		pr_err("PDC Init Table not configured\n");
+	}
+}
+
+static void bt_signal_handler(struct work_struct *w_arg)
+{
+	struct kernel_siginfo siginfo;
+	int rc = 0;
+
+	// Sending signal to HAL layer
+	memset(&siginfo, 0, sizeof(siginfo));
+	siginfo.si_signo = SIGIO;
+	siginfo.si_code = SI_QUEUE;
+	siginfo.si_int = pwr_data->wrkq_signal_state;
+	rc = send_sig_info(siginfo.si_signo, &siginfo, pwr_data->reftask_bt);
+	if (rc < 0) {
+		pr_err("%s: failed (%d) to send SIG to HAL(%d)\n", __func__,
+				rc, pwr_data->reftask_bt->pid);
+		return;
+	}
+	pr_err("%s Succesfull\n", __func__);
+}
+
+static void uwb_signal_handler(struct work_struct *w_arg)
+{
+	struct kernel_siginfo siginfo;
+	int rc = 0;
+
+	// Sending signal to HAL layer
+	memset(&siginfo, 0, sizeof(siginfo));
+	siginfo.si_signo =  SIGIO;
+	siginfo.si_code = SI_QUEUE;
+	siginfo.si_int = pwr_data->wrkq_signal_state;
+	rc = send_sig_info(siginfo.si_signo, &siginfo, pwr_data->reftask_uwb);
+	if (rc < 0) {
+		pr_err("%s: failed (%d) to send SIG to HAL(%d)\n", __func__,
+				rc, pwr_data->reftask_uwb->pid);
+		return;
+	}
+	pr_err("%s Succesfull\n", __func__);
+}
+
+static int bt_power_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int itr;
+
+	/* Fill whole array with -2 i.e NOT_AVAILABLE state by default
+	 * for any GPIO or Reg handle.
+	 */
+	for (itr = PWR_SRC_INIT_STATE_IDX; itr < BT_POWER_SRC_SIZE; ++itr) {
+		power_src.bt_state[itr] = PWR_SRC_NOT_AVAILABLE;
+		power_src.platform_state[itr] = PWR_SRC_NOT_AVAILABLE;
+		power_src.uwb_state[itr] = PWR_SRC_NOT_AVAILABLE;
+	}
+
+	pwr_data = kzalloc(sizeof(*pwr_data), GFP_KERNEL);
+
+	if (!pwr_data)
+		return -ENOMEM;
+
+	pwr_data->pdev = pdev;
+
+	pwr_data->is_ganges_dt = of_property_read_bool(pdev->dev.of_node,
+							"qcom,peach-bt");
+
+	pr_info("%s: is_ganges_dt = %d\n", __func__, pwr_data->is_ganges_dt);
+
+	pwr_data->workq = alloc_workqueue("workq", WQ_HIGHPRI, WQ_DFL_ACTIVE);
+	if (!pwr_data->workq) {
+		pr_err("%s: Failed to creat the Work Queue (workq)\n",
+			__func__);
+		return -ENOMEM;
+	}
+
+	INIT_WORK(&pwr_data->uwb_wq, uwb_signal_handler);
+	INIT_WORK(&pwr_data->bt_wq, bt_signal_handler);
+	INIT_WORK(&pwr_data->wq_pwr_voting, bt_power_vote);
+
+	for (itr = 0; itr < BTPWR_MAX_REQ; itr++)
+		init_waitqueue_head(&pwr_data->rsp_wait_q[itr]);
+
+	skb_queue_head_init(&pwr_data->rxq);
+	mutex_init(&pwr_data->pwr_mtx);
+	mutex_init(&pwr_data->btpower_state.state_machine_lock);
+	pwr_data->btpower_state.power_state = IDLE;
+	pwr_data->btpower_state.retention_mode = RETENTION_IDLE;
+	pwr_data->btpower_state.grant_state = NO_GRANT_FOR_ANY_SS;
+	pwr_data->btpower_state.grant_pending = NO_OTHER_CLIENT_WAITING_FOR_GRANT;
+
+	perisec_cnss_bt_hw_disable_check(pwr_data);
+
+	if (pdev->dev.of_node) {
+		ret = bt_power_populate_dt_pinfo(pdev);
+		if (ret < 0) {
+			pr_err("%s, Failed to populate device tree info\n",
+				__func__);
+			goto free_pdata;
+		}
+		if (pwr_data->bt_sec_hw_disable) {
+			pr_info("%s: bt is in secure mode\n", __func__);
+		} else {
+			pr_info(" %s:send platform data of btpower\n", __func__);
+			pdev->dev.platform_data = pwr_data;
+		}
+	} else if (pdev->dev.platform_data) {
+		/* Optional data set to default if not provided */
+		if (!((struct platform_pwr_data *)
+			(pdev->dev.platform_data))->power_setup)
+			((struct platform_pwr_data *)
+				(pdev->dev.platform_data))->power_setup =
+						power_regulators;
+
+		memcpy(pwr_data, pdev->dev.platform_data,
+			sizeof(struct platform_pwr_data));
+	} else {
+		pr_err("%s: Failed to get platform data\n", __func__);
+		goto free_pdata;
+	}
+
+	if (btpower_rfkill_probe(pdev) < 0)
+		goto free_pdata;
+
+	bt_power_pdc_init_params(pwr_data);
+	btpower_aop_mbox_init(pwr_data);
+
+	probe_finished = true;
+	return 0;
+
+free_pdata:
+	mutex_lock(&pwr_release);
+	kfree(pwr_data);
+	mutex_unlock(&pwr_release);
+	return ret;
+}
+
+static int bt_power_remove(struct platform_device *pdev)
+{
+	mutex_lock(&pwr_release);
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+	probe_finished = false;
+	btpower_rfkill_remove(pdev);
+	bt_power_vreg_put();
+	kfree(pwr_data);
+	mutex_unlock(&pwr_release);
+	return 0;
+}
+
+int btpower_register_slimdev(struct device *dev)
+{
+	pr_info("%s\n", __func__);
+	if (!pwr_data || (dev == NULL)) {
+		pr_err("%s: Failed to allocate memory\n", __func__);
+		return -EINVAL;
+	}
+	pwr_data->slim_dev = dev;
+	return 0;
+}
+
+EXPORT_SYMBOL(btpower_register_slimdev);
+
+int btpower_get_chipset_version(void)
+{
+	pr_info("%s\n", __func__);
+	return soc_id;
+}
+EXPORT_SYMBOL(btpower_get_chipset_version);
+
+static void set_pwr_srcs_status (struct vreg_data *handle, int core_type) {
+	int power_src_state;
+
+	if (!handle) {
+		pr_err("%s: invalid handler received \n", __func__);
+		return;
+	}
+
+	if (handle->is_enabled)
+		power_src_state = (int)regulator_get_voltage(handle->reg);
+	else
+		power_src_state = DEFAULT_INVALID_VALUE;
+
+	switch (core_type) {
+	case BT_CORE:
+		power_src.bt_state[handle->indx.crash] = power_src_state;
+		if (power_src_state != DEFAULT_INVALID_VALUE) {
+			pr_err("%s(%p) value(%d)\n", handle->name, handle,
+				power_src.bt_state[handle->indx.crash]);
+		} else {
+			pr_err("%s:%s is_enabled: %d\n", __func__, handle->name,
+				handle->is_enabled);
+		}
+		break;
+	case UWB_CORE:
+		power_src.uwb_state[handle->indx.crash] = power_src_state;
+		if (power_src_state != DEFAULT_INVALID_VALUE) {
+			pr_err("%s(%p) value(%d)\n", handle->name, handle,
+				power_src.uwb_state[handle->indx.crash]);
+		} else {
+			pr_err("%s:%s is_enabled: %d\n", __func__, handle->name,
+				handle->is_enabled);
+		}
+		break;
+	case PLATFORM_CORE:
+		power_src.platform_state[handle->indx.crash] = power_src_state;
+		if (power_src_state != DEFAULT_INVALID_VALUE) {
+			pr_err("%s(%p) value(%d)\n", handle->name, handle,
+				power_src.platform_state[handle->indx.crash]);
+		} else {
+			pr_err("%s:%s is_enabled: %d\n", __func__, handle->name,
+				handle->is_enabled);
+		}
+		break;
+	default:
+		pr_err("%s: invalid core type received = %d\n", __func__, core_type);
+		break;
+	}
+
+}
+
+static inline void update_pwr_state(int state)
+{
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	pwr_data->btpower_state.power_state = state;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+}
+
+static inline int get_pwr_state(void)
+{
+	int state;
+
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	state = (int)pwr_data->btpower_state.power_state;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+	return state;
+}
+
+static inline void btpower_set_retenion_mode_state(int state)
+{
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	pwr_data->btpower_state.retention_mode = state;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+}
+
+static inline int btpower_get_retenion_mode_state(void)
+{
+	int state;
+
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	state = (int)pwr_data->btpower_state.retention_mode;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+	return state;
+}
+
+static inline void btpower_set_grant_pending_state(enum grant_states state)
+{
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	pwr_data->btpower_state.grant_pending = state;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+}
+
+static inline enum grant_states btpower_get_grant_pending_state(void)
+{
+	enum grant_states state;
+
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	state = pwr_data->btpower_state.grant_pending;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+	return state;
+}
+
+static inline void btpower_set_grant_state(enum grant_states state)
+{
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	pwr_data->btpower_state.grant_state = state;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+}
+
+static inline enum grant_states btpower_get_grant_state(void)
+{
+	enum grant_states state;
+
+	mutex_lock(&pwr_data->btpower_state.state_machine_lock);
+	state = pwr_data->btpower_state.grant_state;
+	mutex_unlock(&pwr_data->btpower_state.state_machine_lock);
+	return state;
+}
+
+static int get_sub_state(void)
+{
+	return (int)pwr_data->sub_state;
+}
+
+static void update_sub_state(int state)
+{
+	pwr_data->sub_state = state;
+}
+
+int power_enable (enum SubSystem SubSystemType)
+{
+	int ret;
+
+	switch (get_pwr_state()) {
+	case IDLE:
+		ret = power_regulators(PLATFORM_CORE, POWER_ENABLE);
+		if (SubSystemType == BLUETOOTH) {
+			ret = power_regulators(BT_CORE, POWER_ENABLE);
+			update_pwr_state(BT_ON);
+		} else {
+			ret = power_regulators(UWB_CORE, POWER_ENABLE);
+			update_pwr_state(UWB_ON);
+		}
+		break;
+	case BT_ON:
+		if (SubSystemType == BLUETOOTH) {
+			pr_err("%s: BT Regulators already Voted-On\n",
+				__func__);
+			return 0;
+		}
+		ret = power_regulators(UWB_CORE, POWER_ENABLE);
+		update_pwr_state(ALL_CLIENTS_ON);
+		break;
+	case UWB_ON:
+		if (SubSystemType == UWB) {
+			pr_err("%s: UWB Regulators already Voted-On\n",
+				__func__);
+			return 0;
+		}
+		ret = power_regulators(BT_CORE, POWER_ENABLE);
+		update_pwr_state(ALL_CLIENTS_ON);
+		break;
+	case ALL_CLIENTS_ON:
+		pr_err("%s: Both BT and UWB Regulators already Voted-On\n",
+			__func__);
+		return 0;
+	}
+	return ret;
+}
+
+void send_signal_to_subsystem (int SubSystemType, int state) {
+	pwr_data->wrkq_signal_state = state;
+	if (SubSystemType == BLUETOOTH)
+		queue_work(pwr_data->workq, &pwr_data->bt_wq);
+	else
+		queue_work(pwr_data->workq, &pwr_data->uwb_wq);
+}
+
+int power_disable(enum SubSystem SubSystemType)
+{
+	int ret = 0;
+	int ret_mode_state = btpower_get_retenion_mode_state();
+	enum grant_states grant_state = btpower_get_grant_state();
+	enum grant_states grant_pending = btpower_get_grant_pending_state();
+
+	switch (get_pwr_state()) {
+	case IDLE:
+		pr_err("%s: both BT and UWB regulators already voted-Off\n", __func__);
+		return 0;
+	case ALL_CLIENTS_ON:
+		if (SubSystemType == BLUETOOTH) {
+			ret = power_regulators(BT_CORE, POWER_DISABLE);
+			update_pwr_state(UWB_ON);
+			if (ret_mode_state == BOTH_CLIENTS_IN_RETENTION)
+				btpower_set_retenion_mode_state(UWB_IN_RETENTION);
+			else if (ret_mode_state == BT_IN_RETENTION)
+				btpower_set_retenion_mode_state(RETENTION_IDLE);
+			if (get_sub_state() == SSR_ON_BT) {
+				update_sub_state(SUB_STATE_IDLE);
+				send_signal_to_subsystem(UWB, BT_SSR_COMPLETED);
+			}
+			if (grant_state == BT_HAS_GRANT) {
+				if (grant_pending == UWB_WAITING_FOR_GRANT) {
+					send_signal_to_subsystem(UWB,
+						SIGIO_SOC_ACCESS_SIGNAL|(ACCESS_GRANTED + 1));
+					btpower_set_grant_state(UWB_HAS_GRANT);
+				} else {
+					btpower_set_grant_state(NO_GRANT_FOR_ANY_SS);
+				}
+			}
+			if (grant_pending == BT_WAITING_FOR_GRANT)
+				btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+		} else {
+			ret  = power_regulators(UWB_CORE, POWER_DISABLE);
+			update_pwr_state(BT_ON);
+			if (ret_mode_state == BOTH_CLIENTS_IN_RETENTION)
+				btpower_set_retenion_mode_state(BT_IN_RETENTION);
+			else if (ret_mode_state == UWB_IN_RETENTION)
+				btpower_set_retenion_mode_state(RETENTION_IDLE);
+			if (get_sub_state() == SSR_ON_UWB) {
+				send_signal_to_subsystem(BLUETOOTH,
+					(SIGIO_INTERACTION_SIGNAL|SIGIO_UWB_SSR_COMPLETED));
+			}
+			if (grant_state == UWB_HAS_GRANT) {
+				if (grant_pending == BT_WAITING_FOR_GRANT) {
+					send_signal_to_subsystem(BLUETOOTH,
+						SIGIO_SOC_ACCESS_SIGNAL|(ACCESS_GRANTED + 1));
+					btpower_set_grant_state(BT_HAS_GRANT);
+				} else {
+					btpower_set_grant_state(NO_GRANT_FOR_ANY_SS);
+				}
+			}
+			if (grant_pending == UWB_WAITING_FOR_GRANT)
+				btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+		}
+		break;
+	case UWB_ON:
+		if (SubSystemType == BLUETOOTH) {
+			pr_err("%s: BT Regulator already Voted-Off\n", __func__);
+			return 0;
+		}
+		ret = power_regulators(UWB_CORE, POWER_DISABLE);
+		ret = power_regulators(PLATFORM_CORE, POWER_DISABLE);
+		update_pwr_state(IDLE);
+		update_sub_state(SUB_STATE_IDLE);
+		btpower_set_retenion_mode_state(RETENTION_IDLE);
+		btpower_set_grant_state(NO_GRANT_FOR_ANY_SS);
+		btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+		break;
+	case BT_ON:
+		if (SubSystemType == UWB) {
+			pr_err("%s: UWB Regulator already Voted-Off\n", __func__);
+			return 0;
+		}
+		ret = power_regulators(BT_CORE, POWER_DISABLE);
+		ret = power_regulators(PLATFORM_CORE, POWER_DISABLE);
+		update_pwr_state(IDLE);
+		update_sub_state(SUB_STATE_IDLE);
+		btpower_set_retenion_mode_state(RETENTION_IDLE);
+		btpower_set_grant_state(NO_GRANT_FOR_ANY_SS);
+		btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+		break;
+	}
+	return ret;
+}
+
+static int client_state_notified(int SubSystemType) {
+	if (get_sub_state() != SUB_STATE_IDLE) {
+		pr_err("%s: SSR is already running on other Sub-system\n", __func__);
+		return -1;
+	}
+
+	if (SubSystemType == BLUETOOTH) {
+		update_sub_state(SSR_ON_BT);
+		if (get_pwr_state() == ALL_CLIENTS_ON) {
+			if (!pwr_data->reftask_uwb) {
+				pr_err("%s: UWB PID is not register to send signal\n",
+					__func__);
+				return -1;
+			}
+			send_signal_to_subsystem(UWB, SSR_ON_BT);
+		}
+	} else {
+		update_sub_state(SSR_ON_UWB);
+		if (get_pwr_state() == ALL_CLIENTS_ON) {
+			if (!pwr_data->reftask_bt) {
+				pr_err("%s: BT PID is not register to send signal\n",
+					__func__);
+				return -1;
+			}
+			send_signal_to_subsystem(BLUETOOTH,
+				(SIGIO_INTERACTION_SIGNAL|SIGIO_SSR_ON_UWB));
+		}
+	}
+	return 0;
+}
+
+void btpower_register_client(int client, int cmd)
+{
+	if (cmd == REG_BT_PID) {
+		pwr_data->reftask_bt = get_current();
+		pr_info("%s: Registering BT Service(PID-%d) with Power driver\n",
+			__func__, pwr_data->reftask_bt->tgid);
+		return;
+	} else if (cmd == REG_UWB_PID) {
+		pwr_data->reftask_uwb = get_current();
+		pr_info("%s: Registering UWB Service(PID-%d) with Power driver\n",
+			__func__, pwr_data->reftask_uwb->tgid);
+		return;
+	}
+
+	if (client == BLUETOOTH)
+		client_state_notified(BLUETOOTH);
+	else
+		client_state_notified(UWB);
+}
+
+void log_power_src_val(void)
+{
+	int itr = 0;
+
+	power_src.platform_state[BT_SW_CTRL_GPIO_CURRENT] =
+		gpio_get_value(pwr_data->bt_gpio_sw_ctrl);
+	power_src.platform_state[BT_RESET_GPIO_CURRENT] =
+		gpio_get_value(pwr_data->bt_gpio_sys_rst);
+
+	for (itr = 0; itr < pwr_data->bt_num_vregs; itr++)
+		set_pwr_srcs_status(&pwr_data->bt_vregs[itr], BT_CORE);
+
+	for (itr = 0; itr < pwr_data->platform_num_vregs; itr++)
+		set_pwr_srcs_status(&pwr_data->platform_vregs[itr], PLATFORM_CORE);
+
+	for (itr = 0; itr < pwr_data->uwb_num_vregs; itr++)
+		set_pwr_srcs_status(&pwr_data->uwb_vregs[itr], UWB_CORE);
+}
+
+int btpower_retenion(enum plt_pwr_state client)
+{
+	int ret;
+	int current_pwr_state = get_pwr_state();
+	int retention_mode_state = btpower_get_retenion_mode_state();
+
+	if (current_pwr_state == IDLE) {
+		pr_err("%s: invalid retention_mode request\n", __func__);
+		return -1;
+	}
+
+	ret = power_regulators((client == POWER_ON_BT_RETENION ? BT_CORE : UWB_CORE),
+				POWER_RETENTION);
+	if (ret < 0)
+		return ret;
+
+	if ((current_pwr_state == BT_ON || current_pwr_state == UWB_ON) &&
+		retention_mode_state == IDLE) {
+		ret = power_regulators(PLATFORM_CORE, POWER_RETENTION);
+		if (ret < 0)
+			return ret;
+		btpower_set_retenion_mode_state(client == POWER_ON_BT_RETENION ?
+				BT_IN_RETENTION: UWB_IN_RETENTION);
+	} else if (current_pwr_state == ALL_CLIENTS_ON &&
+				retention_mode_state == IDLE) {
+		btpower_set_retenion_mode_state(client == POWER_ON_BT_RETENION ?
+				BT_IN_RETENTION: UWB_IN_RETENTION);
+	} else if (current_pwr_state == ALL_CLIENTS_ON &&
+			(retention_mode_state == BT_IN_RETENTION ||
+			retention_mode_state == UWB_IN_RETENTION)) {
+		ret = power_regulators(PLATFORM_CORE, POWER_RETENTION);
+		if (ret < 0)
+			return ret;
+		btpower_set_retenion_mode_state(BOTH_CLIENTS_IN_RETENTION);
+	} else if (retention_mode_state == UWB_OUT_OF_RETENTION ||
+			retention_mode_state == BT_OUT_OF_RETENTION) {
+		ret = power_regulators(PLATFORM_CORE, POWER_RETENTION);
+		if (ret < 0)
+			return ret;
+		btpower_set_retenion_mode_state(BOTH_CLIENTS_IN_RETENTION);
+	}
+
+	return ret;	
+}
+
+int btpower_off(enum plt_pwr_state client)
+{
+	return power_disable((client == POWER_OFF_BT) ? BLUETOOTH : UWB);
+}
+
+int btpower_on(enum plt_pwr_state client)
+{
+	int ret = 0;
+	int current_ssr_state = get_sub_state();
+	int retention_mode_state = btpower_get_retenion_mode_state();
+
+	if (retention_mode_state == UWB_IN_RETENTION ||
+		retention_mode_state == BT_IN_RETENTION) {
+		ret = platform_regulators_pwr(POWER_DISABLE_RETENTION);
+		if (ret < 0)
+			return ret;
+		if (retention_mode_state == BT_IN_RETENTION) 
+			btpower_set_retenion_mode_state(BT_OUT_OF_RETENTION);
+		else
+			btpower_set_retenion_mode_state(UWB_OUT_OF_RETENTION);
+	}
+
+	/* No Point in going further if SSR is on any subsystem */
+	if (current_ssr_state != SUB_STATE_IDLE) {
+		pr_err("%s: %s not allowing to power on\n", __func__,
+			ConvertSsrStatusToString(current_ssr_state));
+		return -1;
+	}
+
+	ret = power_enable(client == POWER_ON_BT ? BLUETOOTH : UWB);
+
+	/* Return current state machine to clients */
+	if (!ret)
+		ret = (int)get_pwr_state();
+
+	return ret;
+}
+
+int STREAM_TO_UINT32(struct sk_buff *skb)
+{
+	return (skb->data[0] | (skb->data[1] << 8) |
+		(skb->data[2] << 16) | (skb->data[3] << 24));
+}
+
+int btpower_access_ctrl(enum plt_pwr_state request)
+{
+	enum grant_states grant_state = btpower_get_grant_state();
+	enum grant_states grant_pending = btpower_get_grant_pending_state();
+	int current_ssr_state = get_sub_state();
+
+	if (current_ssr_state != SUB_STATE_IDLE &&
+		(request == BT_ACCESS_REQ || request == UWB_ACCESS_REQ)) {
+		pr_err("%s: not allowing this request as %s\n", __func__,
+			ConvertSsrStatusToString(current_ssr_state));
+		return (int)ACCESS_DISALLOWED;
+	}
+
+	if ((grant_state == NO_GRANT_FOR_ANY_SS &&
+		grant_pending != NO_OTHER_CLIENT_WAITING_FOR_GRANT)) {
+		pr_err("%s: access ctrl gone for toss, resetting it back\n", __func__);
+		grant_pending = NO_OTHER_CLIENT_WAITING_FOR_GRANT;
+		btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+	}
+
+	if (request == BT_ACCESS_REQ && grant_state == NO_GRANT_FOR_ANY_SS) {
+		btpower_set_grant_state(BT_HAS_GRANT);
+		return ACCESS_GRANTED;
+	} else if (request == UWB_ACCESS_REQ && grant_state == NO_GRANT_FOR_ANY_SS) {
+		btpower_set_grant_state(UWB_HAS_GRANT);
+		return ACCESS_GRANTED;
+	} else if (request == BT_ACCESS_REQ && grant_state == UWB_HAS_GRANT) {
+		btpower_set_grant_pending_state(BT_WAITING_FOR_GRANT);
+		return ACCESS_DENIED;
+	} else if (request == UWB_ACCESS_REQ && grant_state == BT_HAS_GRANT) {
+		btpower_set_grant_pending_state(UWB_WAITING_FOR_GRANT);
+		return ACCESS_DENIED;
+	} else if (request == BT_RELEASE_ACCESS && grant_state == BT_HAS_GRANT) {
+		if (grant_pending == UWB_WAITING_FOR_GRANT) {
+			if (!pwr_data->reftask_uwb) {
+				pr_err("%s: UWB service got killed\n", __func__);
+			} else {
+				send_signal_to_subsystem(UWB,
+					SIGIO_SOC_ACCESS_SIGNAL|(ACCESS_GRANTED + 1));
+				btpower_set_grant_state(UWB_HAS_GRANT);
+			}
+			btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+			return ACCESS_RELEASED;
+			
+		} else {
+			btpower_set_grant_state(NO_GRANT_FOR_ANY_SS);
+			btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+			return ACCESS_RELEASED; 
+		}
+	} else if (request == UWB_RELEASE_ACCESS && grant_state == UWB_HAS_GRANT) {
+		if (grant_pending == BT_WAITING_FOR_GRANT) {
+			if (!pwr_data->reftask_uwb) {
+				pr_err("%s: BT service got killed\n", __func__);
+			} else {
+				send_signal_to_subsystem(BLUETOOTH,
+					SIGIO_SOC_ACCESS_SIGNAL|(ACCESS_GRANTED+1));
+				btpower_set_grant_state(BT_HAS_GRANT);
+			}
+		} else {
+			btpower_set_grant_state(NO_GRANT_FOR_ANY_SS);
+		}
+		btpower_set_grant_pending_state(NO_OTHER_CLIENT_WAITING_FOR_GRANT);
+		return ACCESS_RELEASED;
+	} else {
+		pr_err("%s: unhandled event\n", __func__);
+	}
+	return ACCESS_DISALLOWED;
+}
+
+static void bt_power_vote(struct work_struct *work)
+{
+	struct sk_buff *skb;
+	int request;
+	int ret;
+
+	while (1) {
+		mutex_lock(&pwr_data->pwr_mtx);
+		skb = skb_dequeue(&pwr_data->rxq);
+		if (!skb) {
+			mutex_unlock(&pwr_data->pwr_mtx);
+			break;
+		}
+		request = STREAM_TO_UINT32(skb);
+		skb_pull(skb, sizeof(uint32_t));
+		mutex_unlock(&pwr_data->pwr_mtx);
+		pr_info("%s: Start %s %s, %s state access %s pending %s\n",
+			__func__,
+			ConvertPowerStatusToString(get_pwr_state()),
+			ConvertSsrStatusToString(get_sub_state()),
+			ConvertRetentionModeToString(btpower_get_retenion_mode_state()),
+			ConvertGrantToString(btpower_get_grant_state()),
+			ConvertGrantToString(btpower_get_grant_pending_state()));
+		if (request == POWER_ON_BT || request == POWER_ON_UWB)
+			ret = btpower_on((enum plt_pwr_state)request);
+		else if (request == POWER_OFF_UWB || request == POWER_OFF_BT)
+			ret = btpower_off((enum plt_pwr_state)request);
+		else if (request == POWER_ON_BT_RETENION || request == POWER_ON_UWB_RETENION)
+			ret = btpower_retenion(request);
+		else if (request >= BT_ACCESS_REQ && request <= UWB_RELEASE_ACCESS) {
+			ret = btpower_access_ctrl(request);
+			pr_info("%s: grant status %s\n", __func__, ConvertGrantRetToString((int)ret));
+		}
+		pr_info("%s: Completed %s %s, %s state access %s pending %s\n",
+			__func__,
+			ConvertPowerStatusToString(get_pwr_state()),
+			ConvertSsrStatusToString(get_sub_state()),
+			ConvertRetentionModeToString(btpower_get_retenion_mode_state()),
+			ConvertGrantToString(btpower_get_grant_state()),
+			ConvertGrantToString(btpower_get_grant_pending_state()));
+		pwr_data->wait_status[request] = ret;
+		wake_up_interruptible(&pwr_data->rsp_wait_q[request]);
+	}
+}
+
+int schedule_client_voting(enum plt_pwr_state request)
+{
+	struct sk_buff *skb;
+	wait_queue_head_t *rsp_wait_q;
+	int *status;
+	int ret = 0;
+	uint32_t req = (uint32_t)request;
+
+	mutex_lock(&pwr_data->pwr_mtx);
+	skb = alloc_skb(sizeof(uint32_t), GFP_KERNEL);
+	if (!skb) {
+		mutex_unlock(&pwr_data->pwr_mtx);
+		return -1;
+	}
+
+	rsp_wait_q = &pwr_data->rsp_wait_q[(u8)request];
+	status = &pwr_data->wait_status[(u8)request];
+	*status = PWR_WAITING_RSP;
+	skb_put_data(skb, &req, sizeof(uint32_t));
+	skb_queue_tail(&pwr_data->rxq, skb);
+	queue_work(system_highpri_wq, &pwr_data->wq_pwr_voting);
+	mutex_unlock(&pwr_data->pwr_mtx);
+	ret = wait_event_interruptible_timeout(*rsp_wait_q, (*status) != PWR_WAITING_RSP,
+					       msecs_to_jiffies(BTPOWER_CONFIG_MAX_TIMEOUT));
+	pr_err("%s: %d\n", __func__, *status);
+	if (ret == 0) {
+		pr_err("%s: failed to vote %d due to timeout\n", __func__, request);
+		ret = -ETIMEDOUT;
+	} else {
+		ret = *status;
+	}
+
+	return ret;
+}
+
+char* GetUwbSecondaryCrashReason(enum UwbSecondaryReasonCode reason)
+{
+	for(int i =0; i < (int)(sizeof(uwbSecReasonMap)/sizeof(UwbSecondaryReasonMap)); i++)
+		if (uwbSecReasonMap[i].reason == reason)
+			return uwbSecReasonMap[i].reasonstr;
+
+	return CRASH_REASON_NOT_FOUND;
+}
+
+char* GetUwbPrimaryCrashReason(enum UwbPrimaryReasonCode reason)
+{
+	for(int i =0; i < (int)(sizeof(uwbPriReasonMap)/sizeof(UwbPrimaryReasonMap)); i++)
+		if (uwbPriReasonMap[i].reason == reason)
+			return uwbPriReasonMap[i].reasonstr;
+
+	return CRASH_REASON_NOT_FOUND;
+}
+
+const char *GetSourceSubsystemString(uint32_t source_subsystem)
+{
+	switch (source_subsystem) {
+	case PERI_SS:
+		return "Peri SS";
+	case BT_SS:
+		return "BT SS";
+	case UWB_SS:
+		return "UWB SS";
+	default:
+		return "Unknown Subsystem";
+	}
+}
+
+int btpower_handle_client_request(unsigned int cmd, int arg)
+{
+	int ret = -1;
+
+	pr_info("%s: Start of %s cmd request to %s.\n",
+		__func__,
+		(cmd == BT_CMD_PWR_CTRL ? "BT_CMD_PWR_CTRL" : "UWB_CMD_PWR_CTRL"),
+		ConvertClientReqToString(arg));
+
+	if (cmd == BT_CMD_PWR_CTRL) {
+		switch ((int)arg) {
+		case POWER_DISABLE:
+			ret = schedule_client_voting(POWER_OFF_BT);
+			break;
+		case POWER_ENABLE:
+			ret = schedule_client_voting(POWER_ON_BT);
+			break;
+		case POWER_RETENTION:
+			ret = schedule_client_voting(POWER_ON_BT_RETENION);
+			break;
+		}
+	} else if (cmd == UWB_CMD_PWR_CTRL) {
+		switch ((int)arg) {
+		case POWER_DISABLE:
+			ret = schedule_client_voting(POWER_OFF_UWB);
+			break;
+		case POWER_ENABLE:
+			ret = schedule_client_voting(POWER_ON_UWB);
+			break;
+		case POWER_RETENTION:
+			ret = schedule_client_voting(POWER_ON_UWB_RETENION);
+			break;
+		}
+	}
+	return ret;
+}
+
+int btpower_process_access_req(unsigned int cmd, int req)
+{
+	int ret = -1;
+
+	pr_info("%s: by %s: request type %s\n", __func__,
+		cmd == BT_CMD_ACCESS_CTRL ? "BT_CMD_ACCESS_CTRL" : "UWB_CMD_ACCESS_CTRL",
+		req == 1 ? "Request" : "Release");
+	if (cmd == BT_CMD_ACCESS_CTRL && req == 1)
+		ret = schedule_client_voting(BT_ACCESS_REQ);
+	else if (cmd == BT_CMD_ACCESS_CTRL && req == 2)
+		ret = schedule_client_voting(BT_RELEASE_ACCESS);
+	else if (cmd == UWB_CMD_ACCESS_CTRL && req == 1)
+		ret = schedule_client_voting(UWB_ACCESS_REQ);
+	else if (cmd == UWB_CMD_ACCESS_CTRL && req == 2)
+		ret = schedule_client_voting(UWB_RELEASE_ACCESS);
+	else
+		pr_err("%s: unhandled command %04x req %02x", __func__, cmd, req);
+
+	return ret;
+}
+
+static long bt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+	int chipset_version = 0;
+	int itr;
+	unsigned long panic_reason = 0;
+	unsigned short primary_reason = 0, sec_reason = 0, source_subsystem = 0;
+
+#ifdef CONFIG_MSM_BT_OOBS
+	enum btpower_obs_param clk_cntrl;
+#endif
+
+	if (!pwr_data || !probe_finished) {
+		pr_err("%s: BTPower Probing Pending.Try Again\n", __func__);
+		return -EAGAIN;
+	}
+
+	switch (cmd) {
+#ifdef CONFIG_MSM_BT_OOBS
+	case BT_CMD_OBS_VOTE_CLOCK:
+		if (!gpio_is_valid(pwr_data->bt_gpio_dev_wake)) {
+			pr_err("%s: BT_CMD_OBS_VOTE_CLOCK bt_dev_wake_n(%d) not configured\n",
+				__func__, pwr_data->bt_gpio_dev_wake);
+			return -EIO;
+		}
+		clk_cntrl = (enum btpower_obs_param)arg;
+		switch (clk_cntrl) {
+		case BTPOWER_OBS_CLK_OFF:
+			btpower_uart_transport_locked(pwr_data, false);
+			ret = 0;
+			break;
+		case BTPOWER_OBS_CLK_ON:
+			btpower_uart_transport_locked(pwr_data, true);
+			ret = 0;
+			break;
+		case BTPOWER_OBS_DEV_OFF:
+			gpio_set_value(pwr_data->bt_gpio_dev_wake, 0);
+			ret = 0;
+			break;
+		case BTPOWER_OBS_DEV_ON:
+			gpio_set_value(pwr_data->bt_gpio_dev_wake, 1);
+			ret = 0;
+			break;
+		default:
+			pr_err("%s: BT_CMD_OBS_VOTE_CLOCK clk_cntrl(%d)\n",
+				__func__, clk_cntrl);
+			return -EINVAL;
+		}
+		pr_err("%s: BT_CMD_OBS_VOTE_CLOCK clk_cntrl(%d) %s\n",
+			__func__, clk_cntrl,
+			gpio_get_value(pwr_data->bt_gpio_dev_wake) ?
+				"Assert" : "Deassert");
+		break;
+#endif
+	case BT_CMD_SLIM_TEST:
+#if (defined CONFIG_BT_SLIM)
+		if (!pwr_data->slim_dev) {
+			pr_err("%s: slim_dev is null\n", __func__);
+			return -EINVAL;
+		}
+		ret = btfm_slim_hw_init(
+			pwr_data->slim_dev->platform_data
+		);
+#endif
+		break;
+	case BT_CMD_PWR_CTRL:
+	case UWB_CMD_PWR_CTRL: {
+		ret = btpower_handle_client_request(cmd, (int)arg);
+		break;
+	}
+	case BT_CMD_REGISTRATION:
+		btpower_register_client(BLUETOOTH, (int)arg);
+		break;
+	case UWB_CMD_REGISTRATION:
+		btpower_register_client(UWB, (int)arg);
+		break;
+	case BT_CMD_ACCESS_CTRL:
+	case UWB_CMD_ACCESS_CTRL: {
+		ret = btpower_process_access_req(cmd, (int)arg);
+		break;
+	}
+	case BT_CMD_CHIPSET_VERS:
+		chipset_version = (int)arg;
+		pr_warn("%s: unified Current SOC Version : %x\n", __func__,
+			chipset_version);
+		if (chipset_version) {
+			soc_id = chipset_version;
+		} else {
+			pr_err("%s: got invalid soc version\n", __func__);
+			soc_id = 0;
+		}
+		break;
+	case BT_CMD_GET_CHIPSET_ID:
+		pr_err("%s: BT_CMD_GET_CHIPSET_ID = %s\n", __func__,
+			pwr_data->compatible);
+		if (copy_to_user((void __user *)arg, pwr_data->compatible,
+		    MAX_PROP_SIZE)) {
+			pr_err("%s: copy to user failed\n", __func__);
+			ret = -EFAULT;
+		}
+		break;
+	case BT_CMD_CHECK_SW_CTRL:
+		/*  Check  if  SW_CTRL  is  asserted  */
+		pr_err("BT_CMD_CHECK_SW_CTRL\n");
+		if (pwr_data->bt_gpio_sw_ctrl > 0) {
+			power_src.bt_state[BT_SW_CTRL_GPIO] =
+				DEFAULT_INVALID_VALUE;
+			ret  =  gpio_direction_input(
+				pwr_data->bt_gpio_sw_ctrl);
+			if (ret) {
+				pr_err("%s:gpio_direction_input api\n",
+					 __func__);
+				pr_err("%s:failed for SW_CTRL:%d\n",
+					__func__, ret);
+			} else {
+				power_src.bt_state[BT_SW_CTRL_GPIO] =
+					gpio_get_value(
+					pwr_data->bt_gpio_sw_ctrl);
+				pr_err("bt-sw-ctrl-gpio(%d) value(%d)\n",
+					pwr_data->bt_gpio_sw_ctrl,
+					power_src.bt_state[BT_SW_CTRL_GPIO]);
+			}
+		} else {
+			pr_err("bt_gpio_sw_ctrl not configured\n");
+			return -EINVAL;
+		}
+		break;
+	case BT_CMD_GETVAL_POWER_SRCS:
+		pr_err("BT_CMD_GETVAL_POWER_SRCS\n");
+
+		power_src.platform_state[BT_SW_CTRL_GPIO_CURRENT] =
+			gpio_get_value(pwr_data->bt_gpio_sw_ctrl);
+		power_src.platform_state[BT_RESET_GPIO_CURRENT] =
+			gpio_get_value(pwr_data->bt_gpio_sys_rst);
+
+		for (itr = 0; itr < pwr_data->bt_num_vregs; itr++)
+			set_pwr_srcs_status(&pwr_data->bt_vregs[itr], BT_CORE);
+
+		for (itr = 0; itr < pwr_data->platform_num_vregs; itr++)
+			set_pwr_srcs_status(&pwr_data->platform_vregs[itr], PLATFORM_CORE);
+
+		for (itr = 0; itr < pwr_data->uwb_num_vregs; itr++)
+			set_pwr_srcs_status(&pwr_data->uwb_vregs[itr], UWB_CORE);
+
+		if (copy_to_user((void __user *)arg, &power_src, sizeof(power_src))) {
+			pr_err("%s: copy to user failed\n", __func__);
+			ret = -EFAULT;
+		}
+		break;
+	case BT_CMD_SET_IPA_TCS_INFO:
+		pr_err("%s: BT_CMD_SET_IPA_TCS_INFO\n", __func__);
+		btpower_enable_ipa_vreg(pwr_data);
+		break;
+	case BT_CMD_KERNEL_PANIC:
+
+		pr_err("%s: BT_CMD_KERNEL_PANIC\n", __func__);
+
+		if (copy_from_user(&CrashInfo, (char *)arg, sizeof(CrashInfo))) {
+			pr_err("%s: copy to user failed\n", __func__);
+			ret = -EFAULT;
+		}
+
+		pr_err("%s: BT kernel panic Primary reason = %s, Secondary reason = %s\n",
+			__func__, CrashInfo.PrimaryReason, CrashInfo.SecondaryReason);
+
+		panic("%s: BT kernel panic Primary reason = %s, Secondary reason = %s\n",
+			__func__, CrashInfo.PrimaryReason, CrashInfo.SecondaryReason);
+
+		break;
+	case UWB_CMD_KERNEL_PANIC:
+		pr_err("%s: UWB_CMD_KERNEL_PANIC\n", __func__);
+		panic_reason = arg;
+		primary_reason = panic_reason & 0xFFFF;
+		sec_reason = (panic_reason & 0xFFFF0000) >> 16;
+		source_subsystem = (panic_reason & 0xFFFF00000000) >> 32;
+		pr_err("%s: UWB kernel panic PrimaryReason = (0x%02x)[%s] | SecondaryReason = (0x%02x)[%s] | SourceSubsystem = (0x%02x)[%s]\n",
+			__func__, primary_reason, GetUwbPrimaryCrashReason(primary_reason),
+			sec_reason, GetUwbSecondaryCrashReason(sec_reason),
+			source_subsystem, GetSourceSubsystemString(source_subsystem));
+		panic("%s: UWB kernel panic PrimaryReason = (0x%02x)[%s] | SecondaryReason = (0x%02x)[%s] | SourceSubsystem = (0x%02x)[%s]\n",
+			__func__, primary_reason, GetUwbPrimaryCrashReason(primary_reason),
+			sec_reason, GetUwbSecondaryCrashReason(sec_reason),
+			source_subsystem, GetSourceSubsystemString(source_subsystem));
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return ret;
+}
+
+static int bt_power_release(struct inode *inode, struct file *file)
+{
+
+	mutex_lock(&pwr_release);
+
+	if (!pwr_data || !probe_finished) {
+		pr_err("%s: BTPower Probing Pending.Try Again\n", __func__);
+		return -EAGAIN;
+	}
+
+	pwr_data->reftask = get_current();
+
+	if (pwr_data->reftask_bt != NULL) {
+		if (pwr_data->reftask->tgid == pwr_data->reftask_bt->tgid)
+		{
+			pr_err("%s called by BT service(PID-%d)\n",
+					__func__, pwr_data->reftask->tgid);
+/*
+			if(get_pwr_state() == BT_ON)
+			{
+				bt_regulators_pwr(POWER_DISABLE);
+				platform_regulators_pwr(POWER_DISABLE);
+				update_pwr_state(IDLE);
+
+			}
+			else if (get_pwr_state() == ALL_CLIENTS_ON)
+			{
+				bt_regulators_pwr(POWER_DISABLE);
+				update_pwr_state(UWB_ON);
+			}
+*/
+		}
+	} else if (pwr_data->reftask_uwb != NULL) {
+		if (pwr_data->reftask->tgid == pwr_data->reftask_uwb->tgid)
+		{
+			pr_err("%s called by uwb service(PID-%d)\n",
+					__func__, pwr_data->reftask->tgid);
+/*
+			if(get_pwr_state() == UWB_ON)
+			{
+				uwb_regulators_pwr(POWER_DISABLE);
+				platform_regulators_pwr(POWER_DISABLE);
+				update_pwr_state(IDLE);
+			}
+			else if (get_pwr_state() == ALL_CLIENTS_ON)
+			{
+				uwb_regulators_pwr(POWER_DISABLE);
+				update_pwr_state(BT_ON);
+			}
+*/
+		}
+	}
+	mutex_unlock(&pwr_release);
+	return 0;
+}
+
+static struct platform_driver bt_power_driver = {
+	.probe = bt_power_probe,
+	.remove = bt_power_remove,
+	.driver = {
+		.name = "bt_power",
+		.of_match_table = bt_power_match_table,
+	},
+};
+
+static const struct file_operations bt_dev_fops = {
+	.unlocked_ioctl = bt_ioctl,
+	.compat_ioctl = bt_ioctl,
+	.release = bt_power_release,
+};
+
+static int __init btpower_init(void)
+{
+	int ret = 0;
+
+	probe_finished = false;
+	ret = platform_driver_register(&bt_power_driver);
+	if (ret) {
+		pr_err("%s: platform_driver_register error: %d\n",
+			__func__, ret);
+		goto driver_err;
+	}
+
+	bt_major = register_chrdev(0, "bt", &bt_dev_fops);
+	if (bt_major < 0) {
+		pr_err("%s: failed to allocate char dev\n", __func__);
+		ret = -1;
+		goto chrdev_err;
+	}
+
+	bt_class = class_create(THIS_MODULE, "bt-dev");
+	if (IS_ERR(bt_class)) {
+		pr_err("%s: coudn't create class\n", __func__);
+		ret = -1;
+		goto class_err;
+	}
+
+	if (device_create(bt_class, NULL, MKDEV(bt_major, 0),
+		NULL, "btpower") == NULL) {
+		pr_err("%s: failed to allocate char dev\n", __func__);
+		goto device_err;
+	}
+
+	mutex_init(&pwr_release);
+	return 0;
+
+device_err:
+	class_destroy(bt_class);
+class_err:
+	unregister_chrdev(bt_major, "bt");
+chrdev_err:
+	platform_driver_unregister(&bt_power_driver);
+driver_err:
+	return ret;
+}
+
+/**
+ * bt_aop_send_msg: Sends json message to AOP using QMP
+ * @plat_priv: Pointer to cnss platform data
+ * @msg: String in json format
+ *
+ * AOP accepts JSON message to configure WLAN/BT resources. Format as follows:
+ * To send VReg config: {class: wlan_pdc, ss: <pdc_name>,
+ *                       res: <VReg_name>.<param>, <seq_param>: <value>}
+ * To send PDC Config: {class: wlan_pdc, ss: <pdc_name>, res: pdc,
+ *                      enable: <Value>}
+ * QMP returns timeout error if format not correct or AOP operation fails.
+ *
+ * Return: 0 for success
+ */
+int bt_aop_send_msg(struct platform_pwr_data *plat_priv, char *mbox_msg)
+{
+	struct qmp_pkt pkt;
+	int ret = 0;
+	pkt.size = BTPOWER_MBOX_MSG_MAX_LEN;
+	pkt.data = mbox_msg;
+	pr_err("%s: %s\n", __func__, mbox_msg);
+	ret = mbox_send_message(plat_priv->mbox_chan, &pkt);
+	if (ret < 0)
+		pr_err("Failed to send AOP mbox msg: %s\n", mbox_msg);
+	else
+		ret =0;
+	return ret;
+
+}
+
+int bt_aop_pdc_reconfig(struct platform_pwr_data *pdata)
+{
+	unsigned int i;
+	int ret;
+	if (pdata->pdc_init_table_len <= 0 || !pdata->pdc_init_table)
+		return 0;
+	pr_err("Setting PDC defaults\n");
+	for (i = 0; i < pdata->pdc_init_table_len; i++) {
+		ret =bt_aop_send_msg(pdata,(char *)pdata->pdc_init_table[i]);
+		if (ret < 0)
+			break;
+	}
+	return ret;
+}
+
+int btpower_aop_mbox_init(struct platform_pwr_data *pdata)
+{
+	struct mbox_client *mbox = &pdata->mbox_client_data;
+	struct mbox_chan *chan;
+	int ret = 0;
+
+	mbox->dev = &pdata->pdev->dev;
+	mbox->tx_block = true;
+	mbox->tx_tout = BTPOWER_MBOX_TIMEOUT_MS;
+	mbox->knows_txdone = false;
+
+	pdata->mbox_chan = NULL;
+	chan = mbox_request_channel(mbox, 0);
+	if (IS_ERR(chan)) {
+		pr_err("%s: failed to get mbox channel\n", __func__);
+		return PTR_ERR(chan);
+	}
+	pdata->mbox_chan = chan;
+
+	ret = of_property_read_string(pdata->pdev->dev.of_node,
+				      "qcom,vreg_ipa",
+				      &pdata->vreg_ipa);
+	if (ret)
+		pr_err("%s: vreg for iPA not configured\n", __func__);
+	else
+		pr_err("%s: Mbox channel initialized\n", __func__);
+
+	ret = bt_aop_pdc_reconfig(pdata);
+	if (ret)
+		pr_err("Failed to reconfig BT WLAN PDC, err = %d\n", ret);
+
+	return 0;
+}
+
+static int btpower_aop_set_vreg_param(struct platform_pwr_data *pdata,
+				   const char *vreg_name,
+				   enum btpower_vreg_param param,
+				   enum btpower_tcs_seq seq, int val)
+{
+	struct qmp_pkt pkt;
+	char mbox_msg[BTPOWER_MBOX_MSG_MAX_LEN];
+	static const char * const vreg_param_str[] = {"v", "m", "e"};
+	static const char *const tcs_seq_str[] = {"upval", "dwnval", "enable"};
+	int ret = 0;
+
+	if (param > BTPOWER_VREG_ENABLE || seq > BTPOWER_TCS_ALL_SEQ || !vreg_name)
+		return -EINVAL;
+
+	snprintf(mbox_msg, BTPOWER_MBOX_MSG_MAX_LEN,
+		 "{class: wlan_pdc, res: %s.%s, %s: %d}", vreg_name,
+		 vreg_param_str[param], tcs_seq_str[seq], val);
+
+	pr_err("%s: sending AOP Mbox msg: %s\n", __func__, mbox_msg);
+	pkt.size = BTPOWER_MBOX_MSG_MAX_LEN;
+	pkt.data = mbox_msg;
+
+	ret = mbox_send_message(pdata->mbox_chan, &pkt);
+	if (ret < 0)
+		pr_err("%s:Failed to send AOP mbox msg(%s), err(%d)\n",
+					__func__, mbox_msg, ret);
+
+	return ret;
+}
+
+static int btpower_enable_ipa_vreg(struct platform_pwr_data *pdata)
+{
+	int ret = 0;
+	static bool config_done;
+
+	if (config_done) {
+		pr_err("%s: IPA Vreg already configured\n", __func__);
+		return 0;
+	}
+
+	if (!pdata->vreg_ipa || !pdata->mbox_chan) {
+		pr_err("%s: mbox/iPA vreg not configured\n", __func__);
+	} else {
+		ret = btpower_aop_set_vreg_param(pdata,
+					       pdata->vreg_ipa,
+					       BTPOWER_VREG_ENABLE,
+					       BTPOWER_TCS_UP_SEQ, 1);
+		if (ret >=  0) {
+			pr_err("%s:Enabled iPA\n", __func__);
+			config_done = true;
+		}
+	}
+
+	return ret;
+}
+
+static void __exit btpower_exit(void)
+{
+	platform_driver_unregister(&bt_power_driver);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM Bluetooth power control driver");
+
+module_init(btpower_init);
+module_exit(btpower_exit);

+ 14 - 0
qcom/opensource/bt-kernel/rtc6226/Kconfig

@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config I2C_RTC6226_QCA
+	tristate "Richwave RTC6226 FM Radio Receiver support with I2C for QCA"
+	depends on I2C && VIDEO_V4L2
+	help
+	  This is a driver for I2C devices with the Richwave RTC6226
+	  chip.
+
+	  Say Y here if you want to connect this type of radio to your
+	  computer's I2C port.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called radio-i2c-RTC6226_QCA.

+ 3 - 0
qcom/opensource/bt-kernel/rtc6226/Makefile

@@ -0,0 +1,3 @@
+ccflags-y += -I$(BT_ROOT)/include
+radio-i2c-rtc6226-qca-objs  := radio-rtc6226-i2c.o radio-rtc6226-common.o
+obj-$(CONFIG_I2C_RTC6226_QCA) += radio-i2c-rtc6226-qca.o

+ 2375 - 0
qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-common.c

@@ -0,0 +1,2375 @@
+/*  drivers/media/radio/rtc6226/radio-rtc6226-common.c
+ *
+ *  Driver for Richwave RTC6226 FM Tuner
+ *
+ *  Copyright (c) 2009 Tobias Lorenz <[email protected]>
+ *  Copyright (c) 2012 Hans de Goede <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 2008-01-12	Tobias Lorenz <[email protected]>
+ *		Version 1.0.0
+ *		- First working version
+ * 2008-01-13	Tobias Lorenz <[email protected]>
+ *		Version 1.0.1
+ *		- Improved error handling, every function now returns errno
+ *		- Improved multi user access (start/mute/stop)
+ *		- Channel doesn't get lost anymore after start/mute/stop
+ *		- RDS support added (polling mode via interrupt EP 1)
+ *		- marked default module parameters with *value*
+ *		- switched from bit structs to bit masks
+ *		- header file cleaned and integrated
+ * 2008-01-14	Tobias Lorenz <[email protected]>
+ *		Version 1.0.2
+ *		- hex values are now lower case
+ *		- commented USB ID for ADS/Tech moved on todo list
+ *		- blacklisted in hid-quirks.c
+ *		- rds buffer handling functions integrated into *_work, *_read
+ *		- rds_command exchanged against simple retval
+ *		- check for firmware version 15
+ *		- code order and prototypes still remain the same
+ *		- spacing and bottom of band codes remain the same
+ * 2008-01-16	Tobias Lorenz <[email protected]>
+ *		Version 1.0.3
+ *		- code reordered to avoid function prototypes
+ *		- switch/case defaults are now more user-friendly
+ *		- unified comment style
+ *		- applied all checkpatch.pl v1.12 suggestions
+ *		  except the warning about the too long lines with bit comments
+ *		- renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
+ * 2008-01-22	Tobias Lorenz <[email protected]>
+ *		Version 1.0.4
+ *		- avoid poss. locking when doing copy_to_user which may sleep
+ *		- RDS is automatically activated on read now
+ *		- code cleaned of unnecessary rds_commands
+ *		- USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
+ *		  (thanks to Guillaume RAMOUSSE)
+ * 2008-01-27	Tobias Lorenz <[email protected]>
+ *		Version 1.0.5
+ *		- number of seek_retries changed to tune_timeout
+ *		- fixed problem with incomplete tune operations by own buffers
+ *		- optimization of variables and printf types
+ *		- improved error logging
+ * 2008-01-31	Tobias Lorenz <[email protected]>
+ *		Oliver Neukum <[email protected]>
+ *		Version 1.0.6
+ *		- fixed coverity checker warnings in *_usb_driver_disconnect
+ *		- probe()/open() race by correct ordering in probe()
+ *		- DMA coherency rules by separate allocation of all buffers
+ *		- use of endianness macros
+ *		- abuse of spinlock, replaced by mutex
+ *		- racy handling of timer in disconnect,
+ *		  replaced by delayed_work
+ *		- racy interruptible_sleep_on(),
+ *		  replaced with wait_event_interruptible()
+ *		- handle signals in read()
+ * 2008-02-08	Tobias Lorenz <[email protected]>
+ *		Oliver Neukum <[email protected]>
+ *		Version 1.0.7
+ *		- usb autosuspend support
+ *		- unplugging fixed
+ * 2008-05-07	Tobias Lorenz <[email protected]>
+ *		Version 1.0.8
+ *		- hardware frequency seek support
+ *		- afc indication
+ *		- more safety checks, get_freq return errno
+ *		- vidioc behavior corrected according to v4l2 spec
+ * 2008-10-20	Alexey Klimov <[email protected]>
+ *		- add support for KWorld USB FM Radio FM700
+ *		- blacklisted KWorld radio in hid-core.c and hid-ids.h
+ * 2008-12-03	Mark Lord <[email protected]>
+ *		- add support for DealExtreme USB Radio
+ * 2009-01-31	Bob Ross <[email protected]>
+ *		- correction of stereo detection/setting
+ *		- correction of signal strength indicator scaling
+ * 2009-01-31	Rick Bronson <[email protected]>
+ *		Tobias Lorenz <[email protected]>
+ *		- add LED status output
+ *		- get HW/SW version from scratchpad
+ * 2009-06-16   Edouard Lafargue <[email protected]>
+ *		Version 1.0.10
+ *		- add support for interrupt mode for RDS endpoint,
+ *                instead of polling.
+ *                Improves RDS reception significantly
+ * 2018-02-01	LG Electronics, Inc.
+ * 2018-08-19   Richwave Technology Co.Ltd
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/* kernel includes */
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include "radio-rtc6226.h"
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Bottom of Band (MHz) */
+/* 0: 87.5 - 108 MHz (USA, Europe)*/
+/* 1: 76   - 108 MHz (Japan wide band) */
+/* 2: 76   -  90 MHz (Japan) */
+
+/* De-emphasis */
+/* 0: 75 us (USA) */
+/* 1: 50 us (Europe, Australia, Japan) */
+static unsigned short de;
+
+wait_queue_head_t rtc6226_wq;
+int rtc6226_wq_flag = NO_WAIT;
+#ifdef New_VolumeControl
+unsigned short global_volume;
+#endif
+
+void rtc6226_q_event(struct rtc6226_device *radio,
+		enum rtc6226_evt_t event)
+{
+
+	struct kfifo *data_b;
+	unsigned char evt = event;
+
+	data_b = &radio->data_buf[RTC6226_FM_BUF_EVENTS];
+
+	FMDBG("%s updating event_q with event %x\n", __func__, event);
+	if (kfifo_in_locked(data_b,
+				&evt,
+				1,
+				&radio->buf_lock[RTC6226_FM_BUF_EVENTS]))
+		wake_up_interruptible(&radio->event_queue);
+}
+
+/*
+ * rtc6226_set_chan - set the channel
+ */
+static int rtc6226_set_chan(struct rtc6226_device *radio, unsigned short chan)
+{
+	int retval;
+	unsigned short current_chan =
+		radio->registers[CHANNEL] & CHANNEL_CSR0_CH;
+
+	FMDBG("%s CHAN=%d chan=%d\n", __func__, radio->registers[CHANNEL],
+						chan);
+
+	/* start tuning */
+	radio->registers[CHANNEL] &= ~CHANNEL_CSR0_CH;
+	radio->registers[CHANNEL] |= CHANNEL_CSR0_TUNE | chan;
+	retval = rtc6226_set_register(radio, CHANNEL);
+	if (retval < 0)	{
+		radio->registers[CHANNEL] = current_chan;
+		goto done;
+	}
+
+done:
+	FMDBG("%s exit %d\n", __func__, retval);
+	return retval;
+}
+
+/*
+ * rtc6226_get_freq - get the frequency
+ */
+static int rtc6226_get_freq(struct rtc6226_device *radio, unsigned int *freq)
+{
+	unsigned short chan;
+	unsigned short rssi = 0;
+	int retval;
+
+	FMDBG("%s enter\n", __func__);
+
+	/* read channel */
+	retval = rtc6226_get_register(radio, CHANNEL1);
+	if (retval < 0) {
+		FMDBG("%s fail to get register\n", __func__);
+		goto end;
+	}
+	chan = radio->registers[CHANNEL1] & STATUS_READCH;
+	retval = rtc6226_get_register(radio, RSSI);
+	rssi = radio->registers[RSSI] & RSSI_RSSI;
+	FMDBG("%s chan %d\n", __func__, chan);
+	*freq = chan * TUNE_STEP_SIZE;
+	FMDBG("FMRICHWAVE, freq= %d, rssi= %d dBuV\n", *freq, rssi);
+
+	if (rssi < radio->rssi_th)
+		rtc6226_q_event(radio, RTC6226_EVT_BELOW_TH);
+	else
+		rtc6226_q_event(radio, RTC6226_EVT_ABOVE_TH);
+
+end:
+	return retval;
+}
+
+
+/*
+ * rtc6226_set_freq - set the frequency
+ */
+int rtc6226_set_freq(struct rtc6226_device *radio, unsigned int freq)
+{
+	unsigned int band_bottom;
+	unsigned short chan;
+	unsigned char i;
+	int retval = 0;
+
+	FMDBG("%s enter freq:%d\n", __func__, freq);
+
+	band_bottom = (radio->registers[RADIOSEEKCFG2] &
+		CHANNEL_CSR0_FREQ_BOT) * TUNE_STEP_SIZE;
+
+	if (freq < band_bottom)
+		freq = band_bottom;
+
+	/* Chan = Freq (Mhz) / 10 */
+	chan = (u16)(freq / TUNE_STEP_SIZE);
+
+	FMDBG("%s chan:%d freq:%d  band_bottom:%d\n", __func__,
+			chan, freq, band_bottom);
+	retval = rtc6226_set_chan(radio, chan);
+	if (retval < 0) {
+		FMDBG("%s fail to set chan\n", __func__);
+		goto end;
+	}
+
+	for (i = 0x12; i < RADIO_REGISTER_NUM; i++) {
+		retval = rtc6226_get_register(radio, i);
+		if (retval < 0) {
+			FMDBG("%s fail to get register\n", __func__);
+			goto end;
+		}
+	}
+
+end:
+	return retval;
+}
+
+
+/*
+ * rtc6226_set_seek - set seek
+ */
+static int rtc6226_set_seek(struct rtc6226_device *radio,
+	 unsigned int seek_up, unsigned int seek_wrap)
+{
+	int retval = 0;
+	unsigned short seekcfg1_val = radio->registers[SEEKCFG1];
+
+	FMDBG("%s enter up:%d wrap:%d, th:%d\n", __func__, seek_up, seek_wrap,
+						seekcfg1_val);
+	if (seek_wrap)
+		radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SKMODE;
+	else
+		radio->registers[SEEKCFG1] |= SEEKCFG1_CSR0_SKMODE;
+
+	if (seek_up)
+		radio->registers[SEEKCFG1] |= SEEKCFG1_CSR0_SEEKUP;
+	else
+		radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEKUP;
+
+	radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK;
+	retval = rtc6226_set_register(radio, SEEKCFG1);
+	if (retval < 0) {
+		radio->registers[SEEKCFG1] = seekcfg1_val;
+		goto done;
+	}
+
+	/* start seeking */
+	radio->registers[SEEKCFG1] |= SEEKCFG1_CSR0_SEEK;
+	retval = rtc6226_set_register(radio, SEEKCFG1);
+	if (retval < 0) {
+		radio->registers[SEEKCFG1] = seekcfg1_val;
+		goto done;
+	}
+
+done:
+	FMDBG("%s exit %d\n", __func__, retval);
+	return retval;
+}
+
+static void rtc6226_update_search_list(struct rtc6226_device *radio, int freq)
+{
+	int temp_freq = freq;
+	int index = radio->srch_list.num_stations_found;
+
+	temp_freq = temp_freq -
+		(radio->recv_conf.band_low_limit * TUNE_STEP_SIZE);
+	temp_freq = temp_freq / 50;
+	radio->srch_list.rel_freq[index].rel_freq_lsb = GET_LSB(temp_freq);
+	radio->srch_list.rel_freq[index].rel_freq_msb = GET_MSB(temp_freq);
+	radio->srch_list.num_stations_found++;
+}
+
+void rtc6226_scan(struct work_struct *work)
+{
+	struct rtc6226_device *radio;
+	int current_freq_khz = 0;
+	struct kfifo *data_b;
+	int len = 0;
+	u32 next_freq_khz;
+	int retval = 0;
+	int i, rssi;
+
+	FMDBG("%s enter\n", __func__);
+
+	radio = container_of(work, struct rtc6226_device, work_scan.work);
+
+	retval = rtc6226_get_freq(radio, &current_freq_khz);
+	if (retval < 0) {
+		FMDERR("%s fail to get freq\n", __func__);
+		goto seek_tune_fail;
+	}
+	FMDBG("%s current freq %d\n", __func__, current_freq_khz);
+		/* tune to lowest freq of the band */
+	radio->seek_tune_status = SCAN_PENDING;
+	retval = rtc6226_set_freq(radio,
+		radio->recv_conf.band_low_limit * TUNE_STEP_SIZE);
+	if (retval < 0)
+		goto seek_tune_fail;
+	/* wait for tune to complete. */
+	if (!wait_for_completion_timeout(&radio->completion,
+				msecs_to_jiffies(TUNE_TIMEOUT_MSEC))) {
+		FMDERR("In %s, didn't receive STC for tune\n", __func__);
+		rtc6226_q_event(radio, RTC6226_EVT_ERROR);
+		return;
+	}
+
+	while (1) {
+		if (radio->is_search_cancelled) {
+			FMDERR("%s: scan cancelled\n", __func__);
+			if (radio->g_search_mode == SCAN_FOR_STRONG)
+				goto seek_tune_fail;
+			else
+				goto seek_cancelled;
+			goto seek_cancelled;
+		} else if (radio->mode != FM_RECV) {
+			FMDERR("%s: FM is not in proper state\n", __func__);
+			rtc6226_q_event(radio, RTC6226_EVT_ERROR);
+			return;
+		}
+
+		retval = rtc6226_set_seek(radio, SRCH_UP, WRAP_DISABLE);
+		if (retval < 0) {
+			FMDERR("%s seek fail %d\n", __func__, retval);
+			goto seek_tune_fail;
+		}
+			/* wait for seek to complete */
+		if (!wait_for_completion_timeout(&radio->completion,
+					msecs_to_jiffies(SEEK_TIMEOUT_MSEC))) {
+			FMDERR("%s:timeout didn't receive STC for seek\n",
+						__func__);
+			rtc6226_get_all_registers(radio);
+			for (i = 0; i < 16; i++)
+				FMDBG("%s registers[%d]:%x\n", __func__, i,
+					radio->registers[i]);
+			/* FM is not correct state or scan is cancelled */
+			rtc6226_q_event(radio, RTC6226_EVT_ERROR);
+			return;
+		} else
+			FMDERR("%s: received STC for seek\n", __func__);
+
+		retval = rtc6226_get_freq(radio, &next_freq_khz);
+		if (retval < 0) {
+			FMDERR("%s fail to get freq\n", __func__);
+			goto seek_tune_fail;
+		}
+		FMDBG("%s next freq %d\n", __func__, next_freq_khz);
+
+		retval = rtc6226_get_register(radio, RSSI);
+		if (retval < 0) {
+			FMDERR("%s read fail to RSSI\n", __func__);
+			goto seek_tune_fail;
+		}
+		rssi = radio->registers[RSSI] & RSSI_RSSI;
+		FMDBG("%s valid channel %d, rssi %d threshold rssi %d\n",
+				 __func__, next_freq_khz, rssi, radio->rssi_th);
+
+		if (radio->g_search_mode == SCAN && rssi >= radio->rssi_th)
+			rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC);
+		/*
+		 * If scan is cancelled or FM is not ON, break ASAP so that we
+		 * don't need to sleep for dwell time.
+		 */
+		if (radio->is_search_cancelled) {
+			FMDERR("%s: scan cancelled\n", __func__);
+			if (radio->g_search_mode == SCAN_FOR_STRONG)
+				goto seek_tune_fail;
+			else
+				goto seek_cancelled;
+			goto seek_cancelled;
+		} else if (radio->mode != FM_RECV) {
+			FMDERR("%s: FM is not in proper state\n", __func__);
+			rtc6226_q_event(radio, RTC6226_EVT_ERROR);
+			return;
+		}
+		FMDBG("%s update search list %d\n", __func__, next_freq_khz);
+		if (radio->g_search_mode == SCAN && rssi >= radio->rssi_th) {
+			/* sleep for dwell period */
+			msleep(radio->dwell_time_sec * 1000);
+			/* need to queue the event when the seek completes */
+			FMDBG("%s frequency update list %d\n", __func__,
+				next_freq_khz);
+			rtc6226_q_event(radio, RTC6226_EVT_SCAN_NEXT);
+		} else if (radio->g_search_mode == SCAN_FOR_STRONG
+					&& rssi >= radio->rssi_th) {
+			rtc6226_update_search_list(radio, next_freq_khz);
+		}
+
+		FMDBG("%s : STATUS=0x%4.4hx\n", __func__,
+				radio->registers[STATUS]);
+		if (radio->registers[STATUS] & STATUS_SF ||
+				(radio->recv_conf.band_high_limit *
+				TUNE_STEP_SIZE) == next_freq_khz) {
+			FMDERR("%s Seek one more time if lower freq is valid\n",
+					__func__);
+			retval = rtc6226_set_seek(radio, SRCH_UP, WRAP_ENABLE);
+			if (retval < 0) {
+				FMDERR("%s seek fail %d\n", __func__, retval);
+				goto seek_tune_fail;
+			}
+			if (!wait_for_completion_timeout(&radio->completion,
+					msecs_to_jiffies(SEEK_TIMEOUT_MSEC))) {
+				FMDERR("timeout didn't receive STC for seek\n");
+				rtc6226_q_event(radio, RTC6226_EVT_ERROR);
+				return;
+			} else {
+				FMDERR("%s: received STC for seek\n", __func__);
+				retval = rtc6226_get_freq(radio,
+						&next_freq_khz);
+				if (retval < 0) {
+					FMDERR("%s getFreq failed\n", __func__);
+					goto seek_tune_fail;
+				}
+				retval = rtc6226_get_register(radio, RSSI);
+				if (retval < 0) {
+					FMDERR("%s read fail to RSSI\n",
+								__func__);
+					goto seek_tune_fail;
+				}
+				rssi = radio->registers[RSSI] & RSSI_RSSI;
+				FMDBG("%s freq %d, rssi %d rssi threshold %d\n",
+				 __func__, next_freq_khz, rssi, radio->rssi_th);
+				if ((radio->recv_conf.band_low_limit *
+						TUNE_STEP_SIZE) ==
+							next_freq_khz &&
+						rssi >= radio->rssi_th) {
+					FMDERR("lower band freq is valid\n");
+					rtc6226_q_event(radio,
+						RTC6226_EVT_TUNE_SUCC);
+					/* sleep for dwell period */
+					msleep(radio->dwell_time_sec * 1000);
+					rtc6226_q_event(radio,
+						RTC6226_EVT_SCAN_NEXT);
+				}
+			}
+			break;
+		}
+
+	}
+
+seek_tune_fail:
+	if (radio->g_search_mode == SCAN_FOR_STRONG) {
+		len = radio->srch_list.num_stations_found * 2 +
+			sizeof(radio->srch_list.num_stations_found);
+		data_b = &radio->data_buf[RTC6226_FM_BUF_SRCH_LIST];
+		kfifo_in_locked(data_b, &radio->srch_list, len,
+				&radio->buf_lock[RTC6226_FM_BUF_SRCH_LIST]);
+		rtc6226_q_event(radio, RTC6226_EVT_NEW_SRCH_LIST);
+	}
+seek_cancelled:
+	/* tune to original frequency */
+	retval = rtc6226_set_freq(radio, current_freq_khz);
+	if (retval < 0)
+		FMDERR("%s: Tune to orig freq failed with error %d\n",
+				__func__, retval);
+	else {
+		if (!wait_for_completion_timeout(&radio->completion,
+			msecs_to_jiffies(TUNE_TIMEOUT_MSEC)))
+			FMDERR("%s: didn't receive STD for tune\n", __func__);
+		else
+			FMDERR("%s: received STD for tune\n", __func__);
+	}
+	/* Enable the RDS as it was disabled before scan */
+	rtc6226_rds_on(radio);
+	rtc6226_q_event(radio, RTC6226_EVT_SEEK_COMPLETE);
+	rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC);
+	radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
+	FMDERR("%s seek cancelled %d\n", __func__, retval);
+	return;
+
+}
+
+int rtc6226_cancel_seek(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	FMDBG("%s enter\n", __func__);
+	mutex_lock(&radio->lock);
+
+	/* stop seeking */
+	radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK;
+	retval = rtc6226_set_register(radio, SEEKCFG1);
+	complete(&radio->completion);
+
+	mutex_unlock(&radio->lock);
+	radio->is_search_cancelled = true;
+	if (radio->g_search_mode == SEEK)
+		rtc6226_q_event(radio, RTC6226_EVT_SEEK_COMPLETE);
+
+	return retval;
+
+}
+
+void rtc6226_search(struct rtc6226_device *radio, bool on)
+{
+	int current_freq_khz;
+
+	current_freq_khz = radio->tuned_freq_khz;
+
+	if (on) {
+		FMDBG("%s: Queuing the work onto scan work q\n", __func__);
+		queue_delayed_work(radio->wqueue_scan, &radio->work_scan,
+					msecs_to_jiffies(10));
+	} else {
+		rtc6226_cancel_seek(radio);
+	}
+}
+
+/*
+ * rtc6226_start - switch on radio
+ */
+int rtc6226_start(struct rtc6226_device *radio)
+{
+	int retval;
+	u8 i2c_error;
+	u16 initbuf[] = {0x0000};
+
+	radio->registers[BANKCFG] = 0x0000;
+	i2c_error = 0;
+	/* Keep in case of any unpredicted control */
+	/* Set 0x16AA */
+	radio->registers[DEVICEID] = 0x16AA;
+	/* released the I2C from unexpected I2C start condition */
+	retval = rtc6226_set_register(radio, DEVICEID);
+	/* recheck TH : 10 */
+	while ((retval < 0) && (i2c_error < 10)) {
+		retval = rtc6226_set_register(radio, DEVICEID);
+		i2c_error++;
+	}
+
+	if (retval < 0) {
+		FMDERR("%s set to fail retval = %d\n", __func__, retval);
+		/* goto done;*/
+	}
+	msleep(30);
+
+	/* Don't read all between writing 0x16AA and 0x96AA */
+	i2c_error = 0;
+	radio->registers[DEVICEID] = 0x96AA;
+	retval = rtc6226_set_register(radio, DEVICEID);
+	/* recheck TH : 10 */
+	while ((retval < 0) && (i2c_error < 10)) {
+		retval = rtc6226_set_register(radio, DEVICEID);
+		i2c_error++;
+	}
+
+	if (retval < 0)
+		FMDERR("%s set to fail 0x96AA %d\n", __func__, retval);
+	msleep(30);
+
+	/* get device and chip versions */
+	rtc6226_get_register(radio, DEVICEID);
+	rtc6226_get_register(radio, CHIPID);
+	FMDBG("%s DeviceID=0x%x ChipID=0x%x Addr=0x%x\n", __func__,
+		radio->registers[DEVICEID], radio->registers[CHIPID],
+		radio->client->addr);
+
+	/* Have to update shadow buf from all register */
+	retval = rtc6226_get_all_registers(radio);
+	if (retval < 0)
+		goto done;
+
+	FMDBG("%s rtc6226_power_up1: DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
+		__func__,
+		radio->registers[DEVICEID], radio->registers[CHIPID]);
+	FMDBG("%s rtc6226_power_up2: Reg2=0x%4.4hx Reg3=0x%4.4hx\n", __func__,
+		radio->registers[MPXCFG], radio->registers[CHANNEL]);
+	FMDBG("%s rtc6226_power_up3: Reg4=0x%4.4hx Reg5=0x%4.4hx\n", __func__,
+		radio->registers[SYSCFG], radio->registers[SEEKCFG1]);
+	FMDBG("%s rtc6226_power_up4: Reg6=0x%4.4hx Reg7=0x%4.4hx\n", __func__,
+		radio->registers[POWERCFG], radio->registers[PADCFG]);
+	FMDBG("%s rtc6226_power_up5: Reg8=0x%4.4hx Reg9=0x%4.4hx\n", __func__,
+		radio->registers[8], radio->registers[9]);
+	FMDBG("%s rtc6226_power_up6: regA=0x%4.4hx RegB=0x%4.4hx\n", __func__,
+		radio->registers[10], radio->registers[11]);
+	FMDBG("%s rtc6226_power_up7: regC=0x%4.4hx RegD=0x%4.4hx\n", __func__,
+		radio->registers[12], radio->registers[13]);
+	FMDBG("%s rtc6226_power_up8: regE=0x%4.4hx RegF=0x%4.4hx\n", __func__,
+		radio->registers[14], radio->registers[15]);
+
+
+	FMDBG("%s DeviceID=0x%x ChipID=0x%x Addr=0x%x\n", __func__,
+		radio->registers[DEVICEID], radio->registers[CHIPID],
+		radio->client->addr);
+
+	/* initial patch 01 */
+	initbuf[0] = 0x0038;
+	retval = rtc6226_set_serial_registers(radio, initbuf, 0x40);
+	if (retval < 0)
+		goto done;
+
+	/* initial patch 02 */
+	initbuf[0] = 0xC100;
+	retval = rtc6226_set_serial_registers(radio, initbuf, 0x8E);
+	if (retval < 0)
+		goto done;
+
+done:
+	return retval;
+}
+
+/*
+ * rtc6226_stop - switch off radio
+ */
+int rtc6226_stop(struct rtc6226_device *radio)
+{
+	int retval;
+
+	/* sysconfig */
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN;
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN;
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_STDIRQEN;
+	retval = rtc6226_set_register(radio, SYSCFG);
+	if (retval < 0)
+		goto done;
+
+	/* powerconfig */
+	radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DIS_MUTE;
+	retval = rtc6226_set_register(radio, MPXCFG);
+
+	/* POWERCFG_ENABLE has to automatically go low */
+	radio->registers[POWERCFG] |= POWERCFG_CSR0_DISABLE;
+	radio->registers[POWERCFG] &= ~POWERCFG_CSR0_ENABLE;
+	retval = rtc6226_set_register(radio, POWERCFG);
+
+	/* Set 0x16AA */
+	radio->registers[DEVICEID] = 0x16AA;
+	retval = rtc6226_set_register(radio, DEVICEID);
+
+done:
+	return retval;
+}
+
+static void rtc6226_get_rds(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	mutex_lock(&radio->lock);
+	retval = rtc6226_get_all_registers(radio);
+
+	if (retval < 0) {
+		FMDERR("%s read fail%d\n", __func__, retval);
+		mutex_unlock(&radio->lock);
+		return;
+	}
+	radio->block[0] = radio->registers[BA_DATA];
+	radio->block[1] = radio->registers[BB_DATA];
+	radio->block[2] = radio->registers[BC_DATA];
+	radio->block[3] = radio->registers[BD_DATA];
+
+	radio->bler[0] = (radio->registers[RSSI] & RSSI_RDS_BA_ERRS) >> 14;
+	radio->bler[1] = (radio->registers[RSSI] & RSSI_RDS_BB_ERRS) >> 12;
+	radio->bler[2] = (radio->registers[RSSI] & RSSI_RDS_BC_ERRS) >> 10;
+	radio->bler[3] = (radio->registers[RSSI] & RSSI_RDS_BD_ERRS) >> 8;
+	mutex_unlock(&radio->lock);
+}
+
+static void rtc6226_pi_check(struct rtc6226_device *radio, u16 current_pi)
+{
+	if (radio->pi != current_pi) {
+		FMDBG("%s current_pi %x , radio->pi %x\n"
+				, __func__, current_pi, radio->pi);
+		radio->pi = current_pi;
+	} else {
+		FMDBG("%s Received same PI code\n", __func__);
+	}
+}
+
+static void rtc6226_pty_check(struct rtc6226_device *radio, u8 current_pty)
+{
+	if (radio->pty != current_pty) {
+		FMDBG("%s PTY code of radio->block[1] = %x\n",
+			__func__, current_pty);
+		radio->pty = current_pty;
+	} else {
+		FMDBG("%s PTY repeated\n", __func__);
+	}
+}
+
+static bool is_new_freq(struct rtc6226_device *radio, u32 freq)
+{
+	u8 i = 0;
+
+	for (i = 0; i < radio->af_info2.size; i++) {
+		if (freq == radio->af_info2.af_list[i])
+			return false;
+	}
+
+	return true;
+}
+
+static bool is_different_af_list(struct rtc6226_device *radio)
+{
+	u8 i = 0, j = 0;
+	u32 freq;
+
+	if (radio->af_info1.orig_freq_khz != radio->af_info2.orig_freq_khz)
+		return true;
+
+	/* freq is same, check if the AFs are same. */
+	for (i = 0; i < radio->af_info1.size; i++) {
+		freq = radio->af_info1.af_list[i];
+		for (j = 0; j < radio->af_info2.size; j++) {
+			if (freq == radio->af_info2.af_list[j])
+				break;
+		}
+
+		/* freq is not there in list2 i.e list1, list2 are different.*/
+		if (j == radio->af_info2.size)
+			return true;
+	}
+
+	return false;
+}
+
+static bool is_valid_freq(struct rtc6226_device *radio, u32 freq)
+{
+	u32 band_low_limit;
+	u32 band_high_limit;
+	u8 spacing = 0;
+
+	band_low_limit = radio->recv_conf.band_low_limit * TUNE_STEP_SIZE;
+	band_high_limit = radio->recv_conf.band_high_limit * TUNE_STEP_SIZE;
+
+	if (radio->space == 0)
+		spacing = CH_SPACING_200;
+	else if (radio->space == 1)
+		spacing = CH_SPACING_100;
+	else if (radio->space == 2)
+		spacing = CH_SPACING_50;
+	else
+		return false;
+
+	if ((freq >= band_low_limit) &&
+			(freq <= band_high_limit) &&
+			((freq - band_low_limit) % spacing == 0))
+		return true;
+
+	return false;
+}
+
+static void rtc6226_update_af_list(struct rtc6226_device *radio)
+{
+
+	bool retval;
+	u8 i = 0;
+	u8 af_data = radio->block[2] >> 8;
+	u32 af_freq_khz;
+	u32 tuned_freq_khz;
+	struct kfifo *buff;
+	struct af_list_ev ev;
+	spinlock_t lock = radio->buf_lock[RTC6226_FM_BUF_AF_LIST];
+
+	rtc6226_get_freq(radio, &tuned_freq_khz);
+
+	for (; i < NO_OF_AF_IN_GRP; i++, af_data = radio->block[2] & 0xFF) {
+
+		if (af_data >= MIN_AF_CNT_CODE && af_data <= MAX_AF_CNT_CODE) {
+
+			FMDBG("%s: resetting af info, freq %u, pi %u\n",
+					__func__, tuned_freq_khz, radio->pi);
+			radio->af_info2.inval_freq_cnt = 0;
+			radio->af_info2.cnt = 0;
+			radio->af_info2.orig_freq_khz = 0;
+
+			/* AF count. */
+			radio->af_info2.cnt = af_data - NO_AF_CNT_CODE;
+			radio->af_info2.orig_freq_khz = tuned_freq_khz;
+			radio->af_info2.pi = radio->pi;
+
+			FMDBG("%s: current freq is %u, AF cnt is %u\n",
+				__func__, tuned_freq_khz, radio->af_info2.cnt);
+		} else if (af_data >= MIN_AF_FREQ_CODE &&
+				af_data <= MAX_AF_FREQ_CODE &&
+				radio->af_info2.orig_freq_khz != 0 &&
+				radio->af_info2.size < MAX_NO_OF_AF) {
+
+			af_freq_khz = SCALE_AF_CODE_TO_FREQ_KHZ(af_data);
+			retval = is_valid_freq(radio, af_freq_khz);
+			if (!retval) {
+				FMDBG("%s: Invalid AF\n", __func__);
+				radio->af_info2.inval_freq_cnt++;
+				continue;
+			}
+
+			retval = is_new_freq(radio, af_freq_khz);
+			if (!retval) {
+				FMDBG("%s: Duplicate AF\n", __func__);
+				radio->af_info2.inval_freq_cnt++;
+				continue;
+			}
+
+			/* update the AF list */
+			radio->af_info2.af_list[radio->af_info2.size++] =
+				af_freq_khz;
+			FMDBG("%s: AF is %u\n", __func__, af_freq_khz);
+			if ((radio->af_info2.size +
+					radio->af_info2.inval_freq_cnt ==
+					radio->af_info2.cnt) &&
+					is_different_af_list(radio)) {
+
+				/* Copy the list to af_info1. */
+				radio->af_info1.cnt = radio->af_info2.cnt;
+				radio->af_info1.size = radio->af_info2.size;
+				radio->af_info1.pi = radio->af_info2.pi;
+				radio->af_info1.orig_freq_khz =
+					radio->af_info2.orig_freq_khz;
+				memset(radio->af_info1.af_list, 0,
+					sizeof(radio->af_info1.af_list));
+
+				memcpy(radio->af_info1.af_list,
+					radio->af_info2.af_list,
+					sizeof(radio->af_info2.af_list));
+
+				/* AF list changed, post it to user space */
+				memset(&ev, 0, sizeof(struct af_list_ev));
+
+				ev.tune_freq_khz =
+					radio->af_info1.orig_freq_khz;
+				ev.pi_code = radio->pi;
+				ev.af_size = radio->af_info1.size;
+
+				memcpy(&ev.af_list[0],
+						radio->af_info1.af_list,
+						GET_AF_LIST_LEN(ev.af_size));
+
+				buff = &radio->data_buf[RTC6226_FM_BUF_AF_LIST];
+				kfifo_in_locked(buff,
+						(u8 *)&ev,
+						GET_AF_EVT_LEN(ev.af_size),
+						&lock);
+
+				FMDBG("%s: posting AF list evt,currfreq %u\n",
+						__func__, ev.tune_freq_khz);
+
+				rtc6226_q_event(radio,
+						RTC6226_EVT_NEW_AF_LIST);
+			}
+		}
+	}
+}
+
+static void rtc6226_update_ps(struct rtc6226_device *radio, u8 addr, u8 ps)
+{
+	u8 i;
+	bool ps_txt_chg = false;
+	bool ps_cmplt = true;
+	u8 *data;
+	struct kfifo *data_b;
+
+	FMDBG("%s enter addr:%x ps:%x\n", __func__, addr, ps);
+
+	if (radio->ps_tmp0[addr] == ps) {
+		if (radio->ps_cnt[addr] < PS_VALIDATE_LIMIT) {
+			radio->ps_cnt[addr]++;
+		} else {
+			radio->ps_cnt[addr] = PS_VALIDATE_LIMIT;
+			radio->ps_tmp1[addr] = ps;
+		}
+	} else if (radio->ps_tmp1[addr] == ps) {
+		if (radio->ps_cnt[addr] >= PS_VALIDATE_LIMIT) {
+			ps_txt_chg = true;
+			radio->ps_cnt[addr] = PS_VALIDATE_LIMIT + 1;
+		} else {
+			radio->ps_cnt[addr] = PS_VALIDATE_LIMIT;
+		}
+		radio->ps_tmp1[addr] = radio->ps_tmp0[addr];
+		radio->ps_tmp0[addr] = ps;
+	} else if (!radio->ps_cnt[addr]) {
+		radio->ps_tmp0[addr] = ps;
+		radio->ps_cnt[addr] = 1;
+	} else {
+		radio->ps_tmp1[addr] = ps;
+	}
+
+	if (ps_txt_chg) {
+		for (i = 0; i < MAX_PS_LEN; i++) {
+			if (radio->ps_cnt[i] > 1)
+				radio->ps_cnt[i]--;
+		}
+	}
+
+	for (i = 0; i < MAX_PS_LEN; i++) {
+		if (radio->ps_cnt[i] < PS_VALIDATE_LIMIT) {
+			FMDBG("%s ps_cnt[%d] %d\n", __func__, i,
+				radio->ps_cnt[i]);
+			ps_cmplt = false;
+			return;
+		}
+	}
+
+	if (ps_cmplt) {
+		for (i = 0; (i < MAX_PS_LEN) &&
+			(radio->ps_display[i] == radio->ps_tmp0[i]); i++)
+			;
+		if (i == MAX_PS_LEN) {
+			FMDBG("%s Same PS string repeated\n", __func__);
+			return;
+		}
+
+		for (i = 0; i < MAX_PS_LEN; i++)
+			radio->ps_display[i] = radio->ps_tmp0[i];
+
+		data = kmalloc(PS_EVT_DATA_LEN, GFP_ATOMIC);
+		if (data != NULL) {
+			data[0] = NO_OF_PS;
+			data[1] = radio->pty;
+			data[2] = (radio->pi >> 8) & 0xFF;
+			data[3] = (radio->pi & 0xFF);
+			data[4] = 0;
+			memcpy(data + OFFSET_OF_PS,
+					radio->ps_tmp0, MAX_PS_LEN);
+			data_b = &radio->data_buf[RTC6226_FM_BUF_PS_RDS];
+			kfifo_in_locked(data_b, data, PS_EVT_DATA_LEN,
+				&radio->buf_lock[RTC6226_FM_BUF_PS_RDS]);
+			FMDBG("%s Q the PS event\n", __func__);
+			rtc6226_q_event(radio, RTC6226_EVT_NEW_PS_RDS);
+			kfree(data);
+		} else {
+			FMDERR("%s Memory allocation failed for PTY\n",
+				__func__);
+		}
+	}
+}
+
+static void display_rt(struct rtc6226_device *radio)
+{
+	u8 len = 0, i = 0;
+	u8 *data;
+	struct kfifo *data_b;
+	bool rt_cmplt = true;
+
+	FMDBG("%s enter\n", __func__);
+
+	for (i = 0; i < MAX_RT_LEN; i++) {
+		if (radio->rt_cnt[i] < RT_VALIDATE_LIMIT) {
+			FMDBG("%s rt_cnt %d\n", __func__, radio->rt_cnt[i]);
+			rt_cmplt = false;
+			return;
+		}
+		if (radio->rt_tmp0[i] == END_OF_RT)
+			break;
+	}
+	if (rt_cmplt) {
+		while ((len < MAX_RT_LEN) && (radio->rt_tmp0[len] != END_OF_RT))
+			len++;
+
+		for (i = 0; (i < len) &&
+		(radio->rt_display[i] == radio->rt_tmp0[i]); i++)
+			;
+		if (i == len) {
+			FMDBG("%s Same RT string repeated\n", __func__);
+			return;
+		}
+		for (i = 0; i < len; i++)
+			radio->rt_display[i] = radio->rt_tmp0[i];
+		data = kmalloc(len + OFFSET_OF_RT, GFP_ATOMIC);
+		if (data != NULL) {
+			data[0] = len; /* len of RT */
+			data[1] = radio->pty;
+			data[2] = (radio->pi >> 8) & 0xFF;
+			data[3] = (radio->pi & 0xFF);
+			data[4] = radio->rt_flag;
+			memcpy(data + OFFSET_OF_RT, radio->rt_display, len);
+			data_b = &radio->data_buf[RTC6226_FM_BUF_RT_RDS];
+			kfifo_in_locked(data_b, data, OFFSET_OF_RT + len,
+				&radio->buf_lock[RTC6226_FM_BUF_RT_RDS]);
+			FMDBG("%s Q the RT event\n", __func__);
+			rtc6226_q_event(radio, RTC6226_EVT_NEW_RT_RDS);
+			kfree(data);
+		} else {
+			FMDERR("%s Memory allocation failed for PTY\n",
+				__func__);
+		}
+	}
+}
+
+static void rt_handler(struct rtc6226_device *radio, u8 ab_flg,
+		u8 cnt, u8 addr, u8 *rt)
+{
+	u8 i, errcnt, blermax;
+	bool rt_txt_chg = false;
+
+	FMDBG("%s enter\n", __func__);
+
+	if (ab_flg != radio->rt_flag && radio->valid_rt_flg) {
+		for (i = 0; i < sizeof(radio->rt_cnt); i++) {
+			if (!radio->rt_tmp0[i]) {
+				radio->rt_tmp0[i] = ' ';
+				radio->rt_cnt[i]++;
+			}
+		}
+		memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt));
+		memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0));
+		memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1));
+	}
+
+	radio->rt_flag = ab_flg;
+	radio->valid_rt_flg = true;
+
+	for (i = 0; i < cnt; i++) {
+		if ((i < 2) && (cnt > 2)) {
+			errcnt = radio->bler[2];
+			blermax = CORRECTED_THREE_TO_FIVE;
+		} else {
+			errcnt = radio->bler[3];
+			blermax = CORRECTED_THREE_TO_FIVE;
+		}
+		if (errcnt <= blermax) {
+			if (!rt[i])
+				rt[i] = ' ';
+			if (radio->rt_tmp0[addr+i] == rt[i]) {
+				if (radio->rt_cnt[addr+i] < RT_VALIDATE_LIMIT) {
+					radio->rt_cnt[addr+i]++;
+				} else {
+					radio->rt_cnt[addr+i] =
+							RT_VALIDATE_LIMIT;
+					radio->rt_tmp1[addr+i] = rt[i];
+				}
+			} else if (radio->rt_tmp1[addr+i] == rt[i]) {
+				if (radio->rt_cnt[addr+i] >=
+						RT_VALIDATE_LIMIT) {
+					rt_txt_chg = true;
+					radio->rt_cnt[addr+i] =
+						RT_VALIDATE_LIMIT + 1;
+				} else {
+					radio->rt_cnt[addr+i] =
+						RT_VALIDATE_LIMIT;
+				}
+				radio->rt_tmp1[addr+i] = radio->rt_tmp0[addr+i];
+				radio->rt_tmp0[addr+i] = rt[i];
+			} else if (!radio->rt_cnt[addr+i]) {
+				radio->rt_tmp0[addr+i] = rt[i];
+				radio->rt_cnt[addr+i] = 1;
+			} else {
+				radio->rt_tmp1[addr+i] = rt[i];
+			}
+		}
+	}
+
+	if (rt_txt_chg) {
+		for (i = 0; i < MAX_RT_LEN; i++) {
+			if (radio->rt_cnt[i] > 1)
+				radio->rt_cnt[i]--;
+		}
+	}
+	display_rt(radio);
+}
+
+static void rtc6226_raw_rds(struct rtc6226_device *radio)
+{
+	u16 aid, app_grp_typ;
+
+	aid = radio->block[3];
+	app_grp_typ = radio->block[1] & APP_GRP_typ_MASK;
+	FMDBG("%s app_grp_typ = %x\n", __func__, app_grp_typ);
+	FMDBG("%s AID = %x\n", __func__, aid);
+
+	switch (aid) {
+	case ERT_AID:
+		radio->utf_8_flag = (radio->block[2] & 1);
+		radio->formatting_dir = EXTRACT_BIT(radio->block[2],
+			ERT_FORMAT_DIR_BIT);
+		if (radio->ert_carrier != app_grp_typ) {
+			rtc6226_q_event(radio, RTC6226_EVT_NEW_ODA);
+			radio->ert_carrier = app_grp_typ;
+		}
+		break;
+	case RT_PLUS_AID:
+		/*Extract 5th bit of MSB (b7b6b5b4b3b2b1b0)*/
+		radio->rt_ert_flag = EXTRACT_BIT(radio->block[2],
+				RT_ERT_FLAG_BIT);
+		if (radio->rt_plus_carrier != app_grp_typ) {
+			rtc6226_q_event(radio, RTC6226_EVT_NEW_ODA);
+			radio->rt_plus_carrier = app_grp_typ;
+		}
+		break;
+	default:
+		FMDBG("%s Not handling the AID of  %x\n", __func__, aid);
+		break;
+	}
+}
+
+static void rtc6226_ev_ert(struct rtc6226_device *radio)
+{
+	u8 *data = NULL;
+	struct kfifo *data_b;
+
+	if (radio->ert_len <= 0)
+		return;
+
+	FMDBG("%s enter\n", __func__);
+	data = kmalloc((radio->ert_len + ERT_OFFSET), GFP_ATOMIC);
+	if (data != NULL) {
+		data[0] = radio->ert_len;
+		data[1] = radio->utf_8_flag;
+		data[2] = radio->formatting_dir;
+		memcpy((data + ERT_OFFSET), radio->ert_buf, radio->ert_len);
+		data_b = &radio->data_buf[RTC6226_FM_BUF_ERT];
+		kfifo_in_locked(data_b, data, (radio->ert_len + ERT_OFFSET),
+				&radio->buf_lock[RTC6226_FM_BUF_ERT]);
+		rtc6226_q_event(radio, RTC6226_EVT_NEW_ERT);
+		kfree(data);
+	}
+}
+
+static void rtc6226_buff_ert(struct rtc6226_device *radio)
+{
+	int i;
+	u16 info_byte = 0;
+	u8 byte_pair_index;
+
+	byte_pair_index = radio->block[1] & APP_GRP_typ_MASK;
+	if (byte_pair_index == 0) {
+		radio->c_byt_pair_index = 0;
+		radio->ert_len = 0;
+	}
+	if (radio->c_byt_pair_index == byte_pair_index) {
+		for (i = 2; i <= 3; i++) {
+			info_byte = radio->block[i];
+			FMDBG("%s info_byte = %x\n", __func__, info_byte);
+			FMDBG("%s ert_len = %x\n", __func__, radio->ert_len);
+			if (radio->ert_len > (MAX_ERT_LEN - 2))
+				return;
+			radio->ert_buf[radio->ert_len] = radio->block[i] >> 8;
+			radio->ert_buf[radio->ert_len + 1] =
+				radio->block[i] & 0xFF;
+			radio->ert_len += ERT_CNT_PER_BLK;
+			FMDBG("%s utf_8_flag = %d\n", __func__,
+				radio->utf_8_flag);
+			if ((radio->utf_8_flag == 0) &&
+					(info_byte == END_OF_RT)) {
+				radio->ert_len -= ERT_CNT_PER_BLK;
+				break;
+			} else if ((radio->utf_8_flag == 1) &&
+					(radio->block[i] >> 8 == END_OF_RT)) {
+				info_byte = END_OF_RT;
+				radio->ert_len -= ERT_CNT_PER_BLK;
+				break;
+			} else if ((radio->utf_8_flag == 1) &&
+					((radio->block[i] & 0xFF)
+					 == END_OF_RT)) {
+				info_byte = END_OF_RT;
+				radio->ert_len--;
+				break;
+			}
+		}
+		if ((byte_pair_index == MAX_ERT_SEGMENT) ||
+				(info_byte == END_OF_RT)) {
+			rtc6226_ev_ert(radio);
+			radio->c_byt_pair_index = 0;
+			radio->ert_len = 0;
+		}
+		radio->c_byt_pair_index++;
+	} else {
+		radio->ert_len = 0;
+		radio->c_byt_pair_index = 0;
+	}
+}
+
+static void rtc6226_rt_plus(struct rtc6226_device *radio)
+{
+	u8 tag_type1, tag_type2;
+	u8 *data = NULL;
+	int len = 0;
+	u16 grp_typ;
+	struct kfifo *data_b;
+
+	grp_typ = radio->block[1] & APP_GRP_typ_MASK;
+	/*
+	 *right most 3 bits of Lsb of block 2
+	 * and left most 3 bits of Msb of block 3
+	 */
+	tag_type1 = (((grp_typ & TAG1_MSB_MASK) << TAG1_MSB_OFFSET) |
+			(radio->block[2] >> TAG1_LSB_OFFSET));
+	/*
+	 *right most 1 bit of lsb of 3rd block
+	 * and left most 5 bits of Msb of 4th block
+	 */
+	tag_type2 = (((radio->block[2] & TAG2_MSB_MASK)
+				<< TAG2_MSB_OFFSET) |
+			(radio->block[2] >> TAG2_LSB_OFFSET));
+
+	if (tag_type1 != DUMMY_CLASS)
+		len += RT_PLUS_LEN_1_TAG;
+	if (tag_type2 != DUMMY_CLASS)
+		len += RT_PLUS_LEN_1_TAG;
+
+	if (len != 0) {
+		len += RT_PLUS_OFFSET;
+		data = kmalloc(len, GFP_ATOMIC);
+	} else {
+		FMDERR("%s:Len is zero\n", __func__);
+		return;
+	}
+	if (data != NULL) {
+		data[0] = len;
+		len = RT_ERT_FLAG_OFFSET;
+		data[len++] = radio->rt_ert_flag;
+		if (tag_type1 != DUMMY_CLASS) {
+			data[len++] = tag_type1;
+			/*
+			 *start position of tag1
+			 *right most 5 bits of msb of 3rd block
+			 *and left most bit of lsb of 3rd block
+			 */
+			data[len++] = (radio->block[2] >> TAG1_POS_LSB_OFFSET)
+				& TAG1_POS_MSB_MASK;
+			/*
+			 *length of tag1
+			 *left most 6 bits of lsb of 3rd block
+			 */
+			data[len++] = (radio->block[2] >> TAG1_LEN_OFFSET) &
+				TAG1_LEN_MASK;
+		}
+		if (tag_type2 != DUMMY_CLASS) {
+			data[len++] = tag_type2;
+			/*
+			 *start position of tag2
+			 *right most 3 bit of msb of 4th block
+			 *and left most 3 bits of lsb of 4th block
+			 */
+			data[len++] = (radio->block[3] >> TAG2_POS_LSB_OFFSET) &
+				TAG2_POS_MSB_MASK;
+			/*
+			 *length of tag2
+			 *right most 5 bits of lsb of 4th block
+			 */
+			data[len++] = radio->block[3] & TAG2_LEN_MASK;
+		}
+		data_b = &radio->data_buf[RTC6226_FM_BUF_RT_PLUS];
+		kfifo_in_locked(data_b, data, len,
+				&radio->buf_lock[RTC6226_FM_BUF_RT_PLUS]);
+		rtc6226_q_event(radio, RTC6226_EVT_NEW_RT_PLUS);
+		kfree(data);
+	} else {
+		FMDERR("%s:memory allocation failed\n", __func__);
+	}
+}
+
+void rtc6226_rds_handler(struct work_struct *worker)
+{
+	struct rtc6226_device *radio;
+	u8 rt_blks[NO_OF_RDS_BLKS];
+	u8 grp_type, addr, ab_flg;
+
+	radio = container_of(worker, struct rtc6226_device, rds_worker);
+
+	if (!radio) {
+		FMDERR("%s:radio is null\n", __func__);
+		return;
+	}
+
+	FMDBG("%s enter\n", __func__);
+
+	rtc6226_get_rds(radio);
+
+	if (radio->bler[0] < CORRECTED_THREE_TO_FIVE)
+		rtc6226_pi_check(radio, radio->block[0]);
+
+	if (radio->bler[1] < CORRECTED_ONE_TO_TWO) {
+		grp_type = radio->block[1] >> OFFSET_OF_GRP_TYP;
+		FMDBG("%s grp_type = %d\n", __func__, grp_type);
+	} else {
+		/* invalid data case */
+		return;
+	}
+	if (grp_type & 0x01)
+		rtc6226_pi_check(radio, radio->block[2]);
+
+	rtc6226_pty_check(radio, (radio->block[1] >> OFFSET_OF_PTY) & PTY_MASK);
+
+	switch (grp_type) {
+	case RDS_TYPE_0A:
+		if (radio->bler[2] <= CORRECTED_THREE_TO_FIVE)
+			rtc6226_update_af_list(radio);
+		/* fall through */
+		fallthrough;
+	case RDS_TYPE_0B:
+		addr = (radio->block[1] & PS_MASK) * NO_OF_CHARS_IN_EACH_ADD;
+		FMDBG("%s RDS is PS\n", __func__);
+		if (radio->bler[3] <= CORRECTED_THREE_TO_FIVE) {
+			rtc6226_update_ps(radio, addr+0, radio->block[3] >> 8);
+			rtc6226_update_ps(radio, addr+1,
+				radio->block[3] & 0xff);
+		}
+		break;
+	case RDS_TYPE_2A:
+		FMDBG("%s RDS is RT 2A group\n", __func__);
+		rt_blks[0] = (u8)(radio->block[2] >> 8);
+		rt_blks[1] = (u8)(radio->block[2] & 0xFF);
+		rt_blks[2] = (u8)(radio->block[3] >> 8);
+		rt_blks[3] = (u8)(radio->block[3] & 0xFF);
+		addr = (radio->block[1] & 0xf) * 4;
+		ab_flg = (radio->block[1] & 0x0010) >> 4;
+		rt_handler(radio, ab_flg, CNT_FOR_2A_GRP_RT, addr, rt_blks);
+		break;
+	case RDS_TYPE_2B:
+		FMDBG("%s RDS is RT 2B group\n", __func__);
+		rt_blks[0] = (u8)(radio->block[3] >> 8);
+		rt_blks[1] = (u8)(radio->block[3] & 0xFF);
+		rt_blks[2] = 0;
+		rt_blks[3] = 0;
+		addr = (radio->block[1] & 0xf) * 2;
+		ab_flg = (radio->block[1] & 0x0010) >> 4;
+		radio->rt_tmp0[MAX_LEN_2B_GRP_RT] = END_OF_RT;
+		radio->rt_tmp1[MAX_LEN_2B_GRP_RT] = END_OF_RT;
+		radio->rt_cnt[MAX_LEN_2B_GRP_RT] = RT_VALIDATE_LIMIT;
+		rt_handler(radio, ab_flg, CNT_FOR_2B_GRP_RT, addr, rt_blks);
+		break;
+	case RDS_TYPE_3A:
+		FMDBG("%s RDS is 3A group\n", __func__);
+		rtc6226_raw_rds(radio);
+		break;
+	default:
+		FMDERR("%s Not handling the group type %d\n", __func__,
+			grp_type);
+		break;
+	}
+	FMDBG("%s rt_plus_carrier = %x\n", __func__, radio->rt_plus_carrier);
+	FMDBG("%s ert_carrier = %x\n", __func__, radio->ert_carrier);
+	if (radio->rt_plus_carrier && (grp_type == radio->rt_plus_carrier))
+		rtc6226_rt_plus(radio);
+	else if (radio->ert_carrier && (grp_type == radio->ert_carrier))
+		rtc6226_buff_ert(radio);
+}
+
+/*
+ * rtc6226_rds_on - switch on rds reception
+ */
+int rtc6226_rds_on(struct rtc6226_device *radio)
+{
+	int retval;
+
+	FMDBG("%s enter\n", __func__);
+	/* sysconfig */
+	radio->registers[SYSCFG] |= SYSCFG_CSR0_RDS_EN;
+	retval = rtc6226_set_register(radio, SYSCFG);
+
+	if (retval < 0)
+		radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN;
+
+	return retval;
+}
+
+int rtc6226_reset_rds_data(struct rtc6226_device *radio)
+{
+	mutex_lock(&radio->lock);
+	radio->pi = 0;
+	/* reset PS bufferes */
+	memset(radio->ps_display, 0, sizeof(radio->ps_display));
+	memset(radio->ps_tmp0, 0, sizeof(radio->ps_tmp0));
+	memset(radio->ps_tmp1, 0, sizeof(radio->ps_tmp1));
+	memset(radio->ps_cnt, 0, sizeof(radio->ps_cnt));
+
+	memset(radio->rt_display, 0, sizeof(radio->rt_display));
+	memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0));
+	memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1));
+	memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt));
+	radio->wr_index = 0;
+	radio->rd_index = 0;
+	memset(radio->buffer, 0, radio->buf_size);
+	mutex_unlock(&radio->lock);
+
+	return 0;
+}
+
+int rtc6226_set_rssi_threshold(struct rtc6226_device *radio, u16 rssi)
+{
+	int retval = 0;
+
+	/*csr_rssi_low_th = RSSI_threshold/4*/
+	rssi = rssi/4;
+	if ((rssi < MIN_RSSI) && (rssi > MAX_RSSI))
+		return -EINVAL;
+	radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_RSSI_LOW_TH;
+	radio->registers[SEEKCFG1] |= rssi << 8;
+	retval = rtc6226_set_register(radio, SEEKCFG1);
+	radio->rssi_th = (u8)(rssi*4);
+	return retval;
+}
+
+int rtc6226_power_down(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	FMDBG("%s enter\n", __func__);
+
+	mutex_lock(&radio->lock);
+		/* stop radio */
+	retval = rtc6226_stop(radio);
+
+	//rtc6226_disable_irq(radio);
+	mutex_unlock(&radio->lock);
+	FMDBG("%s exit %d\n", __func__, retval);
+
+	return retval;
+}
+
+int rtc6226_power_up(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	mutex_lock(&radio->lock);
+
+	FMDBG("%s enter\n", __func__);
+
+	/* start radio */
+	retval = rtc6226_start(radio);
+	if (retval < 0)
+		goto done;
+	FMDBG("%s : after initialization\n", __func__);
+
+	/* mpxconfig */
+	/* Disable Mute / De-emphasis / Volume 12 */
+	radio->registers[MPXCFG] = 0x000c |
+		MPXCFG_CSR0_DIS_MUTE |
+		((de << 12) & MPXCFG_CSR0_DEEM);
+	retval = rtc6226_set_register(radio, MPXCFG);
+	if (retval < 0)
+		goto done;
+
+	/* enable RDS / STC interrupt */
+	radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN;
+	radio->registers[SYSCFG] |= SYSCFG_CSR0_STDIRQEN;
+	/*radio->registers[SYSCFG] |= SYSCFG_CSR0_RDS_EN;*/
+	retval = rtc6226_set_register(radio, SYSCFG);
+	if (retval < 0)
+		goto done;
+
+	radio->registers[PADCFG] &= ~PADCFG_CSR0_GPIO;
+	radio->registers[PADCFG] |= 0x1 << 2;
+	retval = rtc6226_set_register(radio, PADCFG);
+	if (retval < 0)
+		goto done;
+
+	/* I2S salve */
+	radio->registers[I2SCFG] = 0x2480;
+	retval = rtc6226_set_register(radio, I2SCFG);
+	if (retval < 0)
+		goto done;
+
+	/*set default rssi threshold*/
+	retval = rtc6226_set_rssi_threshold(radio, DEFAULT_RSSI_TH);
+	if (retval < 0)
+		FMDERR("%s fail to set rssi threshold\n", __func__);
+
+	/* powerconfig */
+	/* Enable FM */
+	radio->registers[POWERCFG] = POWERCFG_CSR0_ENABLE;
+	retval = rtc6226_set_register(radio, POWERCFG);
+	if (retval < 0)
+		goto done;
+	/*wait for radio enable to complete*/
+	msleep(30);
+	retval = rtc6226_get_all_registers(radio);
+	if (retval < 0)
+		goto done;
+
+	FMDBG("%s : DeviceID=0x%4.4hx ChipID=0x%4.4hx\n", __func__,
+		radio->registers[DEVICEID], radio->registers[CHIPID]);
+	FMDBG("%s : Reg2=0x%4.4hx Reg3=0x%4.4hx\n", __func__,
+		radio->registers[MPXCFG], radio->registers[CHANNEL]);
+	FMDBG("%s : Reg4=0x%4.4hx Reg5=0x%4.4hx\n", __func__,
+		radio->registers[SYSCFG], radio->registers[SEEKCFG1]);
+	FMDBG("%s : Reg6=0x%4.4hx Reg7=0x%4.4hx\n", __func__,
+		radio->registers[POWERCFG], radio->registers[PADCFG]);
+	FMDBG("%s : Reg8=0x%4.4hx Reg9=0x%4.4hx\n", __func__,
+		radio->registers[8], radio->registers[9]);
+	FMDBG("%s : regA=0x%4.4hx RegB=0x%4.4hx\n", __func__,
+		radio->registers[10], radio->registers[11]);
+	FMDBG("%s : regC=0x%4.4hx RegD=0x%4.4hx\n", __func__,
+		radio->registers[12], radio->registers[13]);
+	FMDBG("%s : regE=0x%4.4hx RegF=0x%4.4hx\n", __func__,
+		radio->registers[14], radio->registers[15]);
+
+done:
+	FMDBG("%s exit %d\n", __func__, retval);
+	mutex_unlock(&radio->lock);
+	return retval;
+}
+
+/**************************************************************************
+ * File Operations Interface
+ **************************************************************************/
+
+/*
+ * rtc6226_fops_read - read event data
+ */
+static ssize_t rtc6226_fops_read(struct file *file, char __user *buffer,
+		size_t count, loff_t *ppos)
+{
+	struct rtc6226_device *radio = video_get_drvdata(video_devdata(file));
+	enum rtc6226_buf_t buf_type = -1;
+	u8 buf_fifo[STD_BUF_SIZE] = {0};
+	struct kfifo *data_fifo = NULL;
+	int len = 0, retval = -1;
+	u32 bytesused = 0;
+
+	if ((radio == NULL) || (buffer == NULL)) {
+		FMDERR("%s radio/buffer is NULL\n", __func__);
+		return -ENXIO;
+	}
+
+	buf_type = count;
+	len = STD_BUF_SIZE;
+	FMDBG("%s: requesting buffer %d\n", __func__, buf_type);
+
+	if ((buf_type < RTC6226_FM_BUF_MAX) && (buf_type >= 0)) {
+		data_fifo = &radio->data_buf[buf_type];
+		if (buf_type == RTC6226_FM_BUF_EVENTS) {
+			if (wait_event_interruptible(radio->event_queue,
+						kfifo_len(data_fifo)) < 0) {
+				return -EINTR;
+			}
+		}
+	} else {
+		FMDERR("%s invalid buffer type\n", __func__);
+		return -EINVAL;
+	}
+	if (len <= STD_BUF_SIZE) {
+		bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0],
+				len, &radio->buf_lock[buf_type]);
+	} else {
+		FMDERR("%s kfifo_out_locked can not use len more than 128\n",
+			__func__);
+		return -EINVAL;
+	}
+	retval = copy_to_user(buffer, &buf_fifo[0], bytesused);
+	if (retval > 0) {
+		FMDERR("%s Failed to copy %d bytes data\n", __func__, retval);
+		return -EAGAIN;
+	}
+	retval = bytesused;
+	return retval;
+}
+
+/*
+ * rtc6226_fops_poll - poll RDS data
+ */
+static unsigned int rtc6226_fops_poll(struct file *file,
+		struct poll_table_struct *pts)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+
+	/* switch on rds reception */
+	mutex_lock(&radio->lock);
+	if ((radio->registers[SYSCFG] & SYSCFG_CSR0_RDS_EN) == 0)
+		rtc6226_rds_on(radio);
+	mutex_unlock(&radio->lock);
+
+	poll_wait(file, &radio->read_queue, pts);
+
+	if (radio->rd_index != radio->wr_index)
+		retval = POLLIN | POLLRDNORM;
+
+	return retval;
+}
+
+/* static */
+int rtc6226_vidioc_g_ctrl(struct file *file, void *priv,
+	struct v4l2_control *ctrl)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+
+	FMDBG("%s enter, ctrl->id: %x, value:%d\n", __func__,
+		ctrl->id, ctrl->value);
+
+	mutex_lock(&radio->lock);
+
+	switch (ctrl->id) {
+	case V4L2_CID_PRIVATE_CSR0_ENABLE:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_ENABLE val=%d\n", ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DISABLE:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_DISABLE val=%d\n", ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_VOLUME:
+	case V4L2_CID_AUDIO_VOLUME:
+		ctrl->value = radio->registers[MPXCFG] & MPXCFG_CSR0_VOLUME;
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DIS_MUTE:
+	case V4L2_CID_AUDIO_MUTE:
+		ctrl->value = ((radio->registers[MPXCFG] &
+			MPXCFG_CSR0_DIS_MUTE) == 0) ? 1 : 0;
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DIS_SMUTE:
+		ctrl->value = ((radio->registers[MPXCFG] &
+			MPXCFG_CSR0_DIS_SMUTE) == 0) ? 1 : 0;
+		break;
+	case V4L2_CID_PRIVATE_CSR0_BAND:
+		ctrl->value = radio->band;
+		break;
+	case V4L2_CID_PRIVATE_CSR0_SEEKRSSITH:
+		ctrl->value = radio->registers[SEEKCFG1] &
+			SEEKCFG1_CSR0_RSSI_LOW_TH;
+		break;
+	case V4L2_CID_PRIVATE_RSSI:
+		rtc6226_get_all_registers(radio);
+		ctrl->value = radio->registers[RSSI] & RSSI_RSSI;
+		FMDBG("Get V4L2_CONTROL V4L2_CID_PRIVATE_RSSI: RSSI = %d\n",
+			radio->registers[RSSI] & RSSI_RSSI);
+		break;
+	case V4L2_CID_PRIVATE_DEVICEID:
+		ctrl->value = radio->registers[DEVICEID] & DEVICE_ID;
+		FMDBG("Get V4L2_CID_PRIVATE_DEVICEID: DEVICEID=0x%4.4hx\n",
+			radio->registers[DEVICEID]);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_RDSGROUP_PROC:
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SIGNAL_TH:
+	/* intentional fallthrough */
+	case V4L2_CID_PRIVATE_RTC6226_RSSI_TH:
+		ctrl->value = radio->rssi_th;
+		break;
+	default:
+		FMDBG("%s in default id:%d\n", __func__, ctrl->id);
+		retval = -EINVAL;
+	}
+
+	mutex_unlock(&radio->lock);
+	return retval;
+}
+
+static int rtc6226_vidioc_dqbuf(struct file *file, void *priv,
+		struct v4l2_buffer *buffer)
+{
+
+	struct rtc6226_device *radio = video_get_drvdata(video_devdata(file));
+	enum rtc6226_buf_t buf_type = -1;
+	u8 buf_fifo[STD_BUF_SIZE] = {0};
+	struct kfifo *data_fifo = NULL;
+	u8 *buf = NULL;
+	int len = 0, retval = -1;
+
+	if ((radio == NULL) || (buffer == NULL)) {
+		FMDERR("%s radio/buffer is NULL\n", __func__);
+		return -ENXIO;
+	}
+
+	buf_type = buffer->index;
+	buf = (u8 *)buffer->m.userptr;
+	len = buffer->length;
+	FMDBG("%s: requesting buffer %d\n", __func__, buf_type);
+
+	if ((buf_type < RTC6226_FM_BUF_MAX) && (buf_type >= 0)) {
+		data_fifo = &radio->data_buf[buf_type];
+		if (buf_type == RTC6226_FM_BUF_EVENTS) {
+			if (wait_event_interruptible(radio->event_queue,
+						kfifo_len(data_fifo)) < 0) {
+				return -EINTR;
+			}
+		}
+	} else {
+		FMDERR("%s invalid buffer type\n", __func__);
+		return -EINVAL;
+	}
+	if (len <= STD_BUF_SIZE) {
+		buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0],
+				len, &radio->buf_lock[buf_type]);
+	} else {
+		FMDERR("%s kfifo_out_locked can not use len more than 128\n",
+			__func__);
+		return -EINVAL;
+	}
+	retval = copy_to_user(buf, &buf_fifo[0], buffer->bytesused);
+	if (retval > 0) {
+		FMDERR("%s Failed to copy %d bytes data\n", __func__, retval);
+		return -EAGAIN;
+	}
+
+	return retval;
+}
+
+static bool check_mode(struct rtc6226_device *radio)
+{
+	bool retval = true;
+
+	if (radio->mode == FM_OFF || radio->mode == FM_RECV)
+		retval = false;
+
+	return retval;
+}
+
+
+
+static int rtc6226_disable(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	/* disable RDS/STC interrupt */
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN;
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN;
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_STDIRQEN;
+	retval = rtc6226_set_register(radio, SYSCFG);
+	if (retval < 0) {
+		FMDERR("%s fail to disable RDS/SCT interrupt\n", __func__);
+		goto done;
+	}
+	retval = rtc6226_power_down(radio);
+	if (retval < 0) {
+		FMDERR("%s fail to turn off fmradio\n", __func__);
+		goto done;
+	}
+
+	if (radio->mode == FM_TURNING_OFF || radio->mode == FM_RECV) {
+		FMDBG("%s: posting RTC6226_EVT_RADIO_DISABLED event\n",
+				__func__);
+		rtc6226_q_event(radio, RTC6226_EVT_RADIO_DISABLED);
+		radio->mode = FM_OFF;
+	}
+	/* flush_workqueue(radio->wqueue); */
+
+done:
+	return retval;
+}
+
+static int rtc6226_enable(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	rtc6226_get_register(radio, POWERCFG);
+	retval = rtc6226_power_up(radio);
+	if (retval < 0)
+		goto done;
+
+	if ((radio->registers[SYSCFG] &  SYSCFG_CSR0_STDIRQEN) == 0) {
+		radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN;
+		radio->registers[SYSCFG] |= SYSCFG_CSR0_STDIRQEN;
+		retval = rtc6226_set_register(radio, SYSCFG);
+		if (retval < 0) {
+			FMDERR("%s set register fail\n", __func__);
+			goto done;
+		} else {
+			rtc6226_q_event(radio, RTC6226_EVT_RADIO_READY);
+			radio->mode = FM_RECV;
+		}
+	} else {
+		rtc6226_q_event(radio, RTC6226_EVT_RADIO_READY);
+		radio->mode = FM_RECV;
+	}
+done:
+	return retval;
+
+}
+
+bool rtc6226_is_valid_srch_mode(int srch_mode)
+{
+	if ((srch_mode >= RTC6226_MIN_SRCH_MODE) &&
+			(srch_mode <= RTC6226_MAX_SRCH_MODE))
+		return true;
+	else
+		return false;
+}
+
+/*
+ * rtc6226_vidioc_s_ctrl - set the value of a control
+ */
+int rtc6226_vidioc_s_ctrl(struct file *file, void *priv,
+	struct v4l2_control *ctrl)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+
+	FMDBG("%s enter, ctrl->id: %x, value:%d\n", __func__,
+		ctrl->id, ctrl->value);
+
+	switch (ctrl->id) {
+	case V4L2_CID_PRIVATE_RTC6226_STATE:
+		if (ctrl->value == FM_RECV) {
+			if (check_mode(radio)) {
+				FMDERR("%s:fm is not in proper state\n",
+						__func__);
+				retval = -EINVAL;
+				goto end;
+			}
+			radio->mode = FM_RECV_TURNING_ON;
+			retval = rtc6226_enable(radio);
+			if (retval < 0) {
+				FMDERR(
+				"%s Error while enabling RECV FM %d\n",
+					__func__, retval);
+				radio->mode = FM_OFF;
+				goto end;
+			}
+		} else if (ctrl->value == FM_OFF) {
+			radio->mode = FM_TURNING_OFF;
+			retval = rtc6226_disable(radio);
+			if (retval < 0) {
+				FMDERR("Err on disable recv FM %d\n", retval);
+				radio->mode = FM_RECV;
+				goto end;
+			}
+		}
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SET_AUDIO_PATH:
+	case V4L2_CID_PRIVATE_RTC6226_SRCH_ALGORITHM:
+	case V4L2_CID_PRIVATE_RTC6226_REGION:
+		retval = 0;
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_EMPHASIS:
+		if (ctrl->value == 1)
+			radio->registers[MPXCFG] |= MPXCFG_CSR0_DEEM;
+		else
+			radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DEEM;
+		retval = rtc6226_set_register(radio, MPXCFG);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_RDS_STD:
+		/* enable RDS / STC interrupt */
+		radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN;
+		radio->registers[SYSCFG] |= SYSCFG_CSR0_STDIRQEN;
+		/*radio->registers[SYSCFG] |= SYSCFG_CSR0_RDS_EN;*/
+		retval = rtc6226_set_register(radio, SYSCFG);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SRCHON:
+		rtc6226_search(radio, (bool)ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_LP_MODE:
+		if (ctrl->value) {
+			/* disable RDS interrupts */
+			radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN;
+			retval = rtc6226_set_register(radio, SYSCFG);
+		} else {
+			/* enable RDS interrupts */
+			radio->registers[SYSCFG] |= SYSCFG_CSR0_RDSIRQEN;
+			retval = rtc6226_set_register(radio, SYSCFG);
+		}
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_ANTENNA:
+	case V4L2_CID_PRIVATE_RTC6226_AF_JUMP:
+	case V4L2_CID_PRIVATE_RTC6226_SRCH_CNT:
+	case V4L2_CID_PRIVATE_RTC6226_RXREPEATCOUNT:
+	case V4L2_CID_PRIVATE_RTC6226_SINR_THRESHOLD:
+		retval = 0;
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SIGNAL_TH:
+		retval = rtc6226_set_rssi_threshold(radio, ctrl->value);
+		if (retval < 0)
+			FMDERR("%s fail to set rssi threshold\n", __func__);
+		rtc6226_get_register(radio, SEEKCFG1);
+		FMDBG("FMRICHWAVE RSSI_TH: Dec = %d , Hexa = %x\n",
+			radio->registers[SEEKCFG1] & 0xFF,
+			radio->registers[SEEKCFG1] & 0xFF);
+		break;
+	/* case V4L2_CID_PRIVATE_RTC6226_OFS_THRESHOLD: */
+	case V4L2_CID_PRIVATE_RTC6226_SPUR_FREQ_RMSSI:
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_RDSD_BUF:
+	case V4L2_CID_PRIVATE_RTC6226_RDSGROUP_MASK:
+	case V4L2_CID_PRIVATE_RTC6226_RDSGROUP_PROC:
+		if ((radio->registers[SYSCFG] & SYSCFG_CSR0_RDS_EN) == 0)
+			rtc6226_rds_on(radio);
+		retval = 0;
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SRCHMODE:
+		if (rtc6226_is_valid_srch_mode(ctrl->value)) {
+			radio->g_search_mode = ctrl->value;
+		} else {
+			FMDERR("%s:srch mode is not valid\n", __func__);
+			retval = -EINVAL;
+			goto end;
+		}
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_PSALL:
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SCANDWELL:
+		if ((ctrl->value >= MIN_DWELL_TIME) &&
+				(ctrl->value <= MAX_DWELL_TIME)) {
+			radio->dwell_time_sec = ctrl->value;
+		} else {
+			FMDERR(
+			"%s:scandwell period is not valid\n", __func__);
+			retval = -EINVAL;
+		}
+		break;
+	case V4L2_CID_PRIVATE_CSR0_ENABLE:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_ENABLE val=%d\n",
+			ctrl->value);
+		retval = rtc6226_power_up(radio);
+		/* must keep below line */
+		ctrl->value = 0;
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DISABLE:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_DISABLE val=%d\n",
+			ctrl->value);
+		retval = rtc6226_power_down(radio);
+		/* must keep below line */
+		ctrl->value = 0;
+		break;
+	case V4L2_CID_PRIVATE_DEVICEID:
+		FMDBG("V4L2_CID_PRIVATE_DEVICEID val=%d\n", ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_VOLUME:
+	case V4L2_CID_AUDIO_VOLUME:
+		FMDBG("MPXCFG=0x%4.4hx POWERCFG=0x%4.4hx\n",
+		radio->registers[MPXCFG], radio->registers[POWERCFG]);
+		radio->registers[MPXCFG] &= ~MPXCFG_CSR0_VOLUME;
+		radio->registers[MPXCFG] |=
+			(ctrl->value > 15) ? 8 : ctrl->value;
+		FMDBG("MPXCFG=0x%4.4hx POWERCFG=0x%4.4hx\n",
+		radio->registers[MPXCFG], radio->registers[POWERCFG]);
+		retval = rtc6226_set_register(radio, MPXCFG);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DIS_MUTE:
+	case V4L2_CID_AUDIO_MUTE:
+		if (ctrl->value == 1)
+			radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DIS_MUTE;
+		else
+			radio->registers[MPXCFG] |= MPXCFG_CSR0_DIS_MUTE;
+		retval = rtc6226_set_register(radio, MPXCFG);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SOFT_MUTE:
+		FMDBG("V4L2_CID_PRIVATE_RTC6226_SOFT_MUTE\n");
+		if (ctrl->value == 1)
+			radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DIS_SMUTE;
+		else
+			radio->registers[MPXCFG] |= MPXCFG_CSR0_DIS_SMUTE;
+		retval = rtc6226_set_register(radio, MPXCFG);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DEEM:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_DEEM\n");
+		if (ctrl->value == 1)
+			radio->registers[MPXCFG] |= MPXCFG_CSR0_DEEM;
+		else
+			radio->registers[MPXCFG] &= ~MPXCFG_CSR0_DEEM;
+		retval = rtc6226_set_register(radio, MPXCFG);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_BLNDADJUST:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_BLNDADJUST val=%d\n",
+				ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_BAND:
+		FMDBG(
+		"V4L2_CID_PRIVATE_CSR0_BAND : FREQ_TOP=%d FREQ_BOT=%d %d\n",
+			radio->registers[RADIOSEEKCFG1],
+			radio->registers[RADIOSEEKCFG2], ctrl->value);
+		switch (ctrl->value) {
+		case FMBAND_87_108_MHZ:
+			radio->registers[RADIOSEEKCFG1] = 10800;
+			radio->registers[RADIOSEEKCFG2] = 8750;
+			break;
+		case FMBAND_76_108_MHZ:
+			radio->registers[RADIOSEEKCFG1] = 10800;
+			radio->registers[RADIOSEEKCFG2] = 7600;
+			break;
+		case FMBAND_76_91_MHZ:
+			radio->registers[RADIOSEEKCFG1] = 9100;
+			radio->registers[RADIOSEEKCFG2] = 7600;
+			break;
+		case FMBAND_64_76_MHZ:
+			radio->registers[RADIOSEEKCFG1] = 7600;
+			radio->registers[RADIOSEEKCFG2] = 6400;
+			break;
+		default:
+			retval = -EINVAL;
+			break;
+		}
+		FMDBG(
+		"V4L2_CID_PRIVATE_CSR0_BAND : FREQ_TOP=%d FREQ_BOT=%d %d\n",
+			radio->registers[RADIOSEEKCFG1],
+			radio->registers[RADIOSEEKCFG2], ctrl->value);
+		radio->band = ctrl->value;
+		retval = rtc6226_set_register(radio, RADIOSEEKCFG1);
+		retval = rtc6226_set_register(radio, RADIOSEEKCFG2);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_SPACING:
+	case V4L2_CID_PRIVATE_CSR0_CHSPACE:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_CHSPACE : FM_SPACE=%d %d\n",
+			radio->registers[RADIOCFG], ctrl->value);
+		radio->space = ctrl->value;
+		radio->registers[RADIOCFG] &= ~CHANNEL_CSR0_CHSPACE;
+
+		switch (ctrl->value) {
+		case FMSPACE_200_KHZ:
+			radio->registers[RADIOCFG] |= 0x1400;
+			break;
+		case FMSPACE_100_KHZ:
+			radio->registers[RADIOCFG] |= 0x0A00;
+			break;
+		case FMSPACE_50_KHZ:
+			radio->registers[RADIOCFG] |= 0x0500;
+			break;
+		default:
+			retval = -EINVAL;
+			break;
+		}
+		radio->space = ctrl->value;
+		FMDBG("V4L2_CID_PRIVATE_CSR0_CHSPACE : FM_SPACE=%d %d\n",
+			radio->registers[RADIOCFG], ctrl->value);
+		retval = rtc6226_set_register(radio, RADIOCFG);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_DIS_AGC:
+		FMDBG("V4L2_CID_PRIVATE_CSR0_DIS_AGC val=%d\n",
+			ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_RTC6226_RDSON:
+		FMDBG(
+		"V4L2_CSR0_RDS_EN:CHANNEL=0x%4.4hx SYSCFG=0x%4.4hx\n",
+			radio->registers[CHANNEL],
+			radio->registers[SYSCFG]);
+		rtc6226_reset_rds_data(radio);
+		radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN;
+		radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDSIRQEN;
+		radio->registers[SYSCFG] |= (ctrl->value << 15);
+		radio->registers[SYSCFG] |= (ctrl->value << 12);
+		FMDBG
+		("V4L2_CSR0_RDS_EN : CHANNEL=0x%4.4hx SYSCFG=0x%4.4hx\n",
+			radio->registers[CHANNEL],
+			radio->registers[SYSCFG]);
+		retval = rtc6226_set_register(radio, SYSCFG);
+		break;
+	case V4L2_CID_PRIVATE_SEEK_CANCEL:
+		rtc6226_search(radio, (bool)ctrl->value);
+		break;
+	case V4L2_CID_PRIVATE_CSR0_SEEKRSSITH:
+		radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_RSSI_LOW_TH;
+		radio->registers[SEEKCFG1] |= ctrl->value;
+		retval = rtc6226_set_register(radio, SEEKCFG1);
+		break;
+	default:
+		FMDBG("%s id: %x in default\n", __func__, ctrl->id);
+		retval = -EINVAL;
+		break;
+	}
+
+end:
+	FMDBG("%s exit id: %x , ret: %d\n", __func__, ctrl->id, retval);
+
+	return retval;
+}
+
+/*
+ * rtc6226_vidioc_g_audio - get audio attributes
+ */
+static int rtc6226_vidioc_g_audio(struct file *file, void *priv,
+	struct v4l2_audio *audio)
+{
+	/* driver constants */
+	audio->index = 0;
+	strlcpy(audio->name, "Radio", sizeof(audio->name));
+	audio->capability = V4L2_AUDCAP_STEREO;
+	audio->mode = 0;
+
+	return 0;
+}
+
+
+/*
+ * rtc6226_vidioc_g_tuner - get tuner attributes
+ */
+static int rtc6226_vidioc_g_tuner(struct file *file, void *priv,
+	struct v4l2_tuner *tuner)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+
+	FMDBG("%s enter\n", __func__);
+
+	if (tuner->index != 0) {
+		retval = -EINVAL;
+		goto done;
+	}
+
+	retval = rtc6226_get_register(radio, RSSI);
+	if (retval < 0)
+		goto done;
+
+	/* driver constants */
+	strlcpy(tuner->name, "FM", sizeof(tuner->name));
+	tuner->type = V4L2_TUNER_RADIO;
+	tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
+		V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO;
+
+	tuner->rangehigh = (radio->registers[RADIOSEEKCFG1] &
+		CHANNEL_CSR0_FREQ_TOP) * TUNE_STEP_SIZE * TUNE_PARAM;
+	tuner->rangelow = (radio->registers[RADIOSEEKCFG2] &
+		CHANNEL_CSR0_FREQ_BOT) * TUNE_STEP_SIZE * TUNE_PARAM;
+
+	FMDBG("%s low:%d high:%d\n", __func__,
+		tuner->rangelow, tuner->rangehigh);
+	/* stereo indicator == stereo (instead of mono) */
+	if ((radio->registers[STATUS] & STATUS_SI) == 0)
+		tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+	else
+		tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+	/* If there is a reliable method of detecting an RDS channel,
+	 * then this code should check for that before setting this
+	 * RDS subchannel.
+	 */
+	tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
+
+	/* mono/stereo selector */
+	if ((radio->registers[MPXCFG] & MPXCFG_CSR0_MONO) == 0) {
+		tuner->audmode = V4L2_TUNER_MODE_STEREO;
+		rtc6226_q_event(radio, RTC6226_EVT_STEREO);
+	} else {
+		tuner->audmode = V4L2_TUNER_MODE_MONO;
+		rtc6226_q_event(radio, RTC6226_EVT_MONO);
+	}
+
+	/* min is worst, max is best; rssi: 0..0xff */
+	tuner->signal = (radio->registers[RSSI] & RSSI_RSSI);
+
+done:
+	FMDBG("%s exit %d\n", __func__, retval);
+
+	return retval;
+}
+
+
+/*
+ * rtc6226_vidioc_s_tuner - set tuner attributes
+ */
+static int rtc6226_vidioc_s_tuner(struct file *file, void *priv,
+	const struct v4l2_tuner *tuner)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+	u16 bottom_freq;
+	u16 top_freq;
+
+	FMDBG("%s entry\n", __func__);
+
+	if (tuner->index != 0) {
+		FMDBG("%s index :%d\n", __func__, tuner->index);
+		goto done;
+	}
+
+	/* mono/stereo selector */
+	switch (tuner->audmode) {
+	case V4L2_TUNER_MODE_MONO:
+		radio->registers[MPXCFG] |= MPXCFG_CSR0_MONO;  /* force mono */
+		break;
+	case V4L2_TUNER_MODE_STEREO:
+		radio->registers[MPXCFG] &= ~MPXCFG_CSR0_MONO; /* try stereo */
+		break;
+	default:
+		FMDBG("%s audmode is not set\n", __func__);
+	}
+
+	retval = rtc6226_set_register(radio, MPXCFG);
+
+	/*  unit is 10kHz */
+	top_freq = (u16)((tuner->rangehigh / TUNE_PARAM) / TUNE_STEP_SIZE);
+	bottom_freq = (u16)((tuner->rangelow / TUNE_PARAM) / TUNE_STEP_SIZE);
+
+	FMDBG("%s low:%d high:%d\n", __func__,
+		bottom_freq, top_freq);
+
+	radio->registers[RADIOSEEKCFG1] = top_freq;
+	radio->registers[RADIOSEEKCFG2] = bottom_freq;
+
+	retval = rtc6226_set_register(radio, RADIOSEEKCFG1);
+	if (retval < 0)
+		FMDERR("In %s, error %d setting higher limit freq\n",
+			__func__, retval);
+	else
+		radio->recv_conf.band_high_limit = top_freq;
+
+	retval = rtc6226_set_register(radio, RADIOSEEKCFG2);
+	if (retval < 0)
+		FMDERR("In %s, error %d setting lower limit freq\n",
+			__func__, retval);
+	else
+		radio->recv_conf.band_low_limit = bottom_freq;
+done:
+	FMDBG("%s exit %d\n", __func__, retval);
+	return retval;
+}
+
+
+/*
+ * rtc6226_vidioc_g_frequency - get tuner or modulator radio frequency
+ */
+static int rtc6226_vidioc_g_frequency(struct file *file, void *priv,
+	struct v4l2_frequency *freq)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+	unsigned int frq;
+
+	FMDBG("%s enter freq %d\n", __func__, freq->frequency);
+
+	freq->type = V4L2_TUNER_RADIO;
+	retval = rtc6226_get_freq(radio, &frq);
+	freq->frequency = frq * TUNE_PARAM;
+	radio->tuned_freq_khz = frq * TUNE_STEP_SIZE;
+	FMDBG(" %s *freq=%d, ret %d\n", __func__, freq->frequency, retval);
+
+	if (retval < 0)
+		FMDERR(" %s get frequency failed with %d\n", __func__, retval);
+
+	return retval;
+}
+
+
+/*
+ * rtc6226_vidioc_s_frequency - set tuner or modulator radio frequency
+ */
+static int rtc6226_vidioc_s_frequency(struct file *file, void *priv,
+	const struct v4l2_frequency *freq)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+	u32 f = 0;
+
+	FMDBG("%s enter freq = %d\n", __func__, freq->frequency);
+	if (unlikely(freq == NULL)) {
+		FMDERR("%s:freq is null\n", __func__);
+		return -EINVAL;
+	}
+	if (freq->type != V4L2_TUNER_RADIO)
+		return -EINVAL;
+	f = (freq->frequency)/TUNE_PARAM;
+
+	radio->seek_tune_status = TUNE_PENDING;
+	retval = rtc6226_set_freq(radio, f);
+	if (retval < 0)
+		FMDERR("%s set frequency failed with %d\n", __func__, retval);
+	else
+		radio->tuned_freq_khz = f;
+
+	return retval;
+}
+
+
+/*
+ * rtc6226_vidioc_s_hw_freq_seek - set hardware frequency seek
+ */
+static int rtc6226_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+	const struct v4l2_hw_freq_seek *seek)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+
+	FMDBG("%s enter\n", __func__);
+
+	if (file->f_flags & O_NONBLOCK)
+		return -EWOULDBLOCK;
+
+	radio->is_search_cancelled = false;
+
+	/* Disable the rds before seek */
+	radio->registers[SYSCFG] &= ~SYSCFG_CSR0_RDS_EN;
+	retval = rtc6226_set_register(radio, SYSCFG);
+	if (retval < 0) {
+		FMDERR("%s fail to disable RDS\n", __func__);
+		return retval;
+	}
+
+	if (radio->g_search_mode == SEEK) {
+		/* seek */
+		FMDBG("%s starting seek\n", __func__);
+		radio->seek_tune_status = SEEK_PENDING;
+		retval = rtc6226_set_seek(radio, seek->seek_upward,
+				WRAP_ENABLE);
+	} else if ((radio->g_search_mode == SCAN) ||
+			(radio->g_search_mode == SCAN_FOR_STRONG)) {
+		/* scan */
+		if (radio->g_search_mode == SCAN_FOR_STRONG) {
+			FMDBG("%s starting search list\n", __func__);
+			memset(&radio->srch_list, 0,
+					sizeof(struct rtc6226_srch_list_compl));
+		} else {
+			FMDBG("%s starting scan\n", __func__);
+		}
+		rtc6226_search(radio, START_SCAN);
+	} else {
+		retval = -EINVAL;
+		FMDERR("In %s, invalid search mode %d\n",
+				__func__, radio->g_search_mode);
+	}
+	FMDBG("%s exit %d\n", __func__, retval);
+	return retval;
+}
+
+static const struct v4l2_file_operations rtc6226_fops = {
+	.owner			= THIS_MODULE,
+	.unlocked_ioctl	= video_ioctl2,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl32	= v4l2_compat_ioctl32,
+#endif
+	.read			= rtc6226_fops_read,
+	.poll			= rtc6226_fops_poll,
+	.open			= rtc6226_fops_open,
+	.release		= rtc6226_fops_release,
+};
+
+/*
+ * rtc6226_ioctl_ops - video device ioctl operations
+ */
+/* static */
+const struct v4l2_ioctl_ops rtc6226_ioctl_ops = {
+	.vidioc_querycap            =   rtc6226_vidioc_querycap,
+	.vidioc_g_audio             =   rtc6226_vidioc_g_audio,
+	.vidioc_g_tuner             =   rtc6226_vidioc_g_tuner,
+	.vidioc_s_tuner             =   rtc6226_vidioc_s_tuner,
+	.vidioc_g_ctrl              =   rtc6226_vidioc_g_ctrl,
+	.vidioc_s_ctrl              =   rtc6226_vidioc_s_ctrl,
+	.vidioc_g_frequency         =   rtc6226_vidioc_g_frequency,
+	.vidioc_s_frequency         =   rtc6226_vidioc_s_frequency,
+	.vidioc_s_hw_freq_seek      =   rtc6226_vidioc_s_hw_freq_seek,
+	.vidioc_dqbuf               =   rtc6226_vidioc_dqbuf,
+};
+
+/*
+ * rtc6226_viddev_template - video device interface
+ */
+struct video_device rtc6226_viddev_template = {
+	.fops           =   &rtc6226_fops,
+	.name           =   DRIVER_NAME,
+	.release        =   video_device_release_empty,
+	.ioctl_ops      =   &rtc6226_ioctl_ops,
+};
+
+/**************************************************************************
+ * Module Interface
+ **************************************************************************/
+
+/*
+ * rtc6226_i2c_init - module init
+ */
+static __init int rtc6226_init(void)
+{
+	FMDBG(DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+	return rtc6226_i2c_init();
+}
+
+/*
+ * rtc6226_i2c_exit - module exit
+ */
+static void __exit rtc6226_exit(void)
+{
+	i2c_del_driver(&rtc6226_i2c_driver);
+}
+
+module_init(rtc6226_init);
+module_exit(rtc6226_exit);

+ 1016 - 0
qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-i2c.c

@@ -0,0 +1,1016 @@
+/* drivers/media/radio/rtc6226/radio-rtc6226-i2c.c
+ *
+ *  Driver for Richwave RTC6226 FM Tuner
+ *
+ *  Copyright (c) 2009 Samsung Electronics Co.Ltd
+ *  Author: Joonyoung Shim <[email protected]>
+ *  Copyright (c) 2009 Tobias Lorenz <[email protected]>
+ *  Copyright (c) 2012 Hans de Goede <[email protected]>
+ *  Copyright (c) 2018 LG Electronics, Inc.
+ *  Copyright (c) 2018 Richwave Technology Co.Ltd
+ *  Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* kernel includes */
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/of_gpio.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/consumer.h>
+#include "radio-rtc6226.h"
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+static const struct of_device_id rtc6226_i2c_dt_ids[] = {
+	{.compatible = "rtc6226"},
+	{}
+};
+
+/* I2C Device ID List */
+static const struct i2c_device_id rtc6226_i2c_id[] = {
+    /* Generic Entry */
+	{ "rtc6226", 0 },
+	/* Terminating entry */
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, rtc6226_i2c_id);
+
+
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Radio Nr */
+static int radio_nr = -1;
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+/* RDS buffer blocks */
+static unsigned int rds_buf = 100;
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
+
+enum rtc6226_ctrl_id {
+	RTC6226_ID_CSR0_ENABLE,
+	RTC6226_ID_CSR0_DISABLE,
+	RTC6226_ID_DEVICEID,
+	RTC6226_ID_CSR0_DIS_SMUTE,
+	RTC6226_ID_CSR0_DIS_MUTE,
+	RTC6226_ID_CSR0_DEEM,
+	RTC6226_ID_CSR0_BLNDADJUST,
+	RTC6226_ID_CSR0_VOLUME,
+	RTC6226_ID_CSR0_BAND,
+	RTC6226_ID_CSR0_CHSPACE,
+	RTC6226_ID_CSR0_DIS_AGC,
+	RTC6226_ID_CSR0_RDS_EN,
+	RTC6226_ID_SEEK_CANCEL,
+	RTC6226_ID_CSR0_SEEKRSSITH,
+	RTC6226_ID_CSR0_OFSTH,
+	RTC6226_ID_CSR0_QLTTH,
+	RTC6226_ID_RSSI,
+	RTC6226_ID_RDS_RDY,
+	RTC6226_ID_STD,
+	RTC6226_ID_SF,
+	RTC6226_ID_RDS_SYNC,
+	RTC6226_ID_SI,
+};
+
+
+/**************************************************************************
+ * I2C Definitions
+ **************************************************************************/
+/* Write starts with the upper byte of register 0x02 */
+#define WRITE_REG_NUM       3
+#define WRITE_INDEX(i)      ((i + 0x02)%16)
+
+/* Read starts with the upper byte of register 0x0a */
+#define READ_REG_NUM        2
+#define READ_INDEX(i)       ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
+
+/*static*/
+struct tasklet_struct my_tasklet;
+/*
+ * rtc6226_get_register - read register
+ */
+int rtc6226_get_register(struct rtc6226_device *radio, int regnr)
+{
+	u8 reg[1];
+	u8 buf[READ_REG_NUM];
+	struct i2c_msg msgs[2] = {
+		{ radio->client->addr, 0, 1, reg },
+		{ radio->client->addr, I2C_M_RD, sizeof(buf), buf },
+	};
+
+	reg[0] = (u8)(regnr);
+	if (i2c_transfer(radio->client->adapter, msgs, 2) != 2)
+		return -EIO;
+
+	radio->registers[regnr] =
+		(u16)(((buf[0] << 8) & 0xff00) | buf[1]);
+
+	return 0;
+}
+
+/*
+ * rtc6226_set_register - write register
+ */
+int rtc6226_set_register(struct rtc6226_device *radio, int regnr)
+{
+	u8 buf[WRITE_REG_NUM];
+	struct i2c_msg msgs[1] = {
+		{ radio->client->addr, 0, sizeof(u8) * WRITE_REG_NUM,
+			(void *)buf },
+	};
+
+	buf[0] = (u8)(regnr);
+	buf[1] = (u8)((radio->registers[(u8)(regnr) & 0xFF] >> 8) & 0xFF);
+	buf[2] = (u8)(radio->registers[(u8)(regnr) & 0xFF] & 0xFF);
+
+	if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * rtc6226_set_register - write register
+ */
+int rtc6226_set_serial_registers(struct rtc6226_device *radio,
+	u16 *data, int regnr)
+{
+	u8 buf[WRITE_REG_NUM];
+	struct i2c_msg msgs[1] = {
+		{ radio->client->addr, 0, sizeof(u8) * WRITE_REG_NUM,
+			(void *)buf },
+	};
+
+	buf[0] = (u8)(regnr);
+	buf[1] = (u8)((data[0] >> 8) & 0xFF);
+	buf[2] = (u8)(data[0] & 0xFF);
+
+	if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
+		return -EIO;
+
+	return 0;
+}
+
+/**************************************************************************
+ * General Driver Functions - ENTIRE REGISTERS
+ **************************************************************************/
+/*
+ * rtc6226_get_all_registers - read entire registers
+ */
+/* changed from static */
+int rtc6226_get_all_registers(struct rtc6226_device *radio)
+{
+	int i;
+	int err;
+	u8 reg[1] = {0x00};
+	u8 buf[RADIO_REGISTER_NUM];
+	struct i2c_msg msgs1[1] = {
+		{ radio->client->addr, 0, 1, reg},
+	};
+	struct i2c_msg msgs[1] = {
+		{ radio->client->addr, I2C_M_RD, sizeof(buf), buf },
+	};
+
+	if (i2c_transfer(radio->client->adapter, msgs1, 1) != 1)
+		return -EIO;
+
+	err = i2c_transfer(radio->client->adapter, msgs, 1);
+
+	if (err < 0)
+		return -EIO;
+
+	for (i = 0; i < 16; i++)
+		radio->registers[i] =
+			(u16)(((buf[i*2] << 8) & 0xff00) | buf[i*2+1]);
+
+	return 0;
+}
+
+/*
+ * rtc6226_vidioc_querycap - query device capabilities
+ */
+int rtc6226_vidioc_querycap(struct file *file, void *priv,
+	struct v4l2_capability *capability)
+{
+	FMDBG("%s enter\n", __func__);
+	strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
+	strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+	capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE |
+		V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
+	capability->capabilities = capability->device_caps |
+		V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+/*
+ * rtc6226_i2c_interrupt - interrupt handler
+ */
+static void rtc6226_i2c_interrupt_handler(struct rtc6226_device *radio)
+{
+	unsigned char regnr;
+	int retval = 0;
+	unsigned short current_chan;
+
+	FMDBG("%s enter\n", __func__);
+
+	/* check Seek/Tune Complete */
+	retval = rtc6226_get_register(radio, STATUS);
+	if (retval < 0) {
+		FMDERR("%s read fail to STATUS\n", __func__);
+		goto end;
+	}
+
+	if (radio->registers[STATUS] & STATUS_STD) {
+		FMDBG("%s : STATUS=0x%4.4hx\n", __func__,
+				radio->registers[STATUS]);
+
+		retval = rtc6226_get_register(radio, RSSI);
+		if (retval < 0) {
+			FMDERR("%s read fail to RSSI\n", __func__);
+			goto end;
+		}
+		FMDBG("%s : RSSI=0x%4.4hx\n", __func__, radio->registers[RSSI]);
+			/* stop seeking : clear STD*/
+		radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK;
+		retval = rtc6226_set_register(radio, SEEKCFG1);
+		/*clear the status bit to allow another tune or seek*/
+		current_chan = radio->registers[CHANNEL] & CHANNEL_CSR0_CH;
+		radio->registers[CHANNEL] &= ~CHANNEL_CSR0_TUNE;
+		retval = rtc6226_set_register(radio, CHANNEL);
+		if (retval < 0)
+			radio->registers[CHANNEL] = current_chan;
+		rtc6226_reset_rds_data(radio);
+		FMDBG("%s clear Seek/Tune bit\n", __func__);
+		if (radio->seek_tune_status == SEEK_PENDING) {
+			/* Enable the RDS as it was disabled before seek */
+			rtc6226_rds_on(radio);
+			FMDBG("posting RTC6226_EVT_SEEK_COMPLETE event\n");
+			rtc6226_q_event(radio, RTC6226_EVT_SEEK_COMPLETE);
+			/* post tune comp evt since seek results in a tune.*/
+			FMDBG("posting RICHWAVE_EVT_TUNE_SUCC event\n");
+			rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC);
+			radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
+		} else if (radio->seek_tune_status == TUNE_PENDING) {
+			FMDBG("posting RICHWAVE_EVT_TUNE_SUCC event\n");
+			rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC);
+			radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
+		} else if (radio->seek_tune_status == SCAN_PENDING) {
+			/* when scan is pending and STC int is set, signal
+			 * so that scan can proceed
+			 */
+			FMDBG("In %s, signalling scan thread\n", __func__);
+			complete(&radio->completion);
+		}
+		FMDBG("%s Seek/Tune done\n", __func__);
+	} else {
+		/* Check RDS data after tune/seek interrupt finished
+		 * Update RDS registers
+		 */
+		for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) {
+			retval = rtc6226_get_register(radio, STATUS + regnr);
+			if (retval < 0)
+				goto end;
+		}
+		/* get rds blocks */
+		if ((radio->registers[STATUS] & STATUS_RDS_RDY) == 0) {
+			/* No RDS group ready, better luck next time */
+			FMDERR("%s No RDS group ready\n", __func__);
+			goto end;
+		} else {
+			/* avoid RDS interrupt lock disable_irq*/
+			if ((radio->registers[SYSCFG] &
+						SYSCFG_CSR0_RDS_EN) != 0) {
+				schedule_work(&radio->rds_worker);
+			}
+		}
+	}
+end:
+	FMDBG("%s exit :%d\n", __func__, retval);
+}
+
+static irqreturn_t rtc6226_isr(int irq, void *dev_id)
+{
+	struct rtc6226_device *radio = dev_id;
+	/*
+	 * The call to queue_delayed_work ensures that a minimum delay
+	 * (in jiffies) passes before the work is actually executed. The return
+	 * value from the function is nonzero if the work_struct was actually
+	 * added to queue (otherwise, it may have already been there and will
+	 * not be added a second time).
+	 */
+
+	queue_delayed_work(radio->wqueue, &radio->work,
+				msecs_to_jiffies(10));
+
+	return IRQ_HANDLED;
+}
+
+static void rtc6226_handler(struct work_struct *work)
+{
+	struct rtc6226_device *radio;
+
+	radio = container_of(work, struct rtc6226_device, work.work);
+
+	rtc6226_i2c_interrupt_handler(radio);
+}
+
+void rtc6226_disable_irq(struct rtc6226_device *radio)
+{
+	int irq;
+
+	irq = radio->irq;
+	disable_irq_wake(irq);
+	free_irq(irq, radio);
+
+	cancel_delayed_work_sync(&radio->work);
+	flush_workqueue(radio->wqueue);
+
+	cancel_work_sync(&radio->rds_worker);
+	flush_workqueue(radio->wqueue_rds);
+	cancel_delayed_work_sync(&radio->work_scan);
+	flush_workqueue(radio->wqueue_scan);
+}
+
+int rtc6226_enable_irq(struct rtc6226_device *radio)
+{
+	int retval;
+	int irq;
+
+	retval = gpio_direction_input(radio->int_gpio);
+	if (retval) {
+		FMDERR("%s unable to set the gpio %d direction(%d)\n",
+				__func__, radio->int_gpio, retval);
+		return retval;
+	}
+	radio->irq = gpio_to_irq(radio->int_gpio);
+	irq = radio->irq;
+
+	if (radio->irq < 0) {
+		FMDERR("%s: gpio_to_irq returned %d\n", __func__, radio->irq);
+		goto open_err_req_irq;
+	}
+
+	FMDBG("%s irq number is = %d\n", __func__, radio->irq);
+
+	retval = request_any_context_irq(radio->irq, rtc6226_isr,
+			IRQF_TRIGGER_FALLING, DRIVER_NAME, radio);
+
+	if (retval < 0) {
+		FMDERR("%s Couldn't acquire FM gpio %d, retval:%d\n",
+			 __func__, radio->irq, retval);
+		goto open_err_req_irq;
+	} else {
+		FMDBG("%s FM GPIO %d registered\n", __func__, radio->irq);
+	}
+	retval = enable_irq_wake(irq);
+	if (retval < 0) {
+		FMDERR("Could not wake FM interrupt\n");
+		free_irq(irq, radio);
+	}
+	return retval;
+
+open_err_req_irq:
+	rtc6226_disable_irq(radio);
+
+	return retval;
+}
+
+static int rtc6226_fm_vio_reg_cfg(struct rtc6226_device *radio, bool on)
+{
+	int rc = 0;
+	struct fm_power_vreg_data *vreg;
+
+	vreg = radio->vioreg;
+	if (!vreg) {
+		FMDERR("In %s, vio reg is NULL\n", __func__);
+		return rc;
+	}
+	if (on) {
+		FMDBG("vreg is : %s\n", vreg->name);
+		rc = regulator_set_voltage(vreg->reg,
+					vreg->low_vol_level,
+					vreg->high_vol_level);
+		if (rc < 0) {
+			FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
+			return rc;
+		}
+		rc = regulator_enable(vreg->reg);
+		if (rc < 0) {
+			FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc);
+				regulator_set_voltage(vreg->reg,
+						0,
+						vreg->high_vol_level);
+			return rc;
+		}
+		vreg->is_enabled = true;
+
+	} else {
+		rc = regulator_disable(vreg->reg);
+		if (rc < 0) {
+			FMDERR("reg disable(%s) fail rc=%d\n", vreg->name, rc);
+			return rc;
+		}
+		vreg->is_enabled = false;
+
+		/* Set the min voltage to 0 */
+		rc = regulator_set_voltage(vreg->reg,
+					0,
+					vreg->high_vol_level);
+		if (rc < 0) {
+			FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
+			return rc;
+		}
+	}
+	return rc;
+}
+
+static int rtc6226_fm_vdd_reg_cfg(struct rtc6226_device *radio, bool on)
+{
+	int rc = 0;
+	struct fm_power_vreg_data *vreg;
+
+	vreg = radio->vddreg;
+	if (!vreg) {
+		FMDERR("In %s, vdd reg is NULL\n", __func__);
+		return rc;
+	}
+
+	if (on) {
+		FMDBG("vreg is : %s\n", vreg->name);
+		rc = regulator_set_voltage(vreg->reg,
+					vreg->low_vol_level,
+					vreg->high_vol_level);
+		if (rc < 0) {
+			FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
+			return rc;
+		}
+		if (vreg->vdd_load) {
+			rc = regulator_set_load(vreg->reg, vreg->vdd_load);
+			if (rc < 0) {
+				FMDERR("%s Unable to set the load %d ,err=%d\n",
+				__func__, vreg->vdd_load, rc);
+				return rc;
+			}
+		}
+
+		rc = regulator_enable(vreg->reg);
+		if (rc < 0) {
+			FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc);
+			regulator_set_voltage(vreg->reg,
+					0,
+					vreg->high_vol_level);
+			return rc;
+		}
+		vreg->is_enabled = true;
+	} else {
+		rc = regulator_disable(vreg->reg);
+		if (rc < 0) {
+			FMDERR("reg disable(%s) fail. rc=%d\n", vreg->name, rc);
+			return rc;
+		}
+		vreg->is_enabled = false;
+
+			/* Set the min voltage to 0 */
+		rc = regulator_set_voltage(vreg->reg,
+					0,
+					vreg->high_vol_level);
+		if (rc < 0) {
+			FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
+			return rc;
+		}
+		if (vreg->vdd_load) {
+			rc = regulator_set_load(vreg->reg, 0);
+			if (rc < 0) {
+				FMDERR("%s Unable to set the load 0 ,err=%d\n",
+					__func__, rc);
+				return rc;
+			}
+		}
+	}
+	return rc;
+}
+
+static int rtc6226_fm_power_cfg(struct rtc6226_device *radio, bool powerflag)
+{
+	int rc = 0;
+
+	if (powerflag) {
+		/* Turn ON sequence */
+		rc = rtc6226_fm_vdd_reg_cfg(radio, powerflag);
+		if (rc < 0) {
+			FMDERR("In %s, vdd reg cfg failed %x\n", __func__, rc);
+			return rc;
+		}
+		rc = rtc6226_fm_vio_reg_cfg(radio, powerflag);
+		if (rc < 0) {
+			FMDERR("In %s, vio reg cfg failed %x\n", __func__, rc);
+			rtc6226_fm_vdd_reg_cfg(radio, false);
+			return rc;
+		}
+	} else {
+		/* Turn OFF sequence */
+		rc = rtc6226_fm_vdd_reg_cfg(radio, powerflag);
+		if (rc < 0)
+			FMDERR("In %s, vdd reg cfg failed %x\n", __func__, rc);
+		rc = rtc6226_fm_vio_reg_cfg(radio, powerflag);
+		if (rc < 0)
+			FMDERR("In %s, vio reg cfg failed %x\n", __func__, rc);
+	}
+	return rc;
+}
+/*
+ * rtc6226_fops_open - file open
+ */
+int rtc6226_fops_open(struct file *file)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval;
+
+	FMDBG("%s enter user num = %d\n", __func__, radio->users);
+	if (atomic_inc_return(&radio->users) != 1) {
+		FMDERR("Device already in use. Try again later\n");
+		atomic_dec(&radio->users);
+		return -EBUSY;
+	}
+
+	INIT_DELAYED_WORK(&radio->work, rtc6226_handler);
+	INIT_DELAYED_WORK(&radio->work_scan, rtc6226_scan);
+	INIT_WORK(&radio->rds_worker, rtc6226_rds_handler);
+
+	/* Power up  Supply voltage to VDD and VIO */
+	retval = rtc6226_fm_power_cfg(radio, TURNING_ON);
+	if (retval) {
+		FMDERR("%s: failed to supply voltage\n", __func__);
+		goto open_err_setup;
+	}
+
+	retval = rtc6226_enable_irq(radio);
+	/* Wait for the value to take effect on gpio. */
+	msleep(100);
+	if (retval) {
+		FMDERR("%s:enable irq failed\n", __func__);
+		goto open_err_req_irq;
+	}
+	return retval;
+
+open_err_req_irq:
+	rtc6226_fm_power_cfg(radio, TURNING_OFF);
+open_err_setup:
+	atomic_dec(&radio->users);
+	return retval;
+}
+
+/*
+ * rtc6226_fops_release - file release
+ */
+int rtc6226_fops_release(struct file *file)
+{
+	struct rtc6226_device *radio = video_drvdata(file);
+	int retval = 0;
+
+	FMDBG("%s : Exit\n", __func__);
+	if (radio->mode != FM_OFF) {
+		rtc6226_power_down(radio);
+		radio->mode = FM_OFF;
+	}
+	rtc6226_disable_irq(radio);
+	atomic_dec(&radio->users);
+	retval = rtc6226_fm_power_cfg(radio, TURNING_OFF);
+	if (retval < 0)
+		FMDERR("%s: failed to apply voltage\n", __func__);
+	return retval;
+}
+
+static int rtc6226_parse_dt(struct device *dev,
+			struct rtc6226_device *radio)
+{
+	int rc = 0;
+	struct device_node *np = dev->of_node;
+
+	radio->int_gpio = of_get_named_gpio(np, "fmint-gpio", 0);
+	if (radio->int_gpio < 0) {
+		FMDERR("%s int-gpio not provided in device tree\n", __func__);
+		rc = radio->int_gpio;
+		goto err_int_gpio;
+	}
+
+	rc = gpio_request(radio->int_gpio, "fm_int");
+	if (rc) {
+		FMDERR("%s unable to request gpio %d (%d)\n", __func__,
+						radio->int_gpio, rc);
+		goto err_int_gpio;
+	}
+
+	rc = gpio_direction_output(radio->int_gpio, 0);
+	if (rc) {
+		FMDERR("%s unable to set the gpio %d direction(%d)\n",
+		__func__, radio->int_gpio, rc);
+		goto err_int_gpio;
+	}
+		/* Wait for the value to take effect on gpio. */
+	msleep(100);
+
+	return rc;
+
+err_int_gpio:
+	gpio_free(radio->int_gpio);
+
+	return rc;
+}
+
+static int rtc6226_pinctrl_init(struct rtc6226_device *radio)
+{
+	int retval = 0;
+
+	radio->fm_pinctrl = devm_pinctrl_get(&radio->client->dev);
+	if (IS_ERR_OR_NULL(radio->fm_pinctrl)) {
+		FMDERR("%s: target does not use pinctrl\n", __func__);
+		retval = PTR_ERR(radio->fm_pinctrl);
+		return retval;
+	}
+
+	radio->gpio_state_active =
+			pinctrl_lookup_state(radio->fm_pinctrl,
+						"pmx_fm_active");
+	if (IS_ERR_OR_NULL(radio->gpio_state_active)) {
+		FMDERR("%s: cannot get FM active state\n", __func__);
+		retval = PTR_ERR(radio->gpio_state_active);
+		goto err_active_state;
+	}
+
+	radio->gpio_state_suspend =
+				pinctrl_lookup_state(radio->fm_pinctrl,
+							"pmx_fm_suspend");
+	if (IS_ERR_OR_NULL(radio->gpio_state_suspend)) {
+		FMDERR("%s: cannot get FM suspend state\n", __func__);
+		retval = PTR_ERR(radio->gpio_state_suspend);
+		goto err_suspend_state;
+	}
+
+	return retval;
+
+err_suspend_state:
+	radio->gpio_state_suspend = 0;
+
+err_active_state:
+	radio->gpio_state_active = 0;
+
+	return retval;
+}
+
+static int rtc6226_dt_parse_vreg_info(struct device *dev,
+			struct fm_power_vreg_data *vreg, const char *vreg_name)
+{
+	int ret = 0;
+	u32 vol_suply[2];
+	struct device_node *np = dev->of_node;
+
+	ret = of_property_read_u32_array(np, vreg_name, vol_suply, 2);
+	if (ret < 0) {
+		FMDERR("Invalid property name\n");
+		ret =  -EINVAL;
+	} else {
+		vreg->low_vol_level = vol_suply[0];
+		vreg->high_vol_level = vol_suply[1];
+	}
+	return ret;
+}
+
+/*
+ * rtc6226_i2c_probe - probe for the device
+ */
+static int rtc6226_i2c_probe(struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct rtc6226_device *radio;
+	struct v4l2_device *v4l2_dev;
+	struct v4l2_ctrl_handler *hdl;
+	struct regulator *vddvreg = NULL;
+	struct regulator *viovreg = NULL;
+	int retval = 0;
+	int i = 0;
+	int kfifo_alloc_rc = 0;
+
+	/* struct v4l2_ctrl *ctrl; */
+	/* need to add description "irq-fm" in dts */
+
+	FMDBG("%s enter\n", __func__);
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		retval = -ENODEV;
+		return retval;
+	}
+
+	/*
+	 * if voltage regulator is not ready yet, return the error
+	 * if error is -EPROBE_DEFER to kernel then probe will be called at
+	 * later point of time.
+	 */
+	viovreg = regulator_get(&client->dev, "vio");
+	if (IS_ERR(viovreg)) {
+		retval = PTR_ERR(viovreg);
+		FMDERR("%s: regulator_get(vio) failed. retval=%d\n",
+			__func__, retval);
+		return retval;
+	}
+
+	vddvreg = regulator_get(&client->dev, "vdd");
+	if (IS_ERR(vddvreg)) {
+		retval = PTR_ERR(vddvreg);
+		FMDERR("%s: regulator_get(vdd) failed. retval=%d\n",
+			__func__, retval);
+		regulator_put(viovreg);
+		return retval;
+	}
+
+	/* private data allocation and initialization */
+	radio = kzalloc(sizeof(struct rtc6226_device), GFP_KERNEL);
+	if (!radio) {
+		retval = -ENOMEM;
+		regulator_put(viovreg);
+		regulator_put(vddvreg);
+		return retval;
+	}
+
+	v4l2_dev = &radio->v4l2_dev;
+	retval = v4l2_device_register(&client->dev, v4l2_dev);
+	if (retval < 0) {
+		FMDERR("%s couldn't register v4l2_device\n", __func__);
+		goto err_vreg;
+	}
+
+	FMDBG("v4l2_device_register successfully\n");
+	hdl = &radio->ctrl_handler;
+
+	/* initialize the device count */
+	atomic_set(&radio->users, 0);
+	radio->client = client;
+	mutex_init(&radio->lock);
+	init_completion(&radio->completion);
+
+	retval = rtc6226_parse_dt(&client->dev, radio);
+	if (retval) {
+		FMDERR("%s: Parsing DT failed(%d)\n", __func__, retval);
+		goto err_v4l2;
+	}
+
+	radio->vddreg = devm_kzalloc(&client->dev,
+				sizeof(struct fm_power_vreg_data),
+				GFP_KERNEL);
+	if (!radio->vddreg) {
+		FMDERR("%s: allocating memory for vdd vreg failed\n",
+							__func__);
+		retval = -ENOMEM;
+		goto err_v4l2;
+	}
+
+	radio->vddreg->reg = vddvreg;
+	radio->vddreg->name = "vdd";
+	radio->vddreg->is_enabled = false;
+	of_property_read_u32(client->dev.of_node,
+			"rtc6226,vdd-load", &radio->vddreg->vdd_load);
+	FMDERR("%s: rtc6226,vdd-load val %d\n",
+		__func__, radio->vddreg->vdd_load);
+	retval = rtc6226_dt_parse_vreg_info(&client->dev,
+			radio->vddreg, "rtc6226,vdd-supply-voltage");
+	if (retval < 0) {
+		FMDERR("%s: parsing vdd-supply failed\n", __func__);
+		goto err_v4l2;
+	}
+
+	radio->vioreg = devm_kzalloc(&client->dev,
+				sizeof(struct fm_power_vreg_data),
+				GFP_KERNEL);
+	if (!radio->vioreg) {
+		FMDERR("%s: allocating memory for vio vreg failed\n",
+							__func__);
+		retval = -ENOMEM;
+		goto err_v4l2;
+	}
+	radio->vioreg->reg = viovreg;
+	radio->vioreg->name = "vio";
+	radio->vioreg->is_enabled = false;
+	retval = rtc6226_dt_parse_vreg_info(&client->dev,
+			radio->vioreg, "rtc6226,vio-supply-voltage");
+	if (retval < 0) {
+		FMDERR("%s: parsing vio-supply failed\n", __func__);
+		goto err_v4l2;
+	}
+	/* Initialize pin control*/
+	retval = rtc6226_pinctrl_init(radio);
+	if (retval) {
+		FMDERR("%s: rtc6226_pinctrl_init returned %d\n",
+							__func__, retval);
+		/* if pinctrl is not supported, -EINVAL is returned*/
+		if (retval == -EINVAL)
+			retval = 0;
+	} else {
+		FMDBG("%s rtc6226_pinctrl_init success\n", __func__);
+	}
+
+	memcpy(&radio->videodev, &rtc6226_viddev_template,
+		sizeof(struct video_device));
+
+	radio->videodev.v4l2_dev = v4l2_dev;
+	radio->videodev.ioctl_ops = &rtc6226_ioctl_ops;
+	radio->videodev.device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE
+		| V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
+	video_set_drvdata(&radio->videodev, radio);
+
+	/* rds buffer allocation */
+	radio->buf_size = rds_buf * 3;
+	radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
+	if (!radio->buffer) {
+		retval = -EIO;
+		goto err;
+	}
+
+	for (i = 0; i < RTC6226_FM_BUF_MAX; i++) {
+		spin_lock_init(&radio->buf_lock[i]);
+
+		kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
+				STD_BUF_SIZE, GFP_KERNEL);
+
+		if (kfifo_alloc_rc != 0) {
+			FMDERR("%s: failed allocating buffers %d\n",
+					__func__, kfifo_alloc_rc);
+			retval = -ENOMEM;
+			goto err_rds;
+		}
+	}
+	radio->wqueue = NULL;
+	radio->wqueue_scan = NULL;
+	radio->wqueue_rds = NULL;
+	radio->band = -1;
+
+	/* rds buffer configuration */
+	radio->wr_index = 0;
+	radio->rd_index = 0;
+	init_waitqueue_head(&radio->event_queue);
+	init_waitqueue_head(&radio->read_queue);
+	init_waitqueue_head(&rtc6226_wq);
+
+	radio->wqueue  = create_singlethread_workqueue("fmradio");
+	if (!radio->wqueue) {
+		retval = -ENOMEM;
+		goto err_rds;
+	}
+
+	radio->wqueue_scan  = create_singlethread_workqueue("fmradioscan");
+	if (!radio->wqueue_scan) {
+		retval = -ENOMEM;
+		goto err_wqueue;
+	}
+
+	radio->wqueue_rds  = create_singlethread_workqueue("fmradiords");
+	if (!radio->wqueue_rds) {
+		retval = -ENOMEM;
+		goto err_wqueue_scan;
+	}
+
+	/* register video device */
+	retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
+		radio_nr);
+	if (retval) {
+		dev_info(&client->dev, "Could not register video device\n");
+		goto err_all;
+	}
+
+	i2c_set_clientdata(client, radio);		/* move from below */
+	FMDBG("%s exit\n", __func__);
+	return 0;
+
+err_all:
+	destroy_workqueue(radio->wqueue_rds);
+err_wqueue_scan:
+	destroy_workqueue(radio->wqueue_scan);
+err_wqueue:
+	destroy_workqueue(radio->wqueue);
+err_rds:
+	kfree(radio->buffer);
+err:
+	video_device_release_empty(&radio->videodev);
+err_v4l2:
+	v4l2_device_unregister(v4l2_dev);
+err_vreg:
+	if (radio && radio->vioreg && radio->vioreg->reg) {
+		regulator_put(radio->vioreg->reg);
+		devm_kfree(&client->dev, radio->vioreg);
+	} else {
+		regulator_put(viovreg);
+	}
+	if (radio && radio->vddreg && radio->vddreg->reg) {
+		regulator_put(radio->vddreg->reg);
+		devm_kfree(&client->dev, radio->vddreg);
+	} else {
+		regulator_put(vddvreg);
+	}
+	kfree(radio);
+	return retval;
+}
+
+/*
+ * rtc6226_i2c_remove - remove the device
+ */
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0))
+static void rtc6226_i2c_remove(struct i2c_client *client)
+#else
+static int rtc6226_i2c_remove(struct i2c_client *client)
+#endif
+{
+	struct rtc6226_device *radio = i2c_get_clientdata(client);
+
+	free_irq(client->irq, radio);
+	kfree(radio->buffer);
+	v4l2_ctrl_handler_free(&radio->ctrl_handler);
+	if (video_is_registered(&radio->videodev))
+		video_unregister_device(&radio->videodev);
+	video_device_release_empty(&radio->videodev);
+	v4l2_device_unregister(&radio->v4l2_dev);
+	kfree(radio);
+	FMDBG("%s exit\n", __func__);
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0))
+	return 0;
+#endif
+}
+
+#ifdef CONFIG_PM
+/*
+ * rtc6226_i2c_suspend - suspend the device
+ */
+static int rtc6226_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct rtc6226_device *radio = i2c_get_clientdata(client);
+
+	FMDBG("%s %d\n", __func__, radio->client->addr);
+
+	return 0;
+}
+
+
+/*
+ * rtc6226_i2c_resume - resume the device
+ */
+static int rtc6226_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct rtc6226_device *radio = i2c_get_clientdata(client);
+
+	FMDBG("%s %d\n", __func__, radio->client->addr);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rtc6226_i2c_pm, rtc6226_i2c_suspend,
+						rtc6226_i2c_resume);
+#endif
+
+
+/*
+ * rtc6226_i2c_driver - i2c driver interface
+ */
+struct i2c_driver rtc6226_i2c_driver = {
+	.driver = {
+		.name			= "rtc6226",
+		.owner			= THIS_MODULE,
+		.of_match_table = of_match_ptr(rtc6226_i2c_dt_ids),
+#ifdef CONFIG_PM
+		.pm				= &rtc6226_i2c_pm,
+#endif
+	},
+	.probe				= rtc6226_i2c_probe,
+	.remove				= rtc6226_i2c_remove,
+	.id_table			= rtc6226_i2c_id,
+};
+
+/*
+ * rtc6226_i2c_init
+ */
+int rtc6226_i2c_init(void)
+{
+	FMDBG(DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+	return i2c_add_driver(&rtc6226_i2c_driver);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);

+ 700 - 0
qcom/opensource/bt-kernel/rtc6226/radio-rtc6226.h

@@ -0,0 +1,700 @@
+/*  drivers/media/radio/rtc6226/radio-rtc6226.h
+ *
+ *  Driver for Richwave RTC6226 FM Tuner
+ *
+ *  Copyright (c) 2009 Tobias Lorenz <[email protected]>
+ *  Copyright (c) 2012 Hans de Goede <[email protected]>
+ *  Copyright (c) 2018 LG Electronics, Inc.
+ *  Copyright (c) 2018 Richwave Technology Co.Ltd
+ *  Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* driver definitions */
+/* #define _RDSDEBUG */
+#define DRIVER_NAME "rtc6226-fmtuner"
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dev.h>
+#include <asm/unaligned.h>
+#include <linux/interrupt.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/kfifo.h>
+#include <asm/unaligned.h>
+
+#define RW_Kernel_ENG
+
+#define DEBUG
+#undef FMDBG
+#define FMDBG(fmt, args...) pr_debug("rtc6226: " fmt, ##args)
+
+#undef FMDERR
+#define FMDERR(fmt, args...) pr_err("rtc6226: " fmt, ##args)
+
+/* driver definitions */
+#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 1)
+#define DRIVER_CARD "Richwave rtc6226 FM Tuner"
+#define DRIVER_DESC "I2C radio driver for rtc6226 FM Tuner"
+#define DRIVER_VERSION "0.1.0"
+
+/**************************************************************************
+ * Register Definitions
+ **************************************************************************/
+#define RADIO_REGISTER_SIZE         2       /* 16 register bit width */
+#define RADIO_REGISTER_NUM          32      /* DEVICEID */
+#define RDS_REGISTER_NUM            6       /* STATUSRSSI */
+
+#define DEVICEID                    0       /* Device ID Code */
+#define DEVICE_ID                   0xffff  /* [15:00] Device ID */
+#define DEVICEID_PN                 0xf000  /* [15:12] Part Number */
+#define DEVICEID_MFGID              0x0fff  /* [11:00] Manufacturer ID */
+
+#define CHIPID                      1       /* Chip ID Code */
+#define CHIPID_REVISION_NO          0xfc00  /* [15:10] Chip Reversion */
+
+#define MPXCFG                      2       /* Power Configuration */
+#define MPXCFG_CSR0_DIS_SMUTE       0x8000  /* [15:15] Disable Softmute */
+#define MPXCFG_CSR0_DIS_MUTE        0x4000  /* [14:14] Disable Mute */
+#define MPXCFG_CSR0_MONO            0x2000  /* [13:13] Mono or Auto Detect */
+#define MPXCFG_CSR0_DEEM            0x1000  /* [12:12] DE-emphasis */
+#define MPXCFG_CSR0_VOLUME_EXT      0x0400  /* [10:10] Volume Extend */
+#define MPXCFG_CSR0_BLNDADJUST      0x0300  /* [09:08] Blending Adjust */
+#define MPXCFG_CSR0_SMUTERATE       0x00c0  /* [07:06] Softmute Rate */
+#define MPXCFG_CSR0_SMUTEATT        0x0030  /* [05:04] Softmute Attenuation */
+#define MPXCFG_CSR0_VOLUME          0x000f  /* [03:00] Volume */
+
+#define CHANNEL                     3       /* Tuning Channel Setting */
+#define CHANNEL_CSR0_TUNE           0x8000  /* [15:15] Tune */
+#define CHANNEL_CSR0_CH             0x7fff  /* [14:00] Tuning Channel */
+
+#define SYSCFG                      4       /* System Configuration 1 */
+#define SYSCFG_CSR0_RDSIRQEN        0x8000  /* [15:15] RDS Interrupt Enable */
+#define SYSCFG_CSR0_STDIRQEN        0x4000  /* [14:14] STD Interrupt Enable */
+#define SYSCFG_CSR0_DIS_AGC         0x2000  /* [13:13] Disable AGC */
+#define SYSCFG_CSR0_RDS_EN          0x1000  /* [12:12] RDS Enable */
+#define SYSCFG_CSR0_RBDS_M          0x0300  /* [09:08] MMBS setting */
+
+#define SEEKCFG1                    5       /* Seek Configuration 1 */
+#define SEEKCFG1_CSR0_SEEK          0x8000  /* [15:15] Enable Seek Function */
+#define SEEKCFG1_CSR0_SEEKUP        0x4000  /* [14:14] Seek Direction */
+#define SEEKCFG1_CSR0_SKMODE        0x2000  /* [13:13] Seek Mode */
+#define SEEKCFG1_CSR0_RSSI_LOW_TH   0x0f00  /* [11:08] RSSI Seek Threshold */
+#define SEEKCFG1_CSR0_RSSI_MONO_TH  0x000f  /* [03:00] RSSI Seek Threshold */
+
+#define POWERCFG                    6       /* Power Configuration */
+#define POWERCFG_CSR0_ENABLE        0x8000  /* [15:15] Power-up Enable */
+#define POWERCFG_CSR0_DISABLE       0x4000  /* [14:14] Power-up Disable */
+#define POWERCFG_CSR0_BLNDOFS       0x0f00  /* [11:08] Blending Offset Value */
+
+#define PADCFG                      7       /* PAD Configuration */
+#define PADCFG_CSR0_GPIO            0x0004  /* [03:02] General purpose I/O */
+
+#define BANKCFG                     8       /* Bank Serlection */
+
+#define SEEKCFG2                    9       /* Seek Configuration 2 */
+
+#define STATUS                      10      /* Status and Work channel */
+#define STATUS_RDS_RDY              0x8000  /* [15:15] RDS Ready */
+#define STATUS_STD                  0x4000  /* [14:14] Seek/Tune Done */
+#define STATUS_SF                   0x2000  /* [13:13] Seek Fail */
+#define STATUS_RDS_SYNC             0x0800  /* [11:11] RDS synchronization */
+#define STATUS_SI                   0x0400  /* [10:10] Stereo Indicator */
+
+#define RSSI                        11      /* RSSI and RDS error */
+#define RSSI_RDS_BA_ERRS            0xc000  /* [15:14] RDS Block A Errors */
+#define RSSI_RDS_BB_ERRS            0x3000  /* [15:14] RDS Block B Errors */
+#define RSSI_RDS_BC_ERRS            0x0c00  /* [13:12] RDS Block C Errors */
+#define RSSI_RDS_BD_ERRS            0x0300  /* [11:10] RDS Block D Errors */
+#define RSSI_RSSI                   0x00ff  /* [09:00] Read Channel */
+
+#define BA_DATA                     12      /* Block A data */
+#define RDSA_RDSA                   0xffff  /* [15:00] RDS Block A Data */
+
+#define BB_DATA                     13      /* Block B data */
+#define RDSB_RDSB                   0xffff  /* [15:00] RDS Block B Data */
+
+#define BC_DATA                     14      /* Block C data */
+#define RDSC_RDSC                   0xffff  /* [15:00] RDS Block C Data */
+
+#define BD_DATA                     15      /* Block D data */
+#define RDSD_RDSD                   0xffff  /* [15:00] RDS Block D Data */
+
+#define AUDIOCFG					0x12
+#define AUDIOCFG_CSR0_VOL_AUTOFIX   0x0800  //[11:11] LSB Volume Bit Auto Fix(1)
+
+#define RADIOCFG					0x13
+#define CHANNEL_CSR0_CHSPACE        0x1f00  /* [12:08] Channel Sapcing */
+
+#define RADIOSEEKCFG1				0x14
+/* [14:00] FM Seek Top CH, Unit 10KHz */
+#define CHANNEL_CSR0_FREQ_TOP       0x7fff
+
+#define RADIOSEEKCFG2				0x15
+/*[14:00] FM Seek Bottom CH, Unit 10KHz */
+#define CHANNEL_CSR0_FREQ_BOT       0x7fff
+
+#define I2SCFG			    0x1c
+/* [13:13] I2S DSP Mode(0:Normal, 1:Special) */
+#define I2S_DSP_SEL                 0x2000
+/* [12:12] BCLK Polarity(0:Falling, 1:Rising) */
+#define I2S_BCLK_POL                0x1000
+/* [11:10] Word Bits Select(0:8b, 1:16b, 2:20b, 3:24b) */
+#define I2S_WD_SEL                  0x0c00
+/* [09:08] Right CH Control(0:On, 1:Off, 1x:Auto) */
+#define I2S_RCH_SEL                 0x0300
+/* [07:07] I2S Enable */
+#define I2S_EN			    0x0080  /* [07:07] I2S Enable */
+#define I2S_MSEL                    0x0040  /* [06:06] I2S Master */
+/* [05:04] I2S Output Mode(0:I2S, 1:LJ, 2:DSPA, 3:DSPB) */
+#define I2S_MODE                    0x0030
+/* [03:02] I2S Sample Rate(0:32K, 1:44.1K, 2:48K) */
+#define I2S_FS_AUD_SEL              0x000c
+/* [05:04] I2S BCLK Ratio(0:M32, 1:M64, 2:M128, 3:M256) */
+#define I2S_BCLK_AUD_SEL            0x0030
+
+#define CHANNEL1		    0x1e
+#define STATUS_READCH		    0x7fff  /* [14:00] Read Channel */
+
+#define TURN_ON 1
+#define TURN_OFF 0
+#define SRCH_UP          1
+#define SRCH_DOWN        0
+
+#define WRAP_ENABLE      1
+#define WRAP_DISABLE     0
+#define DEFAULT_RSSI_TH  8
+/* Standard buffer size */
+#define STD_BUF_SIZE     256
+
+/* to distinguish between seek, tune during STC int. */
+#define NO_SEEK_TUNE_PENDING 0
+#define TUNE_PENDING 1
+#define SEEK_PENDING 2
+#define SCAN_PENDING 3
+#define START_SCAN 1
+#define TUNE_TIMEOUT_MSEC 3000
+#define SEEK_TIMEOUT_MSEC 15000
+
+#define RTC6226_MIN_SRCH_MODE 0x00
+#define RTC6226_MAX_SRCH_MODE 0x02
+
+#define MIN_DWELL_TIME 0x00
+#define MAX_DWELL_TIME 0x0F
+
+#define TUNE_STEP_SIZE 10
+#define NO_OF_RDS_BLKS 4
+
+#define GET_MSB(x)((x >> 8) & 0xFF)
+#define GET_LSB(x)((x) & 0xFF)
+
+#define OFFSET_OF_GRP_TYP 11
+#define RDS_INT_BIT 0x01
+#define FIFO_CNT_16 0x10
+#define UNCORRECTABLE_RDS_EN 0xFF01
+
+/* Write starts with the upper byte of register 0x02 */
+#define WRITE_REG_NUM       3
+#define WRITE_INDEX(i)      ((i + 0x02)%16)
+
+/* Read starts with the upper byte of register 0x0a */
+#define READ_REG_NUM        2
+#define READ_INDEX(i)       ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
+
+#define MSB_OF_BLK_0 4
+#define LSB_OF_BLK_0 5
+#define MSB_OF_BLK_1 6
+#define LSB_OF_BLK_1 7
+#define MSB_OF_BLK_2 8
+#define LSB_OF_BLK_2 9
+#define MSB_OF_BLK_3 10
+#define LSB_OF_BLK_3 11
+#define MAX_RT_LEN 64
+#define END_OF_RT 0x0d
+#define MAX_PS_LEN 8
+#define OFFSET_OF_PS 5
+#define PS_VALIDATE_LIMIT 2
+#define RT_VALIDATE_LIMIT 2
+#define RDS_CMD_LEN 3
+#define RDS_RSP_LEN 13
+#define PS_EVT_DATA_LEN (MAX_PS_LEN + OFFSET_OF_PS)
+#define NO_OF_PS 1
+#define OFFSET_OF_RT 5
+#define OFFSET_OF_PTY 5
+#define MAX_LEN_2B_GRP_RT 32
+#define CNT_FOR_2A_GRP_RT 4
+#define CNT_FOR_2B_GRP_RT 2
+#define PS_MASK 0x3
+#define PTY_MASK 0x1F
+#define NO_OF_CHARS_IN_EACH_ADD 2
+
+#define CORRECTED_NONE          0
+#define CORRECTED_ONE_TO_TWO    1
+#define CORRECTED_THREE_TO_FIVE 2
+#define ERRORS_CORRECTED(data, block) ((data>>block)&0x03)
+/*Block Errors are reported in .5% increments*/
+#define BLER_SCALE_MAX 200
+
+/* freqs are divided by 10. */
+#define SCALE_AF_CODE_TO_FREQ_KHZ(x) (87500 + (x*100))
+
+#define RDS_TYPE_0A     (0 * 2 + 0)
+#define RDS_TYPE_0B     (0 * 2 + 1)
+#define RDS_TYPE_2A     (2 * 2 + 0)
+#define RDS_TYPE_2B     (2 * 2 + 1)
+#define RDS_TYPE_3A     (3 * 2 + 0)
+#define UNCORRECTABLE           3
+
+#define APP_GRP_typ_MASK	0x1F
+/*ERT*/
+#define ERT_AID			0x6552
+#define MAX_ERT_SEGMENT		31
+#define MAX_ERT_LEN		256
+#define ERT_OFFSET		3
+#define ERT_FORMAT_DIR_BIT	1
+#define ERT_CNT_PER_BLK		2
+/*RT PLUS*/
+#define DUMMY_CLASS		0
+#define RT_PLUS_LEN_1_TAG	3
+#define RT_ERT_FLAG_BIT		13
+#define RT_PLUS_AID             0x4bd7
+#define RT_ERT_FLAG_OFFSET	1
+#define RT_PLUS_OFFSET		2
+/*TAG1*/
+#define TAG1_MSB_OFFSET		3
+#define TAG1_MSB_MASK		7
+#define TAG1_LSB_OFFSET		13
+#define TAG1_POS_MSB_MASK	0x3F
+#define TAG1_POS_MSB_OFFSET	1
+#define TAG1_POS_LSB_OFFSET	7
+#define TAG1_LEN_OFFSET		1
+#define TAG1_LEN_MASK		0x3F
+/*TAG2*/
+#define TAG2_MSB_OFFSET		5
+#define TAG2_MSB_MASK		9
+#define TAG2_LSB_OFFSET		11
+#define TAG2_POS_MSB_MASK	0x3F
+#define TAG2_POS_MSB_OFFSET	3
+#define TAG2_POS_LSB_OFFSET	5
+#define TAG2_LEN_MASK		0x1F
+
+#define DEFAULT_AF_RSSI_LOW_TH 25
+#define NO_OF_AF_IN_GRP 2
+#define MAX_NO_OF_AF 25
+#define MAX_AF_LIST_SIZE (MAX_NO_OF_AF * 4) /* 4 bytes per freq */
+#define GET_AF_EVT_LEN(x) (7 + x*4)
+#define GET_AF_LIST_LEN(x) (x*4)
+#define MIN_AF_FREQ_CODE 1
+#define MAX_AF_FREQ_CODE 204
+#define MIN_RSSI 0
+#define MAX_RSSI 15
+
+/* 25 AFs supported for a freq. 224 means 1 AF. 225 means 2 AFs and so on */
+#define NO_AF_CNT_CODE 224
+#define MIN_AF_CNT_CODE 225
+#define MAX_AF_CNT_CODE 249
+#define AF_WAIT_SEC 10
+#define MAX_AF_WAIT_SEC 255
+#define AF_PI_WAIT_TIME 50 /* 50*100msec = 5sec */
+
+#define CH_SPACING_200 200
+#define CH_SPACING_100 100
+#define CH_SPACING_50 50
+#define TURNING_ON 1
+#define TURNING_OFF 0
+
+#define RW_PRIBASE	(V4L2_CID_USER_BASE | 0xf000)
+
+/* freqs are divided by 10. */
+#define SCALE_AF_CODE_TO_FREQ_KHZ(x) (87500 + (x*100))
+
+#define EXTRACT_BIT(data, bit_pos) ((data >> bit_pos) & 1)
+
+#define V4L2_CID_PRIVATE_CSR0_ENABLE    (RW_PRIBASE + (DEVICEID<<4) + 1)
+#define V4L2_CID_PRIVATE_CSR0_DISABLE   (RW_PRIBASE + (DEVICEID<<4) + 2)
+#define V4L2_CID_PRIVATE_DEVICEID       (RW_PRIBASE + (DEVICEID<<4) + 3)
+
+#define V4L2_CID_PRIVATE_CSR0_DIS_SMUTE (RW_PRIBASE + (DEVICEID<<4) + 4)
+#define V4L2_CID_PRIVATE_CSR0_DIS_MUTE  (RW_PRIBASE + (DEVICEID<<4) + 5)
+#define V4L2_CID_PRIVATE_CSR0_DEEM      (RW_PRIBASE + (DEVICEID<<4) + 6)
+#define V4L2_CID_PRIVATE_CSR0_BLNDADJUST (RW_PRIBASE + (DEVICEID<<4) + 7)
+#define V4L2_CID_PRIVATE_CSR0_VOLUME    (RW_PRIBASE + (DEVICEID<<4) + 8)
+
+#define V4L2_CID_PRIVATE_CSR0_BAND      (RW_PRIBASE + (DEVICEID<<4) + 9)
+#define V4L2_CID_PRIVATE_CSR0_CHSPACE   (RW_PRIBASE + (DEVICEID<<4) + 10)
+
+#define V4L2_CID_PRIVATE_CSR0_DIS_AGC   (RW_PRIBASE + (DEVICEID<<4) + 11)
+#define V4L2_CID_PRIVATE_CSR0_RDS_EN    (RW_PRIBASE + (DEVICEID<<4) + 12)
+
+#define V4L2_CID_PRIVATE_SEEK_CANCEL    (RW_PRIBASE + (DEVICEID<<4) + 13)
+
+#define V4L2_CID_PRIVATE_CSR0_SEEKRSSITH (RW_PRIBASE + (DEVICEID<<4) + 14)
+#define V4L2_CID_PRIVATE_RSSI           (RW_PRIBASE + (CHIPID<<4) + 1)
+
+#define V4L2_CID_PRIVATE_RDS_RDY        (RW_PRIBASE + (CHIPID<<4) + 2)
+#define V4L2_CID_PRIVATE_STD            (RW_PRIBASE + (CHIPID<<4) + 3)
+#define V4L2_CID_PRIVATE_SF	            (RW_PRIBASE + (CHIPID<<4) + 4)
+#define V4L2_CID_PRIVATE_RDS_SYNC	    (RW_PRIBASE + (CHIPID<<4) + 5)
+#define V4L2_CID_PRIVATE_SI	            (RW_PRIBASE + (CHIPID<<4) + 6)
+
+#define NO_WAIT				2
+#define RDS_WAITING			5
+#define SEEK_CANCEL			6
+#define TUNE_PARAM 16
+
+/**************************************************************************
+ * General Driver Definitions
+ **************************************************************************/
+
+enum rtc6226_buf_t {
+	RTC6226_FM_BUF_SRCH_LIST,
+	RTC6226_FM_BUF_EVENTS,
+	RTC6226_FM_BUF_RT_RDS,
+	RTC6226_FM_BUF_PS_RDS,
+	RTC6226_FM_BUF_RAW_RDS,
+	RTC6226_FM_BUF_AF_LIST,
+	RTC6226_FM_BUF_RT_PLUS = 11,
+	RTC6226_FM_BUF_ERT,
+	RTC6226_FM_BUF_MAX
+};
+
+enum rtc6226_evt_t {
+	RTC6226_EVT_RADIO_READY,
+	RTC6226_EVT_TUNE_SUCC,
+	RTC6226_EVT_SEEK_COMPLETE,
+	RTC6226_EVT_SCAN_NEXT,
+	RTC6226_EVT_NEW_RAW_RDS,
+	RTC6226_EVT_NEW_RT_RDS,
+	RTC6226_EVT_NEW_PS_RDS,
+	RTC6226_EVT_ERROR,
+	RTC6226_EVT_BELOW_TH,
+	RTC6226_EVT_ABOVE_TH,
+	RTC6226_EVT_STEREO,
+	RTC6226_EVT_MONO,
+	RTC6226_EVT_RDS_AVAIL,
+	RTC6226_EVT_RDS_NOT_AVAIL,
+	RTC6226_EVT_NEW_SRCH_LIST,
+	RTC6226_EVT_NEW_AF_LIST,
+	RTC6226_EVT_TXRDSDAT,
+	RTC6226_EVT_TXRDSDONE,
+	RTC6226_EVT_RADIO_DISABLED,
+	RTC6226_EVT_NEW_ODA,
+	RTC6226_EVT_NEW_RT_PLUS,
+	RTC6226_EVT_NEW_ERT
+};
+
+struct rtc6226_recv_conf_req {
+	__u16	emphasis;
+	__u16	ch_spacing;
+	/* limits stored as actual freq / TUNE_STEP_SIZE */
+	__u16	band_low_limit;
+	__u16	band_high_limit;
+};
+
+struct rtc6226_rel_freq {
+	__u8  rel_freq_msb;
+	__u8  rel_freq_lsb;
+} __packed;
+
+struct rtc6226_srch_list_compl {
+	__u8    num_stations_found;
+	struct rtc6226_rel_freq  rel_freq[20];
+} __packed;
+
+struct af_list_ev {
+	__le32   tune_freq_khz;
+	__le16   pi_code;
+	__u8    af_size;
+	__u8    af_list[MAX_AF_LIST_SIZE];
+} __packed;
+
+struct rtc6226_af_info {
+	/* no. of invalid AFs. */
+	u8 inval_freq_cnt;
+	/* no. of AFs in the list. */
+	u8 cnt;
+	/* actual size of the list */
+	u8 size;
+	/* index of currently tuned station in the AF list. */
+	u8 index;
+	/* PI of the frequency */
+	u16 pi;
+	/* freq to which AF list belongs to. */
+	u32 orig_freq_khz;
+	/* AF list */
+	u32 af_list[MAX_NO_OF_AF];
+};
+
+struct fm_power_vreg_data {
+	/* voltage regulator handle */
+	struct regulator *reg;
+	/* regulator name */
+	const char *name;
+	/* voltage levels to be set */
+	unsigned int low_vol_level;
+	unsigned int high_vol_level;
+	int vdd_load;
+	/* is this regulator enabled? */
+	bool is_enabled;
+};
+
+/*
+ * rtc6226_device - private data
+ */
+struct rtc6226_device {
+	int int_gpio;
+	int fm_sw_gpio;
+	int ext_ldo_gpio;
+	int reset_gpio;
+	struct regulator *vdd_reg;
+	struct v4l2_device v4l2_dev;
+	struct video_device videodev;
+	struct pinctrl *fm_pinctrl;
+	struct pinctrl_state *gpio_state_active;
+	struct pinctrl_state *gpio_state_suspend;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct fm_power_vreg_data *vddreg;
+	struct fm_power_vreg_data *vioreg;
+	int band;
+	int space;
+	atomic_t users;
+	unsigned int mode;
+	u8 seek_tune_status;
+	u8 rssi_th;
+	/* Richwave internal registers (0..15) */
+	unsigned short registers[RADIO_REGISTER_NUM];
+
+	/* RDS receive buffer */
+	wait_queue_head_t read_queue;
+	int irq;
+	int tuned_freq_khz;
+	int dwell_time_sec;
+	struct mutex lock;      /* buffer locking */
+	unsigned char *buffer;      /* size is always multiple of three */
+	bool is_search_cancelled;
+	u8 g_search_mode;
+	struct rtc6226_srch_list_compl srch_list;
+	/* buffer locks*/
+	spinlock_t buf_lock[RTC6226_FM_BUF_MAX];
+	struct rtc6226_recv_conf_req recv_conf;
+	struct workqueue_struct *wqueue;
+	struct workqueue_struct *wqueue_scan;
+	struct workqueue_struct *wqueue_rds;
+	struct work_struct rds_worker;
+	struct rtc6226_af_info af_info1;
+	struct rtc6226_af_info af_info2;
+
+	struct delayed_work work;
+	struct delayed_work work_scan;
+
+	wait_queue_head_t event_queue;
+	u8 write_buf[WRITE_REG_NUM];
+	/* TO read events, data*/
+	u8 read_buf[READ_REG_NUM];
+
+	u16 pi; /* PI of tuned channel */
+	u8 pty; /* programe type of the tuned channel */
+
+	u16 block[NO_OF_RDS_BLKS];
+	u8 rt_display[MAX_RT_LEN];   /* RT that will be displayed */
+	u8 rt_tmp0[MAX_RT_LEN]; /* high probability RT */
+	u8 rt_tmp1[MAX_RT_LEN]; /* low probability RT */
+	u8 rt_cnt[MAX_RT_LEN];  /* high probability RT's hit count */
+	u8 rt_flag;          /* A/B flag of RT */
+	bool valid_rt_flg;     /* validity of A/B flag */
+	u8 ps_display[MAX_PS_LEN];    /* PS that will be displayed */
+	u8 ps_tmp0[MAX_PS_LEN]; /* high probability PS */
+	u8 ps_tmp1[MAX_PS_LEN]; /* low probability PS */
+	u8 ps_cnt[MAX_PS_LEN];  /* high probability PS's hit count */
+	u8 bler[NO_OF_RDS_BLKS];
+	u8 rt_plus_carrier;
+	u8 ert_carrier;
+	u8 ert_buf[MAX_ERT_LEN];
+	u8 ert_len;
+	u8 c_byt_pair_index;
+	u8 utf_8_flag;
+	u8 rt_ert_flag;
+	u8 formatting_dir;
+	unsigned int buf_size;
+	unsigned int rd_index;
+	unsigned int wr_index;
+
+	struct kfifo data_buf[RTC6226_FM_BUF_MAX];
+
+	struct completion completion;
+	bool stci_enabled;      /* Seek/Tune Complete Interrupt */
+
+	struct i2c_client *client;
+	unsigned int tuner_state;
+	int lna_en;
+	int lna_gain;
+};
+
+enum radio_state_t {
+	FM_OFF,
+	FM_RECV,
+	FM_RESET,
+	FM_CALIB,
+	FM_TURNING_OFF,
+	FM_RECV_TURNING_ON,
+	FM_MAX_NO_STATES,
+};
+
+enum search_t {
+	SEEK,
+	SCAN,
+	SCAN_FOR_STRONG,
+};
+
+/**************************************************************************
+ * Frequency Multiplicator
+ **************************************************************************/
+#define FREQ_MUL 1000
+#define CONFIG_RDS
+
+enum v4l2_cid_private_rtc6226_t {
+	V4L2_CID_PRIVATE_RTC6226_SRCHMODE = (V4L2_CID_PRIVATE_BASE + 1),
+	V4L2_CID_PRIVATE_RTC6226_SCANDWELL,
+	V4L2_CID_PRIVATE_RTC6226_SRCHON,
+	V4L2_CID_PRIVATE_RTC6226_STATE,
+	V4L2_CID_PRIVATE_RTC6226_TRANSMIT_MODE,
+	V4L2_CID_PRIVATE_RTC6226_RDSGROUP_MASK,
+	V4L2_CID_PRIVATE_RTC6226_REGION,
+	V4L2_CID_PRIVATE_RTC6226_SIGNAL_TH,
+	V4L2_CID_PRIVATE_RTC6226_SRCH_PTY,
+	V4L2_CID_PRIVATE_RTC6226_SRCH_PI,
+	V4L2_CID_PRIVATE_RTC6226_SRCH_CNT,
+	V4L2_CID_PRIVATE_RTC6226_EMPHASIS,	/* 800000c */
+	V4L2_CID_PRIVATE_RTC6226_RDS_STD,
+	V4L2_CID_PRIVATE_RTC6226_SPACING,
+	V4L2_CID_PRIVATE_RTC6226_RDSON,
+	V4L2_CID_PRIVATE_RTC6226_RDSGROUP_PROC,
+	V4L2_CID_PRIVATE_RTC6226_LP_MODE,
+	V4L2_CID_PRIVATE_RTC6226_ANTENNA,
+	V4L2_CID_PRIVATE_RTC6226_RDSD_BUF,
+	V4L2_CID_PRIVATE_RTC6226_PSALL,
+	/*v4l2 Tx controls*/
+	V4L2_CID_PRIVATE_RTC6226_TX_SETPSREPEATCOUNT,
+	V4L2_CID_PRIVATE_RTC6226_STOP_RDS_TX_PS_NAME,
+	V4L2_CID_PRIVATE_RTC6226_STOP_RDS_TX_RT,
+	V4L2_CID_PRIVATE_RTC6226_IOVERC,
+	V4L2_CID_PRIVATE_RTC6226_INTDET,
+	V4L2_CID_PRIVATE_RTC6226_MPX_DCC,
+	V4L2_CID_PRIVATE_RTC6226_AF_JUMP,
+	V4L2_CID_PRIVATE_RTC6226_RSSI_DELTA,
+	V4L2_CID_PRIVATE_RTC6226_HLSI,
+
+	/*
+	 * Here we have IOCTl's that are specific to IRIS
+	 * (V4L2_CID_PRIVATE_BASE + 0x1E to V4L2_CID_PRIVATE_BASE + 0x28)
+	 */
+	V4L2_CID_PRIVATE_RTC6226_SOFT_MUTE,/* 0x800001E*/
+	V4L2_CID_PRIVATE_RTC6226_RIVA_ACCS_ADDR,
+	V4L2_CID_PRIVATE_RTC6226_RIVA_ACCS_LEN,
+	V4L2_CID_PRIVATE_RTC6226_RIVA_PEEK,
+	V4L2_CID_PRIVATE_RTC6226_RIVA_POKE,
+	V4L2_CID_PRIVATE_RTC6226_SSBI_ACCS_ADDR,
+	V4L2_CID_PRIVATE_RTC6226_SSBI_PEEK,
+	V4L2_CID_PRIVATE_RTC6226_SSBI_POKE,
+	V4L2_CID_PRIVATE_RTC6226_TX_TONE,
+	V4L2_CID_PRIVATE_RTC6226_RDS_GRP_COUNTERS,
+	V4L2_CID_PRIVATE_RTC6226_SET_NOTCH_FILTER, /* 0x8000028 */
+
+	V4L2_CID_PRIVATE_RTC6226_SET_AUDIO_PATH, /* 0x8000029 */
+	V4L2_CID_PRIVATE_RTC6226_DO_CALIBRATION, /* 0x800002A : IRIS */
+	V4L2_CID_PRIVATE_RTC6226_SRCH_ALGORITHM, /* 0x800002B */
+	V4L2_CID_PRIVATE_RTC6226_GET_SINR, /* 0x800002C : IRIS */
+	V4L2_CID_PRIVATE_RTC6226_INTF_LOW_THRESHOLD, /* 0x800002D */
+	V4L2_CID_PRIVATE_RTC6226_INTF_HIGH_THRESHOLD, /* 0x800002E */
+	/* 0x800002F : IRIS, For Richwave Spike TH */
+	V4L2_CID_PRIVATE_RTC6226_SINR_THRESHOLD,
+	/* V4L2_CID_PRIVATE_RTC6226_QLT_THRESHOLD,
+	 */ /* 0x800002F : IRIS, For Richwave Spike TH
+	 */
+	V4L2_CID_PRIVATE_RTC6226_SINR_SAMPLES, /* 0x8000030 : IRIS */
+	V4L2_CID_PRIVATE_RTC6226_SPUR_FREQ,
+	V4L2_CID_PRIVATE_RTC6226_SPUR_FREQ_RMSSI, /* For Richwave DC TH */
+	/* V4L2_CID_PRIVATE_RTC6226_OFS_THRESHOLD, */ /* For Richwave DC TH */
+	V4L2_CID_PRIVATE_RTC6226_SPUR_SELECTION,
+	V4L2_CID_PRIVATE_RTC6226_UPDATE_SPUR_TABLE,
+	V4L2_CID_PRIVATE_RTC6226_VALID_CHANNEL,
+	V4L2_CID_PRIVATE_RTC6226_AF_RMSSI_TH,
+	V4L2_CID_PRIVATE_RTC6226_AF_RMSSI_SAMPLES,
+	V4L2_CID_PRIVATE_RTC6226_GOOD_CH_RMSSI_TH,
+	V4L2_CID_PRIVATE_RTC6226_SRCHALGOTYPE,
+	V4L2_CID_PRIVATE_RTC6226_CF0TH12,
+	V4L2_CID_PRIVATE_RTC6226_SINRFIRSTSTAGE,
+	V4L2_CID_PRIVATE_RTC6226_RMSSIFIRSTSTAGE,
+	V4L2_CID_PRIVATE_RTC6226_RXREPEATCOUNT,
+	V4L2_CID_PRIVATE_RTC6226_RSSI_TH, /* 0x800003E */
+	V4L2_CID_PRIVATE_RTC6226_AF_JUMP_RSSI_TH /* 0x800003F */
+};
+
+enum FMBAND {FMBAND_87_108_MHZ, FMBAND_76_108_MHZ, FMBAND_76_91_MHZ,
+							FMBAND_64_76_MHZ};
+enum FMSPACE {FMSPACE_200_KHZ, FMSPACE_100_KHZ, FMSPACE_50_KHZ};
+
+
+/**************************************************************************
+ * Common Functions
+ **************************************************************************/
+extern struct i2c_driver rtc6226_i2c_driver;
+extern struct video_device rtc6226_viddev_template;
+extern const struct v4l2_ioctl_ops rtc6226_ioctl_ops;
+extern const struct v4l2_ctrl_ops rtc6226_ctrl_ops;
+
+extern struct tasklet_struct my_tasklet;
+extern int rtc6226_wq_flag;
+extern wait_queue_head_t rtc6226_wq;
+extern int rtc6226_get_all_registers(struct rtc6226_device *radio);
+extern int rtc6226_get_register(struct rtc6226_device *radio, int regnr);
+extern int rtc6226_set_register(struct rtc6226_device *radio, int regnr);
+extern int rtc6226_set_serial_registers(struct rtc6226_device *radio,
+	u16 *data, int bytes);
+int rtc6226_i2c_init(void);
+int rtc6226_reset_rds_data(struct rtc6226_device *radio);
+int rtc6226_set_freq(struct rtc6226_device *radio, unsigned int freq);
+int rtc6226_start(struct rtc6226_device *radio);
+int rtc6226_stop(struct rtc6226_device *radio);
+int rtc6226_fops_open(struct file *file);
+int rtc6226_power_up(struct rtc6226_device *radio);
+int rtc6226_power_down(struct rtc6226_device *radio);
+int rtc6226_fops_release(struct file *file);
+int rtc6226_vidioc_querycap(struct file *file, void *priv,
+	struct v4l2_capability *capability);
+int rtc6226_enable_irq(struct rtc6226_device *radio);
+void rtc6226_disable_irq(struct rtc6226_device *radio);
+void rtc6226_scan(struct work_struct *work);
+void rtc6226_search(struct rtc6226_device *radio, bool on);
+int rtc6226_cancel_seek(struct rtc6226_device *radio);
+void rtc6226_rds_handler(struct work_struct *worker);
+void rtc6226_q_event(struct rtc6226_device *radio, enum rtc6226_evt_t event);
+int rtc6226_reset_rds_data(struct rtc6226_device *radio);
+int rtc6226_rds_on(struct rtc6226_device *radio);

+ 25 - 0
qcom/opensource/bt-kernel/slimbus/Kconfig

@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config BTFM_SLIM
+	tristate "MSM Bluetooth/FM Slimbus Device"
+	depends on MSM_BT_POWER
+	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.
+
+		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.

+ 8 - 0
qcom/opensource/bt-kernel/slimbus/Makefile

@@ -0,0 +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

+ 750 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim.c

@@ -0,0 +1,750 @@
+// 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/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_slim.h"
+#include "btfm_slim_slave.h"
+#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC)
+#include "btfm_slim_hw_interface.h"
+#endif
+
+#define DELAY_FOR_PORT_OPEN_MS (200)
+#define SLIM_MANF_ID_QCOM	0x217
+#define SLIM_PROD_CODE		0x221
+#define BT_CMD_SLIM_TEST	0xbfac
+
+struct class *btfm_slim_class;
+static int btfm_slim_major;
+
+struct btfmslim *btfm_slim_drv_data;
+
+static int btfm_num_ports_open;
+
+static bool is_registered;
+
+int btfm_slim_write(struct btfmslim *btfmslim,
+		uint16_t reg, uint8_t reg_val, uint8_t pgd)
+{
+	int ret = -1;
+	uint32_t reg_addr;
+	int slim_write_tries = SLIM_SLAVE_RW_MAX_TRIES;
+
+	BTFMSLIM_INFO("Write to %s", pgd?"PGD":"IFD");
+	reg_addr = SLIM_SLAVE_REG_OFFSET + reg;
+
+	for ( ; slim_write_tries != 0; slim_write_tries--) {
+		mutex_lock(&btfmslim->xfer_lock);
+		ret = slim_writeb(pgd ? btfmslim->slim_pgd :
+			&btfmslim->slim_ifd, reg_addr, reg_val);
+		mutex_unlock(&btfmslim->xfer_lock);
+		if (ret) {
+			BTFMSLIM_DBG("retrying to Write 0x%02x to reg 0x%x ret %d",
+					 reg_val, reg_addr, ret);
+		} else {
+			BTFMSLIM_DBG("Written 0x%02x to reg 0x%x ret %d", reg_val, reg_addr, ret);
+			break;
+		}
+
+		usleep_range(5000, 5100);
+	}
+	if (ret) {
+		BTFMSLIM_DBG("retrying to Write 0x%02x to reg 0x%x ret %d",
+				reg_val, reg_addr, ret);
+	}
+	return ret;
+}
+
+int btfm_slim_read(struct btfmslim *btfmslim, uint32_t reg, uint8_t pgd)
+{
+	int ret = -1;
+	int slim_read_tries = SLIM_SLAVE_RW_MAX_TRIES;
+	uint32_t reg_addr;
+	BTFMSLIM_DBG("Read from %s", pgd?"PGD":"IFD");
+	reg_addr = SLIM_SLAVE_REG_OFFSET + reg;
+
+	for ( ; slim_read_tries != 0; slim_read_tries--) {
+		mutex_lock(&btfmslim->xfer_lock);
+
+		ret = slim_readb(pgd ? btfmslim->slim_pgd :
+				&btfmslim->slim_ifd, reg_addr);
+		BTFMSLIM_DBG("Read 0x%02x from reg 0x%x", ret, reg_addr);
+		mutex_unlock(&btfmslim->xfer_lock);
+		if (ret > 0)
+			break;
+		usleep_range(5000, 5100);
+	}
+	return ret;
+}
+
+int btfm_slim_enable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
+	uint8_t rxport, uint32_t rates, uint8_t nchan)
+{
+	int ret = -1;
+	int i = 0;
+	struct btfmslim_ch *chan = ch;
+	int chipset_ver;
+
+	if (!btfmslim || !ch)
+		return -EINVAL;
+
+	BTFMSLIM_DBG("port: %d ch: %d", ch->port, ch->ch);
+
+	chan->dai.sruntime = slim_stream_allocate(btfmslim->slim_pgd, "BTFM_SLIM");
+	if (chan->dai.sruntime == NULL) {
+		BTFMSLIM_ERR("slim_stream_allocate failed");
+		return -EINVAL;
+	}
+	chan->dai.sconfig.bps = btfmslim->bps;
+	chan->dai.sconfig.direction = btfmslim->direction;
+	chan->dai.sconfig.rate = rates;
+	chan->dai.sconfig.ch_count = nchan;
+	chan->dai.sconfig.chs = kcalloc(nchan, sizeof(unsigned int), GFP_KERNEL);
+	if (!chan->dai.sconfig.chs)
+		return -ENOMEM;
+
+	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;
+			}
+		}
+		chan->dai.sconfig.chs[i] = ch->ch;
+		chan->dai.sconfig.port_mask |= BIT(ch->port);
+	}
+
+	/* Activate the channel immediately */
+	BTFMSLIM_INFO("port: %d, ch: %d", chan->port, chan->ch);
+	chipset_ver = btpower_get_chipset_version();
+	BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
+
+	/* for feedback channel, PCM bit should not be set */
+	if (btfm_feedback_ch_setting) {
+		BTFMSLIM_DBG("port open for feedback ch, not setting PCM bit");
+		//prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;
+		/* reset so that next port open sets the data format properly */
+		btfm_feedback_ch_setting = 0;
+	}
+
+	ret = slim_stream_prepare(chan->dai.sruntime, &chan->dai.sconfig);
+	if (ret) {
+		BTFMSLIM_ERR("slim_stream_prepare failed = %d", ret);
+		goto error;
+	}
+
+	ret = slim_stream_enable(chan->dai.sruntime);
+	if (ret) {
+		BTFMSLIM_ERR("slim_stream_enable failed = %d", ret);
+		goto error;
+	}
+
+	if (ret == 0)
+		btfm_num_ports_open++;
+	BTFMSLIM_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
+	return ret;
+error:
+	BTFMSLIM_INFO("error %d while opening port, btfm_num_ports_open: %d",
+			ret, btfm_num_ports_open);
+	kfree(chan->dai.sconfig.chs);
+	chan->dai.sconfig.chs = NULL;
+	return ret;
+}
+
+int btfm_slim_disable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
+			uint8_t rxport, uint8_t nchan)
+{
+	int ret = -1;
+	int i = 0;
+	int chipset_ver = 0;
+	if (!btfmslim || !ch)
+		return -EINVAL;
+
+	BTFMSLIM_INFO("port:%d ", ch->port);
+	if (ch->dai.sruntime == NULL) {
+		BTFMSLIM_ERR("Channel not enabled yet. returning");
+		return -EINVAL;
+	}
+
+	if (rxport && (btfmslim->sample_rate == 44100 ||
+		btfmslim->sample_rate == 88200)) {
+		BTFMSLIM_INFO("disconnecting the ports, removing the channel");
+		/* disconnect the ports of the stream */
+		ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime,
+				true, false);
+		if (ret != 0)
+			BTFMSLIM_ERR("slim_stream_unprepare failed %d", ret);
+	}
+
+	ret = slim_stream_disable(ch->dai.sruntime);
+	if (ret != 0) {
+		BTFMSLIM_ERR("slim_stream_disable failed returned val = %d", ret);
+		if ((btfmslim->sample_rate != 44100) && (btfmslim->sample_rate != 88200)) {
+			/* disconnect the ports of the stream */
+			ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime,
+					true, false);
+			if (ret != 0)
+				BTFMSLIM_ERR("slim_stream_unprepare failed %d", ret);
+		}
+	}
+
+	/* free the ports allocated to the stream */
+	ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime, false, true);
+	if (ret != 0)
+		BTFMSLIM_ERR("slim_stream_unprepare failed returned val = %d", ret);
+
+	/* 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 [%d]", ret);
+				break;
+			}
+		}
+	}
+	ch->dai.sconfig.port_mask = 0;
+	if (ch->dai.sconfig.chs != NULL) {
+		kfree(ch->dai.sconfig.chs);
+		BTFMSLIM_INFO("setting ch->dai.sconfig.chs to NULL");
+		ch->dai.sconfig.chs = NULL;
+	} else
+		BTFMSLIM_ERR("ch->dai.sconfig.chs is already NULL");
+
+	if (btfm_num_ports_open > 0)
+		btfm_num_ports_open--;
+
+	ch->dai.sruntime = NULL;
+
+	BTFMSLIM_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
+
+	chipset_ver = btpower_get_chipset_version();
+
+	if (btfm_num_ports_open == 0 && (chipset_ver == QCA_HSP_SOC_ID_0200 ||
+		chipset_ver == QCA_HSP_SOC_ID_0210 ||
+		chipset_ver == QCA_HSP_SOC_ID_1201 ||
+		chipset_ver == QCA_HSP_SOC_ID_1211 ||
+		chipset_ver == QCA_HAMILTON_SOC_ID_0100 ||
+		chipset_ver == QCA_HAMILTON_SOC_ID_0101 ||
+		chipset_ver == QCA_HAMILTON_SOC_ID_0200 ||
+		chipset_ver == QCA_APACHE_SOC_ID_0100 ||
+		chipset_ver == QCA_APACHE_SOC_ID_0110 ||
+		chipset_ver == QCA_APACHE_SOC_ID_0121 ||
+		chipset_ver == QCA_MOSELLE_SOC_ID_0100 ||
+		chipset_ver == QCA_MOSELLE_SOC_ID_0110 ||
+		chipset_ver == QCA_MOSELLE_SOC_ID_0120)) {
+		BTFMSLIM_INFO("SB reset needed after all ports disabled, sleeping");
+		msleep(DELAY_FOR_PORT_OPEN_MS);
+	}
+
+	return ret;
+}
+
+static int btfm_slim_alloc_port(struct btfmslim *btfmslim)
+{
+	int ret = -EINVAL, i;
+	int  chipset_ver;
+	struct btfmslim_ch *rx_chs;
+	struct btfmslim_ch *tx_chs;
+
+	if (!btfmslim)
+		return ret;
+
+	chipset_ver = btpower_get_chipset_version();
+	BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
+
+	rx_chs = btfmslim->rx_chs;
+	tx_chs = btfmslim->tx_chs;
+	if ((chipset_ver >=  QCA_CHEROKEE_SOC_ID_0310) &&
+		(chipset_ver <=  QCA_CHEROKEE_SOC_ID_0320_UMC)) {
+		for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
+		(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) {
+			if (tx_chs->port == SLAVE_SB_PGD_PORT_TX1_FM)
+				tx_chs->port = CHRKVER3_SB_PGD_PORT_TX1_FM;
+			else if (tx_chs->port == SLAVE_SB_PGD_PORT_TX2_FM)
+				tx_chs->port = CHRKVER3_SB_PGD_PORT_TX2_FM;
+			BTFMSLIM_INFO("Tx port:%d", tx_chs->port);
+		}
+		tx_chs = btfmslim->tx_chs;
+	}
+	if (!rx_chs || !tx_chs)
+		return ret;
+
+	return 0;
+}
+
+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);
+	BTFMSLIM_INFO("");
+
+	do {
+
+		ret = slim_get_logical_addr(slim);
+		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;
+}
+
+int btfm_slim_hw_init(struct btfmslim *btfmslim)
+{
+	int ret = -1;
+	int chipset_ver;
+	struct slim_device *slim;
+	struct slim_device *slim_ifd;
+
+	BTFMSLIM_DBG("");
+	if (!btfmslim)
+		return -EINVAL;
+
+	if (btfmslim->enabled) {
+		BTFMSLIM_DBG("Already enabled");
+		return 0;
+	}
+
+	slim = btfmslim->slim_pgd;
+	slim_ifd = &btfmslim->slim_ifd;
+
+	mutex_lock(&btfmslim->io_lock);
+	BTFMSLIM_INFO(
+		"PGD Enum Addr: mfr id:%.02x prod code:%.02x dev ind:%.02x ins:%.02x",
+		slim->e_addr.manf_id, slim->e_addr.prod_code,
+		slim->e_addr.dev_index, slim->e_addr.instance);
+
+
+	chipset_ver = btpower_get_chipset_version();
+	BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
+
+	if (chipset_ver == QCA_HSP_SOC_ID_0100 ||
+		chipset_ver == QCA_HSP_SOC_ID_0110 ||
+		chipset_ver == QCA_HSP_SOC_ID_0200 ||
+		chipset_ver == QCA_HSP_SOC_ID_0210 ||
+		chipset_ver == QCA_HSP_SOC_ID_1201 ||
+		chipset_ver == QCA_HSP_SOC_ID_1211) {
+		BTFMSLIM_INFO("chipset is hastings prime, overwriting EA");
+		slim->is_laddr_valid = false;
+		slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim->e_addr.prod_code = SLIM_PROD_CODE;
+		slim->e_addr.dev_index = 0x01;
+		slim->e_addr.instance = 0x0;
+		/* we are doing this to indicate that this is not a child node
+		 * (doesn't have call back functions). Needed only for querying
+		 * logical address.
+		 */
+		slim_ifd->dev.driver = NULL;
+		slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
+		slim_ifd->is_laddr_valid = false;
+		slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim_ifd->e_addr.prod_code = SLIM_PROD_CODE;
+		slim_ifd->e_addr.dev_index = 0x0;
+		slim_ifd->e_addr.instance = 0x0;
+		slim_ifd->laddr = 0x0;
+	} else if (chipset_ver == QCA_MOSELLE_SOC_ID_0100 ||
+		chipset_ver == QCA_MOSELLE_SOC_ID_0110 ||
+		chipset_ver == QCA_MOSELLE_SOC_ID_0120) {
+		BTFMSLIM_INFO("chipset is Moselle, overwriting EA");
+		slim->is_laddr_valid = false;
+		slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim->e_addr.prod_code = 0x222;
+		slim->e_addr.dev_index = 0x01;
+		slim->e_addr.instance = 0x0;
+		/* we are doing this to indicate that this is not a child node
+		 * (doesn't have call back functions). Needed only for querying
+		 * logical address.
+		 */
+		slim_ifd->dev.driver = NULL;
+		slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
+		slim_ifd->is_laddr_valid = false;
+		slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim_ifd->e_addr.prod_code = 0x222;
+		slim_ifd->e_addr.dev_index = 0x0;
+		slim_ifd->e_addr.instance = 0x0;
+		slim_ifd->laddr = 0x0;
+	} else if (chipset_ver == QCA_HAMILTON_SOC_ID_0100 ||
+		chipset_ver ==  QCA_HAMILTON_SOC_ID_0101 ||
+		chipset_ver ==  QCA_HAMILTON_SOC_ID_0200) {
+		BTFMSLIM_INFO("chipset is Hamliton, overwriting EA");
+		slim->is_laddr_valid = false;
+		slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim->e_addr.prod_code = 0x220;
+		slim->e_addr.dev_index = 0x01;
+		slim->e_addr.instance = 0x0;
+		/* we are doing this to indicate that this is not a child node
+		 * (doesn't have call back functions). Needed only for querying
+		 * logical address.
+		 */
+		slim_ifd->dev.driver = NULL;
+		slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
+		slim_ifd->is_laddr_valid = false;
+		slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim_ifd->e_addr.prod_code = 0x220;
+		slim_ifd->e_addr.dev_index = 0x0;
+		slim_ifd->e_addr.instance = 0x0;
+		slim_ifd->laddr = 0x0;
+	} else if (chipset_ver == QCA_CHEROKEE_SOC_ID_0200 ||
+		chipset_ver ==  QCA_CHEROKEE_SOC_ID_0201  ||
+		chipset_ver ==  QCA_CHEROKEE_SOC_ID_0210  ||
+		chipset_ver ==  QCA_CHEROKEE_SOC_ID_0211  ||
+		chipset_ver ==  QCA_CHEROKEE_SOC_ID_0310  ||
+		chipset_ver ==  QCA_CHEROKEE_SOC_ID_0320  ||
+		chipset_ver ==  QCA_CHEROKEE_SOC_ID_0320_UMC  ||
+		chipset_ver ==  QCA_APACHE_SOC_ID_0100  ||
+		chipset_ver ==  QCA_APACHE_SOC_ID_0110  ||
+		chipset_ver ==  QCA_APACHE_SOC_ID_0120 ||
+		chipset_ver ==  QCA_APACHE_SOC_ID_0121 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_0101 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_0110 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_0120 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_0130 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_4130 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_5120 ||
+		chipset_ver ==  QCA_COMANCHE_SOC_ID_5130 ) {
+		BTFMSLIM_INFO("chipset is Chk/Apache/CMC, overwriting EA");
+		slim->is_laddr_valid = false;
+		slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim->e_addr.prod_code = 0x220;
+		slim->e_addr.dev_index = 0x01;
+		slim->e_addr.instance = 0x0;
+		/* we are doing this to indicate that this is not a child node
+		 * (doesn't have call back functions). Needed only for querying
+		 * logical address.
+		 */
+		slim_ifd->dev.driver = NULL;
+		slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
+		slim_ifd->is_laddr_valid = false;
+		slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
+		slim_ifd->e_addr.prod_code = 0x220;
+		slim_ifd->e_addr.dev_index = 0x0;
+		slim_ifd->e_addr.instance = 0x0;
+		slim_ifd->laddr = 0x0;
+	}
+		BTFMSLIM_INFO(
+			"PGD Enum Addr: manu id:%.02x prod code:%.02x dev idx:%.02x instance:%.02x",
+			slim->e_addr.manf_id, slim->e_addr.prod_code,
+			slim->e_addr.dev_index, slim->e_addr.instance);
+
+		BTFMSLIM_INFO(
+			"IFD Enum Addr: manu id:%.02x prod code:%.02x dev idx:%.02x instance:%.02x",
+			slim_ifd->e_addr.manf_id, slim_ifd->e_addr.prod_code,
+			slim_ifd->e_addr.dev_index, slim_ifd->e_addr.instance);
+
+	/* 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 logical address: %d", 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 logical address: %d", ret);
+		goto error;
+	}
+
+	ret = btfm_slim_alloc_port(btfmslim);
+	if (ret != 0)
+		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;
+
+	BTFMSLIM_INFO("");
+	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;
+}
+
+#if IS_ENABLED (CONFIG_BTFM_SLIM)
+void btfm_slim_get_hwep_details(struct slim_device *dev, struct btfmslim *btfm_slim)
+{
+}
+#else
+void btfm_slim_get_hwep_details(struct slim_device *slim, struct btfmslim *btfm_slim)
+{
+	struct device_node *np = slim->dev.of_node;
+	const __be32 *prop;
+	struct btfmslim_ch *rx_chs = btfm_slim->rx_chs;
+	struct btfmslim_ch *tx_chs = btfm_slim->tx_chs;
+	int len;
+
+	prop = of_get_property(np, "qcom,btslim-address", &len);
+	if (prop) {
+		btfm_slim->device_id = be32_to_cpup(&prop[0]);
+		BTFMSLIM_DBG("hwep slim address define in dt %08x", btfm_slim->device_id);
+	} else {
+		BTFMSLIM_ERR("btslim-address is not defined in dt using default address");
+		btfm_slim->device_id = 0;
+	}
+
+	if (!rx_chs || !tx_chs) {
+		BTFMSLIM_ERR("either rx/tx channels are configured to null");
+		return;
+	}
+
+	prop = of_get_property(np, "qcom,btslimrx-channels", &len);
+	if (prop) {
+		/* Check if we need any protection for index */
+		rx_chs[0].ch = (uint8_t)be32_to_cpup(&prop[0]);
+		rx_chs[1].ch = (uint8_t)be32_to_cpup(&prop[1]);
+		BTFMSLIM_DBG("Rx: id\tname\tport\tch");
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%d", rx_chs[0].id,
+				rx_chs[0].name, rx_chs[0].port,
+				rx_chs[0].ch);
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%d", rx_chs[1].id,
+				rx_chs[1].name, rx_chs[1].port,
+				rx_chs[1].ch);
+	} else {
+		BTFMSLIM_ERR("btslimrx channels are missing in dt using default values");
+	}
+
+	prop = of_get_property(np, "qcom,btslimtx-channels", &len);
+	if (prop) {
+		/* Check if we need any protection for index */
+		tx_chs[0].ch = (uint8_t)be32_to_cpup(&prop[0]);
+		tx_chs[1].ch = (uint8_t)be32_to_cpup(&prop[1]);
+		BTFMSLIM_DBG("Tx: id\tname\tport\tch");
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x", tx_chs[0].id,
+				tx_chs[0].name, tx_chs[0].port,
+				tx_chs[0].ch);
+		BTFMSLIM_DBG("    %d\t%s\t%d\t%x", tx_chs[1].id,
+				tx_chs[1].name, tx_chs[1].port,
+				tx_chs[1].ch);
+	} else {
+		BTFMSLIM_ERR("btslimtx channels are missing in dt using default values");
+	}
+
+}
+#endif
+static int btfm_slim_status(struct slim_device *sdev,
+				enum slim_device_status status)
+{
+	int ret = 0;
+	struct device *dev = &sdev->dev;
+	struct btfmslim *btfm_slim;
+	btfm_slim = dev_get_drvdata(dev);
+
+#if IS_ENABLED(CONFIG_BTFM_SLIM)
+	if (!is_registered) {
+		ret = btfm_slim_register_codec(btfm_slim);
+	}
+#else
+	if (!is_registered) {
+		btfm_slim_get_hwep_details(sdev, btfm_slim);
+		ret = btfm_slim_register_hw_ep(btfm_slim);
+	}
+#endif
+	if (!ret)
+		is_registered = true;
+	else
+		BTFMSLIM_ERR("error, registering slimbus codec failed");
+
+	return ret;
+}
+
+static long btfm_slim_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+
+	switch (cmd) {
+	case BT_CMD_SLIM_TEST:
+		BTFMSLIM_INFO("cmd BT_CMD_SLIM_TEST, call btfm_slim_hw_init");
+		ret = btfm_slim_hw_init(btfm_slim_drv_data);
+		break;
+	}
+	return ret;
+}
+
+static const struct file_operations bt_dev_fops = {
+	.unlocked_ioctl = btfm_slim_ioctl,
+	.compat_ioctl = btfm_slim_ioctl,
+};
+
+static int btfm_slim_probe(struct slim_device *slim)
+{
+	int ret = 0;
+	struct btfmslim *btfm_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;
+	is_registered = false;
+
+	dev_set_name(&slim->dev, "%s", BTFMSLIM_DEV_NAME);
+	pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
+
+	BTFMSLIM_DBG("");
+	BTFMSLIM_ERR("is_laddr_valid is true");
+	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);
+	dev_set_drvdata(&slim->dev, btfm_slim);
+
+	/* Driver specific data allocation */
+	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;
+	}
+
+	btfm_slim_drv_data = btfm_slim;
+	btfm_slim_major = register_chrdev(0, "btfm_slim", &bt_dev_fops);
+	if (btfm_slim_major < 0) {
+		BTFMSLIM_ERR("%s: failed to allocate char dev\n", __func__);
+		ret = -1;
+		goto register_err;
+	}
+
+	btfm_slim_class = class_create(THIS_MODULE, "btfmslim-dev");
+	if (IS_ERR(btfm_slim_class)) {
+		BTFMSLIM_ERR("%s: coudn't create class\n", __func__);
+		ret = -1;
+		goto class_err;
+	}
+
+	if (device_create(btfm_slim_class, NULL, MKDEV(btfm_slim_major, 0),
+		NULL, "btfmslim") == NULL) {
+		BTFMSLIM_ERR("%s: failed to allocate char dev\n", __func__);
+		ret = -1;
+		goto device_err;
+	}
+	return ret;
+
+device_err:
+	class_destroy(btfm_slim_class);
+class_err:
+	unregister_chrdev(btfm_slim_major, "btfm_slim");
+register_err:
+#if IS_ENABLED(CONFIG_BTFM_SLIM)
+	btfm_slim_unregister_codec(&slim->dev);
+#else
+	btfm_slim_unregister_hwep();
+#endif
+dealloc:
+	mutex_destroy(&btfm_slim->io_lock);
+	mutex_destroy(&btfm_slim->xfer_lock);
+	kfree(btfm_slim);
+	return ret;
+}
+
+static void btfm_slim_remove(struct slim_device *slim)
+{
+	struct device *dev = &slim->dev;
+	struct btfmslim *btfm_slim = dev_get_drvdata(dev);
+	BTFMSLIM_DBG("");
+	mutex_destroy(&btfm_slim->io_lock);
+	mutex_destroy(&btfm_slim->xfer_lock);
+	snd_soc_unregister_component(&slim->dev);
+	kfree(btfm_slim);
+}
+
+static const struct slim_device_id btfm_slim_id[] = {
+	{
+	.manf_id = SLIM_MANF_ID_QCOM,
+	.prod_code = SLIM_PROD_CODE,
+	.dev_index = 0x1,
+	.instance = 0x0,
+	},
+	{
+	.manf_id = SLIM_MANF_ID_QCOM,
+	.prod_code = 0x220,
+	.dev_index = 0x1,
+	.instance = 0x0,
+	}
+};
+
+MODULE_DEVICE_TABLE(slim, btfm_slim_id);
+
+static struct slim_driver btfm_slim_driver = {
+	.driver = {
+		.name = "btfmslim-driver",
+		.owner = THIS_MODULE,
+	},
+	.probe = btfm_slim_probe,
+	.device_status = btfm_slim_status,
+	.remove = btfm_slim_remove,
+	.id_table = btfm_slim_id
+};
+
+module_slim_driver(btfm_slim_driver);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BTFM Slimbus Slave driver");

+ 176 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim.h

@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef BTFM_SLIM_H
+#define BTFM_SLIM_H
+#include <linux/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
+
+#if IS_ENABLED(CONFIG_BTFM_SLIM)
+#define BTFMSLIM_DEV_NAME "btfmslim_slave"
+#else
+#define BTFMSLIM_DEV_NAME "btfmslim"
+#endif
+
+/* 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
+};
+
+struct btfm_slim_codec_dai_data {
+	struct slim_stream_config sconfig;
+	struct slim_stream_runtime *sruntime;
+};
+
+struct btfmslim_ch {
+	int id;
+	char *name;
+	uint16_t port;		/* slimbus port number */
+	uint8_t ch;		/* slimbus channel number */
+	struct btfm_slim_codec_dai_data dai;
+};
+
+/* Slimbus Port defines - This should be redefined in specific device file */
+#define BTFM_SLIM_PGD_PORT_LAST				0xFF
+
+struct btfmslim {
+	struct device *dev;
+	struct slim_device *slim_pgd; //Physical address
+	struct slim_device slim_ifd; //Interface address
+	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;
+	uint32_t bps;
+	uint16_t direction;
+	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);
+#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC)
+	int device_id;
+#endif
+};
+
+extern int btfm_feedback_ch_setting;
+
+/**
+ * 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
+ * @reg_val: value to write at register address
+ * @pgd: selection for device: either PGD or IFD
+ * Returns:
+   No of bytes written
+   -1
+ */
+int btfm_slim_write(struct btfmslim *btfmslim,
+	uint16_t reg, uint8_t reg_val, uint8_t pgd);
+
+
+
+/**
+ * btfm_slim_read: read value from pgd or ifd device
+ * @btfmslim: slimbus slave device data pointer.
+ * @reg: slimbus slave register address
+ * @dest: data pointer to read
+ * @pgd: selection for device: either PGD or IFD
+ * Returns:
+   No of bytes read
+   -1
+ */
+int btfm_slim_read(struct btfmslim *btfmslim,
+	uint32_t reg, 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 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
+ * @nChan: number of chaneels.
+ * Returns:
+ * -EINVAL
+ * -ETIMEDOUT
+ * -ENOMEM
+ */
+int btfm_slim_disable_ch(struct btfmslim *btfmslim,
+	struct btfmslim_ch *ch, uint8_t rxport, uint8_t nchan);
+
+/**
+ * btfm_slim_register_codec: Register codec driver in slimbus device node
+ * @btfmslim: slimbus slave device data pointer.
+ * Returns:
+ * -ENOMEM
+ * 0
+ */
+int btfm_slim_register_codec(struct btfmslim *btfmslim);
+
+/**
+ * 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 */

+ 466 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim_codec.c

@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 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"
+
+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(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	int ret = -1;
+	struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
+
+	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 = snd_soc_component_get_drvdata(dai->component);
+	struct btfmslim_ch *ch;
+	uint8_t rxport, 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:
+		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, 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)
+{
+	struct btfmslim *btfmslim;
+
+	btfmslim = snd_soc_component_get_drvdata(dai->component);
+	btfmslim->bps = params_width(params);
+	btfmslim->direction = substream->stream;
+	BTFMSLIM_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));
+	return 0;
+}
+
+static int btfm_slim_dai_prepare(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	int ret = -EINVAL;
+	int i = 0;
+	struct btfmslim_ch *ch;
+	uint8_t rxport, nchan = 1;
+	struct btfmslim *btfmslim;
+
+	btfmslim = snd_soc_component_get_drvdata(dai->component);
+	btfmslim->direction = substream->stream;
+	bt_soc_enable_status = 0;
+	BTFMSLIM_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d", dai->name,
+		dai->id, dai->rate, btfmslim->direction);
+
+	/* save sample rate */
+	btfmslim->sample_rate = dai->rate;
+
+	switch (dai->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("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, 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(struct snd_soc_dai *dai,
+				unsigned int tx_num, unsigned int *tx_slot,
+				unsigned int rx_num, unsigned int *rx_slot)
+{
+	int ret = 0, i;
+	struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
+	struct btfmslim_ch *rx_chs;
+	struct btfmslim_ch *tx_chs;
+
+	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%d\t", 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%d\t", tx_chs->id,
+			tx_chs->name, tx_chs->port, tx_chs->ch);
+	}
+
+	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 = snd_soc_component_get_drvdata(dai->component);
+	struct btfmslim_ch *ch = NULL;
+
+	if (!btfmslim)
+		return ret;
+
+	switch (dai->id) {
+	case BTFM_FM_SLIM_TX:
+		num = 2;
+		/* fall through */
+		fallthrough;
+	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 -> lpass */
+		.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_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 = &btfmslim_dai_ops,
+	},
+	{	/* Bluetooth SCO voice downlink: lpass -> 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
+				| 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 = &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 btfmslim *btfm_slim)
+{
+	int ret = 0;
+	struct device *dev = btfm_slim->dev;
+
+	BTFMSLIM_DBG("");
+	dev_err(dev, "\n");
+
+	/* 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");

+ 546 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.c

@@ -0,0 +1,546 @@
+// 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 uint8_t usecase_codec;
+
+static int btfm_slim_hwep_write(struct snd_soc_component *codec,
+			unsigned int reg, unsigned int value)
+{
+	BTFMSLIM_DBG("");
+	return 0;
+}
+
+static unsigned int btfm_slim_hwep_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 int btfm_get_codec_type(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSLIM_DBG("current codec type:%s", codec_text[usecase_codec]);
+	ucontrol->value.integer.value[0] = usecase_codec;
+	return 1;
+}
+
+static int btfm_put_codec_type(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	usecase_codec = ucontrol->value.integer.value[0];
+	BTFMSLIM_DBG("codec type set to:%s", codec_text[usecase_codec]);
+	return 1;
+}
+static 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),
+	SOC_ENUM_EXT("BT codec type", codec_display,
+	btfm_get_codec_type, btfm_put_codec_type),
+};
+
+
+static int btfm_slim_hwep_probe(struct snd_soc_component *codec)
+{
+	BTFMSLIM_DBG("");
+	return 0;
+}
+
+static void btfm_slim_hwep_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,
+				   uint8_t num_channels) {
+	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;
+}
+
+void btfm_get_sampling_rate(uint32_t *sampling_rate)
+{
+	uint8_t codec_types_avb = ARRAY_SIZE(codec_text);
+	if (usecase_codec > (codec_types_avb - 1)) {
+		BTFMSLIM_ERR("falling back to use default sampling_rate: %u",
+				*sampling_rate);
+		return;
+	}
+
+	if (*sampling_rate == 44100 || *sampling_rate == 48000) {
+		if (usecase_codec == LDAC ||
+		    usecase_codec == APTX_AD)
+			*sampling_rate = (*sampling_rate) *2;
+	}
+
+	if (usecase_codec == LC3_VOICE ||
+	    usecase_codec == APTX_AD_SPEECH ||
+	    usecase_codec == LC3 || usecase_codec == APTX_AD_R4) {
+		*sampling_rate = 96000;
+	}
+
+	if (usecase_codec == APTX_AD_QLEA)
+		*sampling_rate = 192000;
+
+	BTFMSLIM_INFO("current usecase codec type %s and sampling rate:%u khz",
+			codec_text[usecase_codec], *sampling_rate);
+}
+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;
+
+	btfm_get_sampling_rate(&sampling_rate);
+	/* 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", 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", 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;
+		fallthrough;
+	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;
+}
+
+int btfm_slim_dai_get_configs(void *dai, void *config, uint8_t id)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
+	struct master_hwep_configurations *hwep_config;
+	struct btfmslim_ch *ch = NULL;
+	int i = 0;
+
+	BTFMSLIM_DBG("");
+
+	hwep_config = (struct master_hwep_configurations *) config;
+	hwep_config->stream_id = id;
+	hwep_config->device_id = btfmslim->device_id;
+	hwep_config->sample_rate = btfmslim->sample_rate;
+	hwep_config->bit_width = (uint8_t)btfmslim->bps;
+	hwep_config->codectype = usecase_codec;
+	hwep_config->direction = btfmslim->direction;
+
+	switch (id) {
+		case BTFM_FM_SLIM_TX:
+		case BTFM_BT_SCO_SLIM_TX:
+			ch = btfmslim->tx_chs;
+			break;
+		case BTFM_BT_SCO_A2DP_SLIM_RX:
+		case BTFM_BT_SPLIT_A2DP_SLIM_RX:
+			ch = btfmslim->rx_chs;
+			break;
+	}
+
+	for (; i < id ; i++) {
+		if (ch[i].id == id) {
+			BTFMSLIM_DBG("id matched");
+			hwep_config->num_channels = 1;
+			hwep_config->chan_num = ch[i].ch;
+			break;
+		}
+	}
+
+	return 1;
+}
+
+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,
+	.hwep_get_configs = btfm_slim_dai_get_configs,
+	.hwep_codectype = &usecase_codec,
+};
+
+static struct hwep_dai_driver btfmslim_dai_driver[] = {
+	{	/* Bluetooth SCO voice uplink: bt -> lpass */
+		.dai_name = "btaudio_tx",
+		.id = BTAUDIO_TX,
+		.capture = {
+			.stream_name = "BT Audio Slim 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 = "btaudio_rx",
+		.id = BTAUDIO_RX,
+		.playback = {
+			.stream_name = "BT Audio Slim 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_hwep_probe,
+	.hwep_remove	= btfm_slim_hwep_remove,
+	.hwep_read	= btfm_slim_hwep_read,
+	.hwep_write	= btfm_slim_hwep_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;
+	hwep_info->num_mixer_ctrl = ARRAY_SIZE(status_controls);
+	hwep_info->mixer_ctrl = status_controls;
+	/* 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");

+ 43 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.h

@@ -0,0 +1,43 @@
+// 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
+
+// Todo protect with flags
+int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim);
+void btfm_slim_unregister_hwep(void);
+
+/* Codec driver defines */
+enum {
+	BTAUDIO_TX = 1,
+	BTAUDIO_RX = 2,
+	BTAUDIO_NUM_CODEC_DAIS
+};
+
+typedef enum Codec {
+	SBC = 0,
+	AAC,
+	LDAC,
+	APTX,
+	APTX_HD,
+	APTX_AD,
+	LC3,
+	APTX_AD_SPEECH,
+	LC3_VOICE,
+	APTX_AD_QLEA,
+	APTX_AD_R4,
+	NO_CODEC
+} codectype;
+
+static char const *codec_text[] = {"CODEC_TYPE_SBC", "CODEC_TYPE_AAC",
+				   "CODEC_TYPE_LDAC", "CODEC_TYPE_APTX",
+				   "CODEC_TYPE_APTX_HD", "CODEC_TYPE_APTX_AD",
+				   "CODEC_TYPE_LC3", "CODEC_TYPE_APTX_AD_SPEECH",
+				   "CODEC_TYPE_LC3_VOICE", "CODEC_TYPE_APTX_AD_QLEA",
+				   "CODEC_TYPE_APTX_AD_R4","CODEC_TYPE_INVALID"};
+
+static SOC_ENUM_SINGLE_EXT_DECL(codec_display, codec_text);
+#endif /*__LINUX_BTFM_SLIM_HW_INTERFACE_H*/

+ 187 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.c

@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/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_BT_SCO_SLIM_TX, .name = "SCO_Tx",
+	.port = SLAVE_SB_PGD_PORT_TX_SCO},
+	{.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_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;
+	uint32_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, IFD);
+	if (ret < 0)
+		BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
+
+	BTFMSLIM_DBG("Major Rev: 0x%x, Minor Rev: 0x%x",
+		(ret & 0xF0) >> 4, (ret & 0x0F));
+
+	/* Get SB_SLAVE_HW_REV_LSB value*/
+	reg = SLAVE_SB_SLAVE_HW_REV_LSB;
+	ret = btfm_slim_read(btfmslim, reg, IFD);
+	if (ret < 0)
+		BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
+	else {
+		BTFMSLIM_INFO("read (%d) reg 0x%x", ret, reg);
+		ret = 0;
+	}
+	return ret;
+}
+
+static inline int is_fm_port(uint8_t port_num)
+{
+	if (port_num == SLAVE_SB_PGD_PORT_TX1_FM ||
+		port_num == CHRKVER3_SB_PGD_PORT_TX1_FM ||
+		port_num == CHRKVER3_SB_PGD_PORT_TX2_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, reg_val, IFD);
+			if (ret < 0) {
+				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)) {
+		if (port_num == CHRKVER3_SB_PGD_PORT_TX1_FM)
+			reg_val = (0x1 << CHRKVER3_SB_PGD_PORT_TX1_FM);
+		else if (port_num == CHRKVER3_SB_PGD_PORT_TX2_FM)
+			reg_val = (0x1 << CHRKVER3_SB_PGD_PORT_TX2_FM);
+		else
+			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);
+		BTFMSLIM_INFO("writing reg_val (%d) to reg(%x)", reg_val, reg);
+		ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
+		if (ret < 0) {
+			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, reg_val, IFD);
+		if (ret < 0) {
+			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, reg_val, IFD);
+	if (ret < 0) {
+		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, reg_val, IFD);
+	if (ret < 0)
+		BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
+
+error:
+	return ret;
+}

+ 187 - 0
qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.h

@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. 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 CHRKVER3_SB_PGD_PORT_TX1_FM			5
+#define CHRKVER3_SB_PGD_PORT_TX2_FM			4
+#define SLAVE_SB_PGD_PORT_RX_SCO			16
+#define SLAVE_SB_PGD_PORT_RX_A2P			17
+
+enum {
+	QCA_CHEROKEE_SOC_ID_0200  = 0x40010200,
+	QCA_CHEROKEE_SOC_ID_0201  = 0x40010201,
+	QCA_CHEROKEE_SOC_ID_0210  = 0x40010214,
+	QCA_CHEROKEE_SOC_ID_0211  = 0x40010224,
+	QCA_CHEROKEE_SOC_ID_0310  = 0x40010310,
+	QCA_CHEROKEE_SOC_ID_0320  = 0x40010320,
+	QCA_CHEROKEE_SOC_ID_0320_UMC  = 0x40014320,
+};
+
+enum {
+	QCA_APACHE_SOC_ID_0100  = 0x40020120,
+	QCA_APACHE_SOC_ID_0110  = 0x40020130,
+	QCA_APACHE_SOC_ID_0120  = 0x40020140,
+	QCA_APACHE_SOC_ID_0121  = 0x40020150,
+};
+
+enum {
+	QCA_COMANCHE_SOC_ID_0101  = 0x40070101,
+	QCA_COMANCHE_SOC_ID_0110  = 0x40070110,
+	QCA_COMANCHE_SOC_ID_0120  = 0x40070120,
+	QCA_COMANCHE_SOC_ID_0130  = 0x40070130,
+	QCA_COMANCHE_SOC_ID_4130  = 0x40074130,
+	QCA_COMANCHE_SOC_ID_5120  = 0x40075120,
+	QCA_COMANCHE_SOC_ID_5130  = 0x40075130,
+};
+
+enum {
+	QCA_HASTINGS_SOC_ID_0200 = 0x400A0200,
+};
+
+enum {
+	QCA_HSP_SOC_ID_0100 = 0x400C0100,
+	QCA_HSP_SOC_ID_0110 = 0x400C0110,
+	QCA_HSP_SOC_ID_0200 = 0x400C0200,
+	QCA_HSP_SOC_ID_0210 = 0x400C0210,
+	QCA_HSP_SOC_ID_1201 = 0x400C1201,
+	QCA_HSP_SOC_ID_1211 = 0x400C1211,
+};
+
+enum {
+	QCA_MOSELLE_SOC_ID_0100 = 0x40140100,
+	QCA_MOSELLE_SOC_ID_0110 = 0x40140110,
+	QCA_MOSELLE_SOC_ID_0120 = 0x40140120,
+};
+
+enum {
+	QCA_HAMILTON_SOC_ID_0100 = 0x40170100,
+	QCA_HAMILTON_SOC_ID_0101 = 0x40170101,
+	QCA_HAMILTON_SOC_ID_0200 = 0x40170200,
+};
+
+/* 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

+ 12 - 0
qcom/opensource/bt-kernel/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
qcom/opensource/bt-kernel/soundwire/Makefile

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

+ 288 - 0
qcom/opensource/bt-kernel/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_hw_interface.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_hw_ep(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_hwep();
+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");
+

+ 86 - 0
qcom/opensource/bt-kernel/soundwire/btfm_swr.h

@@ -0,0 +1,86 @@
+// 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);
+#endif /* BTFM_SWR_H */

+ 442 - 0
qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.c

@@ -0,0 +1,442 @@
+// 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"
+#include "btfm_swr_hw_interface.h"
+#include "btfm_codec_hw_interface.h"
+
+#define LPAIF_AUD     0x05
+
+static int bt_soc_enable_status;
+int btfm_feedback_ch_setting;
+static uint8_t usecase_codec;
+
+static int btfm_swr_hwep_write(struct snd_soc_component *codec,
+			unsigned int reg, unsigned int value)
+{
+	BTFMSWR_DBG("");
+	return 0;
+}
+
+static unsigned int btfm_swr_hwep_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 int btfm_get_codec_type(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	BTFMSWR_DBG("current codec type:%s", codec_text[usecase_codec]);
+	ucontrol->value.integer.value[0] = usecase_codec;
+	return 1;
+}
+
+static int btfm_put_codec_type(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	usecase_codec = ucontrol->value.integer.value[0];
+	BTFMSWR_DBG("codec type set to:%s", codec_text[usecase_codec]);
+	return 1;
+}
+
+static 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),
+	SOC_ENUM_EXT("BT codec type", codec_display,
+	btfm_get_codec_type, btfm_put_codec_type),
+};
+
+
+static int btfm_swr_hwep_probe(struct snd_soc_component *codec)
+{
+	BTFMSWR_DBG("");
+	return 0;
+}
+
+static void btfm_swr_hwep_remove(struct snd_soc_component *codec)
+{
+	BTFMSWR_DBG("");
+}
+
+static int btfm_swr_dai_startup(void *dai)
+{
+	//struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	int ret = -1;
+
+	BTFMSWR_DBG("");
+
+	ret = btfm_swr_hw_init();
+	return ret;
+}
+
+static void btfm_swr_dai_shutdown(void *dai, int id)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
+	int ret = 0;
+	u8 port_type;
+
+	BTFMSWR_INFO("");
+
+	if (btfmswr == NULL || btfmswr->p_dai_port == NULL) {
+		BTFMSWR_INFO("port shutdown might have called with out open\n");
+		return;
+	}
+
+	switch (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", id);
+		return;
+	}
+
+	ret = btfm_swr_disable_port(btfmswr->p_dai_port->port_info[id].port,
+				    btfmswr->num_channels, port_type);
+}
+
+static int btfm_swr_dai_hw_params(void *dai, uint32_t bps,
+				   uint32_t direction, uint8_t num_channels)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
+
+	BTFMSWR_DBG("");
+	btfmswr->bps = bps;
+	btfmswr->direction = direction;
+	btfmswr->num_channels = num_channels;
+
+	return 0;
+}
+
+void btfm_get_sampling_rate(uint32_t *sampling_rate)
+{
+	uint8_t codec_types_avb = ARRAY_SIZE(codec_text);
+
+	if (usecase_codec > (codec_types_avb - 1)) {
+		BTFMSWR_ERR("falling back to use default sampling_rate: %u",
+				*sampling_rate);
+		return;
+	}
+
+	if (*sampling_rate == 44100 || *sampling_rate == 48000) {
+		if (usecase_codec == LDAC ||
+		    usecase_codec == APTX_AD)
+			*sampling_rate = (*sampling_rate) * 2;
+	}
+
+	if (usecase_codec == LC3_VOICE ||
+	    usecase_codec == APTX_AD_SPEECH ||
+	    usecase_codec == LC3 || usecase_codec == APTX_AD_QLEA ||
+	    usecase_codec == APTX_AD_R4) {
+		*sampling_rate = 96000;
+	}
+
+	BTFMSWR_INFO("current usecase codec type %s and sampling rate:%u khz",
+			codec_text[usecase_codec], *sampling_rate);
+}
+
+static int btfm_swr_dai_prepare(void *dai, uint32_t sampling_rate, uint32_t direction, int id)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
+	int ret = -EINVAL;
+	u8 port_type;
+
+	bt_soc_enable_status = 0;
+	BTFMSWR_INFO("dai->id: %d, dai->rate: %d direction: %d", id, sampling_rate, direction);
+
+	btfm_get_sampling_rate(&sampling_rate);
+	btfmswr->sample_rate = sampling_rate;
+
+	switch (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", id);
+		return -EINVAL;
+	}
+
+	ret = btfm_swr_enable_port(btfmswr->p_dai_port->port_info[id].port,
+				   btfmswr->num_channels, sampling_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(void *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(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 btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
+
+	*rx_slot = 0;
+	*tx_slot = 0;
+	*rx_num = 0;
+	*tx_num = 0;
+
+	switch (id) {
+	case FMAUDIO_TX:
+	case BTAUDIO_TX:
+	case BTAUDIO_A2DP_SINK_TX:
+		*tx_num = btfmswr->num_channels;
+		*tx_slot = btfmswr->num_channels == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK;
+		break;
+	case BTAUDIO_RX:
+		*rx_num = btfmswr->num_channels;
+		*rx_slot = btfmswr->num_channels == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK;
+		break;
+
+	default:
+		BTFMSWR_ERR("Unsupported DAI %d", id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int btfm_swr_dai_get_configs(void *dai, void *config, uint8_t id)
+{
+	struct hwep_data *hwep_info = (struct hwep_data *)dai;
+	struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
+	struct hwep_dma_configurations *hwep_config;
+
+	BTFMSWR_DBG("");
+	hwep_config = (struct hwep_dma_configurations *)config;
+
+	hwep_config->stream_id = id;
+	hwep_config->sample_rate = btfmswr->sample_rate;
+	hwep_config->bit_width = (uint8_t)btfmswr->bps;
+	hwep_config->codectype = usecase_codec;
+
+	hwep_config->num_channels = btfmswr->num_channels;
+	hwep_config->active_channel_mask = (btfmswr->num_channels == 2 ?
+					   TWO_CHANNEL_MASK : ONE_CHANNEL_MASK);
+	hwep_config->lpaif = LPAIF_AUD;
+	hwep_config->inf_index = 1;
+	return 1;
+}
+
+static struct hwep_dai_ops  btfmswr_hw_dai_ops = {
+	.hwep_startup = btfm_swr_dai_startup,
+	.hwep_shutdown = btfm_swr_dai_shutdown,
+	.hwep_hw_params = btfm_swr_dai_hw_params,
+	.hwep_prepare = btfm_swr_dai_prepare,
+	.hwep_set_channel_map = btfm_swr_dai_set_channel_map,
+	.hwep_get_channel_map = btfm_swr_dai_get_channel_map,
+	.hwep_get_configs = btfm_swr_dai_get_configs,
+	.hwep_codectype = &usecase_codec,
+};
+
+static struct hwep_dai_driver btfmswr_dai_driver[] = {
+	{	/* FM Audio data multiple channel  : FM -> lpass */
+		.dai_name = "btaudio_fm_tx",
+		.id = FMAUDIO_TX,
+		.capture = {
+			.stream_name = "FM SWR 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,
+		},
+		.dai_ops = &btfmswr_hw_dai_ops,
+	},
+	{	/* Bluetooth SCO voice uplink: bt -> lpass */
+		.dai_name = "btaudio_tx",
+		.id = BTAUDIO_TX,
+		.capture = {
+			.stream_name = "BT Audio SWR 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 = &btfmswr_hw_dai_ops,
+	},
+	{	/* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
+		.dai_name = "btaudio_rx",
+		.id = BTAUDIO_RX,
+		.playback = {
+			.stream_name = "BT Audio SWR 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 = &btfmswr_hw_dai_ops,
+	},
+	{	/* Bluetooth A2DP sink: bt -> lpass */
+		.dai_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,
+		},
+		.dai_ops = &btfmswr_hw_dai_ops,
+	}
+};
+
+static struct hwep_comp_drv btfmswr_hw_driver = {
+	.hwep_probe	= btfm_swr_hwep_probe,
+	.hwep_remove	= btfm_swr_hwep_remove,
+	.hwep_read	= btfm_swr_hwep_read,
+	.hwep_write	= btfm_swr_hwep_write,
+};
+
+int btfm_swr_register_hw_ep(struct btfmswr *btfm_swr)
+{
+	struct device *dev = btfm_swr->dev;
+	struct hwep_data *hwep_info;
+	int ret = 0;
+
+	BTFMSWR_INFO("Registering with BTFMCODEC HWEP interface\n");
+	hwep_info = kzalloc(sizeof(struct hwep_data), GFP_KERNEL);
+
+	if (!hwep_info) {
+		BTFMSWR_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;
+	strscpy(hwep_info->driver_name, SWR_SLAVE_COMPATIBLE_STR, DEVICE_NAME_MAX_LEN);
+	hwep_info->drv = &btfmswr_hw_driver;
+	hwep_info->dai_drv = btfmswr_dai_driver;
+	hwep_info->num_dai = ARRAY_SIZE(btfmswr_dai_driver);
+	hwep_info->num_dai = 4;
+	hwep_info->num_mixer_ctrl = ARRAY_SIZE(status_controls);
+	hwep_info->mixer_ctrl = status_controls;
+	/* Register to hardware endpoint */
+	ret = btfmcodec_register_hw_ep(hwep_info);
+	if (ret) {
+		BTFMSWR_ERR("failed to register with btfmcodec driver hw interface (%d)", ret);
+		goto end;
+	}
+
+	BTFMSWR_INFO("Registered succesfull with BTFMCODEC HWEP interface\n");
+	return ret;
+end:
+	return ret;
+}
+
+void btfm_swr_unregister_hwep(void)
+{
+	BTFMSWR_INFO("Unregistered with BTFMCODEC HWEP	interface");
+	/* Unregister with BTFMCODEC HWEP	driver */
+	btfmcodec_unregister_hw_ep(SWR_SLAVE_COMPATIBLE_STR);
+
+}
+
+MODULE_DESCRIPTION("BTFM SoundWire Codec driver");
+MODULE_LICENSE("GPL v2");

+ 35 - 0
qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.h

@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef __LINUX_BTFM_SWR_HW_INTERFACE_H
+#define __LINUX_BTFM_SWR_HW_INTERFACE_H
+
+int btfm_swr_register_hw_ep(struct btfmswr *a);
+void btfm_swr_unregister_hwep(void);
+
+enum Codec {
+	SBC = 0,
+	AAC,
+	LDAC,
+	APTX,
+	APTX_HD,
+	APTX_AD,
+	LC3,
+	APTX_AD_SPEECH,
+	LC3_VOICE,
+	APTX_AD_QLEA,
+	APTX_AD_R4,
+	NO_CODEC
+};
+
+static const char * const codec_text[] = {"CODEC_TYPE_SBC", "CODEC_TYPE_AAC",
+				   "CODEC_TYPE_LDAC", "CODEC_TYPE_APTX",
+				   "CODEC_TYPE_APTX_HD", "CODEC_TYPE_APTX_AD",
+				   "CODEC_TYPE_LC3", "CODEC_TYPE_APTX_AD_SPEECH",
+				   "CODEC_TYPE_LC3_VOICE", "CODEC_TYPE_APTX_AD_QLEA",
+				   "CODEC_TYPE_APTX_AD_R4", "CODEC_TYPE_INVALID"};
+
+static SOC_ENUM_SINGLE_EXT_DECL(codec_display, codec_text);
+#endif /*__LINUX_BTFM_SWR_HW_INTERFACE_H*/

+ 44 - 0
qcom/opensource/bt-kernel/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
qcom/opensource/bt-kernel/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

+ 83 - 0
qcom/opensource/bt-kernel/target.bzl

@@ -0,0 +1,83 @@
+load(":bt_kernel.bzl", "define_bt_modules")
+
+def define_pineapple():
+    define_bt_modules(
+        target = "pineapple",
+        modules = [
+            "btpower",
+            "bt_fm_slim",
+            "radio-i2c-rtc6226-qca",
+            # "btfm_slim_codec",
+        ],
+        config_options = [
+            "CONFIG_MSM_BT_POWER",
+            "CONFIG_BTFM_SLIM",
+            "CONFIG_I2C_RTC6226_QCA",
+            # "CONFIG_SLIM_BTFM_CODEC",
+            "CONFIG_BT_HW_SECURE_DISABLE",
+        ]
+    )
+
+def define_blair():
+    define_bt_modules(
+        target = "blair",
+        modules = [
+            "btpower",
+            "bt_fm_slim",
+            "radio-i2c-rtc6226-qca",
+        ],
+        config_options = [
+            "CONFIG_MSM_BT_POWER",
+            "CONFIG_BTFM_SLIM",
+            "CONFIG_I2C_RTC6226_QCA",
+            "CONFIG_BT_HW_SECURE_DISABLE",
+        ]
+    )
+
+def define_pitti():
+    define_bt_modules(
+	target = "pitti",
+	modules = [
+	    "btpower",
+	    "bt_fm_slim",
+	    "radio-i2c-rtc6226-qca",
+	],
+	config_options = [
+	    "CONFIG_MSM_BT_POWER",
+	    "CONFIG_BTFM_SLIM",
+	    "CONFIG_I2C_RTC6226_QCA",
+	    "CONFIG_BT_HW_SECURE_DISABLE",
+	]
+   )
+
+def define_niobe():
+    define_bt_modules(
+	target = "niobe",
+	modules = [
+	    "btpower",
+	    "bt_fm_slim",
+	    "radio-i2c-rtc6226-qca",
+	],
+	config_options = [
+	    "CONFIG_MSM_BT_POWER",
+	    "CONFIG_BTFM_SLIM",
+	    "CONFIG_I2C_RTC6226_QCA",
+	    "CONFIG_BT_HW_SECURE_DISABLE",
+	]
+   )
+
+def define_anorak61():
+    define_bt_modules(
+	target = "anorak61",
+	modules = [
+	    "btpower",
+	    "bt_fm_slim",
+	    "radio-i2c-rtc6226-qca",
+	],
+	config_options = [
+	    "CONFIG_MSM_BT_POWER",
+	    "CONFIG_BTFM_SLIM",
+	    "CONFIG_I2C_RTC6226_QCA",
+	    "CONFIG_BT_HW_SECURE_DISABLE",
+	]
+   )