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.

PCM3168A: 6 independent audios

Part Number: PCM3168A
Other Parts Discussed in Thread: PCM3168

Hi,

I'm in a project where I need to have 6 different audio communication paths. I need each channel to be able to establish calls to one or several sip phones.

In A, i have an analogic microphone and speaker.

I find the PCM3168A codec, who have 6 inputs/8 outputs, so I made a custom prototype with that codec, connected to J1001 connector of IMX8mm EVK (see schematic.pdf).

6378.schematic.pdf

Based on https://bootlin.com/blog/eight-channels-audio-on-i-mx7-with-pcm3168/ I configured the device tree:

sound-pcm3168a {
        compatible = "simple-audio-card";
        simple-audio-card,name = "pcm3168a-audio";
        simple-audio-card,widgets =    "Speaker", "Channel1out",
                                                                    "Speaker", "Channel2out",
                                                                    "Speaker", "Channel3out",
                                                                    "Speaker", "Channel4out",
                                                                    "Speaker", "Channel5out",
                                                                    "Speaker", "Channel6out",
                                                                    "Microphone", "Channel1in",    
                                                                    "Microphone", "Channel2in",
                                                                    "Microphone", "Channel3in",
                                                                    "Microphone", "Channel4in",
                                                                    "Microphone", "Channel5in",
                                                                    "Microphone", "Channel6in";
     simple-audio-card,routing =     "Channel1out", "AOUT1L",
                                                                 "Channel2out", "AOUT1R",
                                                                 "Channel3out", "AOUT2L",
                                                                 "Channel4out", "AOUT2R",
                                                                 "Channel5out", "AOUT3L",
                                                                 "Channel6out", "AOUT3R",
                                                                 "Channel1in", "AIN1L",
                                                                 "Channel2in", "AIN1R",
                                                                 "Channel3in", "AIN2L",
                                                                 "Channel4in", "AIN2R",
                                                                 "Channel5in", "AIN3L",
                                                                 "Channel6in", "AIN3R";
        simple-audio-card,dai-link@1 {
                        format = "left_j";
                bitclock-master = <&pcm3168a_dac>;
                frame-master = <&pcm3168a_dac>;
                    cpu {
                            sound-dai = <&sai1>;
                        dai-tdm-slot-num = <8>;
                        dai-tdm-slot-width = <32>;
                    };
                    pcm3168a_dac: codec {
                        sound-dai = <&pcm3168a 0>;
                        clocks = <&clk IMX8MM_CLK_SAI1_ROOT>;
                    };
     };
            simple-audio-card,dai-link@2 {
                        format = "left_j";
                        
                         bitclock-master = <&pcm3168a_adc>;
                        frame-master = <&pcm3168a_adc>;
                        cpu {
                                sound-dai = <&sai1>;
                                dai-tdm-slot-num = <8>;
                                dai-tdm-slot-width = <32>;
                        };
                        pcm3168a_adc: codec {
                                sound-dai = <&pcm3168a 1>;
                                clocks = <&clk IMX8MM_CLK_SAI1_ROOT>;
                        };
     };
    };

 

///I2C3

pcm3168a: audio-codec@44 {
        compatible = "ti,pcm3168a";
        #sound-dai-cells = <1>;
        reg = <0x44>;
        reset-gpios = <&gpio4 17 GPIO_ACTIVE_LOW>;
    clocks = <&clk IMX8MM_CLK_SAI1_ROOT>;
        clock-names = "scki";
        /*VDD1-supply = <&supply3v3>;
        VDD2-supply = <&supply3v3>;
        VCCAD1-supply = <&supply5v0>;
        VCCAD2-supply = <&supply5v0>;
        VCCDA1-supply = <&supply5v0>;
        VCCDA2-supply = <&supply5v0>;*/
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_pcm3168a>;
    };

 

//iomux

pinctrl_pcm3168a: pinctrl_pcm3168a{
        fsl,pins = <
            MX8MM_IOMUXC_SAI1_TXD5_GPIO4_IO17    0x19    /*RESET*/
            MX8MM_IOMUXC_SAI1_TXD6_GPIO4_IO18    0x19    /*OVF*/
            MX8MM_IOMUXC_SAI1_TXD7_GPIO4_IO19    0x19    /*ZERO*/
        >;
    };
   

pinctrl_sai1: sai1grp {
        fsl,pins = <
            MX8MM_IOMUXC_SAI1_MCLK_SAI1_MCLK    0xd6
            MX8MM_IOMUXC_SAI1_TXFS_SAI1_TX_SYNC    0xd6
            MX8MM_IOMUXC_SAI1_TXC_SAI1_TX_BCLK    0xd6
            MX8MM_IOMUXC_SAI1_TXD0_SAI1_TX_DATA0    0xd6
            MX8MM_IOMUXC_SAI1_TXD1_SAI1_TX_DATA1    0xd6
            MX8MM_IOMUXC_SAI1_TXD2_SAI1_TX_DATA2    0xd6
            MX8MM_IOMUXC_SAI1_TXD3_SAI1_TX_DATA3    0xd6
            MX8MM_IOMUXC_SAI1_TXD4_SAI1_TX_DATA4    0xd6
            
            MX8MM_IOMUXC_SAI1_RXD0_SAI1_RX_DATA0    0xd6
            MX8MM_IOMUXC_SAI1_RXD1_SAI1_RX_DATA1    0xd6
            MX8MM_IOMUXC_SAI1_RXD2_SAI1_RX_DATA2    0xd6
            MX8MM_IOMUXC_SAI1_RXC_SAI1_RX_BCLK        0xd6
            MX8MM_IOMUXC_SAI1_RXFS_SAI1_RX_SYNC    0xd6
        >;
    }; 

When loaded the device tree on EVK, I have:

[ 2.375321] debugfs: Directory '30010000.sai' with parent 'pcm3168a-audio' already present!
[ 2.383813] asoc-simple-card sound-pcm3168a: pcm3168a-dac <-> 30010000.sai mapping ok
[ 2.391733] asoc-simple-card sound-pcm3168a: pcm3168a-adc <-> 30010000.sai mapping ok
[ 2.399624] asoc-simple-card sound-pcm3168a: ASoC: no DMI vendor name!

[ 2.552554] ALSA device list:
[ 2.557548] #0: imx-spdif
[ 2.568080] #1: imx-audio-micfil
[ 2.571488] #2: pcm3168a-audio
[ 2.574717] #3: wm8524-audio

 

aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: imxspdif [imx-spdif], device 0: S/PDIF PCM snd-soc-dummy-dai-0 [S/PDIF PCM snd-soc-dummy-dai-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: btscoaudio [bt-sco-audio], device 0: 30020000.sai-bt-sco-pcm-wb bt-sco-pcm-wb-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 3: wm8524audio [wm8524-audio], device 0: 30030000.sai-wm8524-hifi wm8524-hifi-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 4: pcm3168aaudio [pcm3168a-audio], device 0: 30010000.sai-pcm3168a-dac pcm3168a-dac-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0


arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: imxspdif [imx-spdif], device 0: S/PDIF PCM snd-soc-dummy-dai-0 [S/PDIF PCM snd-soc-dummy-dai-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: imxaudiomicfil [imx-audio-micfil], device 0: micfil hifi snd-soc-dummy-dai-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: btscoaudio [bt-sco-audio], device 0: 30020000.sai-bt-sco-pcm-wb bt-sco-pcm-wb-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 4: pcm3168aaudio [pcm3168a-audio], device 1: 30010000.sai-pcm3168a-adc pcm3168a-adc-1 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

 

When tried to record a audio:

arecord -D hw:4,1  -r 48000  sample.wav
Recording WAVE 'sample.wav' : Unsigned 8 bit, Rate 48000 Hz, Mono
arecord: set_params:1339: Sample format U8 non available
Available formats:
- S24_LE

arecord -D hw:4,1 -f S24_LE -r 48000  sample.wav
Recording WAVE 'sample.wav' : Signed 24 bit Little Endian, Rate 48000 Hz, Mono
arecord: set_params:1345: Channels count non available

arecord -D hw:4,1 -f S24_LE -r 48000  -c 2 sample.wav
Recording WAVE 'sample.wav' : [ 1857.768567]
Signed 24 bit Little Endian, Rate[ 1857.777720] pcm3168a 2-0044: 24-bit slots not supported in master mode, or slave mode using DSP
[ 1857.789217] pcm3168a 2-0044: ASoC: can't set pcm3168a-adc hw params: -22
 48000 Hz, Stereo
arecord: set_params:1403: Unable to install hw params:
ACCESS:  RW_INTERLEAVED
FORMAT:  S24_LE
SUBFORMAT:  STD
SAMPLE_BITS: 32
FRAME_BITS: 64
CHANNELS: 2
RATE: 48000
PERIOD_TIME: (42666 42667)
PERIOD_SIZE: 2048
PERIOD_BYTES: 16384
PERIODS: 4
BUFFER_TIME: (170666 170667)
BUFFER_SIZE: 8192
BUFFER_BYTES: 65536
TICK_TIME: 0

So, my questions are:

1-Can I use the PCM3168A to have the 6 independent audios, like I need?

2-If yes, has anyone managed to configure the pcm3168 with imx8mm? Anyone can help me with the device tree configuration? How do i differentiate the different channels in linux? How I can record/play audio in a specified audio input/output of the pcm3168a?

3-If not, the solution can passed to using 6 simple codecs (like wm8960 that was on many imx evk)? There are other options (but simple to implement in this micro)?

Thanks for any help.imx8mm-evk.zip

 

Thanks.

  • Hi,

    1. There's only 1 set of I2S bus for each ADC or DAC, so as long as all input/output paths are using the same rate you can use this part.

    2. I'm not an expert in Linux implementation, let me ask someone who might know.

    Regards.

  • For the second question, kindly check whether the code contain following bold lines.

    #define PCMDEVICE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
    SNDRV_PCM_FMTBIT_S24_LE)

    static struct snd_soc_dai_driver pcmdevice_dai_driver[] = {
    {
    .name = "pcmdevice-codec",
    .capture = {
    .stream_name = "Capture",
    .channels_min = 2,
    .channels_max = PCMDEVICE_MAX_CHANNELS,
    .rates = PCMDEVICE_RATES,
    .formats = PCMDEVICE_FORMATS,
    },
    .ops = &pcmdevice_dai_ops,
    .symmetric_rates = 1,
    }
    };

  • Hi,

    I'm using NXP release of 5.4.70_2.3.0

    Like you can see in pcm3168a.c, the define is declared with name PCM3168A_FORMATS (line 22), and is assigned on line 615.

    Thanks.soc-core.c

    soc-dai.c
    // SPDX-License-Identifier: GPL-2.0
    //
    // soc-dai.c
    //
    // Copyright (C) 2019 Renesas Electronics Corp.
    // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
    //
    
    #include <sound/soc.h>
    #include <sound/soc-dai.h>
    
    /**
     * snd_soc_dai_set_sysclk - configure DAI system or master clock.
     * @dai: DAI
     * @clk_id: DAI specific clock ID
     * @freq: new clock frequency in Hz
     * @dir: new clock direction - input/output.
     *
     * Configures the DAI master (MCLK) or system (SYSCLK) clocking.
     */
    int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
    			   unsigned int freq, int dir)
    {
    	if (dai->driver->ops->set_sysclk)
    		return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
    
    	return snd_soc_component_set_sysclk(dai->component, clk_id, 0,
    					    freq, dir);
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
    
    /**
     * snd_soc_dai_set_clkdiv - configure DAI clock dividers.
     * @dai: DAI
     * @div_id: DAI specific clock divider ID
     * @div: new clock divisor.
     *
     * Configures the clock dividers. This is used to derive the best DAI bit and
     * frame clocks from the system or master clock. It's best to set the DAI bit
     * and frame clocks as low as possible to save system power.
     */
    int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai,
    			   int div_id, int div)
    {
    	if (dai->driver->ops->set_clkdiv)
    		return dai->driver->ops->set_clkdiv(dai, div_id, div);
    	else
    		return -EINVAL;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv);
    
    /**
     * snd_soc_dai_set_pll - configure DAI PLL.
     * @dai: DAI
     * @pll_id: DAI specific PLL ID
     * @source: DAI specific source for the PLL
     * @freq_in: PLL input clock frequency in Hz
     * @freq_out: requested PLL output clock frequency in Hz
     *
     * Configures and enables PLL to generate output clock based on input clock.
     */
    int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
    			unsigned int freq_in, unsigned int freq_out)
    {
    	if (dai->driver->ops->set_pll)
    		return dai->driver->ops->set_pll(dai, pll_id, source,
    						 freq_in, freq_out);
    
    	return snd_soc_component_set_pll(dai->component, pll_id, source,
    					 freq_in, freq_out);
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
    
    /**
     * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio.
     * @dai: DAI
     * @ratio: Ratio of BCLK to Sample rate.
     *
     * Configures the DAI for a preset BCLK to sample rate ratio.
     */
    int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
    {
    	if (dai->driver->ops->set_bclk_ratio)
    		return dai->driver->ops->set_bclk_ratio(dai, ratio);
    	else
    		return -EINVAL;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio);
    
    /**
     * snd_soc_dai_set_fmt - configure DAI hardware audio format.
     * @dai: DAI
     * @fmt: SND_SOC_DAIFMT_* format value.
     *
     * Configures the DAI hardware format and clocking.
     */
    int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
    {
    	if (dai->driver->ops->set_fmt == NULL)
    		return -ENOTSUPP;
    	return dai->driver->ops->set_fmt(dai, fmt);
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt);
    
    /**
     * snd_soc_xlate_tdm_slot - generate tx/rx slot mask.
     * @slots: Number of slots in use.
     * @tx_mask: bitmask representing active TX slots.
     * @rx_mask: bitmask representing active RX slots.
     *
     * Generates the TDM tx and rx slot default masks for DAI.
     */
    static int snd_soc_xlate_tdm_slot_mask(unsigned int slots,
    				       unsigned int *tx_mask,
    				       unsigned int *rx_mask)
    {
    	if (*tx_mask || *rx_mask)
    		return 0;
    
    	if (!slots)
    		return -EINVAL;
    
    	*tx_mask = (1 << slots) - 1;
    	*rx_mask = (1 << slots) - 1;
    
    	return 0;
    }
    
    /**
     * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation
     * @dai: The DAI to configure
     * @tx_mask: bitmask representing active TX slots.
     * @rx_mask: bitmask representing active RX slots.
     * @slots: Number of slots in use.
     * @slot_width: Width in bits for each slot.
     *
     * This function configures the specified DAI for TDM operation. @slot contains
     * the total number of slots of the TDM stream and @slot_with the width of each
     * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the
     * active slots of the TDM stream for the specified DAI, i.e. which slots the
     * DAI should write to or read from. If a bit is set the corresponding slot is
     * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to
     * the first slot, bit 1 to the second slot and so on. The first active slot
     * maps to the first channel of the DAI, the second active slot to the second
     * channel and so on.
     *
     * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask,
     * @rx_mask and @slot_width will be ignored.
     *
     * Returns 0 on success, a negative error code otherwise.
     */
    int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
    			     unsigned int tx_mask, unsigned int rx_mask,
    			     int slots, int slot_width)
    {
    	if (dai->driver->ops->xlate_tdm_slot_mask)
    		dai->driver->ops->xlate_tdm_slot_mask(slots,
    						      &tx_mask, &rx_mask);
    	else
    		snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask);
    
    	dai->tx_mask = tx_mask;
    	dai->rx_mask = rx_mask;
    
    	if (dai->driver->ops->set_tdm_slot)
    		return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask,
    						      slots, slot_width);
    	else
    		return -ENOTSUPP;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
    
    /**
     * snd_soc_dai_set_channel_map - configure DAI audio channel map
     * @dai: DAI
     * @tx_num: how many TX channels
     * @tx_slot: pointer to an array which imply the TX slot number channel
     *           0~num-1 uses
     * @rx_num: how many RX channels
     * @rx_slot: pointer to an array which imply the RX slot number channel
     *           0~num-1 uses
     *
     * configure the relationship between channel number and TDM slot number.
     */
    int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai,
    				unsigned int tx_num, unsigned int *tx_slot,
    				unsigned int rx_num, unsigned int *rx_slot)
    {
    	if (dai->driver->ops->set_channel_map)
    		return dai->driver->ops->set_channel_map(dai, tx_num, tx_slot,
    							 rx_num, rx_slot);
    	else
    		return -ENOTSUPP;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map);
    
    /**
     * snd_soc_dai_get_channel_map - Get DAI audio channel map
     * @dai: DAI
     * @tx_num: how many TX channels
     * @tx_slot: pointer to an array which imply the TX slot number channel
     *           0~num-1 uses
     * @rx_num: how many RX channels
     * @rx_slot: pointer to an array which imply the RX slot number channel
     *           0~num-1 uses
     */
    int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai,
    				unsigned int *tx_num, unsigned int *tx_slot,
    				unsigned int *rx_num, unsigned int *rx_slot)
    {
    	if (dai->driver->ops->get_channel_map)
    		return dai->driver->ops->get_channel_map(dai, tx_num, tx_slot,
    							 rx_num, rx_slot);
    	else
    		return -ENOTSUPP;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map);
    
    /**
     * snd_soc_dai_set_tristate - configure DAI system or master clock.
     * @dai: DAI
     * @tristate: tristate enable
     *
     * Tristates the DAI so that others can use it.
     */
    int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate)
    {
    	if (dai->driver->ops->set_tristate)
    		return dai->driver->ops->set_tristate(dai, tristate);
    	else
    		return -EINVAL;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate);
    
    /**
     * snd_soc_dai_digital_mute - configure DAI system or master clock.
     * @dai: DAI
     * @mute: mute enable
     * @direction: stream to mute
     *
     * Mutes the DAI DAC.
     */
    int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute,
    			     int direction)
    {
    	if (dai->driver->ops->mute_stream)
    		return dai->driver->ops->mute_stream(dai, mute, direction);
    	else if (direction == SNDRV_PCM_STREAM_PLAYBACK &&
    		 dai->driver->ops->digital_mute)
    		return dai->driver->ops->digital_mute(dai, mute);
    	else
    		return -ENOTSUPP;
    }
    EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
    
    int snd_soc_dai_hw_params(struct snd_soc_dai *dai,
    			  struct snd_pcm_substream *substream,
    			  struct snd_pcm_hw_params *params)
    {
    	struct snd_soc_pcm_runtime *rtd = substream->private_data;
    	int ret;
    
    	/* perform any topology hw_params fixups before DAI  */
    	if (rtd->dai_link->be_hw_params_fixup) {
    		ret = rtd->dai_link->be_hw_params_fixup(rtd, params);
    		if (ret < 0) {
    			dev_err(rtd->dev,
    				"ASoC: hw_params topology fixup failed %d\n",
    				ret);
    			return ret;
    		}
    	}
    
    	if (dai->driver->ops->hw_params) {
    		ret = dai->driver->ops->hw_params(substream, params, dai);
    		if (ret < 0) {
    			dev_err(dai->dev, "ASoC: can't set %s hw params: %d\n",
    				dai->name, ret);
    			return ret;
    		}
    	}
    
    	return 0;
    }
    
    void snd_soc_dai_hw_free(struct snd_soc_dai *dai,
    			 struct snd_pcm_substream *substream)
    {
    	if (dai->driver->ops->hw_free)
    		dai->driver->ops->hw_free(substream, dai);
    }
    
    int snd_soc_dai_startup(struct snd_soc_dai *dai,
    			struct snd_pcm_substream *substream)
    {
    	int ret = 0;
    
    	if (dai->driver->ops->startup)
    		ret = dai->driver->ops->startup(substream, dai);
    
    	return ret;
    }
    
    void snd_soc_dai_shutdown(struct snd_soc_dai *dai,
    			 struct snd_pcm_substream *substream)
    {
    	if (dai->driver->ops->shutdown)
    		dai->driver->ops->shutdown(substream, dai);
    }
    
    int snd_soc_dai_prepare(struct snd_soc_dai *dai,
    			struct snd_pcm_substream *substream)
    {
    	int ret = 0;
    
    	if (dai->driver->ops->prepare)
    		ret = dai->driver->ops->prepare(substream, dai);
    
    	return ret;
    }
    
    int snd_soc_dai_trigger(struct snd_soc_dai *dai,
    			struct snd_pcm_substream *substream,
    			int cmd)
    {
    	int ret = 0;
    
    	if (dai->driver->ops->trigger)
    		ret = dai->driver->ops->trigger(substream, cmd, dai);
    
    	return ret;
    }
    
    int snd_soc_dai_bespoke_trigger(struct snd_soc_dai *dai,
    				struct snd_pcm_substream *substream,
    				int cmd)
    {
    	int ret = 0;
    
    	if (dai->driver->ops->bespoke_trigger)
    		ret = dai->driver->ops->bespoke_trigger(substream, cmd, dai);
    
    	return ret;
    }
    
    snd_pcm_sframes_t snd_soc_dai_delay(struct snd_soc_dai *dai,
    				    struct snd_pcm_substream *substream)
    {
    	int delay = 0;
    
    	if (dai->driver->ops->delay)
    		delay = dai->driver->ops->delay(substream, dai);
    
    	return delay;
    }
    
    void snd_soc_dai_suspend(struct snd_soc_dai *dai)
    {
    	if (dai->driver->suspend)
    		dai->driver->suspend(dai);
    }
    
    void snd_soc_dai_resume(struct snd_soc_dai *dai)
    {
    	if (dai->driver->resume)
    		dai->driver->resume(dai);
    }
    
    int snd_soc_dai_probe(struct snd_soc_dai *dai)
    {
    	if (dai->driver->probe)
    		return dai->driver->probe(dai);
    	return 0;
    }
    
    int snd_soc_dai_remove(struct snd_soc_dai *dai)
    {
    	if (dai->driver->remove)
    		return dai->driver->remove(dai);
    	return 0;
    }
    
    int snd_soc_dai_compress_new(struct snd_soc_dai *dai,
    			     struct snd_soc_pcm_runtime *rtd, int num)
    {
    	if (dai->driver->compress_new)
    		return dai->driver->compress_new(rtd, num);
    	return -ENOTSUPP;
    }
    
    /*
     * snd_soc_dai_stream_valid() - check if a DAI supports the given stream
     *
     * Returns true if the DAI supports the indicated stream type.
     */
    bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int dir)
    {
    	struct snd_soc_pcm_stream *stream;
    
    	if (dir == SNDRV_PCM_STREAM_PLAYBACK)
    		stream = &dai->driver->playback;
    	else
    		stream = &dai->driver->capture;
    
    	/* If the codec specifies any channels at all, it supports the stream */
    	return stream->channels_min;
    }
    

    pcm3168a.c
    // SPDX-License-Identifier: GPL-2.0-only
    /*
     * PCM3168A codec driver
     *
     * Copyright (C) 2015 Imagination Technologies Ltd.
     *
     * Author: Damien Horsley <Damien.Horsley@imgtec.com>
     */
    
    #include <linux/clk.h>
    #include <linux/delay.h>
    #include <linux/module.h>
    #include <linux/pm_runtime.h>
    #include <linux/regulator/consumer.h>
    
    #include <sound/pcm_params.h>
    #include <sound/soc.h>
    #include <sound/tlv.h>
    
    #include "pcm3168a.h"
    
    #define PCM3168A_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
    			 SNDRV_PCM_FMTBIT_S24_3LE | \
    			 SNDRV_PCM_FMTBIT_S24_LE)
    
    #define PCM3168A_FMT_I2S		0x0
    #define PCM3168A_FMT_LEFT_J		0x1
    #define PCM3168A_FMT_RIGHT_J		0x2
    #define PCM3168A_FMT_RIGHT_J_16		0x3
    #define PCM3168A_FMT_DSP_A		0x4
    #define PCM3168A_FMT_DSP_B		0x5
    #define PCM3168A_FMT_I2S_TDM		0x6
    #define PCM3168A_FMT_LEFT_J_TDM		0x7
    #define PCM3168A_FMT_DSP_MASK		0x4
    
    #define PCM3168A_NUM_SUPPLIES 6
    static const char *const pcm3168a_supply_names[PCM3168A_NUM_SUPPLIES] = {
    	"VDD1",
    	"VDD2",
    	"VCCAD1",
    	"VCCAD2",
    	"VCCDA1",
    	"VCCDA2"
    };
    
    #define PCM3168A_DAI_DAC		0
    #define PCM3168A_DAI_ADC		1
    
    /* ADC/DAC side parameters */
    struct pcm3168a_io_params {
    	bool master_mode;
    	unsigned int fmt;
    	int tdm_slots;
    	u32 tdm_mask;
    	int slot_width;
    };
    
    struct pcm3168a_priv {
    	struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES];
    	struct regmap *regmap;
    	struct clk *scki;
    	unsigned long sysclk;
    
    	struct pcm3168a_io_params io_params[2];
    };
    
    static const char *const pcm3168a_roll_off[] = { "Sharp", "Slow" };
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_d1_roll_off, PCM3168A_DAC_OP_FLT,
    		PCM3168A_DAC_FLT_SHIFT, pcm3168a_roll_off);
    static SOC_ENUM_SINGLE_DECL(pcm3168a_d2_roll_off, PCM3168A_DAC_OP_FLT,
    		PCM3168A_DAC_FLT_SHIFT + 1, pcm3168a_roll_off);
    static SOC_ENUM_SINGLE_DECL(pcm3168a_d3_roll_off, PCM3168A_DAC_OP_FLT,
    		PCM3168A_DAC_FLT_SHIFT + 2, pcm3168a_roll_off);
    static SOC_ENUM_SINGLE_DECL(pcm3168a_d4_roll_off, PCM3168A_DAC_OP_FLT,
    		PCM3168A_DAC_FLT_SHIFT + 3, pcm3168a_roll_off);
    
    static const char *const pcm3168a_volume_type[] = {
    		"Individual", "Master + Individual" };
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_volume_type, PCM3168A_DAC_ATT_DEMP_ZF,
    		PCM3168A_DAC_ATMDDA_SHIFT, pcm3168a_volume_type);
    
    static const char *const pcm3168a_att_speed_mult[] = { "2048", "4096" };
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_att_mult, PCM3168A_DAC_ATT_DEMP_ZF,
    		PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_att_speed_mult);
    
    static const char *const pcm3168a_demp[] = {
    		"Disabled", "48khz", "44.1khz", "32khz" };
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_demp, PCM3168A_DAC_ATT_DEMP_ZF,
    		PCM3168A_DAC_DEMP_SHIFT, pcm3168a_demp);
    
    static const char *const pcm3168a_zf_func[] = {
    		"DAC 1/2/3/4 AND", "DAC 1/2/3/4 OR", "DAC 1/2/3 AND",
    		"DAC 1/2/3 OR", "DAC 4 AND", "DAC 4 OR" };
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_func, PCM3168A_DAC_ATT_DEMP_ZF,
    		PCM3168A_DAC_AZRO_SHIFT, pcm3168a_zf_func);
    
    static const char *const pcm3168a_pol[] = { "Active High", "Active Low" };
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_pol, PCM3168A_DAC_ATT_DEMP_ZF,
    		PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_pol);
    
    static const char *const pcm3168a_con[] = { "Differential", "Single-Ended" };
    
    static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc1_con, PCM3168A_ADC_SEAD,
    				0, 1, pcm3168a_con);
    static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc2_con, PCM3168A_ADC_SEAD,
    				2, 3, pcm3168a_con);
    static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc3_con, PCM3168A_ADC_SEAD,
    				4, 5, pcm3168a_con);
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_volume_type, PCM3168A_ADC_ATT_OVF,
    		PCM3168A_ADC_ATMDAD_SHIFT, pcm3168a_volume_type);
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_att_mult, PCM3168A_ADC_ATT_OVF,
    		PCM3168A_ADC_ATSPAD_SHIFT, pcm3168a_att_speed_mult);
    
    static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_ov_pol, PCM3168A_ADC_ATT_OVF,
    		PCM3168A_ADC_OVFP_SHIFT, pcm3168a_pol);
    
    /* -100db to 0db, register values 0-54 cause mute */
    static const DECLARE_TLV_DB_SCALE(pcm3168a_dac_tlv, -10050, 50, 1);
    
    /* -100db to 20db, register values 0-14 cause mute */
    static const DECLARE_TLV_DB_SCALE(pcm3168a_adc_tlv, -10050, 50, 1);
    
    static const struct snd_kcontrol_new pcm3168a_snd_controls[] = {
    	SOC_SINGLE("DAC Power-Save Switch", PCM3168A_DAC_PWR_MST_FMT,
    			PCM3168A_DAC_PSMDA_SHIFT, 1, 1),
    	SOC_ENUM("DAC1 Digital Filter roll-off", pcm3168a_d1_roll_off),
    	SOC_ENUM("DAC2 Digital Filter roll-off", pcm3168a_d2_roll_off),
    	SOC_ENUM("DAC3 Digital Filter roll-off", pcm3168a_d3_roll_off),
    	SOC_ENUM("DAC4 Digital Filter roll-off", pcm3168a_d4_roll_off),
    	SOC_DOUBLE("DAC1 Invert Switch", PCM3168A_DAC_INV, 0, 1, 1, 0),
    	SOC_DOUBLE("DAC2 Invert Switch", PCM3168A_DAC_INV, 2, 3, 1, 0),
    	SOC_DOUBLE("DAC3 Invert Switch", PCM3168A_DAC_INV, 4, 5, 1, 0),
    	SOC_DOUBLE("DAC4 Invert Switch", PCM3168A_DAC_INV, 6, 7, 1, 0),
    	SOC_ENUM("DAC Volume Control Type", pcm3168a_dac_volume_type),
    	SOC_ENUM("DAC Volume Rate Multiplier", pcm3168a_dac_att_mult),
    	SOC_ENUM("DAC De-Emphasis", pcm3168a_dac_demp),
    	SOC_ENUM("DAC Zero Flag Function", pcm3168a_dac_zf_func),
    	SOC_ENUM("DAC Zero Flag Polarity", pcm3168a_dac_zf_pol),
    	SOC_SINGLE_RANGE_TLV("Master Playback Volume",
    			PCM3168A_DAC_VOL_MASTER, 0, 54, 255, 0,
    			pcm3168a_dac_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("DAC1 Playback Volume",
    			PCM3168A_DAC_VOL_CHAN_START,
    			PCM3168A_DAC_VOL_CHAN_START + 1,
    			0, 54, 255, 0, pcm3168a_dac_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("DAC2 Playback Volume",
    			PCM3168A_DAC_VOL_CHAN_START + 2,
    			PCM3168A_DAC_VOL_CHAN_START + 3,
    			0, 54, 255, 0, pcm3168a_dac_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("DAC3 Playback Volume",
    			PCM3168A_DAC_VOL_CHAN_START + 4,
    			PCM3168A_DAC_VOL_CHAN_START + 5,
    			0, 54, 255, 0, pcm3168a_dac_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("DAC4 Playback Volume",
    			PCM3168A_DAC_VOL_CHAN_START + 6,
    			PCM3168A_DAC_VOL_CHAN_START + 7,
    			0, 54, 255, 0, pcm3168a_dac_tlv),
    	SOC_SINGLE("ADC1 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB,
    			PCM3168A_ADC_BYP_SHIFT, 1, 1),
    	SOC_SINGLE("ADC2 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB,
    			PCM3168A_ADC_BYP_SHIFT + 1, 1, 1),
    	SOC_SINGLE("ADC3 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB,
    			PCM3168A_ADC_BYP_SHIFT + 2, 1, 1),
    	SOC_ENUM("ADC1 Connection Type", pcm3168a_adc1_con),
    	SOC_ENUM("ADC2 Connection Type", pcm3168a_adc2_con),
    	SOC_ENUM("ADC3 Connection Type", pcm3168a_adc3_con),
    	SOC_DOUBLE("ADC1 Invert Switch", PCM3168A_ADC_INV, 0, 1, 1, 0),
    	SOC_DOUBLE("ADC2 Invert Switch", PCM3168A_ADC_INV, 2, 3, 1, 0),
    	SOC_DOUBLE("ADC3 Invert Switch", PCM3168A_ADC_INV, 4, 5, 1, 0),
    	SOC_DOUBLE("ADC1 Mute Switch", PCM3168A_ADC_MUTE, 0, 1, 1, 0),
    	SOC_DOUBLE("ADC2 Mute Switch", PCM3168A_ADC_MUTE, 2, 3, 1, 0),
    	SOC_DOUBLE("ADC3 Mute Switch", PCM3168A_ADC_MUTE, 4, 5, 1, 0),
    	SOC_ENUM("ADC Volume Control Type", pcm3168a_adc_volume_type),
    	SOC_ENUM("ADC Volume Rate Multiplier", pcm3168a_adc_att_mult),
    	SOC_ENUM("ADC Overflow Flag Polarity", pcm3168a_adc_ov_pol),
    	SOC_SINGLE_RANGE_TLV("Master Capture Volume",
    			PCM3168A_ADC_VOL_MASTER, 0, 14, 255, 0,
    			pcm3168a_adc_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("ADC1 Capture Volume",
    			PCM3168A_ADC_VOL_CHAN_START,
    			PCM3168A_ADC_VOL_CHAN_START + 1,
    			0, 14, 255, 0, pcm3168a_adc_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("ADC2 Capture Volume",
    			PCM3168A_ADC_VOL_CHAN_START + 2,
    			PCM3168A_ADC_VOL_CHAN_START + 3,
    			0, 14, 255, 0, pcm3168a_adc_tlv),
    	SOC_DOUBLE_R_RANGE_TLV("ADC3 Capture Volume",
    			PCM3168A_ADC_VOL_CHAN_START + 4,
    			PCM3168A_ADC_VOL_CHAN_START + 5,
    			0, 14, 255, 0, pcm3168a_adc_tlv)
    };
    
    static const struct snd_soc_dapm_widget pcm3168a_dapm_widgets[] = {
    	SND_SOC_DAPM_DAC("DAC1", "Playback", PCM3168A_DAC_OP_FLT,
    			PCM3168A_DAC_OPEDA_SHIFT, 1),
    	SND_SOC_DAPM_DAC("DAC2", "Playback", PCM3168A_DAC_OP_FLT,
    			PCM3168A_DAC_OPEDA_SHIFT + 1, 1),
    	SND_SOC_DAPM_DAC("DAC3", "Playback", PCM3168A_DAC_OP_FLT,
    			PCM3168A_DAC_OPEDA_SHIFT + 2, 1),
    	SND_SOC_DAPM_DAC("DAC4", "Playback", PCM3168A_DAC_OP_FLT,
    			PCM3168A_DAC_OPEDA_SHIFT + 3, 1),
    
    	SND_SOC_DAPM_OUTPUT("AOUT1L"),
    	SND_SOC_DAPM_OUTPUT("AOUT1R"),
    	SND_SOC_DAPM_OUTPUT("AOUT2L"),
    	SND_SOC_DAPM_OUTPUT("AOUT2R"),
    	SND_SOC_DAPM_OUTPUT("AOUT3L"),
    	SND_SOC_DAPM_OUTPUT("AOUT3R"),
    	SND_SOC_DAPM_OUTPUT("AOUT4L"),
    	SND_SOC_DAPM_OUTPUT("AOUT4R"),
    
    	SND_SOC_DAPM_ADC("ADC1", "Capture", PCM3168A_ADC_PWR_HPFB,
    			PCM3168A_ADC_PSVAD_SHIFT, 1),
    	SND_SOC_DAPM_ADC("ADC2", "Capture", PCM3168A_ADC_PWR_HPFB,
    			PCM3168A_ADC_PSVAD_SHIFT + 1, 1),
    	SND_SOC_DAPM_ADC("ADC3", "Capture", PCM3168A_ADC_PWR_HPFB,
    			PCM3168A_ADC_PSVAD_SHIFT + 2, 1),
    
    	SND_SOC_DAPM_INPUT("AIN1L"),
    	SND_SOC_DAPM_INPUT("AIN1R"),
    	SND_SOC_DAPM_INPUT("AIN2L"),
    	SND_SOC_DAPM_INPUT("AIN2R"),
    	SND_SOC_DAPM_INPUT("AIN3L"),
    	SND_SOC_DAPM_INPUT("AIN3R")
    };
    
    static const struct snd_soc_dapm_route pcm3168a_dapm_routes[] = {
    	/* Playback */
    	{ "AOUT1L", NULL, "DAC1" },
    	{ "AOUT1R", NULL, "DAC1" },
    
    	{ "AOUT2L", NULL, "DAC2" },
    	{ "AOUT2R", NULL, "DAC2" },
    
    	{ "AOUT3L", NULL, "DAC3" },
    	{ "AOUT3R", NULL, "DAC3" },
    
    	{ "AOUT4L", NULL, "DAC4" },
    	{ "AOUT4R", NULL, "DAC4" },
    
    	/* Capture */
    	{ "ADC1", NULL, "AIN1L" },
    	{ "ADC1", NULL, "AIN1R" },
    
    	{ "ADC2", NULL, "AIN2L" },
    	{ "ADC2", NULL, "AIN2R" },
    
    	{ "ADC3", NULL, "AIN3L" },
    	{ "ADC3", NULL, "AIN3R" }
    };
    
    static unsigned int pcm3168a_scki_ratios[] = {
    	768,
    	512,
    	384,
    	256,
    	192,
    	128
    };
    
    #define PCM3168A_NUM_SCKI_RATIOS_DAC	ARRAY_SIZE(pcm3168a_scki_ratios)
    #define PCM3168A_NUM_SCKI_RATIOS_ADC	(ARRAY_SIZE(pcm3168a_scki_ratios) - 2)
    
    #define PCM3168A_MAX_SYSCLK		36864000
    
    static int pcm3168a_reset(struct pcm3168a_priv *pcm3168a)
    {
    	int ret;
    
    	ret = regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, 0);
    	if (ret)
    		return ret;
    
    	/* Internal reset is de-asserted after 3846 SCKI cycles */
    	msleep(DIV_ROUND_UP(3846 * 1000, pcm3168a->sysclk));
    
    	return regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE,
    			PCM3168A_MRST_MASK | PCM3168A_SRST_MASK);
    }
    
    static int pcm3168a_digital_mute(struct snd_soc_dai *dai, int mute)
    {
    	struct snd_soc_component *component = dai->component;
    	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component);
    
    	regmap_write(pcm3168a->regmap, PCM3168A_DAC_MUTE, mute ? 0xff : 0);
    
    	return 0;
    }
    
    static int pcm3168a_set_dai_sysclk(struct snd_soc_dai *dai,
    				  int clk_id, unsigned int freq, int dir)
    {
    	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(dai->component);
    	int ret;
    
    	/*
    	 * Some sound card sets 0 Hz as reset,
    	 * but it is impossible to set. Ignore it here
    	 */
    	if (freq == 0)
    		return 0;
    
    	if (freq > PCM3168A_MAX_SYSCLK)
    		return -EINVAL;
    
    	ret = clk_set_rate(pcm3168a->scki, freq);
    	if (ret)
    		return ret;
    
    	pcm3168a->sysclk = freq;
    
    	return 0;
    }
    
    static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format)
    {
    	struct snd_soc_component *component = dai->component;
    	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component);
    	u32 fmt, reg, mask, shift;
    	bool master_mode;
    	printk("\n### %s format: %d\n",__FUNCTION__,format);
    	switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
    	case SND_SOC_DAIFMT_LEFT_J:
    		fmt = PCM3168A_FMT_LEFT_J;
    		break;
    	case SND_SOC_DAIFMT_I2S:
    		fmt = PCM3168A_FMT_I2S;
    		break;
    	case SND_SOC_DAIFMT_RIGHT_J:
    		fmt = PCM3168A_FMT_RIGHT_J;
    		break;
    	case SND_SOC_DAIFMT_DSP_A:
    		fmt = PCM3168A_FMT_DSP_A;
    		break;
    	case SND_SOC_DAIFMT_DSP_B:
    		fmt = PCM3168A_FMT_DSP_B;
    		break;
    	default:
    		dev_err(component->dev, "unsupported dai format\n");
    		return -EINVAL;
    	}
    
    	switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
    	case SND_SOC_DAIFMT_CBS_CFS:
    		master_mode = false;
    		break;
    	case SND_SOC_DAIFMT_CBM_CFM:
    		master_mode = true;
    		break;
    	default:
    		dev_err(component->dev, "unsupported master/slave mode\n");
    		return -EINVAL;
    	}
    
    	switch (format & SND_SOC_DAIFMT_INV_MASK) {
    	case SND_SOC_DAIFMT_NB_NF:
    		break;
    	default:
    		return -EINVAL;
    	}
    
    	if (dai->id == PCM3168A_DAI_DAC) {
    		reg = PCM3168A_DAC_PWR_MST_FMT;
    		mask = PCM3168A_DAC_FMT_MASK;
    		shift = PCM3168A_DAC_FMT_SHIFT;
    	} else {
    		reg = PCM3168A_ADC_MST_FMT;
    		mask = PCM3168A_ADC_FMTAD_MASK;
    		shift = PCM3168A_ADC_FMTAD_SHIFT;
    	}
    
    	pcm3168a->io_params[dai->id].master_mode = master_mode;
    	pcm3168a->io_params[dai->id].fmt = fmt;
    
    	regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift);
    
    	return 0;
    }
    
    static int pcm3168a_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
    				 unsigned int rx_mask, int slots,
    				 int slot_width)
    {
    	struct snd_soc_component *component = dai->component;
    	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component);
    	struct pcm3168a_io_params *io_params = &pcm3168a->io_params[dai->id];
    
    	if (tx_mask >= (1<<slots) || rx_mask >= (1<<slots)) {
    		dev_err(component->dev,
    			"Bad tdm mask tx: 0x%08x rx: 0x%08x slots %d\n",
    			tx_mask, rx_mask, slots);
    		return -EINVAL;
    	}
    
    	if (slot_width &&
    	    (slot_width != 16 && slot_width != 24 && slot_width != 32 )) {
    		dev_err(component->dev, "Unsupported slot_width %d\n",
    			slot_width);
    		return -EINVAL;
    	}
    
    	io_params->tdm_slots = slots;
    	io_params->slot_width = slot_width;
    	/* Ignore the not relevant mask for the DAI/direction */
    	if (dai->id == PCM3168A_DAI_DAC)
    		io_params->tdm_mask = tx_mask;
    	else
    		io_params->tdm_mask = rx_mask;
    
    	return 0;
    }
    
    static int pcm3168a_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 pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component);
    	struct pcm3168a_io_params *io_params = &pcm3168a->io_params[dai->id];
    	bool master_mode;
    	u32 val, mask, shift, reg;
    	unsigned int rate, fmt, ratio, max_ratio;
    	unsigned int tdm_slots;
    	int i, slot_width;
    
    	rate = params_rate(params);
    
    	ratio = pcm3168a->sysclk / rate;
    	printk("\n### %s sysclk %ld; rate %d; ratio %d\n",__FUNCTION__,pcm3168a->sysclk,rate,ratio);
    	if (dai->id == PCM3168A_DAI_DAC) {
    		max_ratio = PCM3168A_NUM_SCKI_RATIOS_DAC;
    		reg = PCM3168A_DAC_PWR_MST_FMT;
    		mask = PCM3168A_DAC_MSDA_MASK;
    		shift = PCM3168A_DAC_MSDA_SHIFT;
    	} else {
    		max_ratio = PCM3168A_NUM_SCKI_RATIOS_ADC;
    		reg = PCM3168A_ADC_MST_FMT;
    		mask = PCM3168A_ADC_MSAD_MASK;
    		shift = PCM3168A_ADC_MSAD_SHIFT;
    	}
    	printk("\n### %s master_mode %d; fmt %d; tdm_slots %d; tdm_mask %d; slot_width %d\n",__FUNCTION__,
    			io_params->master_mode,io_params->fmt,io_params->tdm_slots,io_params->tdm_mask,io_params->slot_width);
    
    	master_mode = io_params->master_mode;
    	fmt = io_params->fmt;
    
    	for (i = 0; i < max_ratio; i++) {
    		if (pcm3168a_scki_ratios[i] == ratio)
    			break;
    	}
    
    	if (i == max_ratio) {
    		dev_err(component->dev, "unsupported sysclk ratio\n");
    		return -EINVAL;
    	}
    
    	if (io_params->slot_width)
    		slot_width = io_params->slot_width;
    	else
    		slot_width = params_width(params);
    	printk("\n### %s master_mode %d; fmt %d; slot_width %d\n",__FUNCTION__,master_mode,fmt,slot_width);
    	switch (slot_width) {
    	case 16:
    		if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) {
    			dev_err(component->dev, "16-bit slots are supported only for slave mode using right justified\n");
    			return -EINVAL;
    		}
    		fmt = PCM3168A_FMT_RIGHT_J_16;
    		break;
    	case 24:
    		if (master_mode || (fmt & PCM3168A_FMT_DSP_MASK)) {
    			dev_err(component->dev, "24-bit slots not supported in master mode, or slave mode using DSP\n");
    			return -EINVAL;
    		}
    		break;
    	case 32:
    		break;
    	default:
    		dev_err(component->dev, "unsupported frame size: %d\n", slot_width);
    		return -EINVAL;
    	}
    
    	if (io_params->tdm_slots)
    		tdm_slots = io_params->tdm_slots;
    	else
    		tdm_slots = params_channels(params);
    	printk("\n### %s tdm_slots %d;\n",__FUNCTION__,tdm_slots);
    	/*
    	 * Switch the codec to TDM mode when more than 2 TDM slots are needed
    	 * for the stream.
    	 * If pcm3168a->tdm_slots is not set or set to more than 2 (8/6 usually)
    	 * then DIN1/DOUT1 is used in TDM mode.
    	 * If pcm3168a->tdm_slots is set to 2 then DIN1/2/3/4 and DOUT1/2/3 is
    	 * used in normal mode, no need to switch to TDM modes.
    	 */
    	if (tdm_slots > 2) {
    		switch (fmt) {
    		case PCM3168A_FMT_I2S:
    		case PCM3168A_FMT_DSP_A:
    			fmt = PCM3168A_FMT_I2S_TDM;
    			break;
    		case PCM3168A_FMT_LEFT_J:
    		case PCM3168A_FMT_DSP_B:
    			fmt = PCM3168A_FMT_LEFT_J_TDM;
    			break;
    		default:
    			dev_err(component->dev,
    				"TDM is supported under DSP/I2S/Left_J only\n");
    			return -EINVAL;
    		}
    	}
    
    	if (master_mode)
    		val = ((i + 1) << shift);
    	else
    		val = 0;
    
    	regmap_update_bits(pcm3168a->regmap, reg, mask, val);
    
    	if (dai->id == PCM3168A_DAI_DAC) {
    		mask = PCM3168A_DAC_FMT_MASK;
    		shift = PCM3168A_DAC_FMT_SHIFT;
    	} else {
    		mask = PCM3168A_ADC_FMTAD_MASK;
    		shift = PCM3168A_ADC_FMTAD_SHIFT;
    	}
    
    	regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift);
    
    	return 0;
    }
    
    static int pcm3168a_startup(struct snd_pcm_substream *substream,
    			    struct snd_soc_dai *dai)
    {
    	struct snd_soc_component *component = dai->component;
    	struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component);
    	unsigned int sample_min;
    	unsigned int channel_max;
    	unsigned int channel_maxs[] = {
    		8, /* DAC */
    		6  /* ADC */
    	};
    	printk("\n### %s dai_id %d, fmt %d; tdm_slots %d\n",__FUNCTION__,dai->id,pcm3168a->io_params[dai->id].fmt,pcm3168a->io_params[dai->id].tdm_slots);
    	/*
    	 * Available Data Bits
    	 *
    	 * RIGHT_J : 24 / 16
    	 * LEFT_J  : 24
    	 * I2S     : 24
    	 *
    	 * TDM available
    	 *
    	 * I2S
    	 * LEFT_J
    	 */
    	switch (pcm3168a->io_params[dai->id].fmt) {
    	case PCM3168A_FMT_RIGHT_J:
    		sample_min  = 16;
    		channel_max =  2;
    		break;
    	case PCM3168A_FMT_LEFT_J:
    	case PCM3168A_FMT_I2S:
    	case PCM3168A_FMT_DSP_A:
    	case PCM3168A_FMT_DSP_B:
    		sample_min  = 24;
    		channel_max = channel_maxs[dai->id];
    		break;
    	default:
    		sample_min  = 24;
    		channel_max =  2;
    	}
    
    	snd_pcm_hw_constraint_minmax(substream->runtime,
    				     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
    				     sample_min, 32);
    
    	/* Allow all channels in multi DIN/DOUT mode */
    	if (pcm3168a->io_params[dai->id].tdm_slots == 2)
    		channel_max = channel_maxs[dai->id];
    
    	snd_pcm_hw_constraint_minmax(substream->runtime,
    				     SNDRV_PCM_HW_PARAM_CHANNELS,
    				     2, channel_max);
    
    	return 0;
    }
    static const struct snd_soc_dai_ops pcm3168a_dai_ops = {
    	.startup	= pcm3168a_startup,
    	.set_fmt	= pcm3168a_set_dai_fmt,
    	.set_sysclk	= pcm3168a_set_dai_sysclk,
    	.hw_params	= pcm3168a_hw_params,
    	.digital_mute	= pcm3168a_digital_mute,
    	.set_tdm_slot	= pcm3168a_set_tdm_slot,
    };
    
    static struct snd_soc_dai_driver pcm3168a_dais[] = {
    	{
    		.name = "pcm3168a-dac",
    		.id = PCM3168A_DAI_DAC,
    		.playback = {
    			.stream_name = "Playback",
    			.channels_min = 1,
    			.channels_max = 8,
    			.rates = SNDRV_PCM_RATE_8000_192000,
    			.formats = PCM3168A_FORMATS
    		},
    		.ops = &pcm3168a_dai_ops
    	},
    	{
    		.name = "pcm3168a-adc",
    		.id = PCM3168A_DAI_ADC,
    		.capture = {
    			.stream_name = "Capture",
    			.channels_min = 1,
    			.channels_max = 6,
    			.rates = SNDRV_PCM_RATE_8000_96000,
    			.formats = PCM3168A_FORMATS
    		},
    		.ops = &pcm3168a_dai_ops
    	},
    };
    
    static const struct reg_default pcm3168a_reg_default[] = {
    	{ PCM3168A_RST_SMODE, PCM3168A_MRST_MASK | PCM3168A_SRST_MASK },
    	{ PCM3168A_DAC_PWR_MST_FMT, 0x00 },
    	{ PCM3168A_DAC_OP_FLT, 0x00 },
    	{ PCM3168A_DAC_INV, 0x00 },
    	{ PCM3168A_DAC_MUTE, 0x00 },
    	{ PCM3168A_DAC_ZERO, 0x00 },
    	{ PCM3168A_DAC_ATT_DEMP_ZF, 0x00 },
    	{ PCM3168A_DAC_VOL_MASTER, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 1, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 2, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 3, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 4, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 5, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 6, 0xff },
    	{ PCM3168A_DAC_VOL_CHAN_START + 7, 0xff },
    	{ PCM3168A_ADC_SMODE, 0x00 },
    	{ PCM3168A_ADC_MST_FMT, 0x00 },
    	{ PCM3168A_ADC_PWR_HPFB, 0x00 },
    	{ PCM3168A_ADC_SEAD, 0x00 },
    	{ PCM3168A_ADC_INV, 0x00 },
    	{ PCM3168A_ADC_MUTE, 0x00 },
    	{ PCM3168A_ADC_OV, 0x00 },
    	{ PCM3168A_ADC_ATT_OVF, 0x00 },
    	{ PCM3168A_ADC_VOL_MASTER, 0xd3 },
    	{ PCM3168A_ADC_VOL_CHAN_START, 0xd3 },
    	{ PCM3168A_ADC_VOL_CHAN_START + 1, 0xd3 },
    	{ PCM3168A_ADC_VOL_CHAN_START + 2, 0xd3 },
    	{ PCM3168A_ADC_VOL_CHAN_START + 3, 0xd3 },
    	{ PCM3168A_ADC_VOL_CHAN_START + 4, 0xd3 },
    	{ PCM3168A_ADC_VOL_CHAN_START + 5, 0xd3 }
    };
    
    static bool pcm3168a_readable_register(struct device *dev, unsigned int reg)
    {
    	if (reg >= PCM3168A_RST_SMODE)
    		return true;
    	else
    		return false;
    }
    
    static bool pcm3168a_volatile_register(struct device *dev, unsigned int reg)
    {
    	switch (reg) {
    	case PCM3168A_DAC_ZERO:
    	case PCM3168A_ADC_OV:
    		return true;
    	default:
    		return false;
    	}
    }
    
    static bool pcm3168a_writeable_register(struct device *dev, unsigned int reg)
    {
    	if (reg < PCM3168A_RST_SMODE)
    		return false;
    
    	switch (reg) {
    	case PCM3168A_DAC_ZERO:
    	case PCM3168A_ADC_OV:
    		return false;
    	default:
    		return true;
    	}
    }
    
    const struct regmap_config pcm3168a_regmap = {
    	.reg_bits = 8,
    	.val_bits = 8,
    
    	.max_register = PCM3168A_ADC_VOL_CHAN_START + 5,
    	.reg_defaults = pcm3168a_reg_default,
    	.num_reg_defaults = ARRAY_SIZE(pcm3168a_reg_default),
    	.readable_reg = pcm3168a_readable_register,
    	.volatile_reg = pcm3168a_volatile_register,
    	.writeable_reg = pcm3168a_writeable_register,
    	.cache_type = REGCACHE_FLAT
    };
    EXPORT_SYMBOL_GPL(pcm3168a_regmap);
    
    static const struct snd_soc_component_driver pcm3168a_driver = {
    	.controls		= pcm3168a_snd_controls,
    	.num_controls		= ARRAY_SIZE(pcm3168a_snd_controls),
    	.dapm_widgets		= pcm3168a_dapm_widgets,
    	.num_dapm_widgets	= ARRAY_SIZE(pcm3168a_dapm_widgets),
    	.dapm_routes		= pcm3168a_dapm_routes,
    	.num_dapm_routes	= ARRAY_SIZE(pcm3168a_dapm_routes),
    	.use_pmdown_time	= 1,
    	.endianness		= 1,
    	.non_legacy_dai_naming	= 1,
    };
    
    int pcm3168a_probe(struct device *dev, struct regmap *regmap)
    {
    	struct pcm3168a_priv *pcm3168a;
    	int ret, i;
    
    	pcm3168a = devm_kzalloc(dev, sizeof(*pcm3168a), GFP_KERNEL);
    	if (pcm3168a == NULL)
    		return -ENOMEM;
    
    	dev_set_drvdata(dev, pcm3168a);
    
    	pcm3168a->scki = devm_clk_get(dev, "scki");
    	if (IS_ERR(pcm3168a->scki)) {
    		ret = PTR_ERR(pcm3168a->scki);
    		if (ret != -EPROBE_DEFER)
    			dev_err(dev, "failed to acquire clock 'scki': %d\n", ret);
    		return ret;
    	}
    
    	ret = clk_prepare_enable(pcm3168a->scki);
    	if (ret) {
    		dev_err(dev, "Failed to enable mclk: %d\n", ret);
    		return ret;
    	}
    
    	pcm3168a->sysclk = clk_get_rate(pcm3168a->scki);
    
    	for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++)
    		pcm3168a->supplies[i].supply = pcm3168a_supply_names[i];
    
    	ret = devm_regulator_bulk_get(dev,
    			ARRAY_SIZE(pcm3168a->supplies), pcm3168a->supplies);
    	if (ret) {
    		if (ret != -EPROBE_DEFER)
    			dev_err(dev, "failed to request supplies: %d\n", ret);
    		goto err_clk;
    	}
    
    	ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies),
    				    pcm3168a->supplies);
    	if (ret) {
    		dev_err(dev, "failed to enable supplies: %d\n", ret);
    		goto err_clk;
    	}
    
    	pcm3168a->regmap = regmap;
    	if (IS_ERR(pcm3168a->regmap)) {
    		ret = PTR_ERR(pcm3168a->regmap);
    		dev_err(dev, "failed to allocate regmap: %d\n", ret);
    		goto err_regulator;
    	}
    
    	ret = pcm3168a_reset(pcm3168a);
    	if (ret) {
    		dev_err(dev, "Failed to reset device: %d\n", ret);
    		goto err_regulator;
    	}
    
    	pm_runtime_set_active(dev);
    	pm_runtime_enable(dev);
    	pm_runtime_idle(dev);
    
    	ret = devm_snd_soc_register_component(dev, &pcm3168a_driver, pcm3168a_dais,
    			ARRAY_SIZE(pcm3168a_dais));
    	if (ret) {
    		dev_err(dev, "failed to register component: %d\n", ret);
    		goto err_regulator;
    	}
    
    	return 0;
    
    err_regulator:
    	regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies),
    			pcm3168a->supplies);
    err_clk:
    	clk_disable_unprepare(pcm3168a->scki);
    
    	return ret;
    }
    EXPORT_SYMBOL_GPL(pcm3168a_probe);
    
    static void pcm3168a_disable(struct device *dev)
    {
    	struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev);
    
    	regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies),
    			       pcm3168a->supplies);
    	clk_disable_unprepare(pcm3168a->scki);
    }
    
    void pcm3168a_remove(struct device *dev)
    {
    	pm_runtime_disable(dev);
    #ifndef CONFIG_PM
    	pcm3168a_disable(dev);
    #endif
    }
    EXPORT_SYMBOL_GPL(pcm3168a_remove);
    
    #ifdef CONFIG_PM
    static int pcm3168a_rt_resume(struct device *dev)
    {
    	struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev);
    	int ret;
    
    	ret = clk_prepare_enable(pcm3168a->scki);
    	if (ret) {
    		dev_err(dev, "Failed to enable mclk: %d\n", ret);
    		return ret;
    	}
    
    	ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies),
    				    pcm3168a->supplies);
    	if (ret) {
    		dev_err(dev, "Failed to enable supplies: %d\n", ret);
    		goto err_clk;
    	}
    
    	ret = pcm3168a_reset(pcm3168a);
    	if (ret) {
    		dev_err(dev, "Failed to reset device: %d\n", ret);
    		goto err_regulator;
    	}
    
    	regcache_cache_only(pcm3168a->regmap, false);
    
    	regcache_mark_dirty(pcm3168a->regmap);
    
    	ret = regcache_sync(pcm3168a->regmap);
    	if (ret) {
    		dev_err(dev, "Failed to sync regmap: %d\n", ret);
    		goto err_regulator;
    	}
    
    	return 0;
    
    err_regulator:
    	regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies),
    			       pcm3168a->supplies);
    err_clk:
    	clk_disable_unprepare(pcm3168a->scki);
    
    	return ret;
    }
    
    static int pcm3168a_rt_suspend(struct device *dev)
    {
    	struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev);
    
    	regcache_cache_only(pcm3168a->regmap, true);
    
    	pcm3168a_disable(dev);
    
    	return 0;
    }
    #endif
    
    const struct dev_pm_ops pcm3168a_pm_ops = {
    	SET_RUNTIME_PM_OPS(pcm3168a_rt_suspend, pcm3168a_rt_resume, NULL)
    };
    EXPORT_SYMBOL_GPL(pcm3168a_pm_ops);
    
    MODULE_DESCRIPTION("PCM3168A codec driver");
    MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
    MODULE_LICENSE("GPL v2");
    
    pcm3168a.h
    pcm3168a-i2c.c
    // SPDX-License-Identifier: GPL-2.0-only
    /*
     * PCM3168A codec i2c driver
     *
     * Copyright (C) 2015 Imagination Technologies Ltd.
     *
     * Author: Damien Horsley <Damien.Horsley@imgtec.com>
     */
    
    #include <linux/i2c.h>
    #include <linux/init.h>
    #include <linux/module.h>
    
    #include <sound/soc.h>
    
    #include "pcm3168a.h"
    
    static int pcm3168a_i2c_probe(struct i2c_client *i2c,
    			     const struct i2c_device_id *id)
    {
    	struct regmap *regmap;
    
    	regmap = devm_regmap_init_i2c(i2c, &pcm3168a_regmap);
    	if (IS_ERR(regmap))
    		return PTR_ERR(regmap);
    
    	return pcm3168a_probe(&i2c->dev, regmap);
    }
    
    static int pcm3168a_i2c_remove(struct i2c_client *i2c)
    {
    	pcm3168a_remove(&i2c->dev);
    
    	return 0;
    }
    
    static const struct i2c_device_id pcm3168a_i2c_id[] = {
    	{ "pcm3168a", },
    	{ }
    };
    MODULE_DEVICE_TABLE(i2c, pcm3168a_i2c_id);
    
    static const struct of_device_id pcm3168a_of_match[] = {
    	{ .compatible = "ti,pcm3168a", },
    	{ }
    };
    MODULE_DEVICE_TABLE(of, pcm3168a_of_match);
    
    static struct i2c_driver pcm3168a_i2c_driver = {
    	.probe		= pcm3168a_i2c_probe,
    	.remove		= pcm3168a_i2c_remove,
    	.id_table	= pcm3168a_i2c_id,
    	.driver		= {
    		.name	= "pcm3168a",
    		.of_match_table = pcm3168a_of_match,
    		.pm		= &pcm3168a_pm_ops,
    	},
    };
    module_i2c_driver(pcm3168a_i2c_driver);
    
    MODULE_DESCRIPTION("PCM3168A I2C codec driver");
    MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
    MODULE_LICENSE("GPL v2");
    
    pcm3168a-spi.c
    // SPDX-License-Identifier: GPL-2.0-only
    /*
     * PCM3168A codec spi driver
     *
     * Copyright (C) 2015 Imagination Technologies Ltd.
     *
     * Author: Damien Horsley <Damien.Horsley@imgtec.com>
     */
    
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/spi/spi.h>
    
    #include <sound/soc.h>
    
    #include "pcm3168a.h"
    
    static int pcm3168a_spi_probe(struct spi_device *spi)
    {
    	struct regmap *regmap;
    
    	regmap = devm_regmap_init_spi(spi, &pcm3168a_regmap);
    	if (IS_ERR(regmap))
    		return PTR_ERR(regmap);
    
    	return pcm3168a_probe(&spi->dev, regmap);
    }
    
    static int pcm3168a_spi_remove(struct spi_device *spi)
    {
    	pcm3168a_remove(&spi->dev);
    
    	return 0;
    }
    
    static const struct spi_device_id pcm3168a_spi_id[] = {
    	{ "pcm3168a", },
    	{ },
    };
    MODULE_DEVICE_TABLE(spi, pcm3168a_spi_id);
    
    static const struct of_device_id pcm3168a_of_match[] = {
    	{ .compatible = "ti,pcm3168a", },
    	{ }
    };
    MODULE_DEVICE_TABLE(of, pcm3168a_of_match);
    
    static struct spi_driver pcm3168a_spi_driver = {
    	.probe		= pcm3168a_spi_probe,
    	.remove		= pcm3168a_spi_remove,
    	.id_table	= pcm3168a_spi_id,
    	.driver = {
    		.name	= "pcm3168a",
    		.of_match_table = pcm3168a_of_match,
    		.pm		= &pcm3168a_pm_ops,
    	},
    };
    module_spi_driver(pcm3168a_spi_driver);
    
    MODULE_DESCRIPTION("PCM3168A SPI codec driver");
    MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
    MODULE_LICENSE("GPL v2");
    

  • Let's wait for Shenghao to review and get back to you.

  • 1-Can I use the PCM3168A to have the 6 independent audios, like I need?

    Do you mean record into 6 different wav file simultaneously? If so, the answer

    is no. Because the clk source is the same, it can record independently multiple different wav file.

  • As to 24-bit, in pcm3168a_hw_params, the code as following.

    case 24:
    if (master_mode || (fmt & PCM3168A_FMT_DSP_MASK)) {
    dev_err(component->dev, "24-bit slots not supported in master mode, or slave mode using DSP\n");
    return -EINVAL;
    }

    Is the audio clk from master the DSP mode instead of I2S?

    see following bold line

    sound-bt-sco {
    compatible = "simple-audio-card";
    simple-audio-card,name = "bt-sco-audio";
    simple-audio-card,format = "dsp_a";  ??????
    simple-audio-card,bitclock-inversion;
    simple-audio-card,frame-master = <&btcpu>;
    simple-audio-card,bitclock-master = <&btcpu>;

    btcpu: simple-audio-card,cpu {
    sound-dai = <&sai2>;
    dai-tdm-slot-num = <2>;
    dai-tdm-slot-width = <16>;
    };

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

  • Hi Shenghao,

    I was able to play audio on all speakers and I can play different audio on each output channel (this last one scenario, I had to create different "devices" in .asoundrc).

    asoundrc.txt
    pcm.spk0 {       
            type dmix     
            ipc_key 234884
            slave {             
                    pcm "hw:4,0"
                    channels 6   
                    format S24_LE
                    rate 48000
            }           
            bindings.0 0
    }   
    pcm.spk1 {
            type dmix
            ipc_key 234884
            slave {
                    pcm "hw:4,0"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 1
    }
    pcm.spk2 {
            type dmix
            ipc_key 234884
            slave {
                    pcm "hw:4,0"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 2
    }
    pcm.spk3 {
            type dmix
            ipc_key 234884
            slave {
                    pcm "hw:4,0"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 3
    }
    pcm.spk4 {
            type dmix
            ipc_key 234884
            slave {
                    pcm "hw:4,0"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 4
    }
    pcm.spk5 {
            type dmix
            ipc_key 234884
            slave {
                    pcm "hw:4,0"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 5
    }
    pcm.spk6 {
            type dmix
            ipc_key 234884
            slave {
                    pcm "hw:4,0"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 6
    }
    
    
    
    pcm.mic1 {
            type dsnoop
            ipc_key 234884
            slave {
                    pcm "hw:4,1"
                    channels 8
                    format S24_LE
                    rate 48000
            }
            bindings.0 1
    }
    pcm.mic2 {
            type dsnoop
            ipc_key 234884
            slave {
                    pcm "hw:4,1"
                    channels 6
                    format S24_LE
                    rate 48000
            }
            bindings.0 2
    }
    pcm.mic3 {
            type dsnoop
            ipc_key 234884
            slave {
                    pcm "hw:4,1"
                    channels 6
                    format S24_LE
                    rate 48000
            }
            bindings.0 3
    }
    pcm.mic4 {
            type dsnoop
            ipc_key 234884
            slave {
                    pcm "hw:4,1"
                    channels 6
                    format S24_LE
                    rate 48000
            }
            bindings.0 4
    }
    pcm.mic5 {
            type dsnoop
            ipc_key 234884
            slave {
                    pcm "hw:4,1"
                    channels 6
                    format S24_LE
                    rate 48000
            }
            bindings.0 5
    }
    pcm.mic6 {
            type dsnoop
            ipc_key 234884
            slave {
                    pcm "hw:4,1"
                    channels 6
                    format S24_LE
                    rate 48000
            }
            bindings.0 6
    }
    
    

    To play audio, I needed to comment out the lines that are in bold:

    sound-pcm3168a {
            compatible = "simple-audio-card";
            simple-audio-card,name = "PCM3168A";

            simple-audio-card,widgets =    "Speaker","Channel1out","Speaker","Channel2out","Speaker","Channel3out","Speaker","Channel4out","Speaker","Channel5out","Speaker", "Channel6out",
                        "Microphone","Channel1in","Microphone","Channel2in","Microphone","Channel3in","Microphone","Channel4in","Microphone","Channel5in","Microphone","Channel6in";
         simple-audio-card,routing =     "Channel1out","AOUT1L","Channel2out","AOUT1R","Channel3out","AOUT2L","Channel4out","AOUT2R","Channel5out","AOUT3L","Channel6out","AOUT3R",
                                                                     "Channel1in","AIN1L","Channel2in","AIN1R","Channel3in","AIN2L","Channel4in","AIN2R","Channel5in","AIN3L","Channel6in","AIN3R";
            simple-audio-card,dai-link@0 {
                            format = "left_j";
                            
                    /*bitclock-master = <&pcm3168a_dac>;
                    frame-master = <&pcm3168a_dac>;*/
                        cpu {
                                sound-dai = <&sai1>;
                            dai-tdm-slot-num = <6>;
                            dai-tdm-slot-width = <32>;
                        };
                        pcm3168a_dac: codec {
                            sound-dai = <&pcm3168a 0>;
                            clocks = <&clk IMX8MM_CLK_SAI1_ROOT>;
                        };
         };
                simple-audio-card,dai-link@1 {
                            format = "left_j";
                             /*bitclock-master = <&pcm3168a_adc>;
                            frame-master = <&pcm3168a_adc>;*/
                            cpu {
                                    sound-dai = <&sai1>;
                                    dai-tdm-slot-num = <8>;
                                    dai-tdm-slot-width = <32>;
                            };
                            pcm3168a_adc: codec {
                                    sound-dai = <&pcm3168a 1>;
                                    clocks = <&clk IMX8MM_CLK_SAI1_ROOT>;
                            };
         };
        };

    Now i'm struggling with recording audio. I can record to wav files, but what I hear is silence.

    arecord -D plug:mic1 -r 48000 -f S24_LE  mic1.wav
    [ 1107.914919] ### pcm3168a_rt_resume
    [ 1107.916124] ### pcm3168a_reset
    [ 1107.945269] ### pcm3168a_startup dai_id 1, fmt 1; tdm_slots 0
    [ 1107.949856] ### pcm3168a_hw_params sysclk 12288000; rate 48000; ratio 256
    [ 1107.956926] ### pcm3168a_hw_params master_mode 0; fmt 1; tdm_slots 0; tdm_mask 0; slot_width 0
    [ 1107.964976] ### pcm3168a_hw_params master_mode 0; fmt 1; slot_width 24
    Recording WAVE 'mic1.wav' : Signed 24 bit Little Endian, Rate 48000 Hz, Mono
    ^CAborted by signal Interrupt...
    [ 1107.974835] ### pcm3168a_hw_params tdm_slots 6;

    [ 1121.627309] ### pcm3168a_rt_suspend

    Looks like some configuration is missing.

    Regards,

    Cesário

  • Kindly dump the register during recording.

    Command like following, don't forget the "&"

    arecord -D plug:mic1 -r 48000 -f S24_LE  mic1.wav &

    I2cdump -fy 1 i2c_addr

  • i2cdump -fy 2 0x44
    No size specified (using byte-data access)
    WARNING! This program can confuse your I2C bus, cause data loss and worse!
    I will probe file /dev/i2c-2, address 0x44, mode byte
    Continue? [Y/n]
    0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
    00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    40: c0 01 f0 00 00 00 00 d7 ff ff ff ff ff ff ff ff ???....?........
    50: 00 07 00 00 00 00 00 00 d7 d7 d7 d7 d7 d7 d7 00 .?......???????.
    60: 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 00 .......?........
    70: 00 90 3f 04 00 2a 2a 2a 2a 2a 2a 00 85 00 00 00 .???.******.?...
    80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
    f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

  • Hi Shenghao,

    I have extracted this register dump and add the comment there.

    Just for info the ADC is of 24-bit Left Justify TDM format.

    config1.xlsx

    Regards.