This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TLV320ADC3140: NVIDIA Jetson (kernel 4.9) driver for TLV320ADCx140

Part Number: TLV320ADC3140
Other Parts Discussed in Thread: TLV320ADC5140

Hi,

We are developing a custom carrier for the NVIDIA TX2 which will include a 3140 in TDM mode. I note that the ALSA driver TI have produced is written for the 5.0 series kernel, but the latest JetPack release only supports the 4.9 kernel. I have gone through the driver and modified various ALSA calls so that the driver now compiles for the TX2. When the TX2 boots, I see that adcx140_i2c_probe() executes without error, but I have not managed to register the driver yet. I am wondering if any driver experts there (and particularly any who might be familiar with the Jetson or earlier kernel) could take a look at the diff I've attahced to see what I might have broken in the process of porting the tlv320adcx140 driver to the 4.9 kernel. We would be happy to make the new code available to open up the Jetson market if we can get it working.

Cheers,

David.

diff --git a/./orig/tlv320adcx140.c b/tlv320adcx140.c
index 714fc63..b6b34be 100644
--- a/./orig/tlv320adcx140.c
+++ b/tlv320adcx140.c
@@ -24,7 +24,7 @@
 #include "tlv320adcx140.h"
 
 struct adcx140_priv {
-	struct snd_soc_component *component;
+	struct snd_soc_codec *codec;
 	struct regulator *supply_areg;
 	struct gpio_desc *gpio_reset;
 	struct regmap *regmap;
@@ -108,7 +108,7 @@ static const struct reg_default adcx140_reg_defaults[] = {
 	{ ADCX140_DSP_CFG0, 0x01 },
 	{ ADCX140_DSP_CFG1, 0x40 },
 	{ ADCX140_DRE_CFG0, 0x7b },
-	{ ADCX140_AGC_CFG0, 0xe7 ],
+	{ ADCX140_AGC_CFG0, 0xe7 },
 	{ ADCX140_IN_CH_EN, 0xf0 },
 	{ ADCX140_ASI_OUT_CH_EN, 0x00 },
 	{ ADCX140_PWR_CFG, 0x00 },
@@ -570,7 +570,7 @@ static int adcx140_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 snd_soc_codec *codec = dai->codec;
 	u8 data = 0;
 
 	switch (params_width(params)) {
@@ -587,12 +587,12 @@ static int adcx140_hw_params(struct snd_pcm_substream *substream,
 		data = ADCX140_32_BIT_WORD;
 		break;
 	default:
-		dev_err(component->dev, "%s: Unsupported width %d\n",
+		dev_err(codec->dev, "%s: Unsupported width %d\n",
 			__func__, params_width(params));
 		return -EINVAL;
 	}
 
-	snd_soc_component_update_bits(component, ADCX140_ASI_CFG0,
+	snd_soc_update_bits(codec, ADCX140_ASI_CFG0,
 			    ADCX140_WORD_LEN_MSK, data);
 
 	return 0;
@@ -601,8 +601,8 @@ static int adcx140_hw_params(struct snd_pcm_substream *substream,
 static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai,
 			       unsigned int fmt)
 {
-	struct snd_soc_component *component = codec_dai->component;
-	struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component);
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct adcx140_priv *adcx140 = snd_soc_codec_get_drvdata(codec);
 	u8 iface_reg1 = 0;
 	u8 iface_reg2 = 0;
 
@@ -616,7 +616,7 @@ static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai,
 	case SND_SOC_DAIFMT_CBS_CFM:
 	case SND_SOC_DAIFMT_CBM_CFS:
 	default:
-		dev_err(component->dev, "Invalid DAI master/slave interface\n");
+		dev_err(codec->dev, "Invalid DAI master/slave interface\n");
 		return -EINVAL;
 	}
 
@@ -634,7 +634,7 @@ static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai,
 	case SND_SOC_DAIFMT_NB_NF:
 		break;
 	default:
-		dev_err(component->dev, "Invalid DAI clock signal polarity\n");
+		dev_err(codec->dev, "Invalid DAI clock signal polarity\n");
 		return -EINVAL;
 	}
 
@@ -650,18 +650,18 @@ static int adcx140_set_dai_fmt(struct snd_soc_dai *codec_dai,
 	case SND_SOC_DAIFMT_DSP_B:
 		break;
 	default:
-		dev_err(component->dev, "Invalid DAI interface format\n");
+		dev_err(codec->dev, "Invalid DAI interface format\n");
 		return -EINVAL;
 	}
 
 	adcx140->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
 
-	snd_soc_component_update_bits(component, ADCX140_ASI_CFG0,
+	snd_soc_update_bits(codec, ADCX140_ASI_CFG0,
 				      ADCX140_FSYNCINV_BIT |
 				      ADCX140_BCLKINV_BIT |
 				      ADCX140_ASI_FORMAT_MSK,
 				      iface_reg1);
-	snd_soc_component_update_bits(component, ADCX140_MST_CFG0,
+	snd_soc_update_bits(codec, ADCX140_MST_CFG0,
 				      ADCX140_BCLK_FSYNC_MASTER, iface_reg2);
 
 	return 0;
@@ -671,19 +671,19 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai,
 				  unsigned int tx_mask, unsigned int rx_mask,
 				  int slots, int slot_width)
 {
-	struct snd_soc_component *component = codec_dai->component;
-	struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component);
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct adcx140_priv *adcx140 = snd_soc_codec_get_drvdata(codec);
 	unsigned int lsb;
 
 	if (tx_mask != rx_mask) {
-		dev_err(component->dev, "tx and rx masks must be symmetric\n");
+		dev_err(codec->dev, "tx and rx masks must be symmetric\n");
 		return -EINVAL;
 	}
 
 	/* TDM based on DSP mode requires slots to be adjacent */
 	lsb = __ffs(tx_mask);
 	if ((lsb + 1) != __fls(tx_mask)) {
-		dev_err(component->dev, "Invalid mask, slots must be adjacent\n");
+		dev_err(codec->dev, "Invalid mask, slots must be adjacent\n");
 		return -EINVAL;
 	}
 
@@ -694,7 +694,7 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai,
 	case 32:
 		break;
 	default:
-		dev_err(component->dev, "Unsupported slot width %d\n", slot_width);
+		dev_err(codec->dev, "Unsupported slot width %d\n", slot_width);
 		return -EINVAL;
 	}
 
@@ -707,8 +707,8 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai,
 static int adcx140_prepare(struct snd_pcm_substream *substream,
 			 struct snd_soc_dai *dai)
 {
-	struct snd_soc_component *component = dai->component;
-	struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component);
+	struct snd_soc_codec *codec = dai->codec;
+	struct adcx140_priv *adcx140 = snd_soc_codec_get_drvdata(codec);
 	int offset = 0;
 	int width = adcx140->slot_width;
 
@@ -722,7 +722,7 @@ static int adcx140_prepare(struct snd_pcm_substream *substream,
 		offset += adcx140->tdm_delay * width;
 
 	/* Configure data offset */
-	snd_soc_component_update_bits(component, ADCX140_ASI_CFG1,
+	snd_soc_update_bits(codec, ADCX140_ASI_CFG1,
 				      ADCX140_TX_OFFSET_MASK, offset);
 
 	return 0;
@@ -735,9 +735,9 @@ static const struct snd_soc_dai_ops adcx140_dai_ops = {
 	.set_tdm_slot	= adcx140_set_dai_tdm_slot,
 };
 
-static int adcx140_codec_probe(struct snd_soc_component *component)
+static int adcx140_codec_probe(struct snd_soc_codec *codec)
 {
-	struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component);
+	struct adcx140_priv *adcx140 = snd_soc_codec_get_drvdata(codec);
 	int sleep_cfg_val = ADCX140_WAKE_DEV;
 	u8 bias_source;
 	u8 vref_source;
@@ -748,9 +748,8 @@ static int adcx140_codec_probe(struct snd_soc_component *component)
 	if (ret)
 		bias_source = ADCX140_MIC_BIAS_VAL_VREF;
 
-	if (bias_source != ADCX140_MIC_BIAS_VAL_VREF &&
-	    bias_source != ADCX140_MIC_BIAS_VAL_VREF_1096 &&
-	    bias_source != ADCX140_MIC_BIAS_VAL_AVDD) {
+	if (bias_source < ADCX140_MIC_BIAS_VAL_VREF ||
+	    bias_source > ADCX140_MIC_BIAS_VAL_AVDD) {
 		dev_err(adcx140->dev, "Mic Bias source value is invalid\n");
 		return -EINVAL;
 	}
@@ -760,9 +759,8 @@ static int adcx140_codec_probe(struct snd_soc_component *component)
 	if (ret)
 		vref_source = ADCX140_MIC_BIAS_VREF_275V;
 
-	if (vref_source != ADCX140_MIC_BIAS_VREF_275V &&
-	    vref_source != ADCX140_MIC_BIAS_VREF_25V &&
-	    vref_source != ADCX140_MIC_BIAS_VREF_1375V) {
+	if (vref_source < ADCX140_MIC_BIAS_VREF_275V ||
+	    vref_source > ADCX140_MIC_BIAS_VREF_1375V) {
 		dev_err(adcx140->dev, "Mic Bias source value is invalid\n");
 		return -EINVAL;
 	}
@@ -794,10 +792,10 @@ out:
 	return ret;
 }
 
-static int adcx140_set_bias_level(struct snd_soc_component *component,
+static int adcx140_set_bias_level(struct snd_soc_codec *codec,
 				  enum snd_soc_bias_level level)
 {
-	struct adcx140_priv *adcx140 = snd_soc_component_get_drvdata(component);
+	struct adcx140_priv *adcx140 = snd_soc_codec_get_drvdata(codec);
 	int pwr_cfg = 0;
 
 	switch (level) {
@@ -815,20 +813,22 @@ static int adcx140_set_bias_level(struct snd_soc_component *component,
 	return regmap_write(adcx140->regmap, ADCX140_PWR_CFG, pwr_cfg);
 }
 
-static const struct snd_soc_component_driver soc_codec_driver_adcx140 = {
-	.probe			= adcx140_codec_probe,
-	.set_bias_level		= adcx140_set_bias_level,
-	.controls		= adcx140_snd_controls,
-	.num_controls		= ARRAY_SIZE(adcx140_snd_controls),
-	.dapm_widgets		= adcx140_dapm_widgets,
-	.num_dapm_widgets	= ARRAY_SIZE(adcx140_dapm_widgets),
-	.dapm_routes		= adcx140_audio_map,
-	.num_dapm_routes	= ARRAY_SIZE(adcx140_audio_map),
-	.suspend_bias_off	= 1,
-	.idle_bias_on		= 0,
-	.use_pmdown_time	= 1,
-	.endianness		= 1,
-	.non_legacy_dai_naming	= 1,
+static const struct snd_soc_codec_driver soc_codec_driver_adcx140 = {
+	.probe			    = adcx140_codec_probe,
+	.set_bias_level     = adcx140_set_bias_level,
+	.suspend_bias_off	= true,
+	.idle_bias_off		= true,
+	.ignore_pmdown_time	= false,
+/*	.endianness		    = true,*/
+/*	.non_legacy_dai_naming	= true,*/
+	.component_driver = {
+		.controls		    = adcx140_snd_controls,
+		.num_controls		= ARRAY_SIZE(adcx140_snd_controls),
+		.dapm_widgets		= adcx140_dapm_widgets,
+		.num_dapm_widgets	= ARRAY_SIZE(adcx140_dapm_widgets),
+		.dapm_routes		= adcx140_audio_map,
+		.num_dapm_routes	= ARRAY_SIZE(adcx140_audio_map),
+	},
 };
 
 static struct snd_soc_dai_driver adcx140_dai_driver[] = {
@@ -838,8 +838,8 @@ static struct snd_soc_dai_driver adcx140_dai_driver[] = {
 			.stream_name	 = "Capture",
 			.channels_min	 = 2,
 			.channels_max	 = ADCX140_MAX_CHANNELS,
-			.rates		 = ADCX140_RATES,
-			.formats	 = ADCX140_FORMATS,
+			.rates		     = ADCX140_RATES,
+			.formats	     = ADCX140_FORMATS,
 		},
 		.ops = &adcx140_dai_ops,
 		.symmetric_rates = 1,
@@ -894,7 +894,7 @@ static int adcx140_i2c_probe(struct i2c_client *i2c,
 	adcx140->dev = &i2c->dev;
 	i2c_set_clientdata(i2c, adcx140);
 
-	return devm_snd_soc_register_component(&i2c->dev,
+	return snd_soc_register_codec(&i2c->dev,
 					       &soc_codec_driver_adcx140,
 					       adcx140_dai_driver, 1);
 }

  • David,

    Looking at your changes, they seem OK. Let me check around with our Linux experts to see if they have any comments.

    Best regards,
    Pedro

  • David,

    Our groups are not very familiar with NVIDIA sound architecture since our experience is with TI processors.With TI processors, the codec is attached to the dai at the device tree level.  Where the TLV320ADCx140 is the node that configures the microphone per the device tree documentation

            simple-audio-card,codec {
                sound-dai = <&tlv320adcx140>;
            };

    You can find some documentation on our driver at:

    https://git.ti.com/gitweb?p=ti-analog-linux-kernel/dmurphy-analog.git;a=blob;f=Documentation/devicetree/bindings/sound/tlv320adcx140.yaml;h=1433ff62b14f0264ae5278d40a612da8a82ad718;hb=47d53a01e1835c56eec2f301b93156c494f3e068

    The device binding tree example for BeagleBone black:

    https://git.ti.com/gitweb?p=ti-analog-linux-kernel/dmurphy-analog.git;a=commit;h=3fc068a34a591d818467da60e06d777b8c7357a9

    Best regards,
    Pedro

  • Thanks Pedro,

    As I'm using a pretty standard ALSA system, I don't imagine my setup is too different from what you guys are used to. Starting at the beginning, I note that in the dt-bindings for the tlv320adcx140 Linux driver, the reset gpio is set thus:

        #include <dt-bindings/gpio/gpio.h>
        i2c0 {
          #address-cells = <1>;
          #size-cells = <0>;
          codec: codec@4c {
            compatible = "ti,tlv320adc5140";
            reg = <0x4c>;
            ti,use-internal-areg;
            ti,mic-bias-source = <6>;
            reset-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
          };
        };
    

    I have something similar, but expected the GPIO to be active low, which is what I need to drive SHDNz, right? In any case, I can see that adcx140_i2c_probe() is executing on system boot without error (apparently), but I'm not getting as far as adcx140_codec_probe(), which would be necessary to actually toggle the GPIO from low to high to get the codec out of shutdown. My understanding is that unless I can toggle SHDNz, the chip will ignore any i2c communications.

    Right now, dmesg just says:

    sound: ASoC: CODEC DAI tlv320adcx140 not registered

    sound: sound_soc_register_card_failed (-517)

    Have I misunderstood anything here? Have you got any pointers?

    Cheers,

    David.

  • David,

    The driver has a reset call to drive shutdown (SHDNz pin) high: adcx140_reset(). So this signal acts as a reset active high, and also shutdown active low.

    Best regards,
    Pedro

  • Thanks again Pedro,

    Have you any guidance on what my ALSA routing should look like? I'm just using four single-ended inputs, but the options in the driver look a bit daunting.

    Cheers,

    David.

  • David,

    No problem, this Linux driver is capturing a lot of features of the device and the whole Linux driver development has a big learning curve. So we understand that it takes a while to get the hang of it.

    Best regards,
    Pedro

  • Hi Pedro,

    I would appreciate some guidance with the routing (eventually), but maybe we should put that on hold as I grapple with another aspect of the TI driver. As I mentioned previously, when my system boots, I don't actually get any complaints from the adcx140_i2c_probe() function, which returns 0 as though everything is OK. That's as far as things get, however, as a null pointer is returned by devm_gpiod_get_optional() in the code below:

    static int adcx140_i2c_probe(struct i2c_client *i2c,
    			     const struct i2c_device_id *id)
    {
    	struct adcx140_priv *adcx140;
    	int ret;
    
    	adcx140 = devm_kzalloc(&i2c->dev, sizeof(*adcx140), GFP_KERNEL);
    	if (!adcx140)
    		return -ENOMEM;
    
    	adcx140->gpio_reset = devm_gpiod_get_optional(adcx140->dev,
    						      "reset", GPIOD_OUT_LOW);
    	if (IS_ERR(adcx140->gpio_reset))
    		dev_info(&i2c->dev, "Reset GPIO not defined\n");
    
    	adcx140->supply_areg = devm_regulator_get_optional(adcx140->dev,
    							   "areg");
    	if (IS_ERR(adcx140->supply_areg)) {
    		if (PTR_ERR(adcx140->supply_areg) == -EPROBE_DEFER)
    			return -EPROBE_DEFER;
    		else
    			adcx140->supply_areg = NULL;
    	} else {
    		ret = regulator_enable(adcx140->supply_areg);
    		if (ret) {
    			dev_err(adcx140->dev, "Failed to enable areg\n");
    			return ret;
    		}
    	}
    
    	adcx140->regmap = devm_regmap_init_i2c(i2c, &adcx140_i2c_regmap);
    	if (IS_ERR(adcx140->regmap)) {
    		ret = PTR_ERR(adcx140->regmap);
    		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
    			ret);
    		return ret;
    	}
    	adcx140->dev = &i2c->dev;
    	i2c_set_clientdata(i2c, adcx140);
    
    	return snd_soc_register_codec(&i2c->dev,
    					       &soc_codec_driver_adcx140,
    					       adcx140_dai_driver, 1);
    }
    

    Unfortunately, it appears that the IS_ERR macro is hiding the null return. What puzzles me is that devm_gpiod_get_optional() appears to be called on an empty (zeroed) piece of allocated memory. The adcx140 structure won't contain any data yet. I believe this is why my system can't find the gpio, rather than any peculiarities of the Jetson. I note that later on, adcx140->dev gets assigned a value. Am I missing something here?

    Cheers,

    David. 

  • David,

    The code:

    adcx140->supply_areg = devm_regulator_get_optional(adcx140->dev, "areg");
    Is an optional connection to monitor if an external AREG supply is used. If this is not defined in the device tree, the pointer is NULL and the TLV320ADCx140 is instructed to turn on its internal regulator. Using the internal regulator is the typical configuration of the TLV320ADCx140. 
    best regards,
      Pedro
  • Hi Pedro,

    I totally understand about the optional nature of both the reset gpio and regulator, but I'd doubt that either could work the way the code is currently. I have changed the adcx140_12c_probe() function to that below and can now configure my reset gpio correctly. adcx140->dev can't be used as an argument to devm_gpiod_get_optional() because it isn't initialized yet - just allocated. In my application, I am using a GPIO connected directly to the SHDNz pin.

    static int adcx140_i2c_probe(struct i2c_client *i2c,
    			     const struct i2c_device_id *id)
    {
    	struct adcx140_priv *adcx140;
    	int ret;
    
    	adcx140 = devm_kzalloc(&i2c->dev, sizeof(*adcx140), GFP_KERNEL);
    	if (!adcx140)
    		return -ENOMEM;
    
    	adcx140->gpio_reset = devm_gpiod_get_optional(&i2c->dev,
    						      "reset", GPIOD_OUT_LOW);
    
    	if (adcx140->gpio_reset)
    		dev_info(&i2c->dev, "Got reset GPIO %d OK\n", 
    			desc_to_gpio(adcx140->gpio_reset));
    	else
    		dev_err(&i2c->dev, "Reset GPIO not defined!\n");
    
    	adcx140->supply_areg = devm_regulator_get_optional(&i2c->dev,
    							   "areg");
    
    	if (IS_ERR(adcx140->supply_areg)) {
    		if (PTR_ERR(adcx140->supply_areg) == -EPROBE_DEFER) {
    			dev_err(&i2c->dev, "Probe deferred...\n");
    			return -EPROBE_DEFER;
    		} else
    			adcx140->supply_areg = NULL;
    	} else {
    		ret = regulator_enable(adcx140->supply_areg);
    		if (ret) {
    			dev_err(&i2c->dev, "Failed to enable areg\n");
    			return ret;
    		}
    	}
    
    	adcx140->regmap = devm_regmap_init_i2c(i2c, &adcx140_i2c_regmap);
    	if (IS_ERR(adcx140->regmap)) {
    		ret = PTR_ERR(adcx140->regmap);
    		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
    			ret);
    		return ret;
    	}
    
    	adcx140->dev = &i2c->dev;
    	i2c_set_clientdata(i2c, adcx140);
    
    	dev_info(adcx140->dev, "Calling snd_soc_register_codec():\n");
    
    	ret = snd_soc_register_codec(&i2c->dev,
    					       &soc_codec_driver_adcx140,
    					       adcx140_dai_driver, 1);
    
    	dev_info(adcx140->dev, "Return value = %d\n", ret);
    
    	return ret;
    }

    So I'm through to the next problem, which I suspect involves routing (or my lack there of), so I'm hoping you can show me what the typical routing might look like for the 3140 in ALSA - which widgets are involved etc. This remains a bit of a black art to me.

    Cheers,

    David.


  • Hello again Pedro,

    I think I now have the driver registered, and I can see the SHDNz go from low to high on boot, but the driver is producing the following errors:

    tlv320adcx140 1-004c: Invalid DAI interface format
    tlv320adcx140 1-004c: ASoC: Failed to set DAI format -22

    I probably need a different bit format, fsync-width etc? My sound node looks like the following:

    	tegra_sound: sound {
    		compatible = "nvidia,tegra-audio-t186ref-mobile-rt565x";
    		nvidia,model = "tegra-snd-t186ref-mobile-rt565x";
    		nvidia,num-codec-link = <1>;
                    nvidia,num-clk = <8>;
                    nvidia,clk-rates = < 270950400  /* PLLA_x11025_RATE */
                                 11289600   /* AUD_MCLK_x11025_RATE */
                                 45158400   /* PLLA_OUT0_x11025_RATE */
                                 45158400   /* AHUB_x11025_RATE */
                                 245760000  /* PLLA_x8000_RATE */
                                 12288000   /* AUD_MCLK_x8000_RATE */
                                 49152000   /* PLLA_OUT0_x8000_RATE */
                                 49152000 >;/* AHUB_x8000_RATE */
                    clocks = <&tegra_car TEGRA186_CLK_PLLP_OUT0>,
                    <&tegra_car TEGRA186_CLK_PLLA>,
                    <&tegra_car TEGRA186_CLK_PLL_A_OUT0>,
                    <&tegra_car TEGRA186_CLK_AHUB>,
                    <&tegra_car TEGRA186_CLK_CLK_M>,
                    <&tegra_car TEGRA186_CLK_AUD_MCLK>;
                   clock-names = "pll_p_out1", "pll_a", "pll_a_out0", "ahub",
                            "clk_m", "extern1";
    
    		assigned-clocks = <&tegra_car TEGRA186_CLK_PLL_A_OUT0>,
    				  <&tegra_car TEGRA186_CLK_AHUB>,
    				  <&tegra_car TEGRA186_CLK_AUD_MCLK>;
    		assigned-clock-parents = <&tegra_car TEGRA186_CLK_PLLA>,
    					 <&tegra_car TEGRA186_CLK_PLL_A_OUT0>,
    					 <&tegra_car TEGRA186_CLK_PLL_A_OUT0>;
    		resets = <&tegra_car TEGRA186_RESET_AUD_MCLK>;
    		reset-names = "extern1_rst";
    
    		status = "okay";
    		nvidia,audio-routing =
    		        "x CH1_ADC",		"x Mic",
    			"x CH2_ADC",		"x Mic",
    		        "x CH3_ADC",		"x Mic",
    			"x CH4_ADC",		"x Mic";
    
    		nvidia,xbar = <&tegra_axbar>;
    		mclk-fs = <256>;
    
    		rt565x_dai_link: nvidia,dai-link-1 {
    			link-name = "tlv320adcx140-codec";
    			cpu-dai = <&tegra_i2s1>;
    			codec-dai = <&tlv320adcx140>;
    			cpu-dai-name = "I2S1";
    			codec-dai-name = "tlv320adcx140-codec";
    			format = "dsp-a";
    			fsync-width = <15>;
                            bitclock-slave;
                            frame-slave;
                            bitclock-noninversion;
                            frame-noninversion;
    			bit-format = "s16_le";
    			bclk_ratio = <1>;
    			srate = <48000>;
    			num-channel = <4>;
    			ignore_suspend;
    			name-prefix = "x";
    			status = "okay";
    		};

     

     

  • David,

    The dts normally has these entries:

    sound {
                     simple-audio-card,format = "i2s";
                     simple-audio-card,bitclock-master = <&dailink0_master>;
                     simple-audio-card,frame-master = <&dailink0_master>;
                     simple-audio-card,widgets =
                             "Microphone", "Mic Jack";
                     simple-audio-card,routing =
                             "MIC1P",                "Mic Jack",
                             "MIC1P",                "Mic Jack";
                     dailink0_master: simple-audio-card,cpu {
                             sound-dai = <&tegra_i2s1>;
                             clocks = <&clk_tegra_i2s1>;
                     };
                     simple-audio-card,codec {
                             sound-dai = <&tlv320adc5140>;
                     };
             };
    };
    I did not see a (clk_tegra_i2s) clock connection. 
    Also, TLV320ADCx140 suports an I2S or TDM format. Depending on the Tegra signaling format, this should match.

    Best regards,
    Pedro

  • Hi Pedro,

    OK - I changed my format from 'dsp-a' to 'dsp_a' in the sound block and the TI driver now registers and initialises without error. I see SHDNz go from low to high on system boot along with some i2c traffic. When I run 'arecord test.wav', I see MCLK from the TX2 become active at around 12.4 MHz, but nothing on FSYNC, BCLK or DOUT yet.

    Obviously, I need to configure my audio routing, but I'd also like to hear your recommendation for the simplest hardware configuration I can use to start some recording. I currently have MCLK bridged through to GPIO1 on the 3140, but will it automatically detect this and configure the other clocks, or do I need to change some of the default register settings in the driver?

    Cheers,

    David.

  • David,

    Glad to hear of the progress! The easiest routing for the audio serial interface is to let the TLV320ADCx140 PLL auto configure the clocks. Just by sending BCLK and FSYNC, the device will auto configure to the programmed sample rate. Just ensure that

    BLCK >= FSYNC * word_length * #_channels

    Then use alsamixer to setup the input channels and volume.

    On boot-up, you should see the sound card and its pin mapping being registered, like this:

    asoc-simple-card sound: tlv320adcx140-codec <-> 48038000.mcasp mapping ok

    arecord -l should show the ALSA device list

    Best regards,
    Pedro