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

git-subtree-dir: qcom/opensource/bt-kernel
git-subtree-mainline: 91a8910061
git-subtree-split: abeb53d57f
Change-Id:
repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom-opensource/bt-kernel
tag: LA.VENDOR.14.3.0.r1-17300-lanai.QSSI15.0
This commit is contained in:
David Wronek 2024-10-06 16:43:53 +02:00
commit 7870029999
46 changed files with 13888 additions and 0 deletions

View File

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

View File

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

View File

@ -0,0 +1,25 @@
ifeq ($(CONFIG_MSM_BT_POWER),m)
KBUILD_CPPFLAGS += -DCONFIG_MSM_BT_POWER
endif
ifeq ($(CONFIG_BTFM_SLIM),m)
KBUILD_CPPFLAGS += -DCONFIG_BTFM_SLIM
endif
ifeq ($(CONFIG_I2C_RTC6226_QCA),m)
KBUILD_CPPFLAGS += -DCONFIG_I2C_RTC6226_QCA
endif
ifeq ($(CONFIG_SLIM_BTFM_CODEC), m)
KBUILD_CPPFLAGS += -DCONFIG_SLIM_BTFM_CODEC
endif
ifeq ($(CONFIG_BT_HW_SECURE_DISABLE), y)
KBUILD_CPPFLAGS += -DCONFIG_BT_HW_SECURE_DISABLE
endif
obj-$(CONFIG_MSM_BT_POWER) += pwr/
obj-$(CONFIG_BTFM_SLIM) += slimbus/
obj-$(CONFIG_I2C_RTC6226_QCA) += rtc6226/
obj-$(CONFIG_BTFM_CODEC) += btfmcodec/
obj-$(CONFIG_SLIM_BTFM_CODEC) += slimbus/

View File

@ -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

View File

@ -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)

View File

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

View File

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

