linux/sound/soc/codecs/tas675x.c
Sen Wang a044f99d00
ASoC: codecs: tas675x: Fix CHx temperature range register bit fields
The initial merged patch mixed up the bits for temp reg with LDG report,
now fixing to the right bits according to TRM (SLOU589A).

Fixes: 133c81f844 ("ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver")
Signed-off-by: Sen Wang <sen@ti.com>
Link: https://patch.msgid.link/20260630183126.2588322-3-sen@ti.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2026-07-01 19:52:37 +01:00

2195 lines
70 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* ALSA SoC Texas Instruments TAS67524 Quad-Channel Audio Amplifier
*
* Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
* Author: Sen Wang <sen@ti.com>
*/
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <linux/property.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/pm_runtime.h>
#include <linux/iopoll.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <sound/pcm_params.h>
#include "tas675x.h"
#define TAS675X_FAULT_CHECK_INTERVAL_MS 200
enum tas675x_type {
TAS67524,
};
struct tas675x_reg_param {
u8 page;
u8 reg;
u32 val;
};
struct tas675x_priv {
struct device *dev;
struct regmap *regmap;
enum tas675x_type dev_type;
/* Custom regmap lock; protects writes across books */
struct mutex io_lock;
struct gpio_desc *pd_gpio;
struct gpio_desc *stby_gpio;
struct regulator_bulk_data supplies[2];
struct regulator *vbat;
bool fast_boot;
int audio_slot;
int llp_slot;
int vpredict_slot;
int isense_slot;
int bclk_offset;
int slot_width;
unsigned int tx_mask;
int gpio1_func;
int gpio2_func;
unsigned long active_playback_dais;
unsigned long active_capture_dais;
unsigned int rate;
unsigned int saved_rtldg_en;
#define TAS675X_DSP_PARAM_NUM 2
struct tas675x_reg_param dsp_params[TAS675X_DSP_PARAM_NUM];
/* Fault monitor, disabled when Fault IRQ is used */
struct delayed_work fault_check_work;
#define TAS675X_FAULT_REGS_NUM 9
unsigned int last_status[TAS675X_FAULT_REGS_NUM];
};
static const char * const tas675x_supply_names[] = {
"dvdd", /* Digital power supply */
"pvdd", /* Output powerstage supply */
};
/* Page 1 setup initialization defaults */
static const struct reg_sequence tas675x_page1_init[] = {
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC8), 0x20), /* Charge pump clock */
REG_SEQ0(TAS675X_PAGE_REG(1, 0x2F), 0x90), /* VBAT idle */
REG_SEQ0(TAS675X_PAGE_REG(1, 0x29), 0x40), /* OC/CBC threshold */
REG_SEQ0(TAS675X_PAGE_REG(1, 0x2E), 0x0C), /* OC/CBC config */
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC5), 0x02), /* OC/CBC config */
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC6), 0x10), /* OC/CBC config */
REG_SEQ0(TAS675X_PAGE_REG(1, 0x1F), 0x20), /* OC/CBC config */
REG_SEQ0(TAS675X_PAGE_REG(1, 0x16), 0x01), /* OC/CBC config */
REG_SEQ0(TAS675X_PAGE_REG(1, 0x1E), 0x04), /* OC/CBC config */
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC1), 0x00), /* CH1 DC fault */
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC2), 0x04), /* CH2 DC fault */
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC3), 0x00), /* CH3 DC fault */
REG_SEQ0(TAS675X_PAGE_REG(1, 0xC4), 0x00), /* CH4 DC fault */
};
static inline const char *tas675x_state_name(unsigned int state)
{
switch (state & 0x0F) {
case TAS675X_STATE_DEEPSLEEP: return "DEEPSLEEP";
case TAS675X_STATE_LOAD_DIAG: return "LOAD_DIAG";
case TAS675X_STATE_SLEEP: return "SLEEP";
case TAS675X_STATE_HIZ: return "HIZ";
case TAS675X_STATE_PLAY: return "PLAY";
case TAS675X_STATE_FAULT: return "FAULT";
case TAS675X_STATE_AUTOREC: return "AUTOREC";
default: return "UNKNOWN";
}
}
static inline int tas675x_set_state_all(struct tas675x_priv *tas, u8 state)
{
const struct reg_sequence seq[] = {
REG_SEQ0(TAS675X_STATE_CTRL_CH1_CH2_REG, state),
REG_SEQ0(TAS675X_STATE_CTRL_CH3_CH4_REG, state),
};
return regmap_multi_reg_write(tas->regmap, seq, ARRAY_SIZE(seq));
}
static inline int tas675x_select_book(struct regmap *regmap, u8 book)
{
int ret;
/* Reset page to 0 before switching books */
ret = regmap_write(regmap, TAS675X_PAGE_CTRL_REG, 0x00);
if (!ret)
ret = regmap_write(regmap, TAS675X_BOOK_CTRL_REG, book);
return ret;
}
/* Raw I2C version of tas675x_select_book, must be called with io_lock held */
static inline int __tas675x_select_book(struct tas675x_priv *tas, u8 book)
{
struct i2c_client *client = to_i2c_client(tas->dev);
int ret;
/* Reset page to 0 before switching books */
ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, 0x00);
if (ret)
return ret;
return i2c_smbus_write_byte_data(client, TAS675X_BOOK_CTRL_REG, book);
}
static int tas675x_dsp_mem_write(struct tas675x_priv *tas, u8 page, u8 reg, u32 val)
{
struct i2c_client *client = to_i2c_client(tas->dev);
u8 buf[4];
int ret;
/* DSP registers are 32 bit big-endian */
buf[0] = (val >> 24) & 0xFF;
buf[1] = (val >> 16) & 0xFF;
buf[2] = (val >> 8) & 0xFF;
buf[3] = val & 0xFF;
/*
* DSP regs in a different book, therefore block
* regmap access before completion.
*/
mutex_lock(&tas->io_lock);
ret = __tas675x_select_book(tas, TAS675X_BOOK_DSP);
if (ret)
goto out;
ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, page);
if (ret)
goto out;
ret = i2c_smbus_write_i2c_block_data(client, reg, sizeof(buf), buf);
out:
__tas675x_select_book(tas, TAS675X_BOOK_DEFAULT);
mutex_unlock(&tas->io_lock);
return ret;
}
static int tas675x_dsp_mem_read(struct tas675x_priv *tas, u8 page, u8 reg, u32 *val)
{
struct i2c_client *client = to_i2c_client(tas->dev);
u8 buf[4];
int ret;
/*
* DSP regs in a different book, therefore block
* regmap access before completion.
*/
mutex_lock(&tas->io_lock);
ret = __tas675x_select_book(tas, TAS675X_BOOK_DSP);
if (ret)
goto out;
ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, page);
if (ret)
goto out;
ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
if (ret == sizeof(buf)) {
*val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
ret = 0;
} else if (ret >= 0) {
ret = -EIO;
}
out:
__tas675x_select_book(tas, TAS675X_BOOK_DEFAULT);
mutex_unlock(&tas->io_lock);
return ret;
}
static const struct {
const char *name;
int val;
} tas675x_gpio_func_map[] = {
/* Output functions */
{ "low", TAS675X_GPIO_SEL_LOW },
{ "auto-mute", TAS675X_GPIO_SEL_AUTO_MUTE_ALL },
{ "auto-mute-ch4", TAS675X_GPIO_SEL_AUTO_MUTE_CH4 },
{ "auto-mute-ch3", TAS675X_GPIO_SEL_AUTO_MUTE_CH3 },
{ "auto-mute-ch2", TAS675X_GPIO_SEL_AUTO_MUTE_CH2 },
{ "auto-mute-ch1", TAS675X_GPIO_SEL_AUTO_MUTE_CH1 },
{ "sdout2", TAS675X_GPIO_SEL_SDOUT2 },
{ "sdout1", TAS675X_GPIO_SEL_SDOUT1 },
{ "warn", TAS675X_GPIO_SEL_WARN },
{ "fault", TAS675X_GPIO_SEL_FAULT },
{ "clock-sync", TAS675X_GPIO_SEL_CLOCK_SYNC },
{ "invalid-clock", TAS675X_GPIO_SEL_INVALID_CLK },
{ "high", TAS675X_GPIO_SEL_HIGH },
/* Input functions */
{ "mute", TAS675X_GPIO_IN_MUTE },
{ "phase-sync", TAS675X_GPIO_IN_PHASE_SYNC },
{ "sdin2", TAS675X_GPIO_IN_SDIN2 },
{ "deep-sleep", TAS675X_GPIO_IN_DEEP_SLEEP },
{ "hiz", TAS675X_GPIO_IN_HIZ },
{ "play", TAS675X_GPIO_IN_PLAY },
{ "sleep", TAS675X_GPIO_IN_SLEEP },
};
static int tas675x_gpio_func_parse(struct device *dev, const char *propname)
{
const char *str;
int i, ret;
ret = device_property_read_string(dev, propname, &str);
if (ret)
return -1;
for (i = 0; i < ARRAY_SIZE(tas675x_gpio_func_map); i++) {
if (!strcmp(str, tas675x_gpio_func_map[i].name))
return tas675x_gpio_func_map[i].val;
}
dev_warn(dev, "Invalid %s value '%s'\n", propname, str);
return -1;
}
static const struct {
unsigned int reg;
unsigned int mask;
} tas675x_gpio_input_table[TAS675X_GPIO_IN_NUM] = {
[TAS675X_GPIO_IN_ID_MUTE] = {
TAS675X_GPIO_INPUT_MUTE_REG, TAS675X_GPIO_IN_MUTE_MASK },
[TAS675X_GPIO_IN_ID_PHASE_SYNC] = {
TAS675X_GPIO_INPUT_SYNC_REG, TAS675X_GPIO_IN_SYNC_MASK },
[TAS675X_GPIO_IN_ID_SDIN2] = {
TAS675X_GPIO_INPUT_SDIN2_REG, TAS675X_GPIO_IN_SDIN2_MASK },
[TAS675X_GPIO_IN_ID_DEEP_SLEEP] = {
TAS675X_GPIO_INPUT_SLEEP_HIZ_REG, TAS675X_GPIO_IN_DEEP_SLEEP_MASK },
[TAS675X_GPIO_IN_ID_HIZ] = {
TAS675X_GPIO_INPUT_SLEEP_HIZ_REG, TAS675X_GPIO_IN_HIZ_MASK },
[TAS675X_GPIO_IN_ID_PLAY] = {
TAS675X_GPIO_INPUT_PLAY_SLEEP_REG, TAS675X_GPIO_IN_PLAY_MASK },
[TAS675X_GPIO_IN_ID_SLEEP] = {
TAS675X_GPIO_INPUT_PLAY_SLEEP_REG, TAS675X_GPIO_IN_SLEEP_MASK },
};
static void tas675x_config_gpio_pin(struct regmap *regmap, int func_id,
unsigned int out_sel_reg,
unsigned int pin_idx,
unsigned int *gpio_ctrl)
{
int id;
if (func_id < 0)
return;
if (func_id & TAS675X_GPIO_FUNC_INPUT) {
/* 3-bit mux: 0 = disabled, 0b1 = GPIO1, 0b10 = GPIO2 */
id = func_id & ~TAS675X_GPIO_FUNC_INPUT;
regmap_update_bits(regmap,
tas675x_gpio_input_table[id].reg,
tas675x_gpio_input_table[id].mask,
(pin_idx + 1) << __ffs(tas675x_gpio_input_table[id].mask));
} else {
/* Output GPIO, update selection register and enable bit */
regmap_write(regmap, out_sel_reg, func_id);
*gpio_ctrl |= pin_idx ? TAS675X_GPIO2_OUTPUT_EN : TAS675X_GPIO1_OUTPUT_EN;
}
}
static int tas675x_rtldg_thresh_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
/* threshold reg ranges up to 24bit */
uinfo->value.integer.max = 0x00FFFFFF;
return 0;
}
static int tas675x_set_rtldg_thresh(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
const struct tas675x_reg_param *t =
(const struct tas675x_reg_param *)kcontrol->private_value;
u32 val = ucontrol->value.integer.value[0];
int ret;
ret = tas675x_dsp_mem_write(tas, t->page, t->reg, val);
/* Cache the value */
if (!ret) {
int i;
for (i = 0; i < ARRAY_SIZE(tas->dsp_params); i++) {
if (tas->dsp_params[i].page == t->page &&
tas->dsp_params[i].reg == t->reg) {
tas->dsp_params[i].val = val;
break;
}
}
}
/* Return 1 to notify change, or propagate error */
return ret ? ret : 1;
}
static int tas675x_get_rtldg_thresh(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
const struct tas675x_reg_param *t =
(const struct tas675x_reg_param *)kcontrol->private_value;
u32 val = 0;
int ret;
ret = tas675x_dsp_mem_read(tas, t->page, t->reg, &val);
if (!ret)
ucontrol->value.integer.value[0] = val;
return ret;
}
static const struct tas675x_reg_param tas675x_dsp_defaults[] = {
[TAS675X_DSP_PARAM_ID_OL_THRESH] = {
TAS675X_DSP_PAGE_RTLDG, TAS675X_DSP_RTLDG_OL_THRESH_REG },
[TAS675X_DSP_PARAM_ID_SL_THRESH] = {
TAS675X_DSP_PAGE_RTLDG, TAS675X_DSP_RTLDG_SL_THRESH_REG },
};
static_assert(ARRAY_SIZE(tas675x_dsp_defaults) == TAS675X_DSP_PARAM_NUM);
static int tas675x_set_dcldg_trigger(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
unsigned int state, state34;
int ret;
if (!ucontrol->value.integer.value[0])
return 0;
if (snd_soc_component_active(comp))
return -EBUSY;
ret = pm_runtime_resume_and_get(tas->dev);
if (ret < 0)
return ret;
/*
* Abort automatic DC LDG retry loops (startup or init-after-fault)
* and clear faults before manual diagnostics.
*/
regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT);
regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
/* Wait for LOAD_DIAG to exit */
ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
state, (state & 0x0F) != TAS675X_STATE_LOAD_DIAG &&
(state >> 4) != TAS675X_STATE_LOAD_DIAG,
TAS675X_POLL_INTERVAL_US,
TAS675X_STATE_TRANSITION_TIMEOUT_US);
ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
state34, (state34 & 0x0F) != TAS675X_STATE_LOAD_DIAG &&
(state34 >> 4) != TAS675X_STATE_LOAD_DIAG,
TAS675X_POLL_INTERVAL_US,
TAS675X_STATE_TRANSITION_TIMEOUT_US);
if (ret) {
dev_err(tas->dev,
"DC LDG: abort timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
state, tas675x_state_name(state), tas675x_state_name(state >> 4),
state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
goto out_restore_ldg_ctrl;
}
/* Transition to HIZ state */
ret = tas675x_set_state_all(tas, TAS675X_STATE_HIZ_BOTH);
if (ret)
goto out_restore_ldg_ctrl;
/* Set LOAD_DIAG state for manual DC LDG */
ret = tas675x_set_state_all(tas, TAS675X_STATE_LOAD_DIAG_BOTH);
if (ret)
goto out_restore_ldg_ctrl;
/* Wait for device to transition to LOAD_DIAG state */
ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
state, state == TAS675X_STATE_LOAD_DIAG_BOTH,
TAS675X_POLL_INTERVAL_US,
TAS675X_STATE_TRANSITION_TIMEOUT_US);
ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
state34, state34 == TAS675X_STATE_LOAD_DIAG_BOTH,
TAS675X_POLL_INTERVAL_US,
TAS675X_STATE_TRANSITION_TIMEOUT_US);
if (ret) {
dev_err(tas->dev,
"DC LDG: LOAD_DIAG timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
state, tas675x_state_name(state), tas675x_state_name(state >> 4),
state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
goto out_restore_hiz;
}
/* Clear ABORT and BYPASS bits to enable manual DC LDG */
ret = regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
0);
if (ret)
goto out_restore_hiz;
dev_dbg(tas->dev, "DC LDG: Started\n");
/* Poll all channels for SLEEP state */
ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
state, state == TAS675X_STATE_SLEEP_BOTH,
TAS675X_POLL_INTERVAL_US,
TAS675X_DC_LDG_TIMEOUT_US);
ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
state34, state34 == TAS675X_STATE_SLEEP_BOTH,
TAS675X_POLL_INTERVAL_US,
TAS675X_DC_LDG_TIMEOUT_US);
if (ret) {
dev_err(tas->dev,
"DC LDG: SLEEP timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
state, tas675x_state_name(state), tas675x_state_name(state >> 4),
state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
goto out_restore_hiz;
}
dev_dbg(tas->dev, "DC LDG: Completed successfully (CH1/2=0x%02x, CH3/4=0x%02x)\n",
state, state34);
out_restore_hiz:
tas675x_set_state_all(tas, TAS675X_STATE_HIZ_BOTH);
out_restore_ldg_ctrl:
regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
0);
pm_runtime_mark_last_busy(tas->dev);
pm_runtime_put_autosuspend(tas->dev);
return ret;
}
static int tas675x_set_acldg_trigger(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
unsigned int state, state34;
int ret;
if (!ucontrol->value.integer.value[0])
return 0;
if (snd_soc_component_active(comp))
return -EBUSY;
ret = pm_runtime_resume_and_get(tas->dev);
if (ret < 0)
return ret;
/* AC Load Diagnostics requires SLEEP state */
ret = tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
if (ret) {
dev_err(tas->dev, "AC LDG: Failed to set SLEEP state: %d\n", ret);
goto out;
}
/* Start AC LDG on all 4 channels (0x0F) */
ret = regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x0F);
if (ret) {
dev_err(tas->dev, "AC LDG: Failed to start: %d\n", ret);
goto out;
}
dev_dbg(tas->dev, "AC LDG: Started\n");
/* Poll all channels for SLEEP state */
ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
state, (state == TAS675X_STATE_SLEEP_BOTH),
TAS675X_POLL_INTERVAL_US,
TAS675X_AC_LDG_TIMEOUT_US);
if (ret) {
dev_err(tas->dev,
"AC LDG: CH1/CH2 timeout: %d (state=0x%02x [%s/%s])\n",
ret, state, tas675x_state_name(state),
tas675x_state_name(state >> 4));
regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
goto out;
}
ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
state34, (state34 == TAS675X_STATE_SLEEP_BOTH),
TAS675X_POLL_INTERVAL_US,
TAS675X_AC_LDG_TIMEOUT_US);
if (ret) {
dev_err(tas->dev,
"AC LDG: CH3/CH4 timeout: %d (state=0x%02x [%s/%s])\n",
ret, state34, tas675x_state_name(state34),
tas675x_state_name(state34 >> 4));
regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
goto out;
}
dev_dbg(tas->dev, "AC LDG: Completed successfully (CH1/2=0x%02x, CH3/4=0x%02x)\n",
state, state34);
regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
out:
pm_runtime_mark_last_busy(tas->dev);
pm_runtime_put_autosuspend(tas->dev);
return ret;
}
static int tas675x_rtldg_impedance_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 0xFFFF;
return 0;
}
static int tas675x_get_rtldg_impedance(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
unsigned int msb_reg = (unsigned int)kcontrol->private_value;
u8 buf[2];
int ret;
ret = regmap_bulk_read(tas->regmap, msb_reg, buf, 2);
if (ret)
return ret;
ucontrol->value.integer.value[0] = (buf[0] << 8) | buf[1];
return 0;
}
static int tas675x_dc_resistance_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
/* 10-bit: 2-bit MSB + 8-bit LSB, 0.1 ohm/code, 0-102.3 ohm */
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1023;
return 0;
}
static int tas675x_get_dc_resistance(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
unsigned int lsb_reg = (unsigned int)kcontrol->private_value;
unsigned int msb, lsb, shift;
int ret;
ret = regmap_read(tas->regmap, TAS675X_DC_LDG_DCR_MSB_REG, &msb);
if (ret)
return ret;
ret = regmap_read(tas->regmap, lsb_reg, &lsb);
if (ret)
return ret;
/* 2-bit MSB: CH1=[7:6], CH2=[5:4], CH3=[3:2], CH4=[1:0] */
shift = 6 - (lsb_reg - TAS675X_CH1_DC_LDG_DCR_LSB_REG) * 2;
msb = (msb >> shift) & 0x3;
ucontrol->value.integer.value[0] = (msb << 8) | lsb;
return 0;
}
/* Counterparts with read-only access */
#define SOC_SINGLE_RO(xname, xreg, xshift, xmax) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
.info = snd_soc_info_volsw, \
.get = snd_soc_get_volsw, \
.private_value = SOC_SINGLE_VALUE(xreg, xshift, 0, xmax, 0, 0) }
#define SOC_DC_RESIST_RO(xname, xlsb_reg) \
{ .name = xname, \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
.info = tas675x_dc_resistance_info, \
.get = tas675x_get_dc_resistance, \
.private_value = (xlsb_reg) }
#define SOC_RTLDG_IMP_RO(xname, xreg) \
{ .name = xname, \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
.info = tas675x_rtldg_impedance_info, \
.get = tas675x_get_rtldg_impedance, \
.private_value = (xreg) }
#define SOC_DSP_THRESH_EXT(xname, xthresh) \
{ .name = xname, \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.info = tas675x_rtldg_thresh_info, \
.get = tas675x_get_rtldg_thresh, \
.put = tas675x_set_rtldg_thresh, \
.private_value = (unsigned long)&(xthresh) }
/*
* DAC digital volumes. From -103 to 0 dB in 0.5 dB steps, -103.5 dB means mute.
* DAC analog gain. From -15.5 to 0 dB in 0.5 dB steps, no mute.
*/
static const DECLARE_TLV_DB_SCALE(tas675x_dig_vol_tlv, -10350, 50, 1);
static const DECLARE_TLV_DB_SCALE(tas675x_ana_gain_tlv, -1550, 50, 0);
static const char * const tas675x_ss_texts[] = {
"Disabled", "Triangle", "Random", "Triangle and Random"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ss_enum, TAS675X_SS_CTRL_REG, 0, tas675x_ss_texts);
static const char * const tas675x_ss_tri_range_texts[] = {
"6.5%", "13.5%", "5%", "10%"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ss_tri_range_enum,
TAS675X_SS_RANGE_CTRL_REG, 0,
tas675x_ss_tri_range_texts);
static const char * const tas675x_ss_rdm_range_texts[] = {
"0.83%", "2.50%", "5.83%", "12.50%", "25.83%"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ss_rdm_range_enum,
TAS675X_SS_RANGE_CTRL_REG, 4,
tas675x_ss_rdm_range_texts);
static const char * const tas675x_ss_rdm_dwell_texts[] = {
"1/FSS to 2/FSS", "1/FSS to 4/FSS", "1/FSS to 8/FSS", "1/FSS to 15/FSS"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ss_rdm_dwell_enum,
TAS675X_SS_RANGE_CTRL_REG, 2,
tas675x_ss_rdm_dwell_texts);
static const char * const tas675x_oc_limit_texts[] = {
"Level 4", "Level 3", "Level 2", "Level 1"
};
static SOC_ENUM_SINGLE_DECL(tas675x_oc_limit_enum, TAS675X_CURRENT_LIMIT_CTRL_REG,
0, tas675x_oc_limit_texts);
static const char * const tas675x_otw_texts[] = {
"Disabled", ">95C", ">110C", ">125C", ">135C", ">145C", ">155C", ">165C"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ch1_otw_enum,
TAS675X_OTW_CTRL_CH1_CH2_REG, 4,
tas675x_otw_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch2_otw_enum,
TAS675X_OTW_CTRL_CH1_CH2_REG, 0,
tas675x_otw_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch3_otw_enum,
TAS675X_OTW_CTRL_CH3_CH4_REG, 4,
tas675x_otw_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch4_otw_enum,
TAS675X_OTW_CTRL_CH3_CH4_REG, 0,
tas675x_otw_texts);
static const char * const tas675x_dc_ldg_sl_texts[] = {
"0.5 Ohm", "1 Ohm", "1.5 Ohm", "2 Ohm", "2.5 Ohm",
"3 Ohm", "3.5 Ohm", "4 Ohm", "4.5 Ohm", "5 Ohm"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ch1_dc_ldg_sl_enum,
TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 4,
tas675x_dc_ldg_sl_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch2_dc_ldg_sl_enum,
TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0,
tas675x_dc_ldg_sl_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch3_dc_ldg_sl_enum,
TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 4,
tas675x_dc_ldg_sl_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch4_dc_ldg_sl_enum,
TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0,
tas675x_dc_ldg_sl_texts);
static const char * const tas675x_dc_slol_ramp_texts[] = {
"15 ms", "30 ms", "10 ms", "20 ms"
};
static SOC_ENUM_SINGLE_DECL(tas675x_dc_slol_ramp_enum,
TAS675X_DC_LDG_TIME_CTRL_REG, 6,
tas675x_dc_slol_ramp_texts);
static const char * const tas675x_dc_slol_settling_texts[] = {
"10 ms", "5 ms", "20 ms", "15 ms"
};
static SOC_ENUM_SINGLE_DECL(tas675x_dc_slol_settling_enum,
TAS675X_DC_LDG_TIME_CTRL_REG, 4,
tas675x_dc_slol_settling_texts);
static const char * const tas675x_dc_s2pg_ramp_texts[] = {
"5 ms", "2.5 ms", "10 ms", "15 ms"
};
static SOC_ENUM_SINGLE_DECL(tas675x_dc_s2pg_ramp_enum,
TAS675X_DC_LDG_TIME_CTRL_REG, 2,
tas675x_dc_s2pg_ramp_texts);
static const char * const tas675x_dc_s2pg_settling_texts[] = {
"10 ms", "5 ms", "20 ms", "30 ms"
};
static SOC_ENUM_SINGLE_DECL(tas675x_dc_s2pg_settling_enum,
TAS675X_DC_LDG_TIME_CTRL_REG, 0,
tas675x_dc_s2pg_settling_texts);
static const char * const tas675x_dsp_mode_texts[] = {
"Normal", "LLP", "FFLP"
};
static SOC_ENUM_SINGLE_DECL(tas675x_dsp_mode_enum,
TAS675X_LL_EN_REG, 0,
tas675x_dsp_mode_texts);
static const char * const tas675x_ana_ramp_texts[] = {
"15us", "60us", "200us", "400us"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ana_ramp_enum,
TAS675X_ANALOG_GAIN_RAMP_CTRL_REG, 2,
tas675x_ana_ramp_texts);
static const char * const tas675x_ramp_rate_texts[] = {
"4 FS", "16 FS", "32 FS", "Instant"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ramp_down_rate_enum,
TAS675X_DIG_VOL_RAMP_CTRL_REG, 6,
tas675x_ramp_rate_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ramp_up_rate_enum,
TAS675X_DIG_VOL_RAMP_CTRL_REG, 2,
tas675x_ramp_rate_texts);
static const char * const tas675x_ramp_step_texts[] = {
"4dB", "2dB", "1dB", "0.5dB"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ramp_down_step_enum,
TAS675X_DIG_VOL_RAMP_CTRL_REG, 4,
tas675x_ramp_step_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ramp_up_step_enum,
TAS675X_DIG_VOL_RAMP_CTRL_REG, 0,
tas675x_ramp_step_texts);
static const char * const tas675x_vol_combine_ch12_texts[] = {
"Independent", "CH2 follows CH1", "CH1 follows CH2"
};
static SOC_ENUM_SINGLE_DECL(tas675x_vol_combine_ch12_enum,
TAS675X_DIG_VOL_COMBINE_CTRL_REG, 0,
tas675x_vol_combine_ch12_texts);
static const char * const tas675x_vol_combine_ch34_texts[] = {
"Independent", "CH4 follows CH3", "CH3 follows CH4"
};
static SOC_ENUM_SINGLE_DECL(tas675x_vol_combine_ch34_enum,
TAS675X_DIG_VOL_COMBINE_CTRL_REG, 2,
tas675x_vol_combine_ch34_texts);
static const char * const tas675x_auto_mute_time_texts[] = {
"11.5ms", "53ms", "106.5ms", "266.5ms",
"535ms", "1065ms", "2665ms", "5330ms"
};
static SOC_ENUM_SINGLE_DECL(tas675x_ch1_mute_time_enum,
TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 4,
tas675x_auto_mute_time_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch2_mute_time_enum,
TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 0,
tas675x_auto_mute_time_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch3_mute_time_enum,
TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 4,
tas675x_auto_mute_time_texts);
static SOC_ENUM_SINGLE_DECL(tas675x_ch4_mute_time_enum,
TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 0,
tas675x_auto_mute_time_texts);
/*
* ALSA Mixer Controls
*
* For detailed documentation of each control see:
* Documentation/sound/codecs/tas675x.rst
*/
static const struct snd_kcontrol_new tas675x_snd_controls[] = {
/* Volume & Gain Control */
SOC_DOUBLE_R_TLV("Analog Playback Volume", TAS675X_ANALOG_GAIN_CH1_CH2_REG,
TAS675X_ANALOG_GAIN_CH3_CH4_REG, 1, 0x1F, 1, tas675x_ana_gain_tlv),
SOC_ENUM("Analog Gain Ramp Step", tas675x_ana_ramp_enum),
SOC_SINGLE_RANGE_TLV("CH1 Digital Playback Volume",
TAS675X_DIG_VOL_CH1_REG, 0, 0x30, 0xFF, 1,
tas675x_dig_vol_tlv),
SOC_SINGLE_RANGE_TLV("CH2 Digital Playback Volume",
TAS675X_DIG_VOL_CH2_REG, 0, 0x30, 0xFF, 1,
tas675x_dig_vol_tlv),
SOC_SINGLE_RANGE_TLV("CH3 Digital Playback Volume",
TAS675X_DIG_VOL_CH3_REG, 0, 0x30, 0xFF, 1,
tas675x_dig_vol_tlv),
SOC_SINGLE_RANGE_TLV("CH4 Digital Playback Volume",
TAS675X_DIG_VOL_CH4_REG, 0, 0x30, 0xFF, 1,
tas675x_dig_vol_tlv),
SOC_ENUM("Volume Ramp Down Rate", tas675x_ramp_down_rate_enum),
SOC_ENUM("Volume Ramp Down Step", tas675x_ramp_down_step_enum),
SOC_ENUM("Volume Ramp Up Rate", tas675x_ramp_up_rate_enum),
SOC_ENUM("Volume Ramp Up Step", tas675x_ramp_up_step_enum),
SOC_ENUM("CH1/2 Volume Combine", tas675x_vol_combine_ch12_enum),
SOC_ENUM("CH3/4 Volume Combine", tas675x_vol_combine_ch34_enum),
/* Auto Mute & Silence Detection */
SOC_SINGLE("CH1 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 0, 1, 0),
SOC_SINGLE("CH2 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 1, 1, 0),
SOC_SINGLE("CH3 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 2, 1, 0),
SOC_SINGLE("CH4 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 3, 1, 0),
SOC_SINGLE("Auto Mute Combine Switch", TAS675X_AUTO_MUTE_EN_REG, 4, 1, 0),
SOC_ENUM("CH1 Auto Mute Time", tas675x_ch1_mute_time_enum),
SOC_ENUM("CH2 Auto Mute Time", tas675x_ch2_mute_time_enum),
SOC_ENUM("CH3 Auto Mute Time", tas675x_ch3_mute_time_enum),
SOC_ENUM("CH4 Auto Mute Time", tas675x_ch4_mute_time_enum),
/* Clock & EMI Management */
SOC_ENUM("Spread Spectrum Mode", tas675x_ss_enum),
SOC_ENUM("SS Triangle Range", tas675x_ss_tri_range_enum),
SOC_ENUM("SS Random Range", tas675x_ss_rdm_range_enum),
SOC_ENUM("SS Random Dwell Range", tas675x_ss_rdm_dwell_enum),
SOC_SINGLE("SS Triangle Dwell Min", TAS675X_SS_DWELL_CTRL_REG, 4, 15, 0),
SOC_SINGLE("SS Triangle Dwell Max", TAS675X_SS_DWELL_CTRL_REG, 0, 15, 0),
/* Hardware Protection */
SOC_SINGLE("OTSD Auto Recovery Switch", TAS675X_OTSD_RECOVERY_EN_REG, 1, 1, 0),
SOC_ENUM("Overcurrent Limit Level", tas675x_oc_limit_enum),
SOC_ENUM("CH1 OTW Threshold", tas675x_ch1_otw_enum),
SOC_ENUM("CH2 OTW Threshold", tas675x_ch2_otw_enum),
SOC_ENUM("CH3 OTW Threshold", tas675x_ch3_otw_enum),
SOC_ENUM("CH4 OTW Threshold", tas675x_ch4_otw_enum),
/* DSP Signal Path & Mode */
SOC_ENUM("DSP Signal Path Mode", tas675x_dsp_mode_enum),
/* DC Load Diagnostics */
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "DC LDG Trigger",
.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
.info = snd_ctl_boolean_mono_info,
.put = tas675x_set_dcldg_trigger,
},
SOC_SINGLE("DC LDG Auto Diagnostics Switch", TAS675X_DC_LDG_CTRL_REG, 0, 1, 1),
SOC_SINGLE("CH1 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 3, 1, 0),
SOC_SINGLE("CH2 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 2, 1, 0),
SOC_SINGLE("CH3 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 1, 1, 0),
SOC_SINGLE("CH4 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 0, 1, 0),
SOC_ENUM("DC LDG SLOL Ramp Time", tas675x_dc_slol_ramp_enum),
SOC_ENUM("DC LDG SLOL Settling Time", tas675x_dc_slol_settling_enum),
SOC_ENUM("DC LDG S2PG Ramp Time", tas675x_dc_s2pg_ramp_enum),
SOC_ENUM("DC LDG S2PG Settling Time", tas675x_dc_s2pg_settling_enum),
SOC_ENUM("CH1 DC LDG SL Threshold", tas675x_ch1_dc_ldg_sl_enum),
SOC_ENUM("CH2 DC LDG SL Threshold", tas675x_ch2_dc_ldg_sl_enum),
SOC_ENUM("CH3 DC LDG SL Threshold", tas675x_ch3_dc_ldg_sl_enum),
SOC_ENUM("CH4 DC LDG SL Threshold", tas675x_ch4_dc_ldg_sl_enum),
SOC_SINGLE_RO("DC LDG Result", TAS675X_DC_LDG_RESULT_REG, 0, 0xFF),
SOC_SINGLE_RO("CH1 DC LDG Report", TAS675X_DC_LDG_REPORT_CH1_CH2_REG, 4, 0x0F),
SOC_SINGLE_RO("CH2 DC LDG Report", TAS675X_DC_LDG_REPORT_CH1_CH2_REG, 0, 0x0F),
SOC_SINGLE_RO("CH3 DC LDG Report", TAS675X_DC_LDG_REPORT_CH3_CH4_REG, 4, 0x0F),
SOC_SINGLE_RO("CH4 DC LDG Report", TAS675X_DC_LDG_REPORT_CH3_CH4_REG, 0, 0x0F),
SOC_SINGLE_RO("CH1 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 7, 1),
SOC_SINGLE_RO("CH2 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 6, 1),
SOC_SINGLE_RO("CH3 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 5, 1),
SOC_SINGLE_RO("CH4 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 4, 1),
SOC_DC_RESIST_RO("CH1 DC Resistance", TAS675X_CH1_DC_LDG_DCR_LSB_REG),
SOC_DC_RESIST_RO("CH2 DC Resistance", TAS675X_CH2_DC_LDG_DCR_LSB_REG),
SOC_DC_RESIST_RO("CH3 DC Resistance", TAS675X_CH3_DC_LDG_DCR_LSB_REG),
SOC_DC_RESIST_RO("CH4 DC Resistance", TAS675X_CH4_DC_LDG_DCR_LSB_REG),
/* AC Load Diagnostics */
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "AC LDG Trigger",
.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
.info = snd_ctl_boolean_mono_info,
.put = tas675x_set_acldg_trigger,
},
SOC_SINGLE("AC LDG Gain", TAS675X_AC_LDG_CTRL_REG, 4, 1, 0),
SOC_SINGLE("AC LDG Test Frequency", TAS675X_AC_LDG_FREQ_CTRL_REG, 0, 0xFF, 0),
SOC_SINGLE_RO("CH1 AC LDG Real", TAS675X_AC_LDG_REPORT_CH1_R_REG, 0, 0xFF),
SOC_SINGLE_RO("CH1 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH1_I_REG, 0, 0xFF),
SOC_SINGLE_RO("CH2 AC LDG Real", TAS675X_AC_LDG_REPORT_CH2_R_REG, 0, 0xFF),
SOC_SINGLE_RO("CH2 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH2_I_REG, 0, 0xFF),
SOC_SINGLE_RO("CH3 AC LDG Real", TAS675X_AC_LDG_REPORT_CH3_R_REG, 0, 0xFF),
SOC_SINGLE_RO("CH3 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH3_I_REG, 0, 0xFF),
SOC_SINGLE_RO("CH4 AC LDG Real", TAS675X_AC_LDG_REPORT_CH4_R_REG, 0, 0xFF),
SOC_SINGLE_RO("CH4 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH4_I_REG, 0, 0xFF),
/* Temperature and Voltage Monitoring */
SOC_SINGLE_RO("PVDD Sense", TAS675X_PVDD_SENSE_REG, 0, 0xFF),
SOC_SINGLE_RO("Global Temperature", TAS675X_TEMP_GLOBAL_REG, 0, 0xFF),
SOC_SINGLE_RO("CH1 Temperature Range", TAS675X_TEMP_CH1_CH2_REG, 0, 7),
SOC_SINGLE_RO("CH2 Temperature Range", TAS675X_TEMP_CH1_CH2_REG, 3, 7),
SOC_SINGLE_RO("CH3 Temperature Range", TAS675X_TEMP_CH3_CH4_REG, 0, 7),
SOC_SINGLE_RO("CH4 Temperature Range", TAS675X_TEMP_CH3_CH4_REG, 3, 7),
/* Speaker Protection & Detection */
SOC_SINGLE("Tweeter Detection Switch", TAS675X_TWEETER_DETECT_CTRL_REG, 0, 1, 1),
SOC_SINGLE("Tweeter Detect Threshold", TAS675X_TWEETER_DETECT_THRESH_REG, 0, 0xFF, 0),
SOC_SINGLE_RO("CH1 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 3, 1),
SOC_SINGLE_RO("CH2 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 2, 1),
SOC_SINGLE_RO("CH3 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 1, 1),
SOC_SINGLE_RO("CH4 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 0, 1),
/*
* Unavailable in LLP, available in Normal & FFLP
*/
SOC_SINGLE("Thermal Foldback Switch", TAS675X_DSP_CTRL_REG, 0, 1, 0),
SOC_SINGLE("PVDD Foldback Switch", TAS675X_DSP_CTRL_REG, 4, 1, 0),
SOC_SINGLE("DC Blocker Bypass Switch", TAS675X_DC_BLOCK_BYP_REG, 0, 1, 0),
SOC_SINGLE("Clip Detect Switch", TAS675X_CLIP_DETECT_CTRL_REG, 6, 1, 0),
SOC_SINGLE("Audio SDOUT Switch", TAS675X_DSP_CTRL_REG, 5, 1, 0),
/*
* Unavailable in both FFLP and LLP, Normal mode only
*/
/* Real-Time Load Diagnostics */
SOC_SINGLE("CH1 RTLDG Switch", TAS675X_RTLDG_EN_REG, 3, 1, 0),
SOC_SINGLE("CH2 RTLDG Switch", TAS675X_RTLDG_EN_REG, 2, 1, 0),
SOC_SINGLE("CH3 RTLDG Switch", TAS675X_RTLDG_EN_REG, 1, 1, 0),
SOC_SINGLE("CH4 RTLDG Switch", TAS675X_RTLDG_EN_REG, 0, 1, 0),
SOC_SINGLE("RTLDG Clip Mask Switch", TAS675X_RTLDG_EN_REG, 4, 1, 0),
SOC_SINGLE("ISENSE Calibration Switch", TAS675X_ISENSE_CAL_REG, 3, 1, 0),
SOC_DSP_THRESH_EXT("RTLDG Open Load Threshold",
tas675x_dsp_defaults[TAS675X_DSP_PARAM_ID_OL_THRESH]),
SOC_DSP_THRESH_EXT("RTLDG Short Load Threshold",
tas675x_dsp_defaults[TAS675X_DSP_PARAM_ID_SL_THRESH]),
SOC_RTLDG_IMP_RO("CH1 RTLDG Impedance", TAS675X_CH1_RTLDG_IMP_MSB_REG),
SOC_RTLDG_IMP_RO("CH2 RTLDG Impedance", TAS675X_CH2_RTLDG_IMP_MSB_REG),
SOC_RTLDG_IMP_RO("CH3 RTLDG Impedance", TAS675X_CH3_RTLDG_IMP_MSB_REG),
SOC_RTLDG_IMP_RO("CH4 RTLDG Impedance", TAS675X_CH4_RTLDG_IMP_MSB_REG),
};
static const struct snd_kcontrol_new tas675x_audio_path_switch =
SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 1);
static const struct snd_kcontrol_new tas675x_anc_path_switch =
SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 1);
static const struct snd_soc_dapm_widget tas675x_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("Analog Core", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("SDOUT Vpredict", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("SDOUT Isense", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_DAC("Audio DAC", "Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC("ANC DAC", "ANC Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_ADC("Feedback ADC", "Feedback Capture", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_SWITCH("Audio Path", SND_SOC_NOPM, 0, 0,
&tas675x_audio_path_switch),
SND_SOC_DAPM_SWITCH("ANC Path", SND_SOC_NOPM, 0, 0,
&tas675x_anc_path_switch),
/*
* Even though all channels are coupled in terms of power control,
* use logical outputs for each channel to allow independent routing
* and DAPM controls if needed.
*/
SND_SOC_DAPM_OUTPUT("OUT_CH1"),
SND_SOC_DAPM_OUTPUT("OUT_CH2"),
SND_SOC_DAPM_OUTPUT("OUT_CH3"),
SND_SOC_DAPM_OUTPUT("OUT_CH4"),
SND_SOC_DAPM_INPUT("SPEAKER_LOAD"),
};
static const struct snd_soc_dapm_route tas675x_dapm_routes[] = {
{ "Audio DAC", NULL, "Analog Core" },
{ "Audio Path", "Switch", "Audio DAC" },
{ "OUT_CH1", NULL, "Audio Path" },
{ "OUT_CH2", NULL, "Audio Path" },
{ "OUT_CH3", NULL, "Audio Path" },
{ "OUT_CH4", NULL, "Audio Path" },
{ "ANC DAC", NULL, "Analog Core" },
{ "ANC Path", "Switch", "ANC DAC" },
{ "OUT_CH1", NULL, "ANC Path" },
{ "OUT_CH2", NULL, "ANC Path" },
{ "OUT_CH3", NULL, "ANC Path" },
{ "OUT_CH4", NULL, "ANC Path" },
{ "Feedback ADC", NULL, "Analog Core" },
{ "Feedback ADC", NULL, "SDOUT Vpredict" },
{ "Feedback ADC", NULL, "SDOUT Isense" },
{ "Feedback ADC", NULL, "SPEAKER_LOAD" },
};
static void tas675x_program_slot_offsets(struct tas675x_priv *tas,
int dai_id, int slot_width)
{
int offset = 0;
switch (dai_id) {
case 0:
/* Standard Audio on SDIN */
if (tas->audio_slot >= 0)
offset = tas->audio_slot * slot_width;
else if (tas->tx_mask)
offset = __ffs(tas->tx_mask) * slot_width;
else
return;
offset += tas->bclk_offset;
regmap_update_bits(tas->regmap, TAS675X_SDIN_OFFSET_MSB_REG,
TAS675X_SDIN_AUDIO_OFF_MSB_MASK,
FIELD_PREP(TAS675X_SDIN_AUDIO_OFF_MSB_MASK, offset >> 8));
regmap_write(tas->regmap, TAS675X_SDIN_AUDIO_OFFSET_REG,
offset & 0xFF);
break;
case 1:
/*
* Low-Latency Playback on SDIN, **only** enabled in LLP mode
* and to be mixed with main audio before output amplification
* to achieve ANC/RNC.
*/
if (tas->llp_slot >= 0)
offset = tas->llp_slot * slot_width;
else if (tas->tx_mask)
offset = __ffs(tas->tx_mask) * slot_width;
else
return;
offset += tas->bclk_offset;
regmap_update_bits(tas->regmap, TAS675X_SDIN_OFFSET_MSB_REG,
TAS675X_SDIN_LL_OFF_MSB_MASK,
FIELD_PREP(TAS675X_SDIN_LL_OFF_MSB_MASK, offset >> 8));
regmap_write(tas->regmap, TAS675X_SDIN_LL_OFFSET_REG,
offset & 0xFF);
break;
case 2:
/* SDOUT Data Output (Vpredict + Isense feedback) */
if (!tas->slot_width)
break;
if (tas->vpredict_slot >= 0) {
offset = tas->vpredict_slot * slot_width;
offset += tas->bclk_offset;
regmap_update_bits(tas->regmap, TAS675X_SDOUT_OFFSET_MSB_REG,
TAS675X_SDOUT_VP_OFF_MSB_MASK,
FIELD_PREP(TAS675X_SDOUT_VP_OFF_MSB_MASK, offset >> 8));
regmap_write(tas->regmap, TAS675X_VPREDICT_OFFSET_REG,
offset & 0xFF);
}
if (tas->isense_slot >= 0) {
offset = tas->isense_slot * slot_width;
offset += tas->bclk_offset;
regmap_update_bits(tas->regmap, TAS675X_SDOUT_OFFSET_MSB_REG,
TAS675X_SDOUT_IS_OFF_MSB_MASK,
FIELD_PREP(TAS675X_SDOUT_IS_OFF_MSB_MASK, offset >> 8));
regmap_write(tas->regmap, TAS675X_ISENSE_OFFSET_REG,
offset & 0xFF);
}
break;
}
if (offset > 511)
dev_warn(tas->dev,
"DAI %d slot offset %d exceeds 511 SCLK limit\n",
dai_id, offset);
}
static int tas675x_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 tas675x_priv *tas = snd_soc_component_get_drvdata(component);
unsigned int rate = params_rate(params);
u8 word_length;
/*
* Single clock domain: SDIN and SDOUT share one SCLK/FSYNC pair,
* so all active DAIs must use the same sample rate.
*/
if ((READ_ONCE(tas->active_playback_dais) || READ_ONCE(tas->active_capture_dais)) &&
tas->rate && tas->rate != rate) {
dev_err(component->dev,
"Rate %u conflicts with active rate %u\n",
rate, tas->rate);
return -EINVAL;
}
switch (params_width(params)) {
case 16:
word_length = TAS675X_WL_16BIT;
break;
case 20:
word_length = TAS675X_WL_20BIT;
break;
case 24:
word_length = TAS675X_WL_24BIT;
break;
case 32:
word_length = TAS675X_WL_32BIT;
break;
default:
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/*
* RTLDG is not supported above 96kHz. Auto-disable to
* prevent DSP overload and restore when rate drops back.
*/
if (rate > 96000) {
unsigned int val;
regmap_read(component->regmap, TAS675X_RTLDG_EN_REG,
&val);
if (val & TAS675X_RTLDG_CH_EN_MASK) {
tas->saved_rtldg_en = val;
dev_dbg(component->dev,
"Sample rate %dHz > 96kHz: Auto-disabling RTLDG\n",
rate);
regmap_update_bits(component->regmap,
TAS675X_RTLDG_EN_REG,
TAS675X_RTLDG_CH_EN_MASK,
0x00);
}
} else if (tas->saved_rtldg_en) {
unsigned int cur;
/*
* Respect overrides and only restore if RTLDG is still auto-disabled
*/
regmap_read(component->regmap, TAS675X_RTLDG_EN_REG,
&cur);
if (!(cur & TAS675X_RTLDG_CH_EN_MASK)) {
dev_dbg(component->dev,
"Restoring RTLDG config after high-rate stream\n");
regmap_update_bits(component->regmap,
TAS675X_RTLDG_EN_REG,
TAS675X_RTLDG_CH_EN_MASK,
TAS675X_RTLDG_CH_EN_MASK &
tas->saved_rtldg_en);
}
tas->saved_rtldg_en = 0;
}
/* Set SDIN word length (audio path + low-latency path) */
regmap_update_bits(component->regmap, TAS675X_SDIN_CTRL_REG,
TAS675X_SDIN_WL_MASK,
FIELD_PREP(TAS675X_SDIN_AUDIO_WL_MASK, word_length) |
FIELD_PREP(TAS675X_SDIN_LL_WL_MASK, word_length));
} else {
/* Set SDOUT word length (VPREDICT + ISENSE) for capture */
regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
TAS675X_SDOUT_WL_MASK,
FIELD_PREP(TAS675X_SDOUT_VP_WL_MASK, word_length) |
FIELD_PREP(TAS675X_SDOUT_IS_WL_MASK, word_length));
}
tas675x_program_slot_offsets(tas, dai->id,
tas->slot_width ?: params_width(params));
tas->rate = rate;
return 0;
}
static int tas675x_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
bool tdm_mode = false, i2s_mode = false;
/* Enforce Clocking Direction (Codec is strictly a consumer) */
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FC:
break;
default:
dev_err(component->dev, "Unsupported clock provider format\n");
return -EINVAL;
}
/* SCLK polarity: NB_NF or IB_NF only (no FSYNC inversion support) */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
regmap_update_bits(component->regmap, TAS675X_SCLK_INV_CTRL_REG,
TAS675X_SCLK_INV_MASK, 0x00);
break;
case SND_SOC_DAIFMT_IB_NF:
regmap_update_bits(component->regmap, TAS675X_SCLK_INV_CTRL_REG,
TAS675X_SCLK_INV_MASK, TAS675X_SCLK_INV_MASK);
break;
default:
dev_err(component->dev, "Unsupported clock inversion\n");
return -EINVAL;
}
/* Configure Audio Format and TDM Enable */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
i2s_mode = true;
tas->bclk_offset = 0;
regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
TAS675X_FS_PULSE_MASK,
TAS675X_SAP_FMT_I2S);
regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
TAS675X_SDOUT_SELECT_MASK,
TAS675X_SDOUT_SELECT_NON_TDM);
break;
case SND_SOC_DAIFMT_RIGHT_J:
tas->bclk_offset = 0;
regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
TAS675X_FS_PULSE_MASK,
TAS675X_SAP_FMT_RIGHT_J);
regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
TAS675X_SDOUT_SELECT_MASK,
TAS675X_SDOUT_SELECT_NON_TDM);
break;
case SND_SOC_DAIFMT_LEFT_J:
tas->bclk_offset = 0;
regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
TAS675X_FS_PULSE_MASK,
TAS675X_SAP_FMT_LEFT_J);
regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
TAS675X_SDOUT_SELECT_MASK,
TAS675X_SDOUT_SELECT_NON_TDM);
break;
case SND_SOC_DAIFMT_DSP_A:
tdm_mode = true;
tas->bclk_offset = 1;
regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
TAS675X_FS_PULSE_MASK,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_TDM |
TAS675X_FS_PULSE_SHORT);
regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
TAS675X_SDOUT_SELECT_MASK,
TAS675X_SDOUT_SELECT_TDM_SDOUT1);
break;
case SND_SOC_DAIFMT_DSP_B:
tdm_mode = true;
tas->bclk_offset = 0;
regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
TAS675X_FS_PULSE_MASK,
TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_TDM |
TAS675X_FS_PULSE_SHORT);
regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
TAS675X_SDOUT_SELECT_MASK,
TAS675X_SDOUT_SELECT_TDM_SDOUT1);
break;
default:
dev_err(component->dev, "Unsupported DAI format\n");
return -EINVAL;
}
/* Setup Vpredict and Isense outputs */
if (dai->id == 2) {
unsigned int sdout_en = 0;
if (tdm_mode) {
/* TDM: Vpredict and Isense may coexist on separate slots */
if (tas->vpredict_slot >= 0)
sdout_en |= TAS675X_SDOUT_EN_VPREDICT;
if (tas->isense_slot >= 0)
sdout_en |= TAS675X_SDOUT_EN_ISENSE;
regmap_update_bits(component->regmap,
TAS675X_SDOUT_EN_REG,
TAS675X_SDOUT_EN_VPREDICT |
TAS675X_SDOUT_EN_ISENSE,
sdout_en);
if (tas->vpredict_slot >= 0 && tas->isense_slot >= 0 &&
abs(tas->vpredict_slot - tas->isense_slot) < 4)
dev_warn(component->dev,
"ti,vpredict-slot-no and ti,isense-slot-no overlaps (each occupies 4 consecutive slots)\n");
} else if (i2s_mode) {
/* I2S: only one source at a time; Vpredict takes priority */
if (tas->vpredict_slot >= 0)
sdout_en = TAS675X_SDOUT_NON_TDM_SEL_VPREDICT |
TAS675X_SDOUT_EN_NON_TDM_ALL;
else if (tas->isense_slot >= 0)
sdout_en = TAS675X_SDOUT_NON_TDM_SEL_ISENSE |
TAS675X_SDOUT_EN_NON_TDM_ALL;
regmap_update_bits(component->regmap,
TAS675X_SDOUT_EN_REG,
TAS675X_SDOUT_NON_TDM_SEL_MASK |
TAS675X_SDOUT_EN_NON_TDM_ALL,
sdout_en);
if (sdout_en &&
tas->gpio1_func != TAS675X_GPIO_SEL_SDOUT2 &&
tas->gpio2_func != TAS675X_GPIO_SEL_SDOUT2)
dev_warn(component->dev,
"sdout enabled in I2S mode but no GPIO configured as SDOUT2; Ch3/Ch4 will be absent\n");
}
}
return 0;
}
static int tas675x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int slot_width)
{
struct tas675x_priv *tas = snd_soc_component_get_drvdata(dai->component);
if (slots == 0) {
tas->slot_width = 0;
tas->tx_mask = 0;
return 0;
}
/* No rx_mask as hardware does not support channel muxing for capture */
tas->slot_width = slot_width;
tas->tx_mask = tx_mask;
return 0;
}
static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
{
struct snd_soc_component *component = dai->component;
struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
unsigned int discard;
int ret;
if (direction == SNDRV_PCM_STREAM_CAPTURE) {
if (mute)
clear_bit(dai->id, &tas->active_capture_dais);
else
set_bit(dai->id, &tas->active_capture_dais);
return 0;
}
/*
* Track which playback DAIs are active.
* The TAS675x has two playback DAIs (main audio and LLP).
* Only transition to SLEEP when ALL are muted.
*/
if (mute)
clear_bit(dai->id, &tas->active_playback_dais);
else
set_bit(dai->id, &tas->active_playback_dais);
/* Last playback stream */
if (mute && !READ_ONCE(tas->active_playback_dais)) {
ret = tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
regmap_read(tas->regmap, TAS675X_CLK_FAULT_LATCHED_REG, &discard);
return ret;
}
return tas675x_set_state_all(tas,
READ_ONCE(tas->active_playback_dais) ?
TAS675X_STATE_PLAY_BOTH :
TAS675X_STATE_SLEEP_BOTH);
}
static const struct snd_soc_dai_ops tas675x_dai_ops = {
.hw_params = tas675x_hw_params,
.set_fmt = tas675x_set_fmt,
.set_tdm_slot = tas675x_set_tdm_slot,
.mute_stream = tas675x_mute_stream,
};
static struct snd_soc_dai_driver tas675x_dais[] = {
{
.name = "tas675x-audio",
.id = 0,
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &tas675x_dai_ops,
},
/* Only available when Low Latency Path (LLP) is enabled */
{
.name = "tas675x-anc",
.id = 1,
.playback = {
.stream_name = "ANC Playback",
.channels_min = 2,
.channels_max = 4,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &tas675x_dai_ops,
},
{
.name = "tas675x-feedback",
.id = 2,
.capture = {
.stream_name = "Feedback Capture",
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &tas675x_dai_ops,
}
};
/*
* Enable regulators and release hardware reset GPIOs.
* The device is not I2C-accessible until this returns.
*/
static int tas675x_hw_enable(struct tas675x_priv *tas)
{
int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(tas->supplies), tas->supplies);
if (ret) {
dev_err(tas->dev, "Failed to enable regulators: %d\n", ret);
return ret;
}
if (!IS_ERR(tas->vbat)) {
ret = regulator_enable(tas->vbat);
if (ret) {
dev_err(tas->dev, "Failed to enable vbat: %d\n", ret);
regulator_bulk_disable(ARRAY_SIZE(tas->supplies), tas->supplies);
return ret;
}
}
if (tas->pd_gpio && tas->stby_gpio) {
/*
* Independent Pin Control
* Deassert PD first to boot digital, then STBY for analog.
*/
/* Min 4ms digital boot wait */
gpiod_set_value_cansleep(tas->pd_gpio, 0);
usleep_range(4000, 5000);
/* ~2ms analog stabilization */
gpiod_set_value_cansleep(tas->stby_gpio, 0);
usleep_range(2000, 3000);
} else if (tas->pd_gpio) {
/*
* Simultaneous Pin Release
* STBY tied to PD or hardwired HIGH.
*/
/* 6ms wait for simultaneous release transition */
gpiod_set_value_cansleep(tas->pd_gpio, 0);
usleep_range(6000, 7000);
} else {
/*
* PD hardwired, device in DEEP_SLEEP.
* Digital core already booted, I2C active. Deassert STBY
* to bring up the analog output stage.
*/
/* ~2ms analog stabilization */
gpiod_set_value_cansleep(tas->stby_gpio, 0);
usleep_range(2000, 3000);
}
return 0;
}
static void tas675x_hw_disable(struct tas675x_priv *tas)
{
if (tas->stby_gpio)
gpiod_set_value_cansleep(tas->stby_gpio, 1);
if (tas->pd_gpio)
gpiod_set_value_cansleep(tas->pd_gpio, 1);
/*
* Hold PD/STBY asserted for at least 10ms
* before removing PVDD, VBAT or DVDD.
*/
usleep_range(10000, 11000);
if (!IS_ERR(tas->vbat))
regulator_disable(tas->vbat);
regulator_bulk_disable(ARRAY_SIZE(tas->supplies), tas->supplies);
}
/*
* Write device start-up defaults.
* Must be called after tas675x_hw_enable() and after regcache is enabled.
*/
static int tas675x_init_device(struct tas675x_priv *tas)
{
struct regmap *regmap = tas->regmap;
unsigned int val;
int ret, i;
/* Clear POR fault flag to prevent IRQ storm */
regmap_read(regmap, TAS675X_POWER_FAULT_LATCHED_REG, &val);
/* Bypass DC Load Diagnostics for fast boot */
if (tas->fast_boot)
regmap_update_bits(regmap, TAS675X_DC_LDG_CTRL_REG,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT);
tas675x_select_book(regmap, TAS675X_BOOK_DEFAULT);
/* Enter setup mode */
ret = regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_ENTER_VAL1);
if (ret)
goto err;
ret = regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_ENTER_VAL2);
if (ret)
goto err;
/* Set all channels to Sleep (required before Page 1 config) */
tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
/* Set DAC clock per TRM startup script */
regmap_write(regmap, TAS675X_DAC_CLK_REG, 0x00);
/*
* Switch to Page 1 for safety-critical OC/CBC configuration,
* while bypassing regcache. (Page 1 not accessible post setup)
*/
regcache_cache_bypass(regmap, true);
ret = regmap_multi_reg_write(regmap, tas675x_page1_init,
ARRAY_SIZE(tas675x_page1_init));
regcache_cache_bypass(regmap, false);
if (ret)
goto err_setup;
/* Resync regmap's cached page selector */
regmap_write(regmap, TAS675X_PAGE_CTRL_REG, 0x00);
/* Exit setup mode */
regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_EXIT_VAL);
regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_EXIT_VAL);
/* Write DSP parameters if cached */
for (i = 0; i < ARRAY_SIZE(tas->dsp_params); i++) {
if (tas->dsp_params[i].val)
tas675x_dsp_mem_write(tas,
tas->dsp_params[i].page,
tas->dsp_params[i].reg,
tas->dsp_params[i].val);
}
/*
* Configure fault and warning event routing:
*
* ROUTING_1: CP fault/UVLO latch, OUTM soft short latch
* ROUTING_2: CBC latch, OTSD latch, OTSD, power fault
* ROUTING_3: CBC latch, OTSD latch, power latch, DC LDG,
* OTSD, power warnings
* ROUTING_4: OC latch, DC latch, protection shutdown
* OTW latch, OTW, clip latch
* ROUTING_5: clock latch+non-latch, RTLDG latch
* CBC warning, clip warning
*/
regmap_write(regmap, TAS675X_REPORT_ROUTING_1_REG, 0x70);
regmap_write(regmap, TAS675X_REPORT_ROUTING_2_REG, 0xA3);
regmap_write(regmap, TAS675X_REPORT_ROUTING_3_REG, 0xBB);
regmap_write(regmap, TAS675X_REPORT_ROUTING_4_REG, 0x7E);
regmap_write(regmap, TAS675X_REPORT_ROUTING_5_REG, 0xF3);
/* Configure GPIO pins if specified in DT */
if (tas->gpio1_func >= 0 || tas->gpio2_func >= 0) {
unsigned int gpio_ctrl = TAS675X_GPIO_CTRL_RSTVAL;
tas675x_config_gpio_pin(regmap, tas->gpio1_func,
TAS675X_GPIO1_OUTPUT_SEL_REG,
0, &gpio_ctrl);
tas675x_config_gpio_pin(regmap, tas->gpio2_func,
TAS675X_GPIO2_OUTPUT_SEL_REG,
1, &gpio_ctrl);
regmap_write(regmap, TAS675X_GPIO_CTRL_REG, gpio_ctrl);
}
/* Clear fast boot bits */
if (tas->fast_boot)
regmap_update_bits(regmap, TAS675X_DC_LDG_CTRL_REG,
TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
0);
/* Clear any stale faults from the boot sequence */
regmap_read(regmap, TAS675X_POWER_FAULT_STATUS_1_REG, &val);
regmap_read(regmap, TAS675X_POWER_FAULT_LATCHED_REG, &val);
regmap_read(regmap, TAS675X_CLK_FAULT_LATCHED_REG, &val);
regmap_write(regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
return 0;
err_setup:
regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_EXIT_VAL);
regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_EXIT_VAL);
err:
dev_err(tas->dev, "Init device failed: %d\n", ret);
return ret;
}
static void tas675x_power_off(struct tas675x_priv *tas)
{
regcache_cache_only(tas->regmap, true);
regcache_mark_dirty(tas->regmap);
tas675x_hw_disable(tas);
}
static int tas675x_power_on(struct tas675x_priv *tas)
{
int ret;
ret = tas675x_hw_enable(tas);
if (ret)
return ret;
regcache_cache_only(tas->regmap, false);
regcache_mark_dirty(tas->regmap);
ret = tas675x_init_device(tas);
if (ret)
goto err_disable;
ret = regcache_sync(tas->regmap);
if (ret) {
dev_err(tas->dev, "Failed to sync regcache: %d\n", ret);
goto err_disable;
}
/* Reset fault tracking */
memset(tas->last_status, 0, sizeof(tas->last_status));
return 0;
err_disable:
tas675x_power_off(tas);
return ret;
}
static int tas675x_runtime_suspend(struct device *dev)
{
struct tas675x_priv *tas = dev_get_drvdata(dev);
disable_delayed_work_sync(&tas->fault_check_work);
tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
return 0;
}
static int tas675x_runtime_resume(struct device *dev)
{
struct tas675x_priv *tas = dev_get_drvdata(dev);
tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
if (!to_i2c_client(dev)->irq) {
enable_delayed_work(&tas->fault_check_work);
schedule_delayed_work(&tas->fault_check_work,
msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
}
return 0;
}
static int tas675x_system_suspend(struct device *dev)
{
struct tas675x_priv *tas = dev_get_drvdata(dev);
int ret;
ret = tas675x_runtime_suspend(dev);
if (ret)
return ret;
if (to_i2c_client(dev)->irq)
disable_irq(to_i2c_client(dev)->irq);
tas675x_power_off(tas);
return 0;
}
static int tas675x_system_resume(struct device *dev)
{
struct tas675x_priv *tas = dev_get_drvdata(dev);
int ret;
ret = tas675x_power_on(tas);
if (ret)
return ret;
if (to_i2c_client(dev)->irq)
enable_irq(to_i2c_client(dev)->irq);
return tas675x_runtime_resume(dev);
}
static const struct snd_soc_component_driver soc_codec_dev_tas675x = {
.controls = tas675x_snd_controls,
.num_controls = ARRAY_SIZE(tas675x_snd_controls),
.dapm_widgets = tas675x_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tas675x_dapm_widgets),
.dapm_routes = tas675x_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(tas675x_dapm_routes),
.endianness = 1,
};
/* Fault register flags */
#define TAS675X_FAULT_CRITICAL BIT(0) /* causes FAULT state, FAULT_CLEAR required */
#define TAS675X_FAULT_TRACK BIT(1) /* track last value, only log on change */
#define TAS675X_FAULT_ACTIVE BIT(2) /* skip when no stream is active */
struct tas675x_fault_reg {
unsigned int reg;
unsigned int flags;
const char *name;
};
static const struct tas675x_fault_reg tas675x_fault_table[] = {
/* Critical */
{ TAS675X_OTSD_LATCHED_REG, TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
"Overtemperature Shutdown" },
{ TAS675X_OC_DC_FAULT_LATCHED_REG, TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
"Overcurrent / DC Fault" },
{ TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG, TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
"Real-Time Load Diagnostic Fault" },
{ TAS675X_CBC_FAULT_WARN_LATCHED_REG, TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
"CBC Fault/Warning" },
/* Warning */
{ TAS675X_POWER_FAULT_STATUS_1_REG, TAS675X_FAULT_TRACK,
"CP / OUTM Fault" },
{ TAS675X_POWER_FAULT_LATCHED_REG, TAS675X_FAULT_TRACK,
"Power Fault" },
{ TAS675X_CLK_FAULT_LATCHED_REG, TAS675X_FAULT_TRACK | TAS675X_FAULT_ACTIVE,
"Clock Fault" },
{ TAS675X_OTW_LATCHED_REG, TAS675X_FAULT_TRACK,
"Overtemperature Warning" },
{ TAS675X_CLIP_WARN_LATCHED_REG, TAS675X_FAULT_ACTIVE,
"Clip Warning" },
};
static_assert(ARRAY_SIZE(tas675x_fault_table) == TAS675X_FAULT_REGS_NUM);
/*
* Read and log all latched fault registers.
* Shared by both the polled fault_check_work and IRQ handler paths
* (which are mutually exclusive, only one is active per device).
* Returns true if any fault register needs to be cleared.
*
* For deciphering fault messages, see "Fault Monitoring" in
* Documentation/sound/codecs/tas675x.rst
*/
static bool tas675x_check_faults(struct tas675x_priv *tas)
{
struct device *dev = tas->dev;
bool needs_clear = false;
unsigned int reg;
int i, ret;
for (i = 0; i < ARRAY_SIZE(tas675x_fault_table); i++) {
const struct tas675x_fault_reg *f = &tas675x_fault_table[i];
ret = regmap_read(tas->regmap, f->reg, &reg);
if (ret) {
if (f->flags & TAS675X_FAULT_CRITICAL) {
dev_err(dev, "failed to read %s: %d\n", f->name, ret);
return needs_clear;
}
continue;
}
if (reg)
needs_clear = true;
/* Skip logging stream-dependent events when no stream is active */
if ((f->flags & TAS675X_FAULT_ACTIVE) &&
!READ_ONCE(tas->active_playback_dais) &&
!READ_ONCE(tas->active_capture_dais))
continue;
/* Log on change or on every non-zero read */
if (reg && (!(f->flags & TAS675X_FAULT_TRACK) ||
reg != tas->last_status[i])) {
if (f->flags & TAS675X_FAULT_CRITICAL)
dev_crit(dev, "%s Latched: 0x%02x\n", f->name, reg);
else
dev_warn(dev, "%s Latched: 0x%02x\n", f->name, reg);
}
if (f->flags & TAS675X_FAULT_TRACK)
tas->last_status[i] = reg;
}
return needs_clear;
}
static void tas675x_fault_check_work(struct work_struct *work)
{
struct tas675x_priv *tas = container_of(work, struct tas675x_priv,
fault_check_work.work);
if (tas675x_check_faults(tas))
regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
schedule_delayed_work(&tas->fault_check_work,
msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
}
static irqreturn_t tas675x_irq_handler(int irq, void *data)
{
struct tas675x_priv *tas = data;
irqreturn_t ret = IRQ_NONE;
if (pm_runtime_resume_and_get(tas->dev) < 0)
return IRQ_NONE;
if (tas675x_check_faults(tas)) {
regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
ret = IRQ_HANDLED;
}
pm_runtime_mark_last_busy(tas->dev);
pm_runtime_put_autosuspend(tas->dev);
return ret;
}
static const struct reg_default tas675x_reg_defaults[] = {
{ TAS675X_PAGE_CTRL_REG, 0x00 },
{ TAS675X_OUTPUT_CTRL_REG, 0x00 },
{ TAS675X_STATE_CTRL_CH1_CH2_REG, TAS675X_STATE_SLEEP_BOTH },
{ TAS675X_STATE_CTRL_CH3_CH4_REG, TAS675X_STATE_SLEEP_BOTH },
{ TAS675X_ISENSE_CTRL_REG, 0x0F },
{ TAS675X_DC_DETECT_CTRL_REG, 0x00 },
{ TAS675X_SCLK_INV_CTRL_REG, 0x00 },
{ TAS675X_AUDIO_IF_CTRL_REG, 0x00 },
{ TAS675X_SDIN_CTRL_REG, 0x0A },
{ TAS675X_SDOUT_CTRL_REG, 0x1A },
{ TAS675X_SDIN_OFFSET_MSB_REG, 0x00 },
{ TAS675X_SDIN_AUDIO_OFFSET_REG, 0x00 },
{ TAS675X_SDIN_LL_OFFSET_REG, 0x60 },
{ TAS675X_SDIN_CH_SWAP_REG, 0x00 },
{ TAS675X_SDOUT_OFFSET_MSB_REG, 0xCF },
{ TAS675X_VPREDICT_OFFSET_REG, 0xFF },
{ TAS675X_ISENSE_OFFSET_REG, 0x00 },
{ TAS675X_SDOUT_EN_REG, 0x00 },
{ TAS675X_LL_EN_REG, 0x00 },
{ TAS675X_RTLDG_EN_REG, 0x10 },
{ TAS675X_DC_BLOCK_BYP_REG, 0x00 },
{ TAS675X_DSP_CTRL_REG, 0x00 },
{ TAS675X_PAGE_AUTO_INC_REG, 0x00 },
{ TAS675X_DIG_VOL_CH1_REG, 0x30 },
{ TAS675X_DIG_VOL_CH2_REG, 0x30 },
{ TAS675X_DIG_VOL_CH3_REG, 0x30 },
{ TAS675X_DIG_VOL_CH4_REG, 0x30 },
{ TAS675X_DIG_VOL_RAMP_CTRL_REG, 0x77 },
{ TAS675X_DIG_VOL_COMBINE_CTRL_REG, 0x00 },
{ TAS675X_AUTO_MUTE_EN_REG, 0x00 },
{ TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 0x00 },
{ TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 0x00 },
{ TAS675X_ANALOG_GAIN_CH1_CH2_REG, 0x00 },
{ TAS675X_ANALOG_GAIN_CH3_CH4_REG, 0x00 },
{ TAS675X_ANALOG_GAIN_RAMP_CTRL_REG, 0x00 },
{ TAS675X_PULSE_INJECTION_EN_REG, 0x03 },
{ TAS675X_CBC_CTRL_REG, 0x07 },
{ TAS675X_CURRENT_LIMIT_CTRL_REG, 0x00 },
{ TAS675X_ISENSE_CAL_REG, 0x00 },
{ TAS675X_PWM_PHASE_CTRL_REG, 0x00 },
{ TAS675X_SS_CTRL_REG, 0x00 },
{ TAS675X_SS_RANGE_CTRL_REG, 0x00 },
{ TAS675X_SS_DWELL_CTRL_REG, 0x00 },
{ TAS675X_RAMP_PHASE_CTRL_GPO_REG, 0x00 },
{ TAS675X_PWM_PHASE_M_CTRL_CH1_REG, 0x00 },
{ TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 },
{ TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 },
{ TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 },
{ TAS675X_DC_LDG_CTRL_REG, 0x00 },
{ TAS675X_DC_LDG_LO_CTRL_REG, 0x00 },
{ TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 },
{ TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 },
{ TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 },
{ TAS675X_AC_LDG_CTRL_REG, 0x10 },
{ TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 },
{ TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 },
{ TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 },
{ TAS675X_REPORT_ROUTING_1_REG, 0x00 },
{ TAS675X_OTSD_RECOVERY_EN_REG, 0x00 },
{ TAS675X_REPORT_ROUTING_2_REG, 0xA2 },
{ TAS675X_REPORT_ROUTING_3_REG, 0x00 },
{ TAS675X_REPORT_ROUTING_4_REG, 0x06 },
{ TAS675X_CLIP_DETECT_CTRL_REG, 0x00 },
{ TAS675X_REPORT_ROUTING_5_REG, 0x00 },
{ TAS675X_GPIO1_OUTPUT_SEL_REG, 0x00 },
{ TAS675X_GPIO2_OUTPUT_SEL_REG, 0x00 },
{ TAS675X_GPIO_CTRL_REG, TAS675X_GPIO_CTRL_RSTVAL },
{ TAS675X_OTW_CTRL_CH1_CH2_REG, 0x11 },
{ TAS675X_OTW_CTRL_CH3_CH4_REG, 0x11 },
};
static bool tas675x_is_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case TAS675X_RESET_REG:
return false;
default:
return true;
}
}
static bool tas675x_is_volatile_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case TAS675X_RESET_REG:
case TAS675X_BOOK_CTRL_REG:
case TAS675X_AUTO_MUTE_STATUS_REG:
case TAS675X_STATE_REPORT_CH1_CH2_REG:
case TAS675X_STATE_REPORT_CH3_CH4_REG:
case TAS675X_PVDD_SENSE_REG:
case TAS675X_TEMP_GLOBAL_REG:
case TAS675X_TEMP_CH1_CH2_REG:
case TAS675X_TEMP_CH3_CH4_REG:
case TAS675X_FS_MON_REG:
case TAS675X_SCLK_MON_REG:
case TAS675X_POWER_FAULT_STATUS_1_REG:
case TAS675X_POWER_FAULT_STATUS_2_REG:
case TAS675X_OT_FAULT_REG:
case TAS675X_OTW_STATUS_REG:
case TAS675X_CLIP_WARN_STATUS_REG:
case TAS675X_CBC_WARNING_STATUS_REG:
case TAS675X_POWER_FAULT_LATCHED_REG:
case TAS675X_OTSD_LATCHED_REG:
case TAS675X_OTW_LATCHED_REG:
case TAS675X_CLIP_WARN_LATCHED_REG:
case TAS675X_CLK_FAULT_LATCHED_REG:
case TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG:
case TAS675X_CBC_FAULT_WARN_LATCHED_REG:
case TAS675X_OC_DC_FAULT_LATCHED_REG:
case TAS675X_WARN_OT_MAX_FLAG_REG:
case TAS675X_DC_LDG_REPORT_CH1_CH2_REG ... TAS675X_TWEETER_REPORT_REG:
case TAS675X_CH1_RTLDG_IMP_MSB_REG ... TAS675X_CH4_DC_LDG_DCR_LSB_REG:
return true;
default:
return false;
}
}
static const struct regmap_range_cfg tas675x_ranges[] = {
{
.name = "Pages",
.range_min = 0,
.range_max = TAS675X_PAGE_SIZE * TAS675X_PAGE_SIZE - 1,
.selector_reg = TAS675X_PAGE_CTRL_REG,
.selector_mask = 0xff,
.selector_shift = 0,
.window_start = 0,
.window_len = TAS675X_PAGE_SIZE,
},
};
static void tas675x_regmap_lock(void *lock_arg)
{
struct tas675x_priv *tas = lock_arg;
mutex_lock(&tas->io_lock);
}
static void tas675x_regmap_unlock(void *lock_arg)
{
struct tas675x_priv *tas = lock_arg;
mutex_unlock(&tas->io_lock);
}
static const struct regmap_config tas675x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = TAS675X_PAGE_SIZE * TAS675X_PAGE_SIZE - 1,
.ranges = tas675x_ranges,
.num_ranges = ARRAY_SIZE(tas675x_ranges),
.cache_type = REGCACHE_MAPLE,
.reg_defaults = tas675x_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(tas675x_reg_defaults),
.readable_reg = tas675x_is_readable_register,
.volatile_reg = tas675x_is_volatile_register,
};
static int tas675x_i2c_probe(struct i2c_client *client)
{
struct regmap_config cfg = tas675x_regmap_config;
struct tas675x_priv *tas;
u32 val;
int i, ret;
tas = devm_kzalloc(&client->dev, sizeof(*tas), GFP_KERNEL);
if (!tas)
return -ENOMEM;
tas->dev = &client->dev;
i2c_set_clientdata(client, tas);
mutex_init(&tas->io_lock);
cfg.lock = tas675x_regmap_lock;
cfg.unlock = tas675x_regmap_unlock;
cfg.lock_arg = tas;
memcpy(tas->dsp_params, tas675x_dsp_defaults, sizeof(tas->dsp_params));
INIT_DELAYED_WORK(&tas->fault_check_work, tas675x_fault_check_work);
tas->regmap = devm_regmap_init_i2c(client, &cfg);
if (IS_ERR(tas->regmap))
return PTR_ERR(tas->regmap);
/* Keep regmap cache-only until hardware is powered on */
regcache_cache_only(tas->regmap, true);
tas->dev_type = (enum tas675x_type)(unsigned long)device_get_match_data(tas->dev);
tas->fast_boot = device_property_read_bool(tas->dev, "ti,fast-boot");
tas->audio_slot = -1;
tas->llp_slot = -1;
tas->vpredict_slot = -1;
tas->isense_slot = -1;
if (!device_property_read_u32(tas->dev, "ti,audio-slot-no", &val))
tas->audio_slot = val;
if (!device_property_read_u32(tas->dev, "ti,llp-slot-no", &val))
tas->llp_slot = val;
if (!device_property_read_u32(tas->dev, "ti,vpredict-slot-no", &val))
tas->vpredict_slot = val;
if (!device_property_read_u32(tas->dev, "ti,isense-slot-no", &val))
tas->isense_slot = val;
tas->gpio1_func = tas675x_gpio_func_parse(tas->dev, "ti,gpio1-function");
tas->gpio2_func = tas675x_gpio_func_parse(tas->dev, "ti,gpio2-function");
for (i = 0; i < ARRAY_SIZE(tas675x_supply_names); i++)
tas->supplies[i].supply = tas675x_supply_names[i];
ret = devm_regulator_bulk_get(tas->dev, ARRAY_SIZE(tas->supplies), tas->supplies);
if (ret)
return dev_err_probe(tas->dev, ret, "Failed to request supplies\n");
tas->vbat = devm_regulator_get_optional(tas->dev, "vbat");
if (IS_ERR(tas->vbat) && PTR_ERR(tas->vbat) != -ENODEV)
return dev_err_probe(tas->dev, PTR_ERR(tas->vbat),
"Failed to get vbat supply\n");
tas->pd_gpio = devm_gpiod_get_optional(tas->dev, "powerdown", GPIOD_OUT_HIGH);
if (IS_ERR(tas->pd_gpio))
return dev_err_probe(tas->dev, PTR_ERR(tas->pd_gpio), "Failed powerdown-gpios\n");
tas->stby_gpio = devm_gpiod_get_optional(tas->dev, "standby", GPIOD_OUT_HIGH);
if (IS_ERR(tas->stby_gpio))
return dev_err_probe(tas->dev, PTR_ERR(tas->stby_gpio), "Failed standby-gpios\n");
if (!tas->pd_gpio && !tas->stby_gpio)
return dev_err_probe(tas->dev, -EINVAL,
"At least one of powerdown-gpios or standby-gpios is required\n");
ret = tas675x_power_on(tas);
if (ret)
return ret;
if (client->irq) {
ret = devm_request_threaded_irq(tas->dev, client->irq, NULL,
tas675x_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"tas675x-fault", tas);
if (ret) {
tas675x_power_off(tas);
return dev_err_probe(tas->dev, ret, "Failed to request IRQ\n");
}
} else {
/* Schedule delayed work for fault checking at probe and runtime resume */
schedule_delayed_work(&tas->fault_check_work,
msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
}
/* Enable runtime PM with 2s autosuspend */
pm_runtime_set_autosuspend_delay(tas->dev, 2000);
pm_runtime_use_autosuspend(tas->dev);
pm_runtime_set_active(tas->dev);
pm_runtime_mark_last_busy(tas->dev);
pm_runtime_enable(tas->dev);
ret = devm_snd_soc_register_component(tas->dev, &soc_codec_dev_tas675x,
tas675x_dais, ARRAY_SIZE(tas675x_dais));
if (ret)
goto err_pm_disable;
return 0;
err_pm_disable:
pm_runtime_force_suspend(tas->dev);
pm_runtime_disable(tas->dev);
tas675x_power_off(tas);
return ret;
}
static void tas675x_i2c_remove(struct i2c_client *client)
{
struct tas675x_priv *tas = dev_get_drvdata(&client->dev);
disable_delayed_work_sync(&tas->fault_check_work);
if (client->irq)
disable_irq(client->irq);
pm_runtime_force_suspend(&client->dev);
pm_runtime_disable(&client->dev);
tas675x_power_off(tas);
}
static const struct dev_pm_ops tas675x_pm_ops = {
SYSTEM_SLEEP_PM_OPS(tas675x_system_suspend, tas675x_system_resume)
RUNTIME_PM_OPS(tas675x_runtime_suspend, tas675x_runtime_resume, NULL)
};
static const struct of_device_id tas675x_of_match[] = {
{ .compatible = "ti,tas67524", .data = (void *)TAS67524 },
{ }
};
MODULE_DEVICE_TABLE(of, tas675x_of_match);
static const struct i2c_device_id tas675x_i2c_id[] = {
{ "tas67524", TAS67524 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tas675x_i2c_id);
static struct i2c_driver tas675x_i2c_driver = {
.driver = {
.name = "tas675x",
.of_match_table = tas675x_of_match,
.pm = pm_ptr(&tas675x_pm_ops),
},
.probe = tas675x_i2c_probe,
.remove = tas675x_i2c_remove,
.id_table = tas675x_i2c_id,
};
module_i2c_driver(tas675x_i2c_driver);
MODULE_AUTHOR("Sen Wang <sen@ti.com>");
MODULE_DESCRIPTION("ASoC TAS675x Audio Amplifier Driver");
MODULE_LICENSE("GPL");