From 3563ee22f64550b411e8f21d0b75cf25e6001906 Mon Sep 17 00:00:00 2001 From: David Wronek Date: Wed, 4 Dec 2024 18:46:59 +0100 Subject: [PATCH] Import S928BXXS4AXJB audio-kernel changes Change-Id: I3d56a872209378068d0eea3caf04f2ea7c5a81d4 --- qcom/opensource/audio-kernel/Android.mk | 12 + qcom/opensource/audio-kernel/EnableBazel.mk | 2 + .../audio-kernel/asoc/codecs/Kbuild | 5 + .../codecs/lpass-cdc/lpass-cdc-tx-macro.c | 117 +- .../audio-kernel/asoc/codecs/tas25xx/Kbuild | 87 + .../audio-kernel/asoc/codecs/tas25xx/Kconfig | 107 + .../audio-kernel/asoc/codecs/tas25xx/Makefile | 53 + .../asoc/codecs/tas25xx/inc/tas25xx-codec.h | 30 + .../asoc/codecs/tas25xx/inc/tas25xx-device.h | 22 + .../asoc/codecs/tas25xx/inc/tas25xx-ext.h | 43 + .../asoc/codecs/tas25xx/inc/tas25xx-logic.h | 42 + .../asoc/codecs/tas25xx/inc/tas25xx-misc.h | 38 + .../tas25xx/inc/tas25xx-regbin-parser.h | 120 + .../asoc/codecs/tas25xx/inc/tas25xx-regmap.h | 26 + .../asoc/codecs/tas25xx/inc/tas25xx.h | 420 +++ .../asoc/codecs/tas25xx/src/tas25xx-codec.c | 716 +++++ .../asoc/codecs/tas25xx/src/tas25xx-logic.c | 1472 ++++++++++ .../asoc/codecs/tas25xx/src/tas25xx-misc.c | 497 ++++ .../tas25xx/src/tas25xx-regbin-parser.c | 2495 +++++++++++++++++ .../asoc/codecs/tas25xx/src/tas25xx-regmap.c | 1121 ++++++++ .../asoc/codecs/tas25xx/src/tas25xx.c | 212 ++ .../opensource/audio-kernel/asoc/msm_common.c | 27 + .../opensource/audio-kernel/asoc/msm_common.h | 19 + .../audio-kernel/asoc/msm_dailink.h | 34 + qcom/opensource/audio-kernel/asoc/pineapple.c | 986 ++++++- .../audio-kernel/audio_kernel_modules.mk | 3 + .../audio_kernel_product_board.mk | 4 +- .../opensource/audio-kernel/dsp/adsp-loader.c | 55 +- 28 files changed, 8750 insertions(+), 15 deletions(-) create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kbuild create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kconfig create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Makefile create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-codec.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-device.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-ext.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-logic.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-misc.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regbin-parser.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regmap.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx.h create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-codec.c create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-logic.c create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-misc.c create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regbin-parser.c create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regmap.c create mode 100644 qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx.c diff --git a/qcom/opensource/audio-kernel/Android.mk b/qcom/opensource/audio-kernel/Android.mk index c495ffe8db..74af0bc2b4 100644 --- a/qcom/opensource/audio-kernel/Android.mk +++ b/qcom/opensource/audio-kernel/Android.mk @@ -569,7 +569,19 @@ LOCAL_MODULE_DEBUG_ENABLE := true LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) include $(DLKM_DIR)/Build_external_kernelmodule.mk endif +ifeq ($(PROJECT_NAME),$(filter $(PROJECT_NAME),q6q b6q q6aq)) ########################################################## +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(AUDIO_SRC_FILES) +LOCAL_MODULE := tas25xx_dlkm.ko +LOCAL_MODULE_KBUILD_NAME := asoc/codecs/tas25xx/tas25xx_dlkm.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/Build_external_kernelmodule.mk +endif +########################################################### + endif # DLKM check endif # supported target check endif diff --git a/qcom/opensource/audio-kernel/EnableBazel.mk b/qcom/opensource/audio-kernel/EnableBazel.mk index c868d294e6..b3a3aa2cdd 100644 --- a/qcom/opensource/audio-kernel/EnableBazel.mk +++ b/qcom/opensource/audio-kernel/EnableBazel.mk @@ -40,6 +40,8 @@ LOCAL_MODULE_KO_DIRS += asoc/codecs/wcd939x/wcd939x_slave_dlkm.ko LOCAL_MODULE_KO_DIRS += asoc/codecs/wcd9378/wcd9378_dlkm.ko LOCAL_MODULE_KO_DIRS += asoc/codecs/wcd9378/wcd9378_slave_dlkm.ko LOCAL_MODULE_KO_DIRS += asoc/codecs/hdmi_dlkm.ko +ifeq ($(PROJECT_NAME),$(filter $(PROJECT_NAME),q6q b6q q6aq)) +LOCAL_MODULE_KO_DIRS += asoc/codecs/tas25xx/tas25xx_dlkm.ko endif ifeq ($(call is-board-platform-in-list,pitti),true) diff --git a/qcom/opensource/audio-kernel/asoc/codecs/Kbuild b/qcom/opensource/audio-kernel/asoc/codecs/Kbuild index 61f6953624..61748cc5da 100644 --- a/qcom/opensource/audio-kernel/asoc/codecs/Kbuild +++ b/qcom/opensource/audio-kernel/asoc/codecs/Kbuild @@ -285,6 +285,11 @@ ifeq ($(KERNEL_BUILD), 1) obj-y += rouleur/ obj-y += ./ endif + +ifdef CONFIG_SND_SOC_TAS25XX + obj-y += tas25xx/ +endif + # Module information used by KBuild framework obj-$(CONFIG_WCD9XXX_CODEC_CORE) += wcd_core_dlkm.o obj-$(CONFIG_WCD9XXX_CODEC_CORE_V2) += wcd_core_dlkm.o diff --git a/qcom/opensource/audio-kernel/asoc/codecs/lpass-cdc/lpass-cdc-tx-macro.c b/qcom/opensource/audio-kernel/asoc/codecs/lpass-cdc/lpass-cdc-tx-macro.c index b2c3ad2486..7cdcc8b1e2 100644 --- a/qcom/opensource/audio-kernel/asoc/codecs/lpass-cdc/lpass-cdc-tx-macro.c +++ b/qcom/opensource/audio-kernel/asoc/codecs/lpass-cdc/lpass-cdc-tx-macro.c @@ -17,6 +17,9 @@ #include "lpass-cdc.h" #include "lpass-cdc-registers.h" #include "lpass-cdc-clk-rsc.h" +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +#include +#endif #define AUTO_SUSPEND_DELAY 50 /* delay in msec */ #define LPASS_CDC_TX_MACRO_MAX_OFFSET 0x1000 @@ -104,6 +107,19 @@ enum { VA_MCLK, }; +/* Based on 9.6MHZ MCLK Freq */ +enum { + CLK_DISABLED = 0, + CLK_2P4MHZ, + CLK_0P6MHZ, +}; + +static int dmic_clk_rate_div[] = { + [CLK_DISABLED] = 0, + [CLK_2P4MHZ] = LPASS_CDC_TX_MACRO_CLK_DIV_4, + [CLK_0P6MHZ] = LPASS_CDC_TX_MACRO_CLK_DIV_16, +}; + struct lpass_cdc_tx_macro_reg_mask_val { u16 reg; u8 mask; @@ -149,8 +165,15 @@ struct lpass_cdc_tx_macro_priv { int pcm_rate[NUM_DECIMATORS]; bool swr_dmic_enable; int wlock_holders; + u32 dmic_rate_override; }; +static const char* const dmic_rate_override_text[] = { + "DISABLED", "CLK_2P4MHZ", "CLK_0P6MHZ" +}; + +static SOC_ENUM_SINGLE_EXT_DECL(dmic_rate_enum, dmic_rate_override_text); + static int lpass_cdc_tx_macro_wake_enable(struct lpass_cdc_tx_macro_priv *tx_priv, bool wake_enable) { @@ -202,12 +225,48 @@ static bool lpass_cdc_tx_macro_get_data(struct snd_soc_component *component, return true; } +static int lpass_cdc_dmic_rate_override_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct lpass_cdc_tx_macro_priv *tx_priv = NULL; + struct device *tx_dev = NULL; + + if (!lpass_cdc_tx_macro_get_data(component, &tx_dev, &tx_priv, __func__)) + return -EINVAL; + + ucontrol->value.enumerated.item[0] = tx_priv->dmic_rate_override; + dev_dbg(component->dev, "%s: dmic rate: %d\n", + __func__, tx_priv->dmic_rate_override); + + return 0; +} + +static int lpass_cdc_dmic_rate_override_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct lpass_cdc_tx_macro_priv *tx_priv = NULL; + struct device *tx_dev = NULL; + + if (!lpass_cdc_tx_macro_get_data(component, &tx_dev, &tx_priv, __func__)) + return -EINVAL; + + tx_priv->dmic_rate_override = ucontrol->value.enumerated.item[0]; + dev_dbg(component->dev, "%s: dmic rate: %d\n", + __func__, tx_priv->dmic_rate_override); + + return 0; +} + static int lpass_cdc_tx_macro_mclk_enable( struct lpass_cdc_tx_macro_priv *tx_priv, bool mclk_enable) { struct regmap *regmap = dev_get_regmap(tx_priv->dev->parent, NULL); - int ret = 0; + int ret = 0, rc = 0; if (regmap == NULL) { dev_err_ratelimited(tx_priv->dev, "%s: regmap is NULL\n", __func__); @@ -232,16 +291,34 @@ static int lpass_cdc_tx_macro_mclk_enable( lpass_cdc_clk_rsc_fs_gen_request(tx_priv->dev, true); regcache_mark_dirty(regmap); - regcache_sync_region(regmap, + + ret = regcache_sync_region(regmap, TX_START_OFFSET, TX_MAX_OFFSET); + if (ret < 0) { + dev_err_ratelimited(tx_priv->dev, + "%s: regcache_sync_region failed\n", + __func__); +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + sdp_info_print("%s: regcache_sync_region failed\n", + __func__); +#endif + lpass_cdc_clk_rsc_fs_gen_request(tx_priv->dev, + false); + lpass_cdc_clk_rsc_request_clock(tx_priv->dev, + TX_CORE_CLK, + TX_CORE_CLK, + false); + goto exit; + } if (tx_priv->tx_mclk_users == 0) { - regmap_update_bits(regmap, + rc = (rc | regmap_update_bits(regmap, LPASS_CDC_TX_CLK_RST_CTRL_MCLK_CONTROL, - 0x01, 0x01); - regmap_update_bits(regmap, + 0x01, 0x01)); + + rc = (rc | regmap_update_bits(regmap, LPASS_CDC_TX_CLK_RST_CTRL_FS_CNT_CONTROL, - 0x01, 0x01); + 0x01, 0x01)); } tx_priv->tx_mclk_users++; } else { @@ -253,17 +330,26 @@ static int lpass_cdc_tx_macro_mclk_enable( } tx_priv->tx_mclk_users--; if (tx_priv->tx_mclk_users == 0) { - regmap_update_bits(regmap, + rc = (rc | regmap_update_bits(regmap, LPASS_CDC_TX_CLK_RST_CTRL_FS_CNT_CONTROL, - 0x01, 0x00); - regmap_update_bits(regmap, + 0x01, 0x00)); + rc = (rc | regmap_update_bits(regmap, LPASS_CDC_TX_CLK_RST_CTRL_MCLK_CONTROL, - 0x01, 0x00); + 0x01, 0x00)); } + + if (rc < 0) { + dev_err_ratelimited(tx_priv->dev, "%s: register writes failed\n", + __func__); +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + sdp_info_print("%s: register writes failed\n", + __func__); +#endif + } lpass_cdc_clk_rsc_fs_gen_request(tx_priv->dev, false); - lpass_cdc_clk_rsc_request_clock(tx_priv->dev, + ret = lpass_cdc_clk_rsc_request_clock(tx_priv->dev, TX_CORE_CLK, TX_CORE_CLK, false); @@ -1869,6 +1955,9 @@ static const struct snd_kcontrol_new lpass_cdc_tx_macro_snd_controls[] = { SOC_ENUM_EXT("BCS CH_SEL", bcs_ch_sel_mux_enum, lpass_cdc_tx_macro_get_bcs_ch_sel, lpass_cdc_tx_macro_put_bcs_ch_sel), + + SOC_ENUM_EXT("DMIC_RATE OVERRIDE", dmic_rate_enum, + lpass_cdc_dmic_rate_override_get, lpass_cdc_dmic_rate_override_put), }; static int lpass_cdc_tx_macro_clk_div_get(struct snd_soc_component *component) @@ -1879,6 +1968,9 @@ static int lpass_cdc_tx_macro_clk_div_get(struct snd_soc_component *component) if (!lpass_cdc_tx_macro_get_data(component, &tx_dev, &tx_priv, __func__)) return -EINVAL; + if (tx_priv->dmic_rate_override) + return dmic_clk_rate_div[tx_priv->dmic_rate_override]; + return (int)tx_priv->dmic_clk_div; } @@ -1935,6 +2027,9 @@ undefined_rate: static const struct lpass_cdc_tx_macro_reg_mask_val lpass_cdc_tx_macro_reg_init[] = { {LPASS_CDC_TX0_TX_PATH_SEC7, 0x3F, 0x0A}, + {LPASS_CDC_TX0_TX_PATH_CFG1, 0x0F, 0x0A}, + {LPASS_CDC_TX1_TX_PATH_CFG1, 0x0F, 0x0A}, + {LPASS_CDC_TX2_TX_PATH_CFG1, 0x0F, 0x0A}, }; static int lpass_cdc_tx_macro_init(struct snd_soc_component *component) diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kbuild b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kbuild new file mode 100644 index 0000000000..cbc06f934d --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kbuild @@ -0,0 +1,87 @@ +ifeq ($(MODNAME),) + KERNEL_BUILD := 1 +else + KERNEL_BUILD := 0 +endif + +ifeq ($(KERNEL_BUILD), 1) + AUDIO_BLD_DIR := $(srctree) + AUDIO_ROOT := $(AUDIO_BLD_DIR)/techpack/audio +endif + + +ifeq ($(KERNEL_BUILD), 0) + ifeq ($(CONFIG_ARCH_WAIPIO), y) + include $(AUDIO_ROOT)/config/waipioauto.conf + INCS += -include $(AUDIO_ROOT)/config/waipioautoconf.h + endif +endif + +############ COMMON ############ +COMMON_DIR := include +COMMON_INC := -I$(AUDIO_ROOT)/$(COMMON_DIR) + +ifdef CONFIG_SND_SOC_TAS25XX + TAS25XX_OBJS += src/tas25xx-codec.o + TAS25XX_OBJS += src/tas25xx-regmap.o + TAS25XX_OBJS += src/tas25xx.o + TAS25XX_OBJS += src/tas25xx-logic.o + TAS25XX_OBJS += src/tas25xx-regbin-parser.o +endif + + +ifneq ($(CONFIG_TAS25XX_ALGO),) +TAS25XX_OBJS += algo/src/tas25xx-algo-intf.o +TAS25XX_OBJS += algo/src/tas25xx-calib.o +TAS25XX_OBJS += algo/src/tas25xx-algo-common.o +endif + +ifneq ($(CONFIG_PLATFORM_MTK),) +TAS25XX_OBJS += algo/platform/mtk/tas25xx-mtk-wrapper.o +endif + +ifneq ($(CONFIG_TAS25XX_MISC),) +TAS25XX_OBJS += src/tas25xx-misc.o +endif + +ifneq ($(CONFIG_PLATFORM_QCOM),) +TAS25XX_OBJS += algo/platform/qcom/tas25xx-algo-qdsp-intf.o +endif + +ifneq ($(CONFIG_TISA_KBIN_INTF),) +TAS25XX_OBJS += algo/src/tas25xx-algo-kbin-intf.o +TAS25XX_OBJS += algo/src/tas25xx-algo-bin-utils.o +endif + +ifneq ($(CONFIG_TAS25XX_CALIB_VAL_BIG),) +TAS25XX_OBJS += algo/src/tas25xx-calib-validation.o +endif + +ifneq ($(CONFIG_TISA_SYSFS_INTF),) +TAS25XX_OBJS += algo/src/tas25xx-sysfs-debugfs-utils.o +TAS25XX_OBJS += algo/src/smartpa-debug-common.o +endif + +ifneq ($(CONFIG_TISA_BIN_INTF),) +TAS25XX_OBJS += algo/src/tas25xx-algo-bin-intf.o +endif + +ifneq ($(CONFIG_PLATFORM_EXYNOS),) +TAS25XX_OBJS += algo/platform/exynos/tas25xx-algo-exynos-dsp-intf.o +TAS25XX_OBJS += algo/platform/exynos/skinprot-sysfs-cb.o +endif + +LINUX_INC += -Iinclude/linux +TAS25XX_INC = -I$(AUDIO_ROOT)/asoc/codecs/tas25xx +TAS25XX_INC += -I$(AUDIO_ROOT)/asoc/codecs/tas25xx/inc +INCS += $(COMMON_INC) $(TAS25XX_INC) +EXTRA_CFLAGS += $(INCS) + + +# Module information used by KBuild framework +obj-$(CONFIG_SND_SOC_TAS25XX) += tas25xx_dlkm.o +tas25xx_dlkm-y := $(TAS25XX_OBJS) + +# inject some build related information +DEFINES += -DBUILD_TIMESTAMP=\"$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')\" + diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kconfig b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kconfig new file mode 100644 index 0000000000..730c5b1a65 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Kconfig @@ -0,0 +1,107 @@ + +menuconfig SND_SOC_TAS25XX + tristate "Texas Instruments TAS25XX SmartAmp(R)" + help + Enable the support for TAS driver. + + This is the main config under which various + other configuration can be enabled based on + the below configurations. + +if SND_SOC_TAS25XX +config PLATFORM_EXYNOS + bool "Exynos platform support" + depends on SND_SOC_TAS25XX + default n + help + Enable support for Exynos platform. + + This enables the platform specific interface + which acts as abstraction layer to the algorithm + for Exynos platform. + +config PLATFORM_MTK + bool "MTK platform support" + depends on SND_SOC_TAS25XX + default n + help + Enable support for MTK platform. + + This enables the platform specific interface + which acts as abstraction layer to the algorithm + for MTK platform. + +config TAS25XX_ALGO + bool "TAS25XX AP DSP Communication Support" + depends on SND_SOC_TAS25XX + help + Enable support for TAS25XX Calibration driver. + + This includes TAS25XX Calibration driver interfaces + and functions also interfacing driver to corresponding + Platform/DSP + +config PLATFORM_QCOM + bool "QCOM platform support" + depends on TAS25XX_ALGO + default n + help + Enable support for Qcom platform. + + This enables the platform specific interface + which acts as abstraction layer to the algorithm + for Exynos platform. + +config TISA_KBIN_INTF + bool "Kbin file method support" + depends on PLATFORM_QCOM + default n + help + Enable support for KBin file method + + This is the algorithm specific configuration + where the binary file will be opened in the kernel + using request_firmware API. This interface currently supported + only on the Qualcomm platform + +config TAS25XX_CALIB_VAL_BIG + bool "For bigdata & calibration support" + depends on TAS25XX_ALGO + default n + help + Enable support for bigdata & calibration. + + Enables the support for sysfs entries under + /sys/class/tas25xx directory + for calibration, validataion and bigdata + +config TAS25XX_IRQ_BD + bool "For bigdata IRQ data" + depends on SND_SOC_TAS25XX + default n + help + Enable support for bigdata & calibration. + + Enables the support for sysfs entries under + /sys/class/tas25xx_dev/ directory + for irq related big data + +config TISA_SYSFS_INTF + bool "sysfs interface for calibration and algo support" + depends on TAS25XX_ALGO + default n + help + Enable the support for sysfs based interfaces + for calibration and algo control + +config TAS25XX_MISC + bool "Misc Driver support" + default y + help + Enable misc driver support. + + Enable the misc driver "TAS25XX" which is + interface to communicate to device via register + read and write + +endif # SND_SOC_TAS25XX diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Makefile b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Makefile new file mode 100644 index 0000000000..303571c508 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/Makefile @@ -0,0 +1,53 @@ +ccflags-y += -I$(srctree)/$(src)/inc/ +ccflags-y += -I$(srctree)/sound/soc/codecs/uda20/driver/tas25xx_driver/inc/ +ccflags-y += -I$(srctree)/sound/soc/mediatek/common/ +ccflags-y += -I$(src) + +obj-$(CONFIG_SND_SOC_TAS25XX) += snd-soc-tas25xx.o + +snd-soc-tas25xx-$(CONFIG_SND_SOC_TAS25XX) := src/tas25xx-codec.o \ + src/tas25xx-regmap.o \ + src/tas25xx.o \ + src/tas25xx-logic.o \ + src/tas25xx-regbin-parser.o + +ifneq ($(CONFIG_TAS25XX_ALGO),) +snd-soc-tas25xx-objs += algo/src/tas25xx-algo-intf.o \ + algo/src/tas25xx-calib.o \ + algo/src/tas25xx-algo-common.o +endif + +ifneq ($(CONFIG_PLATFORM_MTK),) +snd-soc-tas25xx-objs += algo/platform/mtk/tas25xx-mtk-wrapper.o +endif + +ifneq ($(CONFIG_TAS25XX_MISC),) +snd-soc-tas25xx-objs += src/tas25xx-misc.o +endif + +ifneq ($(CONFIG_PLATFORM_QCOM),) +snd-soc-tas25xx-objs += algo/platform/qcom/tas25xx-algo-qdsp-intf.o +endif + +ifneq ($(CONFIG_TISA_KBIN_INTF),) +snd-soc-tas25xx-objs += algo/src/tas25xx-algo-kbin-intf.o \ + algo/src/tas25xx-algo-bin-utils.o +endif + +ifneq ($(CONFIG_TAS25XX_CALIB_VAL_BIG),) +snd-soc-tas25xx-objs += algo/src/tas25xx-calib-validation.o +endif + +ifneq ($(CONFIG_TISA_SYSFS_INTF),) +snd-soc-tas25xx-objs += algo/src/tas25xx-sysfs-debugfs-utils.o \ + algo/src/smartpa-debug-common.o +endif + +ifneq ($(CONFIG_TISA_BIN_INTF),) +snd-soc-tas25xx-objs += algo/src/tas25xx-algo-bin-intf.o +endif + +ifneq ($(CONFIG_PLATFORM_EXYNOS),) +snd-soc-tas25xx-objs += algo/platform/exynos/tas25xx-algo-exynos-dsp-intf.o +snd-soc-tas25xx-objs += algo/platform/exynos/skinprot-sysfs-cb.o +endif diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-codec.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-codec.h new file mode 100644 index 0000000000..ef1c2f5fff --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-codec.h @@ -0,0 +1,30 @@ +/* + * ============================================================================= + * Copyright (c) 2016 Texas Instruments Inc. + * + * 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; version 2. + * + * 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. + * + * File: + * tas25xx-codec.h + * + * Description: + * header file for tas25xx-codec.c + * + * ============================================================================= + */ + +#ifndef _TAS25XX_CODEC_H +#define _TAS25XX_CODEC_H + +#include "tas25xx.h" + +int tas25xx_register_codec(struct tas25xx_priv *p_tas25xx); +int tas25xx_deregister_codec(struct tas25xx_priv *p_tas25xx); + +#endif /* _TAS25XX_CODEC_H */ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-device.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-device.h new file mode 100644 index 0000000000..9678e82b7b --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-device.h @@ -0,0 +1,22 @@ +#ifndef __TAS25XX_DEVICE_ +#define __TAS25XX_DEVICE_ + +#include "tas25xx.h" + +int tas25xx_software_reset(struct tas25xx_priv *p_tas25xx, int ch); +int tas25xx_set_power_mute(struct tas25xx_priv *p_tas25xx, int ch); +int tas25xx_tx_set_edge(struct tas25xx_priv *p_tas25xx, + unsigned int tx_edge, int ch); +int tas25xx_tx_set_start_slot(struct tas25xx_priv *p_tas25xx, + unsigned int tx_start_slot, int ch); +int tas25xx_rx_set_edge(struct tas25xx_priv *p_tas25xx, + unsigned int rx_edge, int ch); +int tas25xx_rx_set_bitwidth(struct tas25xx_priv *p_tas25xx, + int bitwidth, int ch); +/* Interrupt Related Functions */ +int tas_dev_interrupt_clear(struct tas25xx_priv *p_tas25xx, int ch); +int tas_dev_interrupt_enable(struct tas25xx_priv *p_tas25xx, int ch); +int tas_dev_interrupt_disable(struct tas25xx_priv *p_tas25xx, int ch); +int tas_dev_interrupt_read(struct tas25xx_priv *p_tas25xx, int ch, int *type); + +#endif /* __TAS25XX_DEVICE_ */ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-ext.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-ext.h new file mode 100644 index 0000000000..08ee4f1c1f --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-ext.h @@ -0,0 +1,43 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance Smart Amplifier + * + * Copyright (C) 2024 Texas Instruments, Inc. + * + * Author: Niranjan H Y + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#ifndef __TAS25XX_EXT_H__ +#define __TAS25XX_EXT_H__ + +enum tas_amp_status_t { + TAS_AMP_ERR_EINVAL = -3, /* invalid parameter */ + TAS_AMP_ERR_FW_LOAD = -2, /* amp fw download failure */ + TAS_AMP_ERR_I2C = -1, /* amp i2c error */ + TAS_AMP_STATE_UNINIT = 0, /* uninitialized state */ + TAS_AMP_STATE_BOOT_SUCCESS = 1, /* amp first i2c successful */ + TAS_AMP_STATE_FW_LOAD_SUCCESS = 2, /* amp fully initialized for playback */ +}; + +enum tas_amp_error_t { + TAS_AMP_NO_ERR = 0, + TAS_AMP_SHORTCIRCUIT_ERR = 1, + TAS_AMP_ERR_MAX, +}; + +/* get the current amp status */ +enum tas_amp_status_t tas25xx_get_state(uint32_t id); + +/* error callback for i2c error */ +void tas25xx_register_i2c_error_callback(void (*i2c_err_cb)(uint32_t i2caddr)); +void tas25xx_register_amp_error_callback(void (*amp_err_cb)(int32_t i2caddr, int32_t err)); + + +#endif /* __TAS25XX_EXT_H__ */ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-logic.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-logic.h new file mode 100644 index 0000000000..f53954cf17 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-logic.h @@ -0,0 +1,42 @@ +#ifndef __TAS25XX_LOGIC_ +#define __TAS25XX_LOGIC_ + +#include "tas25xx.h" + +#define TAS25XX_STREAM_PLAYBACK 0 +#define TAS25XX_STREAM_CAPTURE 1 + +int tas25xx_register_device(struct tas25xx_priv *p_tas25xx); +int tas25xx_probe(struct tas25xx_priv *p_tas25xx); +void tas25xx_remove(struct tas25xx_priv *p_tas25xx); +int tas25xx_load_init(struct tas25xx_priv *p_tas25xx, int chn); +int tas25xx_irq_work_func(struct tas25xx_priv *p_tas25xx); +void tas25xx_load_config(struct tas25xx_priv *p_tas25xx, int chn); +int tas25xx_init_work_func(struct tas25xx_priv *p_tas25xx, + struct tas_device *dev_tas25xx); +int tas25xx_dc_work_func(struct tas25xx_priv *p_tas25xx, int chn); +void tas_reload(struct tas25xx_priv *p_tas25xx, int chn); +int tas25xx_set_power_state(struct tas25xx_priv *p_tas25xx, + enum tas_power_states_t state, uint32_t chbitmask); +int tas25xx_iv_vbat_slot_config(struct tas25xx_priv *p_tas25xx, + int mn_slot_width); +int tas25xx_set_bitwidth(struct tas25xx_priv *p_tas25xx, + int bitwidth, int stream); +int tas25xx_set_dai_fmt_for_fmt(struct tas25xx_priv *p_tas25xx, + unsigned int fmt); +int tas25xx_set_tdm_rx_slot(struct tas25xx_priv *p_tas25xx, + int slots, int slot_width); +int tas25xx_set_tdm_tx_slot(struct tas25xx_priv *p_tas25xx, + int slots, int slot_width); + +int tas25xx_change_book(struct tas25xx_priv *p_tas25xx, + int32_t chn, int book); + +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) +void tas25xx_log_interrupt_stats(struct tas25xx_priv *p_tas25xx); +void tas25xx_clear_interrupt_stats(struct tas25xx_priv *p_tas25xx); +#endif /* CONFIG_TAS25XX_IRQ_BD */ + +int tas25xx_get_drv_channel_opmode(void); + +#endif /*__TAS25XX_LOGIC_*/ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-misc.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-misc.h new file mode 100644 index 0000000000..f37ee96c31 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-misc.h @@ -0,0 +1,38 @@ +/* + * ============================================================================= + * Copyright (c) 2016 Texas Instruments Inc. + * + * 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; version 2. + * + * 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. + * + * File: + * tas2562-misc.h + * + * Description: + * header file for tas2562-misc.c + * + * ============================================================================= + */ + +#ifndef _TAS25XX_MISC_H +#define _TAS25XX_MISC_H + +#define TIAUDIO_CMD_REG_WITE 1 +#define TIAUDIO_CMD_REG_READ 2 +#define TIAUDIO_CMD_DEBUG_ON 3 +#define TIAUDIO_CMD_CALIBRATION 7 +#define TIAUDIO_CMD_SAMPLERATE 8 +#define TIAUDIO_CMD_BITRATE 9 +#define TIAUDIO_CMD_DACVOLUME 10 +#define TIAUDIO_CMD_SPEAKER 11 + +int tas25xx_register_misc(struct tas25xx_priv *p_tas25xx); +int tas25xx_deregister_misc(struct tas25xx_priv *p_tas25xx); + +#endif /* _TAS25XX_MISC_H */ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regbin-parser.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regbin-parser.h new file mode 100644 index 0000000000..8b6e6ffda7 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regbin-parser.h @@ -0,0 +1,120 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance 4W Smart Amplifier + * + * Copyright (C) 2021 Texas Instruments, Inc. + * + * Author: Vijeth P O + * Modified: 10-07-21 + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#ifndef __TAS25XX_REGBIN_PARSER__ +#define __TAS25XX_REGBIN_PARSER__ + +#define SampleRate_48000 0 +#define SampleRate_44100 1 +#define SampleRate_96000 2 + +#define FMT_INV_NB_NF 0 +#define FMT_INV_IB_NF 1 +#define FMT_INV_NB_IF 2 +#define FMT_INV_IB_IF 3 + +#define FMT_MASK_I2S 0 +#define FMT_MASK_DSP_A 1 +#define FMT_MASK_DSP_B 2 +#define FMT_MASK_LEFT_J 3 + +#define RX_SLOTS_16 0 +#define RX_SLOTS_24 1 +#define RX_SLOTS_32 2 + +#define TX_SLOTS_16 0 +#define TX_SLOTS_24 1 +#define TX_SLOTS_32 2 + +#define RX_BITWIDTH_16 0 +#define RX_BITWIDTH_24 1 +#define RX_BITWIDTH_32 2 + +#define RX_SLOTLEN_16 0 +#define RX_SLOTLEN_24 1 +#define RX_SLOTLEN_32 2 + +#define TX_SLOTLEN_16 0 +#define TX_SLOTLEN_24 1 +#define TX_SLOTLEN_32 2 + +#define SUPPORTED_BIN_VERSION 7 + +#define TAS25XX_DEFAULT 0xFFFFFFFF + +enum kcntl_during_t { + KCNTR_ANYTIME = 0, /* instant update */ + KCNTR_PRE_POWERUP = 1, /* during pre-power up */ + KCNTR_POST_POWERUP = 2, /* during post-power up */ +}; + +struct default_hw_params { + char sample_rate; + char fmt_inv; + char fmt_mask; + char rx_slots; + char tx_slots; + char rx_bitwidth; + char rx_slotlen; + char tx_slotlen; +}; + +struct tas_dev { + u8 device; + u8 i2c_addr; +}; + +struct bin_header { + u32 version; + u8 name[64]; + u32 timestamp; + u32 size; + u32 channels; + struct tas_dev dev[MAX_CHANNELS]; + u8 iv_width; + u8 vbat_mon; + u32 features; +} __attribute__((packed)); + +int32_t tas25xx_load_firmware(struct tas25xx_priv *p_tas25xx, int max_retry_count); +int32_t tas25xx_create_kcontrols(struct tas25xx_priv *p_tas25xx); +int32_t tas25xx_remove_binfile(struct tas25xx_priv *p_tas25xx); +int32_t tas25xx_set_init_params(struct tas25xx_priv *p_tas25xx, int32_t ch); +int32_t tas25xx_set_sample_rate(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t sample_rate); +int32_t tas25xx_set_fmt_inv(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t fmt_inv); +int32_t tas25xx_set_fmt_mask(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t fmt_mask); +int32_t tas25xx_set_rx_slots(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t rx_slot); +int32_t tas25xx_set_tx_slots(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t tx_slot); +int32_t tas25xx_set_rx_bitwidth(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t rx_bitwidth); +int32_t tas25xx_set_rx_slotlen(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t rx_slotlen); +int32_t tas25xx_set_tx_slotlen(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t tx_slotlen); +int32_t tas25xx_set_pre_powerup(struct tas25xx_priv *p_tas25xx, int32_t ch); +int32_t tas25xx_set_post_powerup(struct tas25xx_priv *p_tas25xx, int32_t ch); +int32_t tas25xx_set_pre_powerdown(struct tas25xx_priv *p_tas25xx, int32_t ch); +int32_t tas25xx_set_post_powerdown(struct tas25xx_priv *p_tas25xx, int32_t ch); + +int32_t tas25xx_process_block(struct tas25xx_priv *p_tas25xx, char *mem, int32_t chn); + +int32_t tas25xx_check_if_powered_on(struct tas25xx_priv *p_tas25xx, int *state, int ch); +int tas_write_init_config_params(struct tas25xx_priv *p_tas25xx, int number_of_channels); + +int32_t tas25xx_update_kcontrol_data(struct tas25xx_priv *p_tas25xx, enum kcntl_during_t cur_state, + uint32_t chmask); + +void tas25xx_prep_dev_for_calib(int start); +#endif /* __TAS25XX_REGBIN_PARSER__ */ \ No newline at end of file diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regmap.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regmap.h new file mode 100644 index 0000000000..cc83a1c961 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx-regmap.h @@ -0,0 +1,26 @@ +#ifndef __TAS25XX_REGMAP__ +#define __TAS25XX_REGMAP__ +#include +#include "tas25xx.h" + +struct linux_platform { + struct device *dev; + struct i2c_client *client; + struct regmap *regmap[MAX_CHANNELS]; + struct hrtimer mtimer; + struct snd_soc_component *codec; + /* device is working, but system is suspended */ + int (*runtime_suspend)(struct tas25xx_priv *p_tas25xx); + int (*runtime_resume)(struct tas25xx_priv *p_tas25xx); + bool mb_runtime_suspend; + bool i2c_suspend; +}; + +void tas25xx_select_cfg_blk(void *pContext, int conf_no, + unsigned char block_type); +void tas25xx_dump_regs(struct tas25xx_priv *p_tas25xx, int chn); + + +void tas25xx_register_i2c_error_callback(void (*i2c_err_cb)(uint32_t)); +int tas25xx_check_last_i2c_error_n_reset(void); +#endif /*__TAS25XX_REGMAP__*/ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx.h b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx.h new file mode 100644 index 0000000000..b978d3632c --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/inc/tas25xx.h @@ -0,0 +1,420 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance 4W Smart Amplifier + * + * Copyright (C) 2022 Texas Instruments, Inc. + * + * Author: Niranjan H Y, Vijeth P O + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#ifndef __TAS25XX__ +#define __TAS25XX__ + +#include +#include +#include +#include +#include +#include +#include + +#define TAS25XX_DRIVER_TAG "UDA_0.0.21_6_K6.1" + +#define MAX_CHANNELS 4 + +/* Page Control Register */ +#define TAS25XX_PAGECTL_REG 0 + +/* Book Control Register (available in page0 of each book) */ +#define TAS25XX_BOOKCTL_PAGE 0 +#define TAS25XX_BOOKCTL_REG 127 + +#define TAS25XX_REG(book, page, reg) (((book * 256 * 128) + \ + (page * 128)) + reg) + +#define TAS25XX_BOOK_ID(reg) (reg / (256 * 128)) +#define TAS25XX_PAGE_ID(reg) ((reg % (256 * 128)) / 128) +#define TAS25XX_BOOK_REG(reg) (reg % (256 * 128)) +#define TAS25XX_PAGE_REG(reg) ((reg % (256 * 128)) % 128) +#define TAS25XX_REG_NO_BOOK(reg) (reg - (TAS25XX_BOOK_ID(reg) * 256 * 128)) + +/* Book */ +#define TAS25XX_BOOK TAS25XX_REG(0x0, 0x0, 0x7F) +#define TAS25XX_BOOK_BOOK70_MASK (0xff << 0) + +/* Rev Info */ +#define TAS25XX_REVID_REG TAS25XX_REG(0x0, 0x0, 0x78) + +/* data format */ +#define TAS25XX_DATAFORMAT_SHIFT 2 +#define TAS25XX_DATAFORMAT_I2S 0x0 +#define TAS25XX_DATAFORMAT_DSP 0x1 +#define TAS25XX_DATAFORMAT_RIGHT_J 0x2 +#define TAS25XX_DATAFORMAT_LEFT_J 0x3 + +#define TAS25XX_DAI_FMT_MASK (0x7 << TAS25XX_DATAFORMAT_SHIFT) + + +#define ERROR_NONE 0x0000000 +#define ERROR_PLL_ABSENT 0x0000001 +#define ERROR_DEVA_I2C_COMM 0x0000002 +#define ERROR_DEVB_I2C_COMM 0x0000004 +#define ERROR_CLOCK 0x0000008 +#define ERROR_YRAM_CRCCHK 0x0000010 +#define ERROR_OVER_CURRENT 0x0000020 +#define ERROR_DIE_OVERTEMP 0x0000040 +#define ERROR_OVER_VOLTAGE 0x0000080 +#define ERROR_UNDER_VOLTAGE 0x0000100 +#define ERROR_BROWNOUT 0x0000200 +#define ERROR_CLASSD_PWR 0x0000400 +#define ERROR_FAILSAFE 0x0000800 + +#define TAS25XX_IRQ_DET_TIMEOUT 30000 +#define TAS25XX_IRQ_DET_CNT_LIMIT 500 + +/* 5 second */ +#define CHECK_PERIOD 5000 + +#define TAS25XX_I2C_RETRY_COUNT 3 + +#define TAS25XX_SWITCH 0x10000001 +#define TAS25XX_RIGHT_SWITCH 0x10000002 +#define RX_SCFG_LEFT 0x10000003 +#define RX_SCFG_RIGHT 0x10000004 + +#define RESTART_MAX 3 +#define MAX_CMD_LIST 5 /* sysfs cmd nodes */ + +struct tas25xx_priv; +struct snd_soc_component; + +/* REGBIN related */ +#define TAS25XX_CONFIG_SIZE (10) +#define TAS25XX_DEVICE_SUM (8) + +#define TAS25XX_CMD_SING_W (0x1) +#define TAS25XX_CMD_BURST (0x2) +#define TAS25XX_CMD_DELAY (0x3) +#define TAS25XX_CMD_FIELD_W (0x4) + +#define SMS_HTONS(a, b) ((((a)&0x00FF)<<8) | \ + ((b)&0x00FF)) +#define SMS_HTONL(a, b, c, d) ((((a)&0x000000FF)<<24) |\ + (((b)&0x000000FF)<<16) | \ + (((c)&0x000000FF)<<8) | \ + ((d)&0x000000FF)) + + +#define CMD_SINGLE_WRITE 0 +#define CMD_BURST_WRITES 1 +#define CMD_UPDATE_BITS 2 +#define CMD_DELAY 3 + +#define CMD_SINGLE_WRITE_SZ 6 +#define CMD_BURST_WRITES_SZ 9 +#define CMD_UPDATE_BITS_SZ 7 +#define CMD_DELAY_SZ 5 + +#define DSP_FW_LOAD_NTRIES 20 + +#define INTERRUPT_TYPE_CLOCK_BASED (1<<0) +#define INTERRUPT_TYPE_NON_CLOCK_BASED (1<<1) + +enum tas_power_states_t { + TAS_POWER_ACTIVE = 0, + TAS_POWER_MUTE = 1, + TAS_POWER_SHUTDOWN = 2, +}; + +enum tas25xx_dsp_fw_state { + TAS25XX_DSP_FW_NONE = 0, + TAS25XX_DSP_FW_TRYLOAD, + TAS25XX_DSP_FW_PARSE_FAIL, + TAS25XX_DSP_FW_LOAD_FAIL, + TAS25XX_DSP_FW_OK, +}; + +enum tas25xx_bin_blk_type { + TAS25XX_BIN_BLK_COEFF = 1, + TAS25XX_BIN_BLK_POST_POWER_UP, + TAS25XX_BIN_BLK_PRE_SHUTDOWN, + TAS25XX_BIN_BLK_PRE_POWER_UP, + TAS25XX_BIN_BLK_POST_SHUTDOWN +}; + +enum tas_int_action_t { + TAS_INT_ACTION_NOACTION = 0, + TAS_INT_ACTION_SW_RESET = 1 << 0, + TAS_INT_ACTION_HW_RESET = 1 << 1, + TAS_INT_ACTION_POWER_ON = 1 << 2, +}; + +struct tas25xx_block_data { + unsigned char dev_idx; + unsigned char block_type; + unsigned short yram_checksum; + unsigned int block_size; + unsigned int nSublocks; + unsigned char *regdata; +}; + +struct tas25xx_config_info { + unsigned int nblocks; + unsigned int real_nblocks; + struct tas25xx_block_data **blk_data; +}; + +void tas25xx_select_cfg_blk(void *pContext, int conf_no, + unsigned char block_type); +int tas25xx_load_container(struct tas25xx_priv *pTAS256x); +void tas25xx_config_info_remove(void *pContext); + +struct tas25xx_register { + int book; + int page; + int reg; +}; + +/* Used for Register Dump */ +struct tas25xx_reg { + unsigned int reg_index; + unsigned int reg_val; +}; + +struct tas25xx_dai_cfg { +unsigned int dai_fmt; +unsigned int tdm_delay; +}; + +/*struct tas25xx_buf_cfg { + * unsigned short bufSize; + * unsigned char *buf; + *}; + */ + +//This should be removed or modified +enum ch_bitmask { + channel_left = 0x1 << 0, + channel_right = 0x1 << 1, + channel_both = channel_left|channel_right +}; + +/* + * device ops function structure + */ +struct tas_device_ops { + /**< init typically for loading optimal settings */ + int (*tas_init)(struct tas25xx_priv *p_tas25xx, int chn); + int (*tas_probe)(struct tas25xx_priv *p_tas25xx, + struct snd_soc_component *codec, int chn); +/*TODO:*/ +}; + + +struct tas_device { + int mn_chip_id; + int mn_current_book; + int mn_addr; + int reset_gpio; + int irq_gpio; + int irq_no; + int device_id; + int channel_no; + int rx_mode; + int dvc_pcm; + int bst_vltg; + int bst_ilm; + int ampoutput_lvl; + int lim_switch; + int lim_max_attn; + int lim_thr_max; + int lim_thr_min; + int lim_att_rate; + int lim_rel_rate; + int lim_att_stp_size; + int lim_rel_stp_size; + int lim_max_thd; + int lim_min_thd; + int lim_infl_pt; + int lim_trk_slp; + int bop_enable; + int bop_thd; + int bop_att_rate; + int bop_att_stp_size; + int bop_hld_time; + int bop_mute; + int bosd_enable; + int bosd_thd; + int vbat_lpf; + int rx_cfg; + int classh_timer; + int reciever_enable; + int icn_sw; + int icn_thr; + int icn_hyst; + struct tas_device_ops dev_ops; + struct delayed_work init_work; + struct tas25xx_priv *prv_data; + /* interrupt count since interrupt enable */ + uint8_t irq_count; + unsigned long jiffies; + /* Fail Safe */ + unsigned int mn_restart; +}; + +struct tas25xx_intr_info { + char name[64]; + int32_t reg; + int32_t mask; + int32_t action; + int32_t detected; + int32_t is_clock_based; + int32_t notify_int_val; + uint32_t count; + uint64_t count_persist; + struct device_attribute *dev_attr; +}; + +struct tas25xx_interrupts { + uint8_t count; + uint8_t *buf_intr_enable; + uint8_t *buf_intr_disable; + uint8_t *buf_intr_clear; + struct tas25xx_intr_info *intr_info; + uint32_t processing_delay; +}; + +struct tas_regbin_read_blok_t { + int32_t reg; + int32_t mask; + int32_t value; +}; + +struct tas_block_op_data_t { + uint32_t no_of_rx_blks; + uint32_t no_of_tx_blks; + uint8_t *sw_reset; + uint8_t *power_check; + uint8_t *mute; + uint8_t *cal_init; + uint8_t *cal_deinit; + uint8_t *rx_fmt_data; + uint8_t *tx_fmt_data; +}; + +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) +struct irq_bigdata { + struct device_attribute *p_dev_attr; + struct attribute **p_attr_arr; + struct device *irq_dev; +}; +#endif + +struct cmd_data { + struct device_attribute *p_dev_attr; + struct attribute **p_attr_arr; + struct device *cmd_dev; +}; + +struct tas25xx_priv { + struct linux_platform *platform_data; + struct kobject *k_obj; + int m_power_state; + int mn_frame_size; + int mn_ppg; + int mn_ch_size; + int mn_rx_width; + int mn_tx_slot_width; + int sample_rate; + int mn_iv_width; + int curr_mn_iv_width; + int mn_vbat; + int curr_mn_vbat; + int ch_count; + int mn_slots; + unsigned int mn_fmt; + int mn_fmt_mode; + int mn_frame_start; + int mn_rx_edge; + int mn_rx_offset; + int mn_tx_edge; + int mn_tx_offset; + int *ti_amp_state; + int dac_power; /* this is set based on the DAC events */ + struct tas_device **devs; + int (*read)(struct tas25xx_priv *p_tas25xx, int32_t chn, + unsigned int reg, unsigned int *pValue); + int (*write)(struct tas25xx_priv *p_tas25xx, int32_t chn, + unsigned int reg, unsigned int Value); + int (*bulk_read)(struct tas25xx_priv *p_tas25xx, int32_t chn, + unsigned int reg, unsigned char *p_data, unsigned int len); + int (*bulk_write)(struct tas25xx_priv *p_tas25xx, int32_t chn, + unsigned int reg, unsigned char *p_data, unsigned int len); + int (*update_bits)(struct tas25xx_priv *p_tas25xx, int32_t chn, + unsigned int reg, unsigned int mask, unsigned int value); + void (*hw_reset)(struct tas25xx_priv *p_tas25xx); + void (*clear_irq)(struct tas25xx_priv *p_tas25xx); + void (*enable_irq)(struct tas25xx_priv *p_tas25xx); + void (*disable_irq)(struct tas25xx_priv *p_tas25xx); + unsigned int mn_err_code; + int (*plat_write)(void *plat_data, + unsigned int i2c_addr, unsigned int reg, unsigned int value, + unsigned int channel); + int (*plat_bulk_write)(void *plat_data, unsigned int i2c_addr, + unsigned int reg, unsigned char *pData, + unsigned int nLength, unsigned int channel); + int (*plat_read)(void *plat_data, unsigned int i2c_addr, + unsigned int reg, unsigned int *value, unsigned int channel); + int (*plat_bulk_read)(void *plat_data, unsigned int i2c_addr, + unsigned int reg, unsigned char *pData, + unsigned int nLength, unsigned int channel); + int (*plat_update_bits)(void *plat_data, unsigned int i2c_addr, + unsigned int reg, unsigned int mask, + unsigned int value, unsigned int channel); + void (*schedule_init_work)(struct tas25xx_priv *p_tas25xx, int ch); + void (*cancel_init_work) (struct tas25xx_priv *p_tas25xx, int ch); + struct mutex dev_lock; + struct mutex codec_lock; + struct mutex file_lock; + struct delayed_work irq_work; + struct delayed_work dc_work; + int iv_enable; + uint32_t dev_revid; + uint32_t fw_size; + uint8_t *fw_data; + struct delayed_work post_fw_load_work; + struct delayed_work fw_load_work; + wait_queue_head_t fw_wait; + int fw_load_retry_count; + atomic_t fw_state; + atomic_t fw_wait_complete; + wait_queue_head_t dev_init_wait; + atomic_t dev_init_status; + int device_used; + int irq_enabled[MAX_CHANNELS]; + struct tas25xx_interrupts intr_data[MAX_CHANNELS]; +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) + struct irq_bigdata irqdata; +#endif + struct class *class; + struct cmd_data cmd_data; + struct tas_block_op_data_t block_op_data[MAX_CHANNELS]; +}; + +static inline int is_power_up_state(enum tas_power_states_t state) +{ + if (state == TAS_POWER_ACTIVE) + return 1; + + return 0; +} + +#endif /* __TAS25XX__ */ + diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-codec.c b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-codec.c new file mode 100644 index 0000000000..3be4a6baec --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-codec.c @@ -0,0 +1,716 @@ +/* + * ============================================================================= + * Copyright (c) 2016 Texas Instruments Inc. + * + * 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; version 2. + * + * 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. + * + * File: + * tas25xx-codec.c + * + * Description: + * ALSA SoC driver for Texas Instruments TAS25XX High Performance 4W Smart + * Amplifier + * + * ============================================================================= + */ + +#define DEBUG 5 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../inc/tas25xx.h" +#include "../inc/tas25xx-ext.h" +#include "../inc/tas25xx-device.h" +#include "../inc/tas25xx-logic.h" +#include "../inc/tas25xx-regmap.h" +#include "../inc/tas25xx-regbin-parser.h" +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) +#include "../algo/inc/tas_smart_amp_v2.h" +#include "../algo/inc/tas25xx-calib.h" +#endif /*CONFIG_TAS25XX_ALGO*/ + +static const char *irq_gpio_label[2] = { + "TAS25XX-IRQ", "TAS25XX-IRQ2" +}; + +static struct tas25xx_priv *s_tas25xx; + +int tas25xx_start_fw_load(struct tas25xx_priv *p_tas25xx, int retry_count); + +static unsigned int tas25xx_codec_read(struct snd_soc_component *codec, + unsigned int reg) +{ + unsigned int value = 0; + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + int ret = -1; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + switch (reg) { + case TAS25XX_SWITCH: + dev_dbg(plat_data->dev, "%s: %d, %d TAS25XX_SWITCH", + __func__, reg, value); + value = p_tas25xx->device_used; + break; + + default: + dev_dbg(plat_data->dev, "%s: %d, %d default read", + __func__, reg, value); + ret = p_tas25xx->read(p_tas25xx, 0, reg, &value); + break; + } + + dev_dbg(plat_data->dev, "%s, reg=%d, value=%d", __func__, reg, value); + + if (ret == 0) + return value; + else + return ret; +} + + +static int tas25xx_codec_write(struct snd_soc_component *codec, + unsigned int reg, unsigned int value) +{ + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + int ret = 0; + + switch (reg) { + case TAS25XX_SWITCH: + dev_dbg(plat_data->dev, "%s: %d, %d TAS25XX_SWITCH", + __func__, reg, value); + p_tas25xx->device_used = value; + break; + + default: + ret = -EINVAL; + dev_dbg(plat_data->dev, "%s: %d, %d UNIMPLEMENTED", + __func__, reg, value); + break; + } + + return ret; +} + + +#if IS_ENABLED(CODEC_PM) +static int tas25xx_codec_suspend(struct snd_soc_component *codec) +{ + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + int ret = -1; + + dev_dbg(plat_data->dev, "%s\n", __func__); + + mutex_lock(&p_tas25xx->codec_lock); + ret = plat_data->runtime_suspend(p_tas25xx); + mutex_unlock(&p_tas25xx->codec_lock); + + return ret; +} + +static int tas25xx_codec_resume(struct snd_soc_component *codec) +{ + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + int ret = 0; + + mutex_lock(&p_tas25xx->codec_lock); + + dev_dbg(plat_data->dev, "%s\n", __func__); + ret = plat_data->runtime_resume(p_tas25xx); + + mutex_unlock(&p_tas25xx->codec_lock); + return ret; +} + +#endif + +static int tas25xx_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + int ret = -1; + + mutex_lock(&p_tas25xx->codec_lock); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + p_tas25xx->dac_power = 1; + dev_info(plat_data->dev, "SND_SOC_DAPM_POST_PMU\n"); + ret = tas25xx_set_power_state(p_tas25xx, TAS_POWER_ACTIVE, 0xffff); + break; + + case SND_SOC_DAPM_PRE_PMD: + p_tas25xx->dac_power = 0; + dev_info(plat_data->dev, "SND_SOC_DAPM_PRE_PMD\n"); + if (p_tas25xx->m_power_state != TAS_POWER_SHUTDOWN) + ret = tas25xx_set_power_state(p_tas25xx, TAS_POWER_SHUTDOWN, 0xffff); + else + ret = 0; + break; + } + mutex_unlock(&p_tas25xx->codec_lock); + + return ret; +} + +static const struct snd_kcontrol_new dapm_switch = + SOC_DAPM_SINGLE("Switch", TAS25XX_SWITCH, 0, 1, 0); + +static const struct snd_soc_dapm_widget tas25xx_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SWITCH("TAS25XX ASI", SND_SOC_NOPM, 0, 0, &dapm_switch), + SND_SOC_DAPM_AIF_OUT("Voltage Sense", "ASI1 Capture", 1, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("Current Sense", "ASI1 Capture", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas25xx_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT"), + SND_SOC_DAPM_SIGGEN("VMON"), + SND_SOC_DAPM_SIGGEN("IMON") +}; + +static const struct snd_soc_dapm_route tas25xx_audio_map[] = { + {"DAC", NULL, "ASI1"}, + {"TAS25XX ASI", "Switch", "DAC"}, + {"OUT", NULL, "TAS25XX ASI"}, + {"Voltage Sense", NULL, "VMON"}, + {"Current Sense", NULL, "IMON"}, +}; + +static bool fw_load_required(struct tas25xx_priv *p_tas25xx) +{ + return (atomic_read(&p_tas25xx->fw_state) == TAS25XX_DSP_FW_LOAD_FAIL); +} + +static int tas25xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int i; + struct snd_soc_component *codec = dai->component; + struct tas25xx_priv *p_tas25xx + = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + int bitwidth = 16; + int ret = -EINVAL; + unsigned int channels = params_channels(params); + + if (fw_load_required(p_tas25xx)) { + dev_warn(plat_data->dev, "%s, firmware is not loaded, retry", __func__); + ret = tas25xx_start_fw_load(p_tas25xx, 3); + if (ret < 0) { + dev_err(plat_data->dev, "%s fw load failed", __func__); + return ret; + } + } + + mutex_lock(&p_tas25xx->codec_lock); + + dev_dbg(plat_data->dev, "%s, stream %s format: %d\n", __func__, + (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) ? ("Playback") : ("Capture"), + params_format(params)); + + if (channels > 2) { + /* assume TDM mode */ + p_tas25xx->mn_fmt_mode = 2; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bitwidth = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bitwidth = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bitwidth = 32; + break; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = tas25xx_set_tdm_rx_slot(p_tas25xx, channels, + bitwidth); + else /*Assumed Capture*/ + ret = tas25xx_set_tdm_tx_slot(p_tas25xx, channels, + bitwidth); + } else { + /* assume I2S mode*/ + p_tas25xx->mn_fmt_mode = 1; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bitwidth = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bitwidth = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bitwidth = 32; + break; + } + + ret = tas25xx_set_bitwidth(p_tas25xx, + bitwidth, substream->stream); + if (ret < 0) { + dev_info(plat_data->dev, "set bitwidth failed, %d\n", + ret); + goto ret; + } + } + + dev_info(plat_data->dev, "%s, stream %s sample rate: %d\n", __func__, + (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) ? ("Playback") : ("Capture"), + params_rate(params)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + for (i = 0; i < p_tas25xx->ch_count; i++) + ret = tas25xx_set_sample_rate(p_tas25xx, i, params_rate(params)); + +ret: + mutex_unlock(&p_tas25xx->codec_lock); + return ret; +} + +static int tas25xx_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *codec = dai->component; + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + int ret = -EINVAL; + + if (fw_load_required(p_tas25xx)) { + dev_warn(plat_data->dev, "%s, firmware is not loaded, retry", __func__); + ret = tas25xx_start_fw_load(p_tas25xx, 3); + if (ret < 0) { + dev_err(plat_data->dev, "%s fw load failed", __func__); + return ret; + } + } + + dev_info(plat_data->dev, "%s, format=0x%x\n", __func__, fmt); + + p_tas25xx->mn_fmt = fmt; + ret = tas25xx_set_dai_fmt_for_fmt(p_tas25xx, fmt); + + return ret; +} + +static int tas25xx_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + int ret = -EINVAL; + struct snd_soc_component *codec = dai->component; + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + if (fw_load_required(p_tas25xx)) { + dev_warn(plat_data->dev, "%s, firmware is not loaded, retry", __func__); + ret = tas25xx_start_fw_load(p_tas25xx, 3); + if (ret < 0) { + dev_err(plat_data->dev, "%s fw load failed", __func__); + return ret; + } + } + + dev_dbg(plat_data->dev, "%s, tx_mask:%d, rx_mask:%d", + __func__, tx_mask, rx_mask); + dev_dbg(plat_data->dev, "%s, slots:%d,slot_width:%d", + __func__, slots, slot_width); + + if (rx_mask) { + p_tas25xx->mn_fmt_mode = 2; /*TDM Mode*/ + ret = tas25xx_set_tdm_rx_slot(p_tas25xx, slots, slot_width); + } else if (tx_mask) { + p_tas25xx->mn_fmt_mode = 2; + ret = tas25xx_set_tdm_tx_slot(p_tas25xx, slots, slot_width); + } else { + dev_err(plat_data->dev, "%s, Invalid Mask", + __func__); + p_tas25xx->mn_fmt_mode = 0; + } + + return ret; +} + +static int tas25xx_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + int ret = 0; + struct snd_soc_component *codec = dai->component; + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + if (fw_load_required(p_tas25xx)) { + dev_warn(plat_data->dev, "%s, firmware is not loaded, retry", __func__); + ret = tas25xx_start_fw_load(p_tas25xx, 3); + if (ret < 0) { + dev_err(plat_data->dev, "%s fw load failed", __func__); + return ret; + } + } + + dev_dbg(plat_data->dev, "%s, stream %s mute %d\n", __func__, + (stream == SNDRV_PCM_STREAM_PLAYBACK) ? ("Playback") : ("Capture"), + mute); + + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + return ret; + + mutex_lock(&p_tas25xx->codec_lock); + if (mute) + ret = tas25xx_set_power_state(p_tas25xx, TAS_POWER_MUTE, + (0xf & tas25xx_get_drv_channel_opmode())); + mutex_unlock(&p_tas25xx->codec_lock); + + if (mute) { +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) + tas25xx_stop_algo_processing(); +#endif /* CONFIG_TAS25XX_ALGO */ + +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) + tas25xx_log_interrupt_stats(p_tas25xx); +#endif /* CONFIG_TAS25XX_IRQ_BD */ + } else { +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) + tas25xx_start_algo_processing(p_tas25xx->curr_mn_iv_width, + p_tas25xx->curr_mn_vbat); +#endif /* CONFIG_TAS25XX_ALGO */ +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) + tas25xx_clear_interrupt_stats(p_tas25xx); +#endif /* CONFIG_TAS25XX_IRQ_BD */ + } + + return ret; +} + +static struct snd_soc_dai_ops tas25xx_dai_ops = { + .hw_params = tas25xx_hw_params, + .set_fmt = tas25xx_set_dai_fmt, + .set_tdm_slot = tas25xx_set_dai_tdm_slot, + .mute_stream = tas25xx_mute_stream, +}; + +#define TAS25XX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver tas25xx_dai_driver[] = { + { + .name = "tas25xx ASI1", + .id = 0, + .playback = { + .stream_name = "ASI1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = TAS25XX_FORMATS, + }, + .capture = { + .stream_name = "ASI1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = TAS25XX_FORMATS, + }, + .ops = &tas25xx_dai_ops, + .symmetric_rate = 1, + }, +}; + +static irqreturn_t tas25xx_irq_handler(int irq, void *dev_id) +{ + struct tas25xx_priv *p_tas25xx = (struct tas25xx_priv *)dev_id; + + if (p_tas25xx != s_tas25xx) + return IRQ_NONE; + + schedule_delayed_work(&p_tas25xx->irq_work, + msecs_to_jiffies(p_tas25xx->intr_data[0].processing_delay)); + return IRQ_HANDLED; +} + +static int tas25xx_setup_irq(struct tas25xx_priv *p_tas25xx) +{ + int i, ret = -EINVAL; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + if (!plat_data) + return ret; + + /* register for interrupts */ + for (i = 0; i < p_tas25xx->ch_count; i++) { + if (gpio_is_valid(p_tas25xx->devs[i]->irq_gpio)) { + ret = gpio_request(p_tas25xx->devs[i]->irq_gpio, + irq_gpio_label[i]); + if (ret) { + dev_err(plat_data->dev, + "%s:%u: ch 0x%02x: GPIO %d request error\n", + __func__, __LINE__, + p_tas25xx->devs[i]->mn_addr, + p_tas25xx->devs[i]->irq_gpio); + continue; + } + gpio_direction_input(p_tas25xx->devs[i]->irq_gpio); + + p_tas25xx->devs[i]->irq_no = + gpio_to_irq(p_tas25xx->devs[i]->irq_gpio); + dev_info(plat_data->dev, "irq = %d\n", + p_tas25xx->devs[i]->irq_no); + + ret = devm_request_threaded_irq(plat_data->dev, + p_tas25xx->devs[i]->irq_no, tas25xx_irq_handler, NULL, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, + "tas25xx", p_tas25xx); + if (ret) { + dev_err(plat_data->dev, "request_irq failed, error=%d\n", ret); + } else { + p_tas25xx->irq_enabled[i] = 1; + dev_info(plat_data->dev, "Interrupt registration successful!!!"); + } + } + } + + return ret; +} + +static int init_dev_with_fw_data(struct tas25xx_priv *p_tas25xx) +{ + int ret, i; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + /* software reset and initial writes */ + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_software_reset(p_tas25xx, i); + if (ret < 0) { + dev_err(plat_data->dev, "I2c fail, %d\n", ret); + goto post_fw_load_work_done; + } + } + + ret = tas_write_init_config_params(p_tas25xx, p_tas25xx->ch_count); + if (ret) { + dev_err(plat_data->dev, "Failed to initialize, error=%d", ret); + goto post_fw_load_work_done; + } + + ret = tas25xx_probe(p_tas25xx); + if (ret) { + dev_err(plat_data->dev, "Failed to initialize, error=%d", ret); + goto post_fw_load_work_done; + } + + ret = tas25xx_setup_irq(p_tas25xx); + if (ret) { + dev_err(plat_data->dev, "failed to initialize irq=%d", ret); + } + +post_fw_load_work_done: + return ret; +} + +static void fw_load_work_routine(struct work_struct *work) +{ + int ret; + struct linux_platform *plat_data = NULL; + struct tas25xx_priv *p_tas25xx = + container_of(work, struct tas25xx_priv, fw_load_work.work); + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + ret = tas25xx_load_firmware(p_tas25xx, p_tas25xx->fw_load_retry_count); + dev_info(plat_data->dev, "%s FW loading %s", __func__, + !ret ? "success" : "fail"); + + if (!ret) { + ret = init_dev_with_fw_data(p_tas25xx); + if (ret) + dev_err(plat_data->dev, + "%s fw dnld to device error=%d", __func__, ret); + else + atomic_set(&p_tas25xx->dev_init_status, 1); + } + + if (ret) + atomic_set(&p_tas25xx->dev_init_status, ret); + + wake_up(&p_tas25xx->dev_init_wait); +} + +int tas25xx_start_fw_load(struct tas25xx_priv *p_tas25xx, int retry_count) +{ + int i, ret, i2c_err, ch_count; + struct linux_platform *plat_data = NULL; + + atomic_set(&p_tas25xx->dev_init_status, 0); + atomic_set(&p_tas25xx->fw_state, TAS25XX_DSP_FW_TRYLOAD); + p_tas25xx->fw_load_retry_count = retry_count; + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + ch_count = p_tas25xx->ch_count; + + tas25xx_check_last_i2c_error_n_reset(); + INIT_DELAYED_WORK(&p_tas25xx->fw_load_work, fw_load_work_routine); + schedule_delayed_work(&p_tas25xx->fw_load_work, msecs_to_jiffies(0)); + + wait_event_interruptible(p_tas25xx->dev_init_wait, + atomic_read(&p_tas25xx->dev_init_status) != 0); + + /* set -ve errno or success 1*/ + ret = atomic_read(&p_tas25xx->dev_init_status); + if (ret == 1) { + for (i = 0; i < ch_count; i++) + p_tas25xx->ti_amp_state[i] = TAS_AMP_STATE_FW_LOAD_SUCCESS; + } else { + i2c_err = tas25xx_check_last_i2c_error_n_reset(); + for (i = 0; i < ch_count; i++) + if (i2c_err) + p_tas25xx->ti_amp_state[i] = TAS_AMP_ERR_I2C; + else + p_tas25xx->ti_amp_state[i] = TAS_AMP_ERR_FW_LOAD; + } + + return ret; +} + +static int tas25xx_codec_probe(struct snd_soc_component *codec) +{ + int ret = -1, i = 0; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(codec); + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + char *w_name[4] = {NULL}; + const char *prefix = codec->name_prefix; + int w_count = 0; + + if (plat_data) + plat_data->codec = codec; + + s_tas25xx = p_tas25xx; + + /*Moved from machine driver to codec*/ + if (prefix) { + w_name[0] = kasprintf(GFP_KERNEL, "%s %s", + prefix, "ASI1 Playback"); + w_name[1] = kasprintf(GFP_KERNEL, "%s %s", + prefix, "ASI1 Capture"); + w_name[2] = kasprintf(GFP_KERNEL, "%s %s", + prefix, "OUT"); + w_count = 3; + } else { + w_name[0] = kasprintf(GFP_KERNEL, "%s", "ASI1 Playback"); + w_name[1] = kasprintf(GFP_KERNEL, "%s", "ASI1 Capture"); + w_name[2] = kasprintf(GFP_KERNEL, "%s", "OUT"); + w_count = 3; + } + + for (i = 0; i < w_count; i++) { + snd_soc_dapm_ignore_suspend(dapm, w_name[i]); + kfree(w_name[i]); + } + + snd_soc_dapm_sync(dapm); + + ret = tas25xx_start_fw_load(p_tas25xx, 800); + if (ret == -ENOENT) + ret = 0; + + dev_info(plat_data->dev, "%s returning ret=%d\n", + __func__, ret); + + return ret; +} + +static void tas25xx_codec_remove(struct snd_soc_component *codec) +{ + struct tas25xx_priv *p_tas25xx = snd_soc_component_get_drvdata(codec); + + tas25xx_remove(p_tas25xx); + s_tas25xx = NULL; +} + +static struct snd_soc_component_driver soc_codec_driver_tas25xx = { + .probe = tas25xx_codec_probe, + .remove = tas25xx_codec_remove, + .read = tas25xx_codec_read, + .write = tas25xx_codec_write, +#if IS_ENABLED(CODEC_PM) + .suspend = tas25xx_codec_suspend, + .resume = tas25xx_codec_resume, +#endif + .dapm_widgets = tas25xx_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas25xx_dapm_widgets), + .dapm_routes = tas25xx_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas25xx_audio_map), +}; + + +int tas25xx_register_codec(struct tas25xx_priv *p_tas25xx) +{ + int ret = -1; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s, enter\n", __func__); + + ret = devm_snd_soc_register_component(plat_data->dev, + &soc_codec_driver_tas25xx, + tas25xx_dai_driver, ARRAY_SIZE(tas25xx_dai_driver)); + + return ret; +} + +int tas25xx_deregister_codec(struct tas25xx_priv *p_tas25xx) +{ + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + snd_soc_unregister_component(plat_data->dev); + + return 0; +} + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TAS25XX ALSA SOC Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-logic.c b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-logic.c new file mode 100644 index 0000000000..b2064c7712 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-logic.c @@ -0,0 +1,1472 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance 4W Smart Amplifier + * + * Copyright (C) 2021 Texas Instruments, Inc. + * + * Author: Niranjan H Y, Vijeth P O + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#include +#include "../inc/tas25xx-logic.h" +#include "../inc/tas25xx-device.h" +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) +#include "../algo/inc/tas_smart_amp_v2.h" +#include "../algo/inc/tas25xx-calib.h" +#if IS_ENABLED(CONFIG_TISA_SYSFS_INTF) +#include "../algo/src/tas25xx-sysfs-debugfs-utils.h" +#endif +#endif /* CONFIG_TAS25XX_ALGO*/ +#include "../inc/tas25xx-regmap.h" +#include "../inc/tas25xx-regbin-parser.h" +#include "../inc/tas25xx-ext.h" + +#ifndef DEFAULT_AMBIENT_TEMP +#define DEFAULT_AMBIENT_TEMP 20 +#endif + +/* 128 Register Map to be used during Register Dump*/ +#define REG_CAP_MAX 128 + +const char *tas_power_states_str[] = { + "TAS_POWER_ACTIVE", + "TAS_POWER_MUTE", + "TAS_POWER_SHUTDOWN", +}; + +static struct tas25xx_reg regs[REG_CAP_MAX] = { + {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, + {5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}, + {10, 0}, {11, 0}, {12, 0}, {13, 0}, {14, 0}, + {15, 0}, {16, 0}, {17, 0}, {18, 0}, {19, 0}, + {20, 0}, {21, 0}, {22, 0}, {23, 0}, {24, 0}, + {25, 0}, {26, 0}, {27, 0}, {28, 0}, {29, 0}, + {30, 0}, {31, 0}, {32, 0}, {33, 0}, {34, 0}, + {35, 0}, {36, 0}, {37, 0}, {38, 0}, {39, 0}, + {40, 0}, {41, 0}, {42, 0}, {43, 0}, {44, 0}, + {45, 0}, {46, 0}, {47, 0}, {48, 0}, {49, 0}, + {50, 0}, {51, 0}, {52, 0}, {53, 0}, {54, 0}, + {55, 0}, {56, 0}, {57, 0}, {58, 0}, {59, 0}, + {60, 0}, {61, 0}, {62, 0}, {63, 0}, {64, 0}, + {65, 0}, {66, 0}, {67, 0}, {68, 0}, {69, 0}, + {70, 0}, {71, 0}, {72, 0}, {73, 0}, {74, 0}, + {75, 0}, {76, 0}, {77, 0}, {78, 0}, {79, 0}, + {80, 0}, {81, 0}, {82, 0}, {83, 0}, {84, 0}, + {85, 0}, {86, 0}, {87, 0}, {88, 0}, {89, 0}, + {90, 0}, {91, 0}, {92, 0}, {93, 0}, {94, 0}, + {95, 0}, {96, 0}, {97, 0}, {98, 0}, {99, 0}, + {100, 0}, {101, 0}, {102, 0}, {103, 0}, {104, 0}, + {105, 0}, {106, 0}, {107, 0}, {108, 0}, {109, 0}, + {110, 0}, {111, 0}, {112, 0}, {113, 0}, {114, 0}, + {115, 0}, {116, 0}, {117, 0}, {118, 0}, {119, 0}, + {120, 0}, {121, 0}, {122, 0}, {123, 0}, {124, 0}, + {125, 0}, {126, 0}, {127, 0}, +}; + +static void (*tas_amp_err_fptr)(int32_t i2c, int32_t err); + +void tas25xx_register_amp_error_callback(void (*amp_err_cb)(int32_t ch, int32_t err)) +{ + tas_amp_err_fptr = amp_err_cb; +} +EXPORT_SYMBOL_GPL(tas25xx_register_amp_error_callback); + +static void tas25xx_post_amp_err_to_platform(int32_t i2c, int32_t err) +{ + if (tas_amp_err_fptr) + tas_amp_err_fptr(i2c, err); +} + +int tas25xx_change_book(struct tas25xx_priv *p_tas25xx, + int32_t chn, int book) +{ + int ret = -EINVAL; + + if (chn >= p_tas25xx->ch_count) + return ret; + + ret = 0; + if (p_tas25xx->devs[chn]->mn_current_book != book) { + ret = p_tas25xx->plat_write( + p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_BOOKCTL_PAGE, 0, chn); + if (ret) { + pr_err("%s, ERROR, L=%d, E=%d, ch=%d\n", + __func__, __LINE__, ret, chn); + } else { + ret = p_tas25xx->plat_write( + p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_BOOKCTL_REG, book, chn); + if (ret) { + pr_err("%s, ERROR, L=%d, E=%d, ch=%d\n", + __func__, __LINE__, ret, chn); + } + } + + if (!ret) + p_tas25xx->devs[chn]->mn_current_book = book; + } + + return ret; +} + + +/* Function to Dump Registers */ +void tas25xx_dump_regs(struct tas25xx_priv *p_tas25xx, int chn) +{ + int i; + struct linux_platform *plat_data; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + for (i = 0; i < REG_CAP_MAX; i++) + p_tas25xx->read(p_tas25xx, chn, regs[i].reg_index, &(regs[i].reg_val)); + + dev_err(plat_data->dev, "--- TAS25XX Channel-%d RegDump ---\n", chn); + for (i = 0; i < REG_CAP_MAX/16; i++) { + dev_err(plat_data->dev, + "%02x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + regs[16 * i].reg_index, regs[16 * i].reg_val, regs[16 * i + 1].reg_val, + regs[16 * i + 2].reg_val, regs[16 * i + 3].reg_val, + regs[16 * i + 4].reg_val, regs[16 * i + 5].reg_val, + regs[16 * i + 6].reg_val, regs[16 * i + 7].reg_val, + regs[16 * i + 8].reg_val, regs[16 * i + 9].reg_val, + regs[16 * i + 10].reg_val, regs[16 * i + 11].reg_val, + regs[16 * i + 12].reg_val, regs[16 * i + 13].reg_val, + regs[16 * i + 14].reg_val, regs[16 * i + 15].reg_val); + } + if (REG_CAP_MAX % 16) { + for (i = 16 * (REG_CAP_MAX / 16); i < REG_CAP_MAX; i++) + dev_err(plat_data->dev, "%02x: %02x\n", regs[i].reg_index, + regs[i].reg_val); + } + dev_err(plat_data->dev, "--- TAS25XX Channel-%d RegDump done ---\n", chn); +} + +/*TODO: Revisit the function as its not usually used*/ +static void tas25xx_hard_reset(struct tas25xx_priv *p_tas25xx) +{ + int i = 0; + + p_tas25xx->hw_reset(p_tas25xx); + + for (i = 0; i < p_tas25xx->ch_count; i++) + p_tas25xx->devs[i]->mn_current_book = -1; + + if (p_tas25xx->mn_err_code) + pr_info("%s: before reset, ErrCode=0x%x\n", __func__, + p_tas25xx->mn_err_code); + p_tas25xx->mn_err_code = 0; +} + +int tas25xx_set_dai_fmt_for_fmt(struct tas25xx_priv *p_tas25xx, unsigned int fmt) +{ + int ret; + int i; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dev_info(plat_data->dev, "SND_SOC_DAIFMT_CBS_CFS\n"); + break; + default: + dev_err(plat_data->dev, "ASI format mask is not found\n"); + ret = -EINVAL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dev_info(plat_data->dev, "INV format: NBNF\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_inv(p_tas25xx, i, FMT_INV_NB_NF); + if (ret) { + dev_err(plat_data->dev, + "Error setting the format FMT_INV_NB_NF for ch=%d\n", i); + break; + } + } + break; + + case SND_SOC_DAIFMT_IB_NF: + dev_info(plat_data->dev, "INV format: IBNF\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_inv(p_tas25xx, i, FMT_INV_IB_NF); + if (ret) { + dev_err(plat_data->dev, + "Error setting the format FMT_INV_IB_NF for ch=%d\n", i); + break; + } + } + break; + + case SND_SOC_DAIFMT_NB_IF: + dev_info(plat_data->dev, "INV format: NBIF\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_inv(p_tas25xx, i, FMT_INV_NB_IF); + if (ret) { + dev_err(plat_data->dev, + "Error setting the format FMT_INV_NB_IF for ch=%d\n", i); + break; + } + } + break; + + case SND_SOC_DAIFMT_IB_IF: + dev_info(plat_data->dev, "INV format: IBIF\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_inv(p_tas25xx, i, FMT_INV_IB_IF); + if (ret) { + dev_err(plat_data->dev, + "Error setting the format FMT_INV_IB_IF for ch=%d\n", i); + break; + } + } + break; + + default: + dev_err(plat_data->dev, "ASI format Inverse is not found\n"); + ret = -EINVAL; + } + + if (ret) + return ret; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case (SND_SOC_DAIFMT_I2S): + dev_info(plat_data->dev, + "SND_SOC_DAIFMT_I2S tdm_rx_start_slot = 1\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_mask(p_tas25xx, i, FMT_MASK_I2S); + if (ret) { + dev_err(plat_data->dev, + "FMT_MASK_I2S set failed for ch=%d\n", i); + break; + } + } + + break; + + case (SND_SOC_DAIFMT_DSP_A): + dev_info(plat_data->dev, + "SND_SOC_DAIFMT_DSP_A tdm_rx_start_slot =1\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_mask(p_tas25xx, i, FMT_MASK_DSP_A); + if (ret) { + dev_err(plat_data->dev, + "FMT_MASK_DSP_A set failed for ch=%d\n", i); + break; + } + } + break; + + case (SND_SOC_DAIFMT_DSP_B): + dev_info(plat_data->dev, + "SND_SOC_DAIFMT_DSP_B tdm_rx_start_slot = 0\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_mask(p_tas25xx, i, FMT_MASK_DSP_B); + if (ret) { + dev_err(plat_data->dev, + "FMT_MASK_DSP_B set failed for ch=%d\n", i); + break; + } + } + break; + + case (SND_SOC_DAIFMT_LEFT_J): + dev_info(plat_data->dev, + "SND_SOC_DAIFMT_LEFT_J tdm_rx_start_slot = 0\n"); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_fmt_mask(p_tas25xx, i, FMT_MASK_LEFT_J); + if (ret) { + dev_err(plat_data->dev, + " set failed for ch=%d\n", i); + break; + } + } + break; + + default: + dev_err(plat_data->dev, "DAI Format is not found, fmt=0x%x\n", fmt); + ret = -EINVAL; + break; + } + + return ret; +} + +/* + * This shall be called during the middle of the playback. + * So all the register settings should be restored back to their original settings. + */ +int tas25xx_reinit(struct tas25xx_priv *p_tas25xx) +{ + int i; + int ret; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + ret = tas_write_init_config_params(p_tas25xx, + p_tas25xx->ch_count); + if (ret) { + dev_err(plat_data->dev, "Failed to initialize, error=%d\n", ret); + return ret; + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_init_params(p_tas25xx, i); + if (ret < 0) { + dev_err(plat_data->dev, "%s error while initialisation for ch=%d\n", + __func__, i); + break; + } + } + + if (ret) + goto reinit_done; + + /* set dai fmt*/ + if (p_tas25xx->mn_fmt) { + ret = tas25xx_set_dai_fmt_for_fmt(p_tas25xx, p_tas25xx->mn_fmt); + if (ret) + goto reinit_done; + } + + /* hw params */ + if (p_tas25xx->mn_fmt_mode == 2) { + /* TDM mode */ + ret = tas25xx_set_tdm_rx_slot(p_tas25xx, + p_tas25xx->ch_count, p_tas25xx->mn_rx_width); + if (ret) { + dev_err(plat_data->dev, "%s failed to set Rx slots\n", __func__); + goto reinit_done; + } + + ret = tas25xx_set_tdm_tx_slot(p_tas25xx, + p_tas25xx->ch_count, p_tas25xx->mn_tx_slot_width); + if (ret) { + dev_err(plat_data->dev, "%s failed to set Tx slots\n", __func__); + goto reinit_done; + } + } else if (p_tas25xx->mn_fmt_mode == 1) { + /* I2S mode */ + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_rx_set_bitwidth(p_tas25xx, p_tas25xx->mn_rx_width, i); + if (ret) { + dev_err(plat_data->dev, + "Error=%d while setting rx bitwidth, ch=%d\n", ret, i); + break; + } + } + if (ret) + goto reinit_done; + + ret = tas25xx_iv_vbat_slot_config(p_tas25xx, p_tas25xx->mn_tx_slot_width); + if (ret) { + dev_err(plat_data->dev, "Error=%d while IV vbat slot config %s\n", + ret, __func__); + goto reinit_done; + } + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_sample_rate(p_tas25xx, i, p_tas25xx->sample_rate); + if (ret) { + dev_err(plat_data->dev, "%s: Error=%d setting sample rate\n", + __func__, ret); + break; + } + } + if (ret) + goto reinit_done; + + ret = tas25xx_update_kcontrol_data(p_tas25xx, KCNTR_ANYTIME, 0xFFFF); + if (ret) + goto reinit_done; + + +reinit_done: + return ret; + +} + +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) +void tas25xx_clear_interrupt_stats(struct tas25xx_priv *p_tas25xx) +{ + int i, j; + struct tas25xx_intr_info *intr_info; + struct tas25xx_interrupts *intr_data; + + for (i = 0; i < p_tas25xx->ch_count; i++) { + intr_data = &p_tas25xx->intr_data[i]; + for (j = 0; j < intr_data->count; j++) { + intr_info = &intr_data->intr_info[j]; + intr_info->count = 0; + } + } +} + +void tas25xx_log_interrupt_stats(struct tas25xx_priv *p_tas25xx) +{ + int i, j; + static u8 log_once; + struct tas25xx_intr_info *intr_info; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + struct tas25xx_interrupts *intr_data; + + if (!log_once) { + dev_info(plat_data->dev, + "irq-bigdata: ||ch\t||name\t||count(total) ||\n"); + log_once = 1; + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + intr_data = &p_tas25xx->intr_data[i]; + for (j = 0; j < intr_data->count; j++) { + intr_info = &intr_data->intr_info[j]; + if (intr_info->count || intr_info->count_persist) + dev_info(plat_data->dev, + "irq-bigdata: |%d |%s |%u(%llu) |\n", i, + intr_info->name, intr_info->count, + intr_info->count_persist); + } + } +} +#endif + +/* + * called with codec lock held + */ +int tas25xx_irq_work_func(struct tas25xx_priv *p_tas25xx) +{ + int8_t clk_intr; + int8_t othr_intr; + int32_t i, j; + int32_t ret = 0; + int32_t type = 0; + int32_t irq_lim_crossed; + int32_t interrupt_count; + int32_t state; + int32_t int_actions, power_on_required; + uint32_t intr_detected = 0; + int32_t reset_done = 0; + struct tas_device *tasdev; + struct linux_platform *plat_data; + struct tas25xx_interrupts *intr_data; + struct tas25xx_intr_info *intr_info; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + p_tas25xx->disable_irq(p_tas25xx); + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas_dev_interrupt_read(p_tas25xx, i, &type); + if (ret) + intr_detected |= (1 << i); + } + p_tas25xx->enable_irq(p_tas25xx); + + if (intr_detected == 0) { + if (is_power_up_state(p_tas25xx->m_power_state)) + for (i = 0; i < p_tas25xx->ch_count; i++) + tas25xx_dump_regs(p_tas25xx, i); + return ret; + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + if ((intr_detected & (1 << i)) == 0) + continue; + + intr_data = &p_tas25xx->intr_data[i]; + interrupt_count = intr_data->count; + + irq_lim_crossed = 0; + + tasdev = p_tas25xx->devs[i]; + +#if IS_ENABLED(CONFIG_TISA_SYSFS_INTF) + tas25xx_algo_bump_oc_count(0, 0); +#endif + ret = tas_dev_interrupt_disable(p_tas25xx, i); + if (tasdev->irq_count != 0) { + if (time_after(jiffies, tasdev->jiffies + + msecs_to_jiffies(TAS25XX_IRQ_DET_TIMEOUT))) { + tasdev->jiffies = jiffies; + tasdev->irq_count = 0; + } else { + tasdev->irq_count++; + if (tasdev->irq_count > TAS25XX_IRQ_DET_CNT_LIMIT) { + dev_err(plat_data->dev, + "INTR: %s: ch=%d continuous interrupt detected %d\n", + __func__, i, tasdev->irq_count); + tas25xx_dump_regs(p_tas25xx, i); + irq_lim_crossed = 1; + } + } + } else { + tasdev->jiffies = jiffies; + tasdev->irq_count = 1; + } + + if (irq_lim_crossed) + continue; + + ret = tas_dev_interrupt_clear(p_tas25xx, i); + if (ret) + dev_warn(plat_data->dev, + "%s Unable to clear interrupt, ch=%d", __func__, i); + + clk_intr = 0; + othr_intr = 0; + power_on_required = 1; + int_actions = 0; + for (j = 0; j < interrupt_count; j++) { + intr_info = &intr_data->intr_info[j]; + if (intr_info->detected) { + dev_err(plat_data->dev, + "ch=%d Interrupt action for the detected interrupt %s is %d", + i, intr_info->name, intr_info->action); + int_actions |= intr_info->action; + if (intr_info->action & TAS_INT_ACTION_POWER_ON) + power_on_required = power_on_required & 1; + else + power_on_required = power_on_required & 0; + + if (intr_info->is_clock_based) + clk_intr = 1; + else + othr_intr = 1; + + if (intr_info->notify_int_val) { + dev_err(plat_data->dev, "ch=%d INTR: %s Notify: %d", + i, intr_info->name, intr_info->notify_int_val); + tas25xx_post_amp_err_to_platform(p_tas25xx->devs[i]->mn_addr, + intr_info->notify_int_val); + } + + /* reset to not detected after detection */ + intr_info->detected = 0; + } + } + + dev_info(plat_data->dev, "ch=%d INTR force power on?(y/n):%s", + i, power_on_required ? "y":"n"); + if (!power_on_required) { + if (othr_intr) + ret = tas25xx_set_power_state(p_tas25xx, TAS_POWER_SHUTDOWN, 1<dev, + "ch=%d INTR power on ignored for [clk=%d oth=%d]", + i, clk_intr, othr_intr); + } + + /* order should be followed */ + if (int_actions & TAS_INT_ACTION_HW_RESET) { + dev_info(plat_data->dev, "ch=%d Interrupt action hard reset", i); + tas25xx_hard_reset(p_tas25xx); + reset_done = 1; + } + + if (int_actions & TAS_INT_ACTION_SW_RESET) { + dev_info(plat_data->dev, "ch=%d Interrupt action software reset", i); + ret = tas25xx_software_reset(p_tas25xx, i); + if (ret) + dev_err(plat_data->dev, + "ch=%d Software reset failed error=%d", i, ret); + reset_done = 1; + } + + if (reset_done) + ret = tas25xx_reinit(p_tas25xx); + + if (int_actions & TAS_INT_ACTION_POWER_ON) { + tas25xx_check_if_powered_on(p_tas25xx, &state, i); + if (state == 0) { + /* interrupts are enabled during power up sequence */ + dev_info(plat_data->dev, + "ch=%d Try powering on the device", i); + ret = tas25xx_set_power_state(p_tas25xx, + TAS_POWER_ACTIVE, (1<dev, + "ch=%d Already powered on, Enable the interrupt", i); + ret = tas_dev_interrupt_enable(p_tas25xx, i); + } + } + } + + return ret; +} + +int tas25xx_init_work_func(struct tas25xx_priv *p_tas25xx, struct tas_device *dev_tas25xx) +{ + int chn = 0; + int ret = 0; + int detected = 0; + int type = 0; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + chn = dev_tas25xx->channel_no; + + dev_info(plat_data->dev, "ch=%d %s\n", chn, __func__); + ret = tas25xx_set_post_powerup(p_tas25xx, chn); + if (ret) + dev_err(plat_data->dev, + "ch=%d Error in post powerup data write. err=%d\n", + chn, ret); + + /* check for interrupts during power up */ + detected = tas_dev_interrupt_read(p_tas25xx, chn, &type); + if (detected) { + if (type == INTERRUPT_TYPE_CLOCK_BASED) { + /* ignore clock based interrupts which we are monitoring */ + dev_warn(plat_data->dev, + "Ignoring clock based interrupts and clear latch"); + ret = tas_dev_interrupt_clear(p_tas25xx, chn); + if (ret) + dev_err(plat_data->dev, + "ch=%d Error while clearing interrup err=%d\n", + chn, ret); + } else { + dev_err(plat_data->dev, + "Non clock based interrupts detected, skip latch clear for recovery"); + } + } + + /* enabling the interrupt here to avoid any clock errors during the bootup*/ + ret = tas_dev_interrupt_enable(p_tas25xx, chn); + if (ret) + dev_err(plat_data->dev, + "ch=%d %s: Failed to enable interrupt\n", chn, __func__); + + return ret; +} + +int tas25xx_dc_work_func(struct tas25xx_priv *p_tas25xx, int chn) +{ + pr_info("%s: ch %d\n", __func__, chn); + //tas25xx_reload(p_tas25xx, chn); + + return 0; +} + +int tas25xx_register_device(struct tas25xx_priv *p_tas25xx) +{ + int i = 0; + + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s:\n", __func__); + + tas25xx_hard_reset(p_tas25xx); + + for (i = 0; i < p_tas25xx->ch_count; i++) + p_tas25xx->devs[i]->channel_no = i; + + return 0; +} + +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) +static ssize_t irq_bd_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int8_t found = 0; + + int32_t i, j; + struct tas25xx_interrupts *intr_data; + struct tas25xx_intr_info *intr_info; + struct tas25xx_priv *p_tas25xx = dev_get_drvdata(dev); + + if (!p_tas25xx) { + dev_info(dev, "dev_get_drvdata returned NULL"); + return -EINVAL; + } + + intr_info = NULL; + for (i = 0; i < p_tas25xx->ch_count; i++) { + intr_data = &p_tas25xx->intr_data[i]; + for (j = 0; j < intr_data->count; j++) { + intr_info = &intr_data->intr_info[j]; + if (attr == intr_info->dev_attr) { + found = 1; + break; + } + } + } + + if (found) + return snprintf(buf, 32, "count=%u\npersist=%llu\n", + intr_info->count, intr_info->count_persist); + else + return snprintf(buf, 32, "something went wrong!!!"); + +} + +static struct attribute_group s_attribute_group = { + .attrs = NULL, +}; + +int tas_smartamp_add_irq_bd(struct tas25xx_priv *p_tas25xx) +{ + int i, j, k; + int ret; + int total_irqs = 0; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + struct device_attribute *p_irqpd; + struct attribute **attribute_array; + struct tas25xx_intr_info *intr_info; + struct tas25xx_interrupts *intr_data; + struct device *irq_dev; + + for (i = 0; i < p_tas25xx->ch_count; i++) { + intr_data = &p_tas25xx->intr_data[i]; + total_irqs += intr_data->count; + } + + p_irqpd = (struct device_attribute *)kzalloc(total_irqs * + sizeof(struct device_attribute), GFP_KERNEL); + if (!p_irqpd) + return -ENOMEM; + + total_irqs++; + attribute_array = kzalloc((sizeof(struct attribute *) * total_irqs), GFP_KERNEL); + + k = 0; + for (i = 0; i < p_tas25xx->ch_count; i++) { + intr_data = &p_tas25xx->intr_data[i]; + for (j = 0; j < intr_data->count; j++) { + intr_info = &intr_data->intr_info[j]; + p_irqpd[k].attr.name = intr_info->name; + p_irqpd[k].attr.mode = 0664; + p_irqpd[k].show = irq_bd_show; + p_irqpd[k].store = NULL; + + intr_info->dev_attr = &p_irqpd[k]; + attribute_array[k] = &p_irqpd[k].attr; + k++; + } + } + s_attribute_group.attrs = attribute_array; + + irq_dev = device_create(p_tas25xx->class, + NULL, 1, NULL, "irqs"); + if (IS_ERR(irq_dev)) { + dev_err(plat_data->dev, + "%sFailed to create irqs\n", __func__); + ret = PTR_ERR(irq_dev); + irq_dev = NULL; + goto err_irqbd; + } + + ret = sysfs_create_group(&irq_dev->kobj, + &s_attribute_group); + if (ret) { + dev_err(plat_data->dev, + "%sFailed to create sysfs group\n", __func__); + goto err_irqbd; + } + + p_tas25xx->irqdata.p_dev_attr = p_irqpd; + p_tas25xx->irqdata.p_attr_arr = attribute_array; + p_tas25xx->irqdata.irq_dev = irq_dev; + + dev_set_drvdata(irq_dev, p_tas25xx); + + dev_info(plat_data->dev, "%s ret=%d\n", __func__, ret); + + return ret; + +err_irqbd: + kfree(p_irqpd); + kfree(attribute_array); + return ret; +} + +static void tas_smartamp_remove_irq_bd(struct tas25xx_priv *p_tas25xx) +{ + struct irq_bigdata *bd = &p_tas25xx->irqdata; + + if (bd->irq_dev) + sysfs_remove_group(&bd->irq_dev->kobj, + &s_attribute_group); + + kfree(bd->p_dev_attr); + kfree(bd->p_attr_arr); + + memset(bd, 0, sizeof(struct irq_bigdata)); +} +#endif + +enum cmd_type_t { + CALIB, + TEMP, + IV_VBAT, + DRV_OPMODE, +}; + +static const char *cmd_str_arr[MAX_CMD_LIST] = { + "calib", + "temp", + "iv_vbat", + "drv_opmode", + NULL, +}; + +static struct device_attribute *cmd_arr[MAX_CMD_LIST] = { + NULL, +}; + +static uint8_t tas25xx_get_amb_temp(void) +{ + struct power_supply *psy; + union power_supply_propval value = {0}; + + psy = power_supply_get_by_name("battery"); + if (!psy || !psy->desc || !psy->desc->get_property) { + pr_err("[TI-SmartPA:%s] getting ambient temp failed, using default value %d\n", + __func__, DEFAULT_AMBIENT_TEMP); + return DEFAULT_AMBIENT_TEMP; + } + psy->desc->get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + + return DIV_ROUND_CLOSEST(value.intval, 10); +} + +static ssize_t cmd_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int32_t i, cmd_count, found = 0, ret = -EINVAL, temp; + + struct tas25xx_priv *p_tas25xx = dev_get_drvdata(dev); + + if (!p_tas25xx) { + dev_info(dev, + "%s get_drvdata returned NULL", __func__); + return ret; + } + + cmd_count = ARRAY_SIZE(cmd_arr); + + for (i = 0; i < cmd_count; i++) { + if (attr == cmd_arr[i]) { + found = 1; + break; + } + } + + if (found) { + switch (i) { + case TEMP: + temp = tas25xx_get_amb_temp(); + ret = snprintf(buf, 32, "%d", temp); + break; + + case IV_VBAT: + temp = ((p_tas25xx->curr_mn_iv_width & 0xFFFF) | + ((p_tas25xx->curr_mn_vbat & 0xFFFF) << 16)); + ret = snprintf(buf, 32, "0x%x", temp); + break; + + case DRV_OPMODE: + temp = tas25xx_get_drv_channel_opmode(); + ret = snprintf(buf, 32, "0x%x", temp); + break; + + default: + ret = snprintf(buf, 32, "unsupported cmd %d", i); + break; + } + } + + return ret; +} + +static ssize_t cmd_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int32_t i, cmd_count, found = 0, ret = -EINVAL; + struct tas25xx_priv *p_tas25xx = dev_get_drvdata(dev); + + if (!p_tas25xx) { + dev_info(dev, "%s drv_data is null", __func__); + return ret; + } + + cmd_count = ARRAY_SIZE(cmd_arr); + + for (i = 0; i < cmd_count; i++) { + if (attr == cmd_arr[i]) { + found = 1; + break; + } + } + + if (found) { + if (i == CALIB) { + if (!strncmp(buf, "cal_init_blk", strlen("cal_init_blk"))) + tas25xx_prep_dev_for_calib(1); + else if (!strncmp(buf, "cal_deinit_blk", strlen("cal_deinit_blk"))) + tas25xx_prep_dev_for_calib(0); + else + dev_info(dev, + "%s Not supported %s, for calib", __func__, buf); + } else { + dev_info(dev, "%s Not supported %s", __func__, buf); + } + } else { + dev_info(dev, "%s Not supported %s", __func__, buf); + } + + return size; +} + +static struct attribute_group cmd_attribute_group = { + .attrs = NULL, +}; + +int tas_smartamp_add_cmd_intf(struct tas25xx_priv *p_tas25xx) +{ + int i, cmd_count, ret; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + struct device_attribute *p_attr_arr; + struct attribute **attr_arry; + struct device *cmd_dev; + + cmd_count = ARRAY_SIZE(cmd_arr); + + p_attr_arr = (struct device_attribute *)kzalloc(cmd_count * + sizeof(struct device_attribute), GFP_KERNEL); + if (!p_attr_arr) { + attr_arry = NULL; + ret = -ENOMEM; + goto err_cmd; + } + + attr_arry = kzalloc((sizeof(struct attribute *) * + cmd_count), GFP_KERNEL); + if (!attr_arry) { + ret = -ENOMEM; + goto err_cmd; + } + + for (i = 0; (i < cmd_count) && cmd_str_arr[i]; i++) { + p_attr_arr[i].attr.name = cmd_str_arr[i]; + p_attr_arr[i].attr.mode = 0664; + p_attr_arr[i].show = cmd_show; + p_attr_arr[i].store = cmd_store; + + cmd_arr[i] = &p_attr_arr[i]; + attr_arry[i] = &p_attr_arr[i].attr; + } + cmd_attribute_group.attrs = attr_arry; + + cmd_dev = device_create(p_tas25xx->class, + NULL, 1, NULL, "cmd"); + if (IS_ERR(cmd_dev)) { + dev_err(plat_data->dev, + "%sFailed to create cmds\n", __func__); + ret = PTR_ERR(cmd_dev); + goto err_cmd; + } + + ret = sysfs_create_group(&cmd_dev->kobj, + &cmd_attribute_group); + if (ret) { + dev_err(plat_data->dev, + "%s Failed to create sysfs group\n", __func__); + goto err_cmd; + } + + p_tas25xx->cmd_data.p_dev_attr = p_attr_arr; + p_tas25xx->cmd_data.p_attr_arr = attr_arry; + p_tas25xx->cmd_data.cmd_dev = cmd_dev; + + dev_set_drvdata(cmd_dev, p_tas25xx); + + dev_info(plat_data->dev, "%s ret=%d\n", __func__, ret); + + return ret; + +err_cmd: + kfree(p_attr_arr); + kfree(attr_arry); + return ret; +} + +static void tas_smartamp_remove_cmd_intf(struct tas25xx_priv *p_tas25xx) +{ + struct cmd_data *cmd_data = &p_tas25xx->cmd_data; + + if (cmd_data->cmd_dev) { + sysfs_remove_group(&cmd_data->cmd_dev->kobj, + &cmd_attribute_group); + } + + kfree(cmd_data->p_dev_attr); + kfree(cmd_data->p_attr_arr); + + memset(cmd_data, 0, sizeof(struct cmd_data)); +} + +int tas_smartamp_add_sysfs(struct tas25xx_priv *p_tas25xx) +{ + int ret = 0; + struct class *class = NULL; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + class = class_create(THIS_MODULE, "tas25xx_dev"); + if (IS_ERR(class)) { + ret = PTR_ERR(class); + dev_err(plat_data->dev, + "%s err class create %d\n", __func__, ret); + class = NULL; + } + + if (class) { + p_tas25xx->class = class; +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) + ret = tas_smartamp_add_irq_bd(p_tas25xx); + if (ret) + dev_err(plat_data->dev, + "%s err registring irqbd %d\n", __func__, ret); +#endif + tas_smartamp_add_cmd_intf(p_tas25xx); + } + + return ret; +} + +void tas_smartamp_remove_sysfs(struct tas25xx_priv *p_tas25xx) +{ +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; +#endif + + tas_smartamp_remove_cmd_intf(p_tas25xx); +#if IS_ENABLED(CONFIG_TAS25XX_IRQ_BD) + tas_smartamp_remove_irq_bd(p_tas25xx); + dev_info(plat_data->dev, + "%s de-registring irqbd done\n", __func__); +#endif + + if (p_tas25xx->class) { + device_destroy(p_tas25xx->class, 1); + class_destroy(p_tas25xx->class); + p_tas25xx->class = NULL; + } +} + +int tas25xx_probe(struct tas25xx_priv *p_tas25xx) +{ + int ret = -1, i = 0; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_set_init_params(p_tas25xx, i); + if (ret < 0) { + dev_err(plat_data->dev, "%s err=%d, initialisation", + __func__, ret); + goto end; + } + } + + ret = tas25xx_create_kcontrols(p_tas25xx); + if (ret) { + dev_warn(plat_data->dev, "%s err=%d creating controls", + __func__, ret); + ret = 0; + } + +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) + tas_smartamp_add_algo_controls(plat_data->codec, plat_data->dev, + p_tas25xx->ch_count); +#endif + tas_smartamp_add_sysfs(p_tas25xx); + +end: + return ret; +} + +void tas25xx_remove(struct tas25xx_priv *p_tas25xx) +{ +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + tas_smartamp_remove_algo_controls(plat_data->codec); +#endif + /* REGBIN related */ + tas25xx_remove_binfile(p_tas25xx); + tas_smartamp_remove_sysfs(p_tas25xx); +} + +int tas25xx_set_power_state(struct tas25xx_priv *p_tas25xx, + enum tas_power_states_t state, uint32_t ch_bitmask) +{ + int ret = 0, i = 0; + enum tas_power_states_t cur_state; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s: state %s\n", __func__, + (state <= TAS_POWER_SHUTDOWN) ? tas_power_states_str[state] : "INVALID"); + + cur_state = p_tas25xx->m_power_state; + p_tas25xx->m_power_state = state; + + /* supports max 4 channels */ + ch_bitmask &= tas25xx_get_drv_channel_opmode() & 0xF; + + switch (state) { + case TAS_POWER_ACTIVE: + for (i = 0; i < p_tas25xx->ch_count; i++) { + if ((ch_bitmask & (1<dev, + "ch=%d %s: clearing interrupts \n", i, __func__); + + ret = tas_dev_interrupt_clear(p_tas25xx, i); + if (ret) { + dev_err(plat_data->dev, + "ch=%d %s: Error clearing interrupt\n", i, __func__); + ret = 0; + } + } else { + dev_dbg(plat_data->dev, + "ch=%d %s: skipping clearing interrupts \n", i, __func__); + } + + ret = tas25xx_set_pre_powerup(p_tas25xx, i); + if (ret) { + dev_err(plat_data->dev, "ch=%d %s setting power state failed, err=%d\n", + i, __func__, ret); + } else { + ret = tas25xx_update_kcontrol_data(p_tas25xx, KCNTR_PRE_POWERUP, (1 << i)); + p_tas25xx->schedule_init_work(p_tas25xx, i); + } + } + break; + + case TAS_POWER_MUTE: + for (i = 0; i < p_tas25xx->ch_count; i++) { + if ((ch_bitmask & (1<ch_count; i++) { + if ((ch_bitmask & (1<cancel_init_work(p_tas25xx, i); + ret = tas25xx_set_pre_powerdown(p_tas25xx, i); + ret |= tas25xx_set_post_powerdown(p_tas25xx, i); + } + break; + + default: + ret = -EINVAL; + dev_err(plat_data->dev, "wrong power state setting %d\n", + state); + } + + return ret; +} + +int find_fmt_match(struct tas25xx_priv *p_tas25xx, char *fmt, uint8_t **in_out_buf) +{ + int ret, i; + uint32_t size, blk_count, sublk_sz, fmt_len; + uint8_t *buf; + struct linux_platform *plat_data; + + ret = -EINVAL; + buf = *in_out_buf; + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + size = *((uint32_t *)buf); + buf += sizeof(uint32_t); + blk_count = *((uint32_t *)buf); + buf += sizeof(uint32_t); + + if (!fmt) { + *in_out_buf = NULL; + return ret; + } + + fmt_len = strlen(fmt); + for (i = 0; i < blk_count; i++) { + if (memcmp(fmt, buf, fmt_len) == 0) { + /* block found */ + buf += fmt_len; + break; + } else { + /* move to next block */ + buf += fmt_len; + sublk_sz = *((uint32_t *)buf); + buf += sizeof(uint32_t) + sublk_sz; + } + } + + if (i < blk_count) { + *in_out_buf = buf; + ret = i; + } else { + *in_out_buf = NULL; + } + + return ret; +} + +int get_next_possible_iv_width(int current_iv_width) +{ + int ret; + + switch (current_iv_width) { + case 16: + ret = 12; + break; + + case 12: + ret = 8; + break; + + case 8: + default: + ret = -1; + break; + } + + return ret; +} + +int tas25xx_iv_vbat_slot_config(struct tas25xx_priv *p_tas25xx, + int mn_slot_width) +{ + int i; + int ret = -EINVAL; + int iv_width, prev_iv_width, vbat_on; + int near_match_found = 0; + char tx_fmt[32]; + struct linux_platform *plat_data; + + /* treating 24bit and 32bit as same */ + if (mn_slot_width == 32) + mn_slot_width = 24; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + scnprintf(tx_fmt, 15, "%2d_%s_%02d_%02d_%1d", mn_slot_width, + p_tas25xx->mn_fmt_mode == 2 ? "TDM" : "I2S", p_tas25xx->ch_count, + p_tas25xx->mn_iv_width, p_tas25xx->mn_vbat); + + dev_info(plat_data->dev, "finding exact match for %s", tx_fmt); + + for (i = 0; i < p_tas25xx->ch_count; i++) { + uint8_t *data = p_tas25xx->block_op_data[i].tx_fmt_data; + + iv_width = p_tas25xx->mn_iv_width; + vbat_on = p_tas25xx->mn_vbat; + + /* Try to find exact match */ + if ((i > 0) && (near_match_found)) { + dev_info(plat_data->dev, "same format is being used %s, ch=%d", tx_fmt, i); + } else { + scnprintf(tx_fmt, 15, "%2d_%s_%02d_%02d_%1d", mn_slot_width, + p_tas25xx->mn_fmt_mode == 2 ? "TDM" : "I2S", p_tas25xx->ch_count, + p_tas25xx->mn_iv_width, p_tas25xx->mn_vbat); + } + + if (find_fmt_match(p_tas25xx, tx_fmt, &data) >= 0) { + /* match found */ + dev_info(plat_data->dev, "exact match found for %s, ch=%d", tx_fmt, i); + } else { + /* try near possible option */ + dev_err(plat_data->dev, "exact match was not found for %s, trying to find near match", tx_fmt); + /*1. Try with reduced bit width - 16 -> 12 -> 8 */ + while (1) { + prev_iv_width = iv_width; + iv_width = get_next_possible_iv_width(prev_iv_width); + if (iv_width < 0) { + if (vbat_on) { + /*2. reset iv width and check with vbat off */ + iv_width = p_tas25xx->mn_iv_width; + vbat_on = 0; + } else { + data = NULL; + break; + } + } + scnprintf(tx_fmt, 15, "%2d_%s_%02d_%02d_%1d", mn_slot_width, + p_tas25xx->mn_fmt_mode == 2 ? "TDM" : "I2S", p_tas25xx->ch_count, + iv_width, vbat_on); + dev_info(plat_data->dev, "finding near match with %s", tx_fmt); + /* reset data for fresh search with new string */ + data = p_tas25xx->block_op_data[i].tx_fmt_data; + if (find_fmt_match(p_tas25xx, tx_fmt, &data) >= 0) { + dev_info(plat_data->dev, "near match found: %s for ch=%d", tx_fmt, i); + near_match_found |= (1 << i); + break; + } + } /* */ + } /* if-else */ + + if (data) { + dev_info(plat_data->dev, "possible/exact match found %s for iv=%d, vbat=%d", + tx_fmt, p_tas25xx->mn_iv_width, p_tas25xx->mn_vbat); + ret = tas25xx_process_block(p_tas25xx, (char *)data, i); + /* error setting iv width */ + if (ret) { + dev_info(plat_data->dev, "process block error for %s for iv=%d, vbat=%d", + tx_fmt, p_tas25xx->mn_iv_width, p_tas25xx->mn_vbat); + } + } else { + dev_err(plat_data->dev, "no near match found iv=%d, vbat=%d", + p_tas25xx->mn_iv_width, p_tas25xx->mn_vbat); + ret = -EINVAL; + break; + } + } /* */ + + if (ret == 0) { + p_tas25xx->curr_mn_iv_width = iv_width; + p_tas25xx->curr_mn_vbat = vbat_on; + p_tas25xx->mn_tx_slot_width = mn_slot_width; + } + + return ret; +} + +/* tas25xx_set_bitwidth function is redesigned to accomodate change in + * tas25xx_iv_vbat_slot_config() + */ +int tas25xx_set_bitwidth(struct tas25xx_priv *p_tas25xx, + int bitwidth, int stream) +{ + int i; + int ret = -EINVAL; + struct linux_platform *plat_data; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s: bitwidth %d stream %d\n", __func__, bitwidth, stream); + + if (stream == TAS25XX_STREAM_PLAYBACK) { + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_rx_set_bitwidth(p_tas25xx, bitwidth, i); + if (ret) { + dev_err(plat_data->dev, "Error =%d while setting bitwidth, ch=%d,", + ret, i); + } + } + /*stream == TAS25XX_STREAM_CAPTURE*/ + } else { + ret = tas25xx_iv_vbat_slot_config(p_tas25xx, bitwidth); + if (ret) + dev_err(plat_data->dev, "Error =%d with %s", ret, __func__); + } + + return ret; +} + +/* tas25xx_set_tdm_rx_slot function is redesigned to accomodate change in + * tas25xx_iv_vbat_slot_config() + */ +int tas25xx_set_tdm_rx_slot(struct tas25xx_priv *p_tas25xx, + int slots, int slot_width) +{ + int i; + int ret = -1; + struct linux_platform *plat_data; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s: slots=%d, slot_width=%d", + __func__, slots, slot_width); + + if (((p_tas25xx->ch_count == 1) && (slots < 1)) || + ((p_tas25xx->ch_count == 2) && (slots < 2))) { + dev_err(plat_data->dev, "Invalid Slots %d\n", slots); + return ret; + } + p_tas25xx->mn_slots = slots; + + if ((slot_width != 16) && + (slot_width != 24) && + (slot_width != 32)) { + dev_err(plat_data->dev, "Unsupported slot width %d\n", slot_width); + return ret; + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + ret = tas25xx_rx_set_bitwidth(p_tas25xx, slot_width, i); + if (ret) { + dev_err(plat_data->dev, "Error =%d while setting bitwidth, ch=%d,", + ret, i); + } + } + + return ret; +} + +/* tas25xx_set_tdm_tx_slot function is redesigned to accomodate change in + * tas25xx_iv_vbat_slot_config() + */ +int tas25xx_set_tdm_tx_slot(struct tas25xx_priv *p_tas25xx, + int slots, int slot_width) +{ + int ret = -1; + + if ((slot_width != 16) && + (slot_width != 24) && + (slot_width != 32)) { + pr_err("Unsupported slot width %d\n", slot_width); + return ret; + } + + if (((p_tas25xx->ch_count == 1) && (slots < 2)) || + ((p_tas25xx->ch_count == 2) && (slots < 4))) { + pr_err("Invalid Slots %d\n", slots); + return ret; + } + p_tas25xx->mn_slots = slots; + + ret = tas25xx_iv_vbat_slot_config(p_tas25xx, slot_width); + + return ret; +} diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-misc.c b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-misc.c new file mode 100644 index 0000000000..852f7322e3 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-misc.c @@ -0,0 +1,497 @@ +/* + * ============================================================================= + * Copyright (c) 2016 Texas Instruments Inc. + * + * 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; version 2. + * + * 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. + * File: + * tas25xx-misc.c + * + * Description: + * misc driver for Texas Instruments + * TAS25XX High Performance 4W Smart Amplifier + * + * ============================================================================= + */ + +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../inc/tas25xx.h" +#include "../inc/tas25xx-misc.h" +#include + +#define FMT "%02x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n" +#define MAX_FMT_COUNT 512 + +enum tas_devop_t { + OP_REG_READ = 0, + OP_PAGE_READ = 1, + OP_SNG_WRITE = 2, + OP_BURST_WRITE = 3, +}; + +struct tas_audio_dev { + uint8_t channel; + uint8_t book; + uint8_t page; + uint8_t reg; + uint8_t read_pending; + enum tas_devop_t op; +}; + + +static struct tas_audio_dev s_tasdevop; +static uint32_t s_r[128]; +static struct tas25xx_priv *g_tas25xx; + +static int32_t tas25xx_file_open(struct inode *inode, struct file *file) +{ + struct tas25xx_priv *p_tas25xx = g_tas25xx; + + file->private_data = (void *)p_tas25xx; + + pr_info("TAS25XX %s\n", __func__); + return 0; +} + +static int32_t tas25xx_file_release(struct inode *inode, struct file *file) +{ + pr_info("TAS25XX %s\n", __func__); + + file->private_data = (void *)NULL; + + return 0; +} + +static ssize_t tas25xx_file_read(struct file *file, + char *buf, size_t count, loff_t *ppos) +{ + struct tas25xx_priv *p_tas25xx = + (struct tas25xx_priv *)file->private_data; + int32_t ret = 0, i, count_l; + uint8_t *p_kbuf = NULL; + uint32_t reg = 0; + uint32_t len = 0; + uint32_t value = 0; + uint32_t channel = 0; + + mutex_lock(&p_tas25xx->file_lock); + + pr_info("%s size=%zu", __func__, count); + + if (count > 8) { + uint8_t *ref_ptr; + if (!s_tasdevop.read_pending) { + count = 0; + goto done_read; + } + + p_kbuf = kzalloc(MAX_FMT_COUNT, GFP_KERNEL); + if (p_kbuf == NULL) + goto done_read; + + ref_ptr = p_kbuf; + + channel = s_tasdevop.channel; + pr_info("%s ch=%d B:P %02x:%02x\n", + __func__, channel, s_tasdevop.book, s_tasdevop.page); + + switch (s_tasdevop.op) { + case OP_REG_READ: + reg = TAS25XX_REG(s_tasdevop.book, s_tasdevop.page, s_tasdevop.reg); + ret = p_tas25xx->read(p_tas25xx, channel, + reg, &value); + if (ret < 0) + count = snprintf(ref_ptr, 64, "%s\n", "Error"); + else + count = snprintf(ref_ptr, 64, "%02x\n", value); + + ret = copy_to_user(buf, ref_ptr, count); + if (ret) { + pr_err("TAS25XX:%d reg read, copy to user buf ret= %d", __LINE__, ret); + count = ret; + } else { + pr_info("TAS25XX: %s ch=%d B:P:R %02x:%02x:%02x(%d) value=%d\n", + __func__, channel, s_tasdevop.book, s_tasdevop.page, + s_tasdevop.reg, reg, value); + } + break; + + case OP_PAGE_READ: + reg = TAS25XX_REG(s_tasdevop.book, s_tasdevop.page, 0); + for (i = 0; i < 128; i++) { + ret = p_tas25xx->read(p_tas25xx, channel, + reg + i, &s_r[i]); + if (ret) { + memset(s_r, 0, sizeof(s_r)); + break; + } + } + p_tas25xx->read(p_tas25xx, channel, reg, &s_r[0]); + + if (ret) { + count = snprintf(ref_ptr, 64, "error=%d\n", ret); + } else { + count_l = 0; + if (count < ((52*8)+1)) { + count = snprintf(ref_ptr, 64, + "page dump not possible\n"); + } else { + for (i = 0; i < 8; i++) { + count_l += snprintf(ref_ptr, 64, FMT, i, s_r[(i*16) + 0], + s_r[(i*16) + 1], s_r[(i*16) + 2], s_r[(i*16) + 3], s_r[(i*16) + 4], + s_r[(i*16) + 5], s_r[(i*16) + 6], s_r[(i*16) + 7], s_r[(i*16) + 8], + s_r[(i*16) + 9], s_r[(i*16) + 10], s_r[(i*16) + 11], s_r[(i*16) + 12], + s_r[(i*16) + 13], s_r[(i*16) + 14], s_r[(i*16) + 15]); + ref_ptr += 52; + } + count = count_l; + } + } + + ret = copy_to_user(buf, p_kbuf, count); + if (ret) { + pr_err("TAS25XX:%s page read, copy buffer failed.\n", __func__); + count = ret; + } + break; + + default: + count = snprintf(p_kbuf, 64, "%s\n", "invalid op"); + ret = copy_to_user(buf, p_kbuf, count); + if (ret) { + pr_err("TAS25XX:%s invalid op, copy buffer failed.\n", __func__); + count = ret; + } + break; + } + + s_tasdevop.read_pending = 0; + goto done_read; + } else if (count == 7) { + + p_kbuf = kzalloc(count, GFP_KERNEL); + if (p_kbuf == NULL) + goto done_read; + + ret = copy_from_user(p_kbuf, buf, count); + if (ret != 0) { + pr_err("TAS25XX copy_from_user failed.\n"); + count = ret; + goto done_read; + } + + if (p_kbuf[1] >= p_tas25xx->ch_count) + goto done_read; + + channel = p_kbuf[1]; + + switch (p_kbuf[0]) { + case TIAUDIO_CMD_REG_READ: + { + reg = ((uint32_t)p_kbuf[2] << 24) + + ((uint32_t)p_kbuf[3] << 16) + + ((uint32_t)p_kbuf[4] << 8) + + (uint32_t)p_kbuf[5]; + + pr_info("TAS25XX TIAUDIO_CMD_REG_READ: current_reg = 0x%x, count=%d\n", + reg, (int)count-6); + len = count-6; + if (len == 1) { + value = 0; + + ret = p_tas25xx->read(p_tas25xx, channel, + reg, &value); + if (ret < 0) { + pr_err("TAS25XX dev read fail %d\n", ret); + break; + } + p_kbuf[6] = value; + ret = copy_to_user(buf, p_kbuf, count); + if (ret) { + count = ret; + pr_err("TAS25XX TIAUDIO_CMD_REG_READ copy to user %d\n", ret); + } + } else if (len > 1) { + ret = p_tas25xx->bulk_read(p_tas25xx, channel, + reg, (uint8_t *)&p_kbuf[6], len); + if (ret < 0) { + pr_err("TAS25XX dev bulk read fail %d\n", ret); + } else { + ret = copy_to_user(buf, p_kbuf, count); + if (ret) { + count = ret; + pr_err("TAS25XX TIAUDIO_CMD_REG_READ copy to user fail %d\n", + ret); + } + } + } + } + break; + } + } else { + pr_err("TAS25XX:%d invalid size %zu", __LINE__, count); + goto done_read; + } + +done_read: + kfree(p_kbuf); + mutex_unlock(&p_tas25xx->file_lock); + return count; +} + +static int32_t handle_read_write(struct tas25xx_priv *p_tas25xx, + int32_t read_write_op, int32_t count, uint8_t *buf) +{ + static uint8_t ch_bpr[8] = {0}; + int32_t val, buf_sz, i; + int32_t ret = 0; + int32_t reg; + int8_t l_buf[3]; + + buf_sz = count - 2; + buf += 2; + + i = 0; + l_buf[2] = 0; + while (buf_sz >= 2) { + memcpy(l_buf, buf, 2); + ret = kstrtoint(l_buf, 16, &val); + if (ret) { + pr_err("TAS25XX:%d Parsing err for %s, err=%d", __LINE__, l_buf, ret); + break; + } + if (i <= 7) { + pr_info("tas25xx: %s i=%d, val=%d\n", __func__, i, val); + ch_bpr[i] = (uint8_t)val; + } else { + pr_info("tas25xx: write supported only for 4 bytes,ignoring additional bytes\n"); + break; + } + buf += 3; + buf_sz -= 3; + i++; + } + + if (ret) + goto read_write_done; + + pr_info("tas25xx: ch=%d, BPR %02x:%02x:%02x v=%d %d %d %d(cnt=%d)\n", + ch_bpr[0], ch_bpr[1], ch_bpr[2], ch_bpr[3], + ch_bpr[4], ch_bpr[5], ch_bpr[6], ch_bpr[7], i); + + if (ch_bpr[0] >= p_tas25xx->ch_count) { + ret = -EINVAL; + goto read_write_done; + } + + s_tasdevop.channel = ch_bpr[0]; + s_tasdevop.book = ch_bpr[1]; + s_tasdevop.page = ch_bpr[2]; + s_tasdevop.reg = ch_bpr[3]; + if (read_write_op == 1) + s_tasdevop.read_pending = 1; + else + s_tasdevop.read_pending = 0; + + if (read_write_op == 1) { + if (i == 3) { + pr_info("tas25xx: page read\n"); + s_tasdevop.op = OP_PAGE_READ; + } else if (i == 4) { + pr_info("tas25xx: single read\n"); + s_tasdevop.op = OP_REG_READ; + } else { + pr_info("tas25xx: page/single read is supported\n"); + s_tasdevop.read_pending = 0; + ret = -EINVAL; + } + } else if (read_write_op == 2) { + if (i == 5) { + pr_info("tas25xx: single write\n"); + s_tasdevop.op = OP_SNG_WRITE; + } else if (i == 8) { + pr_info("tas25xx: burst write\n"); + s_tasdevop.op = OP_BURST_WRITE; + } else { + pr_info("tas25xx: signle/burst write is supported\n"); + ret = -EINVAL; + } + } else { + pr_info("tas25xx: Only read and write is supported\n"); + ret = -EINVAL; + } + + if (read_write_op == 2) { + val = ch_bpr[4]; + reg = TAS25XX_REG(s_tasdevop.book, s_tasdevop.page, s_tasdevop.reg); + if (s_tasdevop.op == OP_SNG_WRITE) { + ret = p_tas25xx->write(p_tas25xx, + s_tasdevop.channel, reg, val); + } else if (s_tasdevop.op == OP_BURST_WRITE) { + ret = p_tas25xx->bulk_write(p_tas25xx, + s_tasdevop.channel, reg, &ch_bpr[4], 4); + } + } + +read_write_done: + if (ret < 0) + count = ret; + + return count; +} + +static ssize_t tas25xx_file_write(struct file *file, + const char *buf, size_t count, loff_t *ppos) +{ + struct tas25xx_priv *p_tas25xx = + (struct tas25xx_priv *)file->private_data; + int32_t ret = 0; + uint8_t *p_kbuf = NULL; + uint32_t reg = 0; + uint32_t len = 0; + uint32_t channel = 0; + int32_t read_write_op; + + mutex_lock(&p_tas25xx->file_lock); + + pr_info("%s size=%zu", __func__, count); + + if (count < 7) { + pr_err("TAS25XX invalid size %zu\n", count); + ret = -EINVAL; + goto done_write; + } + + p_kbuf = kzalloc(count, GFP_KERNEL); + if (p_kbuf == NULL) { + ret = -ENOMEM; + goto done_write; + } + + ret = copy_from_user(p_kbuf, buf, count); + if (ret != 0) { + count = ret; + pr_err("TAS25XX copy_from_user failed.\n"); + goto done_write; + } + + if ((p_kbuf[0] == 'w' || p_kbuf[0] == 'W') && p_kbuf[1] == ' ') + read_write_op = 2; + else if ((p_kbuf[0] == 'r' || p_kbuf[0] == 'R') && p_kbuf[1] == ' ') + read_write_op = 1; + else + read_write_op = 0; + + if (read_write_op) { + count = handle_read_write(p_tas25xx, read_write_op, count, p_kbuf); + goto done_write; + } + + if (p_kbuf[1] >= p_tas25xx->ch_count) { + pr_err("TAS25XX: channel count exceeds actual chanel count\n"); + goto done_write; + } + channel = p_kbuf[1]; + + switch (p_kbuf[0]) { + case TIAUDIO_CMD_REG_WITE: + if (count > 5) { + reg = ((uint32_t)p_kbuf[2] << 24) + + ((uint32_t)p_kbuf[3] << 16) + + ((uint32_t)p_kbuf[4] << 8) + + (uint32_t)p_kbuf[5]; + len = count - 6; + pr_info("TAS25XX TIAUDIO_CMD_REG_WITE, Reg=0x%x, Val=0x%x\n", + reg, p_kbuf[6]); + if (len == 1) { + uint32_t value = 0; + + value = p_kbuf[6]; + ret = p_tas25xx->write(p_tas25xx, channel, reg, + value); + } else if (len > 1) { + ret = p_tas25xx->bulk_write(p_tas25xx, channel, + reg, &p_kbuf[6], len); + } + } else { + pr_err("TAS25XX %s, write len fail, count=%d.\n", + __func__, (int)count); + } + break; + } + +done_write: + kfree(p_kbuf); + mutex_unlock(&p_tas25xx->file_lock); + + return count; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = tas25xx_file_read, + .write = tas25xx_file_write, + .unlocked_ioctl = NULL, + .open = tas25xx_file_open, + .release = tas25xx_file_release, +}; + +#define MODULE_NAME "tas_audio_dev" +static struct miscdevice tas25xx_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = MODULE_NAME, + .fops = &fops, +}; + +int32_t tas25xx_register_misc(struct tas25xx_priv *p_tas25xx) +{ + int32_t ret = 0; + + g_tas25xx = p_tas25xx; + ret = misc_register(&tas25xx_misc); + if (ret) + pr_err("TI-SmartPA TAS25XX misc fail: %d\n", ret); + + pr_info("TI-SmartPA %s, leave\n", __func__); + + return ret; +} +EXPORT_SYMBOL(tas25xx_register_misc); + +int32_t tas25xx_deregister_misc(struct tas25xx_priv *p_tas25xx) +{ + misc_deregister(&tas25xx_misc); + g_tas25xx = NULL; + return 0; +} +EXPORT_SYMBOL(tas25xx_deregister_misc); + +/* + * MODULE_AUTHOR("Texas Instruments Inc."); + * MODULE_DESCRIPTION("TAS25XX Misc Smart Amplifier driver"); + * MODULE_LICENSE("GPL v2"); + */ diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regbin-parser.c b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regbin-parser.c new file mode 100644 index 0000000000..43be5acaf7 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regbin-parser.c @@ -0,0 +1,2495 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance 4W Smart Amplifier + * + * Copyright (C) 2022 Texas Instruments, Inc. + * + * Author: Niranjan H Y, Vijeth P O + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include +#include +#include +#include "../inc/tas25xx.h" +#include "../inc/tas25xx-regmap.h" +#include "../inc/tas25xx-regbin-parser.h" +#include "../inc/tas25xx-device.h" + +#define TAS25XX_BINFILE_NAME "tismartpa_driver_tuning.bin" + +#define MDATA_HDR_SZ 9 +#define ONE_BIN_MD_SZ 20 +#define HDR_STR_SZ 5 +#define MAIN_BLOCK_SIZE 5 +#define HDR_N_ITS_SZ 9 +#define ANY_CHANNEL 0xffffffff +#define MDATA "MDATA" +#define HEADER "HEADR" +#define INITP_STR "INITP" +#define INTRP_STR "INTRP" +#define HW_PRMS_STR "HWPRM" +#define PROFP_STR "PROFP" +#define KCNTRL_STR "KCNTR" +#define BLK_OP_STR "BLKOP" +#define ALGOP_STR "ALGOP" + +enum block_types_t { + BLK_SW_RST, + BLK_POWER_CHECK, + BLK_MUTE, + BLK_CAL_INIT, + BLK_CAL_DEINIT, + BLK_RX_FMT, + BLK_TX_FMT, + BLK_INVALID, +}; + +const char *SampleRate[3] = { "48000", "44100", "96000"}; +const char *FMT_INV[4] = { "NB_NF", "IB_NF", "NB_IF", "IB_IF" }; +const char *FMT_MASK[4] = { "I2S", "DSP_A", "DSP_B", "LEFT_J"}; +const char *RX_SLOTS[3] = { "16", "24", "32" }; +const char *TX_SLOTS[3] = { "16", "24", "32" }; +const char *RX_BITWIDTH[3] = { "16", "24", "32" }; +const char *RX_SLOTLEN[3] = { "16", "24", "32" }; +const char *TX_SLOTLEN[3] = { "16", "24", "32" }; +const char *CMD_ID[4] = { "CMD_SINGLE_WRITE", "CMD_BURST_WRITES", "CMD_UPDATE_BITS", "CMD_DELAY"}; + +struct hw_params_t { + char *SampleRate[3]; + char *FMT_INV[4]; + char *FMT_MASK[4]; + char *RX_SLOTS[3]; + char *TX_SLOTS[3]; + char *RX_BITWIDTH[3]; + char *RX_SLOTLEN[3]; + char *TX_SLOTLEN[3]; +}; + +struct regbin_parser { + struct bin_header head; + struct default_hw_params def_hw_params; + char *init_params[MAX_CHANNELS]; + struct hw_params_t hw_params[MAX_CHANNELS]; +}; + +static struct regbin_parser s_rbin; + +static uint32_t g_no_of_profiles; +static int32_t g_tas25xx_profile; + +struct tas25xx_profiles { + uint8_t name[64]; + uint32_t misc_control; + uint8_t *pre_power_up; + uint8_t *post_power_up; + uint8_t *pre_power_down; + uint8_t *post_power_down; +}; + +struct tas25xx_profiles_channel { + struct tas25xx_profiles *tas_profiles; +}; + +static struct tas25xx_profiles_channel g_profile_data_list[MAX_CHANNELS]; + +static char **g_profile_list; +static struct soc_enum tas25xx_switch_enum; +static struct snd_kcontrol_new tas25xx_profile_ctrl; + + +struct tas25xx_kcontrol_int { + char *name; + char channel; + int32_t reg; + char reg_type; + int32_t mask; + int32_t range_min; + int32_t range_max; + int32_t step; + int32_t def; + int32_t count; + int32_t curr_val; + uint32_t misc_info; /* additional info regarding kcontrol */ + char *chardata; + int32_t *intdata; + struct soc_mixer_control mctl; +}; + +struct tas25xx_kcontrol_enum_data { + char name[64]; + char *data; +}; + +struct tas25xx_kcontrol_enum { + char *name; + char channel; + char def; + int32_t count; + int32_t curr_val; + uint32_t misc_info; /* additional info regarding kcontrol */ + struct tas25xx_kcontrol_enum_data *data; + char **enum_texts; + struct soc_enum tas25xx_kcontrol_enum; +}; + +union tas25xx_kcontrols_types { + struct tas25xx_kcontrol_int int_type; + struct tas25xx_kcontrol_enum enum_type; +}; + +struct tas25xx_kcontrols { + char type; + union tas25xx_kcontrols_types kcontrol; +}; + +static int32_t g_no_of_kcontrols; +/* bin file parsed data */ +static struct tas25xx_kcontrols *g_kctrl_data; +/* creating kcontrols */ +static struct snd_kcontrol_new *g_kctrl_ctrl; +static struct tas25xx_priv *g_tas25xx; + +static uint8_t *tas25xx_read_size_bytes(uint8_t *in, uint8_t **out); +static uint32_t get_block_size_noadvance(uint8_t *mem_in); + +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) +void tas25xx_parse_algo_bin(int ch_count, u8 *buf); +#endif /* CONFIG_TAS25XX_ALGO */ + +int32_t change_endian(void *data, int32_t size) +{ + int32_t i = 0; + int32_t j = 0; + int32_t *in; + char *data_l; + char c; + + if (size%4 != 0) { + pr_err("tas25xx: %s size %d are not 4bytes aligned!!!", + __func__, size); + } else { + in = (int32_t *)data; + c = 0; + for (i = 0; i < size/4; i++) { + data_l = (char *)&in[i]; + for (j = 0; j < 2; j++) { + c = data_l[3-j]; + data_l[3-j] = data_l[j]; + data_l[j] = c; + } + } + } + return 0; +} + +static bool header_check(struct tas25xx_priv *p_tas25xx, + const uint8_t *s1, const uint8_t *s2) +{ + bool success = false; + struct linux_platform *plat_data = NULL; + + plat_data = p_tas25xx->platform_data; + + if (memcmp(s1, s2, HDR_STR_SZ) == 0) + success = true; + + return success; +} + +static int8_t *find_block_for_channel(struct tas25xx_priv *p_tas25xx, + uint8_t *inp, uint8_t *blk_name, uint32_t ch) +{ + int32_t sz; + uint8_t *outp = NULL; + uint8_t *buf; + uint32_t count = -1; + uint32_t any_channel = 0; + struct linux_platform *plat_data = NULL; + + plat_data = p_tas25xx->platform_data; + + /* header 5 bytes + size 4 bytes */ + if (inp <= (p_tas25xx->fw_data + p_tas25xx->fw_size - HDR_N_ITS_SZ)) + if (inp && header_check(p_tas25xx, inp, blk_name)) + return inp; + + /* start from begginging */ + buf = p_tas25xx->fw_data; + + if (ch == ANY_CHANNEL) + any_channel = 1; + + while (buf <= (p_tas25xx->fw_data + p_tas25xx->fw_size - HDR_N_ITS_SZ)) { + if (header_check(p_tas25xx, buf, INITP_STR)) { + dev_info(plat_data->dev, + "block %s found, incrementing count", INITP_STR); + count++; + } else { + dev_info(plat_data->dev, + "block check, found %.5s(@%p) count=%d", buf, buf, count); + } + if (header_check(p_tas25xx, buf, blk_name)) { + if (any_channel || (count == ch)) { + outp = buf; + break; + } + } + + buf += 5; /* header */ + sz = get_block_size_noadvance(buf); + buf += 4; + buf += sz; + } + + if (outp) { + dev_warn(plat_data->dev, + "found block %s @%p, ch=%d", blk_name, outp, count); + } else { + dev_err(plat_data->dev, "block %s not found", blk_name); + } + + return outp; +} + +/* For profile KControls */ +static int32_t tas25xx_profile_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (!ucontrol) { + pr_err("tas25xx: %s:ucontrol is NULL\n", __func__); + return -EINVAL; + } + + ucontrol->value.integer.value[0] = g_tas25xx_profile; + + return 0; +} + +static int32_t tas25xx_profile_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int32_t temp, ret = -EINVAL; + + if (!ucontrol) + return ret; + + temp = ucontrol->value.integer.value[0]; + if (temp >= 0 && temp < g_no_of_profiles) { + g_tas25xx_profile = temp; + pr_info("tas25xx: setting profile %d", g_tas25xx_profile); + ret = 0; + } + + return ret; +} + +static int32_t tas25xx_create_profile_controls(struct tas25xx_priv *p_tas25xx) +{ + struct tas25xx_profiles *tas_profiles_t = (struct tas25xx_profiles *)g_profile_data_list[0].tas_profiles; + struct linux_platform *plat_data = + (struct linux_platform *)p_tas25xx->platform_data; + int32_t ret = 0; + int32_t i = 0; + + g_profile_list = kzalloc(g_no_of_profiles * sizeof(char *), GFP_KERNEL); + if (!g_profile_list) { + ret = -ENOMEM; + goto EXIT; + } + + for (i = 0; i < g_no_of_profiles; i++) { + g_profile_list[i] = kzalloc(64, GFP_KERNEL); + if (!g_profile_list[i]) { + ret = -ENOMEM; + goto EXIT; + } + memcpy(g_profile_list[i], tas_profiles_t[i].name, 64); + } + + tas25xx_switch_enum.items = g_no_of_profiles; + tas25xx_switch_enum.texts = (const char * const *)g_profile_list; + + tas25xx_profile_ctrl.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + tas25xx_profile_ctrl.name = "TAS25XX CODEC PROFILE"; + tas25xx_profile_ctrl.info = snd_soc_info_enum_double; + tas25xx_profile_ctrl.get = tas25xx_profile_get; + tas25xx_profile_ctrl.put = tas25xx_profile_put; + tas25xx_profile_ctrl.private_value = (unsigned long)(&tas25xx_switch_enum); + + ret = snd_soc_add_component_controls(plat_data->codec, + &tas25xx_profile_ctrl, 1); +EXIT: + return ret; +} + + +static uint8_t *process_block_get_cmd(uint8_t *mem_in, int8_t *cmd) +{ + *cmd = *mem_in; + mem_in++; + return mem_in; +} + +static uint32_t get_block_size_noadvance(uint8_t *mem_in) +{ + int32_t sz; + int32_t *ptr = (int32_t *)mem_in; + + sz = *ptr; + ptr++; + return sz; +} + +static uint8_t *process_block_get_single_write_data(uint8_t *mem_in, int32_t *reg, uint8_t *val) +{ + uint8_t *data8b; + int32_t *data32b; + + data32b = (int32_t *)mem_in; + *reg = *data32b; + data32b++; + + data8b = (uint8_t *)data32b; + *val = *data8b; + data8b++; + + return data8b; +} + +static uint8_t *process_block_get_burst_write_data(uint8_t *mem_in, + int32_t *reg, int32_t *count, uint8_t **bulk_buffer) +{ + uint8_t *data8b; + int32_t *data32b; + + data32b = (int32_t *)mem_in; + *reg = *data32b; + data32b++; + + *count = *data32b; + data32b++; + + data8b = (uint8_t *)data32b; + *bulk_buffer = data8b; + + data8b = data8b + (*count); + + return data8b; +} + +static uint8_t *process_block_get_bit_update_data(uint8_t *mem_in, + int32_t *reg, uint8_t *mask, uint8_t *value) +{ + uint8_t *data8b; + int32_t *data32b; + + data32b = (int32_t *)mem_in; + *reg = *data32b; + data32b++; + + data8b = (uint8_t *)data32b; + *mask = *data8b; + data8b++; + *value = *data8b; + data8b++; + + return data8b; +} + +static uint8_t *process_block_get_delay_data(uint8_t *mem_in, int32_t *delay) +{ + uint8_t *data8b; + int32_t *data32b; + + data32b = (int32_t *)mem_in; + *delay = *data32b; + data32b++; + + data8b = (uint8_t *)data32b; + + return data8b; +} + +int32_t tas25xx_process_block(struct tas25xx_priv *p_tas25xx, char *mem, int32_t chn) +{ + int32_t i = 0; + int32_t block_size = 0; + struct linux_platform *plat_data = NULL; + int32_t ret = 0; + int32_t ret_i = 0; + int32_t reg; + int32_t count; + int32_t delay; + int fw_state; + int8_t cmd; + uint8_t mask; + uint8_t val; + uint8_t *buffer = NULL; + uint8_t *ptr = NULL; + + fw_state = atomic_read(&p_tas25xx->fw_state); + if (fw_state != TAS25XX_DSP_FW_OK) + return -EINVAL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + memcpy(&block_size, mem, sizeof(int32_t)); + mem += sizeof(int32_t); + ptr = mem; + + while (i < block_size) { + ptr = process_block_get_cmd(ptr, &cmd); + switch (cmd) { + case CMD_SINGLE_WRITE: + ptr = process_block_get_single_write_data(ptr, ®, &val); + i += CMD_SINGLE_WRITE_SZ; + ret_i = p_tas25xx->write(p_tas25xx, chn, reg, val); + dev_info(plat_data->dev, "ch=%d Cmd = %s B:P:R %02x:%02x:%02x, value=%02x, ret=%d\n", + chn, CMD_ID[cmd], TAS25XX_BOOK_ID(reg), TAS25XX_PAGE_ID(reg), + TAS25XX_PAGE_REG(reg), val, ret_i); + ret |= ret_i; + break; + + case CMD_BURST_WRITES: + ptr = process_block_get_burst_write_data(ptr, ®, &count, &buffer); + i += CMD_BURST_WRITES_SZ + count; + ret_i = p_tas25xx->bulk_write(p_tas25xx, chn, reg, buffer, count); + dev_info(plat_data->dev, + "ch=%d Cmd = %s B:P:R %02x:%02x:%02x, count=%d, buf=%02x %02x %02x %02x ret=%d\n", + chn, CMD_ID[cmd], TAS25XX_BOOK_ID(reg), TAS25XX_PAGE_ID(reg), TAS25XX_PAGE_REG(reg), + count, buffer[0], buffer[1], buffer[2], buffer[3], ret_i); + ret |= ret_i; + break; + + case CMD_UPDATE_BITS: + ptr = process_block_get_bit_update_data(ptr, ®, &mask, &val); + i += CMD_UPDATE_BITS_SZ; + ret_i = p_tas25xx->update_bits(p_tas25xx, chn, reg, mask, val); + dev_info(plat_data->dev, + "ch=%d Cmd = %s B:P:R %02x:%02x:%02x mask=%02x, val=%02x, ret=%d", + chn, CMD_ID[cmd], TAS25XX_BOOK_ID(reg), TAS25XX_PAGE_ID(reg), TAS25XX_PAGE_REG(reg), + mask, val, ret_i); + ret |= ret_i; + break; + + case CMD_DELAY: + ptr = process_block_get_delay_data(ptr, &delay); + i += CMD_DELAY_SZ; + dev_info(plat_data->dev, "ch=%d Cmd = %s delay=%x(%d)\n", + chn, CMD_ID[cmd], delay, delay); + ret |= 0; + /*delay in ms, convert to us*/ + usleep_range(delay * 1000, (delay * 1000) + 100); + break; + } + } + + return ret; +} + +int32_t tas25xx_check_if_powered_on(struct tas25xx_priv *p_tas25xx, int *state, int chn) +{ + int8_t cmd; + uint8_t expected_val = 0; + uint8_t mask; + uint32_t val; + int32_t reg; + int32_t i = 0; + int32_t block_size = 0; + struct linux_platform *plat_data = NULL; + int32_t ret = 0; + uint8_t *ptr = p_tas25xx->block_op_data[chn].power_check; + bool expected = true; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + if (!ptr) { + dev_info(plat_data->dev, "%s null buffer recieved for ch=%d", + __func__, chn); + return -EINVAL; + } + + memcpy(&block_size, ptr, sizeof(int32_t)); + ptr += sizeof(int32_t); + + while (i < block_size) { + ptr = process_block_get_cmd(ptr, &cmd); + switch (cmd) { + case CMD_SINGLE_WRITE: + ptr = process_block_get_single_write_data(ptr, ®, &expected_val); + i += CMD_SINGLE_WRITE_SZ; + ret = p_tas25xx->read(p_tas25xx, chn, reg, &val); + dev_info(plat_data->dev, + "Chn=%d Cmd = %s reg=%x(%d), value read=%02x, expected=%02x\n", + chn, CMD_ID[cmd], reg, reg, val, expected_val); + if ((val & 0xFFFF) != expected_val) + expected = expected && false; + break; + + case CMD_UPDATE_BITS: + ptr = process_block_get_bit_update_data(ptr, ®, &mask, &expected_val); + i += CMD_UPDATE_BITS_SZ; + dev_info(plat_data->dev, "Cmd = %s reg=%x(%d), mask=%02x, expected_val=%02x\n", + CMD_ID[cmd], reg, reg, mask, expected_val); + ret = p_tas25xx->read(p_tas25xx, chn, reg, &val); + dev_info(plat_data->dev, + "Chn=%d Cmd = %s reg=%x(%d), value read=%02x, expected=%02x\n", + chn, CMD_ID[cmd], reg, reg, val, expected_val); + if ((val & 0xFFFF) != expected_val) + expected = expected && false; + break; + + default: + dev_info(plat_data->dev, "Chn=%d default cmd=%d", chn, cmd); + break; + } + } + + if (expected) + *state = 1; + else + *state = 0; + + return ret; +} + +static int32_t tas25xx_parse_init_params(struct tas25xx_priv *p_tas25xx, uint8_t **buf, int32_t ch) +{ + int32_t ret = 0; + int32_t size = 0; + struct linux_platform *plat_data = NULL; + uint8_t *data = *buf; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + data = find_block_for_channel(p_tas25xx, data, INITP_STR, ch); + if (!data) + return -EINVAL; + + data += HDR_STR_SZ; + + /* Parsing Init Params */ + memcpy(&size, data, sizeof(size)); + s_rbin.init_params[ch] = data; + data += (size + sizeof(size)); + + *buf = data; + + return ret; +} + +static uint8_t *tas25xx_read_size_bytes(uint8_t *in, uint8_t **out) +{ + uint8_t *buf; + int32_t size = 0; + + if (!in || !out) + return NULL; + + buf = in; + *out = buf; + + memcpy(&size, buf, sizeof(size)); + buf += size + sizeof(int32_t); + + /*buf_data = in + sizeof(int32_t); + dev_info(plat_data->dev, "tas25xx: dump buffer, size=%d\n", size); + for (i = 0; i < size; i++) { + dev_info(plat_data->dev, "tas25xx: buf[%d] = 0x%02x,\t", i, buf_data[i]); + } + dev_info(plat_data->dev, "\n"); + */ + + return buf; +} + +static int32_t tas25xx_parse_hw_params(struct tas25xx_priv *p_tas25xx, uint8_t **inbuf, int32_t ch) +{ + int32_t ret = 0; + uint8_t *next = *inbuf; + uint8_t *first = *inbuf; + uint8_t *out = NULL; + int32_t size = 0; + int32_t no_of_hw_params = 0; + int count = 1; + int end = 0; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + next = find_block_for_channel(p_tas25xx, next, HW_PRMS_STR, ch); + if (!next) + return -EINVAL; + + next += HDR_STR_SZ; + size = get_block_size_noadvance(next); + next += 4; + no_of_hw_params = get_block_size_noadvance(next); + next += 4; + + dev_info(plat_data->dev, "parsing HW params, inbuf=0x%p, size=%d, no_of_hw_params=%d", + next, size, no_of_hw_params); + + next = tas25xx_read_size_bytes(next, &out); + while (next) { + switch (count) { + case 1: + s_rbin.hw_params[ch].SampleRate[SampleRate_48000] = out; + break; + + case 2: + s_rbin.hw_params[ch].SampleRate[SampleRate_44100] = out; + break; + + case 3: + s_rbin.hw_params[ch].SampleRate[SampleRate_96000] = out; + break; + + case 4: + /* Parsing FMT_INV_NB_NF */ + s_rbin.hw_params[ch].FMT_INV[FMT_INV_NB_NF] = out; + break; + + case 5: + /* Parsing FMT_INV_IB_NF */ + s_rbin.hw_params[ch].FMT_INV[FMT_INV_IB_NF] = out; + break; + + case 6: + /* Parsing FMT_INV_NB_IF */ + s_rbin.hw_params[ch].FMT_INV[FMT_INV_NB_IF] = out; + break; + + case 7: + /* Parsing FMT_INV_IB_IF */ + s_rbin.hw_params[ch].FMT_INV[FMT_INV_IB_IF] = out; + break; + + case 8: + /* Parsing FMT_MASK_I2S */ + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_I2S] = out; + break; + + case 9: + /* Parsing FMT_MASK_DSP_A */ + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_DSP_A] = out; + break; + + case 10: + /* Parsing FMT_MASK_DSP_B */ + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_DSP_B] = out; + break; + + case 11: + /* Parsing FMT_MASK_LEFT_J */ + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_LEFT_J] = out; + break; + + + case 12: + /* Parsing RX_SLOTS_16 */ + s_rbin.hw_params[ch].RX_SLOTS[RX_SLOTS_16] = out; + break; + + + case 13: + /* Parsing RX_SLOTS_24 */ + s_rbin.hw_params[ch].RX_SLOTS[RX_SLOTS_24] = out; + break; + + + case 14: + /* Parsing RX_SLOTS_32 */ + s_rbin.hw_params[ch].RX_SLOTS[RX_SLOTS_32] = out; + break; + + + case 15: + /* Parsing TX_SLOTS_16 */ + s_rbin.hw_params[ch].TX_SLOTS[TX_SLOTS_16] = out; + break; + + + case 16: + /* Parsing TX_SLOTS_24 */ + s_rbin.hw_params[ch].TX_SLOTS[TX_SLOTS_24] = out; + break; + + + case 17: + /* Parsing TX_SLOTS_32 */ + s_rbin.hw_params[ch].TX_SLOTS[TX_SLOTS_32] = out; + break; + + + case 18: + /* Parsing RX_BITWIDTH_16 */ + s_rbin.hw_params[ch].RX_BITWIDTH[RX_BITWIDTH_16] = out; + break; + + + case 19: + /* Parsing RX_BITWIDTH_24 */ + s_rbin.hw_params[ch].RX_BITWIDTH[RX_BITWIDTH_24] = out; + break; + + + case 20: + /* Parsing RX_BITWIDTH_32 */ + s_rbin.hw_params[ch].RX_BITWIDTH[RX_BITWIDTH_32] = out; + break; + + + case 21: + /* Parsing RX_SLOTLEN_16 */ + s_rbin.hw_params[ch].RX_SLOTLEN[RX_SLOTLEN_16] = out; + break; + + + case 22: + /* Parsing RX_SLOTLEN_24 */ + s_rbin.hw_params[ch].RX_SLOTLEN[RX_SLOTLEN_24] = out; + break; + + case 23: + /* Parsing RX_SLOTLEN_32 */ + s_rbin.hw_params[ch].RX_SLOTLEN[RX_SLOTLEN_32] = out; + break; + + case 24: + /* Parsing TX_SLOTLEN_16 */ + s_rbin.hw_params[ch].TX_SLOTLEN[TX_SLOTLEN_16] = out; + break; + + case 25: + /* Parsing TX_SLOTLEN_24 */ + s_rbin.hw_params[ch].TX_SLOTLEN[TX_SLOTLEN_24] = out; + break; + + case 26: + /* Parsing TX_SLOTLEN_32 */ + s_rbin.hw_params[ch].TX_SLOTLEN[TX_SLOTLEN_32] = out; + end = 1; + break; + + default: + end = 1; + break; + } /*<>*/ + + if (!end) { + next = tas25xx_read_size_bytes(next, &out); + count++; + } else { + break; + } + } /*<>*/ + + size = (int32_t)(next - first); + if (size < 0) + size = -1 * size; + *inbuf = next; + + dev_info(plat_data->dev, "parsing HW params exit, next=0x%p, size=%d", next, size); + + return ret; +} + +int tas25xx_check_if_algo_ctrl_bypassed(int ch) +{ + struct tas25xx_profiles *tas_profiles_t; + if (ch >= s_rbin.head.channels) + return -EINVAL; + + tas_profiles_t = (struct tas25xx_profiles *)g_profile_data_list[ch].tas_profiles; + + /* bit5, 1 = algo control bypass, 0 = use algo */ + return (tas_profiles_t[g_tas25xx_profile].misc_control & 0x10); +} +EXPORT_SYMBOL(tas25xx_check_if_algo_ctrl_bypassed); + +int tas25xx_get_drv_channel_opmode(void) +{ + struct tas25xx_profiles *tas_profiles_t; + + tas_profiles_t = (struct tas25xx_profiles *)g_profile_data_list[0].tas_profiles; + return tas_profiles_t[g_tas25xx_profile].misc_control; +} +EXPORT_SYMBOL(tas25xx_get_drv_channel_opmode); + + +static int32_t tas25xx_parse_profiles(struct tas25xx_priv *p_tas25xx, uint8_t **inbuf, int32_t ch) +{ + int32_t ret = 0; + int32_t size; + int32_t i = 0; + uint8_t *buf = *inbuf; + uint8_t *out = NULL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + buf = find_block_for_channel(p_tas25xx, buf, PROFP_STR, ch); + if (!buf) + return -EINVAL; + + buf += HDR_STR_SZ; + size = get_block_size_noadvance(buf); + buf += 4; + dev_info(plat_data->dev, "parsing profiles, total size=%d", size); + + memcpy(&g_no_of_profiles, buf, 1); + buf++; + + dev_info(plat_data->dev, "Number of profiles=%d, allocation=%u", (int)g_no_of_profiles, + (uint32_t)(g_no_of_profiles * sizeof(struct tas25xx_profiles))); + + g_profile_data_list[ch].tas_profiles = + kzalloc(g_no_of_profiles*(sizeof(struct tas25xx_profiles)), GFP_KERNEL); + if (!g_profile_data_list[ch].tas_profiles) + return -ENOMEM; + + for (i = 0; i < g_no_of_profiles; i++) { + struct tas25xx_profiles *tas_profiles_t = + (struct tas25xx_profiles *)g_profile_data_list[ch].tas_profiles; + /* Parsing Name */ + memcpy(tas_profiles_t[i].name, buf, 64); + buf += 64; + + /* bitmask indicating misc control for profile */ + memcpy(&tas_profiles_t[i].misc_control, buf, sizeof(uint32_t)); + buf += 4; + + /* pre_power_up */ + buf = tas25xx_read_size_bytes(buf, &out); + tas_profiles_t[i].pre_power_up = out; + + /* post_power_up */ + buf = tas25xx_read_size_bytes(buf, &out); + tas_profiles_t[i].post_power_up = out; + + /* pre_power_down */ + buf = tas25xx_read_size_bytes(buf, &out); + tas_profiles_t[i].pre_power_down = out; + + /* post_power_down */ + buf = tas25xx_read_size_bytes(buf, &out); + tas_profiles_t[i].post_power_down = out; + } + + *inbuf = buf; + return ret; +} + +void tas25xx_prep_dev_for_calib(int start) +{ + int i, ret; + uint8_t *mem; + struct linux_platform *plat_data = NULL; + + if (unlikely(!g_tas25xx)) + return; + + plat_data = (struct linux_platform *) g_tas25xx->platform_data; + + for (i = 0; i < g_tas25xx->ch_count; i++) { + if (start) + mem = g_tas25xx->block_op_data[i].cal_init; + else + mem = g_tas25xx->block_op_data[i].cal_deinit; + + if (mem) { + ret = tas25xx_process_block(g_tas25xx, mem, i); + if (ret) + dev_err(plat_data->dev, "ch=%d failed to %s device for calibration", + i, start ? "init" : "deinit"); + } + } +} +EXPORT_SYMBOL(tas25xx_prep_dev_for_calib); + + +static int32_t tas25xx_parse_block_data(struct tas25xx_priv *p_tas25xx, uint8_t **inbuf, int32_t ch) +{ + int32_t i = 0; + int32_t size; + int32_t ret = 0; + uint32_t number_of_blocks = 0; + enum block_types_t type; + uint8_t *out = NULL; + uint8_t *start; + uint8_t *end; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + start = *inbuf; + end = *inbuf; + + end = find_block_for_channel(p_tas25xx, end, BLK_OP_STR, ch); + if (!end) + return -EINVAL; + + end += HDR_STR_SZ; + size = get_block_size_noadvance(end); + end += 4; + + number_of_blocks = *((uint32_t *)end); + end += sizeof(uint32_t); + + dev_info(plat_data->dev, "parsing block op data, total size=%d, toal blocks=%u", + size, number_of_blocks); + + for (i = 0; i < number_of_blocks; i++) { + if (memcmp(end, "SWRST", MAIN_BLOCK_SIZE) == 0) { + type = BLK_SW_RST; + } else if (memcmp(end, "PWRCK", MAIN_BLOCK_SIZE) == 0) { + type = BLK_POWER_CHECK; + } else if (memcmp(end, "MUTE0", MAIN_BLOCK_SIZE) == 0) { + type = BLK_MUTE; + } else if (memcmp(end, "RXFMT", MAIN_BLOCK_SIZE) == 0) { + type = BLK_RX_FMT; + } else if (memcmp(end, "TXFMT", MAIN_BLOCK_SIZE) == 0) { + type = BLK_TX_FMT; + } else if (memcmp(end, "CLINI", MAIN_BLOCK_SIZE) == 0) { + type = BLK_CAL_INIT; + } else if (memcmp(end, "CLDIN", MAIN_BLOCK_SIZE) == 0) { + type = BLK_CAL_DEINIT; + } else { + type = BLK_INVALID; + dev_err(plat_data->dev, "%s, number of block=%u\n", + __func__, number_of_blocks); + } + end += 5; + + switch (type) { + case BLK_SW_RST: + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->block_op_data[ch].sw_reset = out; + else + ret = -EINVAL; + break; + + case BLK_POWER_CHECK: + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->block_op_data[ch].power_check = out; + else + ret = -EINVAL; + break; + + case BLK_MUTE: + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->block_op_data[ch].mute = out; + else + ret = -EINVAL; + break; + + case BLK_CAL_INIT: + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->block_op_data[ch].cal_init = out; + else + ret = -EINVAL; + break; + + case BLK_CAL_DEINIT: + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->block_op_data[ch].cal_deinit = out; + else + ret = -EINVAL; + break; + + case BLK_RX_FMT: + end = tas25xx_read_size_bytes(end, &out); + dev_info(plat_data->dev, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + out[0], out[1], out[2], out[3], out[4], out[5], out[6], out[7], out[8], out[9], out[10], out[11]); + if (out) + p_tas25xx->block_op_data[ch].rx_fmt_data = out; + else + ret = -EINVAL; + break; + + case BLK_TX_FMT: + end = tas25xx_read_size_bytes(end, &out); + dev_info(plat_data->dev, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + out[0], out[1], out[2], out[3], out[4], out[5], out[6], out[7], out[8], out[9], out[10], out[11]); + if (out) + p_tas25xx->block_op_data[ch].tx_fmt_data = out; + else + ret = -EINVAL; + break; + + case BLK_INVALID: + default: + break; + + } + } + + dev_info(plat_data->dev, "%s reset=%p, power-check=%p mute=%p\n", __func__, + p_tas25xx->block_op_data[ch].sw_reset, p_tas25xx->block_op_data[ch].power_check, + p_tas25xx->block_op_data[ch].mute); + + *inbuf = end; + + return ret; +} + +static int32_t tas25xx_parse_interrupts(struct tas25xx_priv *p_tas25xx, uint8_t **inbuf, int32_t ch) +{ + int32_t ret = 0; + int32_t size = 0; + int32_t i = 0; + uint8_t *out = NULL; + uint8_t *start; + uint8_t *end; + int32_t dummy; + struct tas25xx_intr_info *intr_info = NULL; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + start = *inbuf; + end = *inbuf; + + end = find_block_for_channel(p_tas25xx, end, INTRP_STR, ch); + if (!end) + return -EINVAL; + + end += HDR_STR_SZ; + size = get_block_size_noadvance(end); + end += 4; + dev_info(plat_data->dev, "parsing interrupt data, total size=%d, ptr=%p", + size, end - 4 - 5); + + + p_tas25xx->intr_data[ch].count = *end; + end++; + + dev_info(plat_data->dev, "INTR parsing interrupt buffer, interrupt count=%d", + p_tas25xx->intr_data[ch].count); + + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->intr_data[ch].buf_intr_enable = out; + else + return -EINVAL; + + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->intr_data[ch].buf_intr_disable = out; + else + return -EINVAL; + + end = tas25xx_read_size_bytes(end, &out); + if (out) + p_tas25xx->intr_data[ch].buf_intr_clear = out; + else + return -EINVAL; + + dev_info(plat_data->dev, "INTR en=%p dis=%p clr=%p\n", + p_tas25xx->intr_data[ch].buf_intr_enable, + p_tas25xx->intr_data[ch].buf_intr_disable, + p_tas25xx->intr_data[ch].buf_intr_clear); + + dev_info(plat_data->dev, "INTR %c %c %c %c", end[0], end[1], end[2], end[3]); + + size = (p_tas25xx->intr_data[ch].count) * sizeof(struct tas25xx_intr_info); + dev_info(plat_data->dev, "%s:%u: ALLOC Size %d\n", + __func__, __LINE__, size); + + intr_info = kzalloc(size, GFP_KERNEL); + if (!intr_info) + return -ENOMEM; + + for (i = 0; i < p_tas25xx->intr_data[ch].count; i++) { + memcpy(intr_info[i].name, end, 64); + intr_info[i].name[63] = 0; + end += 64; + memcpy(&(intr_info[i].reg), end, 4); + end += 4; + memcpy(&(intr_info[i].mask), end, 4); + end += 4; + memcpy(&(intr_info[i].action), end, 4); + end += 4; + memcpy(&(intr_info[i].is_clock_based), end, 4); + end += 4; + memcpy(&(intr_info[i].notify_int_val), end, 4); + end += 4; + /* 2 dummy ints for future reference */ + memcpy(&dummy, end, 4); + end += 4; + memcpy(&dummy, end, 4); + end += 4; + + dev_info(plat_data->dev, + "INTR %s, REG=%x, MASK=%x, action=%d, clk based=%d notify=%d", intr_info[i].name, + intr_info[i].reg, intr_info[i].mask, intr_info[i].action, + intr_info[i].is_clock_based, intr_info[i].notify_int_val); + } + p_tas25xx->intr_data[ch].intr_info = intr_info; + + p_tas25xx->intr_data[ch].processing_delay = *((uint32_t *)end); + end += 4; + + /* 5 dummy ints for future reference */ + dummy = *((uint32_t *)end); + end += 4; + dummy = *((uint32_t *)end); + end += 4; + dummy = *((uint32_t *)end); + end += 4; + dummy = *((uint32_t *)end); + end += 4; + dummy = *((uint32_t *)end); + end += 4; + + if (!ch) + dev_info(plat_data->dev, "INTR processing_delay=%d", + p_tas25xx->intr_data[ch].processing_delay); + + size = end - start; + if (size < 0) + size = -1 * size; + + dev_info(plat_data->dev, "INTR bin buf size=%d (end=%p - start=%p)", + size, end, start); + *inbuf = end; + + return ret; +} + +static int32_t tas25xx_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int32_t ret = -EINVAL; + int32_t curr_kcontrol = 0; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (!mc) { + pr_err("%s:codec or control is NULL\n", __func__); + return ret; + } + + curr_kcontrol = mc->reg; + if (curr_kcontrol < g_no_of_kcontrols) + ucontrol->value.integer.value[0] = + g_kctrl_data[curr_kcontrol].kcontrol.int_type.curr_val; + else + return ret; + return 0; +} + +static int32_t tas25xx_int_put_idx_value(struct tas25xx_priv *p_tas25xx, + uint32_t ctrl_idx, int32_t value_idx) +{ + int ret = 0; + int32_t reg_w; + int32_t count_w; + int32_t chn; + int32_t mask_w; + int32_t value_w; + struct linux_platform *plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + if (ctrl_idx < g_no_of_kcontrols) { + reg_w = g_kctrl_data[ctrl_idx].kcontrol.int_type.reg; + count_w = g_kctrl_data[ctrl_idx].kcontrol.int_type.count; + chn = g_kctrl_data[ctrl_idx].kcontrol.int_type.channel; + mask_w = g_kctrl_data[ctrl_idx].kcontrol.int_type.mask; + value_w = 0; + + dev_info(plat_data->dev, "%s kcontrol=%s with value index=%d", __func__, + g_kctrl_data[ctrl_idx].kcontrol.int_type.name, value_idx); + + if ((value_idx >= 0) && (value_idx < count_w)) { + switch (g_kctrl_data[ctrl_idx].kcontrol.int_type.reg_type) { + case CMD_SINGLE_WRITE: + value_w = g_kctrl_data[ctrl_idx].kcontrol.int_type.chardata[value_idx]; + ret = p_tas25xx->write(p_tas25xx, chn, reg_w, + value_w); + if (ret) { + dev_err(plat_data->dev, "tas25xx:%s failed ret = %d\n", __func__, ret); + } else { + dev_info(plat_data->dev, + "ch=%d Cmd = %s B:P:R %02x:%02x:%02x, value=%02x, ret=%d\n", chn, + CMD_ID[CMD_SINGLE_WRITE], TAS25XX_BOOK_ID(reg_w), TAS25XX_PAGE_ID(reg_w), + TAS25XX_PAGE_REG(reg_w), value_w, ret); + } + break; + + case CMD_BURST_WRITES: + value_w = g_kctrl_data[ctrl_idx].kcontrol.int_type.intdata[value_idx]; + change_endian(&value_w, sizeof(int32_t)); + ret = p_tas25xx->bulk_write(p_tas25xx, chn, + reg_w, + (char *)(&value_w), sizeof(int32_t)); + if (ret) { + dev_err(plat_data->dev, "tas25xx:%s failed ret = %d\n", __func__, ret); + } else { + dev_info(plat_data->dev, "ch=%d Cmd = %s B:P:R %02x:%02x:%02x, value=%02x, ret=%d\n", + chn, CMD_ID[CMD_BURST_WRITES], TAS25XX_BOOK_ID(reg_w), TAS25XX_PAGE_ID(reg_w), + TAS25XX_PAGE_REG(reg_w), value_w, ret); + } + break; + + case CMD_UPDATE_BITS: + value_w = g_kctrl_data[ctrl_idx].kcontrol.int_type.chardata[value_idx]; + ret = p_tas25xx->update_bits(p_tas25xx, + chn, reg_w, mask_w, value_w); + if (ret) { + dev_err(plat_data->dev, "tas25xx:%s failed ret = %d\n", __func__, ret); + } else { + dev_info(plat_data->dev, + "ch=%d Cmd = %s B:P:R %02x:%02x:%02x, mask=%02x, value=%02x, ret=%d\n", + chn, CMD_ID[CMD_UPDATE_BITS], TAS25XX_BOOK_ID(reg_w), TAS25XX_PAGE_ID(reg_w), + TAS25XX_PAGE_REG(reg_w), mask_w, value_w, ret); + } + break; + } + } else { + dev_err(plat_data->dev, "tas25xx:%s out of bound, value_idx=%d\n", __func__, value_idx); + ret = -EINVAL; + } + } else { + dev_err(plat_data->dev, "tas25xx:%s out of bound ctrl_idx=%d\n", __func__, ctrl_idx); + ret = -EINVAL; + } + + return ret; +} + +static int32_t tas25xx_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct tas25xx_priv *p_tas25xx = NULL; + struct linux_platform *plat_data = NULL; + int32_t ret = -EINVAL; + uint32_t curr_kcontrol = 0; + uint32_t misc_info; + + if ((codec == NULL) || (mc == NULL)) { + pr_err("tas25xx: %s:codec or control is NULL\n", __func__); + return ret; + } + + p_tas25xx = snd_soc_component_get_drvdata(codec); + if (p_tas25xx == NULL) { + pr_err("tas25xx: %s:p_tas25xx is NULL\n", __func__); + return ret; + } + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + mutex_lock(&p_tas25xx->codec_lock); + + curr_kcontrol = mc->reg; + if (curr_kcontrol < g_no_of_kcontrols) { + int32_t value_idx = ucontrol->value.integer.value[0]; + misc_info = g_kctrl_data[curr_kcontrol].kcontrol.int_type.misc_info; + + if (((misc_info & 0xf) == KCNTR_ANYTIME) || + (p_tas25xx->m_power_state == TAS_POWER_ACTIVE)) + ret = tas25xx_int_put_idx_value(p_tas25xx, curr_kcontrol, value_idx); + else + ret = 0; + + if (!ret) + g_kctrl_data[curr_kcontrol].kcontrol.int_type.curr_val = value_idx; + } else { + dev_err(plat_data->dev, "tas25xx:%s invalid kcontrol %d\n", __func__, curr_kcontrol); + } + mutex_unlock(&p_tas25xx->codec_lock); + + return ret; +} + +static int32_t tas25xx_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int32_t ret = -EINVAL; + int32_t i = 0; + + if (ucontrol == NULL) { + pr_err("%s:ucontrol is NULL\n", __func__); + return ret; + } + + while (i < g_no_of_kcontrols) { + if (g_kctrl_data[i].type == 1) { + if (strnstr(ucontrol->id.name, + g_kctrl_data[i].kcontrol.enum_type.name, 64)) { + ucontrol->value.integer.value[0] = + g_kctrl_data[i].kcontrol.enum_type.curr_val; + ret = 0; + } + } + i++; + } + return ret; +} + +static int32_t tas25xx_enum_put_idx_value(struct tas25xx_priv *p_tas25xx, + int32_t ctrl_idx, int32_t value_idx) +{ + int ret = 0; + int32_t count_w = g_kctrl_data[ctrl_idx].kcontrol.enum_type.count; + int32_t channel = g_kctrl_data[ctrl_idx].kcontrol.enum_type.channel; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s kcontrol=%s with value index=%d", __func__, + g_kctrl_data[ctrl_idx].kcontrol.enum_type.name, value_idx); + + if ((value_idx >= 0) && (value_idx < count_w)) { + char *mem = g_kctrl_data[ctrl_idx].kcontrol.enum_type.data[value_idx].data; + ret = tas25xx_process_block(p_tas25xx, mem, channel); + if (ret) + dev_err(plat_data->dev, + "tas25xx:%s failed ret = %d\n", __func__, ret); + } else { + dev_err(plat_data->dev, + "tas25xx:%s out of bound, value_idx=%d\n", __func__, value_idx); + ret = -EINVAL; + } + + return ret; +} + +static int32_t tas25xx_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct tas25xx_priv *p_tas25xx = NULL; + struct linux_platform *plat_data = NULL; + int32_t ret = -EINVAL; + int32_t i = 0; + uint32_t misc_info; + + if ((codec == NULL) || (mc == NULL)) { + pr_err("%s:codec or control is NULL\n", __func__); + return ret; + } + + p_tas25xx = snd_soc_component_get_drvdata(codec); + if (p_tas25xx == NULL) { + pr_err("%s:p_tas25xx is NULL\n", __func__); + return ret; + } + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + mutex_lock(&p_tas25xx->codec_lock); + + while (i < g_no_of_kcontrols) { + if (g_kctrl_data[i].type == 1) { + if (strnstr(ucontrol->id.name, + g_kctrl_data[i].kcontrol.enum_type.name, 64)) { + int32_t v_idx = ucontrol->value.integer.value[0]; + misc_info = g_kctrl_data[i].kcontrol.enum_type.misc_info; + + /* check if it needs to be updated now */ + if (((misc_info & 0xf) == KCNTR_ANYTIME) || + (p_tas25xx->m_power_state == TAS_POWER_ACTIVE)) + ret = tas25xx_enum_put_idx_value(p_tas25xx, i, v_idx); + else + ret = 0; /* mixer control will be updated during power up */ + + if (!ret) + g_kctrl_data[i].kcontrol.enum_type.curr_val = v_idx; + } + } + i++; + } + + mutex_unlock(&p_tas25xx->codec_lock); + + return ret; +} + +/* + * Helper function to update the kcontrol local values to device + * when something like SW/HW reset happens. + */ +int32_t tas25xx_update_kcontrol_data(struct tas25xx_priv *p_tas25xx, + enum kcntl_during_t cur_state, uint32_t chmask) +{ + int ret = -EINVAL; + int i = 0, kcntrl_type, misc_info; + int chn; + char *name = NULL; + struct linux_platform *plat_data = NULL; + struct tas25xx_kcontrol_int *int_type; + struct tas25xx_kcontrol_enum *enum_type; + + if (!p_tas25xx || !g_kctrl_data) { + pr_err("%s: p_tas25xx=%p g_kctrl_data=%p\n", + __func__, p_tas25xx, g_kctrl_data); + ret = -EINVAL; + return ret; + } + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + if (!plat_data) + return ret; + + for (i = 0; i < g_no_of_kcontrols; ++i) { + kcntrl_type = g_kctrl_data[i].type; + ret = 0; + + switch (kcntrl_type) { + /* integer */ + case 0: + int_type = &g_kctrl_data[i].kcontrol.int_type; + chn = g_kctrl_data[i].kcontrol.int_type.channel; + if ((1 << chn) & chmask) { + misc_info = int_type->misc_info; + name = int_type->name; + if (cur_state == (misc_info & 0xf)) + ret = tas25xx_int_put_idx_value(p_tas25xx, i, + int_type->curr_val); + } + break; + + /* enum */ + case 1: + enum_type = &g_kctrl_data[i].kcontrol.enum_type; + chn = g_kctrl_data[i].kcontrol.enum_type.channel; + if ((1 << chn) & chmask) { + misc_info = enum_type->misc_info; + name = enum_type->name; + if (cur_state == (misc_info & 0xf)) + ret = tas25xx_enum_put_idx_value(p_tas25xx, i, + enum_type->curr_val); + } + break; + + default: + name = NULL; + dev_err(plat_data->dev, + "%s non supported kcontrol type\n", __func__); + ret = -EINVAL; + break; + + } + + if (ret) { + dev_err(plat_data->dev, "%s %s mixer ctrl update err=%d\n", + __func__, name ? name : "", ret); + continue; + } + } + + return ret; +} + + +static int32_t tas25xx_create_common_kcontrols(struct tas25xx_priv *p_tas25xx) +{ + struct linux_platform *plat_data = + (struct linux_platform *)p_tas25xx->platform_data; + int32_t ret = 0; + int32_t i = 0; + + if (!g_no_of_kcontrols) { + dev_info(plat_data->dev, "%s no kcontrols found", __func__); + return 0; + } + + g_kctrl_ctrl = kzalloc(g_no_of_kcontrols * sizeof(struct snd_kcontrol_new), GFP_KERNEL); + if (!g_kctrl_ctrl) { + ret = -ENOMEM; + goto EXIT; + } + for (i = 0; i < g_no_of_kcontrols; i++) { + /* Integer Type */ + if (g_kctrl_data[i].type == 0) { + g_kctrl_data[i].kcontrol.int_type.mctl.reg = i; + g_kctrl_data[i].kcontrol.int_type.mctl.rreg = i; + g_kctrl_data[i].kcontrol.int_type.mctl.shift = 0; + g_kctrl_data[i].kcontrol.int_type.mctl.rshift = 0; + g_kctrl_data[i].kcontrol.int_type.mctl.max = g_kctrl_data[i].kcontrol.int_type.count-1; + g_kctrl_data[i].kcontrol.int_type.mctl.platform_max = g_kctrl_data[i].kcontrol.int_type.count-1; + g_kctrl_data[i].kcontrol.int_type.mctl.invert = 0; + g_kctrl_data[i].kcontrol.int_type.mctl.autodisable = 0; + + g_kctrl_ctrl[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + g_kctrl_ctrl[i].name = g_kctrl_data[i].kcontrol.int_type.name; + g_kctrl_ctrl[i].info = snd_soc_info_volsw; + g_kctrl_ctrl[i].get = tas25xx_get; + g_kctrl_ctrl[i].put = tas25xx_put; + g_kctrl_ctrl[i].private_value = + (unsigned long)(&(g_kctrl_data[i].kcontrol.int_type.mctl)); + } else { /* Enum Type */ + int32_t count = g_kctrl_data[i].kcontrol.enum_type.count; + + g_kctrl_data[i].kcontrol.enum_type.tas25xx_kcontrol_enum.items = count; + g_kctrl_data[i].kcontrol.enum_type.tas25xx_kcontrol_enum.texts = + (const char * const *)g_kctrl_data[i].kcontrol.enum_type.enum_texts; + + g_kctrl_ctrl[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + g_kctrl_ctrl[i].name = g_kctrl_data[i].kcontrol.enum_type.name; + g_kctrl_ctrl[i].info = snd_soc_info_enum_double; + g_kctrl_ctrl[i].get = tas25xx_enum_get; + g_kctrl_ctrl[i].put = tas25xx_enum_put; + g_kctrl_ctrl[i].private_value = + (unsigned long)(&(g_kctrl_data[i].kcontrol.enum_type.tas25xx_kcontrol_enum)); + } + } + ret = snd_soc_add_component_controls(plat_data->codec, + g_kctrl_ctrl, g_no_of_kcontrols); +EXIT: + return ret; +} + +static int32_t tas25xx_parse_kcontrols(struct tas25xx_priv *p_tas25xx, uint8_t **inbuf) +{ + int32_t ret = 0; + int32_t size = 0; + int32_t j = 0, i = 0; + int32_t count; + struct linux_platform *plat_data = NULL; + uint8_t *buf = *inbuf; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + buf = find_block_for_channel(p_tas25xx, buf, KCNTRL_STR, ANY_CHANNEL); + if (!buf) + return -EINVAL; + + buf += HDR_STR_SZ; + size = get_block_size_noadvance(buf); + buf += 4; + dev_info(plat_data->dev, "parsing kcontrol data, total size=%d", size); + + memcpy(&g_no_of_kcontrols, buf, sizeof(g_no_of_kcontrols)); + buf += sizeof(g_no_of_kcontrols); + + dev_err(plat_data->dev, "%s: g_no_of_kcontrols %d\n", __func__, g_no_of_kcontrols); + + g_kctrl_data = kzalloc(g_no_of_kcontrols * sizeof(struct tas25xx_kcontrols), GFP_KERNEL); + if (!g_kctrl_data) { + ret = -ENOMEM; + g_no_of_kcontrols = 0; + goto EXIT; + } + + for (i = 0; i < g_no_of_kcontrols; i++) { + memcpy(&(g_kctrl_data[i].type), buf, 1); + buf += 1; + /* Integer Type */ + if (g_kctrl_data[i].type == 0) { + + g_kctrl_data[i].kcontrol.int_type.name = kasprintf(GFP_KERNEL, "%s %s", + "TAS25XX", (char *)buf); + buf += 64; + memcpy(&(g_kctrl_data[i].kcontrol.int_type.channel), buf, 1); + buf += 1; + memcpy(&(g_kctrl_data[i].kcontrol.int_type.reg), buf, 4); + buf += 4; + memcpy(&(g_kctrl_data[i].kcontrol.int_type.reg_type), buf, 1); + buf += 1; + if (g_kctrl_data[i].kcontrol.int_type.reg_type == 2) { + memcpy(&(g_kctrl_data[i].kcontrol.int_type.mask), buf, 4); + buf += 4; + } + memcpy(&(g_kctrl_data[i].kcontrol.int_type.range_min), buf, 1); + buf += 4; + memcpy(&(g_kctrl_data[i].kcontrol.int_type.range_max), buf, 4); + buf += 4; + memcpy(&(g_kctrl_data[i].kcontrol.int_type.step), buf, 4); + buf += 4; + memcpy(&(g_kctrl_data[i].kcontrol.int_type.def), buf, 4); + buf += 4; + if (s_rbin.head.version > 6) { + memcpy(&(g_kctrl_data[i].kcontrol.int_type.misc_info), buf, 4); + buf += 4; + } + + /* Assign default value to current value */ + g_kctrl_data[i].kcontrol.int_type.curr_val = g_kctrl_data[i].kcontrol.int_type.def; + dev_err(plat_data->dev, "%s: curr_kcontrol %d\n", __func__, g_kctrl_data[i].kcontrol.int_type.curr_val); + memcpy(&(g_kctrl_data[i].kcontrol.int_type.count), buf, 4); + buf += 4; + if (g_kctrl_data[i].kcontrol.int_type.reg_type == 1) { + g_kctrl_data[i].kcontrol.int_type.intdata = + kzalloc(g_kctrl_data[i].kcontrol.int_type.count * sizeof(int32_t), GFP_KERNEL); + for (j = 0; j < g_kctrl_data[i].kcontrol.int_type.count; j++) { + memcpy(&(g_kctrl_data[i].kcontrol.int_type.intdata[j]), buf, 4); + buf += 4; + } + } else { + g_kctrl_data[i].kcontrol.int_type.chardata = kzalloc(g_kctrl_data[i].kcontrol.int_type.count * sizeof(char), GFP_KERNEL); + for (j = 0; j < g_kctrl_data[i].kcontrol.int_type.count; j++) { + memcpy(&(g_kctrl_data[i].kcontrol.int_type.chardata[j]), buf, 1); + buf += 1; + } + } + } else { /* Enum type */ + g_kctrl_data[i].kcontrol.enum_type.name = kasprintf(GFP_KERNEL, "%s %s", + "TAS25XX", (char *)buf); + buf += 64; + memcpy(&(g_kctrl_data[i].kcontrol.enum_type.channel), buf, 1); + buf += 1; + memcpy(&(g_kctrl_data[i].kcontrol.enum_type.def), buf, 1); + buf += 1; + if (s_rbin.head.version > 6) { + memcpy(&(g_kctrl_data[i].kcontrol.enum_type.misc_info), buf, 4); + buf += 4; + } + g_kctrl_data[i].kcontrol.enum_type.curr_val = + g_kctrl_data[i].kcontrol.enum_type.def; + memcpy(&count, buf, 4); + buf += 4; + g_kctrl_data[i].kcontrol.enum_type.count = count; + + g_kctrl_data[i].kcontrol.enum_type.data = + kzalloc(count * sizeof(struct tas25xx_kcontrol_enum_data), GFP_KERNEL); + if (!g_kctrl_data[i].kcontrol.enum_type.data) { + ret = -ENOMEM; + goto EXIT; + } + for (j = 0; j < count; j++) { + memcpy(g_kctrl_data[i].kcontrol.enum_type.data[j].name, buf, 64); + buf += 64; + memcpy(&size, buf, 4); + g_kctrl_data[i].kcontrol.enum_type.data[j].data = buf; + buf += (size + 4); + } + + g_kctrl_data[i].kcontrol.enum_type.enum_texts = kzalloc(count * sizeof(char *), GFP_KERNEL); + if (!g_kctrl_data[i].kcontrol.enum_type.enum_texts) { + ret = -ENOMEM; + goto EXIT; + } + for (j = 0; j < count; j++) { + g_kctrl_data[i].kcontrol.enum_type.enum_texts[j] = kzalloc(64, GFP_KERNEL); + if (!g_kctrl_data[i].kcontrol.enum_type.enum_texts[j]) { + ret = -ENOMEM; + goto EXIT; + } + memcpy(g_kctrl_data[i].kcontrol.enum_type.enum_texts[j], + g_kctrl_data[i].kcontrol.enum_type.data[j].name, 64); + } + } + } +EXIT: + *inbuf = buf; + return ret; +} + +int tas_write_init_default_config_params(struct tas25xx_priv *p_tas25xx, int number_of_channels) +{ + int i; + int ret = 0; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + for (i = 0; i < number_of_channels && !ret; i++) { + ret = tas25xx_set_sample_rate(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_fmt_inv(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_fmt_mask(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_rx_slots(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_tx_slots(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_rx_bitwidth(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_rx_slotlen(p_tas25xx, i, TAS25XX_DEFAULT); + ret = tas25xx_set_tx_slotlen(p_tas25xx, i, TAS25XX_DEFAULT); + } + + return ret; +} + +int tas_write_init_config_params(struct tas25xx_priv *p_tas25xx, int number_of_channels) +{ + return tas_write_init_default_config_params(p_tas25xx, number_of_channels); +} + +static int32_t tas25xx_parse_algo_data(struct tas25xx_priv *p_tas25xx, uint8_t **inbuf) +{ + int32_t ret = 0; + struct linux_platform *plat_data = NULL; + uint8_t *buf = *inbuf; + uint32_t size; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + buf = find_block_for_channel(p_tas25xx, buf, ALGOP_STR, ANY_CHANNEL); + if (!buf) + return -EINVAL; + + buf += HDR_STR_SZ; + size = get_block_size_noadvance(buf); + buf += 4; + dev_info(plat_data->dev, "parsing algo data, total size=%d", size); + +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) + tas25xx_parse_algo_bin(p_tas25xx->ch_count, buf); +#endif /* CONFIG_TAS25XX_ALGO */ + + return ret; +} + +static void tas25xx_fw_ready(const struct firmware *pFW, void *pContext) +{ + int32_t ret; + uint32_t size; + int32_t i; + int32_t number_of_channels; + uint32_t rev_count; + uint32_t rev_id; + uint64_t file_offset; + struct tas25xx_priv *p_tas25xx = (struct tas25xx_priv *) pContext; + struct linux_platform *plat_data = NULL; + struct i2c_client *p_client = NULL; + uint8_t *bin_data = NULL; + uint8_t *mdata; + struct bin_header *hdrp; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + p_client = container_of(plat_data->dev, + struct i2c_client, dev); + + if (unlikely(!pFW) || unlikely(!pFW->data)) { + dev_err(plat_data->dev, + "firmware is not ready, %s\n", TAS25XX_BINFILE_NAME); + atomic_set(&p_tas25xx->fw_state, TAS25XX_DSP_FW_TRYLOAD); + goto EXIT; + } + + dev_info(plat_data->dev, "%s: pFW->size %d", __func__, (int32_t)pFW->size); + atomic_set(&p_tas25xx->fw_state, TAS25XX_DSP_FW_PARSE_FAIL); + + mdata = (uint8_t *)pFW->data; + if (header_check(p_tas25xx, MDATA, mdata)) { + dev_info(plat_data->dev, "%s: Found metadata, check against rev-id %d", + __func__, p_tas25xx->dev_revid); + file_offset = 0; + size = 0; + mdata += 5; /* header */ + memcpy(&rev_count, mdata, sizeof(uint32_t)); + mdata += 4; /* count */ + for (i = 0; i < rev_count; i++) { + file_offset += size; + + memcpy(&rev_id, mdata, sizeof(uint32_t)); + mdata += 4; /* id */ + memcpy(&size, mdata, sizeof(uint32_t)); + mdata += 4; /* file sz */ + /* dummy data */ + mdata += 4; + mdata += 4; + mdata += 4; + dev_info(plat_data->dev, "%s: revid 0x%x, size=%d, offset=%llu", + __func__, rev_id, size, file_offset); + if (p_tas25xx->dev_revid == rev_id) + break; + } + + if (i == rev_count) { + dev_info(plat_data->dev, + "%s: metadata parse failure...", __func__); + ret = -EINVAL; + goto EXIT; + } else { + dev_info(plat_data->dev, + "%s: using idx=%d revid 0x%x, size=%d", __func__, i, rev_id, size); + p_tas25xx->fw_data = kzalloc(size, GFP_KERNEL); + if (!p_tas25xx->fw_data) { + ret = -ENOMEM; + goto EXIT; + } + + bin_data = (uint8_t *)pFW->data; + bin_data += MDATA_HDR_SZ; + + /* first bin file */ + bin_data += (rev_count * ONE_BIN_MD_SZ) + file_offset; + + + p_tas25xx->fw_size = size; + memcpy(p_tas25xx->fw_data, bin_data, size); + } + + } else { + dev_info(plat_data->dev, "%s: metadata not found, regular fw", __func__); + p_tas25xx->fw_data = kzalloc(pFW->size, GFP_KERNEL); + if (!p_tas25xx->fw_data) { + ret = -ENOMEM; + goto EXIT; + } + p_tas25xx->fw_size = pFW->size; + memcpy(p_tas25xx->fw_data, pFW->data, pFW->size); + } + + bin_data = p_tas25xx->fw_data; + + if (!header_check(p_tas25xx, HEADER, bin_data)) { + ret = -EINVAL; + goto EXIT; + } else { + bin_data += HDR_STR_SZ; + size = get_block_size_noadvance(bin_data); + dev_info(plat_data->dev, "%s: Header size %d", __func__, size); + bin_data += 4; + } + + /* header parsing */ + hdrp = (struct bin_header *)bin_data; + s_rbin.head.version = hdrp->version; + memcpy(s_rbin.head.name, hdrp->name, 64); + s_rbin.head.timestamp = hdrp->timestamp; + s_rbin.head.size = hdrp->size; + s_rbin.head.channels = hdrp->channels; + bin_data += offsetof(struct bin_header, dev); + for (i = 0; i < s_rbin.head.channels; i++) { + s_rbin.head.dev[i].device = bin_data[0]; + s_rbin.head.dev[i].i2c_addr = bin_data[1]; + bin_data += 2; + } + s_rbin.head.iv_width = *bin_data; + bin_data++; + s_rbin.head.vbat_mon = *bin_data; + bin_data++; + s_rbin.head.features = *((uint32_t *)bin_data); + bin_data += sizeof(uint32_t); + /* end of header */ + + memcpy(&s_rbin.def_hw_params, bin_data, sizeof(struct default_hw_params)); + bin_data += sizeof(struct default_hw_params); + + /* skip dummy data */ + bin_data += (sizeof(int32_t) * 5); + + number_of_channels = s_rbin.head.channels; + + dev_info(plat_data->dev, "%s: version 0x%x", __func__, s_rbin.head.version); + dev_info(plat_data->dev, "%s: name %s", __func__, s_rbin.head.name); + dev_info(plat_data->dev, "%s: timestamp %d", __func__, s_rbin.head.timestamp); + dev_info(plat_data->dev, "%s: channels %d", __func__, number_of_channels); + + for (i = 0; i < number_of_channels; i++) { + dev_info(plat_data->dev, "%s: device_%d %d", __func__, i, + s_rbin.head.dev[i].device); + if (p_tas25xx->devs[i]->mn_addr < 0) { + dev_info(plat_data->dev, "%s: Updating the device_%d i2c address to 0x%x from invalid", + __func__, i, s_rbin.head.dev[i].i2c_addr); + p_tas25xx->devs[i]->mn_addr = s_rbin.head.dev[i].i2c_addr; + } + } + + for (i = 0; i < number_of_channels; i++) + if (p_tas25xx->devs[i]->mn_addr == p_client->addr) + break; + if (i == number_of_channels) + dev_warn(plat_data->dev, + "Atlest one address should be %d", p_client->addr); + + dev_info(plat_data->dev, "%s: IVSenseWidth %d", __func__, (int)s_rbin.head.iv_width); + dev_info(plat_data->dev, "%s: Vbat-mon %d", __func__, (int)s_rbin.head.vbat_mon); + p_tas25xx->mn_iv_width = p_tas25xx->curr_mn_iv_width = (int)s_rbin.head.iv_width; + p_tas25xx->mn_vbat = p_tas25xx->curr_mn_vbat = (int)s_rbin.head.vbat_mon; + dev_dbg(plat_data->dev, "%s: updating number of channels %d -> %d", + __func__, p_tas25xx->ch_count, number_of_channels); + p_tas25xx->ch_count = number_of_channels; + + dev_dbg(plat_data->dev, "%s: features %d", __func__, s_rbin.head.features); + dev_dbg(plat_data->dev, "%s: sample_rate %s", __func__, SampleRate[(int32_t)s_rbin.def_hw_params.sample_rate]); + dev_dbg(plat_data->dev, "%s: fmt_inv %s", __func__, FMT_INV[(int32_t)s_rbin.def_hw_params.fmt_inv]); + dev_dbg(plat_data->dev, "%s: fmt_mask %s", __func__, FMT_MASK[(int32_t)s_rbin.def_hw_params.fmt_mask]); + dev_dbg(plat_data->dev, "%s: rx_slots %s", __func__, RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_slots]); + dev_dbg(plat_data->dev, "%s: tx_slots %s", __func__, TX_SLOTS[(int32_t)s_rbin.def_hw_params.tx_slots]); + dev_dbg(plat_data->dev, "%s: rx_bitwidth %s", __func__, RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_bitwidth]); + dev_dbg(plat_data->dev, "%s: rx_slotlen %s", __func__, RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_slotlen]); + dev_dbg(plat_data->dev, "%s: tx_slotlen %s", __func__, TX_SLOTS[(int32_t)s_rbin.def_hw_params.tx_slotlen]); + + if (s_rbin.head.version > SUPPORTED_BIN_VERSION) { + dev_err(plat_data->dev, + "%s: version not compatible. Supported version <= 0x%x", __func__, + SUPPORTED_BIN_VERSION); + goto EXIT; + } + + for (i = 0; i < number_of_channels; i++) { + /* Parsing Init Params */ + ret = tas25xx_parse_init_params(p_tas25xx, &bin_data, i); + if (ret) { + dev_err(plat_data->dev, "%s: Init Parsing failed", __func__); + goto EXIT; + } + ret = tas25xx_parse_hw_params(p_tas25xx, &bin_data, i); + if (ret) { + dev_err(plat_data->dev, + "%s: HW params parsing failed, ignoring..", __func__); + } + ret = tas25xx_parse_profiles(p_tas25xx, &bin_data, i); + if (ret) { + dev_err(plat_data->dev, "%s: profiles failed", __func__); + goto EXIT; + } + ret = tas25xx_parse_interrupts(p_tas25xx, &bin_data, i); + if (ret) { + dev_info(plat_data->dev, + "%s: interrupt parsing failed, ignoring..", __func__); + } + + ret = tas25xx_parse_block_data(p_tas25xx, &bin_data, i); + if (ret) { + dev_err(plat_data->dev, "%s: block data", __func__); + goto EXIT; + } + } + + ret = tas25xx_parse_kcontrols(p_tas25xx, &bin_data); + if (ret) + dev_info(plat_data->dev, + "%s: Error while parsing the kcontrols, ignored..", __func__); + + ret = tas25xx_parse_algo_data(p_tas25xx, &bin_data); + if (ret) + dev_info(plat_data->dev, + "%s: Error while parsing the algo data, ignored..", __func__); + + dev_dbg(plat_data->dev, "%s: Firmware init complete\n", __func__); + + atomic_set(&p_tas25xx->fw_state, TAS25XX_DSP_FW_OK); + +EXIT: + atomic_set(&p_tas25xx->fw_wait_complete, 1); + wake_up(&p_tas25xx->fw_wait); + + if (pFW) + release_firmware(pFW); +} + + +int32_t tas25xx_load_firmware(struct tas25xx_priv *p_tas25xx, int max_fw_tryload_count) +{ + int32_t ret; + uint32_t retry_count = 0; + struct linux_platform *plat_data = NULL; + + g_tas25xx = p_tas25xx; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + if (!max_fw_tryload_count) + max_fw_tryload_count = DSP_FW_LOAD_NTRIES; + atomic_set(&p_tas25xx->fw_wait_complete, 0); + + do { + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, + TAS25XX_BINFILE_NAME, plat_data->dev, GFP_KERNEL, + p_tas25xx, tas25xx_fw_ready); + if (ret) { + dev_err(plat_data->dev, "request_firmware_nowait failed, err=%d", ret); + msleep(100); + } else { + dev_info(plat_data->dev, "fw load requested, trail=%d", retry_count); + /* wait for either firmware to be loaded or failed */ + ret = wait_event_interruptible(p_tas25xx->fw_wait, + atomic_read(&p_tas25xx->fw_wait_complete)); + if (ret) + dev_err(plat_data->dev, "wait failed with error %d", ret); + + /* any other state other than retry */ + if (atomic_read(&p_tas25xx->fw_state) != TAS25XX_DSP_FW_TRYLOAD) + break; + + atomic_set(&p_tas25xx->fw_wait_complete, 0); + msleep(100); + } + + retry_count++; + } while (retry_count < max_fw_tryload_count); + + if (retry_count == max_fw_tryload_count) + atomic_set(&p_tas25xx->fw_state, TAS25XX_DSP_FW_LOAD_FAIL); + + switch (atomic_read(&p_tas25xx->fw_state)) { + case TAS25XX_DSP_FW_LOAD_FAIL: + ret = -ENOENT; + break; + case TAS25XX_DSP_FW_OK: + ret = 0; + break; + case TAS25XX_DSP_FW_PARSE_FAIL: + default: + ret = -EINVAL; + break; + } + + return ret; +} + +int32_t tas25xx_create_kcontrols(struct tas25xx_priv *p_tas25xx) +{ + int32_t ret = -EINVAL; + int fw_state; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + fw_state = atomic_read(&p_tas25xx->fw_state); + if (fw_state == TAS25XX_DSP_FW_OK) { + ret = tas25xx_create_common_kcontrols(p_tas25xx); + if (ret) { + dev_err(plat_data->dev, "%s: kcontrols failed", __func__); + goto EXIT; + } + ret = tas25xx_create_profile_controls(p_tas25xx); + if (ret) { + dev_err(plat_data->dev, "%s: kcontrols failed", __func__); + goto EXIT; + } + } else { + dev_err(plat_data->dev, "%s: firmware not loaded\n", __func__); + } +EXIT: + return ret; +} + +int32_t tas25xx_set_init_params(struct tas25xx_priv *p_tas25xx, int32_t ch) +{ + int32_t ret = -EINVAL; + int fw_state; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "%s: ch-%d\n", __func__, ch); + fw_state = atomic_read(&p_tas25xx->fw_state); + if (fw_state == TAS25XX_DSP_FW_OK) + ret = tas25xx_process_block(p_tas25xx, s_rbin.init_params[ch], ch); + else + dev_err(plat_data->dev, "%s: firmware not loaded\n", __func__); + + return ret; +} + +int32_t tas25xx_set_sample_rate(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t sample_rate) +{ + int32_t ret = -EINVAL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + p_tas25xx->sample_rate = sample_rate; + + switch (sample_rate) { + case 44100: + dev_info(plat_data->dev, "%s: ch-%d SampleRate 44100\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].SampleRate[SampleRate_44100], ch); + break; + + case 96000: + dev_info(plat_data->dev, "%s: ch-%d SampleRate 96000\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].SampleRate[SampleRate_96000], ch); + break; + + case 48000: + dev_info(plat_data->dev, "%s: ch-%d SampleRate 48000\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].SampleRate[SampleRate_48000], ch); + break; + + case TAS25XX_DEFAULT: + default: + p_tas25xx->sample_rate = TAS25XX_DEFAULT; + dev_info(plat_data->dev, "%s:[default] ch-%d SampleRate %s [default]\n", __func__, ch, + SampleRate[(int32_t)s_rbin.def_hw_params.sample_rate]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].SampleRate[(int32_t)s_rbin.def_hw_params.sample_rate], ch); + break; + } + return ret; +} + +int32_t tas25xx_set_fmt_inv(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t fmt_inv) +{ + int32_t ret = -EINVAL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (fmt_inv) { + case FMT_INV_NB_NF: + dev_info(plat_data->dev, "%s: ch-%d FMT_INV_NB_NF\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].FMT_INV[FMT_INV_NB_NF], ch); + break; + case FMT_INV_IB_NF: + dev_info(plat_data->dev, "%s: ch-%d FMT_INV_IB_NF\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].FMT_INV[FMT_INV_IB_NF], ch); + break; + case FMT_INV_NB_IF: + dev_info(plat_data->dev, "%s: ch-%d FMT_INV_NB_IF\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].FMT_INV[FMT_INV_NB_IF], ch); + break; + case FMT_INV_IB_IF: + dev_info(plat_data->dev, "%s: ch-%d FMT_INV_IB_IF\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].FMT_INV[FMT_INV_IB_IF], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + FMT_INV[(int32_t)s_rbin.def_hw_params.fmt_inv]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].FMT_INV[(int32_t)s_rbin.def_hw_params.fmt_inv], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid FMT_INV %d\n", __func__, ch, + fmt_inv); + break; + } + return ret; +} + +int32_t tas25xx_set_fmt_mask(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t fmt_mask) +{ + int32_t ret = -EINVAL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (fmt_mask) { + case FMT_MASK_I2S: + dev_info(plat_data->dev, "%s: ch-%d FMT_MASK_I2S\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_I2S], ch); + break; + case FMT_MASK_DSP_A: + dev_info(plat_data->dev, "%s: ch-%d FMT_MASK_DSP_A\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_DSP_A], ch); + break; + case FMT_MASK_DSP_B: + dev_info(plat_data->dev, "%s: ch-%d FMT_MASK_DSP_B\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_DSP_B], ch); + break; + case FMT_MASK_LEFT_J: + dev_info(plat_data->dev, "%s: ch-%d FMT_MASK_LEFT_J\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].FMT_MASK[FMT_MASK_LEFT_J], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + FMT_MASK[(int32_t)s_rbin.def_hw_params.fmt_mask]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].FMT_MASK[(int32_t)s_rbin.def_hw_params.fmt_mask], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid FMT_MASK %d\n", __func__, ch, + fmt_mask); + break; + } + return ret; +} + +int32_t tas25xx_set_rx_slots(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t rx_slot) +{ + int32_t ret = -EINVAL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (rx_slot) { + case RX_SLOTS_16: + dev_info(plat_data->dev, "%s: ch-%d RX_SLOTS_16\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_SLOTS[RX_SLOTS_16], ch); + break; + case RX_SLOTS_24: + dev_info(plat_data->dev, "%s: ch-%d RX_SLOTS_24\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_SLOTS[RX_SLOTS_24], ch); + break; + case RX_SLOTS_32: + dev_info(plat_data->dev, "%s: ch-%d RX_SLOTS_32\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_SLOTS[RX_SLOTS_32], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_slots]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_slots], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid RX_SLOTS %d\n", __func__, ch, + rx_slot); + break; + } + return ret; +} + +int32_t tas25xx_set_tx_slots(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t tx_slot) +{ + int32_t ret = -EINVAL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (tx_slot) { + case TX_SLOTS_16: + dev_info(plat_data->dev, "%s: ch-%d TX_SLOTS_16\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].TX_SLOTS[TX_SLOTS_16], ch); + break; + case TX_SLOTS_24: + dev_info(plat_data->dev, "%s: ch-%d TX_SLOTS_24\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].TX_SLOTS[TX_SLOTS_24], ch); + break; + case TX_SLOTS_32: + dev_info(plat_data->dev, "%s: ch-%d TX_SLOTS_32\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].TX_SLOTS[TX_SLOTS_32], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + RX_SLOTS[(int32_t)s_rbin.def_hw_params.tx_slots]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].TX_SLOTS[(int32_t)s_rbin.def_hw_params.tx_slots], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid TX_SLOTS %d\n", __func__, ch, + tx_slot); + break; + } + return ret; +} + +int32_t tas25xx_set_rx_bitwidth(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t rx_bitwidth) +{ + int32_t ret = -EINVAL; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (rx_bitwidth) { + case RX_BITWIDTH_16: + dev_info(plat_data->dev, "%s: ch-%d RX_BITWIDTH_16\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_BITWIDTH[RX_BITWIDTH_16], ch); + break; + case RX_BITWIDTH_24: + dev_info(plat_data->dev, "%s: ch-%d RX_BITWIDTH_24\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_BITWIDTH[RX_BITWIDTH_24], ch); + break; + case RX_BITWIDTH_32: + dev_info(plat_data->dev, "%s: ch-%d RX_BITWIDTH_32\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_BITWIDTH[RX_BITWIDTH_32], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_bitwidth]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].RX_BITWIDTH[(int32_t)s_rbin.def_hw_params.rx_bitwidth], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid RX_BITWIDTH %d\n", __func__, ch, + rx_bitwidth); + break; + } + return ret; +} + +int32_t tas25xx_set_rx_slotlen(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t rx_slotlen) +{ + int32_t ret = -1; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (rx_slotlen) { + case RX_SLOTLEN_16: + dev_info(plat_data->dev, "%s: ch-%d RX_SLOTLEN_16\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_SLOTLEN[RX_SLOTLEN_16], ch); + break; + case RX_SLOTLEN_24: + dev_info(plat_data->dev, "%s: ch-%d RX_SLOTLEN_24\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_SLOTLEN[RX_SLOTLEN_24], ch); + break; + case RX_SLOTLEN_32: + dev_info(plat_data->dev, "%s: ch-%d RX_SLOTLEN_32\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].RX_SLOTLEN[RX_SLOTLEN_32], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + RX_SLOTS[(int32_t)s_rbin.def_hw_params.rx_slotlen]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].RX_SLOTLEN[(int32_t)s_rbin.def_hw_params.rx_slotlen], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid RX_SLOTLEN %d\n", __func__, ch, + rx_slotlen); + break; + } + return ret; +} + +int32_t tas25xx_set_tx_slotlen(struct tas25xx_priv *p_tas25xx, int32_t ch, int32_t tx_slotlen) +{ + int32_t ret = -1; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + switch (tx_slotlen) { + case TX_SLOTLEN_16: + dev_info(plat_data->dev, "%s: ch-%d TX_SLOTLEN_16\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].TX_SLOTLEN[TX_SLOTLEN_16], ch); + break; + case TX_SLOTLEN_24: + dev_info(plat_data->dev, "%s: ch-%d TX_SLOTLEN_24\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].TX_SLOTLEN[TX_SLOTLEN_24], ch); + break; + case TX_SLOTLEN_32: + dev_info(plat_data->dev, "%s: ch-%d TX_SLOTLEN_32\n", __func__, ch); + ret = tas25xx_process_block(p_tas25xx, s_rbin.hw_params[ch].TX_SLOTLEN[TX_SLOTLEN_32], ch); + break; + case TAS25XX_DEFAULT: + dev_info(plat_data->dev, "%s:[default] ch-%d %s\n", __func__, ch, + RX_SLOTS[(int32_t)s_rbin.def_hw_params.tx_slotlen]); + ret = tas25xx_process_block(p_tas25xx, + s_rbin.hw_params[ch].TX_SLOTLEN[(int32_t)s_rbin.def_hw_params.tx_slotlen], ch); + break; + default: + dev_info(plat_data->dev, "%s: ch-%d Invalid TX_SLOTLEN %d\n", __func__, ch, + tx_slotlen); + break; + } + return ret; +} + +int32_t tas25xx_set_pre_powerup(struct tas25xx_priv *p_tas25xx, int32_t ch) +{ + int32_t ret = 0; + struct linux_platform *plat_data = NULL; + struct tas25xx_profiles *tas_profiles_t = + (struct tas25xx_profiles *)g_profile_data_list[ch].tas_profiles; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "ch-%d %s profile %s pre_power_up\n", ch, __func__, + tas_profiles_t[g_tas25xx_profile].name); + ret = tas25xx_process_block(p_tas25xx, tas_profiles_t[g_tas25xx_profile].pre_power_up, ch); + return ret; +} + +int32_t tas25xx_set_post_powerup(struct tas25xx_priv *p_tas25xx, int32_t ch) +{ + int32_t ret = -1; + struct linux_platform *plat_data = NULL; + struct tas25xx_profiles *tas_profiles_t = + (struct tas25xx_profiles *)g_profile_data_list[ch].tas_profiles; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "ch-%d %s profile %s post_power_up\n", ch, + __func__, tas_profiles_t[g_tas25xx_profile].name); + ret = tas25xx_process_block(p_tas25xx, tas_profiles_t[g_tas25xx_profile].post_power_up, ch); + return ret; +} + +int32_t tas25xx_set_pre_powerdown(struct tas25xx_priv *p_tas25xx, int32_t ch) +{ + int32_t ret = -1; + struct linux_platform *plat_data = NULL; + struct tas25xx_profiles *tas_profiles_t = (struct tas25xx_profiles *)g_profile_data_list[ch].tas_profiles; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "ch-%d %s profile %s pre_power_down\n", + ch, __func__, tas_profiles_t[g_tas25xx_profile].name); + ret = tas25xx_process_block(p_tas25xx, tas_profiles_t[g_tas25xx_profile].pre_power_down, ch); + return ret; +} + +int32_t tas25xx_set_post_powerdown(struct tas25xx_priv *p_tas25xx, int32_t ch) +{ + int32_t ret = -1; + struct linux_platform *plat_data = NULL; + struct tas25xx_profiles *tas_profiles_t = (struct tas25xx_profiles *)g_profile_data_list[ch].tas_profiles; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "ch-%d %s profile %s post_power_down\n", + ch, __func__, tas_profiles_t[g_tas25xx_profile].name); + ret = tas25xx_process_block(p_tas25xx, tas_profiles_t[g_tas25xx_profile].post_power_down, ch); + return ret; +} + +int32_t tas25xx_remove_binfile(struct tas25xx_priv *p_tas25xx) +{ + int32_t i = 0, j = 0; + int32_t count; + + /* firmware not loaded */ + if (p_tas25xx) + atomic_set(&p_tas25xx->fw_state, TAS25XX_DSP_FW_NONE); + + if (g_profile_list) { + for (i = 0; i < g_no_of_profiles; i++) { + kfree(g_profile_list[i]); + g_profile_list[i] = NULL; + } + kfree(g_profile_list); + g_profile_list = NULL; + } + + for (i = 0; i < s_rbin.head.channels; i++) { + kfree(g_profile_data_list[i].tas_profiles); + g_profile_data_list[i].tas_profiles = NULL; + } + + if (p_tas25xx) { + for (i = 0; i < s_rbin.head.channels; i++) { + kfree(p_tas25xx->intr_data[i].intr_info); + p_tas25xx->intr_data[i].intr_info = NULL; + } + } + + if (g_kctrl_data) { + for (i = 0; i < g_no_of_kcontrols; i++) { + if (g_kctrl_data[i].type != 0) { + count = g_kctrl_data[i].kcontrol.enum_type.count; + for (j = 0; j < count; j++) { + kfree(g_kctrl_data[i].kcontrol.enum_type.enum_texts[j]); + g_kctrl_data[i].kcontrol.enum_type.enum_texts[j] = NULL; + } + kfree(g_kctrl_data[i].kcontrol.enum_type.enum_texts); + g_kctrl_data[i].kcontrol.enum_type.enum_texts = NULL; + + kfree(g_kctrl_data[i].kcontrol.enum_type.data); + g_kctrl_data[i].kcontrol.enum_type.data = NULL; + } else { + kfree(g_kctrl_data[i].kcontrol.int_type.intdata); + g_kctrl_data[i].kcontrol.int_type.intdata = NULL; + kfree(g_kctrl_data[i].kcontrol.int_type.chardata); + g_kctrl_data[i].kcontrol.int_type.chardata = NULL; + } + } + + kfree(g_kctrl_data); + g_kctrl_data = NULL; + } + + kfree(g_kctrl_ctrl); + g_kctrl_ctrl = NULL; + + kfree(p_tas25xx->fw_data); + p_tas25xx->fw_data = NULL; + + g_no_of_kcontrols = 0; + g_no_of_profiles = 0; + g_tas25xx_profile = 0; + memset(&s_rbin, 0, sizeof(s_rbin)); + + g_tas25xx = NULL; + + return 0; +} diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regmap.c b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regmap.c new file mode 100644 index 0000000000..0a6a6f54a7 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx-regmap.c @@ -0,0 +1,1121 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance 4W Smart Amplifier + * + * Copyright (C) 2016 Texas Instruments, Inc. + * + * Author: Niranjan H Y, saiprasad + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +//#define DEBUG 5 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../inc/tas25xx-ext.h" +#include "../inc/tas25xx.h" +#include "../inc/tas25xx-logic.h" +#include "../inc/tas25xx-device.h" +#include "../inc/tas25xx-codec.h" +#include "../inc/tas25xx-regmap.h" +#include "../inc/tas25xx-regbin-parser.h" +#include "../inc/tas25xx-misc.h" +#include +#if IS_ENABLED(CONFIG_TAS25XX_ALGO) +#include "../algo/inc/tas_smart_amp_v2.h" +#endif /*CONFIG_TAS25XX_ALGO*/ +/*For mixer_control implementation*/ +#define MAX_STRING 200 +#define SYS_NODE + +#define CHK_ONE_BIT_SET(x) (((x) & ((x)-1)) == 0) + +static const char *dts_tag[][2] = { + { + "ti,reset-gpio", + "ti,irq-gpio", + }, + { + "ti,reset-gpio2", + "ti,irq-gpio2" + }, + { + "ti,reset-gpio3", + "ti,irq-gpio3", + }, + { + "ti,reset-gpio4", + "ti,irq-gpio4" + } +}; + +static const char *reset_gpio_label[2] = { + "TAS25XX_RESET", "TAS25XX_RESET2" +}; + +static int ti_amp_state[MAX_CHANNELS]; +int tas25xx_get_state(uint32_t id) +{ + if (id >= MAX_CHANNELS) { + pr_err("tas25xx: invalid amp id %d\n", id); + return TAS_AMP_ERR_EINVAL; + } + + return ti_amp_state[id]; +} +EXPORT_SYMBOL_GPL(tas25xx_get_state); + +static void (*tas_i2c_err_fptr)(uint32_t); + +void tas25xx_register_i2c_error_callback(void (*i2c_err_cb)(uint32_t)) +{ + tas_i2c_err_fptr = i2c_err_cb; +} +EXPORT_SYMBOL_GPL(tas25xx_register_i2c_error_callback); + +static int s_last_i2c_error; + +static inline void tas25xx_post_i2c_err_to_platform(uint32_t i2caddr) +{ + pr_err("tas25xx: I2C err on 0x%2x, notify on cb=%d\n", + i2caddr, tas_i2c_err_fptr ? 1 : 0); + if (tas_i2c_err_fptr) + tas_i2c_err_fptr(i2caddr); +} + +int tas25xx_check_last_i2c_error_n_reset(void) +{ + int temp = s_last_i2c_error; + s_last_i2c_error = 0; + return temp; +} + +static int tas25xx_regmap_write(void *plat_data, uint32_t i2c_addr, + uint32_t reg, uint32_t value, uint32_t channel) +{ + int ret; + int retry_count = TAS25XX_I2C_RETRY_COUNT; + struct linux_platform *platform_data = + (struct linux_platform *)plat_data; + + platform_data->client->addr = i2c_addr; + while (retry_count > 0) { + ret = regmap_write(platform_data->regmap[channel], reg, + value); + if (!ret) + break; + tas25xx_post_i2c_err_to_platform(i2c_addr); + mdelay(20); + retry_count--; + } + + if (ret) + s_last_i2c_error = ret; + + return ret; +} + +static int tas25xx_regmap_bulk_write(void *plat_data, uint32_t i2c_addr, + uint32_t reg, uint8_t *pData, + uint32_t nLength, uint32_t channel) +{ + int ret; + int retry_count = TAS25XX_I2C_RETRY_COUNT; + struct linux_platform *platform_data = + (struct linux_platform *)plat_data; + + platform_data->client->addr = i2c_addr; + while (retry_count > 0) { + ret = regmap_bulk_write(platform_data->regmap[channel], reg, + pData, nLength); + if (!ret) + break; + tas25xx_post_i2c_err_to_platform(i2c_addr); + mdelay(20); + retry_count--; + } + + if (ret) + s_last_i2c_error = ret; + + return ret; +} + +static int tas25xx_regmap_read(void *plat_data, uint32_t i2c_addr, + uint32_t reg, uint32_t *value, uint32_t channel) +{ + int ret; + int retry_count = TAS25XX_I2C_RETRY_COUNT; + struct linux_platform *platform_data = + (struct linux_platform *)plat_data; + + platform_data->client->addr = i2c_addr; + while (retry_count > 0) { + ret = regmap_read(platform_data->regmap[channel], reg, + value); + if (!ret) + break; + tas25xx_post_i2c_err_to_platform(i2c_addr); + mdelay(20); + retry_count--; + } + + if (ret) + s_last_i2c_error = ret; + + return ret; +} + +static int tas25xx_regmap_bulk_read(void *plat_data, uint32_t i2c_addr, + uint32_t reg, uint8_t *pData, + uint32_t nLength, uint32_t channel) +{ + int ret; + int retry_count = TAS25XX_I2C_RETRY_COUNT; + struct linux_platform *platform_data = + (struct linux_platform *)plat_data; + + platform_data->client->addr = i2c_addr; + while (retry_count > 0) { + ret = regmap_bulk_read(platform_data->regmap[channel], reg, + pData, nLength); + if (!ret) + break; + tas25xx_post_i2c_err_to_platform(i2c_addr); + mdelay(20); + retry_count--; + } + + if (ret) + s_last_i2c_error = ret; + + return ret; +} + +static int tas25xx_regmap_update_bits(void *plat_data, uint32_t i2c_addr, + uint32_t reg, uint32_t mask, + uint32_t value, uint32_t channel) +{ + int ret; + int retry_count = TAS25XX_I2C_RETRY_COUNT; + struct linux_platform *platform_data = + (struct linux_platform *)plat_data; + + platform_data->client->addr = i2c_addr; + while (retry_count > 0) { + ret = regmap_update_bits(platform_data->regmap[channel], reg, + mask, value); + if (!ret) + break; + tas25xx_post_i2c_err_to_platform(i2c_addr); + mdelay(20); + retry_count--; + } + + if (ret) + s_last_i2c_error = ret; + + return ret; +} + +static int tas25xx_dev_read(struct tas25xx_priv *p_tas25xx, + int32_t chn, uint32_t reg, uint32_t *pValue) +{ + int ret = -EINVAL; + + mutex_lock(&p_tas25xx->dev_lock); + + /* Switch book */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, + TAS25XX_BOOK_ID(reg)); + if (ret) + goto end; + } + + ret = p_tas25xx->plat_read(p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_REG_NO_BOOK(reg), pValue, chn); + if (ret) { + pr_err("%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, ret); + } else { + pr_debug( + "%s: chn:%x:BOOK:PAGE:REG 0x%02x:0x%02x:0x%02x,0x%02x\n", + __func__, + p_tas25xx->devs[chn]->mn_addr, TAS25XX_BOOK_ID(reg), + TAS25XX_PAGE_ID(reg), + TAS25XX_PAGE_REG(reg), *pValue); + } + + /* Switch to Book-0 */ + if (TAS25XX_BOOK_ID(reg)) + ret = tas25xx_change_book(p_tas25xx, chn, 0); + +end: + mutex_unlock(&p_tas25xx->dev_lock); + return ret; +} + +static int tas25xx_dev_write(struct tas25xx_priv *p_tas25xx, int32_t chn, + uint32_t reg, uint32_t value) +{ + int ret = -EINVAL; + + mutex_lock(&p_tas25xx->dev_lock); + + /* Switch book */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, + TAS25XX_BOOK_ID(reg)); + if (ret) + goto end; + } + + ret = p_tas25xx->plat_write( + p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_REG_NO_BOOK(reg), value, chn); + if (ret) { + pr_err( + "%s, ERROR, L=%u, chn=0x%02x, E=%d\n", + __func__, __LINE__, + p_tas25xx->devs[chn]->mn_addr, ret); + } else { + pr_debug( + "%s: %u: chn:0x%02x:BOOK:PAGE:REG 0x%02x:0x%02x:0x%02x, VAL: 0x%02x\n", + __func__, __LINE__, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_BOOK_ID(reg), + TAS25XX_PAGE_ID(reg), + TAS25XX_PAGE_REG(reg), value); + + } + + /* Switch book to 0 */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, 0); + if (ret) + goto end; + } + +end: + mutex_unlock(&p_tas25xx->dev_lock); + return ret; +} + +static int tas25xx_dev_bulk_write(struct tas25xx_priv *p_tas25xx, + int32_t chn, uint32_t reg, uint8_t *p_data, uint32_t n_length) +{ + int ret = -EINVAL; + + mutex_lock(&p_tas25xx->dev_lock); + + /* Switch book */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, + TAS25XX_BOOK_ID(reg)); + if (ret) + goto end; + } + + ret = p_tas25xx->plat_bulk_write( + p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_REG_NO_BOOK(reg), + p_data, n_length, chn); + if (ret) { + pr_err( + "%s, ERROR, L=%u, chn=0x%02x: E=%d\n", + __func__, __LINE__, + p_tas25xx->devs[chn]->mn_addr, ret); + } else { + pr_debug( + "%s: chn%x:BOOK:PAGE:REG 0x%02x:0x%02x:0x%02x, len: %u\n", + __func__, p_tas25xx->devs[chn]->mn_addr, + TAS25XX_BOOK_ID(reg), + TAS25XX_PAGE_ID(reg), + TAS25XX_PAGE_REG(reg), n_length); + } + + /* Switch book to 0*/ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, 0); + if (ret) + goto end; + } + +end: + mutex_unlock(&p_tas25xx->dev_lock); + return ret; +} + +static int tas25xx_dev_bulk_read(struct tas25xx_priv *p_tas25xx, + int32_t chn, uint32_t reg, uint8_t *p_data, uint32_t n_length) +{ + int ret; + + mutex_lock(&p_tas25xx->dev_lock); + + /* Switch book */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, + TAS25XX_BOOK_ID(reg)); + if (ret) + goto end; + } + + ret = p_tas25xx->plat_bulk_read(p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, TAS25XX_REG_NO_BOOK(reg), + p_data, n_length, chn); + if (ret) { + pr_err("%s, ERROR, L=%d, E=%d\n", __func__, __LINE__, ret); + } else { + pr_debug( + "%s: chn%x:BOOK:PAGE:REG %u:%u:%u, len: 0x%02x\n", + __func__, p_tas25xx->devs[chn]->mn_addr, + TAS25XX_BOOK_ID(reg), TAS25XX_PAGE_ID(reg), + TAS25XX_PAGE_REG(reg), n_length); + } + + /* Switch book to 0 */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, 0); + if (ret) + goto end; + } + +end: + mutex_unlock(&p_tas25xx->dev_lock); + return ret; +} + +static int tas25xx_dev_update_bits(struct tas25xx_priv *p_tas25xx, + int32_t chn, uint32_t reg, uint32_t mask, uint32_t value) +{ + int ret; + + mutex_lock(&p_tas25xx->dev_lock); + + /* Switch book */ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, + TAS25XX_BOOK_ID(reg)); + if (ret) + goto end; + } + + ret = p_tas25xx->plat_update_bits( + p_tas25xx->platform_data, + p_tas25xx->devs[chn]->mn_addr, + TAS25XX_REG_NO_BOOK(reg), + mask, value, chn); + if (ret) { + pr_err("%s, ERROR, L=%u, chn=0x%02x: E=%d\n", __func__, __LINE__, + p_tas25xx->devs[chn]->mn_addr, ret); + } else { + pr_debug("%s: chn%x:BOOK:PAGE:REG 0x%02x:0x%02x:0x%02x, mask: 0x%02x, val: 0x%02x\n", + __func__, p_tas25xx->devs[chn]->mn_addr, + TAS25XX_BOOK_ID(reg), TAS25XX_PAGE_ID(reg), TAS25XX_PAGE_REG(reg), mask, value); + } + + /* Switch book to 0*/ + if (TAS25XX_BOOK_ID(reg)) { + ret = tas25xx_change_book(p_tas25xx, chn, 0); + if (ret) + goto end; + } + +end: + mutex_unlock(&p_tas25xx->dev_lock); + return ret; +} + +/* + * consider all the registers as volatile, + * this list is not known to the driver at the time of regmap init + */ +static bool tas25xx_volatile(struct device *dev, unsigned int reg) +{ + return true; +} + +/* + * consider all the registers as writable as + * this list is not known to the driver at the time of regmap init + */ +static bool tas25xx_writeable(struct device *dev, unsigned int reg) +{ + return true; +} + +struct regmap_range_cfg tas25xx_ranges[] = { + { + .name = "tas25xx range", + .range_min = 0, + .range_max = 256 * 128, + .selector_reg = TAS25XX_PAGECTL_REG, + .selector_mask = 0xFF, + .selector_shift = 0, + .window_start = 0, + .window_len = 128, + }, +}; + +static const struct reg_default tas25xx_reg_defaults[] = { + { TAS25XX_PAGECTL_REG, 0x00 }, +}; + +static const struct regmap_config tas25xx_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = tas25xx_writeable, + .volatile_reg = tas25xx_volatile, + .reg_defaults = tas25xx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas25xx_reg_defaults), + .max_register = 256 * 128, + .ranges = tas25xx_ranges, + .num_ranges = ARRAY_SIZE(tas25xx_ranges), + .cache_type = REGCACHE_RBTREE, +}; + +#define TEST_HW_RESET 0 + +static void tas25xx_hw_reset(struct tas25xx_priv *p_tas25xx) +{ + struct linux_platform *plat_data = NULL; + int i = 0; +#if TEST_HW_RESET + uint32_t regval_b = 0; + uint32_t regval_c = 0; + uint32_t regval_d = 0; +#endif + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + dev_info(plat_data->dev, "Before HW reset\n"); +#if TEST_HW_RESET + p_tas25xx->update_bits(p_tas25xx, 0, 0xb, 0x40, 0x40); + p_tas25xx->update_bits(p_tas25xx, 0, 0xb, 0x3F, 0x07); + + p_tas25xx->update_bits(p_tas25xx, 0, 0xc, 0x40, 0x40); + p_tas25xx->update_bits(p_tas25xx, 0, 0xc, 0x3F, 0x08); + + p_tas25xx->update_bits(p_tas25xx, 0, 0xd, 0x40, 0x40); + p_tas25xx->update_bits(p_tas25xx, 0, 0xd, 0x3F, 0x09); + + dev_info(plat_data->dev, "----START----\n"); + + p_tas25xx->read(p_tas25xx, 0, 0xb, ®val_b); + p_tas25xx->read(p_tas25xx, 0, 0xc, ®val_c); + p_tas25xx->read(p_tas25xx, 0, 0xd, ®val_d); + + dev_info(plat_data->dev, "%02x %02x %02x", + regval_b, regval_c, regval_d); +#endif + + for (i = 0; i < p_tas25xx->ch_count; i++) { + if (gpio_is_valid(p_tas25xx->devs[i]->reset_gpio)) { + dev_info(plat_data->dev, "GPIO is valid %d\n", + p_tas25xx->devs[i]->reset_gpio); + gpio_direction_output(p_tas25xx->devs[i]->reset_gpio, 0); + } else { + dev_dbg(plat_data->dev, "GPIO is not valid %d\n", + p_tas25xx->devs[i]->reset_gpio); + } + } + msleep(20); + + for (i = 0; i < p_tas25xx->ch_count; i++) { + if (gpio_is_valid(p_tas25xx->devs[i]->reset_gpio)) + gpio_direction_output(p_tas25xx->devs[i]->reset_gpio, 1); + p_tas25xx->devs[i]->mn_current_book = -1; + } + usleep_range(10000, 10100); + + dev_info(plat_data->dev, "After HW reset\n"); + +#if TEST_HW_RESET + p_tas25xx->read(p_tas25xx, 0, 0xb, ®val_b); + p_tas25xx->read(p_tas25xx, 0, 0xc, ®val_c); + p_tas25xx->read(p_tas25xx, 0, 0xd, ®val_d); + + dev_info(plat_data->dev, "%02x %02x %02x", + regval_b, regval_c, regval_d); + dev_info(plat_data->dev, "---- END ----\n"); +#endif + + dev_info(plat_data->dev, "%s exit\n", __func__); +} + +static void tas25xx_enable_irq(struct tas25xx_priv *p_tas25xx) +{ + struct irq_desc *desc = NULL; + struct linux_platform *plat_data = NULL; + int i = 0; + int irq_no; + int irq_gpio; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + for (i = 0; i < p_tas25xx->ch_count; i++) { + irq_gpio = p_tas25xx->devs[i]->irq_gpio; + if (!gpio_is_valid(irq_gpio)) + continue; + + if (p_tas25xx->irq_enabled[i] == 0) { + irq_no = p_tas25xx->devs[i]->irq_no; + desc = irq_data_to_desc(irq_get_irq_data(irq_no)); + if (desc && desc->depth > 0) { + enable_irq(irq_no); + dev_info(plat_data->dev, "irq_no=%d(gpio=%d), enabled irq", + irq_no, irq_gpio); + p_tas25xx->irq_enabled[i] = 1; + } else if (desc) { + p_tas25xx->irq_enabled[i] = 1; + dev_info(plat_data->dev, "irq_no=%d already enabled", irq_no); + } else { + dev_info(plat_data->dev, "failed to get descritptor"); + } + } else { + dev_info(plat_data->dev, "GPIO %d, previous IRQ status=%s", + irq_gpio, p_tas25xx->irq_enabled[i] ? "Enabled" : "Disabled"); + } + } +} + +static void tas25xx_disable_irq(struct tas25xx_priv *p_tas25xx) +{ + int i = 0; + int irq_gpio; + int irq_no; + + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + for (i = 0; i < p_tas25xx->ch_count; i++) { + irq_gpio = p_tas25xx->devs[i]->irq_gpio; + irq_no = p_tas25xx->devs[i]->irq_no; + if (!gpio_is_valid(irq_gpio)) + continue; + if (p_tas25xx->irq_enabled[i] == 1) { + dev_info(plat_data->dev, "irq_no=%d(gpio=%d), disabling IRQ", irq_no, irq_gpio); + disable_irq_nosync(irq_no); + p_tas25xx->irq_enabled[i] = 0; + } + } +} + +static void irq_work_routine(struct work_struct *work) +{ + struct tas25xx_priv *p_tas25xx = + container_of(work, struct tas25xx_priv, irq_work.work); + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + dev_info(plat_data->dev, "%s\n", __func__); + + /* Codec Lock Hold*/ + mutex_lock(&p_tas25xx->codec_lock); + + if (plat_data->mb_runtime_suspend) { + pr_info("%s, Runtime Suspended\n", __func__); + } else { + /*Logical Layer IRQ function, return is ignored*/ + tas25xx_irq_work_func(p_tas25xx); + } + + /* Codec Lock Release*/ + mutex_unlock(&p_tas25xx->codec_lock); + + return; +} + +static void init_work_routine(struct work_struct *work) +{ + int ret, chn; + struct tas_device *dev_tas25xx = + container_of(work, struct tas_device, init_work.work); + struct tas25xx_priv *p_tas25xx = dev_tas25xx->prv_data; + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + chn = dev_tas25xx->channel_no; + + mutex_lock(&p_tas25xx->codec_lock); + + if (p_tas25xx->m_power_state == TAS_POWER_ACTIVE) { + tas25xx_init_work_func(p_tas25xx, dev_tas25xx); + ret = tas25xx_update_kcontrol_data(p_tas25xx, + KCNTR_POST_POWERUP, 1 << chn); + if (ret) + dev_err(plat_data->dev, + "%s error during post powerup kcontrol update", __func__); + } else { + dev_info(plat_data->dev, + "skipping init work as the device state was changed"); + } + + mutex_unlock(&p_tas25xx->codec_lock); +} + +static int tas25xx_runtime_suspend(struct tas25xx_priv *p_tas25xx) +{ + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + dev_dbg(plat_data->dev, "%s\n", __func__); + + plat_data->mb_runtime_suspend = true; + + if (delayed_work_pending(&p_tas25xx->irq_work)) { + dev_dbg(plat_data->dev, "cancel IRQ work\n"); + cancel_delayed_work(&p_tas25xx->irq_work); + } + + return 0; +} + +static int tas25xx_runtime_resume(struct tas25xx_priv *p_tas25xx) +{ + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + dev_dbg(plat_data->dev, "%s\n", __func__); + + plat_data->mb_runtime_suspend = false; + + return 0; +} + +static int tas25xx_pm_suspend(struct device *dev) +{ + struct tas25xx_priv *p_tas25xx = dev_get_drvdata(dev); + struct linux_platform *plat_data = NULL; + int i = 0; + + if (!p_tas25xx) { + pr_err("tas25xx:%s drvdata is NULL\n", __func__); + return -EINVAL; + } + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + if (!plat_data) { + pr_err("tas25xx:%s plat_data is NULL\n", __func__); + return -EINVAL; + } + + /* Codec Lock Hold*/ + mutex_lock(&p_tas25xx->codec_lock); + plat_data->i2c_suspend = true; + /* Only cache register writes and no actual I2C writes */ + for (i = 0; i < p_tas25xx->ch_count; i++) + regcache_cache_only(plat_data->regmap[i], true); + tas25xx_runtime_suspend(p_tas25xx); + /* Codec Lock Release*/ + mutex_unlock(&p_tas25xx->codec_lock); + return 0; +} + +static int tas25xx_pm_resume(struct device *dev) +{ + struct tas25xx_priv *p_tas25xx = dev_get_drvdata(dev); + struct linux_platform *plat_data = NULL; + int i = 0; + + if (!p_tas25xx) { + pr_err("tas25xx:%s drvdata is NULL\n", __func__); + return -EINVAL; + } + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + if (!plat_data) { + pr_err("tas25xx:%s plat_data is NULL\n", __func__); + return -EINVAL; + } + + /* Codec Lock Hold*/ + mutex_lock(&p_tas25xx->codec_lock); + plat_data->i2c_suspend = false; + + /* Restore all I2C writes happend during supended state */ + for (i = 0; i < p_tas25xx->ch_count; i++) { + regcache_cache_only(plat_data->regmap[i], false); + regcache_sync(plat_data->regmap[i]); + } + + tas25xx_runtime_resume(p_tas25xx); + /* Codec Lock Release*/ + mutex_unlock(&p_tas25xx->codec_lock); + return 0; +} + + +void schedule_init_work(struct tas25xx_priv *p_tas25xx, int ch) +{ + /* actual init work will decide the delay */ + schedule_delayed_work(&p_tas25xx->devs[ch]->init_work, + msecs_to_jiffies(0)); +} + +void cancel_init_work(struct tas25xx_priv *p_tas25xx, int ch) +{ + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + cancel_delayed_work(&p_tas25xx->devs[ch]->init_work); + +} + +static int tas25xx_parse_dt(struct device *dev, + struct tas25xx_priv *p_tas25xx) +{ + + struct device_node *np = dev->of_node; + struct linux_platform *plat_data = NULL; + int rc = 0, i = 0; + uint32_t reset_gpios = 0; + uint32_t irq_gpios = 0; + uint8_t buf[32]; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + + rc = of_property_read_u32(np, "ti,max-channels", &p_tas25xx->ch_count); + if (rc) { + dev_err(plat_data->dev, + "Looking up %s property in node %s, err=%d\n, using default=%d", + "ti,max-channels", np->full_name, rc, MAX_CHANNELS); + p_tas25xx->ch_count = MAX_CHANNELS; + } else { + dev_dbg(plat_data->dev, "ti,max-channels=%d", + p_tas25xx->ch_count); + } + + if (p_tas25xx->ch_count > MAX_CHANNELS) { + dev_err(plat_data->dev, + "ti,max-channels count exceeds limit(%d)", MAX_CHANNELS); + goto EXIT; + } + + /*the device structures array*/ + p_tas25xx->devs = kmalloc(p_tas25xx->ch_count * sizeof(struct tas_device *), + GFP_KERNEL); + for (i = 0; i < p_tas25xx->ch_count; i++) { + p_tas25xx->devs[i] = kmalloc(sizeof(struct tas_device), + GFP_KERNEL); + if (!p_tas25xx->devs[i]) { + rc = -ENOMEM; + break; + } + + p_tas25xx->devs[i]->reset_gpio = of_get_named_gpio(np, dts_tag[i][0], 0); + if (!gpio_is_valid(p_tas25xx->devs[i]->reset_gpio)) { + dev_dbg(plat_data->dev, "Looking up %s property in node %s failed %d\n", + dts_tag[i][0], np->full_name, p_tas25xx->devs[i]->reset_gpio); + } else { + dev_info(plat_data->dev, "%s = %d", dts_tag[i][0], + p_tas25xx->devs[i]->reset_gpio); + reset_gpios |= 1 << i; + } + + p_tas25xx->devs[i]->irq_gpio = of_get_named_gpio(np, dts_tag[i][1], 0); + if (!gpio_is_valid(p_tas25xx->devs[i]->irq_gpio)) { + dev_dbg(plat_data->dev, + "Looking up %s property in node %s failed %d\n", + dts_tag[i][1], np->full_name, + p_tas25xx->devs[i]->irq_gpio); + } else { + dev_info(plat_data->dev, "%s = %d", dts_tag[i][1], + p_tas25xx->devs[i]->irq_gpio); + irq_gpios |= 1 << i; + } + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + snprintf(buf, 32, "ti,channel-%d", i); + rc = of_property_read_u32(np, buf, + &p_tas25xx->devs[i]->mn_addr); + if (rc) { + dev_warn(plat_data->dev, + "Looking up %s property in node %s failed %d\n", + buf, np->full_name, rc); + p_tas25xx->devs[i]->mn_addr = -1; + rc = 0; + } else { + dev_info(plat_data->dev, "using %s = 0x%2x\n", buf, + p_tas25xx->devs[i]->mn_addr); + } + } + + if (!irq_gpios) + dev_err(plat_data->dev, "IRQ GPIOs not found"); + else if (CHK_ONE_BIT_SET(irq_gpios)) + dev_info(plat_data->dev, "Using commong IRQ gpio"); + + if (!reset_gpios) + dev_err(plat_data->dev, "Reset GPIOs not found"); + else if (CHK_ONE_BIT_SET(reset_gpios)) + dev_info(plat_data->dev, "Using commong reset gpio"); + +EXIT: + return rc; +} + +static int tas25xx_i2c_probe(struct i2c_client *p_client, + const struct i2c_device_id *id) +{ + struct tas25xx_priv *p_tas25xx; + struct linux_platform *plat_data; + int ret = 0; + int i = 0; + int errcnt = 0; + + dev_info(&p_client->dev, "Driver Tag: %s, addr=0x%2x\n", + TAS25XX_DRIVER_TAG, p_client->addr); + + p_tas25xx = devm_kzalloc(&p_client->dev, + sizeof(struct tas25xx_priv), GFP_KERNEL); + if (p_tas25xx == NULL) { + ret = -ENOMEM; + goto err; + } + + plat_data = (struct linux_platform *)devm_kzalloc(&p_client->dev, + sizeof(struct linux_platform), GFP_KERNEL); + if (plat_data == NULL) { + ret = -ENOMEM; + goto err; + } + + p_tas25xx->platform_data = plat_data; + /* REGBIN related */ + //p_tas25xx->profile_cfg_id = -1; + + /* high level api */ + p_tas25xx->read = tas25xx_dev_read; + p_tas25xx->write = tas25xx_dev_write; + p_tas25xx->bulk_read = tas25xx_dev_bulk_read; + p_tas25xx->bulk_write = tas25xx_dev_bulk_write; + p_tas25xx->update_bits = tas25xx_dev_update_bits; + + /* low level api */ + p_tas25xx->plat_write = tas25xx_regmap_write; + p_tas25xx->plat_read = tas25xx_regmap_read; + p_tas25xx->plat_bulk_write = tas25xx_regmap_bulk_write; + p_tas25xx->plat_bulk_read = tas25xx_regmap_bulk_read; + p_tas25xx->plat_update_bits = tas25xx_regmap_update_bits; + + plat_data->client = p_client; + plat_data->dev = &p_client->dev; + i2c_set_clientdata(p_client, p_tas25xx); + dev_set_drvdata(&p_client->dev, p_tas25xx); + + mutex_init(&p_tas25xx->dev_lock); + p_tas25xx->hw_reset = tas25xx_hw_reset; + p_tas25xx->enable_irq = tas25xx_enable_irq; + p_tas25xx->disable_irq = tas25xx_disable_irq; + p_tas25xx->schedule_init_work = schedule_init_work; + p_tas25xx->cancel_init_work = cancel_init_work; +#if IS_ENABLED(CODEC_PM) + plat_data->runtime_suspend = tas25xx_runtime_suspend; + plat_data->runtime_resume = tas25xx_runtime_resume; +#endif + p_tas25xx->m_power_state = TAS_POWER_SHUTDOWN; + p_tas25xx->ti_amp_state = ti_amp_state; + + if (p_client->dev.of_node) { + ret = tas25xx_parse_dt(&p_client->dev, p_tas25xx); + if (ret) + goto err; + } + + for (i = 0; i < p_tas25xx->ch_count; i++) { + if (gpio_is_valid(p_tas25xx->devs[i]->reset_gpio)) { + ret = gpio_request(p_tas25xx->devs[i]->reset_gpio, + reset_gpio_label[i]); + if (ret) { + dev_err(plat_data->dev, + "%s: Failed to request gpio %d\n", + __func__, + p_tas25xx->devs[i]->reset_gpio); + ret = -EINVAL; + goto err; + } else { + /* make the gpio output always one */ + dev_info(plat_data->dev, "%s setting reset gpio %d always high, default", + __func__, p_tas25xx->devs[i]->reset_gpio); + gpio_direction_output(p_tas25xx->devs[i]->reset_gpio, 1); + } + + } + plat_data->regmap[i] = devm_regmap_init_i2c(p_client, + &tas25xx_i2c_regmap); + if (IS_ERR(plat_data->regmap[i])) { + ret = PTR_ERR(plat_data->regmap[i]); + dev_err(&p_client->dev, + "Failed to allocate register map: %d channel %d\n", + ret, i); + goto err; + } + } + + ret = tas25xx_register_device(p_tas25xx); + if (ret) + goto err; + + ret = 0; + for (i = 0; i < p_tas25xx->ch_count; i++) { + if (p_tas25xx->devs[i]->mn_addr != -1) { + /* all i2c addressess registered must be accessible */ + ret = p_tas25xx->read(p_tas25xx, i, + TAS25XX_REVID_REG, &p_tas25xx->dev_revid); + if (!ret) { + dev_info(&p_client->dev, + "successfully read revid 0x%x\n", p_tas25xx->dev_revid); + ti_amp_state[i] = TAS_AMP_STATE_BOOT_SUCCESS; + } else { + dev_err(&p_client->dev, + "Unable to read rev id, i2c failure\n"); + ti_amp_state[i] = TAS_AMP_ERR_I2C; + errcnt++; + } + } + } + + /* consider i2c error as fatal */ + if (errcnt > 0) { + ret = -ENOENT; + goto err; + } + + for (i = p_tas25xx->ch_count; i < MAX_CHANNELS; i++) + ti_amp_state[i] = TAS_AMP_ERR_EINVAL; + + init_waitqueue_head(&p_tas25xx->fw_wait); + + init_waitqueue_head(&p_tas25xx->dev_init_wait); + atomic_set(&p_tas25xx->dev_init_status, 0); + + INIT_DELAYED_WORK(&p_tas25xx->irq_work, irq_work_routine); + for (i = 0; i < p_tas25xx->ch_count; i++) { + p_tas25xx->devs[i]->prv_data = p_tas25xx; + INIT_DELAYED_WORK(&p_tas25xx->devs[i]->init_work, + init_work_routine); + } + + mutex_init(&p_tas25xx->codec_lock); + ret = tas25xx_register_codec(p_tas25xx); + if (ret) { + dev_err(plat_data->dev, + "register codec failed, %d\n", ret); + goto err; + } + + mutex_init(&p_tas25xx->file_lock); + ret = tas25xx_register_misc(p_tas25xx); + if (ret) { + dev_err(plat_data->dev, "register codec failed %d\n", + ret); + goto err; + } + +err: + return ret; +} + +static void tas25xx_i2c_remove(struct i2c_client *p_client) +{ + int i = 0; + struct tas25xx_priv *p_tas25xx = i2c_get_clientdata(p_client); + struct linux_platform *plat_data = NULL; + + plat_data = (struct linux_platform *) p_tas25xx->platform_data; + dev_info(plat_data->dev, "%s\n", __func__); + + /*Cancel all the work routine before exiting*/ + for (i = 0; i < p_tas25xx->ch_count; i++) + cancel_delayed_work_sync(&p_tas25xx->devs[i]->init_work); + + cancel_delayed_work_sync(&p_tas25xx->irq_work); + cancel_delayed_work_sync(&p_tas25xx->dc_work); + + tas25xx_deregister_codec(p_tas25xx); + mutex_destroy(&p_tas25xx->codec_lock); + + tas25xx_deregister_misc(p_tas25xx); + mutex_destroy(&p_tas25xx->file_lock); + + mutex_destroy(&p_tas25xx->dev_lock); + + for (i = 0; i < p_tas25xx->ch_count; i++) { + if (gpio_is_valid(p_tas25xx->devs[i]->reset_gpio)) + gpio_free(p_tas25xx->devs[i]->reset_gpio); + if (gpio_is_valid(p_tas25xx->devs[i]->irq_gpio)) + gpio_free(p_tas25xx->devs[i]->irq_gpio); + kfree(p_tas25xx->devs[i]); + } + + kfree(p_tas25xx->devs); +} + + +static const struct i2c_device_id tas25xx_i2c_id[] = { + { "tas25xx", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, tas25xx_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id tas25xx_of_match[] = { + { .compatible = "ti,tas25xx" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tas25xx_of_match); +#endif + +static const struct dev_pm_ops tas25xx_pm_ops = { + .suspend = tas25xx_pm_suspend, + .resume = tas25xx_pm_resume +}; + +static struct i2c_driver tas25xx_i2c_driver = { + .driver = { + .name = "tas25xx", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = of_match_ptr(tas25xx_of_match), +#endif + .pm = &tas25xx_pm_ops, + }, + .probe = tas25xx_i2c_probe, + .remove = tas25xx_i2c_remove, + .id_table = tas25xx_i2c_id, +}; + +module_i2c_driver(tas25xx_i2c_driver); + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TAS25XX I2C Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx.c b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx.c new file mode 100644 index 0000000000..037c860519 --- /dev/null +++ b/qcom/opensource/audio-kernel/asoc/codecs/tas25xx/src/tas25xx.c @@ -0,0 +1,212 @@ +/* + * ALSA SoC Texas Instruments TAS25XX High Performance 4W Smart Amplifier + * + * Copyright (C) 2022 Texas Instruments, Inc. + * + * Author: Niranjan H Y, Vijeth P O + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#include "../inc/tas25xx.h" +#include "../inc/tas25xx-device.h" +#include "../inc/tas25xx-regbin-parser.h" +#include "../inc/tas25xx-regmap.h" + +#define TAS25XX_MDELAY 0xFFFFFFFE +#define TAS25XX_MSLEEP 0xFFFFFFFD +#define TAS25XX_IVSENSER_ENABLE 1 +#define TAS25XX_IVSENSER_DISABLE 0 + +#define STR_RXBITS_SZ 5 +#define STR_16BIT "16BIT" +#define STR_24BIT "24BIT" +#define STR_32BIT "32BIT" + +#ifndef UINT64_MAX +#define UINT64_MAX ((u64)(~((u64)0))) +#endif + +#ifndef UINT32_MAX +#define UINT32_MAX ((u32)(~((u32)0))) +#endif + +int tas25xx_rx_set_bitwidth(struct tas25xx_priv *p_tas25xx, + int bitwidth, int ch) +{ + int ret; + int32_t i; + uint32_t size, blk_count, sublk_sz; + uint8_t *rx_data = p_tas25xx->block_op_data[ch].rx_fmt_data; + uint8_t *fmt; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + + ret = 0; + size = *((uint32_t *)rx_data); + rx_data += sizeof(uint32_t); + blk_count = *((uint32_t *)rx_data); + rx_data += sizeof(uint32_t); + + switch (bitwidth) { + case 16: + fmt = STR_16BIT; + break; + + case 24: + fmt = STR_24BIT; + break; + + case 32: + fmt = STR_32BIT; + break; + + default: + fmt = NULL; + dev_info(plat_data->dev, + "Not supported params bitwidth %d", + bitwidth); + break; + } + + if (!fmt) + return -EINVAL; + + for (i = 0; i < blk_count; i++) { + if (memcmp(fmt, rx_data, STR_RXBITS_SZ) == 0) { + rx_data += STR_RXBITS_SZ; + break; + } else { + rx_data += STR_RXBITS_SZ; + sublk_sz = *((uint32_t *)rx_data); + rx_data += sizeof(uint32_t); + rx_data += sublk_sz; + } + } + + if (i < blk_count) + ret = tas25xx_process_block(p_tas25xx, rx_data, ch); + else + ret = -EINVAL; + + if (ret == 0) + p_tas25xx->mn_rx_width = bitwidth; + + return ret; +} + +int tas_dev_interrupt_clear(struct tas25xx_priv *p_tas25xx, int chn) +{ + if (p_tas25xx->intr_data[chn].buf_intr_clear) + return tas25xx_process_block(p_tas25xx, + p_tas25xx->intr_data[chn].buf_intr_clear, chn); + + return 0; +} + +int tas_dev_interrupt_enable(struct tas25xx_priv *p_tas25xx, int chn) +{ + p_tas25xx->devs[chn]->irq_count = 0; + + if (p_tas25xx->intr_data[chn].buf_intr_enable) + return tas25xx_process_block(p_tas25xx, + p_tas25xx->intr_data[chn].buf_intr_enable, chn); + + return 0; +} + +int tas_dev_interrupt_disable(struct tas25xx_priv *p_tas25xx, int chn) +{ + if (p_tas25xx->intr_data[chn].buf_intr_disable) + return tas25xx_process_block(p_tas25xx, + p_tas25xx->intr_data[chn].buf_intr_disable, chn); + + return 0; +} + +int tas_dev_interrupt_read(struct tas25xx_priv *p_tas25xx, int chn, int *type) +{ + int32_t ret = 0; + int32_t i; + int32_t reg = -1; + int32_t intr_detected = 0; + uint32_t value = 0; + int32_t powered_up = 0; + + struct tas25xx_intr_info *intr_info; + struct linux_platform *plat_data = + (struct linux_platform *) p_tas25xx->platform_data; + struct tas25xx_interrupts *intr_data = &p_tas25xx->intr_data[chn]; + + powered_up = is_power_up_state(p_tas25xx->m_power_state); + + for (i = 0; i < intr_data->count; i++) { + intr_info = &intr_data->intr_info[i]; + if (!powered_up && intr_info->is_clock_based) { + /* ignore clock based interrupt during power off state */ + dev_dbg(plat_data->dev, + "INTR: not checking for %s, reason: not active state", + intr_info->name); + continue; + } + + if ((reg != intr_info->reg) || ret) { + reg = intr_info->reg; + ret = p_tas25xx->read(p_tas25xx, chn, reg, &value); + if (!ret) + dev_err(plat_data->dev, + "INTR: ch=%d reg=0x%2x(%d), value=0x%2x(%d)", + chn, reg, reg, value, value); + } else { + dev_dbg(plat_data->dev, "INTR: skipping reading reg = %d", + intr_info->reg); + } + if (ret) { + dev_err(plat_data->dev, + "INTR: Error reading the interrupt reg=%d, err=%d", reg, ret); + } else { + if (value & intr_info->mask) { + if (powered_up && intr_info->is_clock_based) + *type |= INTERRUPT_TYPE_CLOCK_BASED; + else + *type |= INTERRUPT_TYPE_NON_CLOCK_BASED; + dev_err(plat_data->dev, "INTR: Detected, ch=%d, intr=%s", + chn, intr_info->name); + intr_info->detected = 1; + if (intr_info->count < UINT32_MAX) + intr_info->count++; + if (intr_info->count_persist < UINT64_MAX) + intr_info->count_persist++; + intr_detected |= 1; + } + } + } + + return intr_detected; +} + +int tas25xx_set_power_mute(struct tas25xx_priv *p_tas25xx, int ch) +{ + return tas25xx_process_block(p_tas25xx, + p_tas25xx->block_op_data[ch].mute, ch); +} + +int tas25xx_software_reset(struct tas25xx_priv *p_tas25xx, int ch) +{ + int ret; + + ret = tas25xx_process_block(p_tas25xx, + p_tas25xx->block_op_data[ch].sw_reset, ch); + + p_tas25xx->devs[ch]->mn_current_book = -1; + + usleep_range(10000, 10100); + + return ret; +} diff --git a/qcom/opensource/audio-kernel/asoc/msm_common.c b/qcom/opensource/audio-kernel/asoc/msm_common.c index db6876a8e2..49429ca036 100644 --- a/qcom/opensource/audio-kernel/asoc/msm_common.c +++ b/qcom/opensource/audio-kernel/asoc/msm_common.c @@ -403,6 +403,11 @@ int msm_common_snd_hw_params(struct snd_pcm_substream *substream, struct msm_common_pdata *pdata = msm_common_get_pdata(card); int index = get_mi2s_tdm_auxpcm_intf_index(stream_name); struct clk_cfg intf_clk_cfg; +#ifdef CONFIG_COMMON_AMP_CIRRUS + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + unsigned int num_codecs = rtd->dai_link->num_codecs; + int i; +#endif dev_dbg(rtd->card->dev, "%s: substream = %s stream = %d\n", @@ -447,6 +452,28 @@ int msm_common_snd_hw_params(struct snd_pcm_substream *substream, __func__, ret); goto done; } +#ifdef CONFIG_COMMON_AMP_CIRRUS + for (i = 0; i < num_codecs; i++) { + codec_dai = asoc_rtd_to_codec(rtd, i); + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + intf_clk_cfg.clk_freq_in_hz, SND_SOC_CLOCK_IN); + if (ret < 0) + pr_err("%s: failed to set codec tdm clk, err:%d\n", + __func__, ret); + + ret = snd_soc_component_set_sysclk(codec_dai->component, + CLK_SRC_SCLK, 0, intf_clk_cfg.clk_freq_in_hz, SND_SOC_CLOCK_IN); + if (ret < 0) + pr_err("%s: failed to set component sys clk, err:%d\n", + __func__, ret); + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 32); + if (ret < 0) + pr_err("%s: failed to set tdm slot, err:%d\n", + __func__, ret); + + } +#endif } else if ((strnstr(stream_name, "MI2S", strlen(stream_name)))) { ret = get_mi2s_clk_id(index); diff --git a/qcom/opensource/audio-kernel/asoc/msm_common.h b/qcom/opensource/audio-kernel/asoc/msm_common.h index 8d3c3f9939..9a29d19996 100644 --- a/qcom/opensource/audio-kernel/asoc/msm_common.h +++ b/qcom/opensource/audio-kernel/asoc/msm_common.h @@ -9,6 +9,25 @@ #include #include +#ifdef CONFIG_COMMON_AMP_CIRRUS +#define CLK_SRC_SCLK 0 +#define CLK_SRC_LRCLK 1 +#define CLK_SRC_PDM 2 +#define CLK_SRC_SELF 3 +#define CLK_SRC_MCLK 4 +#define CLK_SRC_SWIRE 5 +#define CLK_SRC_DAI 0 +#define CLK_SRC_CODEC 1 + +#define CS35L43_MONO 0x31 +#define CS35L43_STEREO 0x32 +#define CS35L43_QUAD 0x34 + +#define CS35L45_MONO 0x51 +#define CS35L45_STEREO 0x52 +#define CS35L45_QUAD 0x54 +#endif + enum { MI2S = 0, TDM, diff --git a/qcom/opensource/audio-kernel/asoc/msm_dailink.h b/qcom/opensource/audio-kernel/asoc/msm_dailink.h index dbe32707b5..1776796d79 100644 --- a/qcom/opensource/audio-kernel/asoc/msm_dailink.h +++ b/qcom/opensource/audio-kernel/asoc/msm_dailink.h @@ -262,7 +262,11 @@ SND_SOC_DAILINK_DEFS(pri_mi2s_tx, SND_SOC_DAILINK_DEFS(sec_mi2s_rx, DAILINK_COMP_ARRAY(COMP_CPU("snd-soc-dummy-dai")), +#ifdef CONFIG_USE_CS40L26 + DAILINK_COMP_ARRAY(COMP_CODEC("cs40l26-codec", "cs40l26-pcm")), +#else DAILINK_COMP_ARRAY(COMP_CODEC("msm-stub-codec.1", "msm-stub-rx")), +#endif DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); SND_SOC_DAILINK_DEFS(sec_mi2s_tx, @@ -642,3 +646,33 @@ SND_SOC_DAILINK_DEFS(hs_if4_tdm_tx_0_dummy, DAILINK_COMP_ARRAY(COMP_CODEC("msm-stub-codec.1", "msm-stub-tx")), DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); +#ifdef CONFIG_COMMON_AMP_CIRRUS +SND_SOC_DAILINK_DEFS(cirrus_pri_tdm_rx_0, + DAILINK_COMP_ARRAY(COMP_CPU("snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_DUMMY(), + COMP_DUMMY(), + COMP_DUMMY(), + COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +SND_SOC_DAILINK_DEFS(cirrus_pri_tdm_tx_0, + DAILINK_COMP_ARRAY(COMP_CPU("snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY(COMP_DUMMY(), + COMP_DUMMY(), + COMP_DUMMY(), + COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); +#endif +#ifdef CONFIG_SND_SOC_TAS25XX +SND_SOC_DAILINK_DEFS(tas25xx_pri_tdm_rx_0, + DAILINK_COMP_ARRAY(COMP_CPU("snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY( + COMP_CODEC("tas25xx.18-0048", "tas25xx ASI1")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); + +SND_SOC_DAILINK_DEFS(tas25xx_pri_tdm_tx_0, + DAILINK_COMP_ARRAY(COMP_CPU("snd-soc-dummy-dai")), + DAILINK_COMP_ARRAY( + COMP_CODEC("tas25xx.18-0048", "tas25xx ASI1")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("snd-soc-dummy"))); +#endif diff --git a/qcom/opensource/audio-kernel/asoc/pineapple.c b/qcom/opensource/audio-kernel/asoc/pineapple.c index e2204b7d1d..b93b529a96 100644 --- a/qcom/opensource/audio-kernel/asoc/pineapple.c +++ b/qcom/opensource/audio-kernel/asoc/pineapple.c @@ -50,6 +50,21 @@ #include "msm-audio-defs.h" #include "msm_common.h" #include "msm_dailink.h" +#ifdef CONFIG_COMMON_AMP_CIRRUS +#include +#include +#include +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +#include +#include +#if defined(CONFIG_SND_SOC_TAS25XX) +#include "codecs/tas25xx/inc/tas25xx-ext.h" +#endif +#endif +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif #define DRV_NAME "pineapple-asoc-snd" #define __CHIPSET__ "PINEAPPLE " @@ -77,6 +92,13 @@ enum { WCD9378_DEV_INDEX, }; +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +#define MAX_DEFER_COUNT 10 +#define AMP_INIT_SUCCESS 1 +#define AMP_INIT_FAIL 0 +#define AMP_CHECK_NOT_SUPPORT -1 +#endif + struct msm_asoc_mach_data { struct snd_info_entry *codec_root; struct msm_common_pdata *common_pdata; @@ -107,6 +129,15 @@ static int dmic_0_1_gpio_cnt; static int dmic_2_3_gpio_cnt; static int dmic_4_5_gpio_cnt; static int dmic_6_7_gpio_cnt; +static int sub_pcb_conn; +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +static int defer_count; + +static struct snd_soc_dai_link_component dummy_compononet = { + .name = "snd-soc-dummy", + .dai_name = "snd-soc-dummy-dai", +}; +#endif static void *def_wcd_mbhc_cal(void); @@ -119,6 +150,132 @@ static int msm_int_wsa2_init(struct snd_soc_pcm_runtime *); static int msm_int_wsa884x_2_init(struct snd_soc_pcm_runtime *); static int msm_int_wsa883x_2_init(struct snd_soc_pcm_runtime *); +#ifdef CONFIG_COMMON_AMP_CIRRUS +static const struct snd_soc_dapm_route cirrus_mono_routes[] = { + { "AMP0 SPK", NULL, "Mono SPK" }, +}; + +static const struct snd_soc_dapm_route cirrus_stereo_routes[] = { + { "AMP0 SPK", NULL, "Left AMP SPK" }, + { "AMP1 SPK", NULL, "Right AMP SPK" }, +}; + +static const struct snd_soc_dapm_route cirrus_quad_routes[] = { + { "AMP0 SPK", NULL, "FL SPK" }, + { "AMP1 SPK", NULL, "FR SPK" }, + { "AMP2 SPK", NULL, "RL SPK" }, + { "AMP3 SPK", NULL, "RR SPK" }, +}; + +static struct snd_soc_codec_conf cs35l43_mono_conf[] = { + { + .dlc.name = "cs35l43.18-0040", + .name_prefix = "Mono", + } +}; + +static struct snd_soc_codec_conf cs35l43_stereo_conf[] = { + { + .dlc.name = "cs35l43.18-0040", + .name_prefix = "Right", + }, + { + .dlc.name = "cs35l43.18-0041", + .name_prefix = "Left", + } +}; + +static struct snd_soc_codec_conf cs35l43_quad_conf[] = { + { + .dlc.name = "cs35l43.18-0040", + .name_prefix = "RL", + }, + { + .dlc.name = "cs35l43.18-0041", + .name_prefix = "FL", + }, + { + .dlc.name = "cs35l43.18-0042", + .name_prefix = "RR", + }, + { + .dlc.name = "cs35l43.18-0043", + .name_prefix = "FR", + } +}; + +static struct snd_soc_codec_conf cs35l45_mono_conf[] = { + { + .dlc.name = "cs35l45.18-0030", + .name_prefix = "Mono", + } +}; + +static struct snd_soc_codec_conf cs35l45_stereo_conf[] = { + { + .dlc.name = "cs35l45.18-0030", + .name_prefix = "Right", + }, + { + .dlc.name = "cs35l45.18-0031", + .name_prefix = "Left", + } +}; + +static struct snd_soc_codec_conf cs35l45_quad_conf[] = { + { + .dlc.name = "cs35l45.18-0030", + .name_prefix = "RL", + }, + { + .dlc.name = "cs35l45.18-0031", + .name_prefix = "FL", + }, + { + .dlc.name = "cs35l45.18-0032", + .name_prefix = "RR", + }, + { + .dlc.name = "cs35l45.18-0033", + .name_prefix = "FR", + } +}; + +/* + * We want to configure these at runtime for testing + */ +static unsigned int codec_clk_src = CLK_SRC_MCLK; +static const char *const codec_src_clocks[] = {"SCLK", "LRCLK", "PDM", + "MCLK", "SELF", "SWIRE"}; + +static unsigned int dai_clks = SND_SOC_DAIFMT_CBS_CFS; +static const char *const dai_sub_clocks[] = {"Codec Slave", "Codec Master", + "CODEC BMFS", "CODEC BSFM" +}; + +static unsigned int dai_bit_fmt = SND_SOC_DAIFMT_NB_NF; +static const char *const dai_bit_config[] = {"NormalBF", "NormalB INVF", + "INVB NormalF", "INVB INVF" +}; + +static unsigned int dai_mode_fmt = SND_SOC_DAIFMT_I2S; +static const char *const dai_mode_config[] = {"I2S", "Right J", + "Left J", "DSP A", "DSP B", + "PDM" +}; + +static unsigned int sys_clk_static; +static const char *const static_clk_mode[] = {"Off", "5P6", "6P1", "11P2", + "12", "12P2", "13", "22P5", "24", "24P5", "26" +}; + +unsigned int dai_force_frame32; +static const char *const dai_force_frame32_config[] = {"Off", "On"}; + +static u32 cirrus_amp_conf; +static u32 cirrus_amp_count; +#endif + /* * Need to report LINEIN * if R/L channel impedance is larger than 5K ohm @@ -146,6 +303,437 @@ static struct wcd_mbhc_config wcd_mbhc_cfg = { .moisture_duty_cycle_en = true, }; +#ifdef CONFIG_COMMON_AMP_CIRRUS +static int codec_clk_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int codec_clk_src_val = 0; + + switch (codec_clk_src) { + case CLK_SRC_SCLK: + codec_clk_src_val = 0; + break; + case CLK_SRC_LRCLK: + codec_clk_src_val = 1; + break; + case CLK_SRC_PDM: + codec_clk_src_val = 2; + break; + case CLK_SRC_MCLK: + codec_clk_src_val = 3; + break; + case CLK_SRC_SELF: + codec_clk_src_val = 4; + break; + case CLK_SRC_SWIRE: + codec_clk_src_val = 5; + break; + } + + ucontrol->value.integer.value[0] = codec_clk_src_val; + + return 0; +} + +static int codec_clk_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_info("%s: ucontrol value = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + codec_clk_src = CLK_SRC_SCLK; + break; + case 1: + codec_clk_src = CLK_SRC_LRCLK; + break; + case 2: + codec_clk_src = CLK_SRC_PDM; + break; + case 3: + codec_clk_src = CLK_SRC_MCLK; + break; + case 4: + codec_clk_src = CLK_SRC_SELF; + break; + case 5: + codec_clk_src = CLK_SRC_SWIRE; + break; + } + + return 0; +} + +static int dai_clks_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dai_clks_val = 0; + + switch (dai_clks) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_clks_val = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_clks_val = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + dai_clks_val = 2; + break; + case SND_SOC_DAIFMT_CBS_CFM: + dai_clks_val = 3; + break; + } + + ucontrol->value.integer.value[0] = dai_clks_val; + + return 0; +} + +static int dai_clks_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_info("%s: ucontrol value = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + dai_clks = SND_SOC_DAIFMT_CBS_CFS; + break; + case 1: + dai_clks = SND_SOC_DAIFMT_CBM_CFM; + break; + case 2: + dai_clks = SND_SOC_DAIFMT_CBM_CFS; + break; + case 3: + dai_clks = SND_SOC_DAIFMT_CBS_CFM; + break; + } + + return 0; +} + +static int dai_bitfmt_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dai_bits_val = 0; + + switch (dai_bit_fmt) { + case SND_SOC_DAIFMT_NB_NF: + dai_bits_val = 0; + break; + case SND_SOC_DAIFMT_NB_IF: + dai_bits_val = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + dai_bits_val = 2; + break; + case SND_SOC_DAIFMT_IB_IF: + dai_bits_val = 3; + break; + } + + ucontrol->value.integer.value[0] = dai_bits_val; + + return 0; +} + +static int dai_bitfmt_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_info("%s: ucontrol value = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + dai_bit_fmt = SND_SOC_DAIFMT_NB_NF; + break; + case 1: + dai_bit_fmt = SND_SOC_DAIFMT_NB_IF; + break; + case 2: + dai_bit_fmt = SND_SOC_DAIFMT_IB_NF; + break; + case 3: + dai_bit_fmt = SND_SOC_DAIFMT_IB_IF; + break; + } + + return 0; +} + +static int dai_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dai_mode_val = 0; + + switch (dai_mode_fmt) { + case SND_SOC_DAIFMT_I2S: + dai_mode_val = 0; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dai_mode_val = 1; + break; + case SND_SOC_DAIFMT_LEFT_J: + dai_mode_val = 2; + break; + case SND_SOC_DAIFMT_DSP_A: + dai_mode_val = 3; + break; + case SND_SOC_DAIFMT_DSP_B: + dai_mode_val = 4; + break; + case SND_SOC_DAIFMT_PDM: + dai_mode_val = 5; + break; + } + + ucontrol->value.integer.value[0] = dai_mode_val; + + return 0; +} + +static int dai_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_info("%s: ucontrol value = %ld\n", __func__, + ucontrol->value.integer.value[0]); + + switch (ucontrol->value.integer.value[0]) { + case 0: + dai_mode_fmt = SND_SOC_DAIFMT_I2S; + break; + case 1: + dai_mode_fmt = SND_SOC_DAIFMT_RIGHT_J; + break; + case 2: + dai_mode_fmt = SND_SOC_DAIFMT_LEFT_J; + break; + case 3: + dai_mode_fmt = SND_SOC_DAIFMT_DSP_A; + break; + case 4: + dai_mode_fmt = SND_SOC_DAIFMT_DSP_B; + break; + case 5: + dai_mode_fmt = SND_SOC_DAIFMT_PDM; + break; + } + + return 0; +} + +static int static_clk_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int static_mode_val = 0; + + switch (sys_clk_static) { + case 0: + static_mode_val = 0; + break; + case 5644800: + static_mode_val = 1; + break; + case 6144000: + static_mode_val = 2; + break; + case 11289600: + static_mode_val = 3; + break; + case 12000000: + static_mode_val = 4; + break; + case 12288000: + static_mode_val = 5; + break; + case 13000000: + static_mode_val = 6; + break; + case 22579200: + static_mode_val = 7; + break; + case 24000000: + static_mode_val = 8; + break; + case 24576000: + static_mode_val = 9; + break; + case 26000000: + static_mode_val = 10; + break; + } + + ucontrol->value.integer.value[0] = static_mode_val; + + return 0; +} + +static int static_clk_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + switch (ucontrol->value.integer.value[0]) { + case 0: + sys_clk_static = 0; + break; + case 1: + sys_clk_static = 5644800; + break; + case 2: + sys_clk_static = 6144000; + break; + case 3: + sys_clk_static = 11289600; + break; + case 4: + sys_clk_static = 12000000; + break; + case 5: + sys_clk_static = 12288000; + break; + case 6: + sys_clk_static = 13000000; + break; + case 7: + sys_clk_static = 22579200; + break; + case 8: + sys_clk_static = 24000000; + break; + case 9: + sys_clk_static = 24576000; + break; + case 10: + sys_clk_static = 26000000; + break; + } + + return 0; +} + +static int dai_force_frame32_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dai_force_frame32_val = 0; + + switch (dai_force_frame32) { + case 0: + dai_force_frame32_val = 0; + break; + case 1: + dai_force_frame32_val = 1; + break; + } + + ucontrol->value.integer.value[0] = dai_force_frame32_val; + + return 0; +} + +static int dai_force_frame32_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + switch (ucontrol->value.integer.value[0]) { + case 0: + dai_force_frame32 = 0; + break; + case 1: + dai_force_frame32 = 1; + break; + } + + return 0; +} + +static const struct soc_enum cirrus_snd_enum[] = { + SOC_ENUM_SINGLE_EXT(4, dai_sub_clocks), + SOC_ENUM_SINGLE_EXT(4, dai_bit_config), + SOC_ENUM_SINGLE_EXT(6, dai_mode_config), + SOC_ENUM_SINGLE_EXT(11, static_clk_mode), + SOC_ENUM_SINGLE_EXT(5, codec_src_clocks), + SOC_ENUM_SINGLE_EXT(2, dai_force_frame32_config), +}; + +static const struct snd_kcontrol_new msm_cirrus_snd_controls[] = { + SOC_ENUM_EXT("DAI Clocks", cirrus_snd_enum[0], dai_clks_get, + dai_clks_put), + SOC_ENUM_EXT("DAI Polarity", cirrus_snd_enum[1], dai_bitfmt_get, + dai_bitfmt_put), + SOC_ENUM_EXT("DAI Mode", cirrus_snd_enum[2], dai_mode_get, + dai_mode_put), + SOC_ENUM_EXT("Static MCLK Mode", cirrus_snd_enum[3], + static_clk_mode_get, static_clk_mode_put), + SOC_ENUM_EXT("Codec CLK Source", cirrus_snd_enum[4], codec_clk_src_get, + codec_clk_src_put), + SOC_ENUM_EXT("Force Frame32", cirrus_snd_enum[5], dai_force_frame32_get, + dai_force_frame32_put), +}; + +static int cirrus_amp_0_speaker(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + dev_info(component->dev, "%s ev: %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + cirrus_bd_store_values("_0"); + break; + } + + return 0; +} +static int cirrus_amp_1_speaker(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + dev_info(component->dev, "%s ev: %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + cirrus_bd_store_values("_1"); + break; + } + + return 0; +} +static int cirrus_amp_2_speaker(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + dev_info(component->dev, "%s ev: %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + cirrus_bd_store_values("_2"); + break; + } + + return 0; +} +static int cirrus_amp_3_speaker(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + dev_info(component->dev, "%s ev: %d\n", __func__, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + cirrus_bd_store_values("_3"); + break; + } + + return 0; +} +#endif + static bool msm_usbc_swap_gnd_mic(struct snd_soc_component *component, bool active) { int ret = 0; @@ -438,8 +1026,124 @@ static const struct snd_soc_dapm_widget msm_int_dapm_widgets[] = { SND_SOC_DAPM_MIC("Digital Mic5", msm_dmic_event), SND_SOC_DAPM_MIC("Digital Mic6", NULL), SND_SOC_DAPM_MIC("Digital Mic7", NULL), +#ifdef CONFIG_COMMON_AMP_CIRRUS + SND_SOC_DAPM_SPK("AMP0 SPK", cirrus_amp_0_speaker), + SND_SOC_DAPM_SPK("AMP1 SPK", cirrus_amp_1_speaker), + SND_SOC_DAPM_SPK("AMP2 SPK", cirrus_amp_2_speaker), + SND_SOC_DAPM_SPK("AMP3 SPK", cirrus_amp_3_speaker), +#endif }; +#ifdef CONFIG_COMMON_AMP_CIRRUS +#if IS_ENABLED(CONFIG_SEC_ABC) +void cirrus_i2c_fail_log(const char *suffix) +{ + pr_info("%s(%s)\n", __func__, suffix); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=audio@INFO=spk_amp"); +#else + sec_abc_send_event("MODULE=audio@WARN=spk_amp"); +#endif +} + +void cirrus_amp_fail_event(const char *suffix) +{ + pr_info("%s(%s)\n", __func__, suffix); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=audio@INFO=spk_amp_short"); +#else + sec_abc_send_event("MODULE=audio@WARN=spk_amp_short"); +#endif +} +#endif + +static int pineapple_tdm_cirrus_init(struct snd_soc_pcm_runtime *rtd) +{ + int i; + struct snd_soc_dai *codec_dai_amp; + struct snd_soc_dapm_context *amp_dapm; + + if (cirrus_amp_count < 1 || cirrus_amp_count > 4) { + pr_err("%s: out of range\n", __func__); + return 0; + } + + for (i = 0 ; i < cirrus_amp_count; i++) { + codec_dai_amp = asoc_rtd_to_codec(rtd, i); + amp_dapm = snd_soc_component_get_dapm(codec_dai_amp->component); + + switch (cirrus_amp_conf) { + case CS35L43_MONO: + case CS35L43_STEREO: + case CS35L43_QUAD: + snd_soc_dapm_ignore_suspend(amp_dapm, "AMP Capture"); + snd_soc_dapm_ignore_suspend(amp_dapm, "AMP Playback"); + snd_soc_dapm_ignore_suspend(amp_dapm, "AMP SPK"); + snd_soc_dapm_sync(amp_dapm); + break; + case CS35L45_MONO: + case CS35L45_STEREO: + case CS35L45_QUAD: + snd_soc_dapm_ignore_suspend(amp_dapm, "Capture"); + snd_soc_dapm_ignore_suspend(amp_dapm, "Playback"); + snd_soc_dapm_ignore_suspend(amp_dapm, "SPK"); + snd_soc_dapm_ignore_suspend(amp_dapm, "AP"); + snd_soc_dapm_ignore_suspend(amp_dapm, "AMP Enable"); + snd_soc_dapm_ignore_suspend(amp_dapm, "Entry"); + snd_soc_dapm_ignore_suspend(amp_dapm, "Exit"); + snd_soc_dapm_sync(amp_dapm); + break; + default: + pr_err("%s: cirrus amp conf is not defined\n", + __func__); + break; + } + } + +#if IS_ENABLED(CONFIG_SEC_ABC) + cirrus_amp_register_i2c_error_callback("", cirrus_i2c_fail_log); + cirrus_amp_register_i2c_error_callback("_r", cirrus_i2c_fail_log); + cirrus_amp_register_error_callback("", cirrus_amp_fail_event); + cirrus_amp_register_error_callback("_r", cirrus_amp_fail_event); +#endif + register_cirrus_bigdata_cb(amp_dapm->component); + pr_info("%s end\n", __func__); + return 0; +} +#endif + +#if defined(CONFIG_SND_SOC_TAS25XX) +#if IS_ENABLED(CONFIG_SEC_ABC) +void tas_i2c_fail_log(uint32_t i2caddr) +{ + pr_info("%s(%x)\n", __func__, i2caddr); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=audio@INFO=spk_amp"); +#else + sec_abc_send_event("MODULE=audio@WARN=spk_amp"); +#endif +} +void tas_amp_fail_log(int32_t ch, int32_t err) +{ + pr_info("%s(ch : %d , error : %d)\n", __func__, ch, err); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=audio@INFO=spk_amp_short"); +#else + sec_abc_send_event("MODULE=audio@WARN=spk_amp_short"); +#endif +} +#endif + +static int pineapple_tdm_tas_init(struct snd_soc_pcm_runtime *rtd) +{ +#if IS_ENABLED(CONFIG_SEC_ABC) + tas25xx_register_i2c_error_callback(tas_i2c_fail_log); + tas25xx_register_amp_error_callback(tas_amp_fail_log); +#endif + return 0; +} +#endif + #ifndef CONFIG_AUDIO_BTFM_PROXY static int msm_wcn_init(struct snd_soc_pcm_runtime *rtd) { @@ -1078,6 +1782,10 @@ static struct snd_soc_dai_link msm_mi2s_dai_links[] = { .name = LPASS_BE_SEC_MI2S_RX, .stream_name = LPASS_BE_SEC_MI2S_RX, .playback_only = 1, +#ifdef CONFIG_USE_CS40L26 + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_NB_NF, +#endif .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .ops = &msm_common_be_ops, @@ -1209,10 +1917,28 @@ static struct snd_soc_dai_link msm_tdm_dai_links[] = { .playback_only = 1, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +#ifdef CONFIG_COMMON_AMP_CIRRUS + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_IB_NF, + .init = &pineapple_tdm_cirrus_init, + .ops = &msm_common_be_ops, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + SND_SOC_DAILINK_REG(cirrus_pri_tdm_rx_0), +#elif defined(CONFIG_SND_SOC_TAS25XX) + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_IB_NF, + .init = &pineapple_tdm_tas_init, + .ops = &msm_common_be_ops, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + SND_SOC_DAILINK_REG(tas25xx_pri_tdm_rx_0), +#else .ops = &msm_common_be_ops, .ignore_suspend = 1, .ignore_pmdown_time = 1, SND_SOC_DAILINK_REG(pri_tdm_rx_0), +#endif }, { .name = LPASS_BE_PRI_TDM_TX_0, @@ -1220,9 +1946,23 @@ static struct snd_soc_dai_link msm_tdm_dai_links[] = { .capture_only = 1, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, +#ifdef CONFIG_COMMON_AMP_CIRRUS + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_IB_NF, + .ops = &msm_common_be_ops, + .ignore_suspend = 1, + SND_SOC_DAILINK_REG(cirrus_pri_tdm_tx_0), +#elif defined(CONFIG_SND_SOC_TAS25XX) + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_IB_NF, + .ops = &msm_common_be_ops, + .ignore_suspend = 1, + SND_SOC_DAILINK_REG(tas25xx_pri_tdm_tx_0), +#else .ops = &msm_common_be_ops, .ignore_suspend = 1, SND_SOC_DAILINK_REG(pri_tdm_tx_0), +#endif }, { .name = LPASS_BE_SEC_TDM_RX_0, @@ -1593,6 +2333,36 @@ err_hs_detect: return ret; } +#ifdef CONFIG_SEC_FACTORY +/* + * check whether subpcb is connected or not. + * gpio level to low - sub pcb is connected. + * gpio level to high -sub pcb is disconnected. + */ +static int check_upper_c2c_det(struct device *dev) +{ + int upper_c2c_det = -1; + int gpio_level = 0; + + upper_c2c_det = of_get_named_gpio(dev->of_node, + "upper-c2c-det-gpio", 0); + + if (gpio_is_valid(upper_c2c_det)) { + gpio_level = gpio_get_value(upper_c2c_det); + + dev_info(dev, "%s: upper_c2c_det(%d) is %s\n", + __func__, upper_c2c_det, gpio_level ? + "disconnected":"connected"); + if (gpio_level) + sdp_boot_print("%s: upper_c2c_det(%d) is disconnected\n", + __func__, upper_c2c_det); + + } + + return gpio_level; +} +#endif + static struct snd_soc_card *populate_snd_card_dailinks(struct device *dev, int wsa_max_devs) { struct snd_soc_card *card = NULL; @@ -1609,6 +2379,10 @@ static struct snd_soc_card *populate_snd_card_dailinks(struct device *dev, int w return NULL; } +#ifdef CONFIG_SEC_FACTORY + sub_pcb_conn = check_upper_c2c_det(dev); +#endif + if (!strcmp(match->data, "codec")) { card = &snd_soc_card_pineapple_msm; @@ -1670,7 +2444,7 @@ static struct snd_soc_card *populate_snd_card_dailinks(struct device *dev, int w rc = of_property_read_u32(dev->of_node, "qcom,mi2s-audio-intf", &val); - if (!rc && val) { + if (!rc && val && !sub_pcb_conn) { memcpy(msm_pineapple_dai_links + total_links, msm_mi2s_dai_links, sizeof(msm_mi2s_dai_links)); @@ -1679,7 +2453,7 @@ static struct snd_soc_card *populate_snd_card_dailinks(struct device *dev, int w rc = of_property_read_u32(dev->of_node, "qcom,tdm-audio-intf", &val); - if (!rc && val) { + if (!rc && val && !sub_pcb_conn) { memcpy(msm_pineapple_dai_links + total_links, msm_tdm_dai_links, sizeof(msm_tdm_dai_links)); @@ -2075,6 +2849,9 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_dapm_context *dapm = NULL; struct snd_info_entry *entry = NULL; struct snd_card *card = NULL; +#ifdef CONFIG_COMMON_AMP_CIRRUS + struct snd_soc_card *rtd_card = rtd->card; +#endif struct msm_asoc_mach_data *pdata = snd_soc_card_get_drvdata(rtd->card); int ret = 0; @@ -2088,9 +2865,45 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd) dapm = snd_soc_component_get_dapm(lpass_cdc_component); +#ifdef CONFIG_COMMON_AMP_CIRRUS + ret = snd_soc_add_component_controls(lpass_cdc_component, msm_cirrus_snd_controls, + ARRAY_SIZE(msm_cirrus_snd_controls)); + if (ret < 0) { + pr_err("%s: add_component_controls failed: %d\n", + __func__, ret); + return ret; + } +#endif + snd_soc_dapm_new_controls(dapm, msm_int_dapm_widgets, ARRAY_SIZE(msm_int_dapm_widgets)); +#ifdef CONFIG_COMMON_AMP_CIRRUS + if(!sub_pcb_conn) { + switch (cirrus_amp_conf) { + case CS35L43_MONO: + case CS35L45_MONO: + snd_soc_dapm_add_routes(&rtd_card->dapm, cirrus_mono_routes, + ARRAY_SIZE(cirrus_mono_routes)); + break; + case CS35L43_STEREO: + case CS35L45_STEREO: + snd_soc_dapm_add_routes(&rtd_card->dapm, cirrus_stereo_routes, + ARRAY_SIZE(cirrus_stereo_routes)); + break; + case CS35L43_QUAD: + case CS35L45_QUAD: + snd_soc_dapm_add_routes(&rtd_card->dapm, cirrus_quad_routes, + ARRAY_SIZE(cirrus_quad_routes)); + break; + default: + pr_err("%s: cirrus amp conf is not defined\n", + __func__); + break; + } + } +#endif + snd_soc_dapm_ignore_suspend(dapm, "Digital Mic0"); snd_soc_dapm_ignore_suspend(dapm, "Digital Mic1"); snd_soc_dapm_ignore_suspend(dapm, "Digital Mic2"); @@ -2105,6 +2918,12 @@ static int msm_rx_tx_codec_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_ignore_suspend(dapm, "Analog Mic3"); snd_soc_dapm_ignore_suspend(dapm, "Analog Mic4"); snd_soc_dapm_ignore_suspend(dapm, "Analog Mic5"); +#ifdef CONFIG_COMMON_AMP_CIRRUS + snd_soc_dapm_ignore_suspend(dapm, "AMP0 SPK"); + snd_soc_dapm_ignore_suspend(dapm, "AMP1 SPK"); + snd_soc_dapm_ignore_suspend(dapm, "AMP2 SPK"); + snd_soc_dapm_ignore_suspend(dapm, "AMP3 SPK"); +#endif lpass_cdc_set_port_map(lpass_cdc_component, ARRAY_SIZE(sm_port_map), sm_port_map); @@ -2348,6 +3167,107 @@ void msm_common_set_pdata(struct snd_soc_card *card, pdata->common_pdata = common_pdata; } +#ifdef CONFIG_COMMON_AMP_CIRRUS +/* + * update codec name and dai name in runtime + * to support multiple cirrus amp with one binary. + */ +static void update_cirrus_dai_link(struct device *dev) +{ + struct snd_soc_dai_link_component cirrus_dai_link[] = { + { .name = "snd-soc-dummy", .dai_name = "snd-soc-dummy-dai", }, + { .name = "snd-soc-dummy", .dai_name = "snd-soc-dummy-dai", }, + { .name = "snd-soc-dummy", .dai_name = "snd-soc-dummy-dai", }, + { .name = "snd-soc-dummy", .dai_name = "snd-soc-dummy-dai", }, + }; + struct snd_soc_dai_link *dai_link = msm_tdm_dai_links; + const char *codec_name = "cirrus-codec-name"; + const char *codec_dai_name = "cirrus-codec-dai-name"; + int i, ret = 0; + +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + if (defer_count > MAX_DEFER_COUNT) + return; +#endif + dev_info(dev, "%s:\n", __func__); + + for (i = 0; i < cirrus_amp_count; i++) { + ret = of_property_read_string_index(dev->of_node, + codec_name, i, &cirrus_dai_link[i].name); + if (ret) { + dev_err(dev, "Property '%s' does not exist(%d).\n", + codec_name, ret); + return; + } + + ret = of_property_read_string_index(dev->of_node, + codec_dai_name, i, &cirrus_dai_link[i].dai_name); + if (ret) { + dev_err(dev, "Property '%s' does not exist(%d).\n", + codec_dai_name, ret); + return; + } + + /* 0 : Rx, 1 : Tx */ + dai_link[0].codecs[i].name = cirrus_dai_link[i].name; + dai_link[1].codecs[i].name = cirrus_dai_link[i].name; + dai_link[0].codecs[i].dai_name = cirrus_dai_link[i].dai_name; + dai_link[1].codecs[i].dai_name = cirrus_dai_link[i].dai_name; + } + + dai_link[0].num_codecs = dai_link[1].num_codecs = cirrus_amp_count; +} +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +/* + * change codec component to dummy in dailinks + * when soundcard does not registered several times. +*/ +static void change_snd_card_dailinks(struct snd_soc_card *card) +{ + int i, j = 0; + struct snd_soc_dai *dai = NULL; + + sdp_boot_print("%s\n", __func__); + + for (i = 0; i < card->num_links; i++) { + for (j = 0; j < card->dai_link[i].num_codecs; j++) { + dai = snd_soc_find_dai(&card->dai_link[i].codecs[j]); + if(!dai) { + sdp_boot_print("%s: CANNOT find dai %s\n", + __func__, card->dai_link[i].codecs[j].name); + card->dai_link[i].codecs[j].name = + dummy_compononet.name; + card->dai_link[i].codecs[j].dai_name = + dummy_compononet.dai_name; + card->dai_link[i].init = NULL; + pr_info("%s: Change codec_dai of %s to DUMMY\n", + __func__, card->dai_link[i].name); + } + } + } + return; +} +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +static int get_audio_amp_ready(enum amp_id id) +{ +#if defined(CONFIG_SND_SOC_TAS25XX) + int ret = 0; + + ret = tas25xx_get_state(id); + if (ret > 0) /* amp first i2c successful or amp fully initialized for playback */ + return AMP_INIT_SUCCESS; + else + return AMP_INIT_FAIL; +#endif + return AMP_CHECK_NOT_SUPPORT; +} +#endif + + static int msm_asoc_parse_soundcard_name(struct platform_device *pdev, struct snd_soc_card *card) { @@ -2400,6 +3320,8 @@ static int msm_asoc_machine_probe(struct platform_device *pdev) struct clk *lpass_audio_hw_vote = NULL; const struct of_device_id *match; + pr_info("%s enter\n", __func__); + if (!pdev->dev.of_node) { dev_err(&pdev->dev, "%s: No platform supplied from device tree\n", __func__); return -EINVAL; @@ -2427,6 +3349,23 @@ static int msm_asoc_machine_probe(struct platform_device *pdev) pdata->dedicated_wsa2 = of_find_property(pdev->dev.of_node, "qcom,dedicated-wsa2", NULL); +#ifdef CONFIG_COMMON_AMP_CIRRUS + ret = of_property_read_u32(pdev->dev.of_node, + "cirrus-amp-conf", &cirrus_amp_conf); + if (ret) { + dev_dbg(&pdev->dev, + "%s: codec_conf property missing in DT %s, ret = %d\n", + __func__, pdev->dev.of_node->full_name, ret); + cirrus_amp_conf = 0x32; + } + cirrus_amp_count = cirrus_amp_conf & 0x0F; + + dev_info(&pdev->dev, "%s: cirrus amp conf=0x%x\n", + __func__, cirrus_amp_conf); + + if (cirrus_amp_count > 0 && cirrus_amp_count < 5) + update_cirrus_dai_link(&pdev->dev); +#endif card = populate_snd_card_dailinks(&pdev->dev, pdata->wsa_max_devs); if (!card) { dev_err(&pdev->dev, "%s: Card uninitialized\n", __func__); @@ -2471,6 +3410,44 @@ static int msm_asoc_machine_probe(struct platform_device *pdev) /* parse upd configuration */ msm_parse_upd_configuration(pdev, pdata); +#ifdef CONFIG_COMMON_AMP_CIRRUS + switch (cirrus_amp_conf) { + case CS35L43_MONO: + card->codec_conf = cs35l43_mono_conf; + card->num_configs = ARRAY_SIZE(cs35l43_mono_conf); + break; + case CS35L43_STEREO: + card->codec_conf = cs35l43_stereo_conf; + card->num_configs = ARRAY_SIZE(cs35l43_stereo_conf); + break; + case CS35L43_QUAD: + card->codec_conf = cs35l43_quad_conf; + card->num_configs = ARRAY_SIZE(cs35l43_quad_conf); + break; + case CS35L45_MONO: + card->codec_conf = cs35l45_mono_conf; + card->num_configs = ARRAY_SIZE(cs35l45_mono_conf); + break; + case CS35L45_STEREO: + card->codec_conf = cs35l45_stereo_conf; + card->num_configs = ARRAY_SIZE(cs35l45_stereo_conf); + break; + case CS35L45_QUAD: + card->codec_conf = cs35l45_quad_conf; + card->num_configs = ARRAY_SIZE(cs35l45_quad_conf); + break; + default: + dev_err(&pdev->dev, "%s: cirrus amp conf is not defined\n", + __func__); + break; + } +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + if (++defer_count > MAX_DEFER_COUNT) + change_snd_card_dailinks(card); +#endif + ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret == -EPROBE_DEFER) { if (codec_reg_done) @@ -2542,9 +3519,14 @@ static int msm_asoc_machine_probe(struct platform_device *pdev) is_initial_boot = true; +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + audio_register_ready_cb(get_audio_amp_ready); +#endif + /* change card status to ONLINE */ dev_dbg(&pdev->dev, "%s: setting snd_card to ONLINE\n", __func__); snd_card_set_card_status(SND_CARD_STATUS_ONLINE); + sdp_boot_print("%s: snd_card is ONLINE\n", __func__); return 0; err: diff --git a/qcom/opensource/audio-kernel/audio_kernel_modules.mk b/qcom/opensource/audio-kernel/audio_kernel_modules.mk index 6515676a6f..48f576d1ed 100644 --- a/qcom/opensource/audio-kernel/audio_kernel_modules.mk +++ b/qcom/opensource/audio-kernel/audio_kernel_modules.mk @@ -75,6 +75,9 @@ ifeq ($(call is-board-platform-in-list, holi blair), true) AUDIO_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/wcd938x_dlkm.ko \ $(KERNEL_MODULES_OUT)/wcd938x_slave_dlkm.ko endif +ifeq ($(PROJECT_NAME),$(filter $(PROJECT_NAME),q6q b6q q6aq)) +AUDIO_KERNEL_MODULES += $(KERNEL_MODULES_OUT)/tas25xx_dlkm.ko +endif endif else ifeq ($(call is-board-platform-in-list, gen4 msmnile), true) diff --git a/qcom/opensource/audio-kernel/audio_kernel_product_board.mk b/qcom/opensource/audio-kernel/audio_kernel_product_board.mk index 06d8d904ca..d7f0acf213 100644 --- a/qcom/opensource/audio-kernel/audio_kernel_product_board.mk +++ b/qcom/opensource/audio-kernel/audio_kernel_product_board.mk @@ -60,7 +60,9 @@ ifeq ($(call is-board-platform-in-list, holi blair), true) PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/wcd938x_dlkm.ko \ $(KERNEL_MODULES_OUT)/wcd938x_slave_dlkm.ko endif - +ifeq ($(PROJECT_NAME),$(filter $(PROJECT_NAME),q6q b6q q6aq)) +PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/tas25xx_dlkm.ko +endif ifeq ($(call is-board-platform-in-list, gen4 msmnile), true) ifneq (,$(filter $(TARGET_BOARD_PLATFORM)$(TARGET_BOARD_SUFFIX), gen4_gvm msmnile_gvmq)) PRODUCT_PACKAGES += $(KERNEL_MODULES_OUT)/machine_dlkm.ko \ diff --git a/qcom/opensource/audio-kernel/dsp/adsp-loader.c b/qcom/opensource/audio-kernel/dsp/adsp-loader.c index 9108fb3775..19b685dc12 100644 --- a/qcom/opensource/audio-kernel/dsp/adsp-loader.c +++ b/qcom/opensource/audio-kernel/dsp/adsp-loader.c @@ -19,12 +19,17 @@ #include #include #include - +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +#include +#endif #define Q6_PIL_GET_DELAY_MS 100 #define BOOT_CMD 1 #define SSR_RESET_CMD 1 #define IMAGE_UNLOAD_CMD 0 +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +#define SUB_SNS_VDD_CHECK_CMD 0 +#endif #define MAX_FW_IMAGES 4 #define ADSP_LOADER_APM_TIMEOUT_MS 10000 @@ -43,6 +48,12 @@ static ssize_t adsp_ssr_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +static ssize_t adsp_check_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count); +#endif + struct adsp_loader_private { void *pil_h; struct kobject *boot_adsp_obj; @@ -57,9 +68,17 @@ static struct kobj_attribute adsp_boot_attribute = static struct kobj_attribute adsp_ssr_attribute = __ATTR(ssr, 0220, NULL, adsp_ssr_store); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +static struct kobj_attribute adsp_check_attribute = + __ATTR(check, 0220, NULL, adsp_check_store); +#endif + static struct attribute *attrs[] = { &adsp_boot_attribute.attr, &adsp_ssr_attribute.attr, +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + &adsp_check_attribute.attr, +#endif NULL, }; @@ -172,6 +191,10 @@ load_adsp: if (rc) { dev_err(&pdev->dev, "%s: pil get failed,\n", __func__); +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + sdp_boot_print("%s: ADSP loadig is failed = %d\n", + __func__, rc); +#endif goto fail; } } else if (adsp_state == SPF_SUBSYS_LOADED) { @@ -249,6 +272,36 @@ static ssize_t adsp_boot_store(struct kobject *kobj, return count; } +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +static ssize_t adsp_check_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + int check_command = 0; + + if (kstrtoint(buf, 10, &check_command) < 0) + return -EINVAL; + + if (check_command == SUB_SNS_VDD_CHECK_CMD) { + struct platform_device *pdev = adsp_private; + struct adsp_loader_private *priv = NULL; + struct rproc *adsp_rproc = NULL; + + priv = platform_get_drvdata(pdev); + if (priv) { + adsp_rproc = (struct rproc *)priv->pil_h; + if (adsp_rproc) { + pr_info("check subsensor vdd\n"); + adsp_init_subsensor_regulator(adsp_rproc, + NULL); + } + } + } + return count; +} +#endif + static void adsp_loader_unload(struct platform_device *pdev) { struct adsp_loader_private *priv = NULL;