Browse Source

bt-kernel: Bring initial files for BT family drivers

Bring BT family drivers from msm-5.10 kernel
to BT kernel project.

drivers/bluetooth/btpower.c -> .
drivers/bluetooth/btfm* ->  .
drivers/media/radio/rtc6226/* -> .
include/net/bt* -> include/
Above shows how directories and header files are relocated.

Change-Id: I6263eaa81dec344d7589f09ad3ec525ee7b04a8a
Umesh Vats 3 years ago
parent
commit
eaa3717b1d

+ 84 - 0
Android.mk

@@ -0,0 +1,84 @@
+# 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), true)
+
+BT_SELECT := CONFIG_MSM_BT_POWER=m
+BT_SELECT += CONFIG_BTFM_SLIM=m
+BT_SELECT += CONFIG_I2C_RTC6226_QCA=m
+
+LOCAL_PATH := $(call my-dir)
+
+# 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)/*/*) \
+
+# 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

+ 15 - 0
Kbuild

@@ -0,0 +1,15 @@
+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
+
+obj-$(CONFIG_MSM_BT_POWER) += pwr/
+obj-$(CONFIG_BTFM_SLIM) += slimbus/
+obj-$(CONFIG_I2C_RTC6226_QCA) += rtc6226/

+ 16 - 0
Makefile

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

+ 5 - 0
bt_kernel_product_board.mk

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

+ 10 - 0
bt_kernel_vendor_board.mk

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

+ 88 - 0
include/btpower.h

@@ -0,0 +1,88 @@
+/* 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 __LINUX_BLUETOOTH_POWER_H
+#define __LINUX_BLUETOOTH_POWER_H
+
+#include <linux/types.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox/qmp.h>
+
+/*
+ * voltage regulator information required for configuring the
+ * bluetooth chipset
+ */
+enum bt_power_modes {
+	BT_POWER_DISABLE = 0,
+	BT_POWER_ENABLE,
+	BT_POWER_RETENTION
+};
+
+struct log_index {
+	int init;
+	int crash;
+};
+
+struct bt_power_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 bt_power {
+	char compatible[32];
+	struct bt_power_vreg_data *vregs;
+	int 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? */
+};
+
+/*
+ * Platform data for the bluetooth power driver.
+ */
+struct btpower_platform_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 */
+	int xo_gpio_clk;                       /* XO clock gpio*/
+	struct device *slim_dev;
+	struct bt_power_vreg_data *vreg_info;  /* VDDIO voltage regulator */
+	struct bt_power_clk_data *bt_chip_clk; /* bluetooth reference clock */
+	int (*bt_power_setup)(int id); /* Bluetooth power setup function */
+	char compatible[32]; /*Bluetooth SoC name */
+	int num_vregs;
+	struct mbox_client mbox_client_data;
+	struct mbox_chan *mbox_chan;
+	const char *vreg_ipa;
+};
+
+int btpower_register_slimdev(struct device *dev);
+int btpower_get_chipset_version(void);
+int btpower_aop_mbox_init(struct btpower_platform_data *pdata);
+
+#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
+
+/* total number of power src */
+#define BT_POWER_SRC_SIZE           28
+
+#endif /* __LINUX_BLUETOOTH_POWER_H */

+ 13 - 0
pwr/Kconfig

@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config MSM_BT_POWER
+	tristate "MSM Bluetooth Power Control"
+	depends on ARCH_QCOM
+	help
+		MSM Bluetooth Power control driver.
+		This provides a parameter to switch on/off power from PMIC
+		to Bluetooth device. This will control LDOs/Clock/GPIOs to
+		control Bluetooth Chipset based on power on/off sequence.
+
+		Say Y here to compile support for Bluetooth Power driver
+		into the kernel or say M to compile as a module.

+ 2 - 0
pwr/Makefile

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

+ 1251 - 0
pwr/btpower.c

