* [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers
@ 2026-04-01 13:21 Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver Niranjan H Y
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Niranjan H Y @ 2026-04-01 13:21 UTC (permalink / raw)
To: linux-sound
Cc: linux-kernel, broonie, ckeepax, lgirdwood, perex, tiwai,
cezary.rojewski, peter.ujfalusi, yung-chuan.liao,
ranjani.sridharan, kai.vehmanen, pierre-louis.bossart, baojun.xu,
shenghao-ding, sandeepk, v-hampiholi, Niranjan H Y
Export the Q7.8 volume control helpers to allow reuse
by other ASoC drivers. These functions handle 16-bit
signed Q7.8 fixed-point format values for volume controls.
Changes include:
- Rename q78_get_volsw to sdca_asoc_q78_get_volsw
- Rename q78_put_volsw to sdca_asoc_q78_put_volsw
- Add a convenience macro SDCA_SINGLE_Q78_TLV and
SDCA_DOUBLE_Q78_TLV for creating mixer controls
This allows other ASoC drivers to easily implement controls
using the Q7.8 fixed-point format without duplicating code.
Signed-off-by: Niranjan H Y <niranjan.hy@ti.com>
---
v4:
- add macro SDCA_DOUBLE_Q78_TLV
v3:
- prepend apis with sdca_asoc_
- add macro SDCA_SINGLE_Q78_TLV
- updated the commit message based on the latest changes
v2:
- newly added patch
---
include/sound/sdca_asoc.h | 43 +++++++++++++++++++++++++++++++++++++-
sound/soc/sdca/sdca_asoc.c | 14 +++++++------
2 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h
index aa9124f932189..46a61a52decc5 100644
--- a/include/sound/sdca_asoc.h
+++ b/include/sound/sdca_asoc.h
@@ -13,6 +13,8 @@
struct device;
struct regmap;
struct sdca_function_data;
+struct snd_ctl_elem_value;
+struct snd_kcontrol;
struct snd_kcontrol_new;
struct snd_pcm_hw_params;
struct snd_pcm_substream;
@@ -23,6 +25,42 @@ struct snd_soc_dai_ops;
struct snd_soc_dapm_route;
struct snd_soc_dapm_widget;
+/* convenient macro to handle the mono volume in 7.8 fixed format representation */
+#define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = sdca_asoc_q78_get_volsw, \
+ .put = sdca_asoc_q78_put_volsw, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) { \
+ .reg = (xreg), .rreg = (xreg), \
+ .min = (xmin), .max = (xmax), \
+ .shift = (xstep), .rshift = (xstep), \
+ .sign_bit = 15 \
+ } \
+}
+
+/* convenient macro for stereo volume in 7.8 fixed format with separate registers for L/R */
+#define SDCA_DOUBLE_Q78_TLV(xname, xreg_l, xreg_r, xmin, xmax, xstep, tlv_array) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = sdca_asoc_q78_get_volsw, \
+ .put = sdca_asoc_q78_put_volsw, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) { \
+ .reg = (xreg_l), .rreg = (xreg_r), \
+ .min = (xmin), .max = (xmax), \
+ .shift = (xstep), .rshift = (xstep), \
+ .sign_bit = 15 \
+ } \
+}
+
int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *function,
int *num_widgets, int *num_routes, int *num_controls,
int *num_dais);
@@ -57,5 +95,8 @@ int sdca_asoc_hw_params(struct device *dev, struct regmap *regmap,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
-
+int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
#endif // __SDCA_ASOC_H__
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c
index 7709a4ce26e09..2bfc8e5aee31d 100644
--- a/sound/soc/sdca/sdca_asoc.c
+++ b/sound/soc/sdca/sdca_asoc.c
@@ -820,8 +820,8 @@ static int q78_write(struct snd_soc_component *component,
return snd_soc_component_update_bits(component, reg, mask, reg_val);
}
-static int q78_put_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
@@ -841,6 +841,7 @@ static int q78_put_volsw(struct snd_kcontrol *kcontrol,
return ret;
}
+EXPORT_SYMBOL_NS(sdca_asoc_q78_put_volsw, "SND_SOC_SDCA");
static int q78_read(struct snd_soc_component *component,
struct soc_mixer_control *mc, unsigned int reg)
@@ -855,8 +856,8 @@ static int q78_read(struct snd_soc_component *component,
return val & GENMASK(mc->sign_bit, 0);
}
-static int q78_get_volsw(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
@@ -868,6 +869,7 @@ static int q78_get_volsw(struct snd_kcontrol *kcontrol,
return 0;
}
+EXPORT_SYMBOL_NS(sdca_asoc_q78_get_volsw, "SND_SOC_SDCA");
static int control_limit_kctl(struct device *dev,
struct sdca_entity *entity,
@@ -912,8 +914,8 @@ static int control_limit_kctl(struct device *dev,
kctl->tlv.p = tlv;
kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
- kctl->get = q78_get_volsw;
- kctl->put = q78_put_volsw;
+ kctl->get = sdca_asoc_q78_get_volsw;
+ kctl->put = sdca_asoc_q78_put_volsw;
return 0;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver
2026-04-01 13:21 [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Niranjan H Y
@ 2026-04-01 13:21 ` Niranjan H Y
2026-04-03 13:25 ` Mark Brown
2026-04-01 13:21 ` [PATCH v4 3/4] ASoC: sdw_utils: TI amp utility for tac5xx2 family Niranjan H Y
` (3 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Niranjan H Y @ 2026-04-01 13:21 UTC (permalink / raw)
To: linux-sound
Cc: linux-kernel, broonie, ckeepax, lgirdwood, perex, tiwai,
cezary.rojewski, peter.ujfalusi, yung-chuan.liao,
ranjani.sridharan, kai.vehmanen, pierre-louis.bossart, baojun.xu,
shenghao-ding, sandeepk, v-hampiholi, Niranjan H Y
Add codec driver for tac5xx2 family of devices.
This includes the support for
1. tac5572 - DAC, PDM, UAJ and HID
2. tas2883 - Amplifier with DSP
3. tac5672 - Similar to tac5572 with feedback
4. tac5682 - Similar to tac5672 with Amplifier DSP.
Signed-off-by: Niranjan H Y <niranjan.hy@ti.com>
---
v4:
- make volume controls as stereo controls for amp gain,
playback, dmic and uaj playback.
- better error handling for "CX11 CS Select" mixer control.
- use switch statement for scalability in hw_params and
hw_free
- remove unnecessary else in tac_io_init
- change error log for unsupported sample rates to debug logs
- make firmware binary parsing robust by checking the fw size
during caching and parsing
v3:
- use macro SDCA_SINGLE_Q78_TLV for the volume mixer controls
- replace magic number with macro for current owner
- fix debug log message about the current owner for hid event
v2:
- Define and use consistent macros for register access to improve code
readability and maintainability
- Replace complex event handlers with simpler DAPM widget implementations
using direct register control for muting/unmuting and enabling/disabling
components
- Replace custom volume controls with standard Q7.8 controls from the
SDCA framework
- Fix PDE power state transitions with proper verification of actual power state
- Combine separate locks into a single ops\_lock to protect both firmware
operations and power state transitions
- Clean up interrupt handling by removing unnecessary register accesses
- Fix headset microphone control by using the correct mono channel value
- Add comments to clarify firmware handling and critical sections
---
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tac5xx2-sdw.c | 2152 ++++++++++++++++++++++++++++++++
sound/soc/codecs/tac5xx2.h | 259 ++++
4 files changed, 2424 insertions(+)
create mode 100644 sound/soc/codecs/tac5xx2-sdw.c
create mode 100644 sound/soc/codecs/tac5xx2.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ca3e47db126e3..4a962193301fe 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -264,6 +264,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_STA529
imply SND_SOC_STAC9766
imply SND_SOC_STI_SAS
+ imply SND_SOC_TAC5XX2_SDW
imply SND_SOC_TAS2552
imply SND_SOC_TAS2562
imply SND_SOC_TAS2764
@@ -2139,6 +2140,16 @@ config SND_SOC_STAC9766
config SND_SOC_STI_SAS
tristate "codec Audio support for STI SAS codec"
+config SND_SOC_TAC5XX2_SDW
+ tristate "Texas Instruments TAC5XX2 SoundWire Smart Amplifier"
+ depends on SOUNDWIRE
+ depends on SND_SOC_SDCA
+ help
+ This option enables support for Texas Instruments TAC5XX2 family
+ of SoundWire Smart Amplifiers. This includes TAC5572, TAC5672,
+ TAC568 and TAS2883. To compile this driver as a module, choose
+ M here: the module will be called snd-soc-tac5xx2.
+
config SND_SOC_TAS2552
tristate "Texas Instruments TAS2552 Mono Audio amplifier"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 172861d17cfd0..f64e75dd2f35d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -313,6 +313,7 @@ snd-soc-sta350-y := sta350.o
snd-soc-sta529-y := sta529.o
snd-soc-stac9766-y := stac9766.o
snd-soc-sti-sas-y := sti-sas.o
+snd-soc-tac5xx2-sdw-y := tac5xx2-sdw.o
snd-soc-tas5086-y := tas5086.o
snd-soc-tas571x-y := tas571x.o
snd-soc-tas5720-y := tas5720.o
@@ -746,6 +747,7 @@ obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o
obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o
+obj-$(CONFIG_SND_SOC_TAC5XX2_SDW) += snd-soc-tac5xx2-sdw.o
obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o
obj-$(CONFIG_SND_SOC_TAS2562) += snd-soc-tas2562.o
obj-$(CONFIG_SND_SOC_TAS2764) += snd-soc-tas2764.o
diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c
new file mode 100644
index 0000000000000..b2a3ad366f735
--- /dev/null
+++ b/sound/soc/codecs/tac5xx2-sdw.c
@@ -0,0 +1,2152 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Texas Instruments TAC5XX2 Audio Smart Amplifier
+//
+// Copyright (C) 2025 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// Author: Niranjan H Y <niranjan.hy@ti.com>
+
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <sound/pcm_params.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/pci.h>
+#include <sound/sdw.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/sdca_asoc.h>
+#include <sound/sdca_function.h>
+#include <sound/sdca_regmap.h>
+#include <sound/jack.h>
+#include <linux/unaligned.h>
+
+#include "tac5xx2.h"
+
+#define TAC5XX2_PROBE_TIMEOUT_MS 3000
+
+#define TAC5XX2_DEVICE_RATES (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_88200)
+#define TAC5XX2_DEVICE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+/* Define channel constants */
+#define TAC_CHANNEL_LEFT 1
+#define TAC_CHANNEL_RIGHT 2
+#define TAC_JACK_MONO_CS 2
+
+#define TAC_MUTE_REG(func, fu, ch) \
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \
+ TAC_SDCA_CHANNEL_MUTE, TAC_CHANNEL_##ch)
+#define TAC_USAGE_REG(func, ent) \
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##ent, \
+ TAC_SDCA_CTL_USAGE, 0)
+#define TAC_XU_BYPASS_REG(func, xu) \
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \
+ TAC_SDCA_CTL_XU_BYPASS, 0)
+
+/* mute registers */
+#define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT)
+#define FU21_R_MUTE_REG TAC_MUTE_REG(SA, FU21, RIGHT)
+#define FU23_L_MUTE_REG TAC_MUTE_REG(SA, FU23, LEFT)
+#define FU23_R_MUTE_REG TAC_MUTE_REG(SA, FU23, RIGHT)
+#define FU26_MUTE_REG TAC_MUTE_REG(SA, FU26, LEFT)
+#define FU11_L_MUTE_REG TAC_MUTE_REG(SM, FU11, LEFT)
+#define FU11_R_MUTE_REG TAC_MUTE_REG(SM, FU11, RIGHT)
+#define FU113_L_MUTE_REG TAC_MUTE_REG(SM, FU113, LEFT)
+#define FU113_R_MUTE_REG TAC_MUTE_REG(SM, FU113, RIGHT)
+#define FU41_L_MUTE_REG TAC_MUTE_REG(UAJ, FU41, LEFT)
+#define FU41_R_MUTE_REG TAC_MUTE_REG(UAJ, FU41, RIGHT)
+#define FU36_MUTE_REG TAC_MUTE_REG(UAJ, FU36, RIGHT)
+
+/* it/ot usage */
+#define IT11_USAGE_REG TAC_USAGE_REG(SM, IT11)
+#define IT41_USAGE_REG TAC_USAGE_REG(UAJ, IT41)
+#define IT33_USAGE_REG TAC_USAGE_REG(UAJ, IT33)
+#define OT113_USAGE_REG TAC_USAGE_REG(SM, OT113)
+#define OT45_USAGE_REG TAC_USAGE_REG(UAJ, OT45)
+#define OT36_USAGE_REG TAC_USAGE_REG(UAJ, OT36)
+
+/* xu bypass */
+#define XU12_BYPASS_REG TAC_XU_BYPASS_REG(SM, XU12)
+#define XU42_BYPASS_REG TAC_XU_BYPASS_REG(UAJ, XU42)
+
+#define TAC_DSP_ALGO_STATUS TAC_REG_SDW(0, 3, 12)
+#define TAC_DSP_ALGO_STATUS_RUNNING 0x20
+#define TAC_FW_HDR_SIZE 4
+#define TAC_FW_FILE_HDR 20
+#define TAC_MAX_FW_CHUNKS 512
+
+struct tac_fw_hdr {
+ u32 size;
+ u32 version_offset;
+ u32 plt_id;
+ u32 ppc3_ver;
+ u32 timestamp;
+ u8 ddc_name[64];
+};
+
+/* Firmware file/chunk structure */
+struct tac_fw_file {
+ u32 vendor_id;
+ u32 file_id;
+ u32 version;
+ u32 length;
+ u32 dest_addr;
+ u8 *fw_data;
+};
+
+/* TLV for volume control */
+static const DECLARE_TLV_DB_SCALE(tac5xx2_amp_tlv, 0, 50, 0);
+static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0);
+
+/* Q7.8 volume control parameters: range -72dB to +6dB, step 0.5dB */
+#define TAC_DVC_STEP 128 /* 0.5 dB in Q7.8 format */
+#define TAC_DVC_MIN (-144) /* -72 dB / 0.5 dB step */
+#define TAC_DVC_MAX 12 /* +6 dB / 0.5 dB step */
+
+/* TAC-specific stereo volume control macro using SDW_SDCA_CTL (single control for L/R) */
+#define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \
+ SDCA_DOUBLE_Q78_TLV(name, \
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \
+ TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv)
+
+struct tac5xx2_prv {
+ struct snd_soc_component *component;
+ struct sdw_slave *sdw_peripheral;
+ struct sdca_function_data *sa_func_data;
+ struct sdca_function_data *sm_func_data;
+ struct sdca_function_data *uaj_func_data;
+ struct sdca_function_data *hid_func_data;
+ enum sdw_slave_status status;
+ /* Lock for firmware download and PDE state transitions.
+ * Serializes FW caching/download and DAPM-driven power
+ * state changes to prevent PDE operations during firmware load.
+ */
+ struct mutex pde_lock;
+ struct regmap *regmap;
+ struct device *dev;
+ bool hw_init;
+ bool first_hw_init;
+ u32 part_id;
+ unsigned int cx11_value;
+ struct snd_soc_jack *hs_jack;
+ int jack_type;
+ /* Custom fw binary. UMP File Download is not used. */
+ const u8 *fw_data;
+ size_t fw_size;
+ bool fw_cached;
+ bool fw_dl_success;
+ u8 fw_binaryname[64];
+};
+
+static const struct reg_default tac_reg_default[] = {
+ {TAC_SW_RESET, 0x0},
+ {TAC_SLEEP_MODEZ, 0x0},
+ {TAC_FEATURE_PDZ, 0x0},
+ {TAC_TX_CH_EN, 0xf0},
+ {TAC_REG_SDW(0, 0, 0x5), 0xcf},
+ {TAC_REG_SDW(0, 0, 0x6), 0xa},
+ {TAC_REG_SDW(0, 0, 0x7), 0x0},
+ {TAC_REG_SDW(0, 0, 0x8), 0xfe},
+ {TAC_REG_SDW(0, 0, 0x9), 0x9},
+ {TAC_REG_SDW(0, 0, 0xa), 0x28},
+ {TAC_REG_SDW(0, 0, 0xb), 0x1},
+ {TAC_REG_SDW(0, 0, 0xc), 0x11},
+ {TAC_REG_SDW(0, 0, 0xd), 0x11},
+ {TAC_REG_SDW(0, 0, 0xe), 0x61},
+ {TAC_REG_SDW(0, 0, 0xf), 0x0},
+ {TAC_REG_SDW(0, 0, 0x10), 0x50},
+ {TAC_REG_SDW(0, 0, 0x11), 0x70},
+ {TAC_REG_SDW(0, 0, 0x12), 0x60},
+ {TAC_REG_SDW(0, 0, 0x13), 0x28},
+ {TAC_REG_SDW(0, 0, 0x14), 0x0},
+ {TAC_REG_SDW(0, 0, 0x15), 0x18},
+ {TAC_REG_SDW(0, 0, 0x16), 0x20},
+ {TAC_REG_SDW(0, 0, 0x17), 0x0},
+ {TAC_REG_SDW(0, 0, 0x18), 0x18},
+ {TAC_REG_SDW(0, 0, 0x19), 0x54},
+ {TAC_REG_SDW(0, 0, 0x1a), 0x8},
+ {TAC_REG_SDW(0, 0, 0x1b), 0x0},
+ {TAC_REG_SDW(0, 0, 0x1c), 0x30},
+ {TAC_REG_SDW(0, 0, 0x1d), 0x0},
+ {TAC_REG_SDW(0, 0, 0x1e), 0x0},
+ {TAC_REG_SDW(0, 0, 0x1f), 0x0},
+ {TAC_REG_SDW(0, 0, 0x20), 0x0},
+ {TAC_REG_SDW(0, 0, 0x21), 0x20},
+ {TAC_REG_SDW(0, 0, 0x22), 0x21},
+ {TAC_REG_SDW(0, 0, 0x23), 0x22},
+ {TAC_REG_SDW(0, 0, 0x24), 0x23},
+ {TAC_REG_SDW(0, 0, 0x25), 0x4},
+ {TAC_REG_SDW(0, 0, 0x26), 0x5},
+ {TAC_REG_SDW(0, 0, 0x27), 0x6},
+ {TAC_REG_SDW(0, 0, 0x28), 0x7},
+ {TAC_REG_SDW(0, 0, 0x29), 0x0},
+ {TAC_REG_SDW(0, 0, 0x2a), 0x0},
+ {TAC_REG_SDW(0, 0, 0x2b), 0x0},
+ {TAC_REG_SDW(0, 0, 0x2c), 0x20},
+ {TAC_REG_SDW(0, 0, 0x2d), 0x21},
+ {TAC_REG_SDW(0, 0, 0x2e), 0x2},
+ {TAC_REG_SDW(0, 0, 0x2f), 0x3},
+ {TAC_REG_SDW(0, 0, 0x30), 0x4},
+ {TAC_REG_SDW(0, 0, 0x31), 0x5},
+ {TAC_REG_SDW(0, 0, 0x32), 0x6},
+ {TAC_REG_SDW(0, 0, 0x33), 0x7},
+ {TAC_REG_SDW(0, 0, 0x34), 0x0},
+ {TAC_REG_SDW(0, 0, 0x35), 0x90},
+ {TAC_REG_SDW(0, 0, 0x36), 0x80},
+ {TAC_REG_SDW(0, 0, 0x37), 0x0},
+ {TAC_REG_SDW(0, 0, 0x39), 0x0},
+ {TAC_REG_SDW(0, 0, 0x3a), 0x90},
+ {TAC_REG_SDW(0, 0, 0x3b), 0x80},
+ {TAC_REG_SDW(0, 0, 0x3c), 0x0},
+ {TAC_REG_SDW(0, 0, 0x3e), 0x0},
+ {TAC_REG_SDW(0, 0, 0x3f), 0x90},
+ {TAC_REG_SDW(0, 0, 0x40), 0x80},
+ {TAC_REG_SDW(0, 0, 0x41), 0x0},
+ {TAC_REG_SDW(0, 0, 0x43), 0x90},
+ {TAC_REG_SDW(0, 0, 0x44), 0x80},
+ {TAC_REG_SDW(0, 0, 0x45), 0x0},
+ {TAC_REG_SDW(0, 0, 0x47), 0x90},
+ {TAC_REG_SDW(0, 0, 0x48), 0x80},
+ {TAC_REG_SDW(0, 0, 0x49), 0x0},
+ {TAC_REG_SDW(0, 0, 0x4b), 0x90},
+ {TAC_REG_SDW(0, 0, 0x4c), 0x80},
+ {TAC_REG_SDW(0, 0, 0x4d), 0x0},
+ {TAC_REG_SDW(0, 0, 0x4f), 0x31},
+ {TAC_REG_SDW(0, 0, 0x50), 0x0},
+ {TAC_REG_SDW(0, 0, 0x51), 0x0},
+ {TAC_REG_SDW(0, 0, 0x52), 0x90},
+ {TAC_REG_SDW(0, 0, 0x53), 0x80},
+ {TAC_REG_SDW(0, 0, 0x55), 0x90},
+ {TAC_REG_SDW(0, 0, 0x56), 0x80},
+ {TAC_REG_SDW(0, 0, 0x58), 0x90},
+ {TAC_REG_SDW(0, 0, 0x59), 0x80},
+ {TAC_REG_SDW(0, 0, 0x5b), 0x90},
+ {TAC_REG_SDW(0, 0, 0x5c), 0x80},
+ {TAC_REG_SDW(0, 0, 0x5e), 0x8},
+ {TAC_REG_SDW(0, 0, 0x5f), 0x8},
+ {TAC_REG_SDW(0, 0, 0x60), 0x0},
+ {TAC_REG_SDW(0, 0, 0x61), 0x0},
+ {TAC_REG_SDW(0, 0, 0x62), 0xff},
+ {TAC_REG_SDW(0, 0, 0x63), 0xc0},
+ {TAC_REG_SDW(0, 0, 0x64), 0x5},
+ {TAC_REG_SDW(0, 0, 0x65), 0x3},
+ {TAC_REG_SDW(0, 0, 0x66), 0x0},
+ {TAC_REG_SDW(0, 0, 0x67), 0x0},
+ {TAC_REG_SDW(0, 0, 0x68), 0x0},
+ {TAC_REG_SDW(0, 0, 0x69), 0x8},
+ {TAC_REG_SDW(0, 0, 0x6a), 0x0},
+ {TAC_REG_SDW(0, 0, 0x6b), 0xa0},
+ {TAC_REG_SDW(0, 0, 0x6c), 0x18},
+ {TAC_REG_SDW(0, 0, 0x6d), 0x18},
+ {TAC_REG_SDW(0, 0, 0x6e), 0x18},
+ {TAC_REG_SDW(0, 0, 0x6f), 0x18},
+ {TAC_REG_SDW(0, 0, 0x70), 0x88},
+ {TAC_REG_SDW(0, 0, 0x71), 0xff},
+ {TAC_REG_SDW(0, 0, 0x72), 0x0},
+ {TAC_REG_SDW(0, 0, 0x73), 0x31},
+ {TAC_REG_SDW(0, 0, 0x74), 0xc0},
+ {TAC_REG_SDW(0, 0, 0x75), 0x0},
+ {TAC_REG_SDW(0, 0, 0x76), 0x0},
+ {TAC_REG_SDW(0, 0, 0x77), 0x0},
+ {TAC_REG_SDW(0, 0, 0x78), 0x0},
+ {TAC_REG_SDW(0, 0, 0x7b), 0x0},
+ {TAC_REG_SDW(0, 0, 0x7c), 0xd0},
+ {TAC_REG_SDW(0, 0, 0x7d), 0x0},
+ {TAC_REG_SDW(0, 0, 0x7e), 0x0},
+ {TAC_REG_SDW(0, 1, 0x1), 0x0},
+ {TAC_REG_SDW(0, 1, 0x2), 0x0},
+ {TAC_REG_SDW(0, 1, 0x3), 0x0},
+ {TAC_REG_SDW(0, 1, 0x4), 0x4},
+ {TAC_REG_SDW(0, 1, 0x5), 0x0},
+ {TAC_REG_SDW(0, 1, 0x6), 0x0},
+ {TAC_REG_SDW(0, 1, 0x7), 0x0},
+ {TAC_REG_SDW(0, 1, 0x8), 0x0},
+ {TAC_REG_SDW(0, 1, 0x9), 0x0},
+ {TAC_REG_SDW(0, 1, 0xa), 0x0},
+ {TAC_REG_SDW(0, 1, 0xb), 0x1},
+ {TAC_REG_SDW(0, 1, 0xc), 0x0},
+ {TAC_REG_SDW(0, 1, 0xd), 0x0},
+ {TAC_REG_SDW(0, 1, 0xe), 0x0},
+ {TAC_REG_SDW(0, 1, 0xf), 0x8},
+ {TAC_REG_SDW(0, 1, 0x10), 0x0},
+ {TAC_REG_SDW(0, 1, 0x11), 0x0},
+ {TAC_REG_SDW(0, 1, 0x12), 0x1},
+ {TAC_REG_SDW(0, 1, 0x13), 0x0},
+ {TAC_REG_SDW(0, 1, 0x14), 0x0},
+ {TAC_REG_SDW(0, 1, 0x15), 0x0},
+ {TAC_REG_SDW(0, 1, 0x16), 0x0},
+ {TAC_REG_SDW(0, 1, 0x17), 0x0},
+ {TAC_REG_SDW(0, 1, 0x18), 0x0},
+ {TAC_REG_SDW(0, 1, 0x19), 0x0},
+ {TAC_REG_SDW(0, 1, 0x1a), 0x0},
+ {TAC_REG_SDW(0, 1, 0x1b), 0x0},
+ {TAC_REG_SDW(0, 1, 0x1c), 0x0},
+ {TAC_REG_SDW(0, 1, 0x1d), 0x0},
+ {TAC_REG_SDW(0, 1, 0x1e), 0x2},
+ {TAC_REG_SDW(0, 1, 0x1f), 0x8},
+ {TAC_REG_SDW(0, 1, 0x20), 0x9},
+ {TAC_REG_SDW(0, 1, 0x21), 0xa},
+ {TAC_REG_SDW(0, 1, 0x22), 0xb},
+ {TAC_REG_SDW(0, 1, 0x23), 0xc},
+ {TAC_REG_SDW(0, 1, 0x24), 0xd},
+ {TAC_REG_SDW(0, 1, 0x25), 0xe},
+ {TAC_REG_SDW(0, 1, 0x26), 0xf},
+ {TAC_REG_SDW(0, 1, 0x27), 0x8},
+ {TAC_REG_SDW(0, 1, 0x28), 0x9},
+ {TAC_REG_SDW(0, 1, 0x29), 0xa},
+ {TAC_REG_SDW(0, 1, 0x2a), 0xb},
+ {TAC_REG_SDW(0, 1, 0x2b), 0xc},
+ {TAC_REG_SDW(0, 1, 0x2c), 0xd},
+ {TAC_REG_SDW(0, 1, 0x2d), 0xe},
+ {TAC_REG_SDW(0, 1, 0x2e), 0xf},
+ {TAC_REG_SDW(0, 1, 0x2f), 0x0},
+ {TAC_REG_SDW(0, 1, 0x30), 0x0},
+ {TAC_REG_SDW(0, 1, 0x31), 0x0},
+ {TAC_REG_SDW(0, 1, 0x32), 0x0},
+ {TAC_REG_SDW(0, 1, 0x33), 0x0},
+ {TAC_REG_SDW(0, 1, 0x34), 0x0},
+ {TAC_REG_SDW(0, 1, 0x35), 0x0},
+ {TAC_REG_SDW(0, 1, 0x36), 0x0},
+ {TAC_REG_SDW(0, 1, 0x37), 0x0},
+ {TAC_REG_SDW(0, 1, 0x38), 0x98},
+ {TAC_REG_SDW(0, 1, 0x39), 0x0},
+ {TAC_REG_SDW(0, 1, 0x3a), 0x0},
+ {TAC_REG_SDW(0, 1, 0x3b), 0x0},
+ {TAC_REG_SDW(0, 1, 0x3c), 0x1},
+ {TAC_REG_SDW(0, 1, 0x3d), 0x2},
+ {TAC_REG_SDW(0, 1, 0x3e), 0x3},
+ {TAC_REG_SDW(0, 1, 0x3f), 0x4},
+ {TAC_REG_SDW(0, 1, 0x40), 0x5},
+ {TAC_REG_SDW(0, 1, 0x41), 0x6},
+ {TAC_REG_SDW(0, 1, 0x42), 0x7},
+ {TAC_REG_SDW(0, 1, 0x43), 0x0},
+ {TAC_REG_SDW(0, 1, 0x44), 0x0},
+ {TAC_REG_SDW(0, 1, 0x45), 0x1},
+ {TAC_REG_SDW(0, 1, 0x46), 0x2},
+ {TAC_REG_SDW(0, 1, 0x47), 0x3},
+ {TAC_REG_SDW(0, 1, 0x48), 0x4},
+ {TAC_REG_SDW(0, 1, 0x49), 0x5},
+ {TAC_REG_SDW(0, 1, 0x4a), 0x6},
+ {TAC_REG_SDW(0, 1, 0x4b), 0x7},
+ {TAC_REG_SDW(0, 1, 0x4c), 0x98},
+ {TAC_REG_SDW(0, 1, 0x4d), 0x0},
+ {TAC_REG_SDW(0, 1, 0x4e), 0x0},
+ {TAC_REG_SDW(0, 1, 0x4f), 0x0},
+ {TAC_REG_SDW(0, 1, 0x50), 0x1},
+ {TAC_REG_SDW(0, 1, 0x51), 0x2},
+ {TAC_REG_SDW(0, 1, 0x52), 0x3},
+ {TAC_REG_SDW(0, 1, 0x53), 0x4},
+ {TAC_REG_SDW(0, 1, 0x54), 0x5},
+ {TAC_REG_SDW(0, 1, 0x55), 0x6},
+ {TAC_REG_SDW(0, 1, 0x56), 0x7},
+ {TAC_REG_SDW(0, 1, 0x57), 0x0},
+ {TAC_REG_SDW(0, 1, 0x58), 0x0},
+ {TAC_REG_SDW(0, 1, 0x59), 0x1},
+ {TAC_REG_SDW(0, 1, 0x5a), 0x2},
+ {TAC_REG_SDW(0, 1, 0x5b), 0x3},
+ {TAC_REG_SDW(0, 1, 0x5c), 0x4},
+ {TAC_REG_SDW(0, 1, 0x5d), 0x5},
+ {TAC_REG_SDW(0, 1, 0x5e), 0x6},
+ {TAC_REG_SDW(0, 1, 0x5f), 0x7},
+ {TAC_REG_SDW(0, 1, 0x60), 0x98},
+ {TAC_REG_SDW(0, 1, 0x61), 0x0},
+ {TAC_REG_SDW(0, 1, 0x62), 0x0},
+ {TAC_REG_SDW(0, 1, 0x63), 0x0},
+ {TAC_REG_SDW(0, 1, 0x64), 0x1},
+ {TAC_REG_SDW(0, 1, 0x65), 0x2},
+ {TAC_REG_SDW(0, 1, 0x66), 0x3},
+ {TAC_REG_SDW(0, 1, 0x67), 0x4},
+ {TAC_REG_SDW(0, 1, 0x68), 0x5},
+ {TAC_REG_SDW(0, 1, 0x69), 0x6},
+ {TAC_REG_SDW(0, 1, 0x6a), 0x7},
+ {TAC_REG_SDW(0, 1, 0x6b), 0x0},
+ {TAC_REG_SDW(0, 1, 0x6c), 0x0},
+ {TAC_REG_SDW(0, 1, 0x6d), 0x1},
+ {TAC_REG_SDW(0, 1, 0x6e), 0x2},
+ {TAC_REG_SDW(0, 1, 0x6f), 0x3},
+ {TAC_REG_SDW(0, 1, 0x70), 0x4},
+ {TAC_REG_SDW(0, 1, 0x71), 0x5},
+ {TAC_REG_SDW(0, 1, 0x72), 0x6},
+ {TAC_REG_SDW(0, 1, 0x73), 0x7},
+};
+
+static const struct reg_sequence tac_spk_seq[] = {
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0),
+};
+
+static const struct reg_sequence tac_sm_seq[] = {
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0),
+};
+
+static const struct reg_sequence tac_uaj_seq[] = {
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0),
+ REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36,
+ TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0),
+};
+
+static bool tac_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TAC_REG_SDW(0, 0, 1) ... TAC_REG_SDW(0, 0, 5):
+ case TAC_REG_SDW(0, 2, 1) ... TAC_REG_SDW(0, 2, 6):
+ case TAC_REG_SDW(0, 2, 24) ... TAC_REG_SDW(0, 2, 55):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_HID, TAC_SDCA_ENT_HID1,
+ TAC_SDCA_CTL_HIDTX_CURRENT_OWNER, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_HID, TAC_SDCA_ENT_HID1,
+ TAC_SDCA_CTL_HIDTX_MESSAGE_OFFSET, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35,
+ TAC_SDCA_CTL_DET_MODE, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_PDE23,
+ TAC_SDCA_REQUESTED_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_PDE11,
+ TAC_SDCA_REQUESTED_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_PDE47,
+ TAC_SDCA_REQUESTED_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_PDE34,
+ TAC_SDCA_REQUESTED_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_PDE23,
+ TAC_SDCA_ACTUAL_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_PDE11,
+ TAC_SDCA_ACTUAL_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_PDE47,
+ TAC_SDCA_ACTUAL_PS, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_PDE34,
+ TAC_SDCA_ACTUAL_PS, 0):
+ case SDW_SCP_SDCA_INT1:
+ case SDW_SCP_SDCA_INT2:
+ case SDW_SCP_SDCA_INT3:
+ case SDW_SCP_SDCA_INT4:
+ case SDW_SDCA_CTL(1, 0, 0x10, 0):
+ case SDW_SDCA_CTL(2, 0, 0x10, 0):
+ case SDW_SDCA_CTL(3, 0, 0x10, 0):
+ case SDW_SDCA_CTL(4, 0, 0x1, 0):
+ case 0x44007F80 ... 0x44007F87:
+ case TAC_DSP_ALGO_STATUS: /* DSP algo status - always read from HW */
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static int tac_sdca_mbq_size(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21,
+ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21,
+ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23,
+ TAC_SDCA_MASTER_GAIN, 0):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41,
+ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT):
+ case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36,
+ TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS):
+ return 2;
+
+ default:
+ return 1;
+ }
+}
+
+static const struct regmap_sdw_mbq_cfg tac_mbq_cfg = {
+ .mbq_size = tac_sdca_mbq_size,
+};
+
+static const struct regmap_config tac_regmap = {
+ .reg_bits = 32,
+ .val_bits = 16, /* mbq support */
+ .reg_defaults = tac_reg_default,
+ .num_reg_defaults = ARRAY_SIZE(tac_reg_default),
+ .max_register = 0x47FFFFFF,
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = tac_volatile_reg,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+/* Check if device has DSP algo that needs status monitoring */
+static bool tac_has_dsp_algo(struct tac5xx2_prv *tac_dev)
+{
+ switch (tac_dev->part_id) {
+ case 0x5682:
+ case 0x2883:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Check if device has UAJ (Universal Audio Jack) support */
+static bool tac_has_uaj_support(struct tac5xx2_prv *tac_dev)
+{
+ return tac_dev->uaj_func_data;
+}
+
+/* Forward declaration for headset detection */
+static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev);
+
+/* Define CX11 mux options */
+static const char *const tac_cx11_mux_texts[] = {"CS:18", "CS:11"};
+static const struct soc_enum tac_cx11_mux_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tac_cx11_mux_texts),
+ tac_cx11_mux_texts);
+
+static int tac_cx11_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct tac5xx2_prv *tac_dev;
+
+ ucontrol->value.enumerated.item[0] = 1; /* Default to DC:1 */
+
+ if (!kcontrol || !kcontrol->private_data)
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -ENODEV;
+
+ tac_dev = snd_soc_component_get_drvdata(component);
+ if (!tac_dev)
+ return -ENODEV;
+
+ ucontrol->value.enumerated.item[0] = tac_dev->cx11_value;
+
+ return 0;
+}
+
+static int tac_cx11_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component;
+ struct tac5xx2_prv *tac_dev;
+ unsigned int val;
+ int ret;
+
+ val = ucontrol->value.enumerated.item[0];
+
+ if (val >= ARRAY_SIZE(tac_cx11_mux_texts))
+ return -EINVAL;
+
+ component = snd_kcontrol_chip(kcontrol);
+ if (!component)
+ return -ENODEV;
+
+ tac_dev = snd_soc_component_get_drvdata(component);
+ if (!tac_dev || !tac_dev->sdw_peripheral || !tac_dev->hw_init) {
+ dev_err(component->dev, "failed to get driver data for cx put");
+ return -ENODEV;
+ }
+
+ if (tac_dev->cx11_value == val) {
+ dev_dbg(tac_dev->dev, "cx put, same value");
+ return 0; /* No change */
+ }
+
+ tac_dev->cx11_value = val;
+
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CX11,
+ TAC_SDCA_CTL_CX_CLK_SEL, 0),
+ val);
+ if (ret) {
+ dev_err(tac_dev->dev, "cx put failed: %d", ret);
+ return ret;
+ }
+
+ return 1;
+}
+
+/* Volume controls for mic, hp and mic cap */
+static const struct snd_kcontrol_new tac5xx2_snd_controls[] = {
+ SOC_DOUBLE_R_RANGE_TLV("Amp Volume", TAC_AMP_LVL_CFG0, TAC_AMP_LVL_CFG1,
+ 2, 0, 44, 1, tac5xx2_amp_tlv),
+ TAC_DOUBLE_Q78_TLV("DMIC Capture Volume", SM, FU113),
+ TAC_DOUBLE_Q78_TLV("Speaker Volume", SA, FU21),
+ SOC_DAPM_ENUM_EXT("CX11 CS Select", tac_cx11_mux_enum, tac_cx11_get,
+ tac_cx11_put),
+};
+
+static const struct snd_kcontrol_new tac_uaj_controls[] = {
+ TAC_DOUBLE_Q78_TLV("UAJ Volume", UAJ, FU41),
+ SDCA_SINGLE_Q78_TLV("UAJ Capture Volume",
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36,
+ TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS),
+ TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv),
+};
+
+static const struct snd_soc_dapm_widget tac5xx2_common_widgets[] = {
+ /* Port 1: Speaker Playback Path */
+ SND_SOC_DAPM_AIF_IN("AIF1 Playback", "DP1 Speaker Playback", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_PGA("FU21_L", FU21_L_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("FU21_R", FU21_R_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("FU23_L", FU23_L_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("FU23_R", FU23_R_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("SPK_L"),
+ SND_SOC_DAPM_OUTPUT("SPK_R"),
+
+ /* Port 3: Smart Mic (DMIC) Capture Path */
+ SND_SOC_DAPM_AIF_OUT("AIF3 Capture", "DP3 Mic Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_INPUT("DMIC_L"),
+ SND_SOC_DAPM_INPUT("DMIC_R"),
+ SND_SOC_DAPM_PGA("IT11", IT11_USAGE_REG, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("CS11", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("CS18", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("CS113", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("FU11_L", FU11_L_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("FU11_R", FU11_R_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("PPU11", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("XU12", XU12_BYPASS_REG, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("FU113_L", FU113_L_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("FU113_R", FU113_R_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("OT113", OT113_USAGE_REG, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_widget tac_uaj_widgets[] = {
+ /* Port 4: UAJ (Headphone) Playback Path */
+ SND_SOC_DAPM_AIF_IN("AIF4 Playback", "DP4 UAJ Speaker Playback", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_PGA("IT41", IT41_USAGE_REG, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("FU41_L", FU41_L_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("FU41_R", FU41_R_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("XU42", XU42_BYPASS_REG, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("CS41", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_DAC("OT45", "DP4 UAJ Speaker Playback", OT45_USAGE_REG, 0, 0),
+ SND_SOC_DAPM_OUTPUT("HP_L"),
+ SND_SOC_DAPM_OUTPUT("HP_R"),
+
+ /* Port 7: UAJ (Headset Mic) Capture Path */
+ SND_SOC_DAPM_AIF_OUT("AIF7 Capture", "DP7 UAJ Mic Capture", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_INPUT("UAJ_MIC"),
+ SND_SOC_DAPM_ADC("IT33", "DP7 UAJ Mic Capture", IT33_USAGE_REG, 0, 0),
+ SND_SOC_DAPM_PGA("FU36", FU36_MUTE_REG, 0, 1, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("CS36", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("OT36", OT36_USAGE_REG, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route tac5xx2_common_routes[] = {
+ /* Speaker Playback Path */
+ {"FU21_L", NULL, "AIF1 Playback"},
+ {"FU21_R", NULL, "AIF1 Playback"},
+
+ {"FU23_L", NULL, "FU21_L"},
+ {"FU23_R", NULL, "FU21_R"},
+
+ {"SPK_L", NULL, "FU23_L"},
+ {"SPK_R", NULL, "FU23_R"},
+
+ /* Smart Mic DAPM Routes */
+ {"IT11", NULL, "DMIC_L"},
+ {"IT11", NULL, "DMIC_R"},
+ {"IT11", NULL, "CS11"},
+ {"IT11", NULL, "CS18"},
+ {"FU11_L", NULL, "IT11"},
+ {"FU11_R", NULL, "IT11"},
+ {"PPU11", NULL, "FU11_L"},
+ {"PPU11", NULL, "FU11_R"},
+ {"XU12", NULL, "PPU11"},
+ {"FU113_L", NULL, "XU12"},
+ {"FU113_R", NULL, "XU12"},
+ {"FU113_L", NULL, "CS113"},
+ {"FU113_R", NULL, "CS113"},
+ {"OT113", NULL, "FU113_L"},
+ {"OT113", NULL, "FU113_R"},
+ {"OT113", NULL, "CS113"},
+ {"AIF3 Capture", NULL, "OT113"},
+};
+
+static const struct snd_soc_dapm_route tac_uaj_routes[] = {
+ /* UAJ Playback routes */
+ {"IT41", NULL, "AIF4 Playback"},
+ {"IT41", NULL, "CS41"},
+ {"FU41_L", NULL, "IT41"},
+ {"FU41_R", NULL, "IT41"},
+ {"XU42", NULL, "FU41_L"},
+ {"XU42", NULL, "FU41_R"},
+ {"OT45", NULL, "XU42"},
+ {"OT45", NULL, "CS41"},
+ {"HP_L", NULL, "OT45"},
+ {"HP_R", NULL, "OT45"},
+
+ /* UAJ Capture routes */
+ {"IT33", NULL, "UAJ_MIC"},
+ {"IT33", NULL, "CS36"},
+ {"FU36", NULL, "IT33"},
+ {"OT36", NULL, "FU36"},
+ {"OT36", NULL, "CS36"},
+ {"AIF7 Capture", NULL, "OT36"},
+};
+
+static s32 tac_set_sdw_stream(struct snd_soc_dai *dai,
+ void *sdw_stream, s32 direction)
+{
+ if (sdw_stream)
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+
+ return 0;
+}
+
+static void tac_sdw_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int tac_clear_latch(struct tac5xx2_prv *priv)
+{
+ int ret;
+
+ ret = regmap_write(priv->regmap, TAC_INT_CFG, 0X00);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap, TAC_INT_CFG,
+ TAC_INT_CFG_CLR_REG, TAC_INT_CFG_CLR_REG);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, TAC_INT_CFG, 0X00);
+ return ret;
+}
+
+static int tac_sdw_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct tac5xx2_prv *tac_dev = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_config stream_config = {0};
+ struct sdw_port_config port_config = {0};
+ struct sdw_stream_runtime *sdw_stream;
+ struct sdw_slave *sdw_peripheral = tac_dev->sdw_peripheral;
+ unsigned long time;
+ int ret, retry;
+ int function_id;
+ int pde_entity;
+ int port_num;
+ unsigned int actual_ps = 3; /* off */
+ u8 sample_rate_idx = 0;
+
+ time = wait_for_completion_timeout(&sdw_peripheral->initialization_complete,
+ msecs_to_jiffies(TAC5XX2_PROBE_TIMEOUT_MS));
+ if (!time) {
+ dev_warn(tac_dev->dev, "%s: hw initialization timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+ if (!tac_dev->hw_init) {
+ dev_err(tac_dev->dev,
+ "error: operation without hw initialization");
+ return -EINVAL;
+ }
+
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (!sdw_stream) {
+ dev_err(tac_dev->dev, "failed to get dma data");
+ return -EINVAL;
+ }
+
+ ret = tac_clear_latch(tac_dev);
+ if (ret)
+ dev_warn(tac_dev->dev, "clear latch failed, err=%d", ret);
+
+ switch (dai->id) {
+ case TAC5XX2_DMIC:
+ function_id = TAC_FUNCTION_ID_SM;
+ pde_entity = TAC_SDCA_ENT_PDE11;
+ port_num = TAC_SDW_PORT_NUM_DMIC;
+ break;
+ case TAC5XX2_UAJ:
+ function_id = TAC_FUNCTION_ID_UAJ;
+ pde_entity = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ TAC_SDCA_ENT_PDE47 : TAC_SDCA_ENT_PDE34;
+ port_num = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ TAC_SDW_PORT_NUM_UAJ_PLAYBACK :
+ TAC_SDW_PORT_NUM_UAJ_CAPTURE;
+ /* Detect and set jack type for UAJ path before playback.
+ * This is required as jack is not triggering interrupt
+ * when the device is in suspended mode.
+ */
+ tac5xx2_sdca_headset_detect(tac_dev);
+ break;
+ case TAC5XX2_SPK:
+ function_id = TAC_FUNCTION_ID_SA;
+ pde_entity = TAC_SDCA_ENT_PDE23;
+ port_num = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ TAC_SDW_PORT_NUM_SPK_PLAYBACK :
+ TAC_SDW_PORT_NUM_SPK_CAPTURE;
+ break;
+ default:
+ dev_err(tac_dev->dev, "Invalid dai id: %d", dai->id);
+ return -EINVAL;
+ }
+
+ ret = regmap_write(tac_dev->regmap, SDW_SDCA_CTL(function_id, pde_entity,
+ TAC_SDCA_REQUESTED_PS, 0),
+ 0x03);
+
+ snd_sdw_params_to_config(substream, params, &stream_config, &port_config);
+ port_config.num = port_num;
+ ret = sdw_stream_add_slave(sdw_peripheral, &stream_config,
+ &port_config, 1, sdw_stream);
+ if (ret)
+ dev_err(dai->dev,
+ "Unable to configure port %d: %d\n", port_num, ret);
+
+ switch (params_rate(params)) {
+ case 48000:
+ sample_rate_idx = 0x01;
+ break;
+ case 44100:
+ sample_rate_idx = 0x02;
+ break;
+ case 96000:
+ sample_rate_idx = 0x03;
+ break;
+ case 88200:
+ sample_rate_idx = 0x04;
+ break;
+ default:
+ dev_dbg(tac_dev->dev, "Unsupported sample rate: %d Hz",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ switch (function_id) {
+ case TAC_FUNCTION_ID_SM:
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, TAC_SDCA_ENT_PPU11,
+ TAC_SDCA_CTL_PPU_POSTURE_NUM, 0),
+ 0);
+ if (ret) {
+ dev_err(tac_dev->dev, "Failed to set PPU11: %d", ret);
+ return ret;
+ }
+
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, TAC_SDCA_ENT_CS113,
+ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0),
+ sample_rate_idx);
+ if (ret) {
+ dev_err(tac_dev->dev, "Failed to set CS113 sample rate: %d", ret);
+ return ret;
+ }
+
+ if (tac_dev->cx11_value) {
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, TAC_SDCA_ENT_CS11,
+ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0),
+ sample_rate_idx);
+ } else {
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, TAC_SDCA_ENT_CS18,
+ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0),
+ sample_rate_idx);
+ }
+ if (ret) {
+ dev_err(tac_dev->dev, "Failed to set %s sample rate: %d",
+ tac_dev->cx11_value ? "CS11" : "CS18", ret);
+ return ret;
+ }
+ break;
+ case TAC_FUNCTION_ID_UAJ:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, TAC_SDCA_ENT_CS41,
+ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0),
+ sample_rate_idx);
+ if (ret) {
+ dev_err(tac_dev->dev, "Failed to set CS41 sample rate: %d", ret);
+ return ret;
+ }
+ } else {
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, TAC_SDCA_ENT_CS36,
+ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0),
+ sample_rate_idx);
+ if (ret) {
+ dev_err(tac_dev->dev, "Failed to set CS36 sample rate: %d", ret);
+ return ret;
+ }
+ }
+ break;
+ case TAC_FUNCTION_ID_SA:
+ /* SmartAmp: no additional sample rate configuration needed */
+ break;
+ }
+
+ mutex_lock(&tac_dev->pde_lock);
+ retry = 3;
+ /* make sure that power transition write is successful, before checking atual ps */
+ do {
+ ret = regmap_write(tac_dev->regmap, SDW_SDCA_CTL(function_id, pde_entity,
+ TAC_SDCA_REQUESTED_PS, 0),
+ 0x00);
+ if (!ret)
+ break;
+
+ dev_err(tac_dev->dev, "requested PS write err=%d, retry=%d", ret, retry);
+ usleep_range(1000, 1200);
+ } while (retry--);
+
+ retry = 3;
+ do {
+ ret = regmap_read(tac_dev->regmap, SDW_SDCA_CTL(function_id, pde_entity,
+ TAC_SDCA_ACTUAL_PS, 0),
+ &actual_ps);
+ if (ret) {
+ dev_err(tac_dev->dev, "read actual PS err=%d: retry=%d", ret, retry);
+ continue;
+ }
+
+ if (!actual_ps)
+ break;
+
+ if (retry)
+ usleep_range(1000, 1200);
+ } while (retry--);
+
+ if (actual_ps != 0x00)
+ dev_warn(tac_dev->dev, "err PDE transition to D0: PS=0x%x\n",
+ actual_ps);
+ mutex_unlock(&tac_dev->pde_lock);
+
+ return 0;
+}
+
+static s32 tac_sdw_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ s32 ret;
+ struct snd_soc_component *component = dai->component;
+ struct tac5xx2_prv *tac_dev =
+ snd_soc_component_get_drvdata(component);
+ struct sdw_stream_runtime *sdw_stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+ int pde_entity, function_id;
+
+ sdw_stream_remove_slave(tac_dev->sdw_peripheral, sdw_stream);
+
+ switch (dai->id) {
+ case TAC5XX2_DMIC:
+ pde_entity = TAC_SDCA_ENT_PDE11;
+ function_id = TAC_FUNCTION_ID_SM;
+ break;
+ case TAC5XX2_UAJ:
+ function_id = TAC_FUNCTION_ID_UAJ;
+ pde_entity = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ TAC_SDCA_ENT_PDE47 : TAC_SDCA_ENT_PDE34;
+ break;
+ default:
+ function_id = TAC_FUNCTION_ID_SA;
+ pde_entity = TAC_SDCA_ENT_PDE23;
+ break;
+ }
+ mutex_lock(&tac_dev->pde_lock);
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(function_id, pde_entity, 0x01, 0),
+ 0x03);
+ mutex_unlock(&tac_dev->pde_lock);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops tac_dai_ops = {
+ .hw_params = tac_sdw_hw_params,
+ .hw_free = tac_sdw_pcm_hw_free,
+ .set_stream = tac_set_sdw_stream,
+ .shutdown = tac_sdw_shutdown,
+};
+
+static int tac5xx2_sdca_btn_type(unsigned char *buffer, struct tac5xx2_prv *tac_dev)
+{
+ switch (*buffer) {
+ case 1: /* play pause */
+ return SND_JACK_BTN_0;
+ case 10: /* vol down */
+ return SND_JACK_BTN_3;
+ case 8: /* vol up */
+ return SND_JACK_BTN_2;
+ case 4: /* long press */
+ return SND_JACK_BTN_1;
+ case 2: /* next song */
+ case 32: /* next song */
+ return SND_JACK_BTN_4;
+ default:
+ return 0;
+ }
+}
+
+static int tac5xx2_sdca_button_detect(struct tac5xx2_prv *tac_dev)
+{
+ unsigned int btn_type, offset, idx;
+ int ret, value, owner;
+ u8 buf[2];
+
+ ret = regmap_read(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_HID, TAC_SDCA_ENT_HID1,
+ TAC_SDCA_CTL_HIDTX_CURRENT_OWNER, 0), &owner);
+ if (ret) {
+ dev_err(tac_dev->dev,
+ "Failed to read current UMP message owner 0x%x", ret);
+ return ret;
+ }
+
+ if (owner == SDCA_UMP_OWNER_DEVICE) {
+ dev_dbg(tac_dev->dev, "skip button detect as current owner is not host\n");
+ return 0;
+ }
+
+ ret = regmap_read(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_HID, TAC_SDCA_ENT_HID1,
+ TAC_SDCA_CTL_HIDTX_MESSAGE_OFFSET, 0), &value);
+ if (ret) {
+ dev_err(tac_dev->dev,
+ "Failed to read current UMP message offset: %d", ret);
+ goto end_btn_det;
+ }
+
+ dev_dbg(tac_dev->dev, "btn_ message offset = %x", value);
+ offset = value;
+
+ for (idx = 0; idx < sizeof(buf); idx++) {
+ ret = regmap_read(tac_dev->regmap,
+ TAC_BUF_ADDR_HID1 + offset + idx, &value);
+ if (ret) {
+ dev_err(tac_dev->dev,
+ "Failed to read HID buffer: %d", ret);
+ goto end_btn_det;
+ }
+ buf[idx] = value & 0xff;
+ }
+
+ if (buf[0] == 0x1) {
+ btn_type = tac5xx2_sdca_btn_type(&buf[1], tac_dev);
+ ret = btn_type;
+ }
+
+end_btn_det:
+ regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_HID, TAC_SDCA_ENT_HID1,
+ TAC_SDCA_CTL_HIDTX_CURRENT_OWNER, 0), 0x01);
+
+ return ret;
+}
+
+static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev)
+{
+ int val, ret;
+
+ if (!tac_has_uaj_support(tac_dev))
+ return 0;
+
+ ret = regmap_read(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35,
+ TAC_SDCA_CTL_DET_MODE, 0), &val);
+ if (ret) {
+ dev_err(tac_dev->dev, "Failed to read the detect mode");
+ return ret;
+ }
+
+ switch (val) {
+ case 3:
+ tac_dev->jack_type = SND_JACK_LINEOUT;
+ break;
+ case 4:
+ tac_dev->jack_type = SND_JACK_MICROPHONE;
+ break;
+ case 5:
+ tac_dev->jack_type = SND_JACK_HEADPHONE;
+ break;
+ case 6:
+ tac_dev->jack_type = SND_JACK_HEADSET;
+ break;
+ case 7:
+ tac_dev->jack_type = SND_JACK_LINEIN;
+ break;
+ case 0:
+ default:
+ tac_dev->jack_type = 0;
+ break;
+ }
+
+ ret = regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35,
+ TAC_SDCA_CTL_SEL_MODE, 0), val);
+ if (ret)
+ dev_err(tac_dev->dev, "Failed to update the jack type to device");
+
+ return 0;
+}
+
+static int tac5xx2_set_jack(struct snd_soc_component *component,
+ struct snd_soc_jack *hs_jack, void *data)
+{
+ struct tac5xx2_prv *tac_dev = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ if (!tac_has_uaj_support(tac_dev))
+ return 0;
+
+ tac_dev->hs_jack = hs_jack;
+ if (!tac_dev->hw_init) {
+ dev_err(tac_dev->dev, "jack init failed, hw not initialized");
+ return 0;
+ }
+
+ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2,
+ SDW_SCP_SDCA_INTMASK_SDCA_11);
+ if (ret)
+ dev_warn(tac_dev->dev,
+ "Failed to register jack detection interrupt");
+
+ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3,
+ SDW_SCP_SDCA_INTMASK_SDCA_16);
+ if (ret)
+ dev_warn(tac_dev->dev,
+ "Failed to register for button detect interrupt");
+
+ return ret;
+}
+
+static int tac_interrupt_callback(struct sdw_slave *slave,
+ struct sdw_slave_intr_status *status)
+{
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev);
+ struct device *dev = &slave->dev;
+ int ret = 0;
+ int btn_type = 0;
+ unsigned int sdca_int3;
+
+ if (status->control_port) {
+ if (status->control_port & SDW_SCP_INT1_PARITY)
+ dev_warn(dev, "SCP: Parity error interrupt");
+ if (status->control_port & SDW_SCP_INT1_BUS_CLASH)
+ dev_warn(dev, "SCP: Bus clash interrupt");
+ }
+
+ ret = regmap_read(tac_dev->regmap, SDW_SCP_SDCA_INT3, &sdca_int3);
+ if (ret) {
+ dev_err(dev, "Failed to read SDCA_INT3: %d", ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "SDCA_INT3: 0x%02x", sdca_int3);
+ ret = tac5xx2_sdca_headset_detect(tac_dev);
+ if (ret < 0)
+ goto clear;
+
+ btn_type = tac5xx2_sdca_button_detect(tac_dev);
+ if (btn_type < 0)
+ btn_type = 0;
+
+ if (tac_dev->jack_type == 0)
+ btn_type = 0;
+
+ dev_dbg(tac_dev->dev, "in %s, jack_type=%d\n", __func__, tac_dev->jack_type);
+ dev_dbg(tac_dev->dev, "in %s, btn_type=0x%x\n", __func__, btn_type);
+
+ if (!tac_dev->hs_jack)
+ goto clear;
+
+ snd_soc_jack_report(tac_dev->hs_jack, tac_dev->jack_type | btn_type,
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3 | SND_JACK_BTN_4);
+
+clear:
+ /* clear hid interrupt */
+ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INT3, sdca_int3);
+ if (ret)
+ dev_dbg(tac_dev->dev, "Failed to clear SDW_SCP_SDCA_INT3");
+ return ret;
+}
+
+static struct snd_soc_dai_driver tac5572_dai_driver[] = {
+ {
+ .name = "tac5xx2-aif1",
+ .id = TAC5XX2_SPK,
+ .playback = {
+ .stream_name = "DP1 Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+ {
+ .name = "tac5xx2-aif2",
+ .id = TAC5XX2_DMIC,
+ .capture = {
+ .stream_name = "DP3 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+ {
+ .name = "tac5xx2-aif3",
+ .id = TAC5XX2_UAJ,
+ .playback = {
+ .stream_name = "DP4 UAJ Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP7 UAJ Mic Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+};
+
+static struct snd_soc_dai_driver tac5672_dai_driver[] = {
+ {
+ .name = "tac5xx2-aif1",
+ .id = TAC5XX2_SPK,
+ .playback = {
+ .stream_name = "DP1 Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP8 IV Sense Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ .symmetric_rate = 1,
+ },
+ {
+ .name = "tac5xx2-aif2",
+ .id = TAC5XX2_DMIC,
+ .capture = {
+ .stream_name = "DP3 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+ {
+ .name = "tac5xx2-aif3",
+ .id = TAC5XX2_UAJ,
+ .playback = {
+ .stream_name = "DP4 UAJ Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP7 UAJ Mic Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+};
+
+static struct snd_soc_dai_driver tac5682_dai_driver[] = {
+ {
+ .name = "tac5xx2-aif1",
+ .id = TAC5XX2_SPK,
+ .playback = {
+ .stream_name = "DP1 Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP2 Echo Reference Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ .symmetric_rate = 1,
+ },
+ {
+ .name = "tac5xx2-aif2",
+ .id = TAC5XX2_DMIC,
+ .capture = {
+ .stream_name = "DP3 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+ {
+ .name = "tac5xx2-aif3",
+ .id = TAC5XX2_UAJ,
+ .playback = {
+ .stream_name = "DP4 UAJ Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP7 UAJ Mic Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+};
+
+static struct snd_soc_dai_driver tas2883_dai_driver[] = {
+ {
+ .name = "tac5xx2-aif1",
+ .id = TAC5XX2_SPK,
+ .playback = {
+ .stream_name = "DP1 Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ .symmetric_rate = 1,
+ },
+ {
+ .name = "tac5xx2-aif2",
+ .id = TAC5XX2_DMIC,
+ .capture = {
+ .stream_name = "DP3 Mic Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAC5XX2_DEVICE_RATES,
+ .formats = TAC5XX2_DEVICE_FORMATS,
+ },
+ .ops = &tac_dai_ops,
+ },
+};
+
+static s32 tac_component_probe(struct snd_soc_component *component)
+{
+ struct tac5xx2_prv *tac_dev =
+ snd_soc_component_get_drvdata(component);
+ struct device *dev = tac_dev->dev;
+ struct sdw_slave *slave = tac_dev->sdw_peripheral;
+ unsigned long time;
+ int ret;
+
+ /* Wait for SoundWire hw initialization to complete */
+ time = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(TAC5XX2_PROBE_TIMEOUT_MS));
+ if (!time) {
+ dev_warn(dev, "%s: hw initialization timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ if (!tac_has_uaj_support(tac_dev))
+ goto done_comp_probe;
+
+ ret = snd_soc_dapm_new_controls(snd_soc_component_to_dapm(component),
+ tac_uaj_widgets,
+ ARRAY_SIZE(tac_uaj_widgets));
+ if (ret) {
+ dev_err(component->dev, "Failed to add UAJ widgets: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component),
+ tac_uaj_routes, ARRAY_SIZE(tac_uaj_routes));
+ if (ret) {
+ dev_err(component->dev, "Failed to add UAJ routes: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_add_component_controls(component, tac_uaj_controls,
+ ARRAY_SIZE(tac_uaj_controls));
+ if (ret) {
+ dev_err(dev, "Failed to add UAJ controls: %d\n", ret);
+ return ret;
+ }
+
+done_comp_probe:
+ tac_dev->component = component;
+ return 0;
+}
+
+static void tac_component_remove(struct snd_soc_component *codec)
+{
+ struct tac5xx2_prv *tac_dev = snd_soc_component_get_drvdata(codec);
+
+ tac_dev->component = NULL;
+}
+
+static const struct snd_soc_component_driver soc_codec_driver_tacdevice = {
+ .probe = tac_component_probe,
+ .remove = tac_component_remove,
+ .controls = tac5xx2_snd_controls,
+ .num_controls = ARRAY_SIZE(tac5xx2_snd_controls),
+ .dapm_widgets = tac5xx2_common_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tac5xx2_common_widgets),
+ .dapm_routes = tac5xx2_common_routes,
+ .num_dapm_routes = ARRAY_SIZE(tac5xx2_common_routes),
+ .idle_bias_on = 0,
+ .endianness = 1,
+ .set_jack = tac5xx2_set_jack,
+};
+
+static s32 tac_init(struct tac5xx2_prv *tac_dev)
+{
+ s32 ret;
+ struct snd_soc_dai_driver *dai_drv;
+ int num_dais;
+
+ dev_set_drvdata(tac_dev->dev, tac_dev);
+
+ switch (tac_dev->part_id) {
+ case 0x5572:
+ dai_drv = tac5572_dai_driver;
+ num_dais = ARRAY_SIZE(tac5572_dai_driver);
+ break;
+ case 0x5672:
+ dai_drv = tac5672_dai_driver;
+ num_dais = ARRAY_SIZE(tac5672_dai_driver);
+ break;
+ case 0x5682:
+ dai_drv = tac5682_dai_driver;
+ num_dais = ARRAY_SIZE(tac5682_dai_driver);
+ break;
+ case 0x2883:
+ dai_drv = tas2883_dai_driver;
+ num_dais = ARRAY_SIZE(tas2883_dai_driver);
+ break;
+ default:
+ dev_err(tac_dev->dev, "Unsupported device: 0x%x\n",
+ tac_dev->part_id);
+ return -EINVAL;
+ }
+
+ ret = devm_snd_soc_register_component(tac_dev->dev,
+ &soc_codec_driver_tacdevice,
+ dai_drv, num_dais);
+ if (ret) {
+ dev_err(tac_dev->dev, "%s: codec register error:%d.\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static s32 tac5xx2_sdca_dev_suspend(struct device *dev)
+{
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(dev);
+
+ if (!tac_dev->hw_init)
+ return 0;
+
+ regcache_cache_only(tac_dev->regmap, true);
+ return 0;
+}
+
+static s32 tac5xx2_sdca_dev_system_suspend(struct device *dev)
+{
+ return tac5xx2_sdca_dev_suspend(dev);
+}
+
+static s32 tac5xx2_sdca_dev_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(dev);
+ unsigned long t;
+ int ret;
+
+ if (!tac_dev->hw_init || !tac_dev->first_hw_init) {
+ dev_dbg(dev, "Device not initialized yet, skipping resume sync\n");
+ return 0;
+ }
+
+ if (!slave->unattach_request)
+ goto regmap_sync;
+
+ t = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(TAC5XX2_PROBE_TIMEOUT_MS));
+ if (!t) {
+ dev_err(&slave->dev, "resume: initialization timed out\n");
+ sdw_show_ping_status(slave->bus, true);
+ return -ETIMEDOUT;
+ }
+ slave->unattach_request = 0;
+
+regmap_sync:
+ regcache_cache_only(tac_dev->regmap, false);
+ regcache_mark_dirty(tac_dev->regmap);
+ ret = regcache_sync(tac_dev->regmap);
+ if (ret < 0)
+ dev_warn(dev, "Failed to sync regcache: %d\n", ret);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tac5xx2_sdca_pm = {
+ SYSTEM_SLEEP_PM_OPS(tac5xx2_sdca_dev_system_suspend, tac5xx2_sdca_dev_resume)
+ RUNTIME_PM_OPS(tac5xx2_sdca_dev_suspend, tac5xx2_sdca_dev_resume, NULL)
+};
+
+static s32 tac_fw_read_hdr(const u8 *data, struct tac_fw_hdr *hdr)
+{
+ hdr->size = get_unaligned_le32(data);
+
+ return TAC_FW_HDR_SIZE;
+}
+
+static s32 tac_load_and_cache_firmware(struct tac5xx2_prv *tac_dev)
+{
+ const struct firmware *fmw = NULL;
+ const char *fw_name_used = NULL;
+ s32 ret = 0;
+ u8 *cached_data = NULL;
+ u32 fw_hdr_size;
+
+ mutex_lock(&tac_dev->pde_lock);
+ ret = request_firmware(&fmw, tac_dev->fw_binaryname, tac_dev->dev);
+ if (ret || !fmw) {
+ dev_err(tac_dev->dev,
+ "Failed to read fw binary %s, err=%d\n",
+ tac_dev->fw_binaryname, ret);
+ mutex_unlock(&tac_dev->pde_lock);
+ return -EINVAL;
+ }
+
+ if (!fmw->data || fmw->size == 0 || fmw->size < TAC_FW_HDR_SIZE + TAC_FW_FILE_HDR) {
+ dev_err(tac_dev->dev, "fw file: %s is empty or invalid\n",
+ tac_dev->fw_binaryname);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Verify firmware size from header */
+ fw_hdr_size = get_unaligned_le32(fmw->data);
+ if (fw_hdr_size != fmw->size) {
+ ret = -EINVAL;
+ dev_err(tac_dev->dev, "firmware size mismatch: hdr=%u, actual=%zu\n",
+ fw_hdr_size, fmw->size);
+ goto out;
+ }
+
+ fw_name_used = tac_dev->fw_binaryname;
+
+ cached_data = devm_kmemdup(tac_dev->dev, fmw->data, fmw->size, GFP_KERNEL);
+ if (!cached_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ tac_dev->fw_data = cached_data;
+ tac_dev->fw_size = fmw->size;
+ tac_dev->fw_cached = true;
+
+ dev_dbg(tac_dev->dev, "fw file: %s cached successfully, size=%zu\n",
+ fw_name_used, tac_dev->fw_size);
+
+out:
+ release_firmware(fmw);
+ mutex_unlock(&tac_dev->pde_lock);
+
+ return ret;
+}
+
+static s32 tac_fw_get_next_file(const u8 *data, size_t data_size, struct tac_fw_file *file)
+{
+ u32 file_length;
+
+ /* Validate file header size */
+ if (data_size < TAC_FW_FILE_HDR)
+ return -EINVAL;
+
+ file->vendor_id = get_unaligned_le32(&data[0]);
+ file->file_id = get_unaligned_le32(&data[4]);
+ file->version = get_unaligned_le32(&data[8]);
+ file->length = get_unaligned_le32(&data[12]);
+ file->dest_addr = get_unaligned_le32(&data[16]);
+ file_length = file->length;
+
+ /* Validate file payload exists */
+ if (data_size < TAC_FW_FILE_HDR + file_length)
+ return -EINVAL;
+
+ file->fw_data = (u8 *)&data[20];
+
+ return file_length + sizeof(u32) * 5;
+}
+
+static s32 tac_download(struct tac5xx2_prv *tac_dev,
+ struct tac_fw_file *files, int num_files)
+{
+ s32 ret = 0;
+ u32 i;
+
+ for (i = 0; i < num_files; i++) {
+ ret = sdw_nwrite_no_pm(tac_dev->sdw_peripheral, files[i].dest_addr,
+ files[i].length, files[i].fw_data);
+ if (ret < 0) {
+ dev_err(tac_dev->dev,
+ "FW write failed at addr 0x%x: %d\n",
+ files[i].dest_addr, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * tac5xx2 uses custom firmware binary fw.
+ * This is not using UMP File Download.
+ */
+static s32 tac_download_fw_to_hw(struct tac5xx2_prv *tac_dev)
+{
+ const u8 *buf = NULL;
+ struct tac_fw_hdr *hdr = NULL;
+ struct tac_fw_file *files = NULL;
+ size_t img_sz;
+ s32 ret = 0, num_files = 0;
+ s32 offset;
+
+ mutex_lock(&tac_dev->pde_lock);
+
+ if (!tac_dev->fw_cached || !tac_dev->fw_data) {
+ dev_err(tac_dev->dev, "No cached firmware available\n");
+ mutex_unlock(&tac_dev->pde_lock);
+ return -EINVAL;
+ }
+
+ hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
+ files = kcalloc(TAC_MAX_FW_CHUNKS, sizeof(*files), GFP_KERNEL);
+ if (!files || !hdr) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ buf = tac_dev->fw_data;
+ img_sz = tac_dev->fw_size;
+
+ dev_dbg(tac_dev->dev, "Downloading cached firmware to HW, size=%zu\n", img_sz);
+
+ offset = tac_fw_read_hdr(buf, hdr);
+ while (offset < img_sz && num_files < TAC_MAX_FW_CHUNKS) {
+ u32 file_length;
+
+ if (offset + TAC_FW_FILE_HDR > img_sz) {
+ dev_warn(tac_dev->dev, "Incomplete block header at offset %d\n",
+ offset);
+ break;
+ }
+ /* Validate that the file payload doesn't exceed buffer */
+ file_length = get_unaligned_le32(&buf[offset + 12]);
+ /* Check for integer overflow and buffer bounds */
+ if (file_length > img_sz || offset > img_sz - TAC_FW_FILE_HDR ||
+ file_length > img_sz - offset - TAC_FW_FILE_HDR) {
+ dev_warn(tac_dev->dev, "File at offset %d exceeds buffer: length=%u, available=%zu\n",
+ offset, file_length, img_sz - offset - TAC_FW_FILE_HDR);
+ break;
+ }
+ ret = tac_fw_get_next_file(&buf[offset], img_sz - offset, &files[num_files]);
+ if (ret < 0) {
+ dev_err(tac_dev->dev, "Failed to parse file at offset %d\n", offset);
+ goto out;
+ }
+ offset += ret;
+ num_files++;
+ }
+
+ if (num_files == 0) {
+ dev_err(tac_dev->dev, "firmware with no files\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = tac_download(tac_dev, files, num_files);
+ if (ret < 0) {
+ dev_err(tac_dev->dev, "Firmware download failed: %d\n", ret);
+ goto out;
+ }
+
+ dev_dbg(tac_dev->dev, "Firmware download complete: %d chunks\n", num_files);
+ tac_dev->fw_dl_success = true;
+
+out:
+ kfree(hdr);
+ kfree(files);
+ mutex_unlock(&tac_dev->pde_lock);
+
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_PCI)
+static struct pci_dev *tac_get_pci_dev(struct sdw_slave *peripheral)
+{
+ struct device *dev = &peripheral->dev;
+
+ for (; dev; dev = dev->parent) {
+ if (dev->bus == &pci_bus_type)
+ return to_pci_dev(dev);
+ }
+
+ return NULL;
+}
+#endif
+
+static void tac_generate_fw_name(struct sdw_slave *slave, char *name, size_t size)
+{
+ struct sdw_bus *bus = slave->bus;
+ u16 part_id = slave->id.part_id;
+ u8 unique_id = slave->id.unique_id;
+#if IS_ENABLED(CONFIG_PCI)
+ struct pci_dev *pci = tac_get_pci_dev(slave);
+
+ if (pci) {
+ scnprintf(name, size, "%04X-%1X-%1X.bin",
+ pci->subsystem_device, bus->link_id, unique_id);
+ return;
+ }
+#endif
+ /* Default firmware name based on part ID */
+ scnprintf(name, size, "%s%04x-%1X-%1X.bin",
+ part_id == 0x2883 ? "tas" : "tac",
+ part_id, bus->link_id, unique_id);
+}
+
+static s32 tac_io_init(struct device *dev, struct sdw_slave *slave, bool first)
+{
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(dev);
+ s32 ret = 0;
+
+ if (tac_dev->hw_init) {
+ dev_dbg(dev, "early return hw_init already done..");
+ return 0;
+ }
+
+ if (tac_has_dsp_algo(tac_dev)) {
+ tac_generate_fw_name(slave, tac_dev->fw_binaryname,
+ sizeof(tac_dev->fw_binaryname));
+
+ if (!tac_dev->fw_cached) {
+ ret = tac_load_and_cache_firmware(tac_dev);
+ if (ret)
+ dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret);
+ }
+
+ if (tac_dev->fw_cached) {
+ ret = tac_download_fw_to_hw(tac_dev);
+ if (ret) {
+ dev_err(dev, "FW download failed, fw: %d\n", ret);
+ goto io_init_err;
+ }
+ }
+ }
+
+ if (tac_dev->sa_func_data) {
+ ret = sdca_regmap_write_init(dev, tac_dev->regmap,
+ tac_dev->sa_func_data);
+ if (ret) {
+ dev_err(dev, "smartamp init table update failed\n");
+ goto io_init_err;
+ }
+ dev_dbg(dev, "smartamp init done\n");
+
+ if (first) {
+ ret = regmap_multi_reg_write(tac_dev->regmap, tac_spk_seq,
+ ARRAY_SIZE(tac_spk_seq));
+ if (ret) {
+ dev_err(dev, "init writes failed, err=%d", ret);
+ goto io_init_err;
+ }
+ }
+ }
+
+ if (tac_dev->sm_func_data) {
+ ret = sdca_regmap_write_init(dev, tac_dev->regmap,
+ tac_dev->sm_func_data);
+ if (ret) {
+ dev_err(dev, "smartmic init table update failed\n");
+ goto io_init_err;
+ }
+ dev_dbg(dev, "smartmic init done\n");
+
+ if (first) {
+ /* Set default value to CS:11 */
+ tac_dev->cx11_value = 1;
+ regmap_write(tac_dev->regmap,
+ SDW_SDCA_CTL(TAC_FUNCTION_ID_SM,
+ TAC_SDCA_ENT_CX11,
+ TAC_SDCA_CTL_CX_CLK_SEL, 0),
+ tac_dev->cx11_value);
+
+ ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq,
+ ARRAY_SIZE(tac_sm_seq));
+ if (ret) {
+ dev_err(tac_dev->dev,
+ "init writes failed, err=%d", ret);
+ goto io_init_err;
+ }
+ }
+ }
+
+ if (tac_dev->uaj_func_data) {
+ ret = sdca_regmap_write_init(dev, tac_dev->regmap,
+ tac_dev->uaj_func_data);
+ if (ret) {
+ dev_err(dev, "uaj init table update failed\n");
+ goto io_init_err;
+ }
+ dev_dbg(dev, "uaj init done\n");
+
+ if (first) {
+ ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq,
+ ARRAY_SIZE(tac_uaj_seq));
+ if (ret) {
+ dev_err(tac_dev->dev,
+ "init writes failed, err=%d", ret);
+ goto io_init_err;
+ }
+ }
+ }
+
+ if (tac_dev->hid_func_data) {
+ ret = sdca_regmap_write_init(dev, tac_dev->regmap,
+ tac_dev->hid_func_data);
+ if (ret) {
+ dev_err(dev, "hid init table update failed\n");
+ goto io_init_err;
+ }
+ dev_dbg(dev, "hid init done\n");
+
+ /* register for interrupts */
+ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2,
+ SDW_SCP_SDCA_INTMASK_SDCA_11);
+ if (ret)
+ dev_err(dev, "Failed to register jack detection interrupt");
+
+ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3,
+ SDW_SCP_SDCA_INTMASK_SDCA_16);
+ if (ret)
+ dev_err(dev, "Failed to register for button detect interrupt");
+ }
+
+ tac_dev->hw_init = true;
+
+ return 0;
+
+io_init_err:
+ dev_err(dev, "init writes failed, err=%d", ret);
+ return ret;
+}
+
+static int tac_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ int ret;
+ bool first = false;
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev);
+ struct device *dev = &slave->dev;
+
+ tac_dev->status = status;
+ if (status == SDW_SLAVE_UNATTACHED) {
+ tac_dev->hw_init = false;
+ tac_dev->fw_dl_success = false;
+ }
+
+ if (tac_dev->hw_init || tac_dev->status != SDW_SLAVE_ATTACHED) {
+ dev_dbg(dev, "%s: early return, hw_init=%d, status=%d",
+ __func__, tac_dev->hw_init, tac_dev->status);
+ return 0;
+ }
+
+ if (!tac_dev->first_hw_init) {
+ pm_runtime_set_autosuspend_delay(tac_dev->dev, 3000);
+ pm_runtime_use_autosuspend(tac_dev->dev);
+ pm_runtime_mark_last_busy(tac_dev->dev);
+ pm_runtime_set_active(tac_dev->dev);
+ pm_runtime_enable(tac_dev->dev);
+ tac_dev->first_hw_init = true;
+ first = true;
+ }
+
+ pm_runtime_get_noresume(tac_dev->dev);
+
+ regcache_mark_dirty(tac_dev->regmap);
+ regcache_cache_only(tac_dev->regmap, false);
+ ret = tac_io_init(&slave->dev, slave, first);
+ if (ret) {
+ dev_err(dev, "Device initialization failed: %d\n", ret);
+ goto err_out;
+ }
+
+ ret = regcache_sync(tac_dev->regmap);
+ if (ret)
+ dev_warn(dev, "Failed to sync regcache after init: %d\n", ret);
+
+err_out:
+ pm_runtime_mark_last_busy(tac_dev->dev);
+ pm_runtime_put_autosuspend(tac_dev->dev);
+
+ return ret;
+}
+
+static int tac5xx2_sdw_clk_stop(struct sdw_slave *peripheral,
+ enum sdw_clk_stop_mode mode,
+ enum sdw_clk_stop_type type)
+{
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(&peripheral->dev);
+
+ dev_dbg(tac_dev->dev, "%s: mode:%d type:%d", __func__, mode, type);
+ return 0;
+}
+
+static int tac5xx2_sdw_read_prop(struct sdw_slave *peripheral)
+{
+ struct device *dev = &peripheral->dev;
+ int ret;
+
+ ret = sdw_slave_read_prop(peripheral);
+ if (ret) {
+ dev_err(dev, "sdw_slave_read_prop failed: %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tac_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch,
+ enum sdw_port_prep_ops pre_ops)
+{
+ struct device *dev = &slave->dev;
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ if (pre_ops != SDW_OPS_PORT_POST_PREP)
+ return 0;
+
+ if (!tac_dev->fw_dl_success)
+ return 0;
+
+ ret = regmap_read(tac_dev->regmap, TAC_DSP_ALGO_STATUS, &val);
+ if (ret) {
+ dev_err(dev, "Failed to read algo status: %d\n", ret);
+ return ret;
+ }
+
+ if (val != TAC_DSP_ALGO_STATUS_RUNNING) {
+ dev_dbg(dev, "Algo not running (0x%02x), re-enabling\n", val);
+ ret = regmap_write(tac_dev->regmap, TAC_DSP_ALGO_STATUS,
+ TAC_DSP_ALGO_STATUS_RUNNING);
+ if (ret) {
+ dev_err(dev, "Failed to re-enable algo: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct sdw_slave_ops tac_sdw_ops = {
+ .read_prop = tac5xx2_sdw_read_prop,
+ .update_status = tac_update_status,
+ .interrupt_callback = tac_interrupt_callback,
+ .clk_stop = tac5xx2_sdw_clk_stop,
+ .port_prep = tac_port_prep,
+};
+
+static void tac_remove(struct tac5xx2_prv *tac_dev)
+{
+ snd_soc_unregister_component(tac_dev->dev);
+}
+
+static s32 tac_sdw_probe(struct sdw_slave *peripheral,
+ const struct sdw_device_id *id)
+{
+ struct regmap *regmap;
+ struct device *dev = &peripheral->dev;
+ struct tac5xx2_prv *tac_dev;
+ struct sdca_function_data *function_data = NULL;
+ int ret, i;
+
+ tac_dev = devm_kzalloc(dev, sizeof(*tac_dev), GFP_KERNEL);
+ if (!tac_dev)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed devm_kzalloc");
+
+ if (peripheral->sdca_data.num_functions > 0) {
+ dev_dbg(dev, "SDCA functions found: %d",
+ peripheral->sdca_data.num_functions);
+
+ for (i = 0; i < peripheral->sdca_data.num_functions; i++) {
+ struct sdca_function_data **func_ptr;
+ const char *func_name;
+
+ switch (peripheral->sdca_data.function[i].type) {
+ case SDCA_FUNCTION_TYPE_SMART_AMP:
+ func_ptr = &tac_dev->sa_func_data;
+ func_name = "smartamp";
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_MIC:
+ func_ptr = &tac_dev->sm_func_data;
+ func_name = "smartmic";
+ break;
+ case SDCA_FUNCTION_TYPE_UAJ:
+ func_ptr = &tac_dev->uaj_func_data;
+ func_name = "uaj";
+ break;
+ case SDCA_FUNCTION_TYPE_HID:
+ func_ptr = &tac_dev->hid_func_data;
+ func_name = "hid";
+ break;
+ default:
+ continue;
+ }
+
+ function_data = devm_kzalloc(dev, sizeof(*function_data),
+ GFP_KERNEL);
+ if (!function_data)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to allocate %s function data",
+ func_name);
+
+ ret = sdca_parse_function(dev, peripheral,
+ &peripheral->sdca_data.function[i],
+ function_data);
+ if (!ret)
+ *func_ptr = function_data;
+ else
+ devm_kfree(dev, function_data);
+ }
+ }
+
+ dev_dbg(dev, "SDCA functions enabled: SA=%s SM=%s UAJ=%s HID=%s",
+ tac_dev->sa_func_data ? "yes" : "no",
+ tac_dev->sm_func_data ? "yes" : "no",
+ tac_dev->uaj_func_data ? "yes" : "no",
+ tac_dev->hid_func_data ? "yes" : "no");
+
+ tac_dev->dev = dev;
+ tac_dev->sdw_peripheral = peripheral;
+ tac_dev->hw_init = false;
+ tac_dev->first_hw_init = false;
+ mutex_init(&tac_dev->pde_lock);
+ tac_dev->part_id = id->part_id;
+ dev_set_drvdata(dev, tac_dev);
+
+ regmap = devm_regmap_init_sdw_mbq_cfg(&peripheral->dev, peripheral,
+ &tac_regmap, &tac_mbq_cfg);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed devm_regmap_init_sdw\n");
+
+ regcache_cache_only(regmap, true);
+ tac_dev->regmap = regmap;
+ tac_dev->jack_type = 0;
+
+ return tac_init(tac_dev);
+}
+
+static void tac_sdw_remove(struct sdw_slave *peripheral)
+{
+ struct tac5xx2_prv *tac_dev = dev_get_drvdata(&peripheral->dev);
+
+ tac_remove(tac_dev);
+ mutex_destroy(&tac_dev->pde_lock);
+ dev_set_drvdata(&peripheral->dev, NULL);
+}
+
+static const struct sdw_device_id tac_sdw_id[] = {
+ SDW_SLAVE_ENTRY(0x0102, 0x5572, 0),
+ SDW_SLAVE_ENTRY(0x0102, 0x5672, 0),
+ SDW_SLAVE_ENTRY(0x0102, 0x5682, 0),
+ SDW_SLAVE_ENTRY(0x0102, 0x2883, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, tac_sdw_id);
+
+static struct sdw_driver tac_sdw_driver = {
+ .driver = {
+ .name = "slave-tac5xx2",
+ .pm = pm_ptr(&tac5xx2_sdca_pm),
+ },
+ .probe = tac_sdw_probe,
+ .remove = tac_sdw_remove,
+ .ops = &tac_sdw_ops,
+ .id_table = tac_sdw_id,
+};
+module_sdw_driver(tac_sdw_driver);
+
+MODULE_IMPORT_NS("SND_SOC_SDCA");
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("ASoC TAC5XX2 SoundWire Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h
new file mode 100644
index 0000000000000..eed8e6cf3498b
--- /dev/null
+++ b/sound/soc/codecs/tac5xx2.h
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * ALSA SoC Texas Instruments TAC5XX2 Audio Smart Amplifier
+ *
+ * Copyright (C) 2025 Texas Instruments Incorporated
+ * https://www.ti.com
+ *
+ * This the header file for TAC5XX2 family of devices
+ * which includes TAC5572, TAC5672, TAC5682 and TAS2883
+ *
+ * Author: Niranjan H Y <niranjanhy@ti.com>
+ */
+#ifndef __RGL_TAC5XX2_H__
+#define __RGL_TAC5XX2_H__
+
+/* for soundwire */
+#define TAC_REG_SDW(book, page, reg) (((book) * 256 * 128) + \
+ 0x3000000 + ((page) * 128) + (reg))
+
+/* page 0 registers */
+#define TAC_SW_RESET TAC_REG_SDW(0, 0, 1)
+#define TAC_SLEEP_MODEZ TAC_REG_SDW(0, 0, 2)
+#define TAC_FEATURE_PDZ TAC_REG_SDW(0, 0, 3)
+#define TAC_TX_CH_EN TAC_REG_SDW(0, 0, 4)
+#define TAC_RX_CH_PD TAC_REG_SDW(0, 0, 5)
+#define TAC_SHDNZ_CFG TAC_REG_SDW(0, 0, 6)
+#define TAC_MISC_CFG0 TAC_REG_SDW(0, 0, 7)
+#define TAC_MISC_CFG1 TAC_REG_SDW(0, 0, 8)
+#define TAC_GPIO1_CFG0 TAC_REG_SDW(0, 0, 9)
+#define TAC_GPIO2_CFG0 TAC_REG_SDW(0, 0, 10)
+#define TAC_GPIO3_CFG0 TAC_REG_SDW(0, 0, 11)
+#define TAC_GPIO4_CFG0 TAC_REG_SDW(0, 0, 12)
+#define TAC_GPIO5_CFG0 TAC_REG_SDW(0, 0, 13)
+#define TAC_GPIO6_CFG0 TAC_REG_SDW(0, 0, 14)
+#define TAC_INTF_CFG1 TAC_REG_SDW(0, 0, 15)
+#define TAC_INTF_CFG5 TAC_REG_SDW(0, 0, 16)
+#define TAC_PASI_BCLK_CFG0 TAC_REG_SDW(0, 0, 17)
+#define TAC_PASI_FSYNC_CFG0 TAC_REG_SDW(0, 0, 18)
+#define TAC_PASI_DIN1_CFG0 TAC_REG_SDW(0, 0, 19)
+#define TAC_PASI_DIN2_CFG0 TAC_REG_SDW(0, 0, 20)
+#define TAC_PDM_DIN1_CFG0 TAC_REG_SDW(0, 0, 21)
+#define TAC_PDM_DIN2_CFG0 TAC_REG_SDW(0, 0, 22)
+#define TAC_MCLK_SEL TAC_REG_SDW(0, 0, 23)
+#define TAC_I2C2_CFG0 TAC_REG_SDW(0, 0, 24)
+#define TAC_SDW_IO_CFG0 TAC_REG_SDW(0, 0, 25)
+#define TAC_SDW_CLK_CFG0 TAC_REG_SDW(0, 0, 26)
+#define TAC_PASI_CFG0 TAC_REG_SDW(0, 0, 27)
+#define TAC_PASI_CFG1 TAC_REG_SDW(0, 0, 28)
+#define TAC_PASI_TX_CFG0 TAC_REG_SDW(0, 0, 29)
+#define TAC_PASI_TX_CFG1 TAC_REG_SDW(0, 0, 30)
+#define TAC_PASI_TX_CFG2 TAC_REG_SDW(0, 0, 31)
+#define TAC_PASI_TX_CFG3 TAC_REG_SDW(0, 0, 32)
+#define TAC_PASI_TX_CH1_CFG0 TAC_REG_SDW(0, 0, 33)
+#define TAC_PASI_TX_CH2_CFG0 TAC_REG_SDW(0, 0, 34)
+#define TAC_PASI_TX_CH3_CFG0 TAC_REG_SDW(0, 0, 35)
+#define TAC_PASI_TX_CH4_CFG0 TAC_REG_SDW(0, 0, 36)
+#define TAC_PASI_TX_CH5_CFG0 TAC_REG_SDW(0, 0, 37)
+#define TAC_PASI_TX_CH6_CFG0 TAC_REG_SDW(0, 0, 38)
+#define TAC_PASI_TX_CH7_CFG0 TAC_REG_SDW(0, 0, 39)
+#define TAC_PASI_TX_CH8_CFG0 TAC_REG_SDW(0, 0, 40)
+#define TAC_PASI_RX_CFG0 TAC_REG_SDW(0, 0, 41)
+#define TAC_PASI_RX_CFG1 TAC_REG_SDW(0, 0, 42)
+#define TAC_PASI_RX_CFG2 TAC_REG_SDW(0, 0, 43)
+#define TAC_PASI_RX_CH1_CFG0 TAC_REG_SDW(0, 0, 44)
+#define TAC_PASI_RX_CH2_CFG0 TAC_REG_SDW(0, 0, 45)
+#define TAC_PASI_RX_CH3_CFG0 TAC_REG_SDW(0, 0, 46)
+#define TAC_PASI_RX_CH4_CFG0 TAC_REG_SDW(0, 0, 47)
+#define TAC_PASI_RX_CH5_CFG0 TAC_REG_SDW(0, 0, 48)
+#define TAC_PASI_RX_CH6_CFG0 TAC_REG_SDW(0, 0, 49)
+#define TAC_PASI_RX_CH7_CFG0 TAC_REG_SDW(0, 0, 50)
+#define TAC_PASI_RX_CH8_CFG0 TAC_REG_SDW(0, 0, 51)
+#define TAC_ADC_CH1_CFG0 TAC_REG_SDW(0, 0, 52)
+#define TAC_ADC_DVOL_CFG0 TAC_REG_SDW(0, 0, 53)
+#define TAC_ADC_CH1_FGAIN TAC_REG_SDW(0, 0, 54)
+#define TAC_ADC_CH1_CFG1 TAC_REG_SDW(0, 0, 55)
+#define TAC_ADC_CH2_CFG0 TAC_REG_SDW(0, 0, 57)
+#define TAC_ADC_DVOL_CFG1 TAC_REG_SDW(0, 0, 58)
+#define TAC_ADC_CH2_FGAIN TAC_REG_SDW(0, 0, 59)
+#define TAC_ADC_CH2_CFG1 TAC_REG_SDW(0, 0, 60)
+#define TAC_ADC_CFG1 TAC_REG_SDW(0, 0, 62)
+#define TAC_PDM_CH1_DVOL TAC_REG_SDW(0, 0, 63)
+#define TAC_PDM_CH1_FGAIN TAC_REG_SDW(0, 0, 64)
+#define TAC_PDM_CH1_CFG0 TAC_REG_SDW(0, 0, 65)
+#define TAC_PDM_CH2_DVOL TAC_REG_SDW(0, 0, 67)
+#define TAC_PDM_CH2_FGAIN TAC_REG_SDW(0, 0, 68)
+#define TAC_PDM_CH2_CFG2 TAC_REG_SDW(0, 0, 69)
+#define TAC_PDM_CH3_DVOL TAC_REG_SDW(0, 0, 71)
+#define TAC_PDM_CH3_FGAIN TAC_REG_SDW(0, 0, 72)
+#define TAC_PDM_CH3_CFG0 TAC_REG_SDW(0, 0, 73)
+#define TAC_PDM_CH4_DVOL TAC_REG_SDW(0, 0, 75)
+#define TAC_PDM_CH4_FGAIN TAC_REG_SDW(0, 0, 76)
+#define TAC_PDM_CH4_CFG0 TAC_REG_SDW(0, 0, 77)
+#define TAC_MICBIAS_CFG0 TAC_REG_SDW(0, 0, 79)
+#define TAC_MICPREAMP_CFG TAC_REG_SDW(0, 0, 80)
+#define TAC_MICBIAS_CFG1 TAC_REG_SDW(0, 0, 81)
+#define TAC_CLASSD_CH1_DVOL TAC_REG_SDW(0, 0, 82)
+#define TAC_CLASSD_CH1_FGAIN TAC_REG_SDW(0, 0, 83)
+#define TAC_CLASSD_CH2_DVOL TAC_REG_SDW(0, 0, 85)
+#define TAC_CLASSD_CH2_FGAIN TAC_REG_SDW(0, 0, 86)
+#define TAC_GCHP_CH1_DVOL TAC_REG_SDW(0, 0, 88)
+#define TAC_GCHP_CH1_FGAIN TAC_REG_SDW(0, 0, 89)
+#define TAC_GCHP_CH2_DVOL TAC_REG_SDW(0, 0, 91)
+#define TAC_GCHP_CH2_FGAIN TAC_REG_SDW(0, 0, 92)
+#define TAC_AMP_LVL_CFG0 TAC_REG_SDW(0, 0, 94)
+#define TAC_AMP_LVL_CFG1 TAC_REG_SDW(0, 0, 95)
+#define TAC_AMP_LVL_CFG2 TAC_REG_SDW(0, 0, 96)
+#define TAC_AMP_LVL_CFG3 TAC_REG_SDW(0, 0, 97)
+#define TAC_EFF_MODE_CFG0 TAC_REG_SDW(0, 0, 98)
+#define TAC_EFF_MODE_CFG1 TAC_REG_SDW(0, 0, 99)
+#define TAC_CLASSD_CFG0 TAC_REG_SDW(0, 0, 100)
+#define TAC_CLASSD_CFG1 TAC_REG_SDW(0, 0, 101)
+#define TAC_CLASSD_CFG3 TAC_REG_SDW(0, 0, 102)
+#define TAC_CLASSD_CFG4 TAC_REG_SDW(0, 0, 103)
+#define TAC_CLASSD_CFG5 TAC_REG_SDW(0, 0, 104)
+#define TAC_CLASSD_CFG6 TAC_REG_SDW(0, 0, 105)
+#define TAC_CLASSD_CFG8 TAC_REG_SDW(0, 0, 106)
+#define TAC_ISNS_CFG TAC_REG_SDW(0, 0, 107)
+#define TAC_DSP_CFG0 TAC_REG_SDW(0, 0, 108)
+#define TAC_DSP_CFG1 TAC_REG_SDW(0, 0, 109)
+#define TAC_DSP_CFG2 TAC_REG_SDW(0, 0, 110)
+#define TAC_DSP_CFG3 TAC_REG_SDW(0, 0, 111)
+#define TAC_JACK_DET_CFG1 TAC_REG_SDW(0, 0, 112)
+#define TAC_JACK_DET_CFG2 TAC_REG_SDW(0, 0, 113)
+#define TAC_JACK_DET_CFG3 TAC_REG_SDW(0, 0, 114)
+#define TAC_JACK_DET_CFG4 TAC_REG_SDW(0, 0, 115)
+#define TAC_JACK_DET_CFG7 TAC_REG_SDW(0, 0, 116)
+#define TAC_UJ_IMPEDANCE_L TAC_REG_SDW(0, 0, 117)
+#define TAC_UJ_IMPEDANCE_R TAC_REG_SDW(0, 0, 118)
+#define UJ_IMPEDANCE_L TAC_REG_SDW(0, 0, 119)
+#define UJ_IMPEDANCE_R TAC_REG_SDW(0, 0, 120)
+#define TAC_GP_ANA_STS TAC_REG_SDW(0, 0, 123)
+#define TAC_DEV_ID TAC_REG_SDW(0, 0, 124)
+#define TAC_REV_ID TAC_REG_SDW(0, 0, 125)
+#define TAC_I2C_CKSUM TAC_REG_SDW(0, 0, 126)
+#define TAC_BOOK TAC_REG_SDW(0, 0, 127)
+
+#define TAC_INT_CFG TAC_REG_SDW(0, 2, 1)
+#define TAC_INT_CFG_CLR_REG BIT(3)
+
+/* smartamp function */
+#define TAC_FUNCTION_ID_SA 0x1
+
+#define TAC_SDCA_ENT_ENT0 0x0
+#define TAC_SDCA_ENT_PPU21 0x1
+#define TAC_SDCA_ENT_FU21 0x2
+#define TAC_SDCA_ENT_FU26 0x3
+#define TAC_SDCA_ENT_XU22 0x4
+#define TAC_SDCA_ENT_CS24 0x5
+#define TAC_SDCA_ENT_CS21 0x6
+#define TAC_SDCA_ENT_CS25 0x7
+#define TAC_SDCA_ENT_CS26 0x8
+#define TAC_SDCA_ENT_CS28 0x9
+#define TAC_SDCA_ENT_PPU26 0xa
+#define TAC_SDCA_ENT_FU23 0xb
+#define TAC_SDCA_ENT_PDE23 0xc
+#define TAC_SDCA_ENT_TG23 0x12
+#define TAC_SDCA_ENT_IT21 0x13
+#define TAC_SDCA_ENT_IT29 0x14
+#define TAC_SDCA_ENT_IT26 0x15
+#define TAC_SDCA_ENT_IT28 0x16
+#define TAC_SDCA_ENT_OT24 0x17
+#define TAC_SDCA_ENT_OT23 0x18
+#define TAC_SDCA_ENT_OT25 0x19
+#define TAC_SDCA_ENT_OT28 0x1a
+#define TAC_SDCA_ENT_OT27 0x1c
+#define TAC_SDCA_ENT_SPE199 0x21
+#define TAC_SDCA_ENT_OT20 0x24
+#define TAC_SDCA_ENT_FU27 0x26
+#define TAC_SDCA_ENT_FU20 0x27
+#define TAC_SDCA_ENT_PDE24 0x2e
+#define TAC_SDCA_ENT_PDE27 0x2f
+#define TAC_SDCA_ENT_PDE28 0x30
+#define TAC_SDCA_ENT_PDE20 0x31
+#define TAC_SDCA_ENT_SAPU29 0x35
+
+/* Control selector definitions */
+#define TAC_SDCA_MASTER_GAIN 0x0B
+#define TAC_SDCA_MASTER_MUTE 0x01
+#define TAC_SDCA_CHANNEL_MUTE 0x01
+#define TAC_SDCA_CHANNEL_GAIN 0x02
+#define TAC_SDCA_POSTURENUMBER 0x10
+#define TAC_SDCA_REQUESTED_PS 0x01
+#define TAC_SDCA_ACTUAL_PS 0x10
+#define TAC_SDCA_CHANNEL_VOLUME 0x02
+
+/* 2. smart mic function */
+#define TAC_FUNCTION_ID_SM 0x2
+
+#define TAC_SDCA_ENT_IT11 0x1
+#define TAC_SDCA_ENT_OT113 0x2
+#define TAC_SDCA_ENT_CS11 0x3
+#define TAC_SDCA_ENT_CS18 0x4
+#define TAC_SDCA_ENT_FU113 0x5
+#define TAC_SDCA_ENT_FU13 0x6
+#define TAC_SDCA_ENT_FU11 0x8
+#define TAC_SDCA_ENT_XU12 0xa
+#define TAC_SDCA_ENT_CS113 0xc
+#define TAC_SDCA_ENT_CX11 0xf
+#define TAC_SDCA_ENT_PDE11 0x12
+#define TAC_SDCA_ENT_PPU11 0x9
+
+/* controls */
+#define TAC_SDCA_CTL_USAGE 0x04
+#define TAC_SDCA_CTL_IT_CLUSTER 0x10
+#define TAC_SDCA_CTL_OT_DP_SEL 0x11
+#define TAC_SDCA_CTL_XU_BYPASS 0x01
+/* cx */
+#define TAC_SDCA_CTL_CX_CLK_SEL 0x01
+/* cs */
+#define TAC_SDCA_CTL_CS_CLKVLD 0x02
+#define TAC_SDCA_CTL_CS_SAMP_RATE_IDX 0x10
+/* cs113 end */
+/* ppu */
+#define TAC_SDCA_CTL_PPU_POSTURE_NUM 0x10
+
+/* 3. UAJ function */
+#define TAC_FUNCTION_ID_UAJ 0x3
+#define TAC_SDCA_ENT_PDE47 0x35
+#define TAC_SDCA_ENT_PDE34 0x32
+#define TAC_SDCA_ENT_FU41 0x26 /* user */
+#define TAC_SDCA_ENT_IT41 0x07
+#define TAC_SDCA_ENT_XU42 0x2C
+#define TAC_SDCA_ENT_CS41 0x30
+#define TAC_SDCA_ENT_OT45 0x0E
+#define TAC_SDCA_ENT_IT33 0x03
+#define TAC_SDCA_ENT_OT36 0x0A
+#define TAC_SDCA_ENT_FU36 0x28
+#define TAC_SDCA_ENT_CS36 0x2E
+#define TAC_SDCA_ENT_GE35 0x3B /* 59 */
+
+#define TAC_SDCA_CTL_SEL_MODE 0x1
+#define TAC_SDCA_CTL_DET_MODE 0x2
+
+/* 4. HID function */
+#define TAC_FUNCTION_ID_HID 0x4
+#define TAC_SDCA_ENT_HID1 0x1
+/* HID Control Selectors */
+#define TAC_SDCA_CTL_HIDTX_CURRENT_OWNER 0x10
+#define TAC_SDCA_CTL_HIDTX_MESSAGE_OFFSET 0x12
+#define TAC_SDCA_CTL_HIDTX_MESSAGE_LENGTH 0x13
+#define TAC_SDCA_CTL_DETECTED_MODE 0x10
+#define TAC_SDCA_CTL_SELECTED_MODE 0x11
+
+#define TAC_BUF_ADDR_HID1 0x44007F80
+
+/* DAI interfaces */
+#define TAC5XX2_SPK 0
+#define TAC5XX2_DMIC 2
+#define TAC5XX2_UAJ 3
+
+/* Port numbers for DAIs */
+#define TAC_SDW_PORT_NUM_SPK_PLAYBACK 1
+#define TAC_SDW_PORT_NUM_SPK_CAPTURE 2
+#define TAC_SDW_PORT_NUM_DMIC 3
+#define TAC_SDW_PORT_NUM_UAJ_PLAYBACK 4
+#define TAC_SDW_PORT_NUM_UAJ_CAPTURE 7
+#define TAC_SDW_PORT_NUM_IV_SENSE 8
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 3/4] ASoC: sdw_utils: TI amp utility for tac5xx2 family
2026-04-01 13:21 [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver Niranjan H Y
@ 2026-04-01 13:21 ` Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 4/4] ASoC: tac5xx2-sdw: ACPI match for intel mtl platform Niranjan H Y
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Niranjan H Y @ 2026-04-01 13:21 UTC (permalink / raw)
To: linux-sound
Cc: linux-kernel, broonie, ckeepax, lgirdwood, perex, tiwai,
cezary.rojewski, peter.ujfalusi, yung-chuan.liao,
ranjani.sridharan, kai.vehmanen, pierre-louis.bossart, baojun.xu,
shenghao-ding, sandeepk, v-hampiholi, Niranjan H Y
Add TI amp utility for supporting the tac5xx2 family
of devices to support tac5572, tac5672, tac5682 and
tas2883
Signed-off-by: Niranjan H Y <niranjan.hy@ti.com>
Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>
---
v4:
- no change
v3:
- no change
---
include/sound/soc_sdw_utils.h | 4 +
sound/soc/sdw_utils/soc_sdw_ti_amp.c | 144 ++++++++++++++++++++++++-
sound/soc/sdw_utils/soc_sdw_utils.c | 151 +++++++++++++++++++++++++++
3 files changed, 298 insertions(+), 1 deletion(-)
diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h
index 4890831836734..d713ab2f66203 100644
--- a/include/sound/soc_sdw_utils.h
+++ b/include/sound/soc_sdw_utils.h
@@ -272,7 +272,11 @@ int asoc_sdw_ti_amp_init(struct snd_soc_card *card,
struct asoc_sdw_codec_info *info,
bool playback);
int asoc_sdw_ti_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai);
+int asoc_sdw_ti_tac5xx2_spk_rtd_init(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai);
int asoc_sdw_ti_amp_initial_settings(struct snd_soc_card *card,
const char *name_prefix);
+int asoc_sdw_ti_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai);
+int asoc_sdw_ti_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai);
#endif
diff --git a/sound/soc/sdw_utils/soc_sdw_ti_amp.c b/sound/soc/sdw_utils/soc_sdw_ti_amp.c
index 488ef2ef45d4f..514340a3e3f55 100644
--- a/sound/soc/sdw_utils/soc_sdw_ti_amp.c
+++ b/sound/soc/sdw_utils/soc_sdw_ti_amp.c
@@ -7,12 +7,15 @@
#include <linux/device.h>
#include <linux/errno.h>
-#include <sound/soc.h>
+#include <linux/input.h>
+#include <sound/jack.h>
#include <sound/soc-acpi.h>
#include <sound/soc-dai.h>
+#include <sound/soc.h>
#include <sound/soc_sdw_utils.h>
#define TIAMP_SPK_VOLUME_0DB 200
+#define TAC5XX2_WIDGET_NAME_MAX 32
int asoc_sdw_ti_amp_initial_settings(struct snd_soc_card *card,
const char *name_prefix)
@@ -95,3 +98,142 @@ int asoc_sdw_ti_amp_init(struct snd_soc_card *card,
return 0;
}
EXPORT_SYMBOL_NS(asoc_sdw_ti_amp_init, "SND_SOC_SDW_UTILS");
+
+static int asoc_sdw_ti_add_tac5xx2_routes(struct snd_soc_dapm_context *dapm,
+ const char *name_prefix)
+{
+ struct snd_soc_dapm_route routes[2];
+ char left_widget[TAC5XX2_WIDGET_NAME_MAX];
+ char right_widget[TAC5XX2_WIDGET_NAME_MAX];
+ int ret;
+
+ if (strlen(name_prefix) > (TAC5XX2_WIDGET_NAME_MAX - 7))
+ return -ENAMETOOLONG;
+
+ ret = scnprintf(left_widget, sizeof(left_widget), "%s SPK_L", name_prefix);
+ if (ret <= 0)
+ return -EINVAL;
+
+ ret = scnprintf(right_widget, sizeof(right_widget), "%s SPK_R", name_prefix);
+ if (ret <= 0)
+ return -EINVAL;
+
+ routes[0] = (struct snd_soc_dapm_route){"Left Spk", NULL, left_widget};
+ routes[1] = (struct snd_soc_dapm_route){"Right Spk", NULL, right_widget};
+
+ return snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes));
+}
+
+int asoc_sdw_ti_tac5xx2_spk_rtd_init(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
+ int ret, i;
+ struct snd_soc_dai *codec_dai;
+ const char *prefix;
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ if (!strstr(codec_dai->name, "tac5") &&
+ !strstr(codec_dai->name, "tas2883"))
+ continue;
+
+ prefix = codec_dai->component->name_prefix;
+ if (!prefix) {
+ dev_warn(card->dev,
+ "No name prefix found for codec DAI: %s\n",
+ codec_dai->name);
+ continue;
+ }
+ ret = asoc_sdw_ti_add_tac5xx2_routes(dapm, prefix);
+ if (ret) {
+ dev_err(card->dev, "Failed to add routes for %s: %d\n",
+ prefix, ret);
+ return ret;
+ }
+ }
+
+ dev_dbg(card->dev, "Added TAC5XX2 speaker routes\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asoc_sdw_ti_tac5xx2_spk_rtd_init);
+
+int asoc_sdw_ti_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_component *component;
+ char *mic_name;
+
+ component = dai->component;
+ mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s", component->name_prefix);
+ if (!mic_name)
+ return -ENOMEM;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s mic:%s", card->components,
+ mic_name);
+ if (!card->components)
+ return -ENOMEM;
+
+ dev_dbg(card->dev, "card->components: %s\n", card->components);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_ti_dmic_rtd_init, "SND_SOC_SDW_UTILS");
+
+static struct snd_soc_jack_pin ti_sdca_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+int asoc_sdw_ti_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+ struct snd_soc_jack *jack;
+ int ret;
+
+ component = dai->component;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s hs:%s", card->components,
+ component->name_prefix);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3 | SND_JACK_BTN_4,
+ &ctx->sdw_headset,
+ ti_sdca_jack_pins,
+ ARRAY_SIZE(ti_sdca_jack_pins));
+ if (ret) {
+ dev_err(rtd->card->dev, "Jack create failed%d\n", ret);
+ return ret;
+ }
+
+ jack = &ctx->sdw_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_4, KEY_NEXTSONG);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+ if (ret)
+ dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n",
+ ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_ti_sdca_jack_rtd_init, "SND_SOC_SDW_UTILS");
diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c
index 2807f536eef0c..ffc342c79d5ea 100644
--- a/sound/soc/sdw_utils/soc_sdw_utils.c
+++ b/sound/soc/sdw_utils/soc_sdw_utils.c
@@ -72,6 +72,157 @@ static const struct snd_kcontrol_new rt700_controls[] = {
};
struct asoc_sdw_codec_info codec_info_list[] = {
+ {
+ .vendor_id = 0x0102,
+ .part_id = 0x5572,
+ .name_prefix = "tac5572",
+ .dais = {
+ {
+ /* speaker */
+ .direction = {true, false},
+ .dai_name = "tac5xx2-aif1",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},
+ .init = asoc_sdw_ti_amp_init,
+ .rtd_init = asoc_sdw_ti_tac5xx2_spk_rtd_init,
+ .controls = lr_spk_controls,
+ .num_controls = ARRAY_SIZE(lr_spk_controls),
+ .widgets = lr_spk_widgets,
+ .num_widgets = ARRAY_SIZE(lr_spk_widgets),
+ },
+ {
+ /* mic */
+ .direction = {false, true},
+ .dai_name = "tac5xx2-aif2",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_ti_dmic_rtd_init,
+ },
+ {
+ /* UAJ */
+ .direction = {true, true},
+ .dai_name = "tac5xx2-aif3",
+ .dai_type = SOC_SDW_DAI_TYPE_JACK,
+ .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},
+ .controls = generic_jack_controls,
+ .num_controls = ARRAY_SIZE(generic_jack_controls),
+ .widgets = generic_jack_widgets,
+ .num_widgets = ARRAY_SIZE(generic_jack_widgets),
+ .rtd_init = asoc_sdw_ti_sdca_jack_rtd_init,
+ },
+ },
+ .dai_num = 3,
+ },
+ {
+ .vendor_id = 0x0102,
+ .part_id = 0x5672,
+ .name_prefix = "tac5672",
+ .dais = {
+ {
+ /* speaker with IV sense feedback */
+ .direction = {true, true},
+ .dai_name = "tac5xx2-aif1",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},
+ .init = asoc_sdw_ti_amp_init,
+ .rtd_init = asoc_sdw_ti_tac5xx2_spk_rtd_init,
+ .controls = lr_spk_controls,
+ .num_controls = ARRAY_SIZE(lr_spk_controls),
+ .widgets = lr_spk_widgets,
+ .num_widgets = ARRAY_SIZE(lr_spk_widgets),
+ },
+ {
+ /* mic */
+ .direction = {false, true},
+ .dai_name = "tac5xx2-aif2",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_ti_dmic_rtd_init,
+ },
+ {
+ /* UAJ */
+ .direction = {true, true},
+ .dai_name = "tac5xx2-aif3",
+ .dai_type = SOC_SDW_DAI_TYPE_JACK,
+ .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},
+ .controls = generic_jack_controls,
+ .num_controls = ARRAY_SIZE(generic_jack_controls),
+ .widgets = generic_jack_widgets,
+ .num_widgets = ARRAY_SIZE(generic_jack_widgets),
+ .rtd_init = asoc_sdw_ti_sdca_jack_rtd_init,
+ },
+ },
+ .dai_num = 3,
+ },
+ {
+ .vendor_id = 0x0102,
+ .part_id = 0x5682,
+ .name_prefix = "tac5682",
+ .dais = {
+ {
+ /* speaker with echo reference feedback */
+ .direction = {true, true},
+ .dai_name = "tac5xx2-aif1",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID},
+ .init = asoc_sdw_ti_amp_init,
+ .rtd_init = asoc_sdw_ti_tac5xx2_spk_rtd_init,
+ .controls = lr_spk_controls,
+ .num_controls = ARRAY_SIZE(lr_spk_controls),
+ .widgets = lr_spk_widgets,
+ .num_widgets = ARRAY_SIZE(lr_spk_widgets),
+ },
+ {
+ /* mic */
+ .direction = {false, true},
+ .dai_name = "tac5xx2-aif2",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_ti_dmic_rtd_init,
+ },
+ {
+ /* UAJ */
+ .direction = {true, true},
+ .dai_name = "tac5xx2-aif3",
+ .dai_type = SOC_SDW_DAI_TYPE_JACK,
+ .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},
+ .controls = generic_jack_controls,
+ .num_controls = ARRAY_SIZE(generic_jack_controls),
+ .widgets = generic_jack_widgets,
+ .num_widgets = ARRAY_SIZE(generic_jack_widgets),
+ .rtd_init = asoc_sdw_ti_sdca_jack_rtd_init,
+ },
+ },
+ .dai_num = 3,
+ },
+ {
+ .vendor_id = 0x0102,
+ .part_id = 0x2883,
+ .name_prefix = "tas2883",
+ .dais = {
+ {
+ .direction = {true, false},
+ .dai_name = "tac5xx2-aif1",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},
+ .init = asoc_sdw_ti_amp_init,
+ .rtd_init = asoc_sdw_ti_tac5xx2_spk_rtd_init,
+ .controls = lr_spk_controls,
+ .num_controls = ARRAY_SIZE(lr_spk_controls),
+ .widgets = lr_spk_widgets,
+ .num_widgets = ARRAY_SIZE(lr_spk_widgets),
+ },
+ {
+ /* mic */
+ .direction = {false, true},
+ .dai_name = "tac5xx2-aif2",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_ti_dmic_rtd_init,
+ },
+ },
+ .dai_num = 2,
+ },
{
.vendor_id = 0x0102,
.part_id = 0x0000, /* TAS2783A */
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 4/4] ASoC: tac5xx2-sdw: ACPI match for intel mtl platform
2026-04-01 13:21 [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 3/4] ASoC: sdw_utils: TI amp utility for tac5xx2 family Niranjan H Y
@ 2026-04-01 13:21 ` Niranjan H Y
2026-04-01 16:59 ` [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Charles Keepax
2026-04-03 14:12 ` (subset) " Mark Brown
4 siblings, 0 replies; 8+ messages in thread
From: Niranjan H Y @ 2026-04-01 13:21 UTC (permalink / raw)
To: linux-sound
Cc: linux-kernel, broonie, ckeepax, lgirdwood, perex, tiwai,
cezary.rojewski, peter.ujfalusi, yung-chuan.liao,
ranjani.sridharan, kai.vehmanen, pierre-louis.bossart, baojun.xu,
shenghao-ding, sandeepk, v-hampiholi, Niranjan H Y
Add acpi match entries to support TI's tac5572,
tas2883, tac5672 and tac5682 on link 0 on MTL machine.
Signed-off-by: Niranjan H Y <niranjan.hy@ti.com>
Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>
---
v4:
- no change
v3:
- no change
---
.../intel/common/soc-acpi-intel-mtl-match.c | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c
index 72c35e73078e3..2e4222456f27f 100644
--- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c
+++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c
@@ -122,6 +122,42 @@ static const struct snd_soc_acpi_endpoint spk_r_endpoint = {
.group_id = 1,
};
+static const struct snd_soc_acpi_endpoint tac5xx2_endpoints[] = {
+ { /* Playback Endpoint */
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+ { /* Mic Capture Endpoint */
+ .num = 1,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+ { /* UAJ-HP with Mic Endpoint */
+ .num = 2,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+};
+
+static const struct snd_soc_acpi_endpoint tas2883_endpoints[] = {
+ { /* Playback Endpoint */
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+ { /* Mic Capture Endpoint */
+ .num = 1,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+};
+
static const struct snd_soc_acpi_endpoint rt712_endpoints[] = {
{
.num = 0,
@@ -1011,6 +1047,33 @@ static const struct snd_soc_acpi_adr_device cs42l42_0_adr[] = {
}
};
+static const struct snd_soc_acpi_adr_device tac5572_0_adr[] = {
+ {
+ .adr = 0x0000300102557201ull,
+ .num_endpoints = ARRAY_SIZE(tac5xx2_endpoints),
+ .endpoints = tac5xx2_endpoints,
+ .name_prefix = "tac5572"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device tac5672_0_adr[] = {
+ {
+ .adr = 0x0000300102567201ull,
+ .num_endpoints = ARRAY_SIZE(tac5xx2_endpoints),
+ .endpoints = tac5xx2_endpoints,
+ .name_prefix = "tac5672"
+ }
+};
+
+static const struct snd_soc_acpi_adr_device tac5682_0_adr[] = {
+ {
+ .adr = 0x0000300102568201ull,
+ .num_endpoints = ARRAY_SIZE(tac5xx2_endpoints),
+ .endpoints = tac5xx2_endpoints,
+ .name_prefix = "tac5682"
+ }
+};
+
static const struct snd_soc_acpi_adr_device tas2783_0_adr[] = {
{
.adr = 0x00003c0102000001ull,
@@ -1035,9 +1098,45 @@ static const struct snd_soc_acpi_adr_device tas2783_0_adr[] = {
.num_endpoints = 1,
.endpoints = &spk_r_endpoint,
.name_prefix = "tas2783-4"
+ },
+};
+
+static const struct snd_soc_acpi_adr_device tas2883_0_adr[] = {
+ {
+ .adr = 0x0000300102288301ull,
+ .num_endpoints = ARRAY_SIZE(tas2883_endpoints),
+ .endpoints = tas2883_endpoints,
+ .name_prefix = "tas2883"
}
};
+static const struct snd_soc_acpi_link_adr tac5572_l0[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(tac5572_0_adr),
+ .adr_d = tac5572_0_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tac5672_l0[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(tac5672_0_adr),
+ .adr_d = tac5672_0_adr,
+ },
+ {}
+};
+
+static const struct snd_soc_acpi_link_adr tac5682_l0[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(tac5682_0_adr),
+ .adr_d = tac5682_0_adr,
+ },
+ {}
+};
+
static const struct snd_soc_acpi_link_adr tas2783_link0[] = {
{
.mask = BIT(0),
@@ -1047,6 +1146,15 @@ static const struct snd_soc_acpi_link_adr tas2783_link0[] = {
{}
};
+static const struct snd_soc_acpi_link_adr tas2883_l0[] = {
+ {
+ .mask = BIT(0),
+ .num_adr = ARRAY_SIZE(tas2883_0_adr),
+ .adr_d = tas2883_0_adr,
+ },
+ {}
+};
+
static const struct snd_soc_acpi_link_adr cs42l42_link0_max98363_link2[] = {
/* Expected order: jack -> amp */
{
@@ -1208,12 +1316,36 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = {
.drv_name = "sof_sdw",
.sof_tplg_filename = "sof-mtl-rt715-rt711-rt1308-mono.tplg",
},
+ {
+ .link_mask = BIT(0),
+ .links = tac5572_l0,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-mtl-tac5572.tplg",
+ },
+ {
+ .link_mask = BIT(0),
+ .links = tac5672_l0,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-mtl-tac5672.tplg",
+ },
+ {
+ .link_mask = BIT(0),
+ .links = tac5682_l0,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-mtl-tac5682.tplg",
+ },
{
.link_mask = BIT(0),
.links = tas2783_link0,
.drv_name = "sof_sdw",
.sof_tplg_filename = "sof-mtl-tas2783.tplg",
},
+ {
+ .link_mask = BIT(0),
+ .links = tas2883_l0,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-mtl-tas2883.tplg",
+ },
{
.link_mask = GENMASK(3, 0),
.links = mtl_rt713_l0_rt1316_l12_rt1713_l3,
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers
2026-04-01 13:21 [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Niranjan H Y
` (2 preceding siblings ...)
2026-04-01 13:21 ` [PATCH v4 4/4] ASoC: tac5xx2-sdw: ACPI match for intel mtl platform Niranjan H Y
@ 2026-04-01 16:59 ` Charles Keepax
2026-04-03 14:12 ` (subset) " Mark Brown
4 siblings, 0 replies; 8+ messages in thread
From: Charles Keepax @ 2026-04-01 16:59 UTC (permalink / raw)
To: Niranjan H Y
Cc: linux-sound, linux-kernel, broonie, lgirdwood, perex, tiwai,
cezary.rojewski, peter.ujfalusi, yung-chuan.liao,
ranjani.sridharan, kai.vehmanen, pierre-louis.bossart, baojun.xu,
shenghao-ding, sandeepk, v-hampiholi
On Wed, Apr 01, 2026 at 06:51:45PM +0530, Niranjan H Y wrote:
> Export the Q7.8 volume control helpers to allow reuse
> by other ASoC drivers. These functions handle 16-bit
> signed Q7.8 fixed-point format values for volume controls.
>
> Changes include:
> - Rename q78_get_volsw to sdca_asoc_q78_get_volsw
> - Rename q78_put_volsw to sdca_asoc_q78_put_volsw
> - Add a convenience macro SDCA_SINGLE_Q78_TLV and
> SDCA_DOUBLE_Q78_TLV for creating mixer controls
>
> This allows other ASoC drivers to easily implement controls
> using the Q7.8 fixed-point format without duplicating code.
>
> Signed-off-by: Niranjan H Y <niranjan.hy@ti.com>
> ---
Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Thanks,
Charles
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver
2026-04-01 13:21 ` [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver Niranjan H Y
@ 2026-04-03 13:25 ` Mark Brown
2026-04-06 12:57 ` Holalu Yogendra, Niranjan
0 siblings, 1 reply; 8+ messages in thread
From: Mark Brown @ 2026-04-03 13:25 UTC (permalink / raw)
To: Niranjan H Y
Cc: linux-sound, linux-kernel, ckeepax, lgirdwood, perex, tiwai,
cezary.rojewski, peter.ujfalusi, yung-chuan.liao,
ranjani.sridharan, kai.vehmanen, pierre-louis.bossart, baojun.xu,
shenghao-ding, sandeepk, v-hampiholi
[-- Attachment #1: Type: text/plain, Size: 2855 bytes --]
On Wed, Apr 01, 2026 at 06:51:46PM +0530, Niranjan H Y wrote:
> +static int tac_sdw_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
> + /* Detect and set jack type for UAJ path before playback.
> + * This is required as jack is not triggering interrupt
> + * when the device is in suspended mode.
> + */
> + tac5xx2_sdca_headset_detect(tac_dev);
Do we need locking in case the device is not in suspended mode and this
happens to race with an interrupt?
> + ret = regmap_write(tac_dev->regmap, SDW_SDCA_CTL(function_id, pde_entity,
> + TAC_SDCA_REQUESTED_PS, 0),
> + 0x03);
Other writes here are protected by pde_lock.
> + ret = sdw_stream_add_slave(sdw_peripheral, &stream_config,
> + &port_config, 1, sdw_stream);
> + if (ret)
> + dev_err(dai->dev,
> + "Unable to configure port %d: %d\n", port_num, ret);
Is it safe to only log this error, will the PCM have any chance of
working?
> +static int tac_interrupt_callback(struct sdw_slave *slave,
> + struct sdw_slave_intr_status *status)
> +{
> + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev);
> + struct device *dev = &slave->dev;
> + int ret = 0;
> + int btn_type = 0;
> + unsigned int sdca_int3;
> +
> + ret = regmap_read(tac_dev->regmap, SDW_SCP_SDCA_INT3, &sdca_int3);
> + if (ret) {
> + dev_err(dev, "Failed to read SDCA_INT3: %d", ret);
> + return ret;
> + }
Can an interrupt be generated during runtime suspend (or race with
another thread doing a runtime suspend)?
> + snd_soc_jack_report(tac_dev->hs_jack, tac_dev->jack_type | btn_type,
> + SND_JACK_HEADSET | SND_JACK_BTN_0 |
> + SND_JACK_BTN_1 | SND_JACK_BTN_2 |
> + SND_JACK_BTN_3 | SND_JACK_BTN_4);
The detection function detects line in/out but this doesn't include them
in the report mask.
> +static s32 tac_download_fw_to_hw(struct tac5xx2_prv *tac_dev)
> +{
> + offset = tac_fw_read_hdr(buf, hdr);
> + while (offset < img_sz && num_files < TAC_MAX_FW_CHUNKS) {
> + u32 file_length;
> +
> + if (file_length > img_sz || offset > img_sz - TAC_FW_FILE_HDR ||
> + file_length > img_sz - offset - TAC_FW_FILE_HDR) {
> + dev_warn(tac_dev->dev, "File at offset %d exceeds buffer: length=%u, available=%zu\n",
> + offset, file_length, img_sz - offset - TAC_FW_FILE_HDR);
> + break;
> + }
Shouldn't this jump out of the loop to the error handling like...
> + ret = tac_fw_get_next_file(&buf[offset], img_sz - offset, &files[num_files]);
> + if (ret < 0) {
> + dev_err(tac_dev->dev, "Failed to parse file at offset %d\n", offset);
> + goto out;
> + }
...this does?
> +static void tac_remove(struct tac5xx2_prv *tac_dev)
> +{
> + snd_soc_unregister_component(tac_dev->dev);
> +}
We used devm_snd_soc_register_component() so this should result in a
double free.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: (subset) [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers
2026-04-01 13:21 [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Niranjan H Y
` (3 preceding siblings ...)
2026-04-01 16:59 ` [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Charles Keepax
@ 2026-04-03 14:12 ` Mark Brown
4 siblings, 0 replies; 8+ messages in thread
From: Mark Brown @ 2026-04-03 14:12 UTC (permalink / raw)
To: linux-sound, Niranjan H Y
Cc: linux-kernel, ckeepax, lgirdwood, perex, tiwai, cezary.rojewski,
peter.ujfalusi, yung-chuan.liao, ranjani.sridharan, kai.vehmanen,
pierre-louis.bossart, baojun.xu, shenghao-ding, sandeepk,
v-hampiholi
On Wed, 01 Apr 2026 18:51:45 +0530, Niranjan H Y wrote:
> ASoC: SDCA: Export Q7.8 volume control helpers
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-7.1
Thanks!
[1/4] ASoC: SDCA: Export Q7.8 volume control helpers
https://git.kernel.org/broonie/sound/c/ba2a0e81d4d7
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver
2026-04-03 13:25 ` Mark Brown
@ 2026-04-06 12:57 ` Holalu Yogendra, Niranjan
0 siblings, 0 replies; 8+ messages in thread
From: Holalu Yogendra, Niranjan @ 2026-04-06 12:57 UTC (permalink / raw)
To: Mark Brown
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
ckeepax@opensource.cirrus.com, lgirdwood@gmail.com,
perex@perex.cz, tiwai@suse.com, cezary.rojewski@intel.com,
peter.ujfalusi@linux.intel.com, yung-chuan.liao@linux.intel.com,
ranjani.sridharan@linux.intel.com, kai.vehmanen@linux.intel.com,
pierre-louis.bossart@linux.dev, Xu, Baojun, Ding, Shenghao,
Kasargod, Sandeep, Hampiholi, Vallabha
On 18:56-20260403, Mark Brown wrote:
Subject: Re: [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver
> On Wed, Apr 01, 2026 at 06:51:46PM +0530, Niranjan H Y wrote:
>
> > +static int tac_interrupt_callback(struct sdw_slave *slave,
> > + struct sdw_slave_intr_status *status)
> > +{
> > + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev);
> > + struct device *dev = &slave->dev;
> > + int ret = 0;
> > + int btn_type = 0;
> > + unsigned int sdca_int3;
> > +
>
> > + ret = regmap_read(tac_dev->regmap, SDW_SCP_SDCA_INT3,
> &sdca_int3);
> > + if (ret) {
> > + dev_err(dev, "Failed to read SDCA_INT3: %d", ret);
> > + return ret;
> > + }
>
> Can an interrupt be generated during runtime suspend (or race with
> another thread doing a runtime suspend)?
sdw_handle_slave_alerts in drivers/soundwire/bus.c calls pm_runtime_get_sync before
processing interrupts and only calls pm_runtime_put_autosuspend after the
.interrupt_callback completes. So device should be in resumed state before handling the alerts.
I will add protection here in next version.
Regards
Niranjan
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-04-06 12:57 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-01 13:21 [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 2/4] ASoC: tac5xx2-sdw: add soundwire based codec driver Niranjan H Y
2026-04-03 13:25 ` Mark Brown
2026-04-06 12:57 ` Holalu Yogendra, Niranjan
2026-04-01 13:21 ` [PATCH v4 3/4] ASoC: sdw_utils: TI amp utility for tac5xx2 family Niranjan H Y
2026-04-01 13:21 ` [PATCH v4 4/4] ASoC: tac5xx2-sdw: ACPI match for intel mtl platform Niranjan H Y
2026-04-01 16:59 ` [PATCH v4 1/4] ASoC: SDCA: Export Q7.8 volume control helpers Charles Keepax
2026-04-03 14:12 ` (subset) " Mark Brown
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox