[alsa-devel] [PATCH] tas2770: add tas2770 smart PA dt bindings
From: Frank Shi shifu0704@thundersoft.com
add tas2770 smart PA dt bindings
Signed-off-by: Frank Shi shifu0704@thundersoft.com --- Documentation/devicetree/bindings/tas2770.txt | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/tas2770.txt
diff --git a/Documentation/devicetree/bindings/tas2770.txt b/Documentation/devicetree/bindings/tas2770.txt new file mode 100644 index 0000000..f70b310 --- /dev/null +++ b/Documentation/devicetree/bindings/tas2770.txt @@ -0,0 +1,38 @@ +Texas Instruments TAS2770 Smart PA + +The TAS2770 is a mono, digital input Class-D audio amplifier optimized for +efficiently driving high peak power into small loudspeakers. +Integrated speaker voltage and current sense provides for +real time monitoring of loudspeaker behavior. + +Required properties: + + - compatible: - Should contain "ti,tas2770". + - reg: - The i2c address. Should contain <0x4c>, <0x4d>,<0x4e>, or <0x4f>. + - #address-cells - Should be <1>. + - #size-cells - Should be <0>. + - ti,asi-format: - Sets TDM RX capture edge. 0->Rising; 1->Falling. + - ti,left-slot: - Sets TDM RX left time slots. + - ti,right-slot: - Sets TDM RX right time slots. + - ti,imon-slot-no:- TDM TX current sense time slot. + - ti,vmon-slot-no:- TDM TX voltage sense time slot. + +Optional properties: + + - reset-gpio: Reset GPIO number of left device. + - irq-gpio: IRQ GPIO number of left device. + +Examples: + + tas2770@4c { + compatible = "ti,tas2770"; + reg = <0x4c>; + reset-gpio = <&gpio15 1 GPIO_ACTIVE_LOW>; + irq-gpio = <&gpio16 1 GPIO_ACTIVE_LOW>; + ti,asi-format = <0>; + ti,left-slot = <0>; + ti,right-slot = <1>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + }; +
From: Frank Shi shifu0704@thundersoft.com
add tas2770 smart PA kernel driver
Signed-off-by: Frank Shi shifu0704@thundersoft.com --- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tas2770.c | 1103 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tas2770.h | 173 +++++++ 4 files changed, 1283 insertions(+) create mode 100644 sound/soc/codecs/tas2770.c create mode 100644 sound/soc/codecs/tas2770.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8f3e787..cc92da3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -111,6 +111,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_STI_SAS select SND_SOC_TAS2552 if I2C + select SND_SOC_TAS2770 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C select SND_SOC_TFA9879 if I2C @@ -652,6 +653,10 @@ config SND_SOC_TAS2552 tristate "Texas Instruments TAS2552 Mono Audio amplifier" depends on I2C
+config SND_SOC_TAS2770 + tristate "Texas Instruments TAS2770 speaker amplifier" + depends on I2C + config SND_SOC_TAS5086 tristate "Texas Instruments TAS5086 speaker amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5305cc6..1b58d08 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -116,6 +116,7 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o +snd-soc-tas2770-objs := tas2770.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -332,6 +333,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o +obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c new file mode 100644 index 0000000..9fc0c11 --- /dev/null +++ b/sound/soc/codecs/tas2770.c @@ -0,0 +1,1103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC Texas Instruments TAS2770 20-W Digital Input Mono Class-D + * Audio Amplifier with Speaker I/V Sense + * + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Tracy Yi tracy-yi@ti.com + * Shi Fu shifu0704@thundersoft.com + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/firmware.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "tas2770.h" + +#define TAS2770_MDELAY 0xFFFFFFFE +#define TAS2770_CHECK_PERIOD 5000 /* 5 second */ + +static void tas2770_hw_reset(struct tas2770_priv *p_tas2770) +{ + if (p_tas2770->mn_reset_gpio) { + gpiod_set_value_cansleep(p_tas2770->mn_reset_gpio, 0); + msleep(20); + gpiod_set_value_cansleep(p_tas2770->mn_reset_gpio, 1); + } + + p_tas2770->mn_current_book = -1; + p_tas2770->mn_current_page = -1; +} + +static void tas2770_enable_irq(struct tas2770_priv *p_tas2770, bool enable) +{ + if (enable) { + if (p_tas2770->mb_irq_enable) + return; + + if (gpio_is_valid(p_tas2770->mn_irq)) + enable_irq(p_tas2770->mn_irq); + p_tas2770->mb_irq_enable = true; + } else { + if (gpio_is_valid(p_tas2770->mn_irq)) + disable_irq_nosync(p_tas2770->mn_irq); + p_tas2770->mb_irq_enable = false; + } +} + +static int tas2770_runtime_suspend(struct tas2770_priv *p_tas2770) +{ + p_tas2770->mb_runtime_suspend = true; + + return 0; +} + +static int tas2770_runtime_resume(struct tas2770_priv *p_tas2770) +{ + + p_tas2770->mb_runtime_suspend = false; + + return 0; +} + +static int tas2770_regmap_write(struct tas2770_priv *p_tas2770, + unsigned int reg, unsigned int value) +{ + int nResult = 0; + int retry_count = TAS2770_I2C_RETRY_COUNT; + + while (retry_count--) { + nResult = snd_soc_component_write(p_tas2770->component, reg, + value); + if (!nResult) + break; + msleep(20); + } + if (retry_count == -1) + return ERROR_I2C_FAILED; + else + return 0; +} + +static int tas2770_regmap_read(struct tas2770_priv *p_tas2770, + unsigned int reg, unsigned int *value) +{ + int nResult = 0; + int retry_count = TAS2770_I2C_RETRY_COUNT; + + while (retry_count--) { + nResult = snd_soc_component_read(p_tas2770->component, reg, + value); + if (!nResult) + break; + + msleep(20); + } + if (!retry_count) + return ERROR_I2C_FAILED; + + return 0; +} + +static int tas2770_codec_suspend(struct snd_soc_component *component) +{ + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + + mutex_lock(&p_tas2770->codec_lock); + tas2770_runtime_suspend(p_tas2770); + mutex_unlock(&p_tas2770->codec_lock); + + return 0; +} + +static int tas2770_codec_resume(struct snd_soc_component *component) +{ + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + + mutex_lock(&p_tas2770->codec_lock); + tas2770_runtime_resume(p_tas2770); + mutex_unlock(&p_tas2770->codec_lock); + + return 0; +} + +static const char * const tas2770_ASI1_src[] = { + "I2C offset", "Left", "Right", "LeftRightDiv2", +}; + +static SOC_ENUM_SINGLE_DECL( + tas2770_ASI1_src_enum, TAS2770_TDM_CFG_REG2, + 4, tas2770_ASI1_src); + +static const struct snd_kcontrol_new tas2770_asi1_mux = + SOC_DAPM_ENUM("ASI1 Source", tas2770_ASI1_src_enum); + +static int tas2770_set_power_state(struct tas2770_priv *p_tas2770, int state) +{ + struct snd_soc_component *component = p_tas2770->component; + + switch (state) { + case TAS2770_POWER_ACTIVE: + snd_soc_component_update_bits(component, TAS2770_PWR_CTRL, + TAS2770_PWR_CTRL_MASK, + TAS2770_PWR_CTRL_ACTIVE); + p_tas2770->mn_power_state = TAS2770_POWER_ACTIVE; + tas2770_enable_irq(p_tas2770, true); + break; + + case TAS2770_POWER_MUTE: + snd_soc_component_update_bits(component, TAS2770_PWR_CTRL, + TAS2770_PWR_CTRL_MASK, + TAS2770_PWR_CTRL_MUTE); + p_tas2770->mn_power_state = TAS2770_POWER_MUTE; + break; + + case TAS2770_POWER_SHUTDOWN: + snd_soc_component_update_bits(component, TAS2770_PWR_CTRL, + TAS2770_PWR_CTRL_MASK, + TAS2770_PWR_CTRL_SHUTDOWN); + tas2770_enable_irq(p_tas2770, false); + p_tas2770->mn_power_state = TAS2770_POWER_SHUTDOWN; + break; + + default: + dev_err(p_tas2770->dev, + "wrong power state setting %d\n", state); + return -EINVAL; + } + + return 0; +} + +static int tas2770_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&p_tas2770->codec_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = tas2770_set_power_state(p_tas2770, + TAS2770_POWER_MUTE); + if (ret) + goto end; + break; + case SND_SOC_DAPM_PRE_PMD: + ret = tas2770_set_power_state(p_tas2770, + TAS2770_POWER_SHUTDOWN); + if (ret) + goto end; + break; + default: + dev_err(p_tas2770->dev, "Not supported evevt\n"); + ret = -EINVAL; + } + +end: + mutex_unlock(&p_tas2770->codec_lock); + return ret; +} + +static const struct snd_kcontrol_new isense_switch = + SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 3, 1, 1); +static const struct snd_kcontrol_new vsense_switch = + SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 2, 1, 1); + +static const struct snd_soc_dapm_widget tas2770_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0, + &tas2770_asi1_mux), + SND_SOC_DAPM_SWITCH("ISENSE", TAS2770_PWR_CTRL, 3, 1, + &isense_switch), + SND_SOC_DAPM_SWITCH("VSENSE", TAS2770_PWR_CTRL, 2, 1, + &vsense_switch), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2770_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT"), + SND_SOC_DAPM_SIGGEN("VMON"), + SND_SOC_DAPM_SIGGEN("IMON") +}; + +static const struct snd_soc_dapm_route tas2770_audio_map[] = { + {"ASI1 Sel", "I2C offset", "ASI1"}, + {"ASI1 Sel", "Left", "ASI1"}, + {"ASI1 Sel", "Right", "ASI1"}, + {"ASI1 Sel", "LeftRightDiv2", "ASI1"}, + {"DAC", NULL, "ASI1 Sel"}, + {"OUT", NULL, "DAC"}, + {"ISENSE", "Switch", "IMON"}, + {"VSENSE", "Switch", "VMON"}, +}; + + +static int tas2770_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + int ret; + + + mutex_lock(&p_tas2770->codec_lock); + if (mute) + ret = tas2770_set_power_state(p_tas2770, TAS2770_POWER_MUTE); + else + ret = tas2770_set_power_state(p_tas2770, TAS2770_POWER_ACTIVE); + + mutex_unlock(&p_tas2770->codec_lock); + return ret; +} + +static int tas2770_set_slot(struct snd_soc_component *component, int slot_width) +{ + int ret; + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + + switch (slot_width) { + case 16: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG2, + TAS2770_TDM_CFG_REG2_RXS_MASK, + TAS2770_TDM_CFG_REG2_RXS_16BITS); + break; + + case 24: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG2, + TAS2770_TDM_CFG_REG2_RXS_MASK, + TAS2770_TDM_CFG_REG2_RXS_24BITS); + break; + + case 32: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG2, + TAS2770_TDM_CFG_REG2_RXS_MASK, + TAS2770_TDM_CFG_REG2_RXS_32BITS); + break; + + case 0: + /* Do not change slot width */ + ret = 0; + break; + + default: + ret = -EINVAL; + } + + if (!ret) + p_tas2770->mn_slot_width = slot_width; + + return ret; +} + +static int tas2770_set_bitwidth(struct tas2770_priv *p_tas2770, int bitwidth) +{ + int ret; + struct snd_soc_component *component = p_tas2770->component; + + switch (bitwidth) { + case SNDRV_PCM_FORMAT_S16_LE: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG2, + TAS2770_TDM_CFG_REG2_RXW_MASK, + TAS2770_TDM_CFG_REG2_RXW_16BITS); + /* If machine driver did not call set slot width */ + if (p_tas2770->mn_slot_width == 0) + tas2770_set_slot(component, TAS2770_SLOT_16BIT); + p_tas2770->mn_v_mon_slot_no = p_tas2770->mn_i_mon_slot_no + 2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG2, + TAS2770_TDM_CFG_REG2_RXW_MASK, + TAS2770_TDM_CFG_REG2_RXW_24BITS); + if (p_tas2770->mn_slot_width == 0) + tas2770_set_slot(component, TAS2770_SLOT_32BIT); + p_tas2770->mn_v_mon_slot_no = p_tas2770->mn_i_mon_slot_no + 4; + break; + case SNDRV_PCM_FORMAT_S32_LE: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG2, + TAS2770_TDM_CFG_REG2_RXW_MASK, + TAS2770_TDM_CFG_REG2_RXW_32BITS); + if (p_tas2770->mn_slot_width == 0) + tas2770_set_slot(component, TAS2770_SLOT_32BIT); + p_tas2770->mn_v_mon_slot_no = p_tas2770->mn_i_mon_slot_no + 4; + break; + + default: + return -EINVAL; + } + + p_tas2770->mn_ch_size = bitwidth; + + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG5, + TAS2770_TDM_CFG_REG5_VSNS_MASK | + TAS2770_TDM_CFG_REG5_50_MASK, + TAS2770_TDM_CFG_REG5_VSNS_ENABLE | + p_tas2770->mn_v_mon_slot_no); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG6, + TAS2770_TDM_CFG_REG6_ISNS_MASK | + TAS2770_TDM_CFG_REG6_50_MASK, + TAS2770_TDM_CFG_REG6_ISNS_ENABLE | + p_tas2770->mn_i_mon_slot_no); + +end: + return ret; +} + +static int tas2770_set_samplerate(struct tas2770_priv *p_tas2770, + int samplerate) +{ + int ret; + struct snd_soc_component *component = p_tas2770->component; + + switch (samplerate) { + case 48000: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_SMP_MASK, + TAS2770_TDM_CFG_REG0_SMP_48KHZ); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_31_MASK, + TAS2770_TDM_CFG_REG0_31_44_1_48KHZ); + if (ret) + goto end; + break; + case 44100: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_SMP_MASK, + TAS2770_TDM_CFG_REG0_SMP_44_1KHZ); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_31_MASK, + TAS2770_TDM_CFG_REG0_31_44_1_48KHZ); + if (ret) + goto end; + break; + case 96000: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_SMP_MASK, + TAS2770_TDM_CFG_REG0_SMP_48KHZ); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_31_MASK, + TAS2770_TDM_CFG_REG0_31_88_2_96KHZ); + break; + case 88200: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_SMP_MASK, + TAS2770_TDM_CFG_REG0_SMP_44_1KHZ); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_31_MASK, + TAS2770_TDM_CFG_REG0_31_88_2_96KHZ); + break; + case 19200: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_SMP_MASK, + TAS2770_TDM_CFG_REG0_SMP_48KHZ); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_31_MASK, + TAS2770_TDM_CFG_REG0_31_176_4_192KHZ); + if (ret) + goto end; + break; + case 17640: + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_SMP_MASK, + TAS2770_TDM_CFG_REG0_SMP_44_1KHZ); + if (ret) + goto end; + ret = snd_soc_component_update_bits(component, + TAS2770_TDM_CFG_REG0, + TAS2770_TDM_CFG_REG0_31_MASK, + TAS2770_TDM_CFG_REG0_31_176_4_192KHZ); + break; + default: + ret = -EINVAL; + } + +end: + if (!ret) + p_tas2770->mn_sampling_rate = samplerate; + return ret; +} + +static int tas2770_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 tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&p_tas2770->codec_lock); + + ret = tas2770_set_bitwidth(p_tas2770, params_format(params)); + if (ret < 0) + goto end; + + + ret = tas2770_set_samplerate(p_tas2770, params_rate(params)); + +end: + mutex_unlock(&p_tas2770->codec_lock); + return ret; +} + +static int tas2770_set_fmt(struct tas2770_priv *p_tas2770, unsigned int fmt) +{ + u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0; + int ret; + int value = 0; + struct snd_soc_component *component = p_tas2770->component; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_err(p_tas2770->dev, "ASI format master is not found\n"); + ret = -EINVAL; + return ret; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_RSING; + break; + case SND_SOC_DAIFMT_IB_NF: + asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_FALING; + break; + default: + dev_err(p_tas2770->dev, "ASI format Inverse is not found\n"); + ret = -EINVAL; + return ret; + } + + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG1, + TAS2770_TDM_CFG_REG1_RX_MASK, + asi_cfg_1); + if (ret) + return ret; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case (SND_SOC_DAIFMT_I2S): + tdm_rx_start_slot = 1; + break; + case (SND_SOC_DAIFMT_DSP_A): + case (SND_SOC_DAIFMT_DSP_B): + tdm_rx_start_slot = 1; + break; + case (SND_SOC_DAIFMT_LEFT_J): + tdm_rx_start_slot = 0; + break; + default: + dev_err(p_tas2770->dev, + "DAI Format is not found, fmt=0x%x\n", fmt); + ret = -EINVAL; + return ret; + } + + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG1, + TAS2770_TDM_CFG_REG1_MASK, + (tdm_rx_start_slot << TAS2770_TDM_CFG_REG1_51_SHIFT)); + if (ret) + return ret; + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG3, + TAS2770_TDM_CFG_REG3_30_MASK, + (p_tas2770->mn_left_slot << + TAS2770_TDM_CFG_REG3_30_SHIFT)); + if (ret) + return ret; + ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG3, + TAS2770_TDM_CFG_REG3_RXS_MASK, + (p_tas2770->mn_right_slot << + TAS2770_TDM_CFG_REG3_RXS_SHIFT)); + if (ret) + return ret; + + value = snd_soc_component_read32(component, TAS2770_TDM_CFG_REG3); + + p_tas2770->mn_asi_format = fmt; + + return 0; +} + +static int tas2770_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&p_tas2770->codec_lock); + + ret = tas2770_set_fmt(p_tas2770, fmt); + + mutex_unlock(&p_tas2770->codec_lock); + return ret; +} + +static int tas2770_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + int ret; + struct snd_soc_component *component = dai->component; + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + + mutex_lock(&p_tas2770->codec_lock); + ret = tas2770_set_slot(component, slot_width); + mutex_unlock(&p_tas2770->codec_lock); + + return ret; +} + +static struct snd_soc_dai_ops tas2770_dai_ops = { + .digital_mute = tas2770_mute, + .hw_params = tas2770_hw_params, + .set_fmt = tas2770_set_dai_fmt, + .set_tdm_slot = tas2770_set_dai_tdm_slot, +}; + +#define TAS2770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +#define TAS2770_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_192000\ + ) + +static struct snd_soc_dai_driver tas2770_dai_driver[] = { + { + .name = "tas2770 ASI1", + .id = 0, + .playback = { + .stream_name = "ASI1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TAS2770_RATES, + .formats = TAS2770_FORMATS, + }, + .capture = { + .stream_name = "ASI1 Capture", + .channels_min = 0, + .channels_max = 2, + .rates = TAS2770_RATES, + .formats = TAS2770_FORMATS, + }, + .ops = &tas2770_dai_ops, + .symmetric_rates = 1, + }, +}; + +static int tas2770_codec_probe(struct snd_soc_component *component) +{ + struct tas2770_priv *p_tas2770 = + snd_soc_component_get_drvdata(component); + + p_tas2770->component = component; + + return 0; +} + +static void tas2770_codec_remove(struct snd_soc_component *component) +{ + pm_runtime_put(component->dev); +} + +static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0); +static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -12750, 50, 0); + +static const struct snd_kcontrol_new tas2770_snd_controls[] = { + SOC_SINGLE_TLV("Amp Output Level", TAS2770_PLAY_CFG_REG0, + 0, 0x14, 0, + tas2770_digital_tlv), + SOC_SINGLE_TLV("Playback Volume", TAS2770_PLAY_CFG_REG2, + 0, TAS2770_PLAY_CFG_REG2_VMAX, 1, + tas2770_playback_volume), +}; + +static const struct snd_soc_component_driver soc_component_driver_tas2770 = { + .probe = tas2770_codec_probe, + .remove = tas2770_codec_remove, + .suspend = tas2770_codec_suspend, + .resume = tas2770_codec_resume, + .controls = tas2770_snd_controls, + .num_controls = ARRAY_SIZE(tas2770_snd_controls), + .dapm_widgets = tas2770_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas2770_dapm_widgets), + .dapm_routes = tas2770_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas2770_audio_map), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int tas2770_register_codec(struct tas2770_priv *p_tas2770) +{ + return devm_snd_soc_register_component(p_tas2770->dev, + &soc_component_driver_tas2770, + tas2770_dai_driver, ARRAY_SIZE(tas2770_dai_driver)); +} + +static void tas2770_load_config(struct tas2770_priv *p_tas2770) +{ + int ret; + + tas2770_hw_reset(p_tas2770); + tas2770_regmap_write(p_tas2770, TAS2770_SW_RST, + TAS2770_RST); + + ret = tas2770_set_slot(p_tas2770->component, p_tas2770->mn_slot_width); + if (ret) + goto error_out; + + ret = tas2770_set_fmt(p_tas2770, p_tas2770->mn_asi_format); + if (ret) + goto error_out; + + ret = tas2770_set_bitwidth(p_tas2770, p_tas2770->mn_ch_size); + if (ret) + goto error_out; + + ret = tas2770_set_samplerate(p_tas2770, p_tas2770->mn_sampling_rate); + if (ret) + goto error_out; + + ret = tas2770_set_power_state(p_tas2770, p_tas2770->mn_power_state); + if (ret) + goto error_out; + +error_out: + return; +} + +static const struct reg_default tas2770_reg_defaults[] = { + { TAS2770_PAGE, 0x00 }, + { TAS2770_SW_RST, 0x00 }, + { TAS2770_PWR_CTRL, 0x0e }, + { TAS2770_PLAY_CFG_REG0, 0x10 }, + { TAS2770_PLAY_CFG_REG1, 0x01 }, + { TAS2770_PLAY_CFG_REG2, 0x00 }, + { TAS2770_MSC_CFG_REG0, 0x07 }, + { TAS2770_TDM_CFG_REG1, 0x02 }, + { TAS2770_TDM_CFG_REG2, 0x0a }, + { TAS2770_TDM_CFG_REG3, 0x10 }, + { TAS2770_INT_MASK_REG0, 0xfc }, + { TAS2770_INT_MASK_REG1, 0xb1 }, + { TAS2770_INT_CFG, 0x05 }, + { TAS2770_MISC_IRQ, 0x81 }, + { TAS2770_CLK_CGF, 0x0c }, + +}; + +static bool tas2770_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS2770_PAGE: /* regmap implementation requires this */ + case TAS2770_SW_RST: /* always clears after write */ + case TAS2770_BO_PRV_REG0:/* has a self clearing bit */ + case TAS2770_LVE_INT_REG0: + case TAS2770_LVE_INT_REG1: + case TAS2770_LAT_INT_REG0:/* Sticky interrupt flags */ + case TAS2770_LAT_INT_REG1:/* Sticky interrupt flags */ + case TAS2770_VBAT_MSB: + case TAS2770_VBAT_LSB: + case TAS2770_TEMP_MSB: + case TAS2770_TEMP_LSB: + return true; + } + return false; +} + +static bool tas2770_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS2770_LVE_INT_REG0: + case TAS2770_LVE_INT_REG1: + case TAS2770_LAT_INT_REG0: + case TAS2770_LAT_INT_REG1: + case TAS2770_VBAT_MSB: + case TAS2770_VBAT_LSB: + case TAS2770_TEMP_MSB: + case TAS2770_TEMP_LSB: + case TAS2770_TDM_CLK_DETC: + case TAS2770_REV_AND_GPID: + return false; + } + return true; +} + +static const struct regmap_range_cfg tas2770_regmap_ranges[] = { + { + .range_min = 0, + .range_max = 1 * 128, + .selector_reg = TAS2770_PAGE, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 128, + }, +}; + +static const struct regmap_config tas2770_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = tas2770_writeable, + .volatile_reg = tas2770_volatile, + .reg_defaults = tas2770_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas2770_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .ranges = tas2770_regmap_ranges, + .num_ranges = ARRAY_SIZE(tas2770_regmap_ranges), + .max_register = 1 * 128, +}; + +static irqreturn_t tas2770_irq_handler(int irq, void *dev_id) +{ + struct tas2770_priv *p_tas2770 = (struct tas2770_priv *)dev_id; + + unsigned int nDevInt1Status = 0, nDevInt2Status = 0; + int n_counter = 2; + int n_result; + + mutex_lock(&p_tas2770->codec_lock); + + tas2770_enable_irq(p_tas2770, false); + + if (p_tas2770->mb_runtime_suspend) + goto end; + + if (p_tas2770->mn_power_state == TAS2770_POWER_SHUTDOWN) + goto end; + + n_result = tas2770_regmap_write(p_tas2770, TAS2770_INT_MASK_REG0, + TAS2770_INT_MASK_REG0_DISABLE); + if (n_result) + goto reload; + n_result = tas2770_regmap_write(p_tas2770, TAS2770_INT_MASK_REG1, + TAS2770_INT_MASK_REG1_DISABLE); + if (n_result) + goto reload; + + n_result = tas2770_regmap_read(p_tas2770, + TAS2770_LAT_INT_REG0, &nDevInt1Status); + if (n_result >= 0) + n_result = tas2770_regmap_read(p_tas2770, + TAS2770_LAT_INT_REG1, &nDevInt2Status); + else + goto reload; + + if (((nDevInt1Status & 0x3) != 0) || ((nDevInt2Status & 0x0f) != 0)) { + /* in case of INT_OC, INT_OT, INT_OVLT, INT_UVLT, INT_BO */ + + if (nDevInt1Status & + TAS2770_LAT_INT_REG0_OCE_FLG) { + p_tas2770->mn_err_code |= ERROR_OVER_CURRENT; + dev_err(p_tas2770->dev, "SPK over current!\n"); + } else { + p_tas2770->mn_err_code &= ~ERROR_OVER_CURRENT; + } + + if (nDevInt1Status & + TAS2770_LAT_INT_REG0_OTE_FLG) { + p_tas2770->mn_err_code |= ERROR_DIE_OVERTEMP; + dev_err(p_tas2770->dev, "die over temperature!\n"); + } else { + p_tas2770->mn_err_code &= ~ERROR_DIE_OVERTEMP; + } + + if (nDevInt2Status & + TAS2770_LAT_INT_REG1_VBA_TOV) { + p_tas2770->mn_err_code |= ERROR_OVER_VOLTAGE; + dev_err(p_tas2770->dev, "SPK over voltage!\n"); + } else { + p_tas2770->mn_err_code &= ~ERROR_OVER_VOLTAGE; + } + + if (nDevInt2Status & + TAS2770_LAT_INT_REG1_VBA_TUV) { + p_tas2770->mn_err_code |= ERROR_UNDER_VOLTAGE; + dev_err(p_tas2770->dev, "SPK under voltage!\n"); + } else { + p_tas2770->mn_err_code &= ~ERROR_UNDER_VOLTAGE; + } + + if (nDevInt2Status & + TAS2770_LAT_INT_REG1_BOUT_FLG) { + p_tas2770->mn_err_code |= ERROR_BROWNOUT; + dev_err(p_tas2770->dev, "brownout!\n"); + } else { + p_tas2770->mn_err_code &= ~ERROR_BROWNOUT; + } + + goto reload; + } else { + n_counter = 2; + + while (n_counter > 0) { + n_result = tas2770_regmap_read(p_tas2770, + TAS2770_PWR_CTRL, &nDevInt1Status); + if (n_result) + goto reload; + + if ((nDevInt1Status & + TAS2770_PWR_CTRL_MASK) + != TAS2770_PWR_CTRL_SHUTDOWN) + break; + + n_counter--; + if (n_counter > 0) + msleep(20); + } + + if ((nDevInt1Status & + TAS2770_PWR_CTRL_MASK) + == TAS2770_PWR_CTRL_SHUTDOWN) { + dev_err(p_tas2770->dev, "%s, Critical ERROR REG[0x%x] = 0x%x\n", + __func__, + TAS2770_PWR_CTRL, + nDevInt1Status); + p_tas2770->mn_err_code |= ERROR_CLASSD_PWR; + goto reload; + } + p_tas2770->mn_err_code &= ~ERROR_CLASSD_PWR; + } + + n_result = tas2770_regmap_write(p_tas2770, + TAS2770_INT_MASK_REG0, 0xfc); + if (n_result) + goto reload; + + n_result = tas2770_regmap_write(p_tas2770, + TAS2770_INT_MASK_REG1, 0xb1); + if (n_result) + goto reload; + + goto end; + +reload: + /* hardware reset and reload */ + tas2770_load_config(p_tas2770); + +end: + mutex_unlock(&p_tas2770->codec_lock); + tas2770_enable_irq(p_tas2770, true); + + return IRQ_HANDLED; +} + +static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *p_tas2770) +{ + int rc = 0; + + rc = fwnode_property_read_u32(dev->fwnode, "ti,asi-format", + &p_tas2770->mn_asi_format); + if (rc) { + dev_err(p_tas2770->dev, "Looking up %s property failed %d\n", + "ti,asi-format", rc); + goto end; + } + + rc = fwnode_property_read_u32(dev->fwnode, "ti,left-slot", + &p_tas2770->mn_left_slot); + if (rc) { + dev_err(p_tas2770->dev, "Looking up %s property failed %d\n", + "ti,left-slot", rc); + goto end; + } + + rc = fwnode_property_read_u32(dev->fwnode, "ti,right-slot", + &p_tas2770->mn_right_slot); + if (rc) { + dev_err(p_tas2770->dev, "Looking up %s property failed %d\n", + "ti,right-slot", rc); + goto end; + } + + rc = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no", + &p_tas2770->mn_i_mon_slot_no); + if (rc) { + dev_err(p_tas2770->dev, "Looking up %s property failed %d\n", + "ti,imon-slot-no", rc); + goto end; + } + + rc = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no", + &p_tas2770->mn_v_mon_slot_no); + if (rc) { + dev_err(p_tas2770->dev, "Looking up %s property failed %d\n", + "ti,vmon-slot-no", rc); + goto end; + } + +end: + return rc; +} + +static int tas2770_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tas2770_priv *p_tas2770; + int n_result; + + p_tas2770 = devm_kzalloc(&client->dev, + sizeof(struct tas2770_priv), GFP_KERNEL); + if (p_tas2770 == NULL) + return -ENOMEM; + p_tas2770->dev = &client->dev; + p_tas2770->mn_irq = client->irq; + + i2c_set_clientdata(client, p_tas2770); + dev_set_drvdata(&client->dev, p_tas2770); + p_tas2770->mn_power_state = TAS2770_POWER_SHUTDOWN; + + p_tas2770->regmap = devm_regmap_init_i2c(client, &tas2770_i2c_regmap); + if (IS_ERR(p_tas2770->regmap)) { + n_result = PTR_ERR(p_tas2770->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + n_result); + goto end; + } + + if (client->dev.of_node) { + n_result = tas2770_parse_dt(&client->dev, p_tas2770); + if (n_result) { + dev_err(p_tas2770->dev, "%s: Failed to parse devicetree\n", + __func__); + goto end; + } + } + + p_tas2770->mn_reset_gpio = devm_gpiod_get_optional(p_tas2770->dev, + "reset-gpio", + GPIOD_OUT_HIGH); + if (IS_ERR(p_tas2770->mn_reset_gpio)) { + if (PTR_ERR(p_tas2770->mn_reset_gpio) == -EPROBE_DEFER) + return -EPROBE_DEFER; + p_tas2770->mn_reset_gpio = NULL; + } + + if (p_tas2770->mn_irq > 0) { + n_result = devm_request_threaded_irq(p_tas2770->dev, + p_tas2770->mn_irq, + NULL, tas2770_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, + p_tas2770); + if (n_result) { + dev_err(p_tas2770->dev, "Unable to request IRQ\n"); + return n_result; + } + } + + p_tas2770->mn_ch_size = 0; + p_tas2770->mn_slot_width = 0; + + tas2770_hw_reset(p_tas2770); + tas2770_regmap_write(p_tas2770, TAS2770_SW_RST, + TAS2770_RST); + + mutex_init(&p_tas2770->dev_lock); + + mutex_init(&p_tas2770->codec_lock); + n_result = tas2770_register_codec(p_tas2770); + if (n_result) + dev_err(p_tas2770->dev, "Register codec failed.\n"); + +end: + return n_result; +} + +static int tas2770_i2c_remove(struct i2c_client *client) +{ + struct tas2770_priv *p_tas2770 = i2c_get_clientdata(client); + + mutex_destroy(&p_tas2770->dev_lock); + + return 0; +} + + +static const struct i2c_device_id tas2770_i2c_id[] = { + { "tas2770", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas2770_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id tas2770_of_match[] = { + { .compatible = "ti,tas2770" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tas2770_of_match); +#endif + +static struct i2c_driver tas2770_i2c_driver = { + .driver = { + .name = "tas2770", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = of_match_ptr(tas2770_of_match), +#endif + }, + .probe = tas2770_i2c_probe, + .remove = tas2770_i2c_remove, + .id_table = tas2770_i2c_id, +}; + +module_i2c_driver(tas2770_i2c_driver); + +MODULE_AUTHOR("Shi Fu shifu0704@thundersoft.com"); +MODULE_DESCRIPTION("TAS2770 I2C Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h new file mode 100644 index 0000000..f08e208 --- /dev/null +++ b/sound/soc/codecs/tas2770.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * ALSA SoC TAS2770 codec driver + * + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + */ +#ifndef __TAS2770__ +#define __TAS2770__ + +/* Book Control Register (available in page0 of each book) */ +#define TAS2770_BOOKCTL_PAGE 0 +#define TAS2770_BOOKCTL_REG 127 +#define TAS2770_REG(page, reg) ((page * 128) + reg) + /* Page */ +#define TAS2770_PAGE TAS2770_REG(0X0, 0x00) +#define TAS2770_PAGE_PAGE_MASK 255 + /* Software Reset */ +#define TAS2770_SW_RST TAS2770_REG(0X0, 0x01) +#define TAS2770_RST BIT(0) + /* Power Control */ +#define TAS2770_PWR_CTRL TAS2770_REG(0X0, 0x02) +#define TAS2770_PWR_CTRL_MASK 0x3 +#define TAS2770_PWR_CTRL_ACTIVE 0x0 +#define TAS2770_PWR_CTRL_MUTE BIT(0) +#define TAS2770_PWR_CTRL_SHUTDOWN 0x2 + /* Playback Configuration Reg0 */ +#define TAS2770_PLAY_CFG_REG0 TAS2770_REG(0X0, 0x03) + /* Playback Configuration Reg1 */ +#define TAS2770_PLAY_CFG_REG1 TAS2770_REG(0X0, 0x04) + /* Playback Configuration Reg2 */ +#define TAS2770_PLAY_CFG_REG2 TAS2770_REG(0X0, 0x05) +#define TAS2770_PLAY_CFG_REG2_VMAX 0xc9 + /* Misc Configuration Reg0 */ +#define TAS2770_MSC_CFG_REG0 TAS2770_REG(0X0, 0x07) + /* TDM Configuration Reg0 */ +#define TAS2770_TDM_CFG_REG0 TAS2770_REG(0X0, 0x0A) +#define TAS2770_TDM_CFG_REG0_SMP_MASK BIT(5) +#define TAS2770_TDM_CFG_REG0_SMP_48KHZ 0x0 +#define TAS2770_TDM_CFG_REG0_SMP_44_1KHZ BIT(5) +#define TAS2770_TDM_CFG_REG0_31_MASK 0xe +#define TAS2770_TDM_CFG_REG0_31_44_1_48KHZ 0x6 +#define TAS2770_TDM_CFG_REG0_31_88_2_96KHZ 0x8 +#define TAS2770_TDM_CFG_REG0_31_176_4_192KHZ 0xa + /* TDM Configuration Reg1 */ +#define TAS2770_TDM_CFG_REG1 TAS2770_REG(0X0, 0x0B) +#define TAS2770_TDM_CFG_REG1_MASK 0x3e +#define TAS2770_TDM_CFG_REG1_51_SHIFT 1 +#define TAS2770_TDM_CFG_REG1_RX_MASK BIT(0) +#define TAS2770_TDM_CFG_REG1_RX_RSING 0x0 +#define TAS2770_TDM_CFG_REG1_RX_FALING BIT(0) + /* TDM Configuration Reg2 */ +#define TAS2770_TDM_CFG_REG2 TAS2770_REG(0X0, 0x0C) +#define TAS2770_TDM_CFG_REG2_RXW_MASK 0xc +#define TAS2770_TDM_CFG_REG2_RXW_16BITS 0x0 +#define TAS2770_TDM_CFG_REG2_RXW_24BITS 0x8 +#define TAS2770_TDM_CFG_REG2_RXW_32BITS 0xc +#define TAS2770_TDM_CFG_REG2_RXS_MASK 0x3 +#define TAS2770_TDM_CFG_REG2_RXS_16BITS 0x0 +#define TAS2770_TDM_CFG_REG2_RXS_24BITS BIT(0) +#define TAS2770_TDM_CFG_REG2_RXS_32BITS 0x2 + /* TDM Configuration Reg3 */ +#define TAS2770_TDM_CFG_REG3 TAS2770_REG(0X0, 0x0D) +#define TAS2770_TDM_CFG_REG3_RXS_MASK 0xf0 +#define TAS2770_TDM_CFG_REG3_RXS_SHIFT 0x4 +#define TAS2770_TDM_CFG_REG3_30_MASK 0xf +#define TAS2770_TDM_CFG_REG3_30_SHIFT 0 + /* TDM Configuration Reg5 */ +#define TAS2770_TDM_CFG_REG5 TAS2770_REG(0X0, 0x0F) +#define TAS2770_TDM_CFG_REG5_VSNS_MASK BIT(6) +#define TAS2770_TDM_CFG_REG5_VSNS_ENABLE BIT(6) +#define TAS2770_TDM_CFG_REG5_50_MASK 0x3f + /* TDM Configuration Reg6 */ +#define TAS2770_TDM_CFG_REG6 TAS2770_REG(0X0, 0x10) +#define TAS2770_TDM_CFG_REG6_ISNS_MASK BIT(6) +#define TAS2770_TDM_CFG_REG6_ISNS_ENABLE BIT(6) +#define TAS2770_TDM_CFG_REG6_50_MASK 0x3f + /* Brown Out Prevention Reg0 */ +#define TAS2770_BO_PRV_REG0 TAS2770_REG(0X0, 0x1B) + /* Interrupt MASK Reg0 */ +#define TAS2770_INT_MASK_REG0 TAS2770_REG(0X0, 0x20) +#define TAS2770_INT_MASK_REG0_DISABLE 0xff + /* Interrupt MASK Reg1 */ +#define TAS2770_INT_MASK_REG1 TAS2770_REG(0X0, 0x21) +#define TAS2770_INT_MASK_REG1_DISABLE 0xff + /* Live-Interrupt Reg0 */ +#define TAS2770_LVE_INT_REG0 TAS2770_REG(0X0, 0x22) + /* Live-Interrupt Reg1 */ +#define TAS2770_LVE_INT_REG1 TAS2770_REG(0X0, 0x23) + /* Latched-Interrupt Reg0 */ +#define TAS2770_LAT_INT_REG0 TAS2770_REG(0X0, 0x24) +#define TAS2770_LAT_INT_REG0_OCE_FLG BIT(1) +#define TAS2770_LAT_INT_REG0_OTE_FLG BIT(0) + /* Latched-Interrupt Reg1 */ +#define TAS2770_LAT_INT_REG1 TAS2770_REG(0X0, 0x25) +#define TAS2770_LAT_INT_REG1_VBA_TOV BIT(3) +#define TAS2770_LAT_INT_REG1_VBA_TUV BIT(2) +#define TAS2770_LAT_INT_REG1_BOUT_FLG BIT(1) + /* VBAT MSB */ +#define TAS2770_VBAT_MSB TAS2770_REG(0X0, 0x27) + /* VBAT LSB */ +#define TAS2770_VBAT_LSB TAS2770_REG(0X0, 0x28) + /* TEMP MSB */ +#define TAS2770_TEMP_MSB TAS2770_REG(0X0, 0x29) + /* TEMP LSB */ +#define TAS2770_TEMP_LSB TAS2770_REG(0X0, 0x2A) + /* Interrupt Configuration */ +#define TAS2770_INT_CFG TAS2770_REG(0X0, 0x30) + /* Misc IRQ */ +#define TAS2770_MISC_IRQ TAS2770_REG(0X0, 0x32) + /* Clock Configuration */ +#define TAS2770_CLK_CGF TAS2770_REG(0X0, 0x3C) + /* TDM Clock detection monitor */ +#define TAS2770_TDM_CLK_DETC TAS2770_REG(0X0, 0x77) + /* Revision and PG ID */ +#define TAS2770_REV_AND_GPID TAS2770_REG(0X0, 0x7D) + +#define TAS2770_POWER_ACTIVE 0 +#define TAS2770_POWER_MUTE 1 +#define TAS2770_POWER_SHUTDOWN 2 +#define ERROR_OVER_CURRENT 0x0000001 +#define ERROR_DIE_OVERTEMP 0x0000002 +#define ERROR_OVER_VOLTAGE 0x0000004 +#define ERROR_UNDER_VOLTAGE 0x0000008 +#define ERROR_BROWNOUT 0x0000010 +#define ERROR_CLASSD_PWR 0x0000020 +#define TAS2770_SLOT_16BIT 16 +#define TAS2770_SLOT_32BIT 32 +#define TAS2770_I2C_RETRY_COUNT 3 +#define ERROR_I2C_FAILED -2 + +struct tas2770_register { + int book; + int page; + int reg; +}; + +struct tas2770_dai_cfg { + unsigned int dai_fmt; + unsigned int tdm_delay; +}; + +struct tas2770_priv { + struct device *dev; + struct regmap *regmap; + struct snd_soc_codec *codec; + struct snd_soc_component *component; + struct mutex dev_lock; + struct hrtimer mtimer; + int mn_clkin; + int mn_clkid; + int mn_power_state; + int mn_current_book; + int mn_current_page; + int mn_load; + int mn_asi_format; + struct gpio_desc *mn_reset_gpio; + int mn_irq; + bool mb_irq_enable; + int mn_sampling_rate; + int mn_frame_size; + int mn_pll; + int mn_left_slot; + int mn_right_slot; + int mn_ch_size; + int mn_slot_width; + int mn_v_mon_slot_no; + int mn_i_mon_slot_no; + bool mb_runtime_suspend; + unsigned int mn_err_code; + struct mutex codec_lock; +}; + +#endif /* __TAS2770__ */
On Fri, Sep 06, 2019 at 03:06:04PM +0800, shifu0704@thundersoft.com wrote:
index 0000000..9fc0c11 --- /dev/null +++ b/sound/soc/codecs/tas2770.c @@ -0,0 +1,1103 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- ALSA SoC Texas Instruments TAS2770 20-W Digital Input Mono Class-D
- Audio Amplifier with Speaker I/V Sense
Please make the entire comment block a C++ one so it looks neater.
+static void tas2770_hw_reset(struct tas2770_priv *p_tas2770) +{
- if (p_tas2770->mn_reset_gpio) {
gpiod_set_value_cansleep(p_tas2770->mn_reset_gpio, 0);
msleep(20);
gpiod_set_value_cansleep(p_tas2770->mn_reset_gpio, 1);
- }
- p_tas2770->mn_current_book = -1;
- p_tas2770->mn_current_page = -1;
This is as far as I can tell the only place where these two struct members are accessed, may as well delete them. Also throughout the struct there's odd prefixes on the names of the members which isn't idiomatic for the kernel.
+static void tas2770_enable_irq(struct tas2770_priv *p_tas2770, bool enable) +{
- if (enable) {
if (p_tas2770->mb_irq_enable)
return;
if (gpio_is_valid(p_tas2770->mn_irq))
enable_irq(p_tas2770->mn_irq);
p_tas2770->mb_irq_enable = true;
- } else {
if (gpio_is_valid(p_tas2770->mn_irq))
disable_irq_nosync(p_tas2770->mn_irq);
p_tas2770->mb_irq_enable = false;
- }
+}
What's this doing and why is there some interaction with GPIOs? I see there's something going on to do with powering on the device and handling interrupts but it's not clear what the intent is and it looks like it's misusing the interrupt APIs.
+static int tas2770_runtime_suspend(struct tas2770_priv *p_tas2770) +{
- p_tas2770->mb_runtime_suspend = true;
- return 0;
+}
+static int tas2770_runtime_resume(struct tas2770_priv *p_tas2770) +{
- p_tas2770->mb_runtime_suspend = false;
- return 0;
+}
This isn't doing anything, remove it. You can query the current state from the runtime PM API if it's needed.
+static int tas2770_regmap_write(struct tas2770_priv *p_tas2770,
unsigned int reg, unsigned int value)
+{
- int nResult = 0;
This isn't idiomatic naming for the kernel, there's a lot of this sort of naming in the code.
- int retry_count = TAS2770_I2C_RETRY_COUNT;
- while (retry_count--) {
nResult = snd_soc_component_write(p_tas2770->component, reg,
value);
if (!nResult)
break;
msleep(20);
- }
- if (retry_count == -1)
return ERROR_I2C_FAILED;
This is not a standard kernel error code.
- else
return 0;
+}
This is called regmap, actually wraps the ASoC level function and looks like it's trying to work around some truly horrific hardware bug. Is this *really* needed upstream and not just a workaround for some very specific board?
+static int tas2770_codec_suspend(struct snd_soc_component *component) +{
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- mutex_lock(&p_tas2770->codec_lock);
- tas2770_runtime_suspend(p_tas2770);
- mutex_unlock(&p_tas2770->codec_lock);
- return 0;
+}
+static int tas2770_codec_resume(struct snd_soc_component *component) +{
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- mutex_lock(&p_tas2770->codec_lock);
- tas2770_runtime_resume(p_tas2770);
- mutex_unlock(&p_tas2770->codec_lock);
- return 0;
+}
These don't actually do anything?
+static int tas2770_set_power_state(struct tas2770_priv *p_tas2770, int state) +{
This is called from multiple places with no kind of reference counting or anything to ensure that the power is managed correctly. I'm very suspicious of what this is doing, my best guess is that this should be either directly in a DAPM widget or in set_bias_level() rather than coded outside of DAPM entirely.
There's also no sense in combining the on and off cases into a single function, they share nothing.
+static int tas2770_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 tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- int ret;
- mutex_lock(&p_tas2770->codec_lock);
- ret = tas2770_set_bitwidth(p_tas2770, params_format(params));
- if (ret < 0)
goto end;
- ret = tas2770_set_samplerate(p_tas2770, params_rate(params));
+end:
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
What's the goal with this locking? It's not clear what this is intended to protect.
- default:
dev_err(p_tas2770->dev, "ASI format master is not found\n");
ret = -EINVAL;
return ret;
- }
Just write the return statement directly, no need for the assignment.
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case (SND_SOC_DAIFMT_I2S):
tdm_rx_start_slot = 1;
break;
- case (SND_SOC_DAIFMT_DSP_A):
- case (SND_SOC_DAIFMT_DSP_B):
tdm_rx_start_slot = 1;
break;
- case (SND_SOC_DAIFMT_LEFT_J):
tdm_rx_start_slot = 0;
break;
The two DSP modes should not be identical.
+static int tas2770_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- struct snd_soc_component *component = dai->component;
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- int ret;
- mutex_lock(&p_tas2770->codec_lock);
- ret = tas2770_set_fmt(p_tas2770, fmt);
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
Just inline set_fmt(), there's no need for the extra function wrapping. The same issue applies to a lot of other operations.
+static void tas2770_codec_remove(struct snd_soc_component *component) +{
- pm_runtime_put(component->dev);
+}
This is buggy, there's no matching get and you shouldn't be holding runtime PM on for the entire time the driver is registered.
+static const struct snd_kcontrol_new tas2770_snd_controls[] = {
- SOC_SINGLE_TLV("Amp Output Level", TAS2770_PLAY_CFG_REG0,
0, 0x14, 0,
tas2770_digital_tlv),
All volume controls should end in Volume so userspace knows how to handle them.
- n_result = tas2770_regmap_write(p_tas2770, TAS2770_INT_MASK_REG0,
TAS2770_INT_MASK_REG0_DISABLE);
- if (n_result)
goto reload;
- n_result = tas2770_regmap_write(p_tas2770, TAS2770_INT_MASK_REG1,
TAS2770_INT_MASK_REG1_DISABLE);
- if (n_result)
goto reload;
- n_result = tas2770_regmap_read(p_tas2770,
TAS2770_LAT_INT_REG0, &nDevInt1Status);
- if (n_result >= 0)
n_result = tas2770_regmap_read(p_tas2770,
TAS2770_LAT_INT_REG1, &nDevInt2Status);
- else
goto reload;
So this looks like we've got code for the device randomly resetting underneath us? That seems very bad and is really confusing the code. If this is needed I would suggest writing the driver first for the case where the device doesn't randomly reset and then try to add handling for this separately, that will make it a lot easier and clearer.
+#if defined(CONFIG_OF)
.of_match_table = of_match_ptr(tas2770_of_match),
+#endif
of_match_ptr() means you don't need the ifdef.
Shi
On 9/6/19 2:06 AM, shifu0704@thundersoft.com wrote:
From: Frank Shi shifu0704@thundersoft.com
add tas2770 smart PA kernel driver
Signed-off-by: Frank Shi shifu0704@thundersoft.com
sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tas2770.c | 1103 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tas2770.h | 173 +++++++ 4 files changed, 1283 insertions(+) create mode 100644 sound/soc/codecs/tas2770.c create mode 100644 sound/soc/codecs/tas2770.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8f3e787..cc92da3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -111,6 +111,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_STI_SAS select SND_SOC_TAS2552 if I2C
- select SND_SOC_TAS2770 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C select SND_SOC_TFA9879 if I2C
@@ -652,6 +653,10 @@ config SND_SOC_TAS2552 tristate "Texas Instruments TAS2552 Mono Audio amplifier" depends on I2C
+config SND_SOC_TAS2770
- tristate "Texas Instruments TAS2770 speaker amplifier"
- depends on I2C
- config SND_SOC_TAS5086 tristate "Texas Instruments TAS5086 speaker amplifier" depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5305cc6..1b58d08 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -116,6 +116,7 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o +snd-soc-tas2770-objs := tas2770.o
extra space between = tas2770.o
snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -332,6 +333,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o +obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c new file mode 100644 index 0000000..9fc0c11 --- /dev/null +++ b/sound/soc/codecs/tas2770.c @@ -0,0 +1,1103 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- ALSA SoC Texas Instruments TAS2770 20-W Digital Input Mono Class-D
- Audio Amplifier with Speaker I/V Sense
- Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/
- Author: Tracy Yi tracy-yi@ti.com
- Shi Fu shifu0704@thundersoft.com
- */
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/firmware.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/tlv.h>
+#include "tas2770.h"
+#define TAS2770_MDELAY 0xFFFFFFFE +#define TAS2770_CHECK_PERIOD 5000 /* 5 second */
+static void tas2770_hw_reset(struct tas2770_priv *p_tas2770) +{
- if (p_tas2770->mn_reset_gpio) {
gpiod_set_value_cansleep(p_tas2770->mn_reset_gpio, 0);
msleep(20);
gpiod_set_value_cansleep(p_tas2770->mn_reset_gpio, 1);
- }
- p_tas2770->mn_current_book = -1;
- p_tas2770->mn_current_page = -1;
+}
+static void tas2770_enable_irq(struct tas2770_priv *p_tas2770, bool enable) +{
- if (enable) {
if (p_tas2770->mb_irq_enable)
return;
if (gpio_is_valid(p_tas2770->mn_irq))
enable_irq(p_tas2770->mn_irq);
p_tas2770->mb_irq_enable = true;
- } else {
if (gpio_is_valid(p_tas2770->mn_irq))
disable_irq_nosync(p_tas2770->mn_irq);
p_tas2770->mb_irq_enable = false;
- }
+}
+static int tas2770_runtime_suspend(struct tas2770_priv *p_tas2770) +{
- p_tas2770->mb_runtime_suspend = true;
- return 0;
+}
+static int tas2770_runtime_resume(struct tas2770_priv *p_tas2770) +{
- p_tas2770->mb_runtime_suspend = false;
- return 0;
+}
+static int tas2770_regmap_write(struct tas2770_priv *p_tas2770,
unsigned int reg, unsigned int value)
Why are you calling this function regmap_write when it uses the snd_soc_component_write?
And why do we need this function at all? For retries? Does every call to write or read need to have a retry?
I am think you can eliminate this function and just use the snd_soc_component_read/write directly
+{
- int nResult = 0;
- int retry_count = TAS2770_I2C_RETRY_COUNT;
- while (retry_count--) {
nResult = snd_soc_component_write(p_tas2770->component, reg,
value);
if (!nResult)
break;
msleep(20);
- }
- if (retry_count == -1)
return ERROR_I2C_FAILED;
- else
return 0;
+}
+static int tas2770_regmap_read(struct tas2770_priv *p_tas2770,
unsigned int reg, unsigned int *value)
+{
- int nResult = 0;
- int retry_count = TAS2770_I2C_RETRY_COUNT;
- while (retry_count--) {
nResult = snd_soc_component_read(p_tas2770->component, reg,
value);
if (!nResult)
break;
msleep(20);
- }
- if (!retry_count)
return ERROR_I2C_FAILED;
- return 0;
+}
+static int tas2770_codec_suspend(struct snd_soc_component *component) +{
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- mutex_lock(&p_tas2770->codec_lock);
- tas2770_runtime_suspend(p_tas2770);
- mutex_unlock(&p_tas2770->codec_lock);
- return 0;
+}
+static int tas2770_codec_resume(struct snd_soc_component *component) +{
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- mutex_lock(&p_tas2770->codec_lock);
- tas2770_runtime_resume(p_tas2770);
- mutex_unlock(&p_tas2770->codec_lock);
- return 0;
+}
+static const char * const tas2770_ASI1_src[] = {
- "I2C offset", "Left", "Right", "LeftRightDiv2",
+};
+static SOC_ENUM_SINGLE_DECL(
- tas2770_ASI1_src_enum, TAS2770_TDM_CFG_REG2,
- 4, tas2770_ASI1_src);
+static const struct snd_kcontrol_new tas2770_asi1_mux =
- SOC_DAPM_ENUM("ASI1 Source", tas2770_ASI1_src_enum);
+static int tas2770_set_power_state(struct tas2770_priv *p_tas2770, int state) +{
- struct snd_soc_component *component = p_tas2770->component;
- switch (state) {
- case TAS2770_POWER_ACTIVE:
snd_soc_component_update_bits(component, TAS2770_PWR_CTRL,
TAS2770_PWR_CTRL_MASK,
TAS2770_PWR_CTRL_ACTIVE);
p_tas2770->mn_power_state = TAS2770_POWER_ACTIVE;
tas2770_enable_irq(p_tas2770, true);
break;
- case TAS2770_POWER_MUTE:
snd_soc_component_update_bits(component, TAS2770_PWR_CTRL,
TAS2770_PWR_CTRL_MASK,
TAS2770_PWR_CTRL_MUTE);
p_tas2770->mn_power_state = TAS2770_POWER_MUTE;
break;
- case TAS2770_POWER_SHUTDOWN:
snd_soc_component_update_bits(component, TAS2770_PWR_CTRL,
TAS2770_PWR_CTRL_MASK,
TAS2770_PWR_CTRL_SHUTDOWN);
tas2770_enable_irq(p_tas2770, false);
p_tas2770->mn_power_state = TAS2770_POWER_SHUTDOWN;
break;
- default:
dev_err(p_tas2770->dev,
"wrong power state setting %d\n", state);
return -EINVAL;
- }
- return 0;
+}
+static int tas2770_dac_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
- struct snd_soc_component *component =
snd_soc_dapm_to_component(w->dapm);
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- int ret;
- mutex_lock(&p_tas2770->codec_lock);
- switch (event) {
- case SND_SOC_DAPM_POST_PMU:
ret = tas2770_set_power_state(p_tas2770,
TAS2770_POWER_MUTE);
if (ret)
goto end;
break;
- case SND_SOC_DAPM_PRE_PMD:
ret = tas2770_set_power_state(p_tas2770,
TAS2770_POWER_SHUTDOWN);
if (ret)
goto end;
break;
- default:
dev_err(p_tas2770->dev, "Not supported evevt\n");
ret = -EINVAL;
- }
+end:
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
+static const struct snd_kcontrol_new isense_switch =
- SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 3, 1, 1);
+static const struct snd_kcontrol_new vsense_switch =
- SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 2, 1, 1);
+static const struct snd_soc_dapm_widget tas2770_dapm_widgets[] = {
- SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0,
&tas2770_asi1_mux),
- SND_SOC_DAPM_SWITCH("ISENSE", TAS2770_PWR_CTRL, 3, 1,
&isense_switch),
- SND_SOC_DAPM_SWITCH("VSENSE", TAS2770_PWR_CTRL, 2, 1,
&vsense_switch),
- SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2770_dac_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
- SND_SOC_DAPM_OUTPUT("OUT"),
- SND_SOC_DAPM_SIGGEN("VMON"),
- SND_SOC_DAPM_SIGGEN("IMON")
+};
+static const struct snd_soc_dapm_route tas2770_audio_map[] = {
- {"ASI1 Sel", "I2C offset", "ASI1"},
- {"ASI1 Sel", "Left", "ASI1"},
- {"ASI1 Sel", "Right", "ASI1"},
- {"ASI1 Sel", "LeftRightDiv2", "ASI1"},
- {"DAC", NULL, "ASI1 Sel"},
- {"OUT", NULL, "DAC"},
- {"ISENSE", "Switch", "IMON"},
- {"VSENSE", "Switch", "VMON"},
+};
extra new line
+static int tas2770_mute(struct snd_soc_dai *dai, int mute) +{
- struct snd_soc_component *component = dai->component;
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- int ret;
extra new line
- mutex_lock(&p_tas2770->codec_lock);
- if (mute)
ret = tas2770_set_power_state(p_tas2770, TAS2770_POWER_MUTE);
- else
ret = tas2770_set_power_state(p_tas2770, TAS2770_POWER_ACTIVE);
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
+static int tas2770_set_slot(struct snd_soc_component *component, int slot_width) +{
- int ret;
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- switch (slot_width) {
- case 16:
- ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG2,
TAS2770_TDM_CFG_REG2_RXS_MASK,
TAS2770_TDM_CFG_REG2_RXS_16BITS);
- break;
- case 24:
- ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG2,
TAS2770_TDM_CFG_REG2_RXS_MASK,
TAS2770_TDM_CFG_REG2_RXS_24BITS);
- break;
- case 32:
- ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG2,
TAS2770_TDM_CFG_REG2_RXS_MASK,
TAS2770_TDM_CFG_REG2_RXS_32BITS);
- break;
- case 0:
- /* Do not change slot width */
- ret = 0;
- break;
- default:
ret = -EINVAL;
- }
- if (!ret)
p_tas2770->mn_slot_width = slot_width;
- return ret;
+}
+static int tas2770_set_bitwidth(struct tas2770_priv *p_tas2770, int bitwidth) +{
- int ret;
- struct snd_soc_component *component = p_tas2770->component;
- switch (bitwidth) {
- case SNDRV_PCM_FORMAT_S16_LE:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG2,
TAS2770_TDM_CFG_REG2_RXW_MASK,
TAS2770_TDM_CFG_REG2_RXW_16BITS);
/* If machine driver did not call set slot width */
if (p_tas2770->mn_slot_width == 0)
tas2770_set_slot(component, TAS2770_SLOT_16BIT);
p_tas2770->mn_v_mon_slot_no = p_tas2770->mn_i_mon_slot_no + 2;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG2,
TAS2770_TDM_CFG_REG2_RXW_MASK,
TAS2770_TDM_CFG_REG2_RXW_24BITS);
if (p_tas2770->mn_slot_width == 0)
tas2770_set_slot(component, TAS2770_SLOT_32BIT);
p_tas2770->mn_v_mon_slot_no = p_tas2770->mn_i_mon_slot_no + 4;
break;
- case SNDRV_PCM_FORMAT_S32_LE:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG2,
TAS2770_TDM_CFG_REG2_RXW_MASK,
TAS2770_TDM_CFG_REG2_RXW_32BITS);
if (p_tas2770->mn_slot_width == 0)
tas2770_set_slot(component, TAS2770_SLOT_32BIT);
p_tas2770->mn_v_mon_slot_no = p_tas2770->mn_i_mon_slot_no + 4;
break;
- default:
return -EINVAL;
- }
- p_tas2770->mn_ch_size = bitwidth;
- ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG5,
TAS2770_TDM_CFG_REG5_VSNS_MASK |
TAS2770_TDM_CFG_REG5_50_MASK,
TAS2770_TDM_CFG_REG5_VSNS_ENABLE |
p_tas2770->mn_v_mon_slot_no);
- if (ret)
goto end;
- ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG6,
TAS2770_TDM_CFG_REG6_ISNS_MASK |
TAS2770_TDM_CFG_REG6_50_MASK,
TAS2770_TDM_CFG_REG6_ISNS_ENABLE |
p_tas2770->mn_i_mon_slot_no);
+end:
- return ret;
+}
+static int tas2770_set_samplerate(struct tas2770_priv *p_tas2770,
int samplerate)
+{
- int ret;
- struct snd_soc_component *component = p_tas2770->component;
- switch (samplerate) {
- case 48000:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_SMP_MASK,
TAS2770_TDM_CFG_REG0_SMP_48KHZ);
if (ret)
goto end;
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_31_MASK,
TAS2770_TDM_CFG_REG0_31_44_1_48KHZ);
if (ret)
goto end;
break;
- case 44100:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_SMP_MASK,
TAS2770_TDM_CFG_REG0_SMP_44_1KHZ);
if (ret)
goto end;
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_31_MASK,
TAS2770_TDM_CFG_REG0_31_44_1_48KHZ);
if (ret)
goto end;
break;
- case 96000:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_SMP_MASK,
TAS2770_TDM_CFG_REG0_SMP_48KHZ);
if (ret)
goto end;
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_31_MASK,
TAS2770_TDM_CFG_REG0_31_88_2_96KHZ);
break;
- case 88200:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_SMP_MASK,
TAS2770_TDM_CFG_REG0_SMP_44_1KHZ);
if (ret)
goto end;
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_31_MASK,
TAS2770_TDM_CFG_REG0_31_88_2_96KHZ);
break;
- case 19200:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_SMP_MASK,
TAS2770_TDM_CFG_REG0_SMP_48KHZ);
if (ret)
goto end;
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_31_MASK,
TAS2770_TDM_CFG_REG0_31_176_4_192KHZ);
if (ret)
goto end;
break;
- case 17640:
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_SMP_MASK,
TAS2770_TDM_CFG_REG0_SMP_44_1KHZ);
if (ret)
goto end;
ret = snd_soc_component_update_bits(component,
TAS2770_TDM_CFG_REG0,
TAS2770_TDM_CFG_REG0_31_MASK,
TAS2770_TDM_CFG_REG0_31_176_4_192KHZ);
break;
- default:
ret = -EINVAL;
- }
+end:
- if (!ret)
p_tas2770->mn_sampling_rate = samplerate;
- return ret;
+}
+static int tas2770_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 tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- int ret;
- mutex_lock(&p_tas2770->codec_lock);
- ret = tas2770_set_bitwidth(p_tas2770, params_format(params));
- if (ret < 0)
goto end;
- ret = tas2770_set_samplerate(p_tas2770, params_rate(params));
+end:
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
+static int tas2770_set_fmt(struct tas2770_priv *p_tas2770, unsigned int fmt) +{
- u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0;
- int ret;
- int value = 0;
- struct snd_soc_component *component = p_tas2770->component;
- switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
break;
- default:
dev_err(p_tas2770->dev, "ASI format master is not found\n");
ret = -EINVAL;
return ret;
- }
- switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
- case SND_SOC_DAIFMT_NB_NF:
asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_RSING;
break;
- case SND_SOC_DAIFMT_IB_NF:
asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_FALING;
break;
- default:
dev_err(p_tas2770->dev, "ASI format Inverse is not found\n");
ret = -EINVAL;
return ret;
- }
- ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG1,
TAS2770_TDM_CFG_REG1_RX_MASK,
asi_cfg_1);
- if (ret)
return ret;
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case (SND_SOC_DAIFMT_I2S):
tdm_rx_start_slot = 1;
break;
- case (SND_SOC_DAIFMT_DSP_A):
- case (SND_SOC_DAIFMT_DSP_B):
tdm_rx_start_slot = 1;
break;
- case (SND_SOC_DAIFMT_LEFT_J):
tdm_rx_start_slot = 0;
break;
- default:
dev_err(p_tas2770->dev,
"DAI Format is not found, fmt=0x%x\n", fmt);
ret = -EINVAL;
return ret;
- }
- ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG1,
TAS2770_TDM_CFG_REG1_MASK,
- (tdm_rx_start_slot << TAS2770_TDM_CFG_REG1_51_SHIFT));
- if (ret)
return ret;
- ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG3,
TAS2770_TDM_CFG_REG3_30_MASK,
(p_tas2770->mn_left_slot <<
TAS2770_TDM_CFG_REG3_30_SHIFT));
- if (ret)
return ret;
- ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG3,
TAS2770_TDM_CFG_REG3_RXS_MASK,
- (p_tas2770->mn_right_slot <<
TAS2770_TDM_CFG_REG3_RXS_SHIFT));
- if (ret)
return ret;
- value = snd_soc_component_read32(component, TAS2770_TDM_CFG_REG3);
- p_tas2770->mn_asi_format = fmt;
- return 0;
+}
+static int tas2770_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{
- struct snd_soc_component *component = dai->component;
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- int ret;
- mutex_lock(&p_tas2770->codec_lock);
- ret = tas2770_set_fmt(p_tas2770, fmt);
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
+static int tas2770_set_dai_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask,
unsigned int rx_mask,
int slots, int slot_width)
+{
- int ret;
- struct snd_soc_component *component = dai->component;
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- mutex_lock(&p_tas2770->codec_lock);
- ret = tas2770_set_slot(component, slot_width);
- mutex_unlock(&p_tas2770->codec_lock);
- return ret;
+}
+static struct snd_soc_dai_ops tas2770_dai_ops = {
- .digital_mute = tas2770_mute,
- .hw_params = tas2770_hw_params,
- .set_fmt = tas2770_set_dai_fmt,
- .set_tdm_slot = tas2770_set_dai_tdm_slot,
+};
+#define TAS2770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+#define TAS2770_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
SNDRV_PCM_RATE_96000 |\
SNDRV_PCM_RATE_192000\
)
+static struct snd_soc_dai_driver tas2770_dai_driver[] = {
- {
.name = "tas2770 ASI1",
.id = 0,
.playback = {
.stream_name = "ASI1 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = TAS2770_RATES,
.formats = TAS2770_FORMATS,
},
.capture = {
.stream_name = "ASI1 Capture",
.channels_min = 0,
.channels_max = 2,
.rates = TAS2770_RATES,
.formats = TAS2770_FORMATS,
},
.ops = &tas2770_dai_ops,
.symmetric_rates = 1,
- },
+};
+static int tas2770_codec_probe(struct snd_soc_component *component) +{
- struct tas2770_priv *p_tas2770 =
snd_soc_component_get_drvdata(component);
- p_tas2770->component = component;
- return 0;
+}
+static void tas2770_codec_remove(struct snd_soc_component *component) +{
- pm_runtime_put(component->dev);
+}
+static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0); +static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -12750, 50, 0);
+static const struct snd_kcontrol_new tas2770_snd_controls[] = {
- SOC_SINGLE_TLV("Amp Output Level", TAS2770_PLAY_CFG_REG0,
0, 0x14, 0,
tas2770_digital_tlv),
- SOC_SINGLE_TLV("Playback Volume", TAS2770_PLAY_CFG_REG2,
0, TAS2770_PLAY_CFG_REG2_VMAX, 1,
tas2770_playback_volume),
+};
+static const struct snd_soc_component_driver soc_component_driver_tas2770 = {
- .probe = tas2770_codec_probe,
- .remove = tas2770_codec_remove,
- .suspend = tas2770_codec_suspend,
- .resume = tas2770_codec_resume,
- .controls = tas2770_snd_controls,
- .num_controls = ARRAY_SIZE(tas2770_snd_controls),
- .dapm_widgets = tas2770_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(tas2770_dapm_widgets),
- .dapm_routes = tas2770_audio_map,
- .num_dapm_routes = ARRAY_SIZE(tas2770_audio_map),
- .idle_bias_on = 1,
- .endianness = 1,
- .non_legacy_dai_naming = 1,
+};
+static int tas2770_register_codec(struct tas2770_priv *p_tas2770) +{
- return devm_snd_soc_register_component(p_tas2770->dev,
&soc_component_driver_tas2770,
tas2770_dai_driver, ARRAY_SIZE(tas2770_dai_driver));
+}
+static void tas2770_load_config(struct tas2770_priv *p_tas2770) +{
- int ret;
- tas2770_hw_reset(p_tas2770);
- tas2770_regmap_write(p_tas2770, TAS2770_SW_RST,
TAS2770_RST);
- ret = tas2770_set_slot(p_tas2770->component, p_tas2770->mn_slot_width);
- if (ret)
goto error_out;
- ret = tas2770_set_fmt(p_tas2770, p_tas2770->mn_asi_format);
- if (ret)
goto error_out;
- ret = tas2770_set_bitwidth(p_tas2770, p_tas2770->mn_ch_size);
- if (ret)
goto error_out;
- ret = tas2770_set_samplerate(p_tas2770, p_tas2770->mn_sampling_rate);
- if (ret)
goto error_out;
- ret = tas2770_set_power_state(p_tas2770, p_tas2770->mn_power_state);
- if (ret)
goto error_out;
+error_out:
return;
+}
+static const struct reg_default tas2770_reg_defaults[] = {
- { TAS2770_PAGE, 0x00 },
- { TAS2770_SW_RST, 0x00 },
- { TAS2770_PWR_CTRL, 0x0e },
- { TAS2770_PLAY_CFG_REG0, 0x10 },
- { TAS2770_PLAY_CFG_REG1, 0x01 },
- { TAS2770_PLAY_CFG_REG2, 0x00 },
- { TAS2770_MSC_CFG_REG0, 0x07 },
- { TAS2770_TDM_CFG_REG1, 0x02 },
- { TAS2770_TDM_CFG_REG2, 0x0a },
- { TAS2770_TDM_CFG_REG3, 0x10 },
- { TAS2770_INT_MASK_REG0, 0xfc },
- { TAS2770_INT_MASK_REG1, 0xb1 },
- { TAS2770_INT_CFG, 0x05 },
- { TAS2770_MISC_IRQ, 0x81 },
- { TAS2770_CLK_CGF, 0x0c },
+};
+static bool tas2770_volatile(struct device *dev, unsigned int reg) +{
- switch (reg) {
- case TAS2770_PAGE: /* regmap implementation requires this */
- case TAS2770_SW_RST: /* always clears after write */
- case TAS2770_BO_PRV_REG0:/* has a self clearing bit */
- case TAS2770_LVE_INT_REG0:
- case TAS2770_LVE_INT_REG1:
- case TAS2770_LAT_INT_REG0:/* Sticky interrupt flags */
- case TAS2770_LAT_INT_REG1:/* Sticky interrupt flags */
- case TAS2770_VBAT_MSB:
- case TAS2770_VBAT_LSB:
- case TAS2770_TEMP_MSB:
- case TAS2770_TEMP_LSB:
return true;
- }
- return false;
+}
+static bool tas2770_writeable(struct device *dev, unsigned int reg) +{
- switch (reg) {
- case TAS2770_LVE_INT_REG0:
- case TAS2770_LVE_INT_REG1:
- case TAS2770_LAT_INT_REG0:
- case TAS2770_LAT_INT_REG1:
- case TAS2770_VBAT_MSB:
- case TAS2770_VBAT_LSB:
- case TAS2770_TEMP_MSB:
- case TAS2770_TEMP_LSB:
- case TAS2770_TDM_CLK_DETC:
- case TAS2770_REV_AND_GPID:
return false;
- }
- return true;
+}
+static const struct regmap_range_cfg tas2770_regmap_ranges[] = {
- {
.range_min = 0,
.range_max = 1 * 128,
.selector_reg = TAS2770_PAGE,
.selector_mask = 0xff,
.selector_shift = 0,
.window_start = 0,
.window_len = 128,
- },
+};
+static const struct regmap_config tas2770_i2c_regmap = {
- .reg_bits = 8,
- .val_bits = 8,
- .writeable_reg = tas2770_writeable,
- .volatile_reg = tas2770_volatile,
- .reg_defaults = tas2770_reg_defaults,
- .num_reg_defaults = ARRAY_SIZE(tas2770_reg_defaults),
- .cache_type = REGCACHE_RBTREE,
- .ranges = tas2770_regmap_ranges,
- .num_ranges = ARRAY_SIZE(tas2770_regmap_ranges),
- .max_register = 1 * 128,
+};
+static irqreturn_t tas2770_irq_handler(int irq, void *dev_id) +{
- struct tas2770_priv *p_tas2770 = (struct tas2770_priv *)dev_id;
- unsigned int nDevInt1Status = 0, nDevInt2Status = 0;
- int n_counter = 2;
- int n_result;
- mutex_lock(&p_tas2770->codec_lock);
- tas2770_enable_irq(p_tas2770, false);
- if (p_tas2770->mb_runtime_suspend)
goto end;
- if (p_tas2770->mn_power_state == TAS2770_POWER_SHUTDOWN)
goto end;
- n_result = tas2770_regmap_write(p_tas2770, TAS2770_INT_MASK_REG0,
TAS2770_INT_MASK_REG0_DISABLE);
- if (n_result)
goto reload;
- n_result = tas2770_regmap_write(p_tas2770, TAS2770_INT_MASK_REG1,
TAS2770_INT_MASK_REG1_DISABLE);
- if (n_result)
goto reload;
- n_result = tas2770_regmap_read(p_tas2770,
TAS2770_LAT_INT_REG0, &nDevInt1Status);
- if (n_result >= 0)
n_result = tas2770_regmap_read(p_tas2770,
TAS2770_LAT_INT_REG1, &nDevInt2Status);
- else
goto reload;
- if (((nDevInt1Status & 0x3) != 0) || ((nDevInt2Status & 0x0f) != 0)) {
magic numbers need #defines
/* in case of INT_OC, INT_OT, INT_OVLT, INT_UVLT, INT_BO */
if (nDevInt1Status &
TAS2770_LAT_INT_REG0_OCE_FLG) {
p_tas2770->mn_err_code |= ERROR_OVER_CURRENT;
dev_err(p_tas2770->dev, "SPK over current!\n");
} else {
p_tas2770->mn_err_code &= ~ERROR_OVER_CURRENT;
}
if (nDevInt1Status &
TAS2770_LAT_INT_REG0_OTE_FLG) {
p_tas2770->mn_err_code |= ERROR_DIE_OVERTEMP;
dev_err(p_tas2770->dev, "die over temperature!\n");
} else {
p_tas2770->mn_err_code &= ~ERROR_DIE_OVERTEMP;
}
if (nDevInt2Status &
TAS2770_LAT_INT_REG1_VBA_TOV) {
p_tas2770->mn_err_code |= ERROR_OVER_VOLTAGE;
dev_err(p_tas2770->dev, "SPK over voltage!\n");
} else {
p_tas2770->mn_err_code &= ~ERROR_OVER_VOLTAGE;
}
if (nDevInt2Status &
TAS2770_LAT_INT_REG1_VBA_TUV) {
p_tas2770->mn_err_code |= ERROR_UNDER_VOLTAGE;
dev_err(p_tas2770->dev, "SPK under voltage!\n");
} else {
p_tas2770->mn_err_code &= ~ERROR_UNDER_VOLTAGE;
}
if (nDevInt2Status &
TAS2770_LAT_INT_REG1_BOUT_FLG) {
p_tas2770->mn_err_code |= ERROR_BROWNOUT;
dev_err(p_tas2770->dev, "brownout!\n");
} else {
p_tas2770->mn_err_code &= ~ERROR_BROWNOUT;
}
goto reload;
- } else {
n_counter = 2;
while (n_counter > 0) {
n_result = tas2770_regmap_read(p_tas2770,
TAS2770_PWR_CTRL, &nDevInt1Status);
if (n_result)
goto reload;
if ((nDevInt1Status &
TAS2770_PWR_CTRL_MASK)
!= TAS2770_PWR_CTRL_SHUTDOWN)
break;
n_counter--;
if (n_counter > 0)
msleep(20);
}
if ((nDevInt1Status &
TAS2770_PWR_CTRL_MASK)
== TAS2770_PWR_CTRL_SHUTDOWN) {
dev_err(p_tas2770->dev, "%s, Critical ERROR REG[0x%x] = 0x%x\n",
__func__,
TAS2770_PWR_CTRL,
nDevInt1Status);
The formatting here is a bit funky this looks like most can honor the 80 LoC guidance
p_tas2770->mn_err_code |= ERROR_CLASSD_PWR;
goto reload;
}
p_tas2770->mn_err_code &= ~ERROR_CLASSD_PWR;
- }
- n_result = tas2770_regmap_write(p_tas2770,
TAS2770_INT_MASK_REG0, 0xfc);
Magic number? What is 0xfc do?
- if (n_result)
goto reload;
- n_result = tas2770_regmap_write(p_tas2770,
TAS2770_INT_MASK_REG1, 0xb1);
Magic number? What is 0xb1 do?
- if (n_result)
goto reload;
- goto end;
+reload:
- /* hardware reset and reload */
- tas2770_load_config(p_tas2770);
+end:
- mutex_unlock(&p_tas2770->codec_lock);
- tas2770_enable_irq(p_tas2770, true);
- return IRQ_HANDLED;
+}
+static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *p_tas2770) +{
- int rc = 0;
- rc = fwnode_property_read_u32(dev->fwnode, "ti,asi-format",
&p_tas2770->mn_asi_format);
- if (rc) {
dev_err(p_tas2770->dev, "Looking up %s property failed %d\n",
"ti,asi-format", rc);
goto end;
- }
- rc = fwnode_property_read_u32(dev->fwnode, "ti,left-slot",
&p_tas2770->mn_left_slot);
- if (rc) {
dev_err(p_tas2770->dev, "Looking up %s property failed %d\n",
"ti,left-slot", rc);
goto end;
- }
- rc = fwnode_property_read_u32(dev->fwnode, "ti,right-slot",
&p_tas2770->mn_right_slot);
- if (rc) {
dev_err(p_tas2770->dev, "Looking up %s property failed %d\n",
"ti,right-slot", rc);
goto end;
- }
- rc = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no",
&p_tas2770->mn_i_mon_slot_no);
- if (rc) {
dev_err(p_tas2770->dev, "Looking up %s property failed %d\n",
"ti,imon-slot-no", rc);
goto end;
- }
- rc = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no",
&p_tas2770->mn_v_mon_slot_no);
- if (rc) {
dev_err(p_tas2770->dev, "Looking up %s property failed %d\n",
"ti,vmon-slot-no", rc);
goto end;
- }
+end:
- return rc;
+}
+static int tas2770_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
+{
- struct tas2770_priv *p_tas2770;
- int n_result;
- p_tas2770 = devm_kzalloc(&client->dev,
sizeof(struct tas2770_priv), GFP_KERNEL);
- if (p_tas2770 == NULL)
if (!p_tas2770)
return -ENOMEM;
- p_tas2770->dev = &client->dev;
- p_tas2770->mn_irq = client->irq;
- i2c_set_clientdata(client, p_tas2770);
- dev_set_drvdata(&client->dev, p_tas2770);
- p_tas2770->mn_power_state = TAS2770_POWER_SHUTDOWN;
- p_tas2770->regmap = devm_regmap_init_i2c(client, &tas2770_i2c_regmap);
- if (IS_ERR(p_tas2770->regmap)) {
n_result = PTR_ERR(p_tas2770->regmap);
dev_err(&client->dev, "Failed to allocate register map: %d\n",
n_result);
goto end;
- }
- if (client->dev.of_node) {
n_result = tas2770_parse_dt(&client->dev, p_tas2770);
if (n_result) {
dev_err(p_tas2770->dev, "%s: Failed to parse devicetree\n",
__func__);
goto end;
}
- }
- p_tas2770->mn_reset_gpio = devm_gpiod_get_optional(p_tas2770->dev,
"reset-gpio",
GPIOD_OUT_HIGH);
if (IS_ERR(p_tas2770->mn_reset_gpio)) {
if (PTR_ERR(p_tas2770->mn_reset_gpio) == -EPROBE_DEFER)
return -EPROBE_DEFER;
p_tas2770->mn_reset_gpio = NULL;
}
Formatting issue here
- if (p_tas2770->mn_irq > 0) {
n_result = devm_request_threaded_irq(p_tas2770->dev,
p_tas2770->mn_irq,
NULL, tas2770_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name,
p_tas2770);
if (n_result) {
dev_err(p_tas2770->dev, "Unable to request IRQ\n");
return n_result;
}
- }
- p_tas2770->mn_ch_size = 0;
- p_tas2770->mn_slot_width = 0;
- tas2770_hw_reset(p_tas2770);
- tas2770_regmap_write(p_tas2770, TAS2770_SW_RST,
TAS2770_RST);
Why a hw_reset then a sw reset? Shouldn't this be in a function called reset that either does HW or SW reset?
- mutex_init(&p_tas2770->dev_lock);
- mutex_init(&p_tas2770->codec_lock);
- n_result = tas2770_register_codec(p_tas2770);
- if (n_result)
dev_err(p_tas2770->dev, "Register codec failed.\n");
+end:
- return n_result;
+}
+static int tas2770_i2c_remove(struct i2c_client *client) +{
- struct tas2770_priv *p_tas2770 = i2c_get_clientdata(client);
- mutex_destroy(&p_tas2770->dev_lock);
mutex_destroy(codec_lock)
- return 0;
+}
+static const struct i2c_device_id tas2770_i2c_id[] = {
- { "tas2770", 0},
- { }
+}; +MODULE_DEVICE_TABLE(i2c, tas2770_i2c_id);
+#if defined(CONFIG_OF) +static const struct of_device_id tas2770_of_match[] = {
- { .compatible = "ti,tas2770" },
- {},
+}; +MODULE_DEVICE_TABLE(of, tas2770_of_match); +#endif
+static struct i2c_driver tas2770_i2c_driver = {
- .driver = {
.name = "tas2770",
.owner = THIS_MODULE,
+#if defined(CONFIG_OF)
.of_match_table = of_match_ptr(tas2770_of_match),
+#endif
- },
- .probe = tas2770_i2c_probe,
- .remove = tas2770_i2c_remove,
- .id_table = tas2770_i2c_id,
+};
+module_i2c_driver(tas2770_i2c_driver);
+MODULE_AUTHOR("Shi Fu shifu0704@thundersoft.com"); +MODULE_DESCRIPTION("TAS2770 I2C Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h new file mode 100644 index 0000000..f08e208 --- /dev/null +++ b/sound/soc/codecs/tas2770.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0
- ALSA SoC TAS2770 codec driver
- Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/
- */
+#ifndef __TAS2770__ +#define __TAS2770__
+/* Book Control Register (available in page0 of each book) */ +#define TAS2770_BOOKCTL_PAGE 0 +#define TAS2770_BOOKCTL_REG 127 +#define TAS2770_REG(page, reg) ((page * 128) + reg)
- /* Page */
+#define TAS2770_PAGE TAS2770_REG(0X0, 0x00) +#define TAS2770_PAGE_PAGE_MASK 255
- /* Software Reset */
+#define TAS2770_SW_RST TAS2770_REG(0X0, 0x01) +#define TAS2770_RST BIT(0)
- /* Power Control */
+#define TAS2770_PWR_CTRL TAS2770_REG(0X0, 0x02) +#define TAS2770_PWR_CTRL_MASK 0x3 +#define TAS2770_PWR_CTRL_ACTIVE 0x0 +#define TAS2770_PWR_CTRL_MUTE BIT(0) +#define TAS2770_PWR_CTRL_SHUTDOWN 0x2
- /* Playback Configuration Reg0 */
+#define TAS2770_PLAY_CFG_REG0 TAS2770_REG(0X0, 0x03)
- /* Playback Configuration Reg1 */
+#define TAS2770_PLAY_CFG_REG1 TAS2770_REG(0X0, 0x04)
- /* Playback Configuration Reg2 */
+#define TAS2770_PLAY_CFG_REG2 TAS2770_REG(0X0, 0x05) +#define TAS2770_PLAY_CFG_REG2_VMAX 0xc9
- /* Misc Configuration Reg0 */
+#define TAS2770_MSC_CFG_REG0 TAS2770_REG(0X0, 0x07)
- /* TDM Configuration Reg0 */
+#define TAS2770_TDM_CFG_REG0 TAS2770_REG(0X0, 0x0A) +#define TAS2770_TDM_CFG_REG0_SMP_MASK BIT(5) +#define TAS2770_TDM_CFG_REG0_SMP_48KHZ 0x0 +#define TAS2770_TDM_CFG_REG0_SMP_44_1KHZ BIT(5) +#define TAS2770_TDM_CFG_REG0_31_MASK 0xe +#define TAS2770_TDM_CFG_REG0_31_44_1_48KHZ 0x6 +#define TAS2770_TDM_CFG_REG0_31_88_2_96KHZ 0x8 +#define TAS2770_TDM_CFG_REG0_31_176_4_192KHZ 0xa
- /* TDM Configuration Reg1 */
+#define TAS2770_TDM_CFG_REG1 TAS2770_REG(0X0, 0x0B) +#define TAS2770_TDM_CFG_REG1_MASK 0x3e +#define TAS2770_TDM_CFG_REG1_51_SHIFT 1 +#define TAS2770_TDM_CFG_REG1_RX_MASK BIT(0) +#define TAS2770_TDM_CFG_REG1_RX_RSING 0x0 +#define TAS2770_TDM_CFG_REG1_RX_FALING BIT(0)
- /* TDM Configuration Reg2 */
+#define TAS2770_TDM_CFG_REG2 TAS2770_REG(0X0, 0x0C) +#define TAS2770_TDM_CFG_REG2_RXW_MASK 0xc +#define TAS2770_TDM_CFG_REG2_RXW_16BITS 0x0 +#define TAS2770_TDM_CFG_REG2_RXW_24BITS 0x8 +#define TAS2770_TDM_CFG_REG2_RXW_32BITS 0xc +#define TAS2770_TDM_CFG_REG2_RXS_MASK 0x3 +#define TAS2770_TDM_CFG_REG2_RXS_16BITS 0x0 +#define TAS2770_TDM_CFG_REG2_RXS_24BITS BIT(0) +#define TAS2770_TDM_CFG_REG2_RXS_32BITS 0x2
- /* TDM Configuration Reg3 */
+#define TAS2770_TDM_CFG_REG3 TAS2770_REG(0X0, 0x0D) +#define TAS2770_TDM_CFG_REG3_RXS_MASK 0xf0 +#define TAS2770_TDM_CFG_REG3_RXS_SHIFT 0x4 +#define TAS2770_TDM_CFG_REG3_30_MASK 0xf +#define TAS2770_TDM_CFG_REG3_30_SHIFT 0
- /* TDM Configuration Reg5 */
+#define TAS2770_TDM_CFG_REG5 TAS2770_REG(0X0, 0x0F) +#define TAS2770_TDM_CFG_REG5_VSNS_MASK BIT(6) +#define TAS2770_TDM_CFG_REG5_VSNS_ENABLE BIT(6) +#define TAS2770_TDM_CFG_REG5_50_MASK 0x3f
- /* TDM Configuration Reg6 */
+#define TAS2770_TDM_CFG_REG6 TAS2770_REG(0X0, 0x10) +#define TAS2770_TDM_CFG_REG6_ISNS_MASK BIT(6) +#define TAS2770_TDM_CFG_REG6_ISNS_ENABLE BIT(6) +#define TAS2770_TDM_CFG_REG6_50_MASK 0x3f
- /* Brown Out Prevention Reg0 */
+#define TAS2770_BO_PRV_REG0 TAS2770_REG(0X0, 0x1B)
- /* Interrupt MASK Reg0 */
+#define TAS2770_INT_MASK_REG0 TAS2770_REG(0X0, 0x20) +#define TAS2770_INT_MASK_REG0_DISABLE 0xff
- /* Interrupt MASK Reg1 */
+#define TAS2770_INT_MASK_REG1 TAS2770_REG(0X0, 0x21) +#define TAS2770_INT_MASK_REG1_DISABLE 0xff
- /* Live-Interrupt Reg0 */
+#define TAS2770_LVE_INT_REG0 TAS2770_REG(0X0, 0x22)
- /* Live-Interrupt Reg1 */
+#define TAS2770_LVE_INT_REG1 TAS2770_REG(0X0, 0x23)
- /* Latched-Interrupt Reg0 */
+#define TAS2770_LAT_INT_REG0 TAS2770_REG(0X0, 0x24) +#define TAS2770_LAT_INT_REG0_OCE_FLG BIT(1) +#define TAS2770_LAT_INT_REG0_OTE_FLG BIT(0)
- /* Latched-Interrupt Reg1 */
+#define TAS2770_LAT_INT_REG1 TAS2770_REG(0X0, 0x25) +#define TAS2770_LAT_INT_REG1_VBA_TOV BIT(3) +#define TAS2770_LAT_INT_REG1_VBA_TUV BIT(2) +#define TAS2770_LAT_INT_REG1_BOUT_FLG BIT(1)
- /* VBAT MSB */
+#define TAS2770_VBAT_MSB TAS2770_REG(0X0, 0x27)
- /* VBAT LSB */
+#define TAS2770_VBAT_LSB TAS2770_REG(0X0, 0x28)
- /* TEMP MSB */
+#define TAS2770_TEMP_MSB TAS2770_REG(0X0, 0x29)
- /* TEMP LSB */
+#define TAS2770_TEMP_LSB TAS2770_REG(0X0, 0x2A)
- /* Interrupt Configuration */
+#define TAS2770_INT_CFG TAS2770_REG(0X0, 0x30)
- /* Misc IRQ */
+#define TAS2770_MISC_IRQ TAS2770_REG(0X0, 0x32)
- /* Clock Configuration */
+#define TAS2770_CLK_CGF TAS2770_REG(0X0, 0x3C)
- /* TDM Clock detection monitor */
+#define TAS2770_TDM_CLK_DETC TAS2770_REG(0X0, 0x77)
- /* Revision and PG ID */
+#define TAS2770_REV_AND_GPID TAS2770_REG(0X0, 0x7D)
+#define TAS2770_POWER_ACTIVE 0 +#define TAS2770_POWER_MUTE 1 +#define TAS2770_POWER_SHUTDOWN 2 +#define ERROR_OVER_CURRENT 0x0000001 +#define ERROR_DIE_OVERTEMP 0x0000002 +#define ERROR_OVER_VOLTAGE 0x0000004 +#define ERROR_UNDER_VOLTAGE 0x0000008 +#define ERROR_BROWNOUT 0x0000010 +#define ERROR_CLASSD_PWR 0x0000020 +#define TAS2770_SLOT_16BIT 16 +#define TAS2770_SLOT_32BIT 32 +#define TAS2770_I2C_RETRY_COUNT 3 +#define ERROR_I2C_FAILED -2
+struct tas2770_register {
- int book;
- int page;
- int reg;
+};
+struct tas2770_dai_cfg {
- unsigned int dai_fmt;
- unsigned int tdm_delay;
+};
+struct tas2770_priv {
- struct device *dev;
- struct regmap *regmap;
- struct snd_soc_codec *codec;
- struct snd_soc_component *component;
- struct mutex dev_lock;
- struct hrtimer mtimer;
- int mn_clkin;
- int mn_clkid;
- int mn_power_state;
- int mn_current_book;
- int mn_current_page;
- int mn_load;
- int mn_asi_format;
- struct gpio_desc *mn_reset_gpio;
- int mn_irq;
- bool mb_irq_enable;
- int mn_sampling_rate;
- int mn_frame_size;
- int mn_pll;
- int mn_left_slot;
- int mn_right_slot;
- int mn_ch_size;
- int mn_slot_width;
- int mn_v_mon_slot_no;
- int mn_i_mon_slot_no;
- bool mb_runtime_suspend;
- unsigned int mn_err_code;
- struct mutex codec_lock;
+};
+#endif /* __TAS2770__ */
On Fri, Sep 06, 2019 at 03:06:03PM +0800, shifu0704@thundersoft.com wrote:
- ti,left-slot: - Sets TDM RX left time slots.
- ti,right-slot: - Sets TDM RX right time slots.
This looks like it's duplicating things that are normally done with the set_tdm_slot() callback. Otherwise the binding looks good.
Shi
On 9/6/19 2:06 AM, shifu0704@thundersoft.com wrote:
From: Frank Shi shifu0704@thundersoft.com
Subject should be
dt-bindings: ASoC: Add tas2770 smart PA dt bindings
Also Please add Rob Herring robh+dt@kernel.org for review
add tas2770 smart PA dt bindings
Signed-off-by: Frank Shi shifu0704@thundersoft.com
Documentation/devicetree/bindings/tas2770.txt | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/tas2770.txt
diff --git a/Documentation/devicetree/bindings/tas2770.txt b/Documentation/devicetree/bindings/tas2770.txt new file mode 100644 index 0000000..f70b310 --- /dev/null +++ b/Documentation/devicetree/bindings/tas2770.txt @@ -0,0 +1,38 @@ +Texas Instruments TAS2770 Smart PA
+The TAS2770 is a mono, digital input Class-D audio amplifier optimized for +efficiently driving high peak power into small loudspeakers. +Integrated speaker voltage and current sense provides for +real time monitoring of loudspeaker behavior.
+Required properties:
- compatible: - Should contain "ti,tas2770".
- reg: - The i2c address. Should contain <0x4c>, <0x4d>,<0x4e>, or <0x4f>.
s/should/may
- #address-cells - Should be <1>.
- #size-cells - Should be <0>.
- ti,asi-format: - Sets TDM RX capture edge. 0->Rising; 1->Falling.
- ti,left-slot: - Sets TDM RX left time slots.
- ti,right-slot: - Sets TDM RX right time slots.
- ti,imon-slot-no:- TDM TX current sense time slot.
- ti,vmon-slot-no:- TDM TX voltage sense time slot.
+Optional properties:
- reset-gpio: Reset GPIO number of left device.
- irq-gpio: IRQ GPIO number of left device.
You might want to use
- interrupt-parent: the phandle to the interrupt controller which provides the interrupt. - interrupts: interrupt specification for data-ready.
Instead of irq-gpio
+Examples:
- tas2770@4c {
compatible = "ti,tas2770";
reg = <0x4c>;
Missing
#address-cells = <1>;
#size-cells = <0>;
reset-gpio = <&gpio15 1 GPIO_ACTIVE_LOW>;
irq-gpio = <&gpio16 1 GPIO_ACTIVE_LOW>;
ti,asi-format = <0>;
ti,left-slot = <0>;
ti,right-slot = <1>;
ti,imon-slot-no = <0>;
ti,vmon-slot-no = <2>;
};
Suggestion to provide the URL to the data sheet.
Shi
One other thing
On 9/10/19 8:21 AM, Dan Murphy wrote:
Shi
On 9/6/19 2:06 AM, shifu0704@thundersoft.com wrote:
From: Frank Shi shifu0704@thundersoft.com
Subject should be
dt-bindings: ASoC: Add tas2770 smart PA dt bindings
Also Please add Rob Herring robh+dt@kernel.org for review
add tas2770 smart PA dt bindings
Signed-off-by: Frank Shi shifu0704@thundersoft.com
Documentation/devicetree/bindings/tas2770.txt | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/tas2770.txt
diff --git a/Documentation/devicetree/bindings/tas2770.txt b/Documentation/devicetree/bindings/tas2770.txt
This binding belongs in Documentation/devicetree/bindings/sound
new file mode 100644 index 0000000..f70b310 --- /dev/null +++ b/Documentation/devicetree/bindings/tas2770.txt @@ -0,0 +1,38 @@ +Texas Instruments TAS2770 Smart PA
+The TAS2770 is a mono, digital input Class-D audio amplifier optimized for +efficiently driving high peak power into small loudspeakers. +Integrated speaker voltage and current sense provides for +real time monitoring of loudspeaker behavior.
+Required properties:
- compatible: - Should contain "ti,tas2770".
- reg: - The i2c address. Should contain <0x4c>,
<0x4d>,<0x4e>, or <0x4f>.
s/should/may
- #address-cells - Should be <1>.
- #size-cells - Should be <0>.
- ti,asi-format: - Sets TDM RX capture edge. 0->Rising; 1->Falling.
- ti,left-slot: - Sets TDM RX left time slots.
- ti,right-slot: - Sets TDM RX right time slots.
- ti,imon-slot-no:- TDM TX current sense time slot.
- ti,vmon-slot-no:- TDM TX voltage sense time slot.
+Optional properties:
- reset-gpio: Reset GPIO number of left device.
- irq-gpio: IRQ GPIO number of left device.
You might want to use
- interrupt-parent: the phandle to the interrupt controller which
provides the interrupt.
- interrupts: interrupt specification for data-ready.
Instead of irq-gpio
+Examples:
+ tas2770@4c { + compatible = "ti,tas2770"; + reg = <0x4c>;
Missing
#address-cells = <1>;
#size-cells = <0>;
+ reset-gpio = <&gpio15 1 GPIO_ACTIVE_LOW>; + irq-gpio = <&gpio16 1 GPIO_ACTIVE_LOW>; + ti,asi-format = <0>; + ti,left-slot = <0>; + ti,right-slot = <1>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + };
Suggestion to provide the URL to the data sheet.
On Tue, Sep 10, 2019 at 08:21:25AM -0500, Dan Murphy wrote:
Shi
On 9/6/19 2:06 AM, shifu0704@thundersoft.com wrote:
From: Frank Shi shifu0704@thundersoft.com
Subject should be
dt-bindings: ASoC: Add tas2770 smart PA dt bindings
I do find it easier to use "ASoC: dt-bindings:", helps avoid things being missed.
participants (3)
-
Dan Murphy
-
Mark Brown
-
shifu0704@thundersoft.com