View File

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

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,731 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/slab.h>
#include <linux/kdev_t.h>
#include <linux/refcount.h>
#include <linux/idr.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include "btfm_codec.h"
#include "btfm_codec_pkt.h"
#define dev_to_btfmcodec(_dev) container_of(_dev, struct btfmcodec_data, dev)
static DEFINE_IDR(dev_minor);
static struct class *dev_class;
static dev_t dev_major;
struct btfmcodec_data *btfmcodec;
struct device_driver driver = {.name = "btfmcodec-driver", .owner = THIS_MODULE};
struct btfmcodec_char_device *btfmcodec_dev;
bool is_cp_supported = true;
#define cdev_to_btfmchardev(_cdev) container_of(_cdev, struct btfmcodec_char_device, cdev)
#define MIN_PKT_LEN 0x9
char *coverttostring(enum btfmcodec_states state) {
switch (state) {
case IDLE:
return "IDLE";
break;
case BT_Connected:
return "BT_CONNECTED";
break;
case BT_Connecting:
return "BT_CONNECTING";
break;
case BTADV_AUDIO_Connected:
return "BTADV_AUDIO_CONNECTED";
break;
case BTADV_AUDIO_Connecting:
return "BTADV_AUDIO_CONNECTING";
break;
default:
return "INVALID_STATE";
break;
}
}
/*
* btfmcodec_dev_open() - open() syscall for the btfmcodec dev node
* inode: Pointer to the inode structure.
* file: Pointer to the file structure.
*
* This function is used to open the btfmcodec char device when
* userspace client do a open() system call. All input arguments are
* validated by the virtual file system before calling this function.
* Note: btfmcodec dev node works on nonblocking mode.
*/
static int btfmcodec_dev_open(struct inode *inode, struct file *file)
{
struct btfmcodec_char_device *btfmcodec_dev = cdev_to_btfmchardev(inode->i_cdev);
struct btfmcodec_data *btfmcodec = (struct btfmcodec_data *)btfmcodec_dev->btfmcodec;
unsigned int active_clients = refcount_read(&btfmcodec_dev->active_clients);
btfmcodec->states.current_state = IDLE; /* Just a temp*/
btfmcodec->states.next_state = IDLE;
BTFMCODEC_INFO("for %s by %s:%d active_clients[%d]\n",
btfmcodec_dev->dev_name, current->comm,
task_pid_nr(current), refcount_read(&btfmcodec_dev->active_clients));
/* Don't allow a new client if already one is active. */
if (active_clients > 1) {
BTFMCODEC_WARN("%s: Not honoring open as other client is active", __func__);
return EACCES;
}
/* for now have btfmcodec and later we can think of having it btfmcodec_dev */
file->private_data = btfmcodec;
refcount_inc(&btfmcodec_dev->active_clients);
return 0;
}
/*
* btfmcodec_pkt_release() - release operation on btfmcodec device
* inode: Pointer to the inode structure.
* file: Pointer to the file structure.
*
* This function is used to release the btfmcodec dev node when
* userspace client do a close() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static int btfmcodec_dev_release(struct inode *inode, struct file *file)
{
struct btfmcodec_char_device *btfmcodec_dev = cdev_to_btfmchardev(inode->i_cdev);
unsigned long flags;
int idx;
BTFMCODEC_INFO("for %s by %s:%d active_clients[%u]\n",
btfmcodec_dev->dev_name, current->comm,
task_pid_nr(current), refcount_read(&btfmcodec_dev->active_clients));
refcount_dec(&btfmcodec_dev->active_clients);
if (refcount_read(&btfmcodec_dev->active_clients) == 1) {
spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
skb_queue_purge(&btfmcodec_dev->txq);
/* Wakeup the device if waiting for the data */
wake_up_interruptible(&btfmcodec_dev->readq);
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
/* we need to have separte rx lock for below buff */
skb_queue_purge(&btfmcodec_dev->rxq);
}
/* Notify waiting clients that client is closed or killed */
for (idx = 0; idx < BTM_PKT_TYPE_MAX; idx++) {
btfmcodec_dev->status[idx] = BTM_RSP_NOT_RECV_CLIENT_KILLED;
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
}
if (btfmcodec_dev->wq_hwep_shutdown.func)
cancel_work_sync(&btfmcodec_dev->wq_hwep_shutdown);
if (btfmcodec_dev->wq_hwep_configure.func)
cancel_work_sync(&btfmcodec_dev->wq_hwep_configure);
if (btfmcodec_dev->wq_prepare_bearer.func)
cancel_work_sync(&btfmcodec_dev->wq_prepare_bearer);
btfmcodec->states.current_state = IDLE;
btfmcodec->states.next_state = IDLE;
return 0;
}
btm_opcode STREAM_TO_UINT32 (struct sk_buff *skb)
{
return (skb->data[0] | (skb->data[1] << 8) |
(skb->data[2] << 16) | (skb->data[3] << 24));
}
static void btfmcodec_dev_rxwork(struct work_struct *work)
{
struct btfmcodec_char_device *btfmcodec_dev = container_of(work, struct btfmcodec_char_device, rx_work);
struct sk_buff *skb;
uint32_t len;
uint8_t status;
int idx;
uint8_t *bearer_switch_ind;
BTFMCODEC_DBG("start");
while ((skb = skb_dequeue(&btfmcodec_dev->rxq))) {
btm_opcode opcode = STREAM_TO_UINT32(skb);
skb_pull(skb, sizeof(btm_opcode));
len = STREAM_TO_UINT32(skb);
skb_pull(skb, sizeof(len));
switch (opcode) {
case BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_REQ:
idx = BTM_PKT_TYPE_PREPARE_REQ;
BTFMCODEC_DBG("BTM_BTFMCODEC_PREPARE_AUDIO_BEARER_SWITCH_REQ");
if (len == BTM_PREPARE_AUDIO_BEARER_SWITCH_REQ_LEN) {
/* there are chances where bearer indication is not recevied,
* So inform waiting thread to unblock itself and move to
* previous state.
*/
if (btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND] == BTM_WAITING_RSP) {
BTFMCODEC_DBG("Notifying waiting beare indications");
btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND] = BTM_FAIL_RESP_RECV;
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[BTM_PKT_TYPE_BEARER_SWITCH_IND]);
}
btfmcodec_dev->status[idx] = skb->data[0];
/* Reset bearer switch ind flag */
bearer_switch_ind =
&btfmcodec_dev->status[BTM_PKT_TYPE_BEARER_SWITCH_IND];
*bearer_switch_ind = BTM_WAITING_RSP;
queue_work(btfmcodec_dev->workqueue, &btfmcodec_dev->wq_prepare_bearer);
} else {
BTFMCODEC_ERR("wrong packet format with len:%d", len);
}
break;
case BTM_BTFMCODEC_MASTER_CONFIG_RSP:
idx = BTM_PKT_TYPE_MASTER_CONFIG_RSP;
if (len == BTM_MASTER_CONFIG_RSP_LEN) {
status = skb->data[1];
if (status == MSG_SUCCESS)
btfmcodec_dev->status[idx] = BTM_RSP_RECV;
else
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
} else {
BTFMCODEC_ERR("wrong packet format with len:%d", len);
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
}
BTFMCODEC_INFO("Rx BTM_BTFMCODEC_MASTER_CONFIG_RSP status:%d",
status);
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
break;
case BTM_BTFMCODEC_CODEC_CONFIG_DMA_RSP:
idx = BTM_PKT_TYPE_DMA_CONFIG_RSP;
if (len == BTM_CODEC_CONFIG_DMA_RSP_LEN) {
status = skb->data[1];
if (status == MSG_SUCCESS)
btfmcodec_dev->status[idx] = BTM_RSP_RECV;
else
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
} else {
BTFMCODEC_ERR("wrong packet format with len:%d", len);
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
}
BTFMCODEC_INFO("Rx BTM_BTFMCODEC_CODEC_CONFIG_DMA_RSP status:%d",
status);
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
break;
case BTM_BTFMCODEC_CTRL_MASTER_SHUTDOWN_RSP:
idx = BTM_PKT_TYPE_MASTER_SHUTDOWN_RSP;
if (len == BTM_MASTER_CONFIG_RSP_LEN) {
status = skb->data[1];
if (status == MSG_SUCCESS)
btfmcodec_dev->status[idx] = BTM_RSP_RECV;
else
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
} else {
BTFMCODEC_ERR("wrong packet format with len:%d", len);
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
}
BTFMCODEC_INFO("Rx BTM_BTFMCODEC_CTRL_MASTER_SHUTDOWN_RSP status:%d",
status);
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
BTFMCODEC_INFO("%s: waiting to cancel prepare bearer wq", __func__);
cancel_work_sync(&btfmcodec_dev->wq_prepare_bearer);
BTFMCODEC_INFO("%s: prepare bearer wq canceled", __func__);
break;
case BTM_BTFMCODEC_BEARER_SWITCH_IND:
idx = BTM_PKT_TYPE_BEARER_SWITCH_IND;
if (len == BTM_BEARER_SWITCH_IND_LEN) {
status = skb->data[0];
if (status == MSG_SUCCESS)
btfmcodec_dev->status[idx] = BTM_RSP_RECV;
else
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
} else {
BTFMCODEC_ERR("wrong packet format with len:%d", len);
btfmcodec_dev->status[idx] = BTM_FAIL_RESP_RECV;
}
BTFMCODEC_INFO("Rx BTM_BTFMCODEC_BEARER_SWITCH_IND status:%d",
status);
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
break;
case BTM_BTFMCODEC_CTRL_LOG_LVL_IND:
if (len == BTM_LOG_LVL_IND_LEN) {
log_lvl = skb->data[0];
} else {
BTFMCODEC_ERR("wrong packet format with len:%d", len);
}
BTFMCODEC_INFO("Rx BTM_BTFMCODEC_CTRL_LOG_LVL_IND status:%d",
log_lvl);
wake_up_interruptible(&btfmcodec_dev->rsp_wait_q[idx]);
break;
default:
BTFMCODEC_ERR("wrong opcode:%08x", opcode);
}
kfree_skb(skb);
}
BTFMCODEC_DBG("end");
}
/*
* btfmcodec_pkt_write() - write() syscall for the btfmcodec_pkt device
* file: Pointer to the file structure.
* buf: Pointer to the userspace buffer.
* count: Number bytes to read from the file.
* ppos: Pointer to the position into the file.
*
* This function is used to write the data to btfmcodec dev node when
* userspace client do a write() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static ssize_t btfmcodec_dev_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
struct btfmcodec_data *btfmcodec = file->private_data;
struct btfmcodec_char_device *btfmcodec_dev= NULL;
struct sk_buff *skb;
void *kbuf;
int ret = 0;
if (!btfmcodec || !btfmcodec->btfmcodec_dev || refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1) {
BTFMCODEC_INFO("%s: %d\n", current->comm, task_pid_nr(current));
return -EINVAL;
} else {
btfmcodec_dev = btfmcodec->btfmcodec_dev;
}
if (mutex_lock_interruptible(&btfmcodec_dev->lock)) {
ret = -ERESTARTSYS;
goto free_kbuf;
}
/* Hack for Now */
if (count < MIN_PKT_LEN) {
BTFMCODEC_ERR("minimum packet len should be greater than 3 bytes");
goto free_kbuf;
}
if (refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 0) {
BTFMCODEC_WARN("Client disconnected");
ret = -ENETRESET;
goto free_kbuf;
}
BTFMCODEC_DBG("begin to %s buffer_size %zu\n", btfmcodec_dev->dev_name, count);
kbuf = memdup_user(buf, count);
if (IS_ERR(kbuf)) {
ret = PTR_ERR(kbuf);
goto free_kbuf;
}
/* Check whether we need a dedicated chunk of memory for this driver */
skb = alloc_skb(count* sizeof(size_t), GFP_KERNEL);
if (!skb) {
BTFMCODEC_ERR("failed to allocate memory for recevied packet");
ret = -ENOMEM;
goto free_kbuf;
}
skb_put_data(skb, (uint8_t *)kbuf, count);
skb_queue_tail(&btfmcodec_dev->rxq, skb);
schedule_work(&btfmcodec_dev->rx_work);
kfree(kbuf);
free_kbuf:
mutex_unlock(&btfmcodec_dev->lock);
BTFMCODEC_DBG("finish to %s ret %d\n", btfmcodec_dev->dev_name, ret);
return ret < 0 ? ret : count;
}
int btfmcodec_dev_enqueue_pkt(struct btfmcodec_char_device *btfmcodec_dev, void *buf, int len)
{
struct sk_buff *skb;
unsigned long flags;
uint8_t *cmd = buf;
BTFMCODEC_DBG("start");
spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
if (refcount_read(&btfmcodec_dev->active_clients) == 1) {
BTFMCODEC_WARN("no active clients discarding the packet");
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
return -EINVAL;
}
skb = alloc_skb(len, GFP_ATOMIC);
if (!skb) {
BTFMCODEC_ERR("failed to allocate memory");
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
return -ENOMEM;
}
skb_put_data(skb, cmd, len);
skb_queue_tail(&btfmcodec_dev->txq, skb);
wake_up_interruptible(&btfmcodec_dev->readq);
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
BTFMCODEC_DBG("end");
return 0;
}
/*
* btfmcodec_pkt_poll() - poll() syscall for the btfmcodec device
* file: Pointer to the file structure.
* wait: pointer to Poll table.
*
* This function is used to poll on the btfmcodec dev node when
* userspace client do a poll() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static __poll_t btfmcodec_dev_poll(struct file *file, poll_table *wait)
{
struct btfmcodec_data *btfmcodec = file->private_data;
struct btfmcodec_char_device *btfmcodec_dev= NULL;
__poll_t mask = 0;
unsigned long flags;
BTFMCODEC_DBG("start");
if (!btfmcodec || !btfmcodec->btfmcodec_dev || refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1) {
BTFMCODEC_INFO("%s: %d\n", current->comm, task_pid_nr(current));
return -EINVAL;
} else {
btfmcodec_dev = btfmcodec->btfmcodec_dev;
}
/* Wait here for timeout or for a wakeup signal on readq */
poll_wait(file, &btfmcodec_dev->readq, wait);
mutex_lock(&btfmcodec_dev->lock);
/* recheck if the client has released by the driver */
if (refcount_read(&btfmcodec_dev->active_clients) == 1) {
BTFMCODEC_WARN("port has been closed already");
mutex_unlock(&btfmcodec_dev->lock);
return POLLHUP;
}
spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
/* Set flags if data is avilable to read */
if (!skb_queue_empty(&btfmcodec_dev->txq))
mask |= POLLIN | POLLRDNORM;
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
mutex_unlock(&btfmcodec_dev->lock);
BTFMCODEC_DBG("end with reason %d", mask);
return mask;
}
/*
* btfmcodec_dev_read() - read() syscall for the btfmcodec dev node
* file: Pointer to the file structure.
* buf: Pointer to the userspace buffer.
* count: Number bytes to read from the file.
* ppos: Pointer to the position into the file.
*
* This function is used to Read the data from btfmcodec pkt device when
* userspace client do a read() system call. All input arguments are
* validated by the virtual file system before calling this function.
*/
static ssize_t btfmcodec_dev_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
{
struct btfmcodec_data *btfmcodec = file->private_data;
struct btfmcodec_char_device *btfmcodec_dev= NULL;
unsigned long flags;
struct sk_buff *skb;
int use;
BTFMCODEC_DBG("start");
if (!btfmcodec || !btfmcodec->btfmcodec_dev || refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1) {
BTFMCODEC_INFO("%s: %d\n", current->comm, task_pid_nr(current));
return -EINVAL;
} else {
btfmcodec_dev = btfmcodec->btfmcodec_dev;
}
spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
/* Wait for data in the queue */
if (skb_queue_empty(&btfmcodec_dev->txq)) {
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
/* Wait until we get data*/
if (wait_event_interruptible(btfmcodec_dev->readq,
!skb_queue_empty(&btfmcodec_dev->txq)))
return -ERESTARTSYS;
/* We lost the client while waiting */
if (refcount_read(&btfmcodec->btfmcodec_dev->active_clients) == 1)
return -ENETRESET;
spin_lock_irqsave(&btfmcodec_dev->tx_queue_lock, flags);
}
skb = skb_dequeue(&btfmcodec_dev->txq);
spin_unlock_irqrestore(&btfmcodec_dev->tx_queue_lock, flags);
if (!skb)
return -EFAULT;
use = min_t(size_t, count, skb->len);
if (copy_to_user(buf, skb->data, use))
use = -EFAULT;
kfree_skb(skb);
BTFMCODEC_DBG("end for %s by %s:%d ret[%d]\n", btfmcodec_dev->dev_name,
current->comm, task_pid_nr(current), use);
return use;
}
bool isCpSupported(void)
{
return is_cp_supported;
}
static long btfmcodec_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct btfmcodec_data *btfmcodec = file->private_data;
struct hwep_data *hwep_info;
BTFMCODEC_INFO("%s: command %04x", __func__, cmd);
mutex_lock(&btfmcodec->hwep_drv_lock);
hwep_info = btfmcodec->hwep_info;
if (!hwep_info) {
BTFMCODEC_WARN("%s: HWEP is not registered with btfmcodec", __func__);
BTFMCODEC_WARN("%s: caching required info", __func__);
is_cp_supported = ((int)arg == 1) ? true : false;
mutex_unlock(&btfmcodec->hwep_drv_lock);
return 0;
}
mutex_unlock(&btfmcodec->hwep_drv_lock);
switch (cmd) {
case BTM_CP_UPDATE: {
if ((int)arg == 1) {
if (!strcmp(hwep_info->driver_name, "btfmslim"))
set_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags);
else if (!strcmp(hwep_info->driver_name, "btfmswr_slave"))
set_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags);
BTFMCODEC_INFO("%s: This target support CP hwep %s",
__func__, hwep_info->driver_name);
} else {
clear_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags);
clear_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags);
BTFMCODEC_INFO("%s: This target support doesn't CP", __func__);
}
BTFMCODEC_INFO("%s: mastr %d dma codec %d", __func__,
(int)test_bit(BTADV_AUDIO_MASTER_CONFIG, &hwep_info->flags),
(int)test_bit(BTADV_CONFIGURE_DMA, &hwep_info->flags));
break;
} default: {
BTFMCODEC_ERR("%s unhandled cmd %04x", __func__, cmd);
}
}
return 0;
}
static const struct file_operations btfmcodec_fops = {
.owner = THIS_MODULE,
.open = btfmcodec_dev_open,
.release = btfmcodec_dev_release,
.write = btfmcodec_dev_write,
.poll = btfmcodec_dev_poll,
.read = btfmcodec_dev_read,
/* For Now add no hookups for below callbacks */
.unlocked_ioctl = btfmcodec_ioctl,
.compat_ioctl = btfmcodec_ioctl,
};
static ssize_t btfmcodec_attributes_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t n)
{
struct btfmcodec_data *btfmcodec = dev_to_btfmcodec(dev);
struct btfmcodec_char_device *btfmcodec_dev = btfmcodec->btfmcodec_dev;
long tmp;
mutex_lock(&btfmcodec_dev->lock);
if (kstrtol(buf, 0, &tmp)) {
mutex_unlock(&btfmcodec_dev->lock);
return -EINVAL;
}
mutex_unlock(&btfmcodec_dev->lock);
return n;
}
static ssize_t btfmcodec_attributes_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
// struct btfmcodec_get_current_transport *btfmcodec_dev = dev_to_btfmcodec(dev);
return 0;
}
struct btfmcodec_data* btfm_get_btfmcodec(void)
{
return btfmcodec;
}
EXPORT_SYMBOL(btfm_get_btfmcodec);
static DEVICE_ATTR_RW(btfmcodec_attributes);
static int __init btfmcodec_init(void)
{
struct btfmcodec_state_machine *states;
struct btfmcodec_char_device *btfmcodec_dev;
struct device *dev;
int ret, i;
BTFMCODEC_INFO("starting up the module");
btfmcodec = kzalloc(sizeof(struct btfmcodec_data), GFP_KERNEL);
if (!btfmcodec) {
BTFMCODEC_ERR("failed to allocate memory");
return -ENOMEM;
}
mutex_init(&btfmcodec->hwep_drv_lock);
states = &btfmcodec->states;
states->current_state = IDLE;
states->next_state = IDLE;
BTFMCODEC_INFO("creating device node");
/* create device node for communication between userspace and kernel */
btfmcodec_dev = kzalloc(sizeof(struct btfmcodec_char_device), GFP_KERNEL);
if (!btfmcodec_dev) {
BTFMCODEC_ERR("failed to allocate memory");
ret = -ENOMEM;
goto info_cleanup;
}
BTFMCODEC_INFO("trying to get major number\n");
ret = alloc_chrdev_region(&dev_major, 0, 0, "btfmcodec");
if (ret < 0) {
BTFMCODEC_ERR("failed to allocate character device region");
goto dev_cleanup;
}
BTFMCODEC_INFO("creating btfm codec class");
dev_class = class_create(THIS_MODULE, "btfmcodec");
if (IS_ERR(dev_class)) {
ret = PTR_ERR(dev_class);
BTFMCODEC_ERR("class_create failed ret:%d\n", ret);
goto deinit_chrdev;
}
btfmcodec_dev->reuse_minor = idr_alloc(&dev_minor, btfmcodec, 1, 0, GFP_KERNEL);
if (ret < 0) {
BTFMCODEC_ERR("failed to allocated minor number");
goto deinit_class;
}
dev = &btfmcodec->dev;
dev->driver = &driver;
// ToDo Rethink of having btfmcodec alone instead of btfmcodec
btfmcodec->btfmcodec_dev = btfmcodec_dev;
refcount_set(&btfmcodec_dev->active_clients, 1);
mutex_init(&btfmcodec_dev->lock);
strlcpy(btfmcodec_dev->dev_name, "btfmcodec_dev", DEVICE_NAME_MAX_LEN);
device_initialize(dev);
dev->class = dev_class;
dev->devt = MKDEV(MAJOR(dev_major), btfmcodec_dev->reuse_minor);
dev_set_drvdata(dev, btfmcodec);
cdev_init(&btfmcodec_dev->cdev, &btfmcodec_fops);
btfmcodec_dev->cdev.owner = THIS_MODULE;
btfmcodec_dev->btfmcodec = (struct btfmcodec_data *)btfmcodec;
dev_set_name(dev, btfmcodec_dev->dev_name, btfmcodec_dev->reuse_minor);
ret = cdev_add(&btfmcodec_dev->cdev, dev->devt, 1);
if (ret) {
BTFMCODEC_ERR("cdev_add failed with error no %d", ret);
goto idr_cleanup;
}
// ToDo to handler HIDL abrupt kill
dev->release = NULL;
ret = device_add(dev);
if (ret) {
BTFMCODEC_ERR("Failed to add device error no %d", ret);
goto free_device;
}
BTFMCODEC_ERR("Creating a sysfs entry with name: %s", btfmcodec_dev->dev_name);
ret = device_create_file(dev, &dev_attr_btfmcodec_attributes);
if (ret) {
BTFMCODEC_ERR("Failed to create a devicd node: %s", btfmcodec_dev->dev_name);
goto free_device;
}
BTFMCODEC_INFO("created a node at /dev/%s with %u:%u\n",
btfmcodec_dev->dev_name, dev_major, btfmcodec_dev->reuse_minor);
skb_queue_head_init(&btfmcodec_dev->rxq);
mutex_init(&btfmcodec_dev->lock);
INIT_WORK(&btfmcodec_dev->rx_work, btfmcodec_dev_rxwork);
init_waitqueue_head(&btfmcodec_dev->readq);
spin_lock_init(&btfmcodec_dev->tx_queue_lock);
skb_queue_head_init(&btfmcodec_dev->txq);
INIT_LIST_HEAD(&btfmcodec->config_head);
for (i = 0; i < BTM_PKT_TYPE_MAX; i++) {
init_waitqueue_head(&btfmcodec_dev->rsp_wait_q[i]);
}
mutex_init(&states->state_machine_lock);
btfmcodec_dev->workqueue = alloc_ordered_workqueue("btfmcodec_wq", 0);
if (!btfmcodec_dev->workqueue) {
BTFMCODEC_ERR("btfmcodec_dev Workqueue not initialized properly");
ret = -ENOMEM;
goto free_device;
}
return ret;
free_device:
put_device(dev);
idr_cleanup:
idr_remove(&dev_minor, btfmcodec_dev->reuse_minor);
deinit_class:
class_destroy(dev_class);
deinit_chrdev:
unregister_chrdev_region(MAJOR(dev_major), 0);
dev_cleanup:
kfree(btfmcodec_dev);
info_cleanup:
kfree(btfmcodec);
return ret;
}
static void __exit btfmcodec_deinit(void)
{
struct btfmcodec_char_device *btfmcodec_dev;
struct device *dev;
BTFMCODEC_INFO("%s: cleaning up btfm codec driver", __func__);
if (!btfmcodec) {
BTFMCODEC_ERR("%s: skiping driver cleanup", __func__);
goto info_cleanup;
}
dev = &btfmcodec->dev;
device_remove_file(dev, &dev_attr_btfmcodec_attributes);
put_device(dev);
if (!btfmcodec->btfmcodec_dev) {
BTFMCODEC_ERR("%s: skiping device node cleanup", __func__);
goto info_cleanup;
}
btfmcodec_dev = btfmcodec->btfmcodec_dev;
skb_queue_purge(&btfmcodec_dev->rxq);
idr_remove(&dev_minor, btfmcodec_dev->reuse_minor);
class_destroy(dev_class);
unregister_chrdev_region(MAJOR(dev_major), 0);
kfree(btfmcodec_dev);
info_cleanup:
kfree(btfmcodec);
BTFMCODEC_INFO("%s: btfm codec driver cleanup completed", __func__);
return;
}
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MSM Bluetooth FM CODEC driver");
module_init(btfmcodec_init);
module_exit(btfmcodec_deinit);

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __LINUX_BTFM_CODEC_HW_INTERFACE_H
#define __LINUX_BTFM_CODEC_HW_INTERFACE_H
#include <linux/kernel.h>
#include <linux/bitops.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
/* This flag is set to indicate btfm codec driver is
* responsible to configure master.
*/
#define BTADV_AUDIO_MASTER_CONFIG 0
#define BTADV_CONFIGURE_DMA 1
#define DEVICE_NAME_MAX_LEN 64
struct hwep_configurations {
void *btfmcodec;
uint8_t stream_id;
uint32_t sample_rate;
uint8_t bit_width;
uint8_t codectype;
uint32_t direction;
uint8_t num_channels;
struct list_head dai_list;
};
struct master_hwep_configurations {
uint8_t stream_id;
uint32_t device_id;
uint32_t sample_rate;
uint8_t bit_width;
uint8_t num_channels;
uint8_t chan_num;
uint8_t codectype;
uint16_t direction;
};
struct hwep_dma_configurations {
uint8_t stream_id;
uint32_t sample_rate;
uint8_t bit_width;
uint8_t num_channels;
uint8_t codectype;
uint8_t lpaif; // Low power audio interface
uint8_t inf_index; // interface index
uint8_t active_channel_mask;
};
struct hwep_comp_drv {
int (*hwep_probe) (struct snd_soc_component *);
void (*hwep_remove) (struct snd_soc_component *);
unsigned int (*hwep_read)(struct snd_soc_component *, unsigned int );
int (*hwep_write)(struct snd_soc_component *, unsigned int,
unsigned int);
};
struct hwep_dai_ops {
int (*hwep_startup)(void *);
void (*hwep_shutdown)(void *, int);
int (*hwep_hw_params)(void *, uint32_t, uint32_t, uint8_t);
int (*hwep_prepare)(void *, uint32_t, uint32_t, int);
int (*hwep_set_channel_map)(void *, unsigned int, unsigned int *,
unsigned int, unsigned int *);
int (*hwep_get_channel_map)(void *, unsigned int *, unsigned int *,
unsigned int *, unsigned int *, int);
int (*hwep_get_configs)(void *a, void *b, uint8_t c);
uint8_t *hwep_codectype;
};
struct hwep_dai_driver {
const char *dai_name;
unsigned int id;
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
struct hwep_dai_ops *dai_ops;
};
struct hwep_data {
struct device *dev;
char driver_name [DEVICE_NAME_MAX_LEN];
struct hwep_comp_drv *drv;
struct hwep_dai_driver *dai_drv;
struct snd_kcontrol_new *mixer_ctrl;
int num_dai;
int num_mixer_ctrl;
unsigned long flags;
};
int btfmcodec_register_hw_ep(struct hwep_data *);
int btfmcodec_unregister_hw_ep(char *);
// ToDo below.
/*
#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC_DRV)
int btfmcodec_register_hw_ep(struct hwep_data *);
int btfmcodec_unregister_hw_ep(char *);
#else
static inline int btfmcodec_register_hw_ep(struct hwep_data *hwep_info)
{
return -EOPNOTSUPP;
}
static inline int btfmcodec_unregister_hw_ep(char *dev_name)
{
return -EOPNOTSUPP;
}
#endif
*/
#endif /*__LINUX_BTFM_CODEC_HW_INTERFACE_H */

