TAC5212 Linux Driver (aplay: pcm_write:2058: write error: Input/output error)

Part Number: TAC5212

The Linux driver for the TAC series is still not released to the kernel. submission have been rejected a few times for various reasons. There is a conversation going on  here: https://lkml.org/lkml/2026/3/12/1924
 
I have attempted to build the driver out of kernel source as a module for a 64 bit ARM processor that is using a 5.10.140 kernel. however with the SDK generated from Yocto Dunfell  problems occur
 
1. Sources are for newer kernels ( probe and remove driver calls changed  -> fixed this)
2. No out of kernel tree makefile provided. -> attempted to fix, but follow error occurs:
3. kernel compatibility issues : inaccessible plugin file /sdk/dunfell-5.5/sysroots/x86_64-pokysdk-linux/usr/lib/aarch64-poky-linux/gcc/aarch64-poky-linux/9.3.0/plugin/arm_ssp_per_task_plugin.so expanded from short plugin name arm_ssp_per_task_plugin: No such file or directory
 
Is there any help that can be provided to get this driver built as a module in or out the kernel source for 5.10 kernels ?
Which one is the driver code to use ? 
Another link to source code here  with two .c files(most often the links have a 301/503 but 1 in 20 the files can be accessed): git.ti.com/.../  
Another fork or, it seems unofficial, tree here that uses one .c file github.com/.../tac5x1x-linux-driver
To keep on going, moved the driver in the kernel 5,10.140 source three . The per task plugin" errors are now gone, But it resulted in a failed build with errors any many warning.  Attached the results


ound/soc/codecs/tac5x1x.c: In function ‘tac5x1x_set_dai_fmt’:
sound/soc/codecs/tac5x1x.c:938:30: warning: unused variable ‘tac5x1x’ [-Wunused-variable]
  938 |         struct tac5x1x_priv *tac5x1x = snd_soc_component_get_drvdata(component);
      |                              ^~~~~~~
sound/soc/codecs/tac5x1x.c: At top level:
sound/soc/codecs/tac5x1x.c:1101:10: error: ‘struct snd_soc_dai_driver’ has no member named ‘symmetric_rate’; did you mean ‘symmetric_rates’?
 1101 |         .symmetric_rate = 1,
      |          ^~~~~~~~~~~~~~
      |          symmetric_rates
sound/soc/codecs/tac5x1x.c:1101:27: warning: initialization of ‘const struct snd_soc_cdai_ops *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
 1101 |         .symmetric_rate = 1,
      |                           ^
