diff --git a/qcom/opensource/bt-kernel/Android.mk b/qcom/opensource/bt-kernel/Android.mk new file mode 100644 index 0000000000..d57b76a6ae --- /dev/null +++ b/qcom/opensource/bt-kernel/Android.mk @@ -0,0 +1,162 @@ +# Android makefile for BT kernel modules + +BT_DLKM_ENABLE := true +ifeq ($(TARGET_KERNEL_DLKM_DISABLE), true) + ifeq ($(TARGET_KERNEL_DLKM_BT_OVERRIDE), false) + BT_DLKM_ENABLE := false + endif +endif + +ifeq ($(BT_DLKM_ENABLE), true) + +LOCAL_PATH := $(call my-dir) + +# Build/Package only in case of supported target +ifeq ($(call is-board-platform-in-list, taro kalama pineapple blair sun parrot), true) + +BT_SELECT := CONFIG_MSM_BT_POWER=m +BT_SELECT += CONFIG_I2C_RTC6226_QCA=m + +ifneq ($(call is-board-platform-in-list, parrot), true) +BT_SELECT += CONFIG_FMD_ENABLE=y +endif + +ifeq ($(TARGET_KERNEL_DLKM_SECUREMSM_QTEE_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 += rtc6226/radio-i2c-rtc6226-qca.ko + +ifeq ($(TARGET_BOARD_PLATFORM), sun) +BT_SELECT += CONFIG_BTFM_CODEC=m +BT_SELECT += CONFIG_BTFM_SWR=m +BT_SELECT += CONFIG_SLIM_BTFM_CODEC=m +LOCAL_MODULE_KO_DIRS += btfmcodec/btfmcodec.ko +LOCAL_MODULE_KO_DIRS += slimbus/btfm_slim_codec.ko +LOCAL_MODULE_KO_DIRS += soundwire/bt_fm_swr.ko +else +BT_SELECT += CONFIG_BTFM_SLIM=m +LOCAL_MODULE_KO_DIRS += slimbus/bt_fm_slim.ko +endif + +# 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_SECUREMSM_QTEE_OVERRIDE), true) +ifeq ($(ENABLE_PERIPHERAL_STATE_UTILS), true) +KBUILD_REQUIRED_KOS := smcinvoke_dlkm.ko +endif +endif + + +ifeq ($(TARGET_BOARD_PLATFORM), sun) +KBUILD_REQUIRED_KOS += swr_dlkm.ko +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 + +ifeq ($(call is-board-platform-in-list, sun), true) +################################ BTFM CODEC Driver ######################### +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(BT_SRC_FILES) +LOCAL_MODULE := btfmcodec.ko +LOCAL_MODULE_KBUILD_NAME := btfmcodec/btfmcodec.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/Build_external_kernelmodule.mk +############################ slimbus with BTFM CODEC Driver ################# +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(BT_SRC_FILES) +LOCAL_MODULE := btfm_slim_codec.ko +LOCAL_MODULE_KBUILD_NAME := slimbus/btfm_slim_codec.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/Build_external_kernelmodule.mk +################################ soundwire ################################ +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(BT_SRC_FILES) +LOCAL_MODULE := bt_fm_swr.ko +LOCAL_MODULE_KBUILD_NAME := soundwire/bt_fm_swr.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +#KBUILD_OPTIONS += KBUILD_EXTRA_SYMBOLS+=$(call intermediates-dir-for,DLKM,swr_dlkm)/Module.symvers +#LOCAL_REQUIRED_MODULES := swr_dlkm +#LOCAL_ADDITIONAL_DEPENDENCIES += $(call intermediates-dir-for,DLKM,swr_dlkm)/Module.symvers +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/Build_external_kernelmodule.mk +else +################################ 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 +endif +################################ 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 +endif # TARGET_KERNEL_DLKM_BT_OVERRIDE diff --git a/qcom/opensource/bt-kernel/BUILD.bazel b/qcom/opensource/bt-kernel/BUILD.bazel new file mode 100644 index 0000000000..e3726b230b --- /dev/null +++ b/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") +load(":target.bzl", "define_sun") +load(":target.bzl", "define_parrot") + +define_pineapple() +define_sun() +define_parrot() diff --git a/qcom/opensource/bt-kernel/Kbuild b/qcom/opensource/bt-kernel/Kbuild new file mode 100644 index 0000000000..f46e7da8f1 --- /dev/null +++ b/qcom/opensource/bt-kernel/Kbuild @@ -0,0 +1,34 @@ +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 + +ifeq ($(CONFIG_BTFM_SWR),m) +KBUILD_CPPFLAGS += -DCONFIG_BTFM_SWR +endif + +ifeq ($(CONFIG_FMD_ENABLE), y) +KBUILD_CPPFLAGS += -DCONFIG_FMD_ENABLE +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/ +obj-$(CONFIG_BTFM_SWR) += soundwire/ diff --git a/qcom/opensource/bt-kernel/Makefile b/qcom/opensource/bt-kernel/Makefile new file mode 100644 index 0000000000..de0aa3f278 --- /dev/null +++ b/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 diff --git a/qcom/opensource/bt-kernel/bt_kernel.bzl b/qcom/opensource/bt-kernel/bt_kernel.bzl new file mode 100644 index 0000000000..d1905913e9 --- /dev/null +++ b/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) diff --git a/qcom/opensource/bt-kernel/bt_kernel_product_board.mk b/qcom/opensource/bt-kernel/bt_kernel_product_board.mk new file mode 100644 index 0000000000..b364824494 --- /dev/null +++ b/qcom/opensource/bt-kernel/bt_kernel_product_board.mk @@ -0,0 +1,10 @@ +# Build BT kernel drivers +PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/btpower.ko +PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/radio-i2c-rtc6226-qca.ko +ifeq ($(TARGET_BOARD_PLATFORM), sun) +PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/btfmcodec.ko +PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/btfm_slim_codec.ko +PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/bt_fm_swr.ko +else +BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/bt_fm_slim.ko +endif diff --git a/qcom/opensource/bt-kernel/bt_kernel_vendor_board.mk b/qcom/opensource/bt-kernel/bt_kernel_vendor_board.mk new file mode 100644 index 0000000000..4c39c810ea --- /dev/null +++ b/qcom/opensource/bt-kernel/bt_kernel_vendor_board.mk @@ -0,0 +1,32 @@ +# 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 + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/radio-i2c-rtc6226-qca.ko + ifeq ($(TARGET_BOARD_PLATFORM), sun) + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/btfmcodec.ko + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/btfm_slim_codec.ko + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/bt_fm_swr.ko + else + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/bt_fm_slim.ko + endif + 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 + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/radio-i2c-rtc6226-qca.ko + ifeq ($(TARGET_BOARD_PLATFORM), sun) + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/btfmcodec.ko + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/btfm_slim_codec.ko + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/bt_fm_swr.ko + else + BT_KERNEL_DRIVER += $(KERNEL_MODULES_OUT)/bt_fm_slim.ko + endif + BOARD_VENDOR_KERNEL_MODULES += $(BT_KERNEL_DRIVER) + endif +endif +endif diff --git a/qcom/opensource/bt-kernel/bt_modules.bzl b/qcom/opensource/bt-kernel/bt_modules.bzl new file mode 100644 index 0000000000..3c35e84d56 --- /dev/null +++ b/qcom/opensource/bt-kernel/bt_modules.bzl @@ -0,0 +1,140 @@ +PWR_PATH = "pwr" +SLIMBUS_PATH = "slimbus" +FMRTC_PATH = "rtc6226" +BTFMCODEC_PATH = "btfmcodec" +SWR_PATH = "soundwire" + +# 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": [ ":smcinvoke_kernel_headers", + "//vendor/qcom/opensource/securemsm-kernel:%b_smcinvoke_dlkm", + ] + }, + deps = [ + "//vendor/qcom/opensource/wlan/platform:all-wlan-platform-headers", + "//vendor/qcom/opensource/wlan/platform:%b_cnss_utils", + ], +) + +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", + ], +) + +register_bt_modules( + name = "bt_fm_swr", + path = SWR_PATH, + config_opt = "CONFIG_BTFM_SWR", + srcs = [ + "btfm_swr.c", + "btfm_swr.h", + "btfm_swr_slave.c", + "btfm_swr_slave.h", + "btfm_swr_hw_interface.c", + "btfm_swr_hw_interface.h", + ], + deps = [ + ":%b_btpower", ":%b_btfmcodec", ":btfmcodec_headers", + "//vendor/qcom/opensource/audio-kernel:%b_swr_dlkm", + "//vendor/qcom/opensource/audio-kernel:audio_headers", + ], +) diff --git a/qcom/opensource/bt-kernel/btfmcodec/Kconfig b/qcom/opensource/bt-kernel/btfmcodec/Kconfig new file mode 100644 index 0000000000..0f47ce1d1a --- /dev/null +++ b/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. diff --git a/qcom/opensource/bt-kernel/btfmcodec/Makefile b/qcom/opensource/bt-kernel/btfmcodec/Makefile new file mode 100644 index 0000000000..9b41a19b17 --- /dev/null +++ b/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 diff --git a/qcom/opensource/bt-kernel/btfmcodec/btfm_codec.c b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec.c new file mode 100644 index 0000000000..ca34ae83ff --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec.c @@ -0,0 +1,801 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "btfm_codec.h" +#include "btfm_codec_pkt.h" +#include "btfm_codec_btadv_interface.h" + +#define dev_to_btfmcodec(_dev) container_of(_dev, struct btfmcodec_data, dev) + +static DEFINE_IDR(dev_minor); +static struct class *dev_class; +static dev_t dev_major; +struct btfmcodec_data *btfmcodec; +struct device_driver driver = {.name = "btfmcodec-driver", .owner = THIS_MODULE}; +struct btfmcodec_char_device *btfmcodec_dev; +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_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); + + BTFMCODEC_INFO("current_state %s prev_state %s", + coverttostring(btfmcodec->states.current_state), + coverttostring(btfmcodec->states.prev_state)); + /* Reset state if they are BTADV_AUDIO_CONNECTED or BTADV_AUDIO_CONNECTED + * as these states should be moved to IDLE in previous iteration. + */ + if (btfmcodec->states.current_state == BTADV_AUDIO_Connected || + btfmcodec->states.current_state == BTADV_AUDIO_Connecting) { + btfmcodec->states.current_state = IDLE; + } + + btfmcodec->states.prev_state = IDLE; + 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); + skb_queue_purge(&btfmcodec_dev->trans_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); + + 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; + struct btfmcodec_state_machine *state = &btfmcodec->states; + uint32_t len; + uint8_t status; + int idx; + uint8_t *bearer_switch_ind, *dma_rsp; + + 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) { + /* Reset bearer switch ind flag */ + bearer_switch_ind = + &btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND]; + *bearer_switch_ind = BTM_WAITING_RSP; + btfmcodec_enqueue_transport(btfmcodec_dev, skb->data[0]); + if (skb->data[0] == NONE && + btfmcodec_get_current_transport(state) == BT_Connecting && + btfmcodec_get_prev_transport(state) == + BTADV_AUDIO_Connected) { + BTFMCODEC_INFO("KP might be awaiting for codec dma rsp"); + idx = BTM_PKT_TYPE_DMA_CONFIG_RSP; + dma_rsp = &btfmcodec_dev->status[idx]; + *dma_rsp = BTM_FAIL_RESP_RECV; + wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]); + } + 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; + case BTM_BTFMCODEC_USECASE_START_RSP: + idx = BTM_PKT_TYPE_USECASE_START_RSP; + if (len == BTM_USECASE_START_RSP_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_USECASE_START_RSP status:%d", + status); + 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; +} + +int btfmcodec_enqueue_transport(struct btfmcodec_char_device *btfmcodec_dev, + uint8_t transport) +{ + struct sk_buff *skb; + + mutex_lock(&btfmcodec_dev->trans_lock); + skb = alloc_skb(1, GFP_ATOMIC); + if (!skb) { + BTFMCODEC_ERR("failed to allocate memory"); + mutex_unlock(&btfmcodec_dev->trans_lock); + return -ENOMEM; + } + + skb_put_data(skb, &transport, 1); + skb_queue_tail(&btfmcodec_dev->trans_rxq, skb); + mutex_unlock(&btfmcodec_dev->trans_lock); + wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_BEARER_SWITCH_IND]); + return 0; +} + +int btfmcodec_dequeue_transport(struct btfmcodec_char_device *btfmcodec_dev) +{ + uint8_t transport = 0xFF; + struct sk_buff *skb; + + mutex_lock(&btfmcodec_dev->trans_lock); + skb = skb_dequeue(&btfmcodec_dev->trans_rxq); + if (!skb) { + mutex_unlock(&btfmcodec_dev->trans_lock); + return transport; + } + transport = skb->data[0]; + skb_pull(skb, 1); + kfree_skb(skb); + mutex_unlock(&btfmcodec_dev->trans_lock); + return transport; +} + +/* + * 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("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); + mutex_init(&btfmcodec_dev->trans_lock); + strscpy(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); + skb_queue_head_init(&btfmcodec_dev->trans_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); diff --git a/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_btadv_interface.c b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_btadv_interface.c new file mode 100644 index 0000000000..7dde23b503 --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_btadv_interface.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2024 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; +} + +btfmcodec_state btfmcodec_get_prev_transport(struct btfmcodec_state_machine *state) +{ + btfmcodec_state prev_state; + + mutex_lock(&state->state_machine_lock); + prev_state = state->prev_state; + mutex_unlock(&state->state_machine_lock); + return prev_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 || + skb_queue_empty(&btfmcodec_dev->trans_rxq) != true), + msecs_to_jiffies(BTM_BEARER_SWITCH_IND_TIMEOUT)); + + if (!skb_queue_empty(&btfmcodec_dev->trans_rxq)) { + BTFMCODEC_INFO("%s: new transport is waiting to process", __func__); + ret = -1; + return ret; + } + + 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"); + if (btfmcodec_get_current_transport(state) == IDLE) { + BTFMCODEC_INFO("state moved to IDLE"); + } else if (current_state == btfmcodec_get_prev_transport(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 (current_state != IDLE && 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 transport = btfmcodec_dequeue_transport(btfmcodec_dev); + + BTFMCODEC_INFO("%s new transport:%d", __func__, transport); + + if (transport == 0xFF) + BTFMCODEC_ERR("invalid transport"); + else + btfmcodec_prepare_bearer(btfmcodec_dev, transport); +} diff --git a/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_hw_interface.c b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_hw_interface.c new file mode 100644 index 0000000000..97d4d7d4ca --- /dev/null +++ b/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); diff --git a/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_interface.c b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_interface.c new file mode 100644 index 0000000000..e181172051 --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/btfm_codec_interface.c @@ -0,0 +1,972 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#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 btfmcodec_port_state_notify(uint8_t port_state); + +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); + kfree(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); + hwep_configs->is_port_opened = 0; + 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_INFO("%s not allowing shutdown as state is IDLE", __func__); + return; + } + + if ((btfmcodec_get_current_transport(state) == BTADV_AUDIO_Connecting && + btfmcodec_get_prev_transport(state) == BT_Connected) || + ((btfmcodec_get_current_transport(state) == BT_Connecting && + btfmcodec_get_prev_transport(state) == BTADV_AUDIO_Connected))) { + BTFMCODEC_INFO("%s: Informing port closure to upper layers", __func__); + btfmcodec_port_state_notify(IDLE); + } + + if (btfmcodec_get_current_transport(state) == BTADV_AUDIO_Connecting && + btfmcodec_get_prev_transport(state) == BT_Connected) { + BTFMCODEC_INFO("%s: closing these ports as graph stopped when CIS is active", + __func__); + 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); + return; + } + + if ((btfmcodec_get_current_transport(state) != IDLE && + btfmcodec_get_current_transport(state) != BT_Connected) || + (btfmcodec_get_current_transport(state) == BTADV_AUDIO_Connecting && + btfmcodec_get_prev_transport(state) != BT_Connected)) { + BTFMCODEC_WARN("Allowing cache retention in current state:%s, prev state: %s", + coverttostring(btfmcodec_get_current_transport(state)), + coverttostring(btfmcodec_get_prev_transport(state))); + return; + } 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, bool seamless) +{ + 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 %ld", __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); + } else { + if (seamless == false) + btfmcodec_set_current_state(state, BT_Connected); + } + } else if (ret == 0 && test_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags)) { + /* Don't send request to cp for fm as it is non cp */ + if (id == 0) + return ret; + ret = btfmcodec_configure_dma(btfmcodec, (uint8_t)id); + if (ret < 0) { + BTFMCODEC_ERR("failed to configure Codec DMA %d", ret); + if (dai_drv && dai_drv->dai_ops && + dai_drv->dai_ops->hwep_shutdown) { + dai_drv->dai_ops->hwep_shutdown((void *)hwep_info, id); + } + } else { + if (seamless == false) + 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, uint8_t stream_id) +{ + struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev; + struct btm_usecase_start_ind ind; + wait_queue_head_t *rsp_wait_q = + &btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_USECASE_START_RSP]; + uint8_t *status = &btfmcodec_dev->status[BTM_PKT_TYPE_USECASE_START_RSP]; + int ret; + + *status = BTM_WAITING_RSP; + ind.opcode = BTM_BTFMCODEC_USECASE_START_REQ; + ind.len = BTM_USECASE_START_IND_LEN; + ind.transport = transport; + ind.stream_id = stream_id; + ret = btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &ind, (ind.len + BTM_HEADER_LEN)); + + if (ret < 0) + return ret; + + BTFMCODEC_INFO("waiting for BTM_BTFMCODEC_USECASE_START_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 BTM_USECASE_START_IND_RSP"); + ret = -MSG_INTERNAL_TIMEOUT; + } else { + if (*status == BTM_RSP_RECV) { + ret = 0; + } else if (*status == BTM_FAIL_RESP_RECV) { + BTFMCODEC_ERR("Rx BTM_USECASE_START_IND_RSP 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; +} + +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))); + ret = btfmcodec_notify_usecase_start(btfmcodec, BTADV, (uint8_t)id); + } else { + ret = btfmcodec_hwep_prepare(btfmcodec, sampling_rate, direction, id, false); +/* 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, true); + if (ret < 0) { + hwep_configs->is_port_opened = 1; + BTFMCODEC_ERR("failed to configure hwep %d", hwep_configs->stream_id); + break; + } else { + hwep_configs->is_port_opened = 1; + } + } + + if (ret < 0) { + list_for_each_entry_safe(hwep_configs, tmp, head, dai_list) { + if (hwep_configs->is_port_opened) { + BTFMCODEC_INFO("shuting down dai id:%d", hwep_configs->stream_id); + ret = btfmcodec_hwep_shutdown(btfmcodec, hwep_configs->stream_id, + true); + hwep_configs->is_port_opened = 0; + if (ret < 0) { + BTFMCODEC_ERR("failed to shutdown master with id %d", + hwep_configs->stream_id); + } + } + } + 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; +} + +static int btfmcodec_port_state_notify(uint8_t port_state) +{ + struct btm_port_state_ind state_ind; + struct btfmcodec_data *btfmcodec; + struct btfmcodec_char_device *btfmcodec_dev; + + BTFMCODEC_WARN("%s: port state = %d", __func__, port_state); + btfmcodec = btfm_get_btfmcodec(); + btfmcodec_dev = btfmcodec->btfmcodec_dev; + state_ind.opcode = BTM_BTFMCODEC_PORT_STATE_IND; + state_ind.len = BTM_PORT_STATE_IND_LEN; + state_ind.port_state = (uint8_t)port_state; + btfmcodec_dev_enqueue_pkt(btfmcodec_dev, &state_ind, + (state_ind.len + BTM_HEADER_LEN)); + 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); + 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); + + 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); +} diff --git a/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec.h b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec.h new file mode 100644 index 0000000000..b15f95fac1 --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec.h @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_BTFM_CODEC_H +#define __LINUX_BTFM_CODEC_H + +#include +#include +#include +#include +#include +#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_USECASE_START_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; + struct mutex trans_lock; + int reuse_minor; + char dev_name[DEVICE_NAME_MAX_LEN]; + struct workqueue_struct *workqueue; + struct sk_buff_head rxq; + struct sk_buff_head trans_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 */ diff --git a/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_btadv_interface.h b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_btadv_interface.h new file mode 100644 index 0000000000..64e583ff11 --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_btadv_interface.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022, 2024 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); +btfmcodec_state btfmcodec_get_prev_transport(struct btfmcodec_state_machine *state); +#endif /* __LINUX_BTFM_CODEC_BTADV_INTERFACE_H */ diff --git a/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_hw_interface.h b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_hw_interface.h new file mode 100644 index 0000000000..3963630730 --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_hw_interface.h @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_BTFM_CODEC_HW_INTERFACE_H +#define __LINUX_BTFM_CODEC_HW_INTERFACE_H + +#include +#include +#include +#include +#include +#include +#include + +/* 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; + uint8_t is_port_opened; + 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 */ diff --git a/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_interface.h b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_interface.h new file mode 100644 index 0000000000..3d652df4b5 --- /dev/null +++ b/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 */ diff --git a/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_pkt.h b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_pkt.h new file mode 100644 index 0000000000..a962ffd59d --- /dev/null +++ b/qcom/opensource/bt-kernel/btfmcodec/include/btfm_codec_pkt.h @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2024 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_BTFMCODEC_PORT_STATE_IND 0x58000005 + +#define BTM_MASTER_CONFIG_REQ_LEN 13 +#define BTM_MASTER_CONFIG_RSP_TIMEOUT 5000 +#define BTM_BEARER_SWITCH_IND_TIMEOUT 25000 +#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_PORT_STATE_IND_LEN 1 + +#define BTM_BTFMCODEC_USECASE_START_REQ 0x58000008 +#define BTM_BTFMCODEC_USECASE_START_RSP 0x58000009 +#define BTM_USECASE_START_IND_LEN 2 +#define BTM_USECASE_START_RSP_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; +} __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; + uint8_t stream_id; +} __packed; + +struct btm_master_shutdown_req { + btm_opcode opcode; + uint32_t len; + uint8_t stream_id; +} __packed; + +struct btm_adsp_state_ind { + btm_opcode opcode; + uint32_t len; + uint32_t action; +} __packed; + +struct btm_port_state_ind { + btm_opcode opcode; + uint32_t len; + uint8_t port_state; +} __packed; + +int btfmcodec_dev_enqueue_pkt(struct btfmcodec_char_device *btfmcodec_dev, void *buf, int len); +bool btfmcodec_is_valid_cache_avb(struct btfmcodec_data *btfmcodec); +int btfmcodec_enqueue_transport(struct btfmcodec_char_device *btfmcodec_dev, uint8_t transport); +int btfmcodec_dequeue_transport(struct btfmcodec_char_device *btfmcodec_dev); +#endif /* __LINUX_BTFM_CODEC_PKT_H*/ diff --git a/qcom/opensource/bt-kernel/include/btpower.h b/qcom/opensource/bt-kernel/include/btpower.h new file mode 100644 index 0000000000..028abf4b02 --- /dev/null +++ b/qcom/opensource/bt-kernel/include/btpower.h @@ -0,0 +1,739 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_BLUETOOTH_POWER_H +#define __LINUX_BLUETOOTH_POWER_H + +#include +#include +#include +#include +#include + +/* + * 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 FmdOperation { + ENABLE_FMD, + DISABLE_FMD, + UPDATE_SOC_VER +}; + +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, +}; + +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"; + } +} + +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 *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 fmdOperationStruct { + unsigned char fmdOperation; + unsigned char socFwVer; + short int rebootStatus; + short int fmdCycles; +}; + +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 */ + bool fmd_mode_set; +}; + +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_fmd_clk_ctrl; /* Bluetooth fmd_clk_ctrl gpio */ + int bt_gpio_debug; /* Bluetooth debug gpio */ + unsigned int wlan_sw_ctrl_gpio; /* Wlan switch control gpio*/ + int bt_gpio_resetb; /* BT RESETB 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 *wlan_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 qmp *qmp; + 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_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; + bool is_fmd_mode_enable; + struct nvmem_cell *nvmem_cell_fmd_set; + struct nvmem_cell *nvmem_cell_fmd_chg_pon; + struct nvmem_cell *nvmem_cell_fmd_cnt2_stop; + u32 fmd_clk_gpio_id; +}; + +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 +#define UWB_GET_SSR_STATE 0xbfe6 +#define BT_CMD_FMD_OPERATION 0xbfb2 + +#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 */ diff --git a/qcom/opensource/bt-kernel/pwr/Kconfig b/qcom/opensource/bt-kernel/pwr/Kconfig new file mode 100644 index 0000000000..87cb3c3298 --- /dev/null +++ b/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. diff --git a/qcom/opensource/bt-kernel/pwr/Makefile b/qcom/opensource/bt-kernel/pwr/Makefile new file mode 100644 index 0000000000..000773c3ad --- /dev/null +++ b/qcom/opensource/bt-kernel/pwr/Makefile @@ -0,0 +1,3 @@ +ccflags-y += -I$(BT_ROOT)/include + +obj-$(CONFIG_MSM_BT_POWER) += btpower.o diff --git a/qcom/opensource/bt-kernel/pwr/btpower.c b/qcom/opensource/bt-kernel/pwr/btpower.c new file mode 100644 index 0000000000..0834ee04cd --- /dev/null +++ b/qcom/opensource/bt-kernel/pwr/btpower.c @@ -0,0 +1,3151 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/* + * Bluetooth Power Switch Module + * controls power to external Bluetooth device + * with interface to power management device + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btpower.h" +#ifdef CONFIG_FMD_ENABLE +#include "cnss_utils.h" +#endif +#if (defined CONFIG_BT_SLIM) +#include "btfm_slim.h" +#endif +#include +#include + +#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_NOTIFICATION_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 RESETB_GPIO_HIGH 0x00000001 +#define RESETB_GPIO_LOW 0x00000000 + +#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) + +#define INVALID_SOC 0x00 +#define PEACH_SOC_VERSION_1_0 0x01 +#define PEACH_SOC_VERSION_2_0 0x02 +#define OTHER_FMD_SUPPORTED_BT_SOC 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, + BT_VDD_WLAN_AON_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, + BT_VDD_WLAN_AON_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-vdd-aon", 950000, 950000, 0, false, true, + {BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}}, + {NULL, "qcom,bt-vdd-rfaOp8", 950000, 950000, 0, false, true, + {BT_VDD_RFACMN, BT_VDD_RFACMN_CURRENT}}, + /* BT_CX_MX */ + {NULL, "qcom,bt-vdd-dig", 950000, 950000, 0, false, true, + {BT_VDD_DIG_LDO, BT_VDD_DIG_LDO_CURRENT}}, + {NULL, "qcom,bt-vdd-rfaOp8", 950000, 952000, 0, false, true, + {BT_VDD_RFA_0p8, BT_VDD_RFA_0p8_CURRENT}}, + {NULL, "qcom,bt-vdd-rfa1", 1350000, 1350000, 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-ant-ldo", 1776000, 1776000, 0, false, true, + {BT_VDD_ANT_LDO, BT_VDD_ANT_LDO_CURRENT}}, +}; + +static struct vreg_data platform_vregs_info_peach[] = { + /* VDD1P8_AON */ + {NULL, "qcom,bt-vdd18-aon", 1620000, 1980000, 0, false, true, + {BT_VDD_LDO, BT_VDD_LDO_CURRENT}}, + /* VDD1P2_IOAV91C_VDD Extractor */ + {NULL, "qcom,bt-vdd12-io", 1080000, 1980000, 0, false, true, + {BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}}, + /* AV91C_VDD Extractor */ + {NULL, "qcom,bt-ant-ldo", 1776000, 1776000, 0, false, true, + {BT_VDD_ANT_LDO, BT_VDD_ANT_LDO_CURRENT}}, + /* BT_CX_MX */ + {NULL, "qcom,bt-vdd-dig", 700000, 2100000, 0, false, true, + {BT_VDD_DIG_LDO, BT_VDD_DIG_LDO_CURRENT}}, + /* RFA_CMN/AON */ + {NULL, "qcom,bt-vdd-aon", 800000, 2100000, 0, false, true, + {BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}}, + /* RFA_OP75 */ + {NULL, "qcom,bt-vdd-rfa0p75", 800000, 2100000, 0, false, true, + {BT_VDD_RFA_0p8, BT_VDD_RFA_0p8_CURRENT}}, + /* RFA_1P8 */ + {NULL, "qcom,bt-vdd-rfa1p8", 1850000, 2100000, 0, false, true, + {BT_VDD_RFA2_LDO, BT_VDD_RFA2_LDO_CURRENT}}, + /* RFA_1P25 */ + {NULL, "qcom,bt-vdd-rfa1p25", 1300000, 2100000, 0, false, true, + {BT_VDD_RFA1_LDO, BT_VDD_RFA1_LDO_CURRENT}}, + {NULL, "qcom,bt-vdd-wlan-aon", 950000, 950000, 0, false, false, + {BT_VDD_WLAN_AON_LDO, BT_VDD_WLAN_AON_LDO_CURRENT}}, +}; + +// Regulator structure for WCN399x BT SoC series +static struct pwr_data 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 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 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 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 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 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 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 vreg_info_wcn6750 = { + .compatible = "qcom,wcn6750-bt", + .bt_vregs = bt_vregs_info_qca6xx0, + .bt_num_vregs = ARRAY_SIZE(bt_vregs_info_qca6xx0), +}; + +/* Peach supports both BT & UWB SS. For now it requires + * only platform regulators to be powered ON. + */ +static struct pwr_data vreg_info_peach = { + .compatible = "qcom,peach-bt", + .platform_vregs = platform_vregs_info_peach, + .platform_num_vregs = ARRAY_SIZE(platform_vregs_info_peach), +}; + +static struct pwr_data vreg_info_wcn786x = { + .compatible = "qcom,wcn786x", + .platform_vregs = platform_vregs_info_peach, + .platform_num_vregs = ARRAY_SIZE(platform_vregs_info_peach), +}; + +static struct pwr_data bt_vreg_info_wcn7750 = { + .compatible = "qcom,wcn7750-bt", + .bt_vregs = bt_vregs_info_qca6xx0, + .bt_num_vregs = ARRAY_SIZE(bt_vregs_info_qca6xx0), +}; + +static const struct of_device_id bt_power_match_table[] = { + { .compatible = "qcom,qca6174", .data = &vreg_info_qca6174}, + { .compatible = "qcom,wcn3990", .data = &vreg_info_wcn399x}, + { .compatible = "qcom,qca6390", .data = &vreg_info_qca6390}, + { .compatible = "qcom,qca6490", .data = &vreg_info_qca6490}, + { .compatible = "qcom,kiwi", .data = &vreg_info_kiwi}, + { .compatible = "qcom,kiwi-no-share-ant-power", + .data = &vreg_info_kiwi_no_share_ant_power}, + { .compatible = "qcom,wcn6750-bt", .data = &vreg_info_wcn6750}, + { .compatible = "qcom,bt-qca-converged", .data = &vreg_info_converged}, + { .compatible = "qcom,peach-bt", .data = &vreg_info_peach}, + { .compatible = "qcom,wcn786x", .data = &vreg_info_wcn786x}, + { .compatible = "qcom,wcn7750-bt", .data = &bt_vreg_info_wcn7750}, + {}, +}; + +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; +static struct fmdOperationStruct fmdStruct; +char *default_crash_reason = "Crash reason not found"; + +static int btpower_enable_ipa_vreg(struct platform_pwr_data *pdata); +static inline int btpower_get_retenion_mode_state(void); +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_debug("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_info("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_info("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_debug("%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 host_waking = (SIGIO_OOBS_SINGAL | + gpio_get_value(drvdata->bt_gpio_host_wake)); + int rc = 0; + + pr_debug("%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_info("%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 == false ? vreg->min_vol: 0), + 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 == false ? vreg->load_curr: 0)); + 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_info("%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_debug("%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_info("%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_debug("%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_debug("%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_debug("%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 get_fmd_mode(void) +{ + return pwr_data->is_fmd_mode_enable; +} + +static int bt_pull_resetb(int resetb_gpio, int value) +{ + int rc = 0; + + rc = gpio_direction_output(resetb_gpio, value); + if (rc) { + pr_err("%s: Unable to set direction\n", __func__); + return rc; + } + return rc; +} + +static int bt_resetb_operation(int resetb) +{ + int rc = 0; + + /* making resetb to low */ + pr_info("BTON: Turn bt_resetb_gpio to low\n"); + rc = bt_pull_resetb(resetb, RESETB_GPIO_LOW); + if (rc) + return rc; + msleep(20); + /* making resetb to high after delay */ + pr_info("BTON: Turn bt_resetb_gpio to High\n"); + rc = bt_pull_resetb(resetb, RESETB_GPIO_HIGH); + return rc; +} + +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 bt_resetb_gpio = pwr_data->bt_gpio_resetb; + 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; + } + if (bt_resetb_gpio >= 0) { + rc = gpio_request(bt_resetb_gpio, "bt_resetb_gpio_n"); + if (rc) { + pr_err("%s: unable to request gpio %d (%d)\n", + __func__, bt_resetb_gpio, rc); + return rc; + } + } + + pr_info("BTON:Turn Bt OFF asserting BT_EN to low\n"); + pr_info("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_info("BTON:Turn Bt OFF post asserting BT_EN to low\n"); + pr_info("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_info("Set msm_gpio_mpm_wake_set for sw_cntrl gpio successful\n"); + } + pr_info("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_info("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_info("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_info("BTON: WLAN OFF and BT ON are too close\n"); + pr_info("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); + if (bt_resetb_gpio >= 0) { + pr_err("BTON:Turn resetb High\n"); + bt_pull_resetb(bt_resetb_gpio, RESETB_GPIO_HIGH); + } + } + pr_info("BTON: WLAN OFF waiting for 100ms delay\n"); + pr_info("for AON output to fully discharge\n"); + msleep(100); + pr_info("BTON: WLAN OFF Asserting BT_EN to high\n"); + btpower_set_xo_clk_gpio_state(true); + if (bt_resetb_gpio >= 0) + bt_resetb_operation(bt_resetb_gpio); + 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_info("BTON: WLAN ON and BT ON are too close\n"); + pr_info("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_info("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_info("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_info("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_info("BT-OFF:bt-sw-ctrl-gpio(%d) value(%d)\n", + bt_sw_ctrl_gpio, + gpio_get_value(bt_sw_ctrl_gpio)); + } + } + + pr_info("%s: bt_gpio= %d on: %d\n", __func__, bt_reset_gpio, on); + + return rc; +} + +static int handle_pwr_disable_req(int core, int reg_num, int retenion_state, int fmd_state) +{ + struct vreg_data *vregs; + int rc; + + for (int i = 0; i < reg_num; i++) { + if (core == BT_CORE) + vregs = &pwr_data->bt_vregs[i]; + else if (core == UWB_CORE) + vregs = &pwr_data->uwb_vregs[i]; + else if (core == PLATFORM_CORE) + vregs = &pwr_data->platform_vregs[i]; + + if (fmd_state && vregs->fmd_mode_set) { + if (retenion_state != RETENTION_IDLE) { + vreg_disable_retention(vregs); + pr_err("%s: Brought %s reg out-of retention for FMD\n", + __func__, vregs->name); + } + pr_err("%s: Keeping %s reg on power-on state for FMD\n", + __func__, vregs->name); + } else { + rc = vreg_disable(vregs); + } + } + 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); + } + } + + 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 { + if (!get_fmd_mode()) + bt_configure_gpios(POWER_DISABLE); + } + } +gpio_fail: + if (!get_fmd_mode()) { + 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); + if (pwr_data->bt_gpio_resetb > 0) + gpio_free(pwr_data->bt_gpio_resetb); + } +regulator_fail: + rc = handle_pwr_disable_req(BT_CORE, + bt_num_vregs, + btpower_get_retenion_mode_state(), + get_fmd_mode()); + } 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 regulator_failed; + } + if (uwb_vregs->is_enabled) { + power_src.uwb_state[log_indx] = + regulator_get_voltage(uwb_vregs->reg); + } + } + } + break; + case POWER_DISABLE: +regulator_failed: + 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 bool is_wlan_mx_buck(struct vreg_data *reg) +{ + + if (strcmp(reg->name, "qcom,bt-vdd-wlan-aon")) + return false; + else + return true; +} + +static int vote_wlan_reg_for_fmd(void) +{ + int log_indx; + struct vreg_data *vregs = pwr_data->wlan_vregs; + + if ((vregs == NULL) || (!is_wlan_mx_buck(vregs))) { + pr_err("%s: Regulator qcom,bt-vdd-wlan-aon is not avilable\n", __func__); + return -1; + } + + if (vregs->reg == NULL) + return -1; + + log_indx = vregs->indx.init; + power_src.platform_state[log_indx] = DEFAULT_INVALID_VALUE; + + if (vreg_enable(vregs) < 0) { + pr_err("%s: Platform regulators config failed\n", __func__); + return -1; + } + + if (vregs->is_enabled) { + power_src.platform_state[log_indx] = regulator_get_voltage(vregs->reg); + pr_err("%s: Regulator %s voted-on Successfully\n", __func__, vregs->name); + } else { + pr_err("%s: Regulator qcom,bt-vdd-wlan-aon is not enabled\n", __func__); + return -1; + } + return 0; +} + +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 regulator_failed; + } + 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 gpio_failed; + } + break; + case POWER_DISABLE: + if (!get_fmd_mode()) { + rc = bt_configure_gpios(POWER_DISABLE); + if (rc < 0) { + pr_err("%s: bt_power gpio config failed\n", + __func__); + } + } +gpio_failed: + if (!get_fmd_mode()) { + 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); + } +regulator_failed: + rc = handle_pwr_disable_req(PLATFORM_CORE, + platform_num_vregs, + btpower_get_retenion_mode_state(), + get_fmd_mode()); + 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_debug("%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_err("%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) { + pr_err("%s: Property %s %s, use default\n", + __func__, prop_name, + prop ? "invalid format" : "doesn't exist"); + } else if (len == (5 * sizeof(__be32))) { + 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]); + vreg->fmd_mode_set = be32_to_cpup(&prop[4]); + pr_err("%s: FMD mode %d for regulator %s\n", __func__, + vreg->fmd_mode_set, vreg->name); + } else if (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_debug("%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; + struct pinctrl *pinctrl1; +#ifdef CONFIG_FMD_ENABLE + struct pinctrl_state *sw_ctrl; + struct pinctrl_state *bt_en; +#endif + child = pdev->dev.of_node; + + pinctrl1 = devm_pinctrl_get(&pdev->dev); + 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_warn("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__); + + pwr_data->bt_gpio_resetb = + of_get_named_gpio(child, + "qcom,wl-resetb-gpio", 0); + if (pwr_data->bt_gpio_resetb < 0) + pr_err("%s: bt_gpio_resetb not provided in device tree\n", + __func__); + + 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_warn("bt-sw-ctrl-gpio not provided in devicetree\n"); + + pwr_data->bt_gpio_fmd_clk_ctrl = + of_get_named_gpio(child, + "qcom,bt-fmd-clk-gpio", 0); + if (pwr_data->bt_gpio_fmd_clk_ctrl < 0) + pr_warn("bt-fmd-clk-gpio not provided in devicetree\n"); + + 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"); + } +#ifdef CONFIG_FMD_ENABLE + if (pinctrl1) { + sw_ctrl = pinctrl_lookup_state(pinctrl1, "sw_ctrl"); + if (IS_ERR_OR_NULL(sw_ctrl)) { + ret = PTR_ERR(sw_ctrl); + pr_err("Failed to get sw_ctrl state, err = %d\n", ret); + } else { + ret = pinctrl_select_state(pinctrl1, sw_ctrl); + if (ret) + pr_err("Failed to select sw_ctrl state, err = %d\n", ret); + } + bt_en = pinctrl_lookup_state(pinctrl1, "bt_en"); + if (IS_ERR_OR_NULL(bt_en)) { + ret = PTR_ERR(bt_en); + pr_err("Failed to get bt_en state, err = %d\n", ret); + } else { + ret = pinctrl_select_state(pinctrl1, bt_en); + if (ret) + pr_err("Failed to select bt_en state, err = %d\n", ret); + } + } else { + pr_err("%s: pinctrl is null\n", __func__); + } +#endif + 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; + } + + 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++) { + if (is_wlan_mx_buck(&pwr_data->platform_vregs[i])) { + pwr_data->wlan_vregs = &pwr_data->platform_vregs[i]; + pwr_data->platform_num_vregs--; + pr_err("%s: Found wlan regulator for FMD Operations '%s'\n", + __func__, (pwr_data->wlan_vregs)->name); + } + 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; + } + + pr_info("%s: platform_name = %s: bt_num_vregs =%d uwb_num_vregs =%d platform_num_vregs=%d\n", + __func__, pwr_data->compatible, pwr_data->bt_num_vregs, + pwr_data->uwb_num_vregs, pwr_data->platform_num_vregs); + + } else { + + pr_info("%s: platform_name = %s: bt_num_vregs =%d\n", __func__, + pwr_data->compatible, pwr_data->bt_num_vregs); + + } + return rc; +} + +static int bt_power_populate_dt_pinfo(struct platform_device *pdev) +{ + struct device_node *of_node; + int rc; + + 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_info("%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_info("%s: %s device node found\n", __func__, + pwr_data->uwb_of_node->name); + } + } + } + + rc = get_power_dt_pinfo(pdev); + + if (rc < 0) + return rc; + + get_gpio_dt_pinfo(pdev); + bt_dt_parse_clk_info(&pdev->dev, &pwr_data->bt_chip_clk); + pwr_data->power_setup = power_regulators; + return 0; +} + +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_info("PDC Init table configured\n"); + } else { + pr_debug("PDC Init Table not configured\n"); + } +} + +static void bt_signal_handler(struct work_struct *w_arg) +{ + struct kernel_siginfo siginfo; + int rc = 0; + + 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); + else + pr_err("%s: Signal to BT HAL (PID-%d) succesfull\n", __func__, + pwr_data->reftask_bt->pid); +} + +static void uwb_signal_handler(struct work_struct *w_arg) +{ + struct kernel_siginfo siginfo; + int rc = 0; + + 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); + else + pr_err("%s: Signal to UWB HAL (PID-%d) succesfull\n", __func__, + pwr_data->reftask_uwb->pid); +} + +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->reftask_bt = NULL; + pwr_data->reftask_uwb = NULL; + + struct device *devi = &pwr_data->pdev->dev; + int rc = 0; + pr_info("%s: Get FMD nvmem-cells\n", __func__); +/* Get fmd_set NVMEM Cell Handler */ + pwr_data->nvmem_cell_fmd_set = + devm_nvmem_cell_get(devi, "fmd_set"); + if (IS_ERR(pwr_data->nvmem_cell_fmd_set)) { + rc = PTR_ERR(pwr_data->nvmem_cell_fmd_set); + pr_err("%s:Failed to get fmd_set nvmem-cells %d\n", + __func__, rc); + pr_err("%s:Skip, Reading of other 2 Cells have no use\n", + __func__); + pwr_data->nvmem_cell_fmd_chg_pon = + pwr_data->nvmem_cell_fmd_set; + pwr_data->nvmem_cell_fmd_cnt2_stop = + pwr_data->nvmem_cell_fmd_set; + } else { + pr_info("%s: Got fmd_set nvmem-cells\n", __func__); +/* Get fmd_chg_pon NVMEM Cell Handler */ + pwr_data->nvmem_cell_fmd_chg_pon = + devm_nvmem_cell_get(devi, "fmd_chg_pon"); + if (IS_ERR(pwr_data->nvmem_cell_fmd_chg_pon)) { + rc = PTR_ERR(pwr_data->nvmem_cell_fmd_chg_pon); + pr_err("%s:Failed to get fmd_chg_pon nvmem-cells %d\n", + __func__, rc); + } else { + pr_info("%s: Got fmd_chg_pon nvmem-cells\n", __func__); + } +/* Get fmd_cnt2_stop NVMEM Cell Handler */ + pwr_data->nvmem_cell_fmd_cnt2_stop = + devm_nvmem_cell_get(devi, "fmd_cnt2_stop"); + if (IS_ERR(pwr_data->nvmem_cell_fmd_cnt2_stop)) { + rc = PTR_ERR(pwr_data->nvmem_cell_fmd_cnt2_stop); + pr_err("%s:Failed to get fmd_cnt2_stop nvmem-cells %d\n", + __func__, rc); + } else { + pr_info("%s: Got fmd_cnt2_stop nvmem-cells\n", __func__); + } + } + pr_info("%s: FMD nvmem-cells read completed\n", __func__); + + pwr_data->is_ganges_dt = of_property_read_bool(pdev->dev.of_node, + "qcom,peach-bt") || + of_property_read_bool(pdev->dev.of_node, + "qcom,wcn786x"); + 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: + kfree(pwr_data); + return ret; +} + +static int bt_power_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s\n", __func__); + probe_finished = false; + btpower_rfkill_remove(pdev); + bt_power_vreg_put(); + if (pwr_data->is_ganges_dt) + destroy_workqueue(pwr_data->workq); + kfree(pwr_data); + return 0; +} + +int btpower_register_slimdev(struct device *dev) +{ + pr_debug("%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_debug("%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) { + if (!pwr_data->reftask_bt) { + pr_err("%s: BT client is not register to send signal\n", + __func__); + return; + } + queue_work(pwr_data->workq, &pwr_data->bt_wq); + } else { + if (!pwr_data->reftask_uwb) { + pr_err("%s: UWB client is not register to send signal\n", + __func__); + return; + } + 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) { + 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_NOTIFICATION_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) + send_signal_to_subsystem(UWB, SSR_ON_BT); + } else { + update_sub_state(SSR_ON_UWB); + if (get_pwr_state() == ALL_CLIENTS_ON) + send_signal_to_subsystem(BLUETOOTH, + (SIGIO_NOTIFICATION_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", __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, reseting it back", __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", __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); + if (!(skb = skb_dequeue(&pwr_data->rxq))) { + 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", __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; + kfree_skb(skb); + 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) { + pr_err("%s: Unable to create skbuff\n", __func__); + 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", __func__, *status); + if (ret == 0) { + pr_err("%s: failed to vote %d due to timeout", __func__, request); + ret = -ETIMEDOUT; + } else { + ret = *status; + } + + return ret; +} + +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; +} + +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"; + } +} + +void fmd_set_sdam_bit(unsigned char arg) +{ + int rc = 0; + u8 *buf; + size_t len; + struct device *devi = &pwr_data->pdev->dev; + + if (IS_ERR(pwr_data->nvmem_cell_fmd_set)) { + pr_err("%s: 'fmd_set' cell is not avilable to configure\n", + __func__); + return; + } + + rc = nvmem_cell_write(pwr_data->nvmem_cell_fmd_set, + &arg, + sizeof(arg)); + if (rc < 0) { + pr_err("%s: Write SDAM BIT for FMD operation Failed %d\n", + __func__, rc); + return; + } + + buf = nvmem_cell_read(pwr_data->nvmem_cell_fmd_set, &len); + if (IS_ERR(buf)) { + dev_err(devi, "Failed to read fmd_set: %ld\n", + PTR_ERR(buf)); + pr_err("%s: Failed to read SDAM BIT (fmd_set = %d)\n", + __func__, buf[0]); + kfree(buf); + return; + } + + if (buf[0] == arg) { + dev_info(devi, "Successfully configured the fmd_set\n"); + pr_info("%s: Successfully configured SDAM BIT (fmd_set: %u)\n", + __func__, buf[0]); + } else { + dev_err(devi, "Failed to configure fmd_set: %ld\n", + PTR_ERR(buf)); + pr_err("%s: Failed to configure SDAM BIT (fmd_set = %u)\n", + __func__, buf[0]); + } + kfree(buf); +} + +void fmd_reboot_on_usb_detection(unsigned char arg) +{ + int rc = 0; + u8 *buf; + size_t len; + struct device *devi = &pwr_data->pdev->dev; + + if (IS_ERR(pwr_data->nvmem_cell_fmd_chg_pon)) { + pr_err("%s: 'fmd_chg_pon' cell is not avilable to configure\n", + __func__); + return; + } + + rc = nvmem_cell_write(pwr_data->nvmem_cell_fmd_chg_pon, + &arg, + sizeof(arg)); + if (rc < 0) { + pr_err("%s: Write fmd_chg_pon for FMD operation Failed %d\n", + __func__, rc); + return; + } + + buf = nvmem_cell_read(pwr_data->nvmem_cell_fmd_chg_pon, &len); + if (IS_ERR(buf)) { + dev_err(devi, "Failed to read fmd_chg_pon: %ld\n", + PTR_ERR(buf)); + pr_err("%s: Failed to read fmd_chg_pon = %d\n", + __func__, buf[0]); + kfree(buf); + return; + } + + if (buf[0] == arg) { + dev_info(devi, "Successfully configured the fmd_chg_pon\n"); + pr_info("%s: Successfully configured (fmd_chg_pon: %u)\n", + __func__, buf[0]); + } else { + dev_err(devi, "Failed to configure fmd_chg_pon: %ld\n", + PTR_ERR(buf)); + pr_err("%s: Failed to configure fmd_chg_pon = %u\n", + __func__, buf[0]); + } + kfree(buf); +} + +void fmd_write_stop_counter(unsigned char arg) +{ + int rc = 0; + u8 *buf; + size_t len; + struct device *devi = &pwr_data->pdev->dev; + + if (IS_ERR(pwr_data->nvmem_cell_fmd_cnt2_stop)) { + pr_err("%s: 'fmd_cnt2_stop' cell is not avilable to configure\n", + __func__); + return; + } + + rc = nvmem_cell_write(pwr_data->nvmem_cell_fmd_cnt2_stop, + &arg, + sizeof(arg)); + if (rc < 0) { + pr_err("%s: Write cnt2_stop for FMD operation Failed %d\n", + __func__, rc); + return; + } + + buf = nvmem_cell_read(pwr_data->nvmem_cell_fmd_cnt2_stop, &len); + if (IS_ERR(buf)) { + dev_err(devi, "Failed to read fmd_cnt2_stop: %ld\n", + PTR_ERR(buf)); + pr_err("%s: Failed to read fmd_cnt2_stop = %d\n", + __func__, buf[0]); + kfree(buf); + return; + } + + if (buf[0] == arg) { + dev_info(devi, "Successfully configured the fmd_cnt2_stop\n"); + pr_info("%s: Successfully configured (fmd_cnt2_stop: %u)\n", + __func__, buf[0]); + } else { + dev_err(devi, "Failed to configure fmd_cnt2_stop: %ld\n", + PTR_ERR(buf)); + pr_err("%s: Failed to configure fmd_cnt2_stop = %u\n", + __func__, buf[0]); + } + kfree(buf); +} + +int perform_fmd_operation(void) +{ + int ret = 0; + switch ((enum FmdOperation) fmdStruct.fmdOperation) { + case UPDATE_SOC_VER: { + if ((fmdStruct.socFwVer == INVALID_SOC) || + (fmdStruct.socFwVer > OTHER_FMD_SUPPORTED_BT_SOC)) { + pr_err("%s: Invalid SOC VERSION sent = %d\n", + __func__, fmdStruct.socFwVer); + return -EINVAL; + } + pwr_data->is_fmd_mode_enable = true; + if (fmdStruct.socFwVer == PEACH_SOC_VERSION_1_0) { + pr_info("%s: UPDATE_SOC_VER :: PEACH_SOC_VER_1_0\n", + __func__); + } else if (fmdStruct.socFwVer == PEACH_SOC_VERSION_2_0) { + pr_info("%s: UPDATE_SOC_VER :: PEACH_SOC_VERSION_2_0\n", + __func__); +#ifdef CONFIG_FMD_ENABLE + cnss_utils_fmd_status(true); +#endif + if (vote_wlan_reg_for_fmd() < 0) { + pr_err("%s: failed to vote wlan_reg\n", __func__); + return -EINVAL; + } + } else { + pr_info("%s: UPDATE_SOC_VER :: OTHER_FMD_SUPPORT_BT_SOC\n", + __func__); + } + + if (pwr_data->bt_chip_clk) { + ret = bt_clk_enable(pwr_data->bt_chip_clk); + if (ret < 0) { + pr_err("%s: failed to bt_chip_clk\n", __func__); + return -EINVAL; + } + } + break; + } + case ENABLE_FMD: { + pr_info("%s: ENABLE_FMD\n", __func__); + + fmd_set_sdam_bit((unsigned char)POWER_ENABLE); + + if (fmdStruct.rebootStatus != -1) + fmd_reboot_on_usb_detection((unsigned char)fmdStruct.rebootStatus); + else + pr_err("%s: Reboot status upon usb detection is not configured\n", + __func__); + + if (fmdStruct.fmdCycles != -1) + fmd_write_stop_counter((unsigned char)fmdStruct.fmdCycles); + else + pr_err("%s: Fmd stop_counter is not configured\n", __func__); + + break; + } + case DISABLE_FMD: { + pr_info("%s: DISABLE_FMD\n", __func__); + pwr_data->is_fmd_mode_enable = false; + break; + } + default: { + pr_err("%s: invalid fmd operation received = %d\n", + __func__, fmdStruct.fmdOperation); + ret = -EINVAL; + break; + } + } + return ret; +} + +int bt_kernel_panic(char *arg) { + int ret = 0; + + pr_info("%s\n", __func__); + + if (copy_from_user(&CrashInfo, (char *)arg, sizeof(CrashInfo))) { + pr_err("%s: failed copy to panic reason from BT-Transport\n", + __func__); + memset(&CrashInfo, 0, sizeof(CrashInfo)); + strscpy(CrashInfo. PrimaryReason, + default_crash_reason, strlen(default_crash_reason)); + strscpy(CrashInfo. SecondaryReason, + default_crash_reason, strlen(default_crash_reason)); + 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); + + return ret; +} + +#ifdef CONFIG_MSM_BT_OOBS +int bt_oobs_handler(enum btpower_obs_param clk_cntrl) +{ + if (!gpio_is_valid(pwr_data->bt_gpio_dev_wake)) { + pr_debug("%s: BT_CMD_OBS_VOTE_CLOCK bt_dev_wake_n(%d) not configured\n", + __func__, pwr_data->bt_gpio_dev_wake); + return -EIO; + } + + switch (clk_cntrl) { + case BTPOWER_OBS_CLK_OFF: + btpower_uart_transport_locked(pwr_data, false); + break; + case BTPOWER_OBS_CLK_ON: + btpower_uart_transport_locked(pwr_data, true); + break; + case BTPOWER_OBS_DEV_OFF: + gpio_set_value(pwr_data->bt_gpio_dev_wake, 0); + break; + case BTPOWER_OBS_DEV_ON: + gpio_set_value(pwr_data->bt_gpio_dev_wake, 1); + break; + default: + pr_debug("%s: BT_CMD_OBS_VOTE_CLOCK clk_cntrl(%d)\n", + __func__, clk_cntrl); + return -EINVAL; + } + pr_debug("%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"); + return 0; +} +#endif + +static long bt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int chipset_version = 0; + unsigned long panic_reason = 0; + unsigned short primary_reason = 0, sec_reason = 0, source_subsystem = 0; + int current_ssr_state = SUB_STATE_IDLE; + + 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: + ret = bt_oobs_handler((enum btpower_obs_param)arg); + 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_FMD_OPERATION: { + pr_err("%s: BT_CMD_FMD_OPERATION\n", __func__); + if (copy_from_user(&fmdStruct, (char *)arg, sizeof(fmdStruct))) { + pr_err("%s: copy to user failed\n", __func__); + ret = -EFAULT; + } + ret = perform_fmd_operation(); + 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_info("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_info("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_info("BT_CMD_GETVAL_POWER_SRCS\n"); + log_power_src_val(); + 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_info("%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__); + + ret = bt_kernel_panic((char *)arg); + + 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; + case UWB_GET_SSR_STATE: + current_ssr_state = get_sub_state(); + pr_err("%s: UWB_GET_SSR_STATE current_ssr_state:%d\n", __func__, + current_ssr_state); + if (copy_to_user((void __user *)arg, ¤t_ssr_state, + sizeof(current_ssr_state))) { + pr_err("%s: copy to user failed\n", __func__); + ret = -EFAULT; + } + break; + + default: + return -ENOIOCTLCMD; + } + return ret; +} +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, +}; + +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("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; + } + 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: , + * res: ., : } + * To send PDC Config: {class: wlan_pdc, ss: , res: pdc, + * enable: } + * 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) +{ + int ret = 0; + ret = qmp_send(plat_priv->qmp, mbox_msg, BTPOWER_MBOX_MSG_MAX_LEN); + if (ret < 0) + pr_err("Failed to send AOP qmp xmsg: %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_debug("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) +{ + int ret = 0; +// ret = of_find_property(pdata->pdev->dev.of_node, "qcom,qmp", NULL); + pdata->qmp = qmp_get(&pdata->pdev->dev); + if (IS_ERR(pdata->qmp)) { + pr_err("%s: failed to get qmp\n", __func__); + return PTR_ERR(pdata->qmp); + } + ret = of_property_read_string(pdata->pdev->dev.of_node, + "qcom,vreg_ipa", + &pdata->vreg_ipa); + if (ret) + pr_info("%s: vreg for iPA not configured\n", __func__); + else + pr_info("%s: qmp 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) +{ + 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_info("%s: sending AOP Mbox msg: %s\n", __func__, mbox_msg); + ret = qmp_send(pdata->qmp, mbox_msg, BTPOWER_MBOX_MSG_MAX_LEN); + 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_info("%s: IPA Vreg already configured\n", __func__); + return 0; + } + + if (!pdata->vreg_ipa) { + pr_info("%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_info("%s:Enabled iPA\n", __func__); + config_done = true; + } + } + + return ret; +} + +static void __exit btpower_exit(void) +{ + qmp_put(pwr_data->qmp); + 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); diff --git a/qcom/opensource/bt-kernel/rtc6226/Kconfig b/qcom/opensource/bt-kernel/rtc6226/Kconfig new file mode 100644 index 0000000000..7bc68d747d --- /dev/null +++ b/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. diff --git a/qcom/opensource/bt-kernel/rtc6226/Makefile b/qcom/opensource/bt-kernel/rtc6226/Makefile new file mode 100644 index 0000000000..8eb15acfc9 --- /dev/null +++ b/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 diff --git a/qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-common.c b/qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-common.c new file mode 100644 index 0000000000..2968f4680e --- /dev/null +++ b/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 + * Copyright (c) 2012 Hans de Goede + * + * 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 . + */ + +/* + * 2008-01-12 Tobias Lorenz + * Version 1.0.0 + * - First working version + * 2008-01-13 Tobias Lorenz + * 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 + * 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 + * 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 + * 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 + * 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 + * Oliver Neukum + * 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 + * Oliver Neukum + * Version 1.0.7 + * - usb autosuspend support + * - unplugging fixed + * 2008-05-07 Tobias Lorenz + * 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 + * - add support for KWorld USB FM Radio FM700 + * - blacklisted KWorld radio in hid-core.c and hid-ids.h + * 2008-12-03 Mark Lord + * - add support for DealExtreme USB Radio + * 2009-01-31 Bob Ross + * - correction of stereo detection/setting + * - correction of signal strength indicator scaling + * 2009-01-31 Rick Bronson + * Tobias Lorenz + * - add LED status output + * - get HW/SW version from scratchpad + * 2009-06-16 Edouard Lafargue + * 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-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/* kernel includes */ +#include +#include +#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, ¤t_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; + strscpy(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 */ + strscpy(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); diff --git a/qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-i2c.c b/qcom/opensource/bt-kernel/rtc6226/radio-rtc6226-i2c.c new file mode 100644 index 0000000000..f62041485f --- /dev/null +++ b/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 + * Copyright (c) 2009 Tobias Lorenz + * Copyright (c) 2012 Hans de Goede + * Copyright (c) 2018 LG Electronics, Inc. + * Copyright (c) 2018 Richwave Technology Co.Ltd + * Copyright (c) 2021-2024 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 . + */ + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "radio-rtc6226.h" +#include +#include + +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__); + strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strscpy(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) +{ + 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); diff --git a/qcom/opensource/bt-kernel/rtc6226/radio-rtc6226.h b/qcom/opensource/bt-kernel/rtc6226/radio-rtc6226.h new file mode 100644 index 0000000000..19d5cb48d4 --- /dev/null +++ b/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 + * Copyright (c) 2012 Hans de Goede + * 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 . + */ + +/* driver definitions */ +/* #define _RDSDEBUG */ +#define DRIVER_NAME "rtc6226-fmtuner" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/qcom/opensource/bt-kernel/slimbus/Kconfig b/qcom/opensource/bt-kernel/slimbus/Kconfig new file mode 100644 index 0000000000..45f2272ecb --- /dev/null +++ b/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. diff --git a/qcom/opensource/bt-kernel/slimbus/Makefile b/qcom/opensource/bt-kernel/slimbus/Makefile new file mode 100644 index 0000000000..ae35b58e12 --- /dev/null +++ b/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 diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim.c b/qcom/opensource/bt-kernel/slimbus/btfm_slim.c new file mode 100644 index 0000000000..b3c5f9a606 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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("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"); diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim.h b/qcom/opensource/bt-kernel/slimbus/btfm_slim.h new file mode 100644 index 0000000000..632710dcfd --- /dev/null +++ b/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 + +#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 */ diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim_codec.c b/qcom/opensource/bt-kernel/slimbus/btfm_slim_codec.c new file mode 100644 index 0000000000..c530ef652b --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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"); diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.c b/qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.c new file mode 100644 index 0000000000..a2492b6ce3 --- /dev/null +++ b/qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021, 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 || + usecase_codec == RVP) { + *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; + strscpy(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"); diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.h b/qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.h new file mode 100644 index 0000000000..bdd82073a9 --- /dev/null +++ b/qcom/opensource/bt-kernel/slimbus/btfm_slim_hw_interface.h @@ -0,0 +1,48 @@ +// 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, + RVP, + SSC, + LHDC, + 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_RVP", "CODEC_TYPE_SSC", + "CODEC_TYPE_LHDC", "CODEC_TYPE_INVALID"}; + +static SOC_ENUM_SINGLE_EXT_DECL(codec_display, codec_text); +#endif /*__LINUX_BTFM_SLIM_HW_INTERFACE_H*/ diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.c b/qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.c new file mode 100644 index 0000000000..fb4493eff2 --- /dev/null +++ b/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 +#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; +} diff --git a/qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.h b/qcom/opensource/bt-kernel/slimbus/btfm_slim_slave.h new file mode 100644 index 0000000000..3eb0ff8f9d --- /dev/null +++ b/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 diff --git a/qcom/opensource/bt-kernel/soundwire/Kconfig b/qcom/opensource/bt-kernel/soundwire/Kconfig new file mode 100644 index 0000000000..f01fac6089 --- /dev/null +++ b/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. + diff --git a/qcom/opensource/bt-kernel/soundwire/Makefile b/qcom/opensource/bt-kernel/soundwire/Makefile new file mode 100644 index 0000000000..db71d25bba --- /dev/null +++ b/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 diff --git a/qcom/opensource/bt-kernel/soundwire/btfm_swr.c b/qcom/opensource/bt-kernel/soundwire/btfm_swr.c new file mode 100644 index 0000000000..b518114c8d --- /dev/null +++ b/qcom/opensource/bt-kernel/soundwire/btfm_swr.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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: + case QCA_ORNE_SOC_ID_0100: + case QCA_COLOGNE_SOC_ID_0100: + return GANGES; + case QCA_EVROS_SOC_ID_0100: + case QCA_EVROS_SOC_ID_0101: + case QCA_EVROS_SOC_ID_0102: + case QCA_EVROS_SOC_ID_0103: + case QCA_EVROS_SOC_ID_0104: + case QCA_EVROS_SOC_ID_0105: + case QCA_EVROS_SOC_ID_0200: + return EVROS; + default: + BTFMSWR_ERR("no BT SOC id defined, returning GANGES"); + return GANGES; + } +} + +int btfm_swr_hw_init(void) +{ + uint8_t dev_num = 0; + int ret = 0; + int chipset_ver; + uint8_t retry = 0; + + 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 delay to provide sufficient time for + * soundwire auto enumeration of slave devices as + * per HW requirement. + */ + for ( ; retry < MAX_GET_DEV_NUM_RETRY; retry++) { + ret = swr_get_logical_dev_num(pbtfmswr->swr_slave, + pbtfmswr->p_dai_port->ea, + &dev_num); + if (ret == 0) + break; + usleep_range(2000, 2100); + } + if (ret) { + BTFMSWR_ERR("error getting logical device num after retry %u", + retry); + 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"); + btfm_swr_class = class_create("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"); + diff --git a/qcom/opensource/bt-kernel/soundwire/btfm_swr.h b/qcom/opensource/bt-kernel/soundwire/btfm_swr.h new file mode 100644 index 0000000000..99de813769 --- /dev/null +++ b/qcom/opensource/bt-kernel/soundwire/btfm_swr.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + +#ifndef BTFM_SWR_H +#define BTFM_SWR_H + +#include +#include +#include +#include + +#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 + +#define MAX_GET_DEV_NUM_RETRY 15 + +/* Codec driver defines */ +enum { + FMAUDIO_TX = 0, + BTAUDIO_TX, + BTAUDIO_RX, + // align definitions to stream id definitions in CP + BTAUDIO_RX2, + BTAUDIO_TX2, + 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 */ diff --git a/qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.c b/qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.c new file mode 100644 index 0000000000..456144f901 --- /dev/null +++ b/qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#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_TX2: + port_type = BT_AUDIO_TX2; + break; + case BTAUDIO_RX2: + port_type = BT_AUDIO_RX2; + 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 || + usecase_codec == LHDC) + *sampling_rate = (*sampling_rate) * 2; + } + + if (usecase_codec == LC3_VOICE || + usecase_codec == APTX_AD_SPEECH || + usecase_codec == LC3 || usecase_codec == APTX_AD_R4 || + usecase_codec == RVP) { + *sampling_rate = 96000; + } + + if (usecase_codec == APTX_AD_QLEA) + *sampling_rate = 192000; + 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); + + if (btfmswr == NULL || btfmswr->p_dai_port == NULL) { + BTFMSWR_INFO("port open might have called without hw_init\n"); + return -EINVAL; + } + + 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_TX2: + port_type = BT_AUDIO_TX2; + break; + case BTAUDIO_RX2: + port_type = BT_AUDIO_RX2; + 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_TX2: + *tx_num = btfmswr->num_channels; + *tx_slot = btfmswr->num_channels == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK; + break; + case BTAUDIO_RX: + case BTAUDIO_RX2: + *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_INFO("DAI id %u", id); + 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; + + if (id == BTAUDIO_RX2 || id == BTAUDIO_TX2) { + BTFMSWR_INFO("using interface index 2 for DAI id %u", id); + hwep_config->inf_index = 2; + } else { + BTFMSWR_INFO("using interface index 1 for DAI id %u", id); + 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, HFP client: bt -> lpass */ + .dai_name = "btaudio_tx2", + .id = BTAUDIO_TX2, + .capture = { + .stream_name = "BT Audio SWR Tx2 Capture", + /* 8/16/44.1/48/88.2/96/192 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 audio downlink2: lpass -> bt */ + .dai_name = "btaudio_rx2", + .id = BTAUDIO_RX2, + .playback = { + .stream_name = "BT Audio SWR Rx2 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, + }, +}; + +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); + BTFMSWR_INFO("num_dai is: %lu", ARRAY_SIZE(btfmswr_dai_driver)); + 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); + kfree(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"); diff --git a/qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.h b/qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.h new file mode 100644 index 0000000000..d95bd3b9da --- /dev/null +++ b/qcom/opensource/bt-kernel/soundwire/btfm_swr_hw_interface.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2024 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, + RVP, + SSC, + LHDC, + 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_RVP", + "CODEC_TYPE_SSC", "CODEC_TYPE_LHDC", "CODEC_TYPE_INVALID"}; + +static SOC_ENUM_SINGLE_EXT_DECL(codec_display, codec_text); +#endif /*__LINUX_BTFM_SWR_HW_INTERFACE_H*/ diff --git a/qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.c b/qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.c new file mode 100644 index 0000000000..b5f5ceee0e --- /dev/null +++ b/qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 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[FMAUDIO_TX].dai_id = FMAUDIO_TX, + .port_info[FMAUDIO_TX].port = 5, + + .port_info[BTAUDIO_TX].dai_id = BTAUDIO_TX, + .port_info[BTAUDIO_TX].port = 3, + + .port_info[BTAUDIO_RX].dai_id = BTAUDIO_RX, + .port_info[BTAUDIO_RX].port = 1, + + .port_info[BTAUDIO_RX2].dai_id = BTAUDIO_RX2, + .port_info[BTAUDIO_RX2].port = 2, + + .port_info[BTAUDIO_TX2].dai_id = BTAUDIO_TX2, + .port_info[BTAUDIO_TX2].port = 4, + }, + + // Ganges + { + .ea = GANGES_EA, + // FM is not supported on Ganges. populate with invalid port number + .port_info[FMAUDIO_TX].dai_id = FMAUDIO_TX, + .port_info[FMAUDIO_TX].port = BTFM_INVALID_PORT, + + .port_info[BTAUDIO_TX].dai_id = BTAUDIO_TX, + .port_info[BTAUDIO_TX].port = 4, + + .port_info[BTAUDIO_RX].dai_id = BTAUDIO_RX, + .port_info[BTAUDIO_RX].port = 1, + + .port_info[BTAUDIO_RX2].dai_id = BTAUDIO_RX2, + .port_info[BTAUDIO_RX2].port = 2, + + .port_info[BTAUDIO_TX2].dai_id = BTAUDIO_TX2, + .port_info[BTAUDIO_TX2].port = 5, + }, +}; + diff --git a/qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.h b/qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.h new file mode 100644 index 0000000000..95c10df037 --- /dev/null +++ b/qcom/opensource/bt-kernel/soundwire/btfm_swr_slave.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023-2025 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_0101 = 0x40200101, + QCA_EVROS_SOC_ID_0102 = 0x40200102, + QCA_EVROS_SOC_ID_0103 = 0x40200103, + QCA_EVROS_SOC_ID_0104 = 0x40200104, + QCA_EVROS_SOC_ID_0105 = 0x40200105, + QCA_EVROS_SOC_ID_0200 = 0x40200200, +}; + +enum { + QCA_ORNE_SOC_ID_0100 = 0x40262100, +}; + +enum { + QCA_COLOGNE_SOC_ID_0100 = 0x40292100, +}; + +enum { + EVROS_EA = 0x0108170220, + GANGES_EA = 0x0208170220, +}; + +/* Specific defines for slave slimbus device */ +#define SLAVE_SWR_REG_OFFSET 0x0800 + +#endif diff --git a/qcom/opensource/bt-kernel/target.bzl b/qcom/opensource/bt-kernel/target.bzl new file mode 100644 index 0000000000..202424a115 --- /dev/null +++ b/qcom/opensource/bt-kernel/target.bzl @@ -0,0 +1,55 @@ +load(":bt_kernel.bzl", "define_bt_modules") + +def define_pineapple(): + define_bt_modules( + target = "pineapple", + modules = [ + "btpower", + "bt_fm_slim", + "radio-i2c-rtc6226-qca", + ], + config_options = [ + "CONFIG_MSM_BT_POWER", + "CONFIG_BTFM_SLIM", + "CONFIG_I2C_RTC6226_QCA", + "CONFIG_FMD_ENABLE", + #"CONFIG_BT_HW_SECURE_DISABLE", + ] + ) + +def define_sun(): + define_bt_modules( + target = "sun", + modules = [ + "btpower", + "radio-i2c-rtc6226-qca", + "btfm_slim_codec", + "btfmcodec", + "bt_fm_swr", + ], + config_options = [ + "CONFIG_MSM_BT_POWER", + "CONFIG_I2C_RTC6226_QCA", + "CONFIG_SLIM_BTFM_CODEC", + "CONFIG_BTFM_CODEC", + # "CONFIG_BT_HW_SECURE_DISABLE", + "CONFIG_BTFM_SWR", + "CONFIG_FMD_ENABLE", + ] + ) + +def define_parrot(): + define_bt_modules( + target = "parrot66", + 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", + ] + )