View File

@ -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 */

View File

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

View File

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

View File

@ -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.

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,750 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "btpower.h"
#include "btfm_slim.h"
#include "btfm_slim_slave.h"
#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC)
#include "btfm_slim_hw_interface.h"
#endif
#define DELAY_FOR_PORT_OPEN_MS (200)
#define SLIM_MANF_ID_QCOM 0x217
#define SLIM_PROD_CODE 0x221
#define BT_CMD_SLIM_TEST 0xbfac
struct class *btfm_slim_class;
static int btfm_slim_major;
struct btfmslim *btfm_slim_drv_data;
static int btfm_num_ports_open;
static bool is_registered;
int btfm_slim_write(struct btfmslim *btfmslim,
uint16_t reg, uint8_t reg_val, uint8_t pgd)
{
int ret = -1;
uint32_t reg_addr;
int slim_write_tries = SLIM_SLAVE_RW_MAX_TRIES;
BTFMSLIM_INFO("Write to %s", pgd?"PGD":"IFD");
reg_addr = SLIM_SLAVE_REG_OFFSET + reg;
for ( ; slim_write_tries != 0; slim_write_tries--) {
mutex_lock(&btfmslim->xfer_lock);
ret = slim_writeb(pgd ? btfmslim->slim_pgd :
&btfmslim->slim_ifd, reg_addr, reg_val);
mutex_unlock(&btfmslim->xfer_lock);
if (ret) {
BTFMSLIM_DBG("retrying to Write 0x%02x to reg 0x%x ret %d",
reg_val, reg_addr, ret);
} else {
BTFMSLIM_DBG("Written 0x%02x to reg 0x%x ret %d", reg_val, reg_addr, ret);
break;
}
usleep_range(5000, 5100);
}
if (ret) {
BTFMSLIM_DBG("retrying to Write 0x%02x to reg 0x%x ret %d",
reg_val, reg_addr, ret);
}
return ret;
}
int btfm_slim_read(struct btfmslim *btfmslim, uint32_t reg, uint8_t pgd)
{
int ret = -1;
int slim_read_tries = SLIM_SLAVE_RW_MAX_TRIES;
uint32_t reg_addr;
BTFMSLIM_DBG("Read from %s", pgd?"PGD":"IFD");
reg_addr = SLIM_SLAVE_REG_OFFSET + reg;
for ( ; slim_read_tries != 0; slim_read_tries--) {
mutex_lock(&btfmslim->xfer_lock);
ret = slim_readb(pgd ? btfmslim->slim_pgd :
&btfmslim->slim_ifd, reg_addr);
BTFMSLIM_DBG("Read 0x%02x from reg 0x%x", ret, reg_addr);
mutex_unlock(&btfmslim->xfer_lock);
if (ret > 0)
break;
usleep_range(5000, 5100);
}
return ret;
}
int btfm_slim_enable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
uint8_t rxport, uint32_t rates, uint8_t nchan)
{
int ret = -1;
int i = 0;
struct btfmslim_ch *chan = ch;
int chipset_ver;
if (!btfmslim || !ch)
return -EINVAL;
BTFMSLIM_DBG("port: %d ch: %d", ch->port, ch->ch);
chan->dai.sruntime = slim_stream_allocate(btfmslim->slim_pgd, "BTFM_SLIM");
if (chan->dai.sruntime == NULL) {
BTFMSLIM_ERR("slim_stream_allocate failed");
return -EINVAL;
}
chan->dai.sconfig.bps = btfmslim->bps;
chan->dai.sconfig.direction = btfmslim->direction;
chan->dai.sconfig.rate = rates;
chan->dai.sconfig.ch_count = nchan;
chan->dai.sconfig.chs = kcalloc(nchan, sizeof(unsigned int), GFP_KERNEL);
if (!chan->dai.sconfig.chs)
return -ENOMEM;
for (i = 0; i < nchan; i++, ch++) {
/* Enable port through registration setting */
if (btfmslim->vendor_port_en) {
ret = btfmslim->vendor_port_en(btfmslim, ch->port,
rxport, 1);
if (ret < 0) {
BTFMSLIM_ERR("vendor_port_en failed ret[%d]",
ret);
goto error;
}
}
chan->dai.sconfig.chs[i] = ch->ch;
chan->dai.sconfig.port_mask |= BIT(ch->port);
}
/* Activate the channel immediately */
BTFMSLIM_INFO("port: %d, ch: %d", chan->port, chan->ch);
chipset_ver = btpower_get_chipset_version();
BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
/* for feedback channel, PCM bit should not be set */
if (btfm_feedback_ch_setting) {
BTFMSLIM_DBG("port open for feedback ch, not setting PCM bit");
//prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;
/* reset so that next port open sets the data format properly */
btfm_feedback_ch_setting = 0;
}
ret = slim_stream_prepare(chan->dai.sruntime, &chan->dai.sconfig);
if (ret) {
BTFMSLIM_ERR("slim_stream_prepare failed = %d", ret);
goto error;
}
ret = slim_stream_enable(chan->dai.sruntime);
if (ret) {
BTFMSLIM_ERR("slim_stream_enable failed = %d", ret);
goto error;
}
if (ret == 0)
btfm_num_ports_open++;
BTFMSLIM_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
return ret;
error:
BTFMSLIM_INFO("error %d while opening port, btfm_num_ports_open: %d",
ret, btfm_num_ports_open);
kfree(chan->dai.sconfig.chs);
chan->dai.sconfig.chs = NULL;
return ret;
}
int btfm_slim_disable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
uint8_t rxport, uint8_t nchan)
{
int ret = -1;
int i = 0;
int chipset_ver = 0;
if (!btfmslim || !ch)
return -EINVAL;
BTFMSLIM_INFO("port:%d ", ch->port);
if (ch->dai.sruntime == NULL) {
BTFMSLIM_ERR("Channel not enabled yet. returning");
return -EINVAL;
}
if (rxport && (btfmslim->sample_rate == 44100 ||
btfmslim->sample_rate == 88200)) {
BTFMSLIM_INFO("disconnecting the ports, removing the channel");
/* disconnect the ports of the stream */
ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime,
true, false);
if (ret != 0)
BTFMSLIM_ERR("slim_stream_unprepare failed %d", ret);
}
ret = slim_stream_disable(ch->dai.sruntime);
if (ret != 0) {
BTFMSLIM_ERR("slim_stream_disable failed returned val = %d", ret);
if ((btfmslim->sample_rate != 44100) && (btfmslim->sample_rate != 88200)) {
/* disconnect the ports of the stream */
ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime,
true, false);
if (ret != 0)
BTFMSLIM_ERR("slim_stream_unprepare failed %d", ret);
}
}
/* free the ports allocated to the stream */
ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime, false, true);
if (ret != 0)
BTFMSLIM_ERR("slim_stream_unprepare failed returned val = %d", ret);
/* Disable port through registration setting */
for (i = 0; i < nchan; i++, ch++) {
if (btfmslim->vendor_port_en) {
ret = btfmslim->vendor_port_en(btfmslim, ch->port,
rxport, 0);
if (ret < 0) {
BTFMSLIM_ERR("vendor_port_en failed [%d]", ret);
break;
}
}
}
ch->dai.sconfig.port_mask = 0;
if (ch->dai.sconfig.chs != NULL) {
kfree(ch->dai.sconfig.chs);
BTFMSLIM_INFO("setting ch->dai.sconfig.chs to NULL");
ch->dai.sconfig.chs = NULL;
} else
BTFMSLIM_ERR("ch->dai.sconfig.chs is already NULL");
if (btfm_num_ports_open > 0)
btfm_num_ports_open--;
ch->dai.sruntime = NULL;
BTFMSLIM_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
chipset_ver = btpower_get_chipset_version();
if (btfm_num_ports_open == 0 && (chipset_ver == QCA_HSP_SOC_ID_0200 ||
chipset_ver == QCA_HSP_SOC_ID_0210 ||
chipset_ver == QCA_HSP_SOC_ID_1201 ||
chipset_ver == QCA_HSP_SOC_ID_1211 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0100 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0101 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0200 ||
chipset_ver == QCA_APACHE_SOC_ID_0100 ||
chipset_ver == QCA_APACHE_SOC_ID_0110 ||
chipset_ver == QCA_APACHE_SOC_ID_0121 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0100 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0110 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0120)) {
BTFMSLIM_INFO("SB reset needed after all ports disabled, sleeping");
msleep(DELAY_FOR_PORT_OPEN_MS);
}
return ret;
}
static int btfm_slim_alloc_port(struct btfmslim *btfmslim)
{
int ret = -EINVAL, i;
int chipset_ver;
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
if (!btfmslim)
return ret;
chipset_ver = btpower_get_chipset_version();
BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
rx_chs = btfmslim->rx_chs;
tx_chs = btfmslim->tx_chs;
if ((chipset_ver >= QCA_CHEROKEE_SOC_ID_0310) &&
(chipset_ver <= QCA_CHEROKEE_SOC_ID_0320_UMC)) {
for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) {
if (tx_chs->port == SLAVE_SB_PGD_PORT_TX1_FM)
tx_chs->port = CHRKVER3_SB_PGD_PORT_TX1_FM;
else if (tx_chs->port == SLAVE_SB_PGD_PORT_TX2_FM)
tx_chs->port = CHRKVER3_SB_PGD_PORT_TX2_FM;
BTFMSLIM_INFO("Tx port:%d", tx_chs->port);
}
tx_chs = btfmslim->tx_chs;
}
if (!rx_chs || !tx_chs)
return ret;
return 0;
}
static int btfm_slim_get_logical_addr(struct slim_device *slim)
{
int ret = 0;
const unsigned long timeout = jiffies +
msecs_to_jiffies(SLIM_SLAVE_PRESENT_TIMEOUT);
BTFMSLIM_INFO("");
do {
ret = slim_get_logical_addr(slim);
if (!ret) {
BTFMSLIM_DBG("Assigned l-addr: 0x%x", slim->laddr);
break;
}
/* Give SLIMBUS time to report present and be ready. */
usleep_range(1000, 1100);
BTFMSLIM_DBG("retyring get logical addr");
} while (time_before(jiffies, timeout));
return ret;
}
int btfm_slim_hw_init(struct btfmslim *btfmslim)
{
int ret = -1;
int chipset_ver;
struct slim_device *slim;
struct slim_device *slim_ifd;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
if (btfmslim->enabled) {
BTFMSLIM_DBG("Already enabled");
return 0;
}
slim = btfmslim->slim_pgd;
slim_ifd = &btfmslim->slim_ifd;
mutex_lock(&btfmslim->io_lock);
BTFMSLIM_INFO(
"PGD Enum Addr: mfr id:%.02x prod code:%.02x dev ind:%.02x ins:%.02x",
slim->e_addr.manf_id, slim->e_addr.prod_code,
slim->e_addr.dev_index, slim->e_addr.instance);
chipset_ver = btpower_get_chipset_version();
BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
if (chipset_ver == QCA_HSP_SOC_ID_0100 ||
chipset_ver == QCA_HSP_SOC_ID_0110 ||
chipset_ver == QCA_HSP_SOC_ID_0200 ||
chipset_ver == QCA_HSP_SOC_ID_0210 ||
chipset_ver == QCA_HSP_SOC_ID_1201 ||
chipset_ver == QCA_HSP_SOC_ID_1211) {
BTFMSLIM_INFO("chipset is hastings prime, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = SLIM_PROD_CODE;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = SLIM_PROD_CODE;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
} else if (chipset_ver == QCA_MOSELLE_SOC_ID_0100 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0110 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0120) {
BTFMSLIM_INFO("chipset is Moselle, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = 0x222;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = 0x222;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
} else if (chipset_ver == QCA_HAMILTON_SOC_ID_0100 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0101 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0200) {
BTFMSLIM_INFO("chipset is Hamliton, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = 0x220;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = 0x220;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
} else if (chipset_ver == QCA_CHEROKEE_SOC_ID_0200 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0201 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0210 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0211 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0310 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0320 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0320_UMC ||
chipset_ver == QCA_APACHE_SOC_ID_0100 ||
chipset_ver == QCA_APACHE_SOC_ID_0110 ||
chipset_ver == QCA_APACHE_SOC_ID_0120 ||
chipset_ver == QCA_APACHE_SOC_ID_0121 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0101 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0110 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0120 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0130 ||
chipset_ver == QCA_COMANCHE_SOC_ID_4130 ||
chipset_ver == QCA_COMANCHE_SOC_ID_5120 ||
chipset_ver == QCA_COMANCHE_SOC_ID_5130 ) {
BTFMSLIM_INFO("chipset is Chk/Apache/CMC, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = 0x220;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = 0x220;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
}
BTFMSLIM_INFO(
"PGD Enum Addr: manu id:%.02x prod code:%.02x dev idx:%.02x instance:%.02x",
slim->e_addr.manf_id, slim->e_addr.prod_code,
slim->e_addr.dev_index, slim->e_addr.instance);
BTFMSLIM_INFO(
"IFD Enum Addr: manu id:%.02x prod code:%.02x dev idx:%.02x instance:%.02x",
slim_ifd->e_addr.manf_id, slim_ifd->e_addr.prod_code,
slim_ifd->e_addr.dev_index, slim_ifd->e_addr.instance);
/* Assign Logical Address for PGD (Ported Generic Device)
* enumeration address
*/
ret = btfm_slim_get_logical_addr(btfmslim->slim_pgd);
if (ret) {
BTFMSLIM_ERR("failed to get slimbus logical address: %d", ret);
goto error;
}
/* Assign Logical Address for Ported Generic Device
* enumeration address
*/
ret = btfm_slim_get_logical_addr(&btfmslim->slim_ifd);
if (ret) {
BTFMSLIM_ERR("failed to get slimbus logical address: %d", ret);
goto error;
}
ret = btfm_slim_alloc_port(btfmslim);
if (ret != 0)
goto error;
/* Start vendor specific initialization and get port information */
if (btfmslim->vendor_init)
ret = btfmslim->vendor_init(btfmslim);
/* Only when all registers read/write successfully, it set to
* enabled status
*/
btfmslim->enabled = 1;
error:
mutex_unlock(&btfmslim->io_lock);
return ret;
}
int btfm_slim_hw_deinit(struct btfmslim *btfmslim)
{
int ret = 0;
BTFMSLIM_INFO("");
if (!btfmslim)
return -EINVAL;
if (!btfmslim->enabled) {
BTFMSLIM_DBG("Already disabled");
return 0;
}
mutex_lock(&btfmslim->io_lock);
btfmslim->enabled = 0;
mutex_unlock(&btfmslim->io_lock);
return ret;
}
#if IS_ENABLED (CONFIG_BTFM_SLIM)
void btfm_slim_get_hwep_details(struct slim_device *dev, struct btfmslim *btfm_slim)
{
}
#else
void btfm_slim_get_hwep_details(struct slim_device *slim, struct btfmslim *btfm_slim)
{
struct device_node *np = slim->dev.of_node;
const __be32 *prop;
struct btfmslim_ch *rx_chs = btfm_slim->rx_chs;
struct btfmslim_ch *tx_chs = btfm_slim->tx_chs;
int len;
prop = of_get_property(np, "qcom,btslim-address", &len);
if (prop) {
btfm_slim->device_id = be32_to_cpup(&prop[0]);
BTFMSLIM_DBG("hwep slim address define in dt %08x", btfm_slim->device_id);
} else {
BTFMSLIM_ERR("btslim-address is not defined in dt using default address");
btfm_slim->device_id = 0;
}
if (!rx_chs || !tx_chs) {
BTFMSLIM_ERR("either rx/tx channels are configured to null");
return;
}
prop = of_get_property(np, "qcom,btslimrx-channels", &len);
if (prop) {
/* Check if we need any protection for index */
rx_chs[0].ch = (uint8_t)be32_to_cpup(&prop[0]);
rx_chs[1].ch = (uint8_t)be32_to_cpup(&prop[1]);
BTFMSLIM_DBG("Rx: id\tname\tport\tch");
BTFMSLIM_DBG(" %d\t%s\t%d\t%d", rx_chs[0].id,
rx_chs[0].name, rx_chs[0].port,
rx_chs[0].ch);
BTFMSLIM_DBG(" %d\t%s\t%d\t%d", rx_chs[1].id,
rx_chs[1].name, rx_chs[1].port,
rx_chs[1].ch);
} else {
BTFMSLIM_ERR("btslimrx channels are missing in dt using default values");
}
prop = of_get_property(np, "qcom,btslimtx-channels", &len);
if (prop) {
/* Check if we need any protection for index */
tx_chs[0].ch = (uint8_t)be32_to_cpup(&prop[0]);
tx_chs[1].ch = (uint8_t)be32_to_cpup(&prop[1]);
BTFMSLIM_DBG("Tx: id\tname\tport\tch");
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", tx_chs[0].id,
tx_chs[0].name, tx_chs[0].port,
tx_chs[0].ch);
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", tx_chs[1].id,
tx_chs[1].name, tx_chs[1].port,
tx_chs[1].ch);
} else {
BTFMSLIM_ERR("btslimtx channels are missing in dt using default values");
}
}
#endif
static int btfm_slim_status(struct slim_device *sdev,
enum slim_device_status status)
{
int ret = 0;
struct device *dev = &sdev->dev;
struct btfmslim *btfm_slim;
btfm_slim = dev_get_drvdata(dev);
#if IS_ENABLED(CONFIG_BTFM_SLIM)
if (!is_registered) {
ret = btfm_slim_register_codec(btfm_slim);
}
#else
if (!is_registered) {
btfm_slim_get_hwep_details(sdev, btfm_slim);
ret = btfm_slim_register_hw_ep(btfm_slim);
}
#endif
if (!ret)
is_registered = true;
else
BTFMSLIM_ERR("error, registering slimbus codec failed");
return ret;
}
static long btfm_slim_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch (cmd) {
case BT_CMD_SLIM_TEST:
BTFMSLIM_INFO("cmd BT_CMD_SLIM_TEST, call btfm_slim_hw_init");
ret = btfm_slim_hw_init(btfm_slim_drv_data);
break;
}
return ret;
}
static const struct file_operations bt_dev_fops = {
.unlocked_ioctl = btfm_slim_ioctl,
.compat_ioctl = btfm_slim_ioctl,
};
static int btfm_slim_probe(struct slim_device *slim)
{
int ret = 0;
struct btfmslim *btfm_slim;
pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
/*this as true during the probe then slimbus won't check for logical address*/
slim->is_laddr_valid = true;
is_registered = false;
dev_set_name(&slim->dev, "%s", BTFMSLIM_DEV_NAME);
pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
BTFMSLIM_DBG("");
BTFMSLIM_ERR("is_laddr_valid is true");
if (!slim->ctrl)
return -EINVAL;
/* Allocation btfmslim data pointer */
btfm_slim = kzalloc(sizeof(struct btfmslim), GFP_KERNEL);
if (btfm_slim == NULL) {
BTFMSLIM_ERR("error, allocation failed");
return -ENOMEM;
}
/* BTFM Slimbus driver control data configuration */
btfm_slim->slim_pgd = slim;
/* Assign vendor specific function */
btfm_slim->rx_chs = SLIM_SLAVE_RXPORT;
btfm_slim->tx_chs = SLIM_SLAVE_TXPORT;
btfm_slim->vendor_init = SLIM_SLAVE_INIT;
btfm_slim->vendor_port_en = SLIM_SLAVE_PORT_EN;
/* Created Mutex for slimbus data transfer */
mutex_init(&btfm_slim->io_lock);
mutex_init(&btfm_slim->xfer_lock);
dev_set_drvdata(&slim->dev, btfm_slim);
/* Driver specific data allocation */
btfm_slim->dev = &slim->dev;
ret = btpower_register_slimdev(&slim->dev);
if (ret < 0) {
#if IS_ENABLED(CONFIG_BTFM_SLIM)
btfm_slim_unregister_codec(&slim->dev);
#else
btfm_slim_unregister_hwep();
#endif
ret = -EPROBE_DEFER;
goto dealloc;
}
btfm_slim_drv_data = btfm_slim;
btfm_slim_major = register_chrdev(0, "btfm_slim", &bt_dev_fops);
if (btfm_slim_major < 0) {
BTFMSLIM_ERR("%s: failed to allocate char dev\n", __func__);
ret = -1;
goto register_err;
}
btfm_slim_class = class_create(THIS_MODULE, "btfmslim-dev");
if (IS_ERR(btfm_slim_class)) {
BTFMSLIM_ERR("%s: coudn't create class\n", __func__);
ret = -1;
goto class_err;
}
if (device_create(btfm_slim_class, NULL, MKDEV(btfm_slim_major, 0),
NULL, "btfmslim") == NULL) {
BTFMSLIM_ERR("%s: failed to allocate char dev\n", __func__);
ret = -1;
goto device_err;
}
return ret;
device_err:
class_destroy(btfm_slim_class);
class_err:
unregister_chrdev(btfm_slim_major, "btfm_slim");
register_err:
#if IS_ENABLED(CONFIG_BTFM_SLIM)
btfm_slim_unregister_codec(&slim->dev);
#else
btfm_slim_unregister_hwep();
#endif
dealloc:
mutex_destroy(&btfm_slim->io_lock);
mutex_destroy(&btfm_slim->xfer_lock);
kfree(btfm_slim);
return ret;
}
static void btfm_slim_remove(struct slim_device *slim)
{
struct device *dev = &slim->dev;
struct btfmslim *btfm_slim = dev_get_drvdata(dev);
BTFMSLIM_DBG("");
mutex_destroy(&btfm_slim->io_lock);
mutex_destroy(&btfm_slim->xfer_lock);
snd_soc_unregister_component(&slim->dev);
kfree(btfm_slim);
}
static const struct slim_device_id btfm_slim_id[] = {
{
.manf_id = SLIM_MANF_ID_QCOM,
.prod_code = SLIM_PROD_CODE,
.dev_index = 0x1,
.instance = 0x0,
},
{
.manf_id = SLIM_MANF_ID_QCOM,
.prod_code = 0x220,
.dev_index = 0x1,
.instance = 0x0,
}
};
MODULE_DEVICE_TABLE(slim, btfm_slim_id);
static struct slim_driver btfm_slim_driver = {
.driver = {
.name = "btfmslim-driver",
.owner = THIS_MODULE,
},
.probe = btfm_slim_probe,
.device_status = btfm_slim_status,
.remove = btfm_slim_remove,
.id_table = btfm_slim_id
};
module_slim_driver(btfm_slim_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("BTFM Slimbus Slave driver");

View File

@ -0,0 +1,176 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef BTFM_SLIM_H
#define BTFM_SLIM_H
#include <linux/slimbus.h>
#define BTFMSLIM_DBG(fmt, arg...) pr_debug("%s: " fmt "\n", __func__, ## arg)
#define BTFMSLIM_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg)
#define BTFMSLIM_ERR(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg)
/* Vendor specific defines
* This should redefines in slimbus slave specific header
*/
#define SLIM_SLAVE_COMPATIBLE_STR "btfmslim_slave"
#define SLIM_SLAVE_REG_OFFSET 0x0000
#define SLIM_SLAVE_RXPORT NULL
#define SLIM_SLAVE_TXPORT NULL
#define SLIM_SLAVE_INIT NULL
#define SLIM_SLAVE_PORT_EN NULL
/* Misc defines */
#define SLIM_SLAVE_RW_MAX_TRIES 3
#define SLIM_SLAVE_PRESENT_TIMEOUT 100
#define PGD 1
#define IFD 0
#if IS_ENABLED(CONFIG_BTFM_SLIM)
#define BTFMSLIM_DEV_NAME "btfmslim_slave"
#else
#define BTFMSLIM_DEV_NAME "btfmslim"
#endif
/* Codec driver defines */
enum {
BTFM_FM_SLIM_TX = 0,
BTFM_BT_SCO_SLIM_TX,
BTFM_BT_SCO_A2DP_SLIM_RX,
BTFM_BT_SPLIT_A2DP_SLIM_RX,
BTFM_SLIM_NUM_CODEC_DAIS
};
struct btfm_slim_codec_dai_data {
struct slim_stream_config sconfig;
struct slim_stream_runtime *sruntime;
};
struct btfmslim_ch {
int id;
char *name;
uint16_t port; /* slimbus port number */
uint8_t ch; /* slimbus channel number */
struct btfm_slim_codec_dai_data dai;
};
/* Slimbus Port defines - This should be redefined in specific device file */
#define BTFM_SLIM_PGD_PORT_LAST 0xFF
struct btfmslim {
struct device *dev;
struct slim_device *slim_pgd; //Physical address
struct slim_device slim_ifd; //Interface address
struct mutex io_lock;
struct mutex xfer_lock;
uint8_t enabled;
uint32_t num_rx_port;
uint32_t num_tx_port;
uint32_t sample_rate;
uint32_t bps;
uint16_t direction;
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
int (*vendor_init)(struct btfmslim *btfmslim);
int (*vendor_port_en)(struct btfmslim *btfmslim, uint8_t port_num,
uint8_t rxport, uint8_t enable);
#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC)
int device_id;
#endif
};
extern int btfm_feedback_ch_setting;
/**
* btfm_slim_hw_init: Initialize slimbus slave device
* Returns:
* 0: Success
* else: Fail
*/
int btfm_slim_hw_init(struct btfmslim *btfmslim);
/**
* btfm_slim_hw_deinit: Deinitialize slimbus slave device
* Returns:
* 0: Success
* else: Fail
*/
int btfm_slim_hw_deinit(struct btfmslim *btfmslim);
/**
* btfm_slim_write: write value to pgd or ifd device
* @btfmslim: slimbus slave device data pointer.
* @reg: slimbus slave register address
* @reg_val: value to write at register address
* @pgd: selection for device: either PGD or IFD
* Returns:
No of bytes written
-1
*/
int btfm_slim_write(struct btfmslim *btfmslim,
uint16_t reg, uint8_t reg_val, uint8_t pgd);
/**
* btfm_slim_read: read value from pgd or ifd device
* @btfmslim: slimbus slave device data pointer.
* @reg: slimbus slave register address
* @dest: data pointer to read
* @pgd: selection for device: either PGD or IFD
* Returns:
No of bytes read
-1
*/
int btfm_slim_read(struct btfmslim *btfmslim,
uint32_t reg, uint8_t pgd);
/**
* btfm_slim_enable_ch: enable channel for slimbus slave port
* @btfmslim: slimbus slave device data pointer.
* @ch: slimbus slave channel pointer
* @rxport: rxport or txport
* Returns:
* -EINVAL
* -ETIMEDOUT
* -ENOMEM
*/
int btfm_slim_enable_ch(struct btfmslim *btfmslim,
struct btfmslim_ch *ch, uint8_t rxport, uint32_t rates,
uint8_t nchan);
/**
* btfm_slim_disable_ch: disable channel for slimbus slave port
* @btfmslim: slimbus slave device data pointer.
* @ch: slimbus slave channel pointer
* @rxport: rxport or txport
* @nChan: number of chaneels.
* Returns:
* -EINVAL
* -ETIMEDOUT
* -ENOMEM
*/
int btfm_slim_disable_ch(struct btfmslim *btfmslim,
struct btfmslim_ch *ch, uint8_t rxport, uint8_t nchan);
/**
* btfm_slim_register_codec: Register codec driver in slimbus device node
* @btfmslim: slimbus slave device data pointer.
* Returns:
* -ENOMEM
* 0
*/
int btfm_slim_register_codec(struct btfmslim *btfmslim);
/**
* btfm_slim_unregister_codec: Unregister codec driver in slimbus device node
* @dev: device node
* Returns:
* VOID
*/
void btfm_slim_unregister_codec(struct device *dev);
#endif /* BTFM_SLIM_H */

View File

@ -0,0 +1,466 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/slimbus.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "btfm_slim.h"
static int bt_soc_enable_status;
int btfm_feedback_ch_setting;
static int btfm_slim_codec_write(struct snd_soc_component *codec,
unsigned int reg, unsigned int value)
{
BTFMSLIM_DBG("");
return 0;
}
static unsigned int btfm_slim_codec_read(struct snd_soc_component *codec,
unsigned int reg)
{
BTFMSLIM_DBG("");
return 0;
}
static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
ucontrol->value.integer.value[0] = bt_soc_enable_status;
return 1;
}
static int btfm_soc_status_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
return 1;
}
static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
ucontrol->value.integer.value[0] = btfm_feedback_ch_setting;
return 1;
}
static int btfm_put_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
btfm_feedback_ch_setting = ucontrol->value.integer.value[0];
return 1;
}
static const struct snd_kcontrol_new status_controls[] = {
SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0,
btfm_soc_status_get,
btfm_soc_status_put),
SOC_SINGLE_EXT("BT set feedback channel", 0, 0, 1, 0,
btfm_get_feedback_ch_setting,
btfm_put_feedback_ch_setting)
};
static int btfm_slim_codec_probe(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
snd_soc_add_component_controls(codec, status_controls,
ARRAY_SIZE(status_controls));
return 0;
}
static void btfm_slim_codec_remove(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
}
static int btfm_slim_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = -1;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
BTFMSLIM_DBG("substream = %s stream = %d dai->name = %s",
substream->name, substream->stream, dai->name);
ret = btfm_slim_hw_init(btfmslim);
return ret;
}
static void btfm_slim_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int i;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
struct btfmslim_ch *ch;
uint8_t rxport, nchan = 1;
BTFMSLIM_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
dai->id, dai->rate);
switch (dai->id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("dai->id is invalid:%d", dai->id);
return;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != dai->id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return;
}
btfm_slim_disable_ch(btfmslim, ch, rxport, nchan);
btfm_slim_hw_deinit(btfmslim);
}
static int btfm_slim_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct btfmslim *btfmslim;
btfmslim = snd_soc_component_get_drvdata(dai->component);
btfmslim->bps = params_width(params);
btfmslim->direction = substream->stream;
BTFMSLIM_DBG("dai->name = %s DAI-ID %x rate %d bps %d num_ch %d",
dai->name, dai->id, params_rate(params), params_width(params),
params_channels(params));
return 0;
}
static int btfm_slim_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = -EINVAL;
int i = 0;
struct btfmslim_ch *ch;
uint8_t rxport, nchan = 1;
struct btfmslim *btfmslim;
btfmslim = snd_soc_component_get_drvdata(dai->component);
btfmslim->direction = substream->stream;
bt_soc_enable_status = 0;
BTFMSLIM_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d", dai->name,
dai->id, dai->rate, btfmslim->direction);
/* save sample rate */
btfmslim->sample_rate = dai->rate;
switch (dai->id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("dai->id is invalid:%d", dai->id);
return ret;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != dai->id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return ret;
}
ret = btfm_slim_enable_ch(btfmslim, ch, rxport, dai->rate, nchan);
/* save the enable channel status */
if (ret == 0)
bt_soc_enable_status = 1;
if (ret == -EISCONN) {
BTFMSLIM_ERR("channel opened without closing, returning success");
ret = 0;
}
return ret;
}
/* This function will be called once during boot up */
static int btfm_slim_dai_set_channel_map(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
int ret = 0, i;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
rx_chs = btfmslim->rx_chs;
tx_chs = btfmslim->tx_chs;
if (!rx_chs || !tx_chs)
return ret;
BTFMSLIM_DBG("Rx: id\tname\tport\tch");
for (i = 0; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < rx_num);
i++, rx_chs++) {
/* Set Rx Channel number from machine driver and
* get channel handler from slimbus driver
*/
rx_chs->ch = *(uint8_t *)(rx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%d\t", rx_chs->id,
rx_chs->name, rx_chs->port, rx_chs->ch);
}
BTFMSLIM_DBG("Tx: id\tname\tport\tch");
for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num);
i++, tx_chs++) {
/* Set Tx Channel number from machine driver and
* get channel handler from slimbus driver
*/
tx_chs->ch = *(uint8_t *)(tx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%d\t", tx_chs->id,
tx_chs->name, tx_chs->port, tx_chs->ch);
}
return ret;
}
static int btfm_slim_dai_get_channel_map(struct snd_soc_dai *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot)
{
int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
struct btfmslim_ch *ch = NULL;
if (!btfmslim)
return ret;
switch (dai->id) {
case BTFM_FM_SLIM_TX:
num = 2;
/* fall through */
fallthrough;
case BTFM_BT_SCO_SLIM_TX:
if (!tx_slot || !tx_num) {
BTFMSLIM_ERR("Invalid tx_slot %p or tx_num %p",
tx_slot, tx_num);
return -EINVAL;
}
ch = btfmslim->tx_chs;
if (!ch)
return -EINVAL;
slot = tx_slot;
*rx_slot = 0;
*tx_num = num;
*rx_num = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
if (!rx_slot || !rx_num) {
BTFMSLIM_ERR("Invalid rx_slot %p or rx_num %p",
rx_slot, rx_num);
return -EINVAL;
}
ch = btfmslim->rx_chs;
if (!ch)
return -EINVAL;
slot = rx_slot;
*tx_slot = 0;
*tx_num = 0;
*rx_num = num;
break;
default:
BTFMSLIM_ERR("Unsupported DAI %d", dai->id);
return -EINVAL;
}
do {
if (!ch)
return -EINVAL;
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id !=
BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != dai->id);
ch++, i++)
;
if (ch->id == BTFM_SLIM_NUM_CODEC_DAIS ||
i == BTFM_SLIM_NUM_CODEC_DAIS) {
BTFMSLIM_ERR(
"No channel has been allocated for dai (%d)",
dai->id);
return -EINVAL;
}
if (!slot)
return -EINVAL;
*(slot + j) = ch->ch;
BTFMSLIM_DBG("id:%d, port:%d, ch:%d, slot: %d", ch->id,
ch->port, ch->ch, *(slot + j));
/* In case it has mulitiple channels */
if (++j < num)
ch++;
} while (j < num);
return 0;
}
static struct snd_soc_dai_ops btfmslim_dai_ops = {
.startup = btfm_slim_dai_startup,
.shutdown = btfm_slim_dai_shutdown,
.hw_params = btfm_slim_dai_hw_params,
.prepare = btfm_slim_dai_prepare,
.set_channel_map = btfm_slim_dai_set_channel_map,
.get_channel_map = btfm_slim_dai_get_channel_map,
};
static struct snd_soc_dai_driver btfmslim_dai[] = {
{ /* FM Audio data multiple channel : FM -> qdsp */
.name = "btfm_fm_slim_tx",
.id = BTFM_FM_SLIM_TX,
.capture = {
.stream_name = "FM TX Capture",
.rates = SNDRV_PCM_RATE_48000, /* 48 KHz */
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 48000,
.rate_min = 48000,
.channels_min = 1,
.channels_max = 2,
},
.ops = &btfmslim_dai_ops,
},
{ /* Bluetooth SCO voice uplink: bt -> lpass */
.name = "btfm_bt_sco_slim_tx",
.id = BTFM_BT_SCO_SLIM_TX,
.capture = {
.stream_name = "SCO TX Capture",
/* 8 KHz or 16 KHz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.ops = &btfmslim_dai_ops,
},
{ /* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
.name = "btfm_bt_sco_a2dp_slim_rx",
.id = BTFM_BT_SCO_A2DP_SLIM_RX,
.playback = {
.stream_name = "SCO A2DP RX Playback",
/* 8/16/44.1/48/88.2/96 Khz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.ops = &btfmslim_dai_ops,
},
{ /* Bluetooth Split A2DP data: qdsp -> bt */
.name = "btfm_bt_split_a2dp_slim_rx",
.id = BTFM_BT_SPLIT_A2DP_SLIM_RX,
.playback = {
.stream_name = "SPLIT A2DP Playback",
.rates = SNDRV_PCM_RATE_48000, /* 48 KHz */
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 48000,
.rate_min = 48000,
.channels_min = 1,
.channels_max = 1,
},
.ops = &btfmslim_dai_ops,
},
};
static const struct snd_soc_component_driver btfmslim_codec = {
.probe = btfm_slim_codec_probe,
.remove = btfm_slim_codec_remove,
.read = btfm_slim_codec_read,
.write = btfm_slim_codec_write,
};
int btfm_slim_register_codec(struct btfmslim *btfm_slim)
{
int ret = 0;
struct device *dev = btfm_slim->dev;
BTFMSLIM_DBG("");
dev_err(dev, "\n");
/* Register Codec driver */
ret = snd_soc_register_component(dev, &btfmslim_codec,
btfmslim_dai, ARRAY_SIZE(btfmslim_dai));
if (ret)
BTFMSLIM_ERR("failed to register codec (%d)", ret);
return ret;
}
void btfm_slim_unregister_codec(struct device *dev)
{
BTFMSLIM_DBG("");
/* Unregister Codec driver */
snd_soc_unregister_component(dev);
}
MODULE_DESCRIPTION("BTFM Slimbus Codec driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,546 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021, 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/slimbus.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "btfm_slim.h"
#include "btfm_slim_hw_interface.h"
#include "btfm_codec_hw_interface.h"
static int bt_soc_enable_status;
int btfm_feedback_ch_setting;
static uint8_t usecase_codec;
static int btfm_slim_hwep_write(struct snd_soc_component *codec,
unsigned int reg, unsigned int value)
{
BTFMSLIM_DBG("");
return 0;
}
static unsigned int btfm_slim_hwep_read(struct snd_soc_component *codec,
unsigned int reg)
{
BTFMSLIM_DBG("");
return 0;
}
static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
ucontrol->value.integer.value[0] = bt_soc_enable_status;
return 1;
}
static int btfm_soc_status_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
return 1;
}
static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
ucontrol->value.integer.value[0] = btfm_feedback_ch_setting;
return 1;
}
static int btfm_put_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("");
btfm_feedback_ch_setting = ucontrol->value.integer.value[0];
return 1;
}
static int btfm_get_codec_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("current codec type:%s", codec_text[usecase_codec]);
ucontrol->value.integer.value[0] = usecase_codec;
return 1;
}
static int btfm_put_codec_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
usecase_codec = ucontrol->value.integer.value[0];
BTFMSLIM_DBG("codec type set to:%s", codec_text[usecase_codec]);
return 1;
}
static struct snd_kcontrol_new status_controls[] = {
SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0,
btfm_soc_status_get, btfm_soc_status_put),
SOC_SINGLE_EXT("BT set feedback channel", 0, 0, 1, 0,
btfm_get_feedback_ch_setting,
btfm_put_feedback_ch_setting),
SOC_ENUM_EXT("BT codec type", codec_display,
btfm_get_codec_type, btfm_put_codec_type),
};
static int btfm_slim_hwep_probe(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
return 0;
}
static void btfm_slim_hwep_remove(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
}
static int btfm_slim_dai_startup(void *dai)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
int ret = -1;
BTFMSLIM_DBG("");
ret = btfm_slim_hw_init(btfmslim);
return ret;
}
static void btfm_slim_dai_shutdown(void *dai, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct btfmslim_ch *ch;
int i;
uint8_t rxport, nchan = 1;
BTFMSLIM_DBG("");
switch (id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("id is invalid:%d", id);
return;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return;
}
btfm_slim_disable_ch(btfmslim, ch, rxport, nchan);
btfm_slim_hw_deinit(btfmslim);
}
static int btfm_slim_dai_hw_params(void *dai, uint32_t bps,
uint32_t direction,
uint8_t num_channels) {
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
BTFMSLIM_DBG("");
btfmslim->bps = bps;
btfmslim->direction = direction;
return 0;
}
void btfm_get_sampling_rate(uint32_t *sampling_rate)
{
uint8_t codec_types_avb = ARRAY_SIZE(codec_text);
if (usecase_codec > (codec_types_avb - 1)) {
BTFMSLIM_ERR("falling back to use default sampling_rate: %u",
*sampling_rate);
return;
}
if (*sampling_rate == 44100 || *sampling_rate == 48000) {
if (usecase_codec == LDAC ||
usecase_codec == APTX_AD)
*sampling_rate = (*sampling_rate) *2;
}
if (usecase_codec == LC3_VOICE ||
usecase_codec == APTX_AD_SPEECH ||
usecase_codec == LC3 || usecase_codec == APTX_AD_R4) {
*sampling_rate = 96000;
}
if (usecase_codec == APTX_AD_QLEA)
*sampling_rate = 192000;
BTFMSLIM_INFO("current usecase codec type %s and sampling rate:%u khz",
codec_text[usecase_codec], *sampling_rate);
}
static int btfm_slim_dai_prepare(void *dai, uint32_t sampling_rate, uint32_t direction, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct btfmslim_ch *ch;
int ret = -EINVAL;
int i = 0;
uint8_t rxport, nchan = 1;
btfmslim->direction = direction;
bt_soc_enable_status = 0;
btfm_get_sampling_rate(&sampling_rate);
/* save sample rate */
btfmslim->sample_rate = sampling_rate;
switch (id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("id is invalid:%d", id);
return ret;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return ret;
}
ret = btfm_slim_enable_ch(btfmslim, ch, rxport, sampling_rate, nchan);
/* save the enable channel status */
if (ret == 0)
bt_soc_enable_status = 1;
if (ret == -EISCONN) {
BTFMSLIM_ERR("channel opened without closing, returning success");
ret = 0;
}
return ret;
}
/* This function will be called once during boot up */
static int btfm_slim_dai_set_channel_map(void *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
int ret = 0, i;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
rx_chs = btfmslim->rx_chs;
tx_chs = btfmslim->tx_chs;
if (!rx_chs || !tx_chs)
return ret;
BTFMSLIM_DBG("Rx: id\tname\tport\tch");
for (i = 0; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < rx_num);
i++, rx_chs++) {
/* Set Rx Channel number from machine driver and
* get channel handler from slimbus driver
*/
rx_chs->ch = *(uint8_t *)(rx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", rx_chs->id,
rx_chs->name, rx_chs->port, rx_chs->ch);
}
BTFMSLIM_DBG("Tx: id\tname\tport\tch");
for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num);
i++, tx_chs++) {
/* Set Tx Channel number from machine driver and
* get channel handler from slimbus driver
*/
tx_chs->ch = *(uint8_t *)(tx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", tx_chs->id,
tx_chs->name, tx_chs->port, tx_chs->ch);
}
return ret;
}
static int btfm_slim_dai_get_channel_map(void *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1;
struct btfmslim_ch *ch = NULL;
BTFMSLIM_DBG("");
if (!btfmslim)
return ret;
switch (id) {
case BTFM_FM_SLIM_TX:
num = 2;
fallthrough;
case BTFM_BT_SCO_SLIM_TX:
if (!tx_slot || !tx_num) {
BTFMSLIM_ERR("Invalid tx_slot %p or tx_num %p",
tx_slot, tx_num);
return -EINVAL;
}
ch = btfmslim->tx_chs;
if (!ch)
return -EINVAL;
slot = tx_slot;
*rx_slot = 0;
*tx_num = num;
*rx_num = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
if (!rx_slot || !rx_num) {
BTFMSLIM_ERR("Invalid rx_slot %p or rx_num %p",
rx_slot, rx_num);
return -EINVAL;
}
ch = btfmslim->rx_chs;
if (!ch)
return -EINVAL;
slot = rx_slot;
*tx_slot = 0;
*tx_num = 0;
*rx_num = num;
break;
default:
BTFMSLIM_ERR("Unsupported DAI %d", id);
return -EINVAL;
}
do {
if (!ch)
return -EINVAL;
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id !=
BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != id);
ch++, i++)
;
if (ch->id == BTFM_SLIM_NUM_CODEC_DAIS ||
i == BTFM_SLIM_NUM_CODEC_DAIS) {
BTFMSLIM_ERR(
"No channel has been allocated for dai (%d)",
id);
return -EINVAL;
}
if (!slot)
return -EINVAL;
*(slot + j) = ch->ch;
BTFMSLIM_DBG("id:%d, port:%d, ch:%d, slot: %d", ch->id,
ch->port, ch->ch, *(slot + j));
/* In case it has mulitiple channels */
if (++j < num)
ch++;
} while (j < num);
return 0;
}
int btfm_slim_dai_get_configs(void *dai, void *config, uint8_t id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct master_hwep_configurations *hwep_config;
struct btfmslim_ch *ch = NULL;
int i = 0;
BTFMSLIM_DBG("");
hwep_config = (struct master_hwep_configurations *) config;
hwep_config->stream_id = id;
hwep_config->device_id = btfmslim->device_id;
hwep_config->sample_rate = btfmslim->sample_rate;
hwep_config->bit_width = (uint8_t)btfmslim->bps;
hwep_config->codectype = usecase_codec;
hwep_config->direction = btfmslim->direction;
switch (id) {
case BTFM_FM_SLIM_TX:
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
break;
}
for (; i < id ; i++) {
if (ch[i].id == id) {
BTFMSLIM_DBG("id matched");
hwep_config->num_channels = 1;
hwep_config->chan_num = ch[i].ch;
break;
}
}
return 1;
}
static struct hwep_dai_ops btfmslim_hw_dai_ops = {
.hwep_startup = btfm_slim_dai_startup,
.hwep_shutdown = btfm_slim_dai_shutdown,
.hwep_hw_params = btfm_slim_dai_hw_params,
.hwep_prepare = btfm_slim_dai_prepare,
.hwep_set_channel_map = btfm_slim_dai_set_channel_map,
.hwep_get_channel_map = btfm_slim_dai_get_channel_map,
.hwep_get_configs = btfm_slim_dai_get_configs,
.hwep_codectype = &usecase_codec,
};
static struct hwep_dai_driver btfmslim_dai_driver[] = {
{ /* Bluetooth SCO voice uplink: bt -> lpass */
.dai_name = "btaudio_tx",
.id = BTAUDIO_TX,
.capture = {
.stream_name = "BT Audio Slim Tx Capture",
/* 8 KHz or 16 KHz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.dai_ops = &btfmslim_hw_dai_ops,
},
{ /* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
.dai_name = "btaudio_rx",
.id = BTAUDIO_RX,
.playback = {
.stream_name = "BT Audio Slim Rx Playback",
/* 8/16/44.1/48/88.2/96 Khz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.dai_ops = &btfmslim_hw_dai_ops,
},
};
static struct hwep_comp_drv btfmslim_hw_driver = {
.hwep_probe = btfm_slim_hwep_probe,
.hwep_remove = btfm_slim_hwep_remove,
.hwep_read = btfm_slim_hwep_read,
.hwep_write = btfm_slim_hwep_write,
};
int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim)
{
struct device *dev = btfm_slim->dev;
struct hwep_data *hwep_info;
int ret = 0;
BTFMSLIM_INFO("Registering with BTFMCODEC HWEP interface\n");
hwep_info = kzalloc(sizeof(struct hwep_data), GFP_KERNEL);
if (!hwep_info) {
BTFMSLIM_ERR("%s: failed to allocate memory\n", __func__);
ret = -ENOMEM;
goto end;
}
/* Copy EP device parameters as intercations will be on the same device */
hwep_info->dev = dev;
strlcpy(hwep_info->driver_name, BTFMSLIM_DEV_NAME, DEVICE_NAME_MAX_LEN);
hwep_info->drv = &btfmslim_hw_driver;
hwep_info->dai_drv = btfmslim_dai_driver;
hwep_info->num_dai = ARRAY_SIZE(btfmslim_dai_driver);
hwep_info->num_dai = 2;
hwep_info->num_mixer_ctrl = ARRAY_SIZE(status_controls);
hwep_info->mixer_ctrl = status_controls;
/* Register to hardware endpoint */
ret = btfmcodec_register_hw_ep(hwep_info);
if (ret) {
BTFMSLIM_ERR("failed to register with btfmcodec driver hw interface (%d)", ret);
goto end;
}
BTFMSLIM_INFO("Registered succesfull with BTFMCODEC HWEP interface\n");
return ret;
end:
return ret;
}
void btfm_slim_unregister_hwep(void)
{
BTFMSLIM_INFO("Unregistered with BTFMCODEC HWEP interface");
/* Unregister with BTFMCODEC HWEP driver */
btfmcodec_unregister_hw_ep(BTFMSLIM_DEV_NAME);
}
MODULE_DESCRIPTION("BTFM Slimbus driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __LINUX_BTFM_SLIM_HW_INTERFACE_H
#define __LINUX_BTFM_SLIM_HW_INTERFACE_H
// Todo protect with flags
int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim);
void btfm_slim_unregister_hwep(void);
/* Codec driver defines */
enum {
BTAUDIO_TX = 1,
BTAUDIO_RX = 2,
BTAUDIO_NUM_CODEC_DAIS
};
typedef enum Codec {
SBC = 0,
AAC,
LDAC,
APTX,
APTX_HD,
APTX_AD,
LC3,
APTX_AD_SPEECH,
LC3_VOICE,
APTX_AD_QLEA,
APTX_AD_R4,
NO_CODEC
} codectype;
static char const *codec_text[] = {"CODEC_TYPE_SBC", "CODEC_TYPE_AAC",
"CODEC_TYPE_LDAC", "CODEC_TYPE_APTX",
"CODEC_TYPE_APTX_HD", "CODEC_TYPE_APTX_AD",
"CODEC_TYPE_LC3", "CODEC_TYPE_APTX_AD_SPEECH",
"CODEC_TYPE_LC3_VOICE", "CODEC_TYPE_APTX_AD_QLEA",
"CODEC_TYPE_APTX_AD_R4","CODEC_TYPE_INVALID"};
static SOC_ENUM_SINGLE_EXT_DECL(codec_display, codec_text);
#endif /*__LINUX_BTFM_SLIM_HW_INTERFACE_H*/

View File

@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/slimbus.h>
#include "btfm_slim.h"
#include "btfm_slim_slave.h"
/* SLAVE (WCN3990/QCA6390) Port assignment */
struct btfmslim_ch slave_rxport[] = {
{.id = BTFM_BT_SCO_A2DP_SLIM_RX, .name = "SCO_A2P_Rx",
.port = SLAVE_SB_PGD_PORT_RX_SCO},
{.id = BTFM_BT_SPLIT_A2DP_SLIM_RX, .name = "A2P_Rx",
.port = SLAVE_SB_PGD_PORT_RX_A2P},
{.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "",
.port = BTFM_SLIM_PGD_PORT_LAST},
};
struct btfmslim_ch slave_txport[] = {
{.id = BTFM_BT_SCO_SLIM_TX, .name = "SCO_Tx",
.port = SLAVE_SB_PGD_PORT_TX_SCO},
{.id = BTFM_FM_SLIM_TX, .name = "FM_Tx1",
.port = SLAVE_SB_PGD_PORT_TX1_FM},
{.id = BTFM_FM_SLIM_TX, .name = "FM_Tx2",
.port = SLAVE_SB_PGD_PORT_TX2_FM},
{.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "",
.port = BTFM_SLIM_PGD_PORT_LAST},
};
/* Function description */
int btfm_slim_slave_hw_init(struct btfmslim *btfmslim)
{
int ret = 0;
uint32_t reg;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
/* Get SB_SLAVE_HW_REV_MSB value*/
reg = SLAVE_SB_SLAVE_HW_REV_MSB;
ret = btfm_slim_read(btfmslim, reg, IFD);
if (ret < 0)
BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
BTFMSLIM_DBG("Major Rev: 0x%x, Minor Rev: 0x%x",
(ret & 0xF0) >> 4, (ret & 0x0F));
/* Get SB_SLAVE_HW_REV_LSB value*/
reg = SLAVE_SB_SLAVE_HW_REV_LSB;
ret = btfm_slim_read(btfmslim, reg, IFD);
if (ret < 0)
BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
else {
BTFMSLIM_INFO("read (%d) reg 0x%x", ret, reg);
ret = 0;
}
return ret;
}
static inline int is_fm_port(uint8_t port_num)
{
if (port_num == SLAVE_SB_PGD_PORT_TX1_FM ||
port_num == CHRKVER3_SB_PGD_PORT_TX1_FM ||
port_num == CHRKVER3_SB_PGD_PORT_TX2_FM ||
port_num == SLAVE_SB_PGD_PORT_TX2_FM)
return 1;
else
return 0;
}
int btfm_slim_slave_enable_port(struct btfmslim *btfmslim, uint8_t port_num,
uint8_t rxport, uint8_t enable)
{
int ret = 0;
uint8_t reg_val = 0, en;
uint8_t rxport_num = 0;
uint16_t reg;
BTFMSLIM_DBG("port(%d) enable(%d)", port_num, enable);
if (rxport) {
BTFMSLIM_DBG("sample rate is %d", btfmslim->sample_rate);
if (enable &&
btfmslim->sample_rate != 44100 &&
btfmslim->sample_rate != 88200) {
BTFMSLIM_DBG("setting multichannel bit");
/* For SCO Rx, A2DP Rx other than 44.1 and 88.2Khz */
if (port_num < 24) {
rxport_num = port_num - 16;
reg_val = 0x01 << rxport_num;
reg = SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_0(
rxport_num);
} else {
rxport_num = port_num - 24;
reg_val = 0x01 << rxport_num;
reg = SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_1(
rxport_num);
}
BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x",
ret, reg);
goto error;
}
}
/* Port enable */
reg = SLAVE_SB_PGD_PORT_RX_CFGN(port_num - 0x10);
goto enable_disable_rxport;
}
if (!enable)
goto enable_disable_txport;
/* txport */
/* Multiple Channel Setting */
if (is_fm_port(port_num)) {
if (port_num == CHRKVER3_SB_PGD_PORT_TX1_FM)
reg_val = (0x1 << CHRKVER3_SB_PGD_PORT_TX1_FM);
else if (port_num == CHRKVER3_SB_PGD_PORT_TX2_FM)
reg_val = (0x1 << CHRKVER3_SB_PGD_PORT_TX2_FM);
else
reg_val = (0x1 << SLAVE_SB_PGD_PORT_TX1_FM) |
(0x1 << SLAVE_SB_PGD_PORT_TX2_FM);
reg = SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num);
BTFMSLIM_INFO("writing reg_val (%d) to reg(%x)", reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
goto error;
}
} else if (port_num == SLAVE_SB_PGD_PORT_TX_SCO) {
/* SCO Tx */
reg_val = 0x1 << SLAVE_SB_PGD_PORT_TX_SCO;
reg = SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num);
BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x",
ret, reg);
goto error;
}
}
/* Enable Tx port hw auto recovery for underrun or overrun error */
reg_val = (SLAVE_ENABLE_OVERRUN_AUTO_RECOVERY |
SLAVE_ENABLE_UNDERRUN_AUTO_RECOVERY);
reg = SLAVE_SB_PGD_PORT_TX_OR_UR_CFGN(port_num);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
goto error;
}
enable_disable_txport:
/* Port enable */
reg = SLAVE_SB_PGD_PORT_TX_CFGN(port_num);
enable_disable_rxport:
if (enable)
en = SLAVE_SB_PGD_PORT_ENABLE;
else
en = SLAVE_SB_PGD_PORT_DISABLE;
if (is_fm_port(port_num))
reg_val = en | SLAVE_SB_PGD_PORT_WM_L8;
else if (port_num == SLAVE_SB_PGD_PORT_TX_SCO)
reg_val = enable ? en | SLAVE_SB_PGD_PORT_WM_L1 : en;
else
reg_val = enable ? en | SLAVE_SB_PGD_PORT_WM_LB : en;
if (enable && port_num == SLAVE_SB_PGD_PORT_TX_SCO)
BTFMSLIM_INFO("programming SCO Tx with reg_val %d to reg 0x%x",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0)
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
error:
return ret;
}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

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