sound/soc/codecs/tac5x1x.c:1101:27: note: (near initialization for ‘tac5x1x_dai.cops’)
sound/soc/codecs/tac5x1x.c: In function ‘tac5x1x_add_controls’:
sound/soc/codecs/tac5x1x.c:1224:35: warning: division ‘sizeof (int *) / sizeof (int)’ does not compute the number of array elements [-Wsizeof-pointer-div]
 1224 |                 sizeof(gpio_func) / sizeof(gpio_func[0]))) {
      |                                   ^
sound/soc/codecs/tac5x1x.c:1182:14: note: first ‘sizeof’ operand was declared here
 1182 |         int *gpio_func = tac5x1x->gpio_setup->gpio_func;
      |              ^~~~~~~~~
sound/soc/codecs/tac5x1x.c: In function ‘tac5x1x_add_widgets’:
sound/soc/codecs/tac5x1x.c:1278:35: warning: division ‘sizeof (int *) / sizeof (int)’ does not compute the number of array elements [-Wsizeof-pointer-div]
 1278 |                 sizeof(gpio_func) / sizeof(gpio_func[0]))) {
      |                                   ^
sound/soc/codecs/tac5x1x.c:1240:14: note: first ‘sizeof’ operand was declared here
 1240 |         int *gpio_func = tac5x1x->gpio_setup->gpio_func;
      |              ^~~~~~~~~
sound/soc/codecs/tac5x1x.c: In function ‘tac5x1x_soc_resume’:
sound/soc/codecs/tac5x1x.c:1330:9: warning: ignoring return value of ‘regulator_enable’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
 1330 |         regulator_enable(tac5x1x->supply_avdd);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sound/soc/codecs/tac5x1x.c:1331:9: warning: ignoring return value of ‘regulator_enable’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
 1331 |         regulator_enable(tac5x1x->supply_iovdd);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sound/soc/codecs/tac5x1x.c: In function ‘tac5x1x_add_controls’:
sound/soc/codecs/tac5x1x.c:1209:20: warning: this statement may fall through [-Wimplicit-fallthrough=]
 1209 |                 if (ret)
      |                    ^
sound/soc/codecs/tac5x1x.c:1211:9: note: here
 1211 |         case TAC5312:
      |         ^~~~
make[3]: *** [scripts/Makefile.build:286: sound/soc/codecs/tac5x1x.o] Error 1
make[2]: *** [scripts/Makefile.build:503: sound/soc/codecs] Error 2
make[1]: *** [scripts/Makefile.build:503: sound/soc] Error 2
make: *** [Makefile:1836: sound] Error 2

After  making some changes in the probe , remove cals (because someone decided to change that for later kernels) and fixing the seerrors (but all warning are still there), building the driver in tree as a module, and loading following errors at insmod snd-soc-tac5x1x.ko
After this, the device can be seen on the I2C address 0x51. i2cdetect -y)  as "UU"  (note 0x50 is another device)
The driver seems also picky on the regulator entries. Even with dummy regulators  in the device tree it reports it is using the dummy regulator, also trie do change the driver regulars check s but at no solution.. Not sure this has anything to do with the driver to stay deferred state if is uses regulator status to defer it's function or not.
The  guidance for the dts example is an RPI overlay, was referenced but could not fix the defer state. There is also a distinction to use for sound either the sound simple codec driver or the driver from the platform, any suggestions ?
There is another post here used on a RPI that shows that be branches in the git have significant bug fixes. so not sure what branch or code should work or to be used ?
in the git there is a  branch 5.15. this version in a single .c file  instead of the two separate .c file in the master branch.
Hhowever when building this also  large amount of errors occur, so this does not even build.
sound/soc/codecs/tac5x1x-i2c.c:1311:23: error: ‘SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK’ undeclared (first use in this function); did you mean ‘SND_SOC_DAIFMT_CLOCK_MASK’?
 1311 |         switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                       SND_SOC_DAIFMT_CLOCK_MASK
sound/soc/codecs/tac5x1x-i2c.c:1311:23: note: each undeclared identifier is reported only once for each function it appears in
sound/soc/codecs/tac5x1x-i2c.c:1312:14: error: ‘SND_SOC_DAIFMT_CBP_CFP’ undeclared (first use in this function); did you mean ‘SND_SOC_DAIFMT_CBS_CFM’?
 1312 |         case SND_SOC_DAIFMT_CBP_CFP:
      |              ^~~~~~~~~~~~~~~~~~~~~~
      |              SND_SOC_DAIFMT_CBS_CFM
sound/soc/codecs/tac5x1x-i2c.c:1315:14: error: ‘SND_SOC_DAIFMT_CBC_CFC’ undeclared (first use in this function); did you mean ‘SND_SOC_DAIFMT_CBS_CFM’?
 1315 |         case SND_SOC_DAIFMT_CBC_CFC:
      |              ^~~~~~~~~~~~~~~~~~~~~~
      |              SND_SOC_DAIFMT_CBS_CFM
sound/soc/codecs/tac5x1x-i2c.c: At top level:
sound/soc/codecs/tac5x1x-i2c.c:1522:10: error: ‘struct snd_soc_dai_driver’ has no member named ‘symmetric_rate’; did you mean ‘symmetric_rates’?
 1522 |         .symmetric_rate = 1,
      |          ^~~~~~~~~~~~~~
      |          symmetric_rates
sound/soc/codecs/tac5x1x-i2c.c:1522:27: warning: initialization of ‘const struct snd_soc_cdai_ops *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
 1522 |         .symmetric_rate = 1,
      |                           ^
sound/soc/codecs/tac5x1x-i2c.c:1522:27: note: (near initialization for ‘tac5x1x_dai.cops’)
sound/soc/codecs/tac5x1x-i2c.c:1535:10: error: ‘struct snd_soc_dai_driver’ has no member named ‘symmetric_rate’; did you mean ‘symmetric_rates’?
 1535 |         .symmetric_rate = 1,
      |          ^~~~~~~~~~~~~~
      |          symmetric_rates
sound/soc/codecs/tac5x1x-i2c.c:1535:27: warning: initialization of ‘const struct snd_soc_cdai_ops *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
 1535 |         .symmetric_rate = 1,
      |                           ^
sound/soc/codecs/tac5x1x-i2c.c:1535:27: note: (near initialization for ‘taa5x1x_dai.cops’)
sound/soc/codecs/tac5x1x-i2c.c:1555:10: error: ‘struct snd_soc_dai_driver’ has no member named ‘symmetric_rate’; did you mean ‘symmetric_rates’?
 1555 |         .symmetric_rate = 1,
      |          ^~~~~~~~~~~~~~
      |          symmetric_rates
sound/soc/codecs/tac5x1x-i2c.c:1555:27: warning: initialization of ‘const struct snd_soc_cdai_ops *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
 1555 |         .symmetric_rate = 1,
      |                           ^
sound/soc/codecs/tac5x1x-i2c.c:1555:27: note: (near initialization for ‘tad5x1x_dai.cops’)

While this branch does take into account kernel 5 proble and remove differences, it does not consider the changes in soc-sai. Which are based on their values different in kernel 5.10.140
It may be better to just create different branches for different kernel versions instead of a once for all ? that would maybe more work to maintain, but that is how the Linux kernel itself does it as well 
unless of-course the differences are minor. but then the changes should be tested on all (popular) kernel versions. 

new Kernel (6, 5.15?) versus kernel 5.10.140 soc-dai.h
=======================================
SND_SOC_DAIFMT_CLOCK_MASTER_MASK => SND_SOC_DAIFMT_MASTER_MASK
SND_SOC_DAIFMT_CBP_CFP => SND_SOC_DAIFMT_CBM_CFM    
SND_SOC_DAIFMT_CBC_CFC => SND_SOC_DAIFMT_CBS_CFS
Also this branch is grumpy with the regulators as devm_regulator_bulk_get(dev, tac5x1x_num_regulators,     tac5x1x_regulators); fails,
Something with the device tree but can't seem to change the DT so that the drivers likes it.
Added debug statement in the driver
these nodes iovdd-supply and avdd-supply are defined in the the DTs. The regulator are checked after the tac5x1x_reset() call so it seems the device is detected, however regulators are failing
Some progress made, first the fixed regulators and  I2S block were not configured for the kernel. After that the codec is found by aplay and asound/proc . amixer shows all controls but when the driver loads it shows the following and this errors also is shown  when starting aplay to play a file.
the main issues seems that the bias cannot cannot be turned on
Mixer command seem to work normally
AI suggested to check devmem (not sure if related). AI suggested to add following into unsezure zone but did not fix it : <I2S1_TZNS>,   /* Marks I2S1 as TrustZone Non-Secure for CODEC */
<I2S1_SUBM>,   // Allows the non-secure Linux kernel to own the I2S1 data path pins. <PDMA1_TZNS>, 
The Bias error comes from the sound kernel  source, not the specific MPU or TAC audio driver
./sound/soc/soc-dapm.c:                "ASoC: Failed to turn on bias: %d\n", ret);
First request to TI is guidance to  rule out those parts that should not be related to this issue:
1. The ASoC drivers (inclusing soc-dapm.c power management which seem to be change for newer kernels)
2. The TAC5x1x driver (here branch 5.15 is used, not master branch which appears to be the original Android older source code)
3. The MPU I2S/CODEC driver
4. The MPU SOC hardware
5. The TAC5212 I2S hardware interface to the MPU SoC
6. The TAC 5212 I2C hardware interface to the MPU SoC
7. The TAC5212 hardware circuit itself
Traced this back to problem with the code in the audio driver. 
> soc-component.c => ret = component->driver->set_bias_level(component, level);   (fails for level 1)
> tac5x1x_set_bias_level(struct snd_soc_component *component,  enum snd_soc_bias_level level)
> ret = tac5x1x_pwr_ctrl(component, true);
/soc-dapm.h: SND_SOC_BIAS_ON = 3, OFF = 0  STANDBY=1, PREPARE=2 
static s32 tac5x1x_set_bias_level(struct snd_soc_component *component,
                  enum snd_soc_bias_level level)
    case SND_SOC_BIAS_PREPARE:
        break;
    case SND_SOC_BIAS_STANDBY:
        break;
