2017-12-05 18:54:12 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* ALSA SoC Texas Instruments TAS6424 Quad - Channel Audio Amplifier
*
2020-07-19 18:38:22 +03:00
* Copyright ( C ) 2016 - 2017 Texas Instruments Incorporated - https : //www.ti.com/
2017-12-05 18:54:12 +03:00
* Author : Andreas Dannenberg < dannenberg @ ti . com >
* Andrew F . Davis < afd @ ti . com >
*/
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/device.h>
# include <linux/i2c.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <linux/regulator/consumer.h>
# include <linux/delay.h>
2018-04-27 16:55:47 +03:00
# include <linux/gpio/consumer.h>
2017-12-05 18:54:12 +03:00
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/soc-dapm.h>
# include <sound/tlv.h>
# include "tas6424.h"
/* Define how often to check (and clear) the fault status register (in ms) */
# define TAS6424_FAULT_CHECK_INTERVAL 200
static const char * const tas6424_supply_names [ ] = {
" dvdd " , /* Digital power supply. Connect to 3.3-V supply. */
" vbat " , /* Supply used for higher voltage analog circuits. */
" pvdd " , /* Class-D amp output FETs supply. */
} ;
# define TAS6424_NUM_SUPPLIES ARRAY_SIZE(tas6424_supply_names)
struct tas6424_data {
struct device * dev ;
struct regmap * regmap ;
struct regulator_bulk_data supplies [ TAS6424_NUM_SUPPLIES ] ;
struct delayed_work fault_check_work ;
2018-08-31 18:14:07 +03:00
unsigned int last_cfault ;
2017-12-05 18:54:12 +03:00
unsigned int last_fault1 ;
unsigned int last_fault2 ;
unsigned int last_warn ;
2018-04-27 16:55:47 +03:00
struct gpio_desc * standby_gpio ;
2018-04-27 16:55:48 +03:00
struct gpio_desc * mute_gpio ;
2017-12-05 18:54:12 +03:00
} ;
/*
* DAC digital volumes . From - 103.5 to 24 dB in 0.5 dB steps . Note that
* setting the gain below - 100 dB ( register value < 0x7 ) is effectively a MUTE
* as per device datasheet .
*/
static DECLARE_TLV_DB_SCALE ( dac_tlv , - 10350 , 50 , 0 ) ;
static const struct snd_kcontrol_new tas6424_snd_controls [ ] = {
SOC_SINGLE_TLV ( " Speaker Driver CH1 Playback Volume " ,
TAS6424_CH1_VOL_CTRL , 0 , 0xff , 0 , dac_tlv ) ,
SOC_SINGLE_TLV ( " Speaker Driver CH2 Playback Volume " ,
TAS6424_CH2_VOL_CTRL , 0 , 0xff , 0 , dac_tlv ) ,
SOC_SINGLE_TLV ( " Speaker Driver CH3 Playback Volume " ,
TAS6424_CH3_VOL_CTRL , 0 , 0xff , 0 , dac_tlv ) ,
SOC_SINGLE_TLV ( " Speaker Driver CH4 Playback Volume " ,
TAS6424_CH4_VOL_CTRL , 0 , 0xff , 0 , dac_tlv ) ,
2018-05-03 10:36:27 +03:00
SOC_SINGLE_STROBE ( " Auto Diagnostics Switch " , TAS6424_DC_DIAG_CTRL1 ,
TAS6424_LDGBYPASS_SHIFT , 1 ) ,
2017-12-05 18:54:12 +03:00
} ;
static int tas6424_dac_event ( struct snd_soc_dapm_widget * w ,
struct snd_kcontrol * kcontrol , int event )
{
2018-01-29 07:48:04 +03:00
struct snd_soc_component * component = snd_soc_dapm_to_component ( w - > dapm ) ;
struct tas6424_data * tas6424 = snd_soc_component_get_drvdata ( component ) ;
2017-12-05 18:54:12 +03:00
2018-01-29 07:48:04 +03:00
dev_dbg ( component - > dev , " %s() event=0x%0x \n " , __func__ , event ) ;
2017-12-05 18:54:12 +03:00
if ( event & SND_SOC_DAPM_POST_PMU ) {
/* Observe codec shutdown-to-active time */
msleep ( 12 ) ;
/* Turn on TAS6424 periodic fault checking/handling */
tas6424 - > last_fault1 = 0 ;
tas6424 - > last_fault2 = 0 ;
tas6424 - > last_warn = 0 ;
schedule_delayed_work ( & tas6424 - > fault_check_work ,
msecs_to_jiffies ( TAS6424_FAULT_CHECK_INTERVAL ) ) ;
} else if ( event & SND_SOC_DAPM_PRE_PMD ) {
/* Disable TAS6424 periodic fault checking/handling */
cancel_delayed_work_sync ( & tas6424 - > fault_check_work ) ;
}
return 0 ;
}
static const struct snd_soc_dapm_widget tas6424_dapm_widgets [ ] = {
SND_SOC_DAPM_AIF_IN ( " DAC IN " , " Playback " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
SND_SOC_DAPM_DAC_E ( " DAC " , NULL , SND_SOC_NOPM , 0 , 0 , tas6424_dac_event ,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD ) ,
SND_SOC_DAPM_OUTPUT ( " OUT " )
} ;
static const struct snd_soc_dapm_route tas6424_audio_map [ ] = {
{ " DAC " , NULL , " DAC IN " } ,
{ " OUT " , NULL , " DAC " } ,
} ;
static int tas6424_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
2018-01-29 07:48:04 +03:00
struct snd_soc_component * component = dai - > component ;
2017-12-05 18:54:12 +03:00
unsigned int rate = params_rate ( params ) ;
unsigned int width = params_width ( params ) ;
u8 sap_ctrl = 0 ;
2018-01-29 07:48:04 +03:00
dev_dbg ( component - > dev , " %s() rate=%u width=%u \n " , __func__ , rate , width ) ;
2017-12-05 18:54:12 +03:00
switch ( rate ) {
case 44100 :
sap_ctrl | = TAS6424_SAP_RATE_44100 ;
break ;
case 48000 :
sap_ctrl | = TAS6424_SAP_RATE_48000 ;
break ;
case 96000 :
sap_ctrl | = TAS6424_SAP_RATE_96000 ;
break ;
default :
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " unsupported sample rate: %u \n " , rate ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
switch ( width ) {
case 16 :
sap_ctrl | = TAS6424_SAP_TDM_SLOT_SZ_16 ;
break ;
case 24 :
break ;
default :
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " unsupported sample width: %u \n " , width ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
2018-01-29 07:48:04 +03:00
snd_soc_component_update_bits ( component , TAS6424_SAP_CTRL ,
2017-12-05 18:54:12 +03:00
TAS6424_SAP_RATE_MASK |
TAS6424_SAP_TDM_SLOT_SZ_16 ,
sap_ctrl ) ;
return 0 ;
}
static int tas6424_set_dai_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
{
2018-01-29 07:48:04 +03:00
struct snd_soc_component * component = dai - > component ;
2017-12-05 18:54:12 +03:00
u8 serial_format = 0 ;
2018-01-29 07:48:04 +03:00
dev_dbg ( component - > dev , " %s() fmt=0x%0x \n " , __func__ , fmt ) ;
2017-12-05 18:54:12 +03:00
/* clock masters */
2022-06-02 16:53:07 +03:00
switch ( fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK ) {
case SND_SOC_DAIFMT_CBC_CFC :
2017-12-05 18:54:12 +03:00
break ;
default :
2022-06-02 16:53:07 +03:00
dev_err ( component - > dev , " Invalid DAI clocking \n " ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
/* signal polarity */
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
break ;
default :
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " Invalid DAI clock signal polarity \n " ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
/* interface format */
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
serial_format | = TAS6424_SAP_I2S ;
break ;
case SND_SOC_DAIFMT_DSP_A :
serial_format | = TAS6424_SAP_DSP ;
break ;
case SND_SOC_DAIFMT_DSP_B :
/*
* We can use the fact that the TAS6424 does not care about the
* LRCLK duty cycle during TDM to receive DSP_B formatted data
* in LEFTJ mode ( no delaying of the 1 st data bit ) .
*/
serial_format | = TAS6424_SAP_LEFTJ ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
serial_format | = TAS6424_SAP_LEFTJ ;
break ;
default :
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " Invalid DAI interface format \n " ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
2018-01-29 07:48:04 +03:00
snd_soc_component_update_bits ( component , TAS6424_SAP_CTRL ,
2017-12-05 18:54:12 +03:00
TAS6424_SAP_FMT_MASK , serial_format ) ;
return 0 ;
}
static int tas6424_set_dai_tdm_slot ( struct snd_soc_dai * dai ,
unsigned int tx_mask , unsigned int rx_mask ,
int slots , int slot_width )
{
2018-01-29 07:48:04 +03:00
struct snd_soc_component * component = dai - > component ;
2017-12-05 18:54:12 +03:00
unsigned int first_slot , last_slot ;
bool sap_tdm_slot_last ;
2018-01-29 07:48:04 +03:00
dev_dbg ( component - > dev , " %s() tx_mask=%d rx_mask=%d \n " , __func__ ,
2017-12-05 18:54:12 +03:00
tx_mask , rx_mask ) ;
if ( ! tx_mask | | ! rx_mask )
return 0 ; /* nothing needed to disable TDM mode */
/*
* Determine the first slot and last slot that is being requested so
* we ' ll be able to more easily enforce certain constraints as the
* TAS6424 ' s TDM interface is not fully configurable .
*/
first_slot = __ffs ( tx_mask ) ;
last_slot = __fls ( rx_mask ) ;
if ( last_slot - first_slot ! = 4 ) {
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " tdm mask must cover 4 contiguous slots \n " ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
switch ( first_slot ) {
case 0 :
sap_tdm_slot_last = false ;
break ;
case 4 :
sap_tdm_slot_last = true ;
break ;
default :
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " tdm mask must start at slot 0 or 4 \n " ) ;
2017-12-05 18:54:12 +03:00
return - EINVAL ;
}
2018-01-29 07:48:04 +03:00
snd_soc_component_update_bits ( component , TAS6424_SAP_CTRL , TAS6424_SAP_TDM_SLOT_LAST ,
2017-12-05 18:54:12 +03:00
sap_tdm_slot_last ? TAS6424_SAP_TDM_SLOT_LAST : 0 ) ;
return 0 ;
}
2020-07-09 04:56:30 +03:00
static int tas6424_mute ( struct snd_soc_dai * dai , int mute , int direction )
2017-12-05 18:54:12 +03:00
{
2018-01-29 07:48:04 +03:00
struct snd_soc_component * component = dai - > component ;
2018-04-27 16:55:48 +03:00
struct tas6424_data * tas6424 = snd_soc_component_get_drvdata ( component ) ;
2017-12-05 18:54:12 +03:00
unsigned int val ;
2018-01-29 07:48:04 +03:00
dev_dbg ( component - > dev , " %s() mute=%d \n " , __func__ , mute ) ;
2017-12-05 18:54:12 +03:00
2018-04-27 16:55:48 +03:00
if ( tas6424 - > mute_gpio ) {
gpiod_set_value_cansleep ( tas6424 - > mute_gpio , mute ) ;
return 0 ;
}
2017-12-05 18:54:12 +03:00
if ( mute )
val = TAS6424_ALL_STATE_MUTE ;
else
val = TAS6424_ALL_STATE_PLAY ;
2018-01-29 07:48:04 +03:00
snd_soc_component_write ( component , TAS6424_CH_STATE_CTRL , val ) ;
2017-12-05 18:54:12 +03:00
return 0 ;
}
2018-01-29 07:48:04 +03:00
static int tas6424_power_off ( struct snd_soc_component * component )
2017-12-05 18:54:12 +03:00
{
2018-01-29 07:48:04 +03:00
struct tas6424_data * tas6424 = snd_soc_component_get_drvdata ( component ) ;
2017-12-05 18:54:12 +03:00
int ret ;
2018-01-29 07:48:04 +03:00
snd_soc_component_write ( component , TAS6424_CH_STATE_CTRL , TAS6424_ALL_STATE_HIZ ) ;
2017-12-05 18:54:12 +03:00
regcache_cache_only ( tas6424 - > regmap , true ) ;
regcache_mark_dirty ( tas6424 - > regmap ) ;
ret = regulator_bulk_disable ( ARRAY_SIZE ( tas6424 - > supplies ) ,
tas6424 - > supplies ) ;
if ( ret < 0 ) {
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " failed to disable supplies: %d \n " , ret ) ;
2017-12-05 18:54:12 +03:00
return ret ;
}
return 0 ;
}
2018-01-29 07:48:04 +03:00
static int tas6424_power_on ( struct snd_soc_component * component )
2017-12-05 18:54:12 +03:00
{
2018-01-29 07:48:04 +03:00
struct tas6424_data * tas6424 = snd_soc_component_get_drvdata ( component ) ;
2017-12-05 18:54:12 +03:00
int ret ;
2018-04-27 16:55:48 +03:00
u8 chan_states ;
2018-05-03 10:36:27 +03:00
int no_auto_diags = 0 ;
unsigned int reg_val ;
if ( ! regmap_read ( tas6424 - > regmap , TAS6424_DC_DIAG_CTRL1 , & reg_val ) )
no_auto_diags = reg_val & TAS6424_LDGBYPASS_MASK ;
2017-12-05 18:54:12 +03:00
ret = regulator_bulk_enable ( ARRAY_SIZE ( tas6424 - > supplies ) ,
tas6424 - > supplies ) ;
if ( ret < 0 ) {
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " failed to enable supplies: %d \n " , ret ) ;
2017-12-05 18:54:12 +03:00
return ret ;
}
regcache_cache_only ( tas6424 - > regmap , false ) ;
ret = regcache_sync ( tas6424 - > regmap ) ;
if ( ret < 0 ) {
2018-01-29 07:48:04 +03:00
dev_err ( component - > dev , " failed to sync regcache: %d \n " , ret ) ;
2017-12-05 18:54:12 +03:00
return ret ;
}
2018-04-27 16:55:48 +03:00
if ( tas6424 - > mute_gpio ) {
gpiod_set_value_cansleep ( tas6424 - > mute_gpio , 0 ) ;
/*
* channels are muted via the mute pin . Don ' t also mute
* them via the registers so that subsequent register
* access is not necessary to un - mute the channels
*/
chan_states = TAS6424_ALL_STATE_PLAY ;
} else {
chan_states = TAS6424_ALL_STATE_MUTE ;
}
snd_soc_component_write ( component , TAS6424_CH_STATE_CTRL , chan_states ) ;
2017-12-05 18:54:12 +03:00
/* any time we come out of HIZ, the output channels automatically run DC
2018-05-03 10:36:27 +03:00
* load diagnostics if autodiagnotics are enabled . wait here until this
* completes .
2017-12-05 18:54:12 +03:00
*/
2018-05-03 10:36:27 +03:00
if ( ! no_auto_diags )
msleep ( 230 ) ;
2017-12-05 18:54:12 +03:00
return 0 ;
}
2018-01-29 07:48:04 +03:00
static int tas6424_set_bias_level ( struct snd_soc_component * component ,
2017-12-05 18:54:12 +03:00
enum snd_soc_bias_level level )
{
2018-01-29 07:48:04 +03:00
dev_dbg ( component - > dev , " %s() level=%d \n " , __func__ , level ) ;
2017-12-05 18:54:12 +03:00
switch ( level ) {
case SND_SOC_BIAS_ON :
case SND_SOC_BIAS_PREPARE :
break ;
case SND_SOC_BIAS_STANDBY :
2018-01-29 07:48:04 +03:00
if ( snd_soc_component_get_bias_level ( component ) = = SND_SOC_BIAS_OFF )
tas6424_power_on ( component ) ;
2017-12-05 18:54:12 +03:00
break ;
case SND_SOC_BIAS_OFF :
2018-01-29 07:48:04 +03:00
tas6424_power_off ( component ) ;
2017-12-05 18:54:12 +03:00
break ;
}
return 0 ;
}
2018-01-29 07:48:04 +03:00
static struct snd_soc_component_driver soc_codec_dev_tas6424 = {
. set_bias_level = tas6424_set_bias_level ,
. controls = tas6424_snd_controls ,
. num_controls = ARRAY_SIZE ( tas6424_snd_controls ) ,
. dapm_widgets = tas6424_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( tas6424_dapm_widgets ) ,
. dapm_routes = tas6424_audio_map ,
. num_dapm_routes = ARRAY_SIZE ( tas6424_audio_map ) ,
. use_pmdown_time = 1 ,
. endianness = 1 ,
2017-12-05 18:54:12 +03:00
} ;
2018-10-27 16:34:44 +03:00
static const struct snd_soc_dai_ops tas6424_speaker_dai_ops = {
2017-12-05 18:54:12 +03:00
. hw_params = tas6424_hw_params ,
. set_fmt = tas6424_set_dai_fmt ,
. set_tdm_slot = tas6424_set_dai_tdm_slot ,
2020-07-09 04:56:30 +03:00
. mute_stream = tas6424_mute ,
. no_capture_mute = 1 ,
2017-12-05 18:54:12 +03:00
} ;
static struct snd_soc_dai_driver tas6424_dai [ ] = {
{
. name = " tas6424-amplifier " ,
. playback = {
. stream_name = " Playback " ,
. channels_min = 1 ,
. channels_max = 4 ,
. rates = TAS6424_RATES ,
. formats = TAS6424_FORMATS ,
} ,
. ops = & tas6424_speaker_dai_ops ,
} ,
} ;
static void tas6424_fault_check_work ( struct work_struct * work )
{
struct tas6424_data * tas6424 = container_of ( work , struct tas6424_data ,
fault_check_work . work ) ;
struct device * dev = tas6424 - > dev ;
unsigned int reg ;
int ret ;
2018-08-31 18:14:07 +03:00
ret = regmap_read ( tas6424 - > regmap , TAS6424_CHANNEL_FAULT , & reg ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to read CHANNEL_FAULT register: %d \n " , ret ) ;
goto out ;
}
if ( ! reg ) {
tas6424 - > last_cfault = reg ;
goto check_global_fault1_reg ;
}
/*
* Only flag errors once for a given occurrence . This is needed as
* the TAS6424 will take time clearing the fault condition internally
* during which we don ' t want to bombard the system with the same
* error message over and over .
*/
if ( ( reg & TAS6424_FAULT_OC_CH1 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_OC_CH1 ) )
dev_crit ( dev , " experienced a channel 1 overcurrent fault \n " ) ;
if ( ( reg & TAS6424_FAULT_OC_CH2 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_OC_CH2 ) )
dev_crit ( dev , " experienced a channel 2 overcurrent fault \n " ) ;
if ( ( reg & TAS6424_FAULT_OC_CH3 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_OC_CH3 ) )
dev_crit ( dev , " experienced a channel 3 overcurrent fault \n " ) ;
if ( ( reg & TAS6424_FAULT_OC_CH4 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_OC_CH4 ) )
dev_crit ( dev , " experienced a channel 4 overcurrent fault \n " ) ;
if ( ( reg & TAS6424_FAULT_DC_CH1 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_DC_CH1 ) )
dev_crit ( dev , " experienced a channel 1 DC fault \n " ) ;
if ( ( reg & TAS6424_FAULT_DC_CH2 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_DC_CH2 ) )
dev_crit ( dev , " experienced a channel 2 DC fault \n " ) ;
if ( ( reg & TAS6424_FAULT_DC_CH3 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_DC_CH3 ) )
dev_crit ( dev , " experienced a channel 3 DC fault \n " ) ;
if ( ( reg & TAS6424_FAULT_DC_CH4 ) & & ! ( tas6424 - > last_cfault & TAS6424_FAULT_DC_CH4 ) )
dev_crit ( dev , " experienced a channel 4 DC fault \n " ) ;
/* Store current fault1 value so we can detect any changes next time */
tas6424 - > last_cfault = reg ;
check_global_fault1_reg :
2017-12-05 18:54:12 +03:00
ret = regmap_read ( tas6424 - > regmap , TAS6424_GLOB_FAULT1 , & reg ) ;
if ( ret < 0 ) {
2018-08-31 18:14:06 +03:00
dev_err ( dev , " failed to read GLOB_FAULT1 register: %d \n " , ret ) ;
2017-12-05 18:54:12 +03:00
goto out ;
}
/*
* Ignore any clock faults as there is no clean way to check for them .
* We would need to start checking for those faults * after * the SAIF
* stream has been setup , and stop checking * before * the stream is
* stopped to avoid any false - positives . However there are no
* appropriate hooks to monitor these events .
*/
reg & = TAS6424_FAULT_PVDD_OV |
TAS6424_FAULT_VBAT_OV |
TAS6424_FAULT_PVDD_UV |
TAS6424_FAULT_VBAT_UV ;
2018-08-31 18:14:05 +03:00
if ( ! reg ) {
tas6424 - > last_fault1 = reg ;
2017-12-05 18:54:12 +03:00
goto check_global_fault2_reg ;
2018-08-31 18:14:05 +03:00
}
2017-12-05 18:54:12 +03:00
if ( ( reg & TAS6424_FAULT_PVDD_OV ) & & ! ( tas6424 - > last_fault1 & TAS6424_FAULT_PVDD_OV ) )
dev_crit ( dev , " experienced a PVDD overvoltage fault \n " ) ;
if ( ( reg & TAS6424_FAULT_VBAT_OV ) & & ! ( tas6424 - > last_fault1 & TAS6424_FAULT_VBAT_OV ) )
dev_crit ( dev , " experienced a VBAT overvoltage fault \n " ) ;
if ( ( reg & TAS6424_FAULT_PVDD_UV ) & & ! ( tas6424 - > last_fault1 & TAS6424_FAULT_PVDD_UV ) )
dev_crit ( dev , " experienced a PVDD undervoltage fault \n " ) ;
if ( ( reg & TAS6424_FAULT_VBAT_UV ) & & ! ( tas6424 - > last_fault1 & TAS6424_FAULT_VBAT_UV ) )
dev_crit ( dev , " experienced a VBAT undervoltage fault \n " ) ;
/* Store current fault1 value so we can detect any changes next time */
tas6424 - > last_fault1 = reg ;
check_global_fault2_reg :
ret = regmap_read ( tas6424 - > regmap , TAS6424_GLOB_FAULT2 , & reg ) ;
if ( ret < 0 ) {
2018-08-31 18:14:06 +03:00
dev_err ( dev , " failed to read GLOB_FAULT2 register: %d \n " , ret ) ;
2017-12-05 18:54:12 +03:00
goto out ;
}
reg & = TAS6424_FAULT_OTSD |
TAS6424_FAULT_OTSD_CH1 |
TAS6424_FAULT_OTSD_CH2 |
TAS6424_FAULT_OTSD_CH3 |
TAS6424_FAULT_OTSD_CH4 ;
2018-08-31 18:14:05 +03:00
if ( ! reg ) {
tas6424 - > last_fault2 = reg ;
2017-12-05 18:54:12 +03:00
goto check_warn_reg ;
2018-08-31 18:14:05 +03:00
}
2017-12-05 18:54:12 +03:00
if ( ( reg & TAS6424_FAULT_OTSD ) & & ! ( tas6424 - > last_fault2 & TAS6424_FAULT_OTSD ) )
dev_crit ( dev , " experienced a global overtemp shutdown \n " ) ;
if ( ( reg & TAS6424_FAULT_OTSD_CH1 ) & & ! ( tas6424 - > last_fault2 & TAS6424_FAULT_OTSD_CH1 ) )
dev_crit ( dev , " experienced an overtemp shutdown on CH1 \n " ) ;
if ( ( reg & TAS6424_FAULT_OTSD_CH2 ) & & ! ( tas6424 - > last_fault2 & TAS6424_FAULT_OTSD_CH2 ) )
dev_crit ( dev , " experienced an overtemp shutdown on CH2 \n " ) ;
if ( ( reg & TAS6424_FAULT_OTSD_CH3 ) & & ! ( tas6424 - > last_fault2 & TAS6424_FAULT_OTSD_CH3 ) )
dev_crit ( dev , " experienced an overtemp shutdown on CH3 \n " ) ;
if ( ( reg & TAS6424_FAULT_OTSD_CH4 ) & & ! ( tas6424 - > last_fault2 & TAS6424_FAULT_OTSD_CH4 ) )
dev_crit ( dev , " experienced an overtemp shutdown on CH4 \n " ) ;
/* Store current fault2 value so we can detect any changes next time */
tas6424 - > last_fault2 = reg ;
check_warn_reg :
ret = regmap_read ( tas6424 - > regmap , TAS6424_WARN , & reg ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to read WARN register: %d \n " , ret ) ;
goto out ;
}
reg & = TAS6424_WARN_VDD_UV |
TAS6424_WARN_VDD_POR |
TAS6424_WARN_VDD_OTW |
TAS6424_WARN_VDD_OTW_CH1 |
TAS6424_WARN_VDD_OTW_CH2 |
TAS6424_WARN_VDD_OTW_CH3 |
TAS6424_WARN_VDD_OTW_CH4 ;
2018-08-31 18:14:05 +03:00
if ( ! reg ) {
tas6424 - > last_warn = reg ;
2017-12-05 18:54:12 +03:00
goto out ;
2018-08-31 18:14:05 +03:00
}
2017-12-05 18:54:12 +03:00
if ( ( reg & TAS6424_WARN_VDD_UV ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_UV ) )
dev_warn ( dev , " experienced a VDD under voltage condition \n " ) ;
if ( ( reg & TAS6424_WARN_VDD_POR ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_POR ) )
dev_warn ( dev , " experienced a VDD POR condition \n " ) ;
if ( ( reg & TAS6424_WARN_VDD_OTW ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_OTW ) )
dev_warn ( dev , " experienced a global overtemp warning \n " ) ;
if ( ( reg & TAS6424_WARN_VDD_OTW_CH1 ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_OTW_CH1 ) )
dev_warn ( dev , " experienced an overtemp warning on CH1 \n " ) ;
if ( ( reg & TAS6424_WARN_VDD_OTW_CH2 ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_OTW_CH2 ) )
dev_warn ( dev , " experienced an overtemp warning on CH2 \n " ) ;
if ( ( reg & TAS6424_WARN_VDD_OTW_CH3 ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_OTW_CH3 ) )
dev_warn ( dev , " experienced an overtemp warning on CH3 \n " ) ;
if ( ( reg & TAS6424_WARN_VDD_OTW_CH4 ) & & ! ( tas6424 - > last_warn & TAS6424_WARN_VDD_OTW_CH4 ) )
dev_warn ( dev , " experienced an overtemp warning on CH4 \n " ) ;
/* Store current warn value so we can detect any changes next time */
tas6424 - > last_warn = reg ;
2018-08-31 18:14:06 +03:00
/* Clear any warnings by toggling the CLEAR_FAULT control bit */
2017-12-05 18:54:12 +03:00
ret = regmap_write_bits ( tas6424 - > regmap , TAS6424_MISC_CTRL3 ,
TAS6424_CLEAR_FAULT , TAS6424_CLEAR_FAULT ) ;
if ( ret < 0 )
dev_err ( dev , " failed to write MISC_CTRL3 register: %d \n " , ret ) ;
ret = regmap_write_bits ( tas6424 - > regmap , TAS6424_MISC_CTRL3 ,
TAS6424_CLEAR_FAULT , 0 ) ;
if ( ret < 0 )
dev_err ( dev , " failed to write MISC_CTRL3 register: %d \n " , ret ) ;
out :
/* Schedule the next fault check at the specified interval */
schedule_delayed_work ( & tas6424 - > fault_check_work ,
msecs_to_jiffies ( TAS6424_FAULT_CHECK_INTERVAL ) ) ;
}
static const struct reg_default tas6424_reg_defaults [ ] = {
{ TAS6424_MODE_CTRL , 0x00 } ,
{ TAS6424_MISC_CTRL1 , 0x32 } ,
{ TAS6424_MISC_CTRL2 , 0x62 } ,
{ TAS6424_SAP_CTRL , 0x04 } ,
{ TAS6424_CH_STATE_CTRL , 0x55 } ,
{ TAS6424_CH1_VOL_CTRL , 0xcf } ,
{ TAS6424_CH2_VOL_CTRL , 0xcf } ,
{ TAS6424_CH3_VOL_CTRL , 0xcf } ,
{ TAS6424_CH4_VOL_CTRL , 0xcf } ,
{ TAS6424_DC_DIAG_CTRL1 , 0x00 } ,
{ TAS6424_DC_DIAG_CTRL2 , 0x11 } ,
{ TAS6424_DC_DIAG_CTRL3 , 0x11 } ,
{ TAS6424_PIN_CTRL , 0xff } ,
{ TAS6424_AC_DIAG_CTRL1 , 0x00 } ,
{ TAS6424_MISC_CTRL3 , 0x00 } ,
{ TAS6424_CLIP_CTRL , 0x01 } ,
{ TAS6424_CLIP_WINDOW , 0x14 } ,
{ TAS6424_CLIP_WARN , 0x00 } ,
{ TAS6424_CBC_STAT , 0x00 } ,
{ TAS6424_MISC_CTRL4 , 0x40 } ,
} ;
static bool tas6424_is_writable_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case TAS6424_MODE_CTRL :
case TAS6424_MISC_CTRL1 :
case TAS6424_MISC_CTRL2 :
case TAS6424_SAP_CTRL :
case TAS6424_CH_STATE_CTRL :
case TAS6424_CH1_VOL_CTRL :
case TAS6424_CH2_VOL_CTRL :
case TAS6424_CH3_VOL_CTRL :
case TAS6424_CH4_VOL_CTRL :
case TAS6424_DC_DIAG_CTRL1 :
case TAS6424_DC_DIAG_CTRL2 :
case TAS6424_DC_DIAG_CTRL3 :
case TAS6424_PIN_CTRL :
case TAS6424_AC_DIAG_CTRL1 :
case TAS6424_MISC_CTRL3 :
case TAS6424_CLIP_CTRL :
case TAS6424_CLIP_WINDOW :
case TAS6424_CLIP_WARN :
case TAS6424_CBC_STAT :
case TAS6424_MISC_CTRL4 :
return true ;
default :
return false ;
}
}
static bool tas6424_is_volatile_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case TAS6424_DC_LOAD_DIAG_REP12 :
case TAS6424_DC_LOAD_DIAG_REP34 :
case TAS6424_DC_LOAD_DIAG_REPLO :
case TAS6424_CHANNEL_STATE :
case TAS6424_CHANNEL_FAULT :
case TAS6424_GLOB_FAULT1 :
case TAS6424_GLOB_FAULT2 :
case TAS6424_WARN :
case TAS6424_AC_LOAD_DIAG_REP1 :
case TAS6424_AC_LOAD_DIAG_REP2 :
case TAS6424_AC_LOAD_DIAG_REP3 :
case TAS6424_AC_LOAD_DIAG_REP4 :
return true ;
default :
return false ;
}
}
static const struct regmap_config tas6424_regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
. writeable_reg = tas6424_is_writable_reg ,
. volatile_reg = tas6424_is_volatile_reg ,
. max_register = TAS6424_MAX ,
. reg_defaults = tas6424_reg_defaults ,
. num_reg_defaults = ARRAY_SIZE ( tas6424_reg_defaults ) ,
. cache_type = REGCACHE_RBTREE ,
} ;
# if IS_ENABLED(CONFIG_OF)
static const struct of_device_id tas6424_of_ids [ ] = {
{ . compatible = " ti,tas6424 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , tas6424_of_ids ) ;
# endif
2022-04-05 19:58:32 +03:00
static int tas6424_i2c_probe ( struct i2c_client * client )
2017-12-05 18:54:12 +03:00
{
struct device * dev = & client - > dev ;
struct tas6424_data * tas6424 ;
int ret ;
int i ;
tas6424 = devm_kzalloc ( dev , sizeof ( * tas6424 ) , GFP_KERNEL ) ;
if ( ! tas6424 )
return - ENOMEM ;
dev_set_drvdata ( dev , tas6424 ) ;
tas6424 - > dev = dev ;
tas6424 - > regmap = devm_regmap_init_i2c ( client , & tas6424_regmap_config ) ;
if ( IS_ERR ( tas6424 - > regmap ) ) {
ret = PTR_ERR ( tas6424 - > regmap ) ;
dev_err ( dev , " unable to allocate register map: %d \n " , ret ) ;
return ret ;
}
2018-04-27 16:55:47 +03:00
/*
* Get control of the standby pin and set it LOW to take the codec
* out of the stand - by mode .
* Note : The actual pin polarity is taken care of in the GPIO lib
* according the polarity specified in the DTS .
*/
tas6424 - > standby_gpio = devm_gpiod_get_optional ( dev , " standby " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( tas6424 - > standby_gpio ) ) {
if ( PTR_ERR ( tas6424 - > standby_gpio ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
dev_info ( dev , " failed to get standby GPIO: %ld \n " ,
PTR_ERR ( tas6424 - > standby_gpio ) ) ;
tas6424 - > standby_gpio = NULL ;
}
2018-04-27 16:55:48 +03:00
/*
* Get control of the mute pin and set it HIGH in order to start with
* all the output muted .
* Note : The actual pin polarity is taken care of in the GPIO lib
* according the polarity specified in the DTS .
*/
tas6424 - > mute_gpio = devm_gpiod_get_optional ( dev , " mute " ,
GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( tas6424 - > mute_gpio ) ) {
if ( PTR_ERR ( tas6424 - > mute_gpio ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
dev_info ( dev , " failed to get nmute GPIO: %ld \n " ,
PTR_ERR ( tas6424 - > mute_gpio ) ) ;
tas6424 - > mute_gpio = NULL ;
}
2017-12-05 18:54:12 +03:00
for ( i = 0 ; i < ARRAY_SIZE ( tas6424 - > supplies ) ; i + + )
tas6424 - > supplies [ i ] . supply = tas6424_supply_names [ i ] ;
ret = devm_regulator_bulk_get ( dev , ARRAY_SIZE ( tas6424 - > supplies ) ,
tas6424 - > supplies ) ;
if ( ret ) {
dev_err ( dev , " unable to request supplies: %d \n " , ret ) ;
return ret ;
}
ret = regulator_bulk_enable ( ARRAY_SIZE ( tas6424 - > supplies ) ,
tas6424 - > supplies ) ;
if ( ret ) {
dev_err ( dev , " unable to enable supplies: %d \n " , ret ) ;
return ret ;
}
/* Reset device to establish well-defined startup state */
ret = regmap_update_bits ( tas6424 - > regmap , TAS6424_MODE_CTRL ,
TAS6424_RESET , TAS6424_RESET ) ;
if ( ret ) {
dev_err ( dev , " unable to reset device: %d \n " , ret ) ;
2022-05-10 18:32:50 +03:00
goto disable_regs ;
2017-12-05 18:54:12 +03:00
}
INIT_DELAYED_WORK ( & tas6424 - > fault_check_work , tas6424_fault_check_work ) ;
2018-01-29 07:48:04 +03:00
ret = devm_snd_soc_register_component ( dev , & soc_codec_dev_tas6424 ,
2017-12-05 18:54:12 +03:00
tas6424_dai , ARRAY_SIZE ( tas6424_dai ) ) ;
if ( ret < 0 ) {
dev_err ( dev , " unable to register codec: %d \n " , ret ) ;
2022-05-10 18:32:50 +03:00
goto disable_regs ;
2017-12-05 18:54:12 +03:00
}
return 0 ;
2022-05-10 18:32:50 +03:00
disable_regs :
regulator_bulk_disable ( ARRAY_SIZE ( tas6424 - > supplies ) , tas6424 - > supplies ) ;
return ret ;
2017-12-05 18:54:12 +03:00
}
2022-08-15 11:02:30 +03:00
static void tas6424_i2c_remove ( struct i2c_client * client )
2017-12-05 18:54:12 +03:00
{
struct device * dev = & client - > dev ;
struct tas6424_data * tas6424 = dev_get_drvdata ( dev ) ;
int ret ;
cancel_delayed_work_sync ( & tas6424 - > fault_check_work ) ;
2018-04-27 16:55:47 +03:00
/* put the codec in stand-by */
if ( tas6424 - > standby_gpio )
gpiod_set_value_cansleep ( tas6424 - > standby_gpio , 1 ) ;
2017-12-05 18:54:12 +03:00
ret = regulator_bulk_disable ( ARRAY_SIZE ( tas6424 - > supplies ) ,
tas6424 - > supplies ) ;
2022-04-25 22:32:06 +03:00
if ( ret < 0 )
2017-12-05 18:54:12 +03:00
dev_err ( dev , " unable to disable supplies: %d \n " , ret ) ;
}
static const struct i2c_device_id tas6424_i2c_ids [ ] = {
{ " tas6424 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , tas6424_i2c_ids ) ;
static struct i2c_driver tas6424_i2c_driver = {
. driver = {
. name = " tas6424 " ,
. of_match_table = of_match_ptr ( tas6424_of_ids ) ,
} ,
2023-04-25 12:57:16 +03:00
. probe = tas6424_i2c_probe ,
2017-12-05 18:54:12 +03:00
. remove = tas6424_i2c_remove ,
. id_table = tas6424_i2c_ids ,
} ;
module_i2c_driver ( tas6424_i2c_driver ) ;
MODULE_AUTHOR ( " Andreas Dannenberg <dannenberg@ti.com> " ) ;
MODULE_AUTHOR ( " Andrew F. Davis <afd@ti.com> " ) ;
MODULE_DESCRIPTION ( " TAS6424 Audio amplifier driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;