View File

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

View File

@ -0,0 +1,442 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "btfm_swr.h"
#include "btfm_swr_hw_interface.h"
#include "btfm_codec_hw_interface.h"
#define LPAIF_AUD 0x05
static int bt_soc_enable_status;
int btfm_feedback_ch_setting;
static uint8_t usecase_codec;
static int btfm_swr_hwep_write(struct snd_soc_component *codec,
unsigned int reg, unsigned int value)
{
BTFMSWR_DBG("");
return 0;
}
static unsigned int btfm_swr_hwep_read(struct snd_soc_component *codec,
unsigned int reg)
{
BTFMSWR_DBG("");
return 0;
}
static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSWR_DBG("");
ucontrol->value.integer.value[0] = bt_soc_enable_status;
return 1;
}
static int btfm_soc_status_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSWR_DBG("");
return 1;
}
static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSWR_DBG("");
ucontrol->value.integer.value[0] = btfm_feedback_ch_setting;
return 1;
}
static int btfm_put_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSWR_DBG("");
btfm_feedback_ch_setting = ucontrol->value.integer.value[0];
return 1;
}
static int btfm_get_codec_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSWR_DBG("current codec type:%s", codec_text[usecase_codec]);
ucontrol->value.integer.value[0] = usecase_codec;
return 1;
}
static int btfm_put_codec_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
usecase_codec = ucontrol->value.integer.value[0];
BTFMSWR_DBG("codec type set to:%s", codec_text[usecase_codec]);
return 1;
}
static struct snd_kcontrol_new status_controls[] = {
SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0,
btfm_soc_status_get, btfm_soc_status_put),
SOC_SINGLE_EXT("BT set feedback channel", 0, 0, 1, 0,
btfm_get_feedback_ch_setting,
btfm_put_feedback_ch_setting),
SOC_ENUM_EXT("BT codec type", codec_display,
btfm_get_codec_type, btfm_put_codec_type),
};
static int btfm_swr_hwep_probe(struct snd_soc_component *codec)
{
BTFMSWR_DBG("");
return 0;
}
static void btfm_swr_hwep_remove(struct snd_soc_component *codec)
{
BTFMSWR_DBG("");
}
static int btfm_swr_dai_startup(void *dai)
{
//struct hwep_data *hwep_info = (struct hwep_data *)dai;
int ret = -1;
BTFMSWR_DBG("");
ret = btfm_swr_hw_init();
return ret;
}
static void btfm_swr_dai_shutdown(void *dai, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
int ret = 0;
u8 port_type;
BTFMSWR_INFO("");
if (btfmswr == NULL || btfmswr->p_dai_port == NULL) {
BTFMSWR_INFO("port shutdown might have called with out open\n");
return;
}
switch (id) {
case FMAUDIO_TX:
port_type = FM_AUDIO_TX1;
break;
case BTAUDIO_TX:
port_type = BT_AUDIO_TX1;
break;
case BTAUDIO_RX:
port_type = BT_AUDIO_RX1;
break;
case BTAUDIO_A2DP_SINK_TX:
port_type = BT_AUDIO_TX2;
break;
case BTFM_NUM_CODEC_DAIS:
default:
BTFMSWR_ERR("dai->id is invalid:%d", id);
return;
}
ret = btfm_swr_disable_port(btfmswr->p_dai_port->port_info[id].port,
btfmswr->num_channels, port_type);
}
static int btfm_swr_dai_hw_params(void *dai, uint32_t bps,
uint32_t direction, uint8_t num_channels)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
BTFMSWR_DBG("");
btfmswr->bps = bps;
btfmswr->direction = direction;
btfmswr->num_channels = num_channels;
return 0;
}
void btfm_get_sampling_rate(uint32_t *sampling_rate)
{
uint8_t codec_types_avb = ARRAY_SIZE(codec_text);
if (usecase_codec > (codec_types_avb - 1)) {
BTFMSWR_ERR("falling back to use default sampling_rate: %u",
*sampling_rate);
return;
}
if (*sampling_rate == 44100 || *sampling_rate == 48000) {
if (usecase_codec == LDAC ||
usecase_codec == APTX_AD)
*sampling_rate = (*sampling_rate) * 2;
}
if (usecase_codec == LC3_VOICE ||
usecase_codec == APTX_AD_SPEECH ||
usecase_codec == LC3 || usecase_codec == APTX_AD_QLEA ||
usecase_codec == APTX_AD_R4) {
*sampling_rate = 96000;
}
BTFMSWR_INFO("current usecase codec type %s and sampling rate:%u khz",
codec_text[usecase_codec], *sampling_rate);
}
static int btfm_swr_dai_prepare(void *dai, uint32_t sampling_rate, uint32_t direction, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
int ret = -EINVAL;
u8 port_type;
bt_soc_enable_status = 0;
BTFMSWR_INFO("dai->id: %d, dai->rate: %d direction: %d", id, sampling_rate, direction);
btfm_get_sampling_rate(&sampling_rate);
btfmswr->sample_rate = sampling_rate;
switch (id) {
case FMAUDIO_TX:
port_type = FM_AUDIO_TX1;
break;
case BTAUDIO_TX:
port_type = BT_AUDIO_TX1;
break;
case BTAUDIO_RX:
port_type = BT_AUDIO_RX1;
break;
case BTAUDIO_A2DP_SINK_TX:
port_type = BT_AUDIO_TX2;
break;
case BTFM_NUM_CODEC_DAIS:
default:
BTFMSWR_ERR("dai->id is invalid:%d", id);
return -EINVAL;
}
ret = btfm_swr_enable_port(btfmswr->p_dai_port->port_info[id].port,
btfmswr->num_channels, sampling_rate, port_type);
/* save the enable channel status */
if (ret == 0)
bt_soc_enable_status = 1;
if (ret == -EISCONN) {
BTFMSWR_ERR("channel opened without closing, returning success");
ret = 0;
}
return ret;
}
/* This function will be called once during boot up */
static int btfm_swr_dai_set_channel_map(void *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
BTFMSWR_DBG("");
return 0;
}
static int btfm_swr_dai_get_channel_map(void *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
*rx_slot = 0;
*tx_slot = 0;
*rx_num = 0;
*tx_num = 0;
switch (id) {
case FMAUDIO_TX:
case BTAUDIO_TX:
case BTAUDIO_A2DP_SINK_TX:
*tx_num = btfmswr->num_channels;
*tx_slot = btfmswr->num_channels == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK;
break;
case BTAUDIO_RX:
*rx_num = btfmswr->num_channels;
*rx_slot = btfmswr->num_channels == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK;
break;
default:
BTFMSWR_ERR("Unsupported DAI %d", id);
return -EINVAL;
}
return 0;
}
int btfm_swr_dai_get_configs(void *dai, void *config, uint8_t id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmswr *btfmswr = dev_get_drvdata(hwep_info->dev);
struct hwep_dma_configurations *hwep_config;
BTFMSWR_DBG("");
hwep_config = (struct hwep_dma_configurations *)config;
hwep_config->stream_id = id;
hwep_config->sample_rate = btfmswr->sample_rate;
hwep_config->bit_width = (uint8_t)btfmswr->bps;
hwep_config->codectype = usecase_codec;
hwep_config->num_channels = btfmswr->num_channels;
hwep_config->active_channel_mask = (btfmswr->num_channels == 2 ?
TWO_CHANNEL_MASK : ONE_CHANNEL_MASK);
hwep_config->lpaif = LPAIF_AUD;
hwep_config->inf_index = 1;
return 1;
}
static struct hwep_dai_ops btfmswr_hw_dai_ops = {
.hwep_startup = btfm_swr_dai_startup,
.hwep_shutdown = btfm_swr_dai_shutdown,
.hwep_hw_params = btfm_swr_dai_hw_params,
.hwep_prepare = btfm_swr_dai_prepare,
.hwep_set_channel_map = btfm_swr_dai_set_channel_map,
.hwep_get_channel_map = btfm_swr_dai_get_channel_map,
.hwep_get_configs = btfm_swr_dai_get_configs,
.hwep_codectype = &usecase_codec,
};
static struct hwep_dai_driver btfmswr_dai_driver[] = {
{ /* FM Audio data multiple channel : FM -> lpass */
.dai_name = "btaudio_fm_tx",
.id = FMAUDIO_TX,
.capture = {
.stream_name = "FM SWR TX Capture",
.rates = SNDRV_PCM_RATE_48000, /* 48 KHz */
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 48000,
.rate_min = 48000,
.channels_min = 1,
.channels_max = 2,
},
.dai_ops = &btfmswr_hw_dai_ops,
},
{ /* Bluetooth SCO voice uplink: bt -> lpass */
.dai_name = "btaudio_tx",
.id = BTAUDIO_TX,
.capture = {
.stream_name = "BT Audio SWR Tx Capture",
/* 8 KHz or 16 KHz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.dai_ops = &btfmswr_hw_dai_ops,
},
{ /* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
.dai_name = "btaudio_rx",
.id = BTAUDIO_RX,
.playback = {
.stream_name = "BT Audio SWR Rx Playback",
/* 8/16/44.1/48/88.2/96 Khz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.dai_ops = &btfmswr_hw_dai_ops,
},
{ /* Bluetooth A2DP sink: bt -> lpass */
.dai_name = "btfm_a2dp_sink_swr_tx",
.id = BTAUDIO_A2DP_SINK_TX,
.capture = {
.stream_name = "A2DP sink TX Capture",
/* 8/16/44.1/48/88.2/96/192 Khz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
| SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 1,
},
.dai_ops = &btfmswr_hw_dai_ops,
}
};
static struct hwep_comp_drv btfmswr_hw_driver = {
.hwep_probe = btfm_swr_hwep_probe,
.hwep_remove = btfm_swr_hwep_remove,
.hwep_read = btfm_swr_hwep_read,
.hwep_write = btfm_swr_hwep_write,
};
int btfm_swr_register_hw_ep(struct btfmswr *btfm_swr)
{
struct device *dev = btfm_swr->dev;
struct hwep_data *hwep_info;
int ret = 0;
BTFMSWR_INFO("Registering with BTFMCODEC HWEP interface\n");
hwep_info = kzalloc(sizeof(struct hwep_data), GFP_KERNEL);
if (!hwep_info) {
BTFMSWR_ERR("%s: failed to allocate memory\n", __func__);
ret = -ENOMEM;
goto end;
}
/* Copy EP device parameters as intercations will be on the same device */
hwep_info->dev = dev;
strscpy(hwep_info->driver_name, SWR_SLAVE_COMPATIBLE_STR, DEVICE_NAME_MAX_LEN);
hwep_info->drv = &btfmswr_hw_driver;
hwep_info->dai_drv = btfmswr_dai_driver;
hwep_info->num_dai = ARRAY_SIZE(btfmswr_dai_driver);
hwep_info->num_dai = 4;
hwep_info->num_mixer_ctrl = ARRAY_SIZE(status_controls);
hwep_info->mixer_ctrl = status_controls;
/* Register to hardware endpoint */
ret = btfmcodec_register_hw_ep(hwep_info);
if (ret) {
BTFMSWR_ERR("failed to register with btfmcodec driver hw interface (%d)", ret);
goto end;
}
BTFMSWR_INFO("Registered succesfull with BTFMCODEC HWEP interface\n");
return ret;
end:
return ret;
}
void btfm_swr_unregister_hwep(void)
{
BTFMSWR_INFO("Unregistered with BTFMCODEC HWEP interface");
/* Unregister with BTFMCODEC HWEP driver */
btfmcodec_unregister_hw_ep(SWR_SLAVE_COMPATIBLE_STR);
}
MODULE_DESCRIPTION("BTFM SoundWire Codec driver");
MODULE_LICENSE("GPL v2");

View File

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

View File

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

View File

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

View File

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