return ret.
The driver does nothing for power value of  STANDBY(1) BUT ! it returns an uninitialized ret value. That is why the dmseg error shown is weird.
I changed it to ret=0;  (please don't do such things)
Perhaps this message has nothing to do with the "Invalid argument" problem with aplay ?
Looking trough the driver code there at least twp more instances where a switch statement is used, no defaults and uninitialized return values are returned:
static int tac5x1x_adc_event(struct snd_soc_dapm_widget *w,    struct snd_kcontrol *kcontrol, int event)
static int tac5x1x_dac_event(struct snd_soc_dapm_widget *w,     struct snd_kcontrol *kcontrol, int event)
it a bit hard to trace back but the pull requests to the official Linux kernel seems to come from  https://github.com/SolidRun/tac5x1x-linux-driver   
The issues mentioned here also exist in that tree.
Perhaps another kernel 5.10 branch could be created  and also enable issue reports on the git ?
The BIAS error is now gone.
However aplay -D plughw:0:0  sample-12s.wav stills return audio open error: invalid argument.
amixer -c 0 cset iface=MIXER,name='ASI_RX_CH1_EN Switch' on
amixer -c 0 cset iface=MIXER,name='OUT1x Source' 'DAC Input’
amixer -c 0 cset iface=MIXER,name='OUT1x Config' 'Differential’
amixer -c 0 cset iface=MIXER,name='OUT1x Driver' 'Line-out’
amixer -c 0 cset iface=MIXER,name='ASI_RX_CH2_EN Switch' on
amixer -c 0 cset iface=MIXER,name=‘OUT2x Source' 'DAC Input’
amixer -c 0 cset iface=MIXER,name=‘OUT2x Config' 'Differential’
amixer -c 0 cset iface=MIXER,name=‘OUT2x Driver' 'Line-o'
aplay -c 2 -f S16_LE -r 44100 -d 15 --device='hw:0,0' pure_tone_44.1K_16b_stereo.wav

returns "audio open error: invalid argument."
The invalid argument is returned by aplay utility, which calls the ALSA snd_pcm_open function in alsa-lib.
int snd_pcm_open(snd_pcm_t **pcmp, const char *name,
snd_pcm_stream_t stream, int mode)
{
snd_config_t *top;
int err;

assert(pcmp && name);
if (_snd_is_ucm_device(name)) {
name = uc_mgr_alibcfg_by_device(&top, name);
if (name == NULL)
return -ENODEV;
} else {
err = snd_config_update_ref(&top);
if (err < 0)
return err;
}
err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
snd_config_unref(top);
return err;
}
==========================================================================
===== FROM HERE USING AN UPDATED KERNEL 5.10.140 FROM PLATFORM PROVIDER ====
============================================================================
With an updated kernel (still 5.10.140) from the platform vendors, a bit more progress. 

# insmod snd-soc-tac5x1x.ko
[  203.607549] tac5x1x-codec 2-0051: TAC5x1x driver is looking for supply name: 'iovdd'
[  203.615250] tac5x1x-codec 2-0051: TAC5x1x driver is looking for supply name: 'avdd'
# [  203.666981] tac5x1x-codec 2-0051: TAC5x1x using I2S format
[  203.689895] tac5x1x-codec 2-0051: TAC5x1x bias level set to  1
[  203.695703] tac5x1x-codec 2-0051: TAC5x1x set power level STANDBY ignored

# sh test_stereo.sh
numid=57,iface=MIXER,name='ASI_RX_CH1_EN Switch'
  ; type=BOOLEAN,access=rw------,values=1
  : values=on
numid=54,iface=MIXER,name='OUT1x Source'
  ; type=ENUMERATED,access=rw------,values=1,items=6
  ; Item #0 'Disabled'
  ; Item #1 'DAC Input'
  ; Item #2 'Analog Bypass'
  ; Item #3 'DAC + Analog Bypass Mix'
  ; Item #4 'DAC -> OUTxP, INxP -> OUTxM'
  ; Item #5 'INxM -> OUTxP, DAC -> OUTxM'
  : values=0
numid=56,iface=MIXER,name='OUT1x Driver'
  ; type=ENUMERATED,access=rw------,values=1,items=4
  ; Item #0 'Line-out'
  ; Item #1 'He[  210.077928] tac5x1x-codec 2-0051: TAC5x1x using sample rate of 44100 Hz
adphone'
  ; It[  210.085707] tac5x1x-codec 2-0051: TAC5x1x word length is 16 bits
em #2 '4 ohm'
  ; Item #3 'FD Receiver/Debug'
  : values=0
Playing WAVE 'pure_tone_44.1K_16b_stereo.wav' : Signed 16 bit Little Endian, Rate [  210.105426] i2s_clk: 200000000
44100 Hz, Mono
[  210.108566] ma35d1_audio->pdm_decimation: 0
[  210.114119] sample_rate: 44100 bitrate: 1411200
[  210.118628] bclkdiv: 70, mclkdiv: 8
[  210.122089] i2s_clk=200000000
[  210.125035] sample_rate=44100 channels=1 width=16
[  210.129715] target_bclk=1411200 bclkdiv=70 actual_bclk=1408450 error_ppm=-1948
[  210.136925] target_mclk=12000000 mclkdiv=8 actual_mclk=12500000
[  210.142808] I2S_CLKDIV=0x00004608
[  210.146323] tac5x1x-codec 2-0051: TAC5x1x bias level set to  2
[  210.152126] tac5x1x-codec 2-0051: TAC5x1x set power level PREPARE ignored
[  210.166451] tac5x1x-codec 2-0051: TAC5x1x bias level set to  3
[  210.172235] tac5x1x-codec 2-0051: TAC5x1x set power level UP
aplay: pcm_write:2058: write error: Input/output error
# 

Added a few debug statement in the driver to be able to see what is going on.
Things to notice are
1. The drive sets I2S (correct)
2. Bit size 16 bit (correct) and sample rate is correct 44.1K
3. the CODEC is powered up be a PREPARE and then a ON
4. The requested clock is 12MHZ, but something reports it's changed to 12.5MHZ ? Would this case an issue ?
5. The Input/Output error how up  after a few seconds of starting the playback. 
6. The commands to play is:
amixer -c 0 cset iface=MIXER,name='ASI_RX_CH1_EN Switch' on
amixer -c 0 cset iface=MIXER,name='OUT1x Source' 'DAC Input’
amixer -c 0 cset iface=MIXER,name='OUT1x Config' 'Differential’
amixer -c 0 cset iface=MIXER,name='OUT1x Driver' 'Line-out’
amixer -c 0 cset iface=MIXER,name='ASI_RX_CH2_EN Switch' on
amixer -c 0 cset iface=MIXER,name=‘OUT2x Source' 'DAC Input’
amixer -c 0 cset iface=MIXER,name=‘OUT2x Config' 'Differential’
amixer -c 0 cset iface=MIXER,name=‘OUT2x Driver' 'Line-out'
aplay -c 2 -f S16_LE -r 44100 -d 15 --device='hw:0,0' pure_tone_44.1K_16b_stereo.wav
7. The I2S clock is 200000000 ? (200 MHz? )
The following shows up always after 6 seconds, after starting the aplay file (could be because loading the file?)
Red is from aplay, blue and purple added debug output statement in the tac5x1x driver 
aplay: pcm_write[ 375.714443] tac5x1x_set_sysclk 0 0 0
:2058: write err[ 375.719156] tac5x1x-codec 2-0051: recover cclk from bclk
or: Input/output error