@@ -0,0 +1,1251 @@
+// 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.
+ */
+
+/*
+ * Bluetooth Power Switch Module
+ * controls power to external Bluetooth device
+ * with interface to power management device
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/of_device.h>
+#include <soc/qcom/cmd-db.h>
+#include "btpower.h"
+
+#if defined CONFIG_BT_SLIM_QCA6390 || \
+	defined CONFIG_BT_SLIM_QCA6490 || \
+	defined CONFIG_BTFM_SLIM_WCN3990
+#include "btfm_slim.h"
+#endif
+#include <linux/fs.h>
+
+#define PWR_SRC_NOT_AVAILABLE -2
+#define DEFAULT_INVALID_VALUE -1
+#define PWR_SRC_INIT_STATE_IDX 0
+#define BTPOWER_MBOX_MSG_MAX_LEN 64
+#define BTPOWER_MBOX_TIMEOUT_MS 1000
+#define XO_CLK_RETRY_COUNT_MAX 5
+/**
+ * enum btpower_vreg_param: Voltage regulator TCS param
+ * @BTPOWER_VREG_VOLTAGE: Provides voltage level to be configured in TCS
+ * @BTPOWER_VREG_MODE: Regulator mode
+ * @BTPOWER_VREG_TCS_ENABLE: Set Voltage regulator enable config in TCS
+ */
+enum btpower_vreg_param {
+	BTPOWER_VREG_VOLTAGE,
+	BTPOWER_VREG_MODE,
+	BTPOWER_VREG_ENABLE,
+};
+
+/**
+ * enum btpower_tcs_seq: TCS sequence ID for trigger
+ * BTPOWER_TCS_UP_SEQ: TCS Sequence based on up trigger / Wake TCS
+ * BTPOWER_TCS_DOWN_SEQ: TCS Sequence based on down trigger / Sleep TCS
+ * BTPOWER_TCS_ALL_SEQ: Update for both up and down triggers
+ */
+enum btpower_tcs_seq {
+	BTPOWER_TCS_UP_SEQ,
+	BTPOWER_TCS_DOWN_SEQ,
+	BTPOWER_TCS_ALL_SEQ,
+};
+
+enum power_src_pos {
+	BT_RESET_GPIO = PWR_SRC_INIT_STATE_IDX,
+	BT_SW_CTRL_GPIO,
+	BT_VDD_AON_LDO,
+	BT_VDD_DIG_LDO,
+	BT_VDD_RFA1_LDO,
+	BT_VDD_RFA2_LDO,
+	BT_VDD_ASD_LDO,
+	BT_VDD_XTAL_LDO,
+	BT_VDD_PA_LDO,
+	BT_VDD_CORE_LDO,
+	BT_VDD_IO_LDO,
+	BT_VDD_LDO,
+	BT_VDD_RFA_0p8,
+	BT_VDD_RFACMN,
+	// these indexes GPIOs/regs value are fetched during crash.
+	BT_RESET_GPIO_CURRENT,
+	BT_SW_CTRL_GPIO_CURRENT,
+	BT_VDD_AON_LDO_CURRENT,
+	BT_VDD_DIG_LDO_CURRENT,
+	BT_VDD_RFA1_LDO_CURRENT,
+	BT_VDD_RFA2_LDO_CURRENT,
+	BT_VDD_ASD_LDO_CURRENT,
+	BT_VDD_XTAL_LDO_CURRENT,
+	BT_VDD_PA_LDO_CURRENT,
+	BT_VDD_CORE_LDO_CURRENT,
+	BT_VDD_IO_LDO_CURRENT,
+	BT_VDD_LDO_CURRENT,
+	BT_VDD_RFA_0p8_CURRENT,
+	BT_VDD_RFACMN_CURRENT
+};
+
+// Regulator structure for QCA6390 and QCA6490 BT SoC series
+static struct bt_power_vreg_data bt_vregs_info_qca6x9x[] = {
+	{NULL, "qcom,bt-vdd-io",      1800000, 1800000, 0, false, true,
+		{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-aon",     966000,  966000,  0, false, true,
+		{BT_VDD_AON_LDO, BT_VDD_AON_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfacmn",  950000,  950000,  0, false, true,
+		{BT_VDD_RFACMN, BT_VDD_RFACMN_CURRENT}},
+	/* BT_CX_MX */
+	{NULL, "qcom,bt-vdd-dig",      966000,  966000,  0, false, true,
+		{BT_VDD_DIG_LDO, BT_VDD_DIG_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa-0p8",  950000,  952000,  0, false, true,
+		{BT_VDD_RFA_0p8, BT_VDD_RFA_0p8_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa1",     1900000, 1900000, 0, false, true,
+		{BT_VDD_RFA1_LDO, BT_VDD_RFA1_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-rfa2",     1900000, 1900000, 0, false, true,
+		{BT_VDD_RFA2_LDO, BT_VDD_RFA2_LDO_CURRENT}},
+	{NULL, "qcom,bt-vdd-asd",      2800000, 2800000, 0, false, true,
+		{BT_VDD_ASD_LDO, BT_VDD_ASD_LDO_CURRENT}},
+};
+
+// Regulator structure for WCN399x BT SoC series
+static struct bt_power bt_vreg_info_wcn399x = {
+	.compatible = "qcom,wcn3990",
+	.vregs = (struct bt_power_vreg_data []) {
+		{NULL, "qcom,bt-vdd-io",   1700000, 1900000, 0, false, false,
+			{BT_VDD_IO_LDO, BT_VDD_IO_LDO_CURRENT}},
+		{NULL, "qcom,bt-vdd-core", 1304000, 1304000, 0, false, false,
+			{BT_VDD_CORE_LDO, BT_VDD_CORE_LDO_CURRENT}},
+		{NULL, "qcom,bt-vdd-pa",   3000000, 3312000, 0, false, false,
+			{BT_VDD_PA_LDO, BT_VDD_PA_LDO_CURRENT}},
+		{NULL, "qcom,bt-vdd-xtal", 1700000, 1900000, 0, false, false,
+			{BT_VDD_XTAL_LDO, BT_VDD_XTAL_LDO_CURRENT}},
+	},
+	.num_vregs = 4,
+};
+
+static struct bt_power bt_vreg_info_qca6390 = {
+	.compatible = "qcom,qca6390",
+	.vregs = bt_vregs_info_qca6x9x,
+	.num_vregs = ARRAY_SIZE(bt_vregs_info_qca6x9x),
+};
+
+static struct bt_power bt_vreg_info_qca6490 = {
+	.compatible = "qcom,qca6490",
+	.vregs = bt_vregs_info_qca6x9x,
+	.num_vregs = ARRAY_SIZE(bt_vregs_info_qca6x9x),
+};
+
+static const struct of_device_id bt_power_match_table[] = {
+	{	.compatible = "qcom,qca6174" },
+	{	.compatible = "qcom,wcn3990", .data = &bt_vreg_info_wcn399x},
+	{	.compatible = "qcom,qca6390", .data = &bt_vreg_info_qca6390},
+	{	.compatible = "qcom,qca6490", .data = &bt_vreg_info_qca6490},
+	{},
+};
+
+static int bt_power_vreg_set(enum bt_power_modes mode);
+static int btpower_enable_ipa_vreg(struct btpower_platform_data *pdata);
+
+static int bt_power_src_status[BT_POWER_SRC_SIZE];
+static struct btpower_platform_data *bt_power_pdata;
+static bool previous;
+static int pwr_state;
+static struct class *bt_class;
+static int bt_major;
+static int soc_id;
+static bool probe_finished;
+
+static int bt_vreg_enable(struct bt_power_vreg_data *vreg)
+{
+	int rc = 0;
+
+	pr_debug("%s: vreg_en for : %s\n", __func__, vreg->name);
+
+	if (!vreg->is_enabled) {
+		if ((vreg->min_vol != 0) && (vreg->max_vol != 0)) {
+			rc = regulator_set_voltage(vreg->reg,
+						vreg->min_vol,
+						vreg->max_vol);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_voltage(%s) failed rc=%d\n",
+						__func__, vreg->name, rc);
+				goto out;
+			}
+		}
+
+		if (vreg->load_curr >= 0) {
+			rc = regulator_set_load(vreg->reg,
+					vreg->load_curr);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_load(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+				goto out;
+			}
+		}
+
+		rc = regulator_enable(vreg->reg);
+		if (rc < 0) {
+			pr_err("regulator_enable(%s) failed. rc=%d\n",
+					__func__, vreg->name, rc);
+			goto out;
+		}
+		vreg->is_enabled = true;
+	}
+out:
+	return rc;
+}
+
+static int bt_vreg_enable_retention(struct bt_power_vreg_data *vreg)
+{
+	int rc = 0;
+
+	if (!vreg)
+		return rc;
+
+	pr_debug("%s: enable_retention for : %s\n", __func__, vreg->name);
+
+	if ((vreg->is_enabled) && (vreg->is_retention_supp)) {
+		if ((vreg->min_vol != 0) && (vreg->max_vol != 0)) {
+			/* Set the min voltage to 0 */
+			rc = regulator_set_voltage(vreg->reg, 0, vreg->max_vol);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_voltage(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+				goto out;
+			}
+		}
+		if (vreg->load_curr >= 0) {
+			rc = regulator_set_load(vreg->reg, 0);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_load(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+			}
+		}
+	}
+out:
+	return rc;
+}
+
+static int bt_vreg_disable(struct bt_power_vreg_data *vreg)
+{
+	int rc = 0;
+
+	if (!vreg)
+		return rc;
+
+	pr_debug("%s for : %s\n", __func__, vreg->name);
+
+	if (vreg->is_enabled) {
+		rc = regulator_disable(vreg->reg);
+		if (rc < 0) {
+			pr_err("%s, regulator_disable(%s) failed. rc=%d\n",
+					__func__, vreg->name, rc);
+			goto out;
+		}
+		vreg->is_enabled = false;
+
+		if ((vreg->min_vol != 0) && (vreg->max_vol != 0)) {
+			/* Set the min voltage to 0 */
+			rc = regulator_set_voltage(vreg->reg, 0,
+					vreg->max_vol);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_voltage(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+				goto out;
+			}
+		}
+		if (vreg->load_curr >= 0) {
+			rc = regulator_set_load(vreg->reg, 0);
+			if (rc < 0) {
+				pr_err("%s: regulator_set_load(%s) failed rc=%d\n",
+				__func__, vreg->name, rc);
+			}
+		}
+	}
+out:
+	return rc;
+}
+
+static int bt_clk_enable(struct bt_power_clk_data *clk)
+{
+	int rc = 0;
+
+	pr_info("%s: %s\n", __func__, clk->name);
+
+	/* Get the clock handle for vreg */
+	if (!clk->clk || clk->is_enabled) {
+		pr_err("%s: error - node: %p, clk->is_enabled:%d\n",
+			__func__, clk->clk, clk->is_enabled);
+		return -EINVAL;
+	}
+
+	rc = clk_prepare_enable(clk->clk);
+	if (rc) {
+		pr_err("%s: failed to enable %s, rc(%d)\n",
+				__func__, clk->name, rc);
+		return rc;
+	}
+
+	clk->is_enabled = true;
+	return rc;
+}
+
+static int bt_clk_disable(struct bt_power_clk_data *clk)
+{
+	int rc = 0;
+
+	pr_debug("%s: %s\n", __func__, clk->name);
+
+	/* Get the clock handle for vreg */
+	if (!clk->clk || !clk->is_enabled) {
+		pr_err("%s: error - node: %p, clk->is_enabled:%d\n",
+			__func__, clk->clk, clk->is_enabled);
+		return -EINVAL;
+	}
+	clk_disable_unprepare(clk->clk);
+
+	clk->is_enabled = false;
+	return rc;
+}
+
+static void btpower_set_xo_clk_gpio_state(bool enable)
+{
+	int xo_clk_gpio =  bt_power_pdata->xo_gpio_clk;
+	int retry = 0;
+	int rc = 0;
+
+	if (xo_clk_gpio < 0)
+		return;
+
+retry_gpio_req:
+	rc = gpio_request(xo_clk_gpio, "bt_xo_clk_gpio");
+	if (rc) {
+		if (retry++ < XO_CLK_RETRY_COUNT_MAX) {
+			/* wait for ~(10 - 20) ms */
+			usleep_range(10000, 20000);
+			goto retry_gpio_req;
+		}
+	}
+
+	if (rc) {
+		pr_err("%s: unable to request XO clk gpio %d (%d)\n",
+			__func__, xo_clk_gpio, rc);
+		return;
+	}
+
+	if (enable) {
+		gpio_direction_output(xo_clk_gpio, 1);
+		/*XO CLK must be asserted for some time before BT_EN */
+		usleep_range(100, 200);
+	} else {
+		/* Assert XO CLK ~(2-5)ms before off for valid latch in HW */
+		usleep_range(4000, 6000);
+		gpio_direction_output(xo_clk_gpio, 0);
+	}
+
+	pr_info("%s:gpio(%d) success\n", __func__, xo_clk_gpio);
+
+	gpio_free(xo_clk_gpio);
+}
+
+static int bt_configure_gpios(int on)
+{
+	int rc = 0;
+	int bt_reset_gpio = bt_power_pdata->bt_gpio_sys_rst;
+	int wl_reset_gpio = bt_power_pdata->wl_gpio_sys_rst;
+	int bt_sw_ctrl_gpio  =  bt_power_pdata->bt_gpio_sw_ctrl;
+	int bt_debug_gpio  =  bt_power_pdata->bt_gpio_debug;
+	int assert_dbg_gpio = 0;
+
+	if (on) {
+		rc = gpio_request(bt_reset_gpio, "bt_sys_rst_n");
+		if (rc) {
+			pr_err("%s: unable to request gpio %d (%d)\n",
+					__func__, bt_reset_gpio, rc);
+			return rc;
+		}
+
+		rc = gpio_direction_output(bt_reset_gpio, 0);
+		if (rc) {
+			pr_err("%s: Unable to set direction\n", __func__);
+			return rc;
+		}
+		bt_power_src_status[BT_RESET_GPIO] =
+			gpio_get_value(bt_reset_gpio);
+		msleep(50);
+		pr_info("BTON:Turn Bt Off bt-reset-gpio(%d) value(%d)\n",
+			bt_reset_gpio, gpio_get_value(bt_reset_gpio));
+		if (bt_sw_ctrl_gpio >= 0) {
+			pr_info("BTON:Turn Bt Off\n");
+			bt_power_src_status[BT_SW_CTRL_GPIO] =
+			gpio_get_value(bt_sw_ctrl_gpio);
+			pr_info("bt-sw-ctrl-gpio(%d) value(%d)\n",
+				bt_sw_ctrl_gpio,
+				bt_power_src_status[BT_SW_CTRL_GPIO]);
+		}
+		if (wl_reset_gpio >= 0)
+			pr_info("BTON:Turn Bt On wl-reset-gpio(%d) value(%d)\n",
+				wl_reset_gpio, gpio_get_value(wl_reset_gpio));
+
+		if ((wl_reset_gpio < 0) ||
+			((wl_reset_gpio >= 0) && gpio_get_value(wl_reset_gpio))) {
+
+			btpower_set_xo_clk_gpio_state(true);
+			pr_info("%s: BTON: Asserting BT_EN\n", __func__);
+			rc = gpio_direction_output(bt_reset_gpio, 1);
+			if (rc) {
+				pr_err("%s: Unable to set direction\n", __func__);
+				return rc;
+			}
+			bt_power_src_status[BT_RESET_GPIO] =
+				gpio_get_value(bt_reset_gpio);
+			btpower_set_xo_clk_gpio_state(false);
+		}
+		if ((wl_reset_gpio >= 0) && (gpio_get_value(wl_reset_gpio) == 0)) {
+			if (gpio_get_value(bt_reset_gpio)) {
+				pr_info("%s: Wlan Off and BT On too close\n", __func__);
+				pr_info("%s: reset BT_EN, enable it after delay\n", __func__);
+				rc = gpio_direction_output(bt_reset_gpio, 0);
+				if (rc) {
+					pr_err("%s: Unable to set direction\n", __func__);
+					return rc;
+				}
+				bt_power_src_status[BT_RESET_GPIO] =
+					gpio_get_value(bt_reset_gpio);
+			}
+			pr_info("%s:add 100ms delay for AON output to fully discharge\n",
+				 __func__);
+			msleep(100);
+			btpower_set_xo_clk_gpio_state(true);
+			rc = gpio_direction_output(bt_reset_gpio, 1);
+			if (rc) {
+				pr_err("%s: Unable to set direction\n", __func__);
+				return rc;
+			}
+			bt_power_src_status[BT_RESET_GPIO] =
+				gpio_get_value(bt_reset_gpio);
+			btpower_set_xo_clk_gpio_state(false);
+		}
+		msleep(50);
+		/*  Check  if  SW_CTRL  is  asserted  */
+		if  (bt_sw_ctrl_gpio  >=  0)  {
+			rc  =  gpio_direction_input(bt_sw_ctrl_gpio);
+			if  (rc)  {
+				pr_err("%s:SWCTRL Dir Set Problem:%d\n",
+					__func__, rc);
+			}  else  if  (!gpio_get_value(bt_sw_ctrl_gpio))  {
+				/* SW_CTRL not asserted, assert debug GPIO */
+				if  (bt_debug_gpio  >=  0)
+					assert_dbg_gpio = 1;
+			}
+		}
+		if (assert_dbg_gpio) {
+			rc  =  gpio_request(bt_debug_gpio, "bt_debug_n");
+			if  (rc)  {
+				pr_err("unable to request Debug Gpio\n");
+			}  else  {
+				rc = gpio_direction_output(bt_debug_gpio,  1);
+				if (rc)
+					pr_err("%s:Prob Set Debug-Gpio\n",
+						__func__);
+			}
+		}
+		pr_info("BTON:Turn Bt On bt-reset-gpio(%d) value(%d)\n",
+			bt_reset_gpio, gpio_get_value(bt_reset_gpio));
+		if (bt_sw_ctrl_gpio >= 0) {
+			pr_info("BTON:Turn Bt On\n");
+			bt_power_src_status[BT_SW_CTRL_GPIO] =
+			gpio_get_value(bt_sw_ctrl_gpio);
+			pr_info("bt-sw-ctrl-gpio(%d) value(%d)\n",
+				bt_sw_ctrl_gpio,
+				bt_power_src_status[BT_SW_CTRL_GPIO]);
+		}
+	} else {
+		gpio_set_value(bt_reset_gpio, 0);
+		msleep(100);
+		pr_info("BT-OFF:bt-reset-gpio(%d) value(%d)\n",
+			bt_reset_gpio, gpio_get_value(bt_reset_gpio));
+		if (bt_sw_ctrl_gpio >= 0) {
+			pr_info("BT-OFF:bt-sw-ctrl-gpio(%d) value(%d)\n",
+				bt_sw_ctrl_gpio,
+				gpio_get_value(bt_sw_ctrl_gpio));
+		}
+	}
+
+	pr_info("%s: bt_gpio= %d on: %d\n", __func__, bt_reset_gpio, on);
+
+	return rc;
+}
+
+static int bluetooth_power(int on)
+{
+	int rc = 0;
+
+	pr_debug("%s: on: %d\n", __func__, on);
+
+	if (on == 1) {
+		rc = bt_power_vreg_set(BT_POWER_ENABLE);
+		if (rc < 0) {
+			pr_err("%s: bt_power regulators config failed\n",
+				__func__);
+			goto regulator_fail;
+		}
+		/* Parse dt_info and check if a target requires clock voting.
+		 * Enable BT clock when BT is on and disable it when BT is off
+		 */
+		if (bt_power_pdata->bt_chip_clk) {
+			rc = bt_clk_enable(bt_power_pdata->bt_chip_clk);
+			if (rc < 0) {
+				pr_err("%s: bt_power gpio config failed\n",
+					__func__);
+				goto clk_fail;
+			}
+		}
+		if (bt_power_pdata->bt_gpio_sys_rst > 0) {
+			bt_power_src_status[BT_RESET_GPIO] =
+				DEFAULT_INVALID_VALUE;
+			bt_power_src_status[BT_SW_CTRL_GPIO] =
+				DEFAULT_INVALID_VALUE;
+			rc = bt_configure_gpios(on);
+			if (rc < 0) {
+				pr_err("%s: bt_power gpio config failed\n",
+					__func__);
+				goto gpio_fail;
+			}
+		}
+	} else if (on == 0) {
+		// Power Off
+		if (bt_power_pdata->bt_gpio_sys_rst > 0)
+			bt_configure_gpios(on);
+gpio_fail:
+		if (bt_power_pdata->bt_gpio_sys_rst > 0)
+			gpio_free(bt_power_pdata->bt_gpio_sys_rst);
+		if (bt_power_pdata->bt_gpio_debug  >  0)
+			gpio_free(bt_power_pdata->bt_gpio_debug);
+		if (bt_power_pdata->bt_chip_clk)
+			bt_clk_disable(bt_power_pdata->bt_chip_clk);
+clk_fail:
+regulator_fail:
+		bt_power_vreg_set(BT_POWER_DISABLE);
+	} else if (on == 2) {
+		/* Retention mode */
+		bt_power_vreg_set(BT_POWER_RETENTION);
+	} else {
+		pr_err("%s: Invalid power mode: %d\n", __func__, on);
+		rc = -1;
+	}
+	return rc;
+}
+
+static int btpower_toggle_radio(void *data, bool blocked)
+{
+	int ret = 0;
+	int (*power_control)(int enable);
+
+	power_control =
+		((struct btpower_platform_data *)data)->bt_power_setup;
+
+	if (previous != blocked)
+		ret = (*power_control)(!blocked);
+	if (!ret)
+		previous = blocked;
+	return ret;
+}
+
+static const struct rfkill_ops btpower_rfkill_ops = {
+	.set_block = btpower_toggle_radio,
+};
+
+static ssize_t extldo_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	return scnprintf(buf, 6, "false\n");
+}
+
+static DEVICE_ATTR_RO(extldo);
+
+static int btpower_rfkill_probe(struct platform_device *pdev)
+{
+	struct rfkill *rfkill;
+	int ret;
+
+	rfkill = rfkill_alloc("bt_power", &pdev->dev, RFKILL_TYPE_BLUETOOTH,
+						&btpower_rfkill_ops,
+						pdev->dev.platform_data);
+
+	if (!rfkill) {
+		dev_err(&pdev->dev, "rfkill allocate failed\n");
+		return -ENOMEM;
+	}
+
+	/* add file into rfkill0 to handle LDO27 */
+	ret = device_create_file(&pdev->dev, &dev_attr_extldo);
+	if (ret < 0)
+		pr_err("%s: device create file error\n", __func__);
+
+	/* force Bluetooth off during init to allow for user control */
+	rfkill_init_sw_state(rfkill, 1);
+	previous = true;
+
+	ret = rfkill_register(rfkill);
+	if (ret) {
+		dev_err(&pdev->dev, "rfkill register failed=%d\n", ret);
+		rfkill_destroy(rfkill);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, rfkill);
+
+	return 0;
+}
+
+static void btpower_rfkill_remove(struct platform_device *pdev)
+{
+	struct rfkill *rfkill;
+
+	pr_debug("%s\n", __func__);
+
+	rfkill = platform_get_drvdata(pdev);
+	if (rfkill)
+		rfkill_unregister(rfkill);
+	rfkill_destroy(rfkill);
+	platform_set_drvdata(pdev, NULL);
+}
+
+#define MAX_PROP_SIZE 32
+static int bt_dt_parse_vreg_info(struct device *dev,
+		struct bt_power_vreg_data *vreg_data)
+{
+	int len, ret = 0;
+	const __be32 *prop;
+	char prop_name[MAX_PROP_SIZE];
+	struct bt_power_vreg_data *vreg = vreg_data;
+	struct device_node *np = dev->of_node;
+	const char *vreg_name = vreg_data->name;
+
+	pr_debug("%s: vreg dev tree parse for %s\n", __func__, vreg_name);
+
+	snprintf(prop_name, sizeof(prop_name), "%s-supply", vreg_name);
+	if (of_parse_phandle(np, prop_name, 0)) {
+		vreg->reg = regulator_get(dev, vreg_name);
+		if (IS_ERR(vreg->reg)) {
+			ret = PTR_ERR(vreg->reg);
+			vreg->reg = NULL;
+			pr_warn("%s: failed to get: %s error:%d\n", __func__,
+				vreg_name, ret);
+			return ret;
+		}
+
+		snprintf(prop_name, sizeof(prop_name), "%s-config", vreg->name);
+		prop = of_get_property(dev->of_node, prop_name, &len);
+		if (!prop || len != (4 * sizeof(__be32))) {
+			pr_debug("%s: Property %s %s, use default\n",
+				__func__, prop_name,
+				prop ? "invalid format" : "doesn't exist");
+		} else {
+			vreg->min_vol = be32_to_cpup(&prop[0]);
+			vreg->max_vol = be32_to_cpup(&prop[1]);
+			vreg->load_curr = be32_to_cpup(&prop[2]);
+			vreg->is_retention_supp = be32_to_cpup(&prop[3]);
+		}
+
+		pr_debug("%s: Got regulator: %s, min_vol: %u, max_vol: %u, load_curr: %u,is_retention_supp: %u\n",
+			__func__, vreg->name, vreg->min_vol, vreg->max_vol,
+			vreg->load_curr, vreg->is_retention_supp);
+	} else {
+		pr_info("%s: %s is not provided in device tree\n", __func__,
+			vreg_name);
+	}
+
+	return ret;
+}
+
+static int bt_dt_parse_clk_info(struct device *dev,
+		struct bt_power_clk_data **clk_data)
+{
+	int ret = -EINVAL;
+	struct bt_power_clk_data *clk = NULL;
+	struct device_node *np = dev->of_node;
+
+	pr_debug("%s\n", __func__);
+
+	*clk_data = NULL;
+	if (of_parse_phandle(np, "clocks", 0)) {
+		clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+		if (!clk) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		/* Allocated 20 bytes size buffer for clock name string */
+		clk->name = devm_kzalloc(dev, 20, GFP_KERNEL);
+
+		/* Parse clock name from node */
+		ret = of_property_read_string_index(np, "clock-names", 0,
+				&(clk->name));
+		if (ret < 0) {
+			pr_err("%s: reading \"clock-names\" failed\n",
+				__func__);
+			return ret;
+		}
+
+		clk->clk = devm_clk_get(dev, clk->name);
+		if (IS_ERR(clk->clk)) {
+			ret = PTR_ERR(clk->clk);
+			pr_err("%s: failed to get %s, ret (%d)\n",
+				__func__, clk->name, ret);
+			clk->clk = NULL;
+			return ret;
+		}
+
+		*clk_data = clk;
+	} else {
+		pr_err("%s: clocks is not provided in device tree\n", __func__);
+	}
+
+err:
+	return ret;
+}
+
+static int bt_power_vreg_get(struct platform_device *pdev)
+{
+	int num_vregs, i = 0, ret = 0;
+	const struct bt_power *data;
+
+	data = of_device_get_match_data(&pdev->dev);
+	if (!data) {
+		pr_err("%s: failed to get dev node\n", __func__);
+		return -EINVAL;
+	}
+
+	memcpy(&bt_power_pdata->compatible, &data->compatible, MAX_PROP_SIZE);
+	bt_power_pdata->vreg_info = data->vregs;
+	num_vregs = bt_power_pdata->num_vregs = data->num_vregs;
+	for (; i < num_vregs; i++) {
+		ret = bt_dt_parse_vreg_info(&(pdev->dev),
+					    &bt_power_pdata->vreg_info[i]);
+		/* No point to go further if failed to get regulator handler */
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+static int bt_power_vreg_set(enum bt_power_modes mode)
+{
+	int num_vregs, i = 0, ret = 0;
+	int log_indx;
+	struct bt_power_vreg_data *vreg_info = NULL;
+
+	num_vregs =  bt_power_pdata->num_vregs;
+	if (mode == BT_POWER_ENABLE) {
+		for (; i < num_vregs; i++) {
+			vreg_info = &bt_power_pdata->vreg_info[i];
+			log_indx = vreg_info->indx.init;
+			if (vreg_info->reg) {
+				bt_power_src_status[log_indx] =
+					DEFAULT_INVALID_VALUE;
+				ret = bt_vreg_enable(vreg_info);
+				if (ret < 0)
+					goto out;
+				if (vreg_info->is_enabled) {
+					bt_power_src_status[log_indx] =
+						regulator_get_voltage(
+							vreg_info->reg);
+				}
+			}
+		}
+	} else if (mode == BT_POWER_DISABLE) {
+		for (; i < num_vregs; i++) {
+			vreg_info = &bt_power_pdata->vreg_info[i];
+			ret = bt_vreg_disable(vreg_info);
+		}
+	} else if (mode == BT_POWER_RETENTION) {
+		for (; i < num_vregs; i++) {
+			vreg_info = &bt_power_pdata->vreg_info[i];
+			ret = bt_vreg_enable_retention(vreg_info);
+		}
+	} else {
+		pr_err("%s: Invalid power mode: %d\n", __func__, mode);
+		ret = -1;
+	}
+
+out:
+	return ret;
+}
+
+static void bt_power_vreg_put(void)
+{
+	int i = 0;
+	struct bt_power_vreg_data *vreg_info = NULL;
+	int num_vregs = bt_power_pdata->num_vregs;
+
+	for (; i < num_vregs; i++) {
+		vreg_info = &bt_power_pdata->vreg_info[i];
+		if (vreg_info->reg)
+			regulator_put(vreg_info->reg);
+	}
+}
+
+
+static int bt_power_populate_dt_pinfo(struct platform_device *pdev)
+{
+	int rc;
+
+	pr_debug("%s\n", __func__);
+
+	if (!bt_power_pdata)
+		return -ENOMEM;
+
+	if (pdev->dev.of_node) {
+		rc = bt_power_vreg_get(pdev);
+		if (rc)
+			return rc;
+
+		bt_power_pdata->bt_gpio_sys_rst =
+			of_get_named_gpio(pdev->dev.of_node,
+						"qcom,bt-reset-gpio", 0);
+		if (bt_power_pdata->bt_gpio_sys_rst < 0)
+			pr_warn("bt-reset-gpio not provided in devicetree\n");
+
+		bt_power_pdata->wl_gpio_sys_rst =
+			of_get_named_gpio(pdev->dev.of_node,
+						"qcom,wl-reset-gpio", 0);
+		if (bt_power_pdata->wl_gpio_sys_rst < 0)
+			pr_err("%s: wl-reset-gpio not provided in device tree\n",
+				__func__);
+
+
+		bt_power_pdata->bt_gpio_sw_ctrl  =
+			of_get_named_gpio(pdev->dev.of_node,
+						"qcom,bt-sw-ctrl-gpio",  0);
+		if (bt_power_pdata->bt_gpio_sw_ctrl < 0)
+			pr_warn("bt-sw-ctrl-gpio not provided in devicetree\n");
+
+		bt_power_pdata->bt_gpio_debug  =
+			of_get_named_gpio(pdev->dev.of_node,
+						"qcom,bt-debug-gpio",  0);
+		if (bt_power_pdata->bt_gpio_debug < 0)
+			pr_warn("bt-debug-gpio not provided in devicetree\n");
+
+		bt_power_pdata->xo_gpio_clk =
+			of_get_named_gpio(pdev->dev.of_node,
+						"qcom,xo-clk-gpio", 0);
+		if (bt_power_pdata->xo_gpio_clk < 0)
+			pr_warn("xo-clk-gpio not provided in devicetree\n");
+
+		rc = bt_dt_parse_clk_info(&pdev->dev,
+					&bt_power_pdata->bt_chip_clk);
+		if (rc < 0)
+			pr_warn("%s: clock not provided in device tree\n",
+				__func__);
+	}
+
+	bt_power_pdata->bt_power_setup = bluetooth_power;
+
+	return 0;
+}
+
+static int bt_power_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int itr;
+
+	pr_debug("%s\n", __func__);
+
+	/* Fill whole array with -2 i.e NOT_AVAILABLE state by default
+	 * for any GPIO or Reg handle.
+	 */
+	for (itr = PWR_SRC_INIT_STATE_IDX;
+		itr < BT_POWER_SRC_SIZE; ++itr)
+		bt_power_src_status[itr] = PWR_SRC_NOT_AVAILABLE;
+
+	bt_power_pdata = kzalloc(sizeof(*bt_power_pdata), GFP_KERNEL);
+
+	if (!bt_power_pdata)
+		return -ENOMEM;
+
+	bt_power_pdata->pdev = pdev;
+	if (pdev->dev.of_node) {
+		ret = bt_power_populate_dt_pinfo(pdev);
+		if (ret < 0) {
+			pr_err("%s, Failed to populate device tree info\n",
+				__func__);
+			goto free_pdata;
+		}
+		pdev->dev.platform_data = bt_power_pdata;
+	} else if (pdev->dev.platform_data) {
+		/* Optional data set to default if not provided */
+		if (!((struct btpower_platform_data *)
+			(pdev->dev.platform_data))->bt_power_setup)
+			((struct btpower_platform_data *)
+				(pdev->dev.platform_data))->bt_power_setup =
+						bluetooth_power;
+
+		memcpy(bt_power_pdata, pdev->dev.platform_data,
+			sizeof(struct btpower_platform_data));
+		pwr_state = 0;
+	} else {
+		pr_err("%s: Failed to get platform data\n", __func__);
+		goto free_pdata;
+	}
+
+	if (btpower_rfkill_probe(pdev) < 0)
+		goto free_pdata;
+
+	btpower_aop_mbox_init(bt_power_pdata);
+
+	probe_finished = true;
+	return 0;
+
+free_pdata:
+	kfree(bt_power_pdata);
+	return ret;
+}
+
+static int bt_power_remove(struct platform_device *pdev)
+{
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	probe_finished = false;
+	btpower_rfkill_remove(pdev);
+	bt_power_vreg_put();
+
+	kfree(bt_power_pdata);
+
+	return 0;
+}
+
+int btpower_register_slimdev(struct device *dev)
+{
+	pr_debug("%s\n", __func__);
+	if (!bt_power_pdata || (dev == NULL)) {
+		pr_err("%s: Failed to allocate memory\n", __func__);
+		return -EINVAL;
+	}
+	bt_power_pdata->slim_dev = dev;
+	return 0;
+}
+EXPORT_SYMBOL(btpower_register_slimdev);
+
+int btpower_get_chipset_version(void)
+{
+	pr_debug("%s\n", __func__);
+	return soc_id;
+}
+EXPORT_SYMBOL(btpower_get_chipset_version);
+
+static void  set_pwr_srcs_status(struct bt_power_vreg_data *handle)
+{
+	int ldo_index;
+
+	if (handle) {
+		ldo_index = handle->indx.crash;
+		bt_power_src_status[ldo_index] =
+			DEFAULT_INVALID_VALUE;
+		if (handle->is_enabled &&
+			(regulator_is_enabled(handle->reg))) {
+			bt_power_src_status[ldo_index] =
+				(int)regulator_get_voltage(handle->reg);
+			pr_err("%s(%d) value(%d)\n", handle->name,
+				handle, bt_power_src_status[ldo_index]);
+		} else {
+			pr_err("%s:%s is_enabled: %d\n",
+				__func__, handle->name,
+				handle->is_enabled);
+		}
+	}
+}
+
+static void  set_gpios_srcs_status(char *gpio_name,
+		int gpio_index, int handle)
+{
+	if (handle >= 0) {
+		bt_power_src_status[gpio_index] =
+			DEFAULT_INVALID_VALUE;
+		bt_power_src_status[gpio_index] =
+			gpio_get_value(handle);
+		pr_err("%s(%d) value(%d)\n", gpio_name,
+			handle, bt_power_src_status[gpio_index]);
+	} else {
+		pr_err("%s: %s not configured\n",
+			__func__, gpio_name);
+	}
+}
+
+static long bt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int ret = 0, pwr_cntrl = 0;
+	int chipset_version = 0;
+	int itr, num_vregs;
+	struct bt_power_vreg_data *vreg_info = NULL;
+
+	if (!bt_power_pdata || !probe_finished) {
+		pr_err("%s: BTPower Probing Pending.Try Again\n", __func__);
+		return -EAGAIN;
+	}
+
+	switch (cmd) {
+	case BT_CMD_SLIM_TEST:
+#if (defined CONFIG_BT_SLIM_QCA6390 || \
+	defined CONFIG_BT_SLIM_QCA6490 || \
+	defined CONFIG_BTFM_SLIM_WCN3990)
+		if (!bt_power_pdata->slim_dev) {
+			pr_err("%s: slim_dev is null\n", __func__);
+			return -EINVAL;
+		}
+		ret = btfm_slim_hw_init(
+			bt_power_pdata->slim_dev->platform_data
+		);
+#endif
+		break;
+	case BT_CMD_PWR_CTRL:
+		pwr_cntrl = (int)arg;
+		pr_warn("%s: BT_CMD_PWR_CTRL pwr_cntrl: %d\n",
+			__func__, pwr_cntrl);
+		if (pwr_state != pwr_cntrl) {
+			ret = bluetooth_power(pwr_cntrl);
+			if (!ret)
+				pwr_state = pwr_cntrl;
+		} else {
+			pr_err("%s: BT chip state is already: %d no change\n",
+				__func__, pwr_state);
+			ret = 0;
+		}
+		break;
+	case BT_CMD_CHIPSET_VERS:
+		chipset_version = (int)arg;
+		pr_warn("%s: unified Current SOC Version : %x\n", __func__,
+			chipset_version);
+		if (chipset_version) {
+			soc_id = chipset_version;
+		} else {
+			pr_err("%s: got invalid soc version\n", __func__);
+			soc_id = 0;
+		}
+		break;
+	case BT_CMD_GET_CHIPSET_ID:
+		if (copy_to_user((void __user *)arg, bt_power_pdata->compatible,
+		    MAX_PROP_SIZE)) {
+			pr_err("%s: copy to user failed\n", __func__);
+			ret = -EFAULT;
+		}
+		break;
+	case BT_CMD_CHECK_SW_CTRL:
+		/*  Check  if  SW_CTRL  is  asserted  */
+		pr_info("BT_CMD_CHECK_SW_CTRL\n");
+		if (bt_power_pdata->bt_gpio_sw_ctrl > 0) {
+			bt_power_src_status[BT_SW_CTRL_GPIO] =
+				DEFAULT_INVALID_VALUE;
+			ret  =  gpio_direction_input(
+				bt_power_pdata->bt_gpio_sw_ctrl);
+			if (ret) {
+				pr_err("%s:gpio_direction_input api\n",
+					 __func__);
+				pr_err("%s:failed for SW_CTRL:%d\n",
+					__func__, ret);
+			} else {
+				bt_power_src_status[BT_SW_CTRL_GPIO] =
+					gpio_get_value(
+					bt_power_pdata->bt_gpio_sw_ctrl);
+				pr_info("bt-sw-ctrl-gpio(%d) value(%d)\n",
+					bt_power_pdata->bt_gpio_sw_ctrl,
+					bt_power_src_status[BT_SW_CTRL_GPIO]);
+			}
+		} else {
+			pr_err("bt_gpio_sw_ctrl not configured\n");
+			return -EINVAL;
+		}
+		break;
+	case BT_CMD_GETVAL_POWER_SRCS:
+		pr_err("BT_CMD_GETVAL_POWER_SRCS\n");
+		set_gpios_srcs_status("BT_RESET_GPIO", BT_RESET_GPIO_CURRENT,
+			bt_power_pdata->bt_gpio_sys_rst);
+		set_gpios_srcs_status("SW_CTRL_GPIO", BT_SW_CTRL_GPIO_CURRENT,
+			bt_power_pdata->bt_gpio_sw_ctrl);
+
+		num_vregs =  bt_power_pdata->num_vregs;
+		for (itr = 0; itr < num_vregs; itr++) {
+			vreg_info = &bt_power_pdata->vreg_info[itr];
+			set_pwr_srcs_status(vreg_info);
+		}
+		if (copy_to_user((void __user *)arg,
+			bt_power_src_status, sizeof(bt_power_src_status))) {
+			pr_err("%s: copy to user failed\n", __func__);
+			ret = -EFAULT;
+		}
+		break;
+	case BT_CMD_SET_IPA_TCS_INFO:
+		pr_err("%s: BT_CMD_SET_IPA_TCS_INFO\n", __func__);
+		btpower_enable_ipa_vreg(bt_power_pdata);
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return ret;
+}
+
+static struct platform_driver bt_power_driver = {
+	.probe = bt_power_probe,
+	.remove = bt_power_remove,
+	.driver = {
+		.name = "bt_power",
+		.of_match_table = bt_power_match_table,
+	},
+};
+
+static const struct file_operations bt_dev_fops = {
+	.unlocked_ioctl = bt_ioctl,
+	.compat_ioctl = bt_ioctl,
+};
+
+static int __init btpower_init(void)
+{
+	int ret = 0;
+
+	probe_finished = false;
+	ret = platform_driver_register(&bt_power_driver);
+	if (ret) {
+		pr_err("%s: platform_driver_register error: %d\n",
+			__func__, ret);
+		goto driver_err;
+	}
+
+	bt_major = register_chrdev(0, "bt", &bt_dev_fops);
+	if (bt_major < 0) {
+		pr_err("%s: failed to allocate char dev\n", __func__);
+		ret = -1;
+		goto chrdev_err;
+	}
+
+	bt_class = class_create(THIS_MODULE, "bt-dev");
+	if (IS_ERR(bt_class)) {
+		pr_err("%s: coudn't create class\n", __func__);
+		ret = -1;
+		goto class_err;
+	}
+
+
+	if (device_create(bt_class, NULL, MKDEV(bt_major, 0),
+		NULL, "btpower") == NULL) {
+		pr_err("%s: failed to allocate char dev\n", __func__);
+		goto device_err;
+	}
+	return 0;
+
+device_err:
+	class_destroy(bt_class);
+class_err:
+	unregister_chrdev(bt_major, "bt");
+chrdev_err:
+	platform_driver_unregister(&bt_power_driver);
+driver_err:
+	return ret;
+}
+
+int btpower_aop_mbox_init(struct btpower_platform_data *pdata)
+{
+	struct mbox_client *mbox = &pdata->mbox_client_data;
+	struct mbox_chan *chan;
+	int ret = 0;
+
+	mbox->dev = &pdata->pdev->dev;
+	mbox->tx_block = true;
+	mbox->tx_tout = BTPOWER_MBOX_TIMEOUT_MS;
+	mbox->knows_txdone = false;
+
+	pdata->mbox_chan = NULL;
+	chan = mbox_request_channel(mbox, 0);
+	if (IS_ERR(chan)) {
+		pr_err("%s: failed to get mbox channel\n", __func__);
+		return PTR_ERR(chan);
+	}
+	pdata->mbox_chan = chan;
+
+	ret = of_property_read_string(pdata->pdev->dev.of_node,
+				      "qcom,vreg_ipa",
+				      &pdata->vreg_ipa);
+	if (ret)
+		pr_info("%s: vreg for iPA not configured\n", __func__);
+	else
+		pr_info("%s: Mbox channel initialized\n", __func__);
+
+	return 0;
+}
+
+static int btpower_aop_set_vreg_param(struct btpower_platform_data *pdata,
+				   const char *vreg_name,
+				   enum btpower_vreg_param param,
+				   enum btpower_tcs_seq seq, int val)
+{
+	struct qmp_pkt pkt;
+	char mbox_msg[BTPOWER_MBOX_MSG_MAX_LEN];
+	static const char * const vreg_param_str[] = {"v", "m", "e"};
+	static const char *const tcs_seq_str[] = {"upval", "dwnval", "enable"};
+	int ret = 0;
+
+	if (param > BTPOWER_VREG_ENABLE || seq > BTPOWER_TCS_ALL_SEQ || !vreg_name)
+		return -EINVAL;
+
+	snprintf(mbox_msg, BTPOWER_MBOX_MSG_MAX_LEN,
+		 "{class: wlan_pdc, res: %s.%s, %s: %d}", vreg_name,
+		 vreg_param_str[param], tcs_seq_str[seq], val);
+
+	pr_info("%s: sending AOP Mbox msg: %s\n", __func__, mbox_msg);
+	pkt.size = BTPOWER_MBOX_MSG_MAX_LEN;
+	pkt.data = mbox_msg;
+
+	ret = mbox_send_message(pdata->mbox_chan, &pkt);
+	if (ret < 0)
+		pr_err("%s:Failed to send AOP mbox msg(%s), err(%d)\n",
+					__func__, mbox_msg, ret);
+
+	return ret;
+}
+
+static int btpower_enable_ipa_vreg(struct btpower_platform_data *pdata)
+{
+	int ret = 0;
+	static bool config_done;
+
+	if (config_done) {
+		pr_info("%s: IPA Vreg already configured\n", __func__);
+		return 0;
+	}
+
+	if (!pdata->vreg_ipa || !pdata->mbox_chan) {
+		pr_info("%s: mbox/iPA vreg not configured\n", __func__);
+	} else {
+		ret = btpower_aop_set_vreg_param(pdata,
+					       pdata->vreg_ipa,
+					       BTPOWER_VREG_ENABLE,
+					       BTPOWER_TCS_UP_SEQ, 1);
+		if (ret >=  0) {
+			pr_info("%s:Enabled iPA\n", __func__);
+			config_done = true;
+		}
+	}
+
+	return ret;
+}
+
+static void __exit btpower_exit(void)
+{
+	platform_driver_unregister(&bt_power_driver);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM Bluetooth power control driver");
+
+module_init(btpower_init);
+module_exit(btpower_exit);

+ 14 - 0
rtc6226/Kconfig

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

+ 3 - 0
rtc6226/Makefile

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

+ 2374 - 0
rtc6226/radio-rtc6226-common.c

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

+ 1010 - 0
rtc6226/radio-rtc6226-i2c.c

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

+ 700 - 0
rtc6226/radio-rtc6226.h

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

+ 16 - 0
slimbus/Kconfig

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

+ 3 - 0
slimbus/Makefile

@@ -0,0 +1,3 @@
+ccflags-y += -I$(BT_ROOT)/include
+bt_fm_slim-objs := btfm_slim.o btfm_slim_codec.o btfm_slim_slave.o
+obj-$(CONFIG_BTFM_SLIM) += bt_fm_slim.o

+ 531 - 0
slimbus/btfm_slim.c

@@ -0,0 +1,531 @@
+// 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/ratelimit.h>
+#include <linux/slab.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"
+#define DELAY_FOR_PORT_OPEN_MS (200)
+#define SLIM_MANF_ID_QCOM	0x217
+#define SLIM_PROD_CODE		0x221
+
+#ifdef CONFIG_SLIMBUS
+static bool btfm_is_port_opening_delayed = true;
+#endif
+
+int btfm_slim_write(struct btfmslim *btfmslim,
+		uint16_t reg, uint8_t reg_val, uint8_t pgd)
+{
+	int ret = -1;
+#ifdef CONFIG_SLIMBUS
+	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);
+	}
+#endif
+	return ret;
+}
+
+int btfm_slim_read(struct btfmslim *btfmslim, uint32_t reg, uint8_t pgd)
+{
+	int ret = -1;
+#ifdef CONFIG_SLIMBUS
+	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);
+	}
+#endif
+	return ret;
+}
+
+#ifdef CONFIG_SLIMBUS
+static bool btfm_slim_is_sb_reset_needed(int chip_ver)
+{
+	switch (chip_ver) {
+	case QCA_APACHE_SOC_ID_0100:
+	case QCA_APACHE_SOC_ID_0110:
+	case QCA_APACHE_SOC_ID_0120:
+	case QCA_APACHE_SOC_ID_0121:
+		return true;
+	default:
+		return false;
+	}
+}
+
+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] = chan->ch;
+		chan->dai.sconfig.port_mask |= BIT(chan->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);
+
+	/* Delay port opening for few chipsets if:
+		1. for 8k, feedback channel
+		2. 44.1k, 88.2k rxports
+	*/
+	if (((rates == 8000 && btfm_feedback_ch_setting && rxport == 0) ||
+		(rxport == 1 && (rates == 44100 || rates == 88200))) &&
+		btfm_slim_is_sb_reset_needed(chipset_ver)) {
+
+		BTFMSLIM_INFO("btfm_is_port_opening_delayed %d",
+					btfm_is_port_opening_delayed);
+		if (!btfm_is_port_opening_delayed) {
+			BTFMSLIM_INFO("SB reset needed, sleeping");
+			btfm_is_port_opening_delayed = true;
+			msleep(DELAY_FOR_PORT_OPEN_MS);
+		}
+	}
+
+	/* 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;
+	}
+error:
+	kfree(chan->dai.sconfig.chs);
+	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;
+	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;
+	}
+
+	btfm_is_port_opening_delayed = false;
+
+	ret = slim_stream_disable(ch->dai.sruntime);
+	if (ret != 0)
+		BTFMSLIM_ERR("slim_stream_disable failed returned val = %d", ret);
+	ret = slim_stream_unprepare(ch->dai.sruntime);
+	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;
+	kfree(ch->dai.sconfig.chs);
+	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) {
+		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;
+	}
+		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;
+}
+#endif
+
+static int btfm_slim_status(struct slim_device *sdev,
+				enum slim_device_status status)
+{
+	int ret = 0;
+#ifdef CONFIG_SLIMBUS
+	struct device *dev = &sdev->dev;
+	struct btfmslim *btfm_slim;
+	btfm_slim = dev_get_drvdata(dev);
+	ret = btfm_slim_register_codec(btfm_slim);
+	if (ret)
+		BTFMSLIM_ERR("error, registering slimbus codec failed");
+#endif
+	return ret;
+}
+
+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;
+	dev_set_name(&slim->dev, "%s", "btfmslim_slave");
+	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) {
+		btfm_slim_unregister_codec(&slim->dev);
+		ret = -EPROBE_DEFER;
+		goto dealloc;
+	}
+	return ret;
+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
+};
+
+#ifdef CONFIG_SLIMBUS
+module_slim_driver(btfm_slim_driver);
+#endif
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BTFM Slimbus Slave driver");

+ 169 - 0
slimbus/btfm_slim.h

@@ -0,0 +1,169 @@
+/* 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
+
+
+
+/* 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);
+};
+
+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 */

+ 458 - 0
slimbus/btfm_slim_codec.c

@@ -0,0 +1,458 @@
+// 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 <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;
+
+#ifdef CONFIG_SLIMBUS
+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;
+	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%x\t%d\t%x", rx_chs->id,
+			rx_chs->name, rx_chs->port, rx_chs->ch);
+	}
+
+	BTFMSLIM_DBG("Tx: id\tname\tport\tch");
+	for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num);
+		i++, tx_chs++) {
+		/* Set Tx Channel number from machine driver and
+		 * get channel handler from slimbus driver
+		*/
+		tx_chs->ch = *(uint8_t *)(tx_slot + i);
+	BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id,
+			tx_chs->name, tx_chs->port, tx_chs->ch);
+	}
+
+	return ret;
+}
+
+static int btfm_slim_dai_get_channel_map(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;
+	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 -> modem */
+		.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,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 96000,
+			.rate_min = 8000,
+			.channels_min = 1,
+			.channels_max = 1,
+		},
+		.ops = &btfmslim_dai_ops,
+	},
+	{	/* Bluetooth SCO voice downlink: modem -> 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,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
+			.rate_max = 96000,
+			.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);
+}
+#endif
+
+MODULE_DESCRIPTION("BTFM Slimbus Codec driver");
+MODULE_LICENSE("GPL v2");

+ 187 - 0
slimbus/btfm_slim_slave.c

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

+ 180 - 0
slimbus/btfm_slim_slave.h

@@ -0,0 +1,180 @@
+/* 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,
+};
+
+/* 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