TAS5825MEVM: TAS5825M (GLOBAL_FAULT1 bit2 set)

Part Number: TAS5825MEVM
Other Parts Discussed in Thread: TAS5805M, TAS5825M,

Tool/software:

After getting the TAS5805M to work as a TAS5825M Linux driver, I keep getting the audio amplifier status of 

[ 34.663767] REG_GLOBAL_FAULT1(global1 = 0x4), read OK: 0

The register GLOBAL_FAULT (0x71) with bit2 set implies CLK_FAULT_I.

I am not sure what's generating this fault but the register defintion stated as the following:


Clock fault. Once there is a Clock fault, this bit sets to be 1. Class D
output sets to Hi-Z. Report by FAULT pin (GPIO). Clock fault works
with an auto-recovery mode, once the clock error removes, device
automatically returns to the previous state.
Clear this fault by setting bit 7 of Section 9.6.1.57 to 1 or this bit
keeps 1.

I have tried to clear this but it's still there after the clear.

My I2S 3 signals are connected to the TAS5825M eval board, BCLK, LRCLK(SYNC), and SDIN1.

Any ideas as to why this bit is constantly set?  When it is set, it puts the amplifier output into Hi-Z which doesn't output any audio.

Regards,

Tom

6835.tas5825m.c
// SPDX-License-Identifier: GPL-2.0
//
// Driver for the TAS5825M Audio Amplifier
//
// Author: Andy Liu <andy-liu@ti.com>
// Author: Daniel Beer <daniel.beer@igorinstitute.com>
//
// This is based on a driver originally written by Andy Liu at TI and
// posted here:
//
//    https://e2e.ti.com/support/audio-group/audio/f/audio-forum/722027/linux-tas5825m-linux-drivers
//
// It has been simplified a little and reworked for the 5.x ALSA SoC API.

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/atomic.h>
#include <linux/workqueue.h>

#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/tlv.h>

#include "stereo_flow2_48kHz_default_coldboot_-10dB.h"

/* Datasheet-defined registers on page 0, book 0 */
#define REG_PAGE		0x00
#define REG_DEVICE_CTRL_1	0x02
#define REG_DEVICE_CTRL_2	0x03
#define REG_SIG_CH_CTRL		0x28
#define REG_SAP_CTRL_1		0x33
#define REG_FS_MON		0x37
#define REG_BCK_MON		0x38
#define REG_CLKDET_STATUS	0x39
#define REG_VOL_CTL		0x4c
#define REG_AGAIN		0x54
#define REG_ADR_PIN_CTRL	0x60
#define REG_ADR_PIN_CONFIG	0x61
#define REG_CHAN_FAULT		0x70
#define REG_GLOBAL_FAULT1	0x71
#define REG_GLOBAL_FAULT2	0x72
#define REG_FAULT		0x78
#define REG_BOOK		0x7f

#define TAS5825M_RATES				(SNDRV_PCM_RATE_8000_192000)
#define TAS5825M_FORMATS			(SNDRV_PCM_FMTBIT_S16_LE|SNDRV_PCM_FMTBIT_S20_3LE|SNDDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE)

/* DEVICE_CTRL_2 register values */
#define DCTRL2_MODE_DEEP_SLEEP	0x00
#define DCTRL2_MODE_SLEEP	0x01
#define DCTRL2_MODE_HIZ		0x02
#define DCTRL2_MODE_PLAY	0x03

#define DCTRL2_MUTE		0x08
#define DCTRL2_DIS_DSP		0x10

/* This sequence of register writes must always be sent, prior to the
 * 5ms delay while we wait for the DSP to boot.
 */
static const uint8_t dsp_cfg_preboot[] = {
	0x00, 0x00, 0x7f, 0x00, 0x03, 0x02, 0x01, 0x10,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x7f, 0x00, 0x03, 0x02,
};

static const uint8_t dsp_cfg_firmware_missing[] = {
	0x00, 0x00, 0x7f, 0x00, 0x54, 0x03, 0x78, 0x80,
};

static const uint32_t tas5825m_volume[] = {
	0x0000001B, /*   0, -110dB */ 0x0000001E, /*   1, -109dB */
	0x00000021, /*   2, -108dB */ 0x00000025, /*   3, -107dB */
	0x0000002A, /*   4, -106dB */ 0x0000002F, /*   5, -105dB */
	0x00000035, /*   6, -104dB */ 0x0000003B, /*   7, -103dB */
	0x00000043, /*   8, -102dB */ 0x0000004B, /*   9, -101dB */
	0x00000054, /*  10, -100dB */ 0x0000005E, /*  11,  -99dB */
	0x0000006A, /*  12,  -98dB */ 0x00000076, /*  13,  -97dB */
	0x00000085, /*  14,  -96dB */ 0x00000095, /*  15,  -95dB */
	0x000000A7, /*  16,  -94dB */ 0x000000BC, /*  17,  -93dB */
	0x000000D3, /*  18,  -92dB */ 0x000000EC, /*  19,  -91dB */
	0x00000109, /*  20,  -90dB */ 0x0000012A, /*  21,  -89dB */
	0x0000014E, /*  22,  -88dB */ 0x00000177, /*  23,  -87dB */
	0x000001A4, /*  24,  -86dB */ 0x000001D8, /*  25,  -85dB */
	0x00000211, /*  26,  -84dB */ 0x00000252, /*  27,  -83dB */
	0x0000029A, /*  28,  -82dB */ 0x000002EC, /*  29,  -81dB */
	0x00000347, /*  30,  -80dB */ 0x000003AD, /*  31,  -79dB */
	0x00000420, /*  32,  -78dB */ 0x000004A1, /*  33,  -77dB */
	0x00000532, /*  34,  -76dB */ 0x000005D4, /*  35,  -75dB */
	0x0000068A, /*  36,  -74dB */ 0x00000756, /*  37,  -73dB */
	0x0000083B, /*  38,  -72dB */ 0x0000093C, /*  39,  -71dB */
	0x00000A5D, /*  40,  -70dB */ 0x00000BA0, /*  41,  -69dB */
	0x00000D0C, /*  42,  -68dB */ 0x00000EA3, /*  43,  -67dB */
	0x0000106C, /*  44,  -66dB */ 0x0000126D, /*  45,  -65dB */
	0x000014AD, /*  46,  -64dB */ 0x00001733, /*  47,  -63dB */
	0x00001A07, /*  48,  -62dB */ 0x00001D34, /*  49,  -61dB */
	0x000020C5, /*  50,  -60dB */ 0x000024C4, /*  51,  -59dB */
	0x00002941, /*  52,  -58dB */ 0x00002E49, /*  53,  -57dB */
	0x000033EF, /*  54,  -56dB */ 0x00003A45, /*  55,  -55dB */
	0x00004161, /*  56,  -54dB */ 0x0000495C, /*  57,  -53dB */
	0x0000524F, /*  58,  -52dB */ 0x00005C5A, /*  59,  -51dB */
	0x0000679F, /*  60,  -50dB */ 0x00007444, /*  61,  -49dB */
	0x00008274, /*  62,  -48dB */ 0x0000925F, /*  63,  -47dB */
	0x0000A43B, /*  64,  -46dB */ 0x0000B845, /*  65,  -45dB */
	0x0000CEC1, /*  66,  -44dB */ 0x0000E7FB, /*  67,  -43dB */
	0x00010449, /*  68,  -42dB */ 0x0001240C, /*  69,  -41dB */
	0x000147AE, /*  70,  -40dB */ 0x00016FAA, /*  71,  -39dB */
	0x00019C86, /*  72,  -38dB */ 0x0001CEDC, /*  73,  -37dB */
	0x00020756, /*  74,  -36dB */ 0x000246B5, /*  75,  -35dB */
	0x00028DCF, /*  76,  -34dB */ 0x0002DD96, /*  77,  -33dB */
	0x00033718, /*  78,  -32dB */ 0x00039B87, /*  79,  -31dB */
	0x00040C37, /*  80,  -30dB */ 0x00048AA7, /*  81,  -29dB */
	0x00051884, /*  82,  -28dB */ 0x0005B7B1, /*  83,  -27dB */
	0x00066A4A, /*  84,  -26dB */ 0x000732AE, /*  85,  -25dB */
	0x00081385, /*  86,  -24dB */ 0x00090FCC, /*  87,  -23dB */
	0x000A2ADB, /*  88,  -22dB */ 0x000B6873, /*  89,  -21dB */
	0x000CCCCD, /*  90,  -20dB */ 0x000E5CA1, /*  91,  -19dB */
	0x00101D3F, /*  92,  -18dB */ 0x0012149A, /*  93,  -17dB */
	0x00144961, /*  94,  -16dB */ 0x0016C311, /*  95,  -15dB */
	0x00198A13, /*  96,  -14dB */ 0x001CA7D7, /*  97,  -13dB */
	0x002026F3, /*  98,  -12dB */ 0x00241347, /*  99,  -11dB */
	0x00287A27, /* 100,  -10dB */ 0x002D6A86, /* 101,  -9dB */
	0x0032F52D, /* 102,   -8dB */ 0x00392CEE, /* 103,   -7dB */
	0x004026E7, /* 104,   -6dB */ 0x0047FACD, /* 105,   -5dB */
	0x0050C336, /* 106,   -4dB */ 0x005A9DF8, /* 107,   -3dB */
	0x0065AC8C, /* 108,   -2dB */ 0x00721483, /* 109,   -1dB */
	0x00800000, /* 110,    0dB */ 0x008F9E4D, /* 111,    1dB */
	0x00A12478, /* 112,    2dB */ 0x00B4CE08, /* 113,    3dB */
	0x00CADDC8, /* 114,    4dB */ 0x00E39EA9, /* 115,    5dB */
	0x00FF64C1, /* 116,    6dB */ 0x011E8E6A, /* 117,    7dB */
	0x0141857F, /* 118,    8dB */ 0x0168C0C6, /* 119,    9dB */
	0x0194C584, /* 120,   10dB */ 0x01C62940, /* 121,   11dB */
	0x01FD93C2, /* 122,   12dB */ 0x023BC148, /* 123,   13dB */
	0x02818508, /* 124,   14dB */ 0x02CFCC01, /* 125,   15dB */
	0x0327A01A, /* 126,   16dB */ 0x038A2BAD, /* 127,   17dB */
	0x03F8BD7A, /* 128,   18dB */ 0x0474CD1B, /* 129,   19dB */
	0x05000000, /* 130,   20dB */ 0x059C2F02, /* 131,   21dB */
	0x064B6CAE, /* 132,   22dB */ 0x07100C4D, /* 133,   23dB */
	0x07ECA9CD, /* 134,   24dB */ 0x08E43299, /* 135,   25dB */
	0x09F9EF8E, /* 136,   26dB */ 0x0B319025, /* 137,   27dB */
	0x0C8F36F2, /* 138,   28dB */ 0x0E1787B8, /* 139,   29dB */
	0x0FCFB725, /* 140,   30dB */ 0x11BD9C84, /* 141,   31dB */
	0x13E7C594, /* 142,   32dB */ 0x16558CCB, /* 143,   33dB */
	0x190F3254, /* 144,   34dB */ 0x1C1DF80E, /* 145,   35dB */
	0x1F8C4107, /* 146,   36dB */ 0x2365B4BF, /* 147,   37dB */
	0x27B766C2, /* 148,   38dB */ 0x2C900313, /* 149,   39dB */
	0x32000000, /* 150,   40dB */ 0x3819D612, /* 151,   41dB */
	0x3EF23ECA, /* 152,   42dB */ 0x46A07B07, /* 153,   43dB */
	0x4F3EA203, /* 154,   44dB */ 0x58E9F9F9, /* 155,   45dB */
	0x63C35B8E, /* 156,   46dB */ 0x6FEFA16D, /* 157,   47dB */
	0x7D982575, /* 158,   48dB */
};


#define TAS5825M_VOLUME_MAX	((int)ARRAY_SIZE(tas5825m_volume) - 1)
#define TAS5825M_VOLUME_MIN	0

struct tas5825m_priv {
	struct i2c_client		*i2c;
	struct regulator		*pvdd;
	struct gpio_desc		*gpio_pdn_n;

	uint8_t				*dsp_cfg_data;
	int				dsp_cfg_len;

	struct regmap			*regmap;

	int				vol[2];
	bool				is_powered;
	bool				is_muted;

	struct work_struct		work;
	struct mutex			lock;
};

static void set_dsp_scale(struct regmap *rm, int offset, int vol)
{
	uint8_t v[4];
	uint32_t x = tas5825m_volume[vol];
	int i;

	for (i = 0; i < 4; i++) {
		v[3 - i] = x;
		x >>= 8;
	}

	regmap_bulk_write(rm, offset, v, ARRAY_SIZE(v));
}

static void tas5825m_refresh(struct tas5825m_priv *tas5825m)
{
	struct regmap *rm = tas5825m->regmap;
	int ret;

	dev_dbg(&tas5825m->i2c->dev, "refresh: is_muted=%d, vol=%d/%d\n",
		tas5825m->is_muted, tas5825m->vol[0], tas5825m->vol[1]);

	ret = regmap_write(rm, REG_PAGE, 0x00);
	if(ret != 0) {
		printk(KERN_INFO "REG_PAGE(0x00) write error: %d\n", ret);
	} else {
		printk(KERN_INFO "REG_PAGE(0x00) write OK: %d\n", ret);
	}
	ret = regmap_write(rm, REG_BOOK, 0x8c);
	if(ret != 0) {
		printk(KERN_INFO "REG_BOOK(0x8c) write error: %d\n", ret);
	} else {
		printk(KERN_INFO "REG_BOOK(0x8c) write OK: %d\n", ret);
	}
	
	ret = regmap_write(rm, REG_PAGE, 0x2a);
	if(ret != 0) {
		printk(KERN_INFO "REG_PAGE(0x2a) write error: %d\n", ret);
	} else {
		printk(KERN_INFO "REG_PAGE(0x2a) write OK: %d\n", ret);
	}

	/* Refresh volume. The actual volume control documented in the
	 * datasheet doesn't seem to work correctly. This is a pair of
	 * DSP registers which are *not* documented in the datasheet.
	 */
	set_dsp_scale(rm, 0x24, tas5825m->vol[0]);
	set_dsp_scale(rm, 0x28, tas5825m->vol[1]);

	ret = regmap_write(rm, REG_PAGE, 0x00);
	if(ret != 0) {
		printk(KERN_INFO "REG_PAGE(0x00) write error: %d\n", ret);
	} else {
		printk(KERN_INFO "REG_PAGE(0x00) write OK: %d\n", ret);
	}
	
	ret = regmap_write(rm, REG_BOOK, 0x00);
	if(ret != 0) {
		printk(KERN_INFO "REG_BOOK(0x00) write error: %d\n", ret);
	} else {
		printk(KERN_INFO "REG_BOOK(0x00) write OK: %d\n", ret);
	}
	/* Set/clear digital soft-mute */
	ret = regmap_write(rm, REG_DEVICE_CTRL_2,
		(tas5825m->is_muted ? DCTRL2_MUTE : 0) |
		DCTRL2_MODE_PLAY);
	if(ret != 0) {
		printk(KERN_INFO "REG_DEVICE_CTRL_2(muted = %d) write error: %d\n", tas5825m->is_muted, ret);
	} else {
		printk(KERN_INFO "REG_DEVICE_CTRL_2(muted = %d) write OK: %d\n", tas5825m->is_muted, ret);
	}		
}

static int tas5825m_vol_info(struct snd_kcontrol *kcontrol,
			     struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;

	uinfo->value.integer.min = TAS5825M_VOLUME_MIN;
	uinfo->value.integer.max = TAS5825M_VOLUME_MAX;
	return 0;
}

static int tas5825m_vol_get(struct snd_kcontrol *kcontrol,
			    struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component =
		snd_soc_kcontrol_component(kcontrol);
	struct tas5825m_priv *tas5825m =
		snd_soc_component_get_drvdata(component);

	mutex_lock(&tas5825m->lock);
	ucontrol->value.integer.value[0] = tas5825m->vol[0];
	ucontrol->value.integer.value[1] = tas5825m->vol[1];
	mutex_unlock(&tas5825m->lock);

	return 0;
}

static inline int volume_is_valid(int v)
{
	return (v >= TAS5825M_VOLUME_MIN) && (v <= TAS5825M_VOLUME_MAX);
}

static int tas5825m_vol_put(struct snd_kcontrol *kcontrol,
			    struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component =
		snd_soc_kcontrol_component(kcontrol);
	struct tas5825m_priv *tas5825m =
		snd_soc_component_get_drvdata(component);
	int ret = 0;

	if (!(volume_is_valid(ucontrol->value.integer.value[0]) &&
	      volume_is_valid(ucontrol->value.integer.value[1])))
		return -EINVAL;

	mutex_lock(&tas5825m->lock);
	if (tas5825m->vol[0] != ucontrol->value.integer.value[0] ||
	    tas5825m->vol[1] != ucontrol->value.integer.value[1]) {
		tas5825m->vol[0] = ucontrol->value.integer.value[0];
		tas5825m->vol[1] = ucontrol->value.integer.value[1];
		dev_dbg(component->dev, "set vol=%d/%d (is_powered=%d)\n",
			tas5825m->vol[0], tas5825m->vol[1],
			tas5825m->is_powered);
		if (tas5825m->is_powered)
			tas5825m_refresh(tas5825m);
		ret = 1;
	}
	mutex_unlock(&tas5825m->lock);

	return ret;
}

static const struct snd_kcontrol_new tas5825m_snd_controls[] = {
	{
		.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
		.name	= "Master Playback Volume",
		.access	= SNDRV_CTL_ELEM_ACCESS_TLV_READ |
			  SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info	= tas5825m_vol_info,
		.get	= tas5825m_vol_get,
		.put	= tas5825m_vol_put,
	},
};

static void send_cfg(struct regmap *rm,
		     const uint8_t *s, unsigned int len)
{
	unsigned int i;
	int ret;

	for (i = 0; i + 1 < len; i += 2) {
		ret = regmap_write(rm, s[i], s[i + 1]);
		if(ret != 0) {
			printk(KERN_INFO "s[%d]= %d, write error: %d\n", i, s[i], ret);
		} else {
			printk(KERN_INFO "s[%d]= %d, write OK: %d\n", i, s[i], ret);
		}	
		
	}
}


static void send_cfg_setup(struct regmap *rm, const struct reg_sequence *rg, unsigned int reg_seq_len)
{
	unsigned int i;
	int ret;

	for (i = 0; i< reg_seq_len; i++) {
		ret = regmap_write(rm, rg[i].reg, rg[i].def);
		if(ret != 0) {
			printk(KERN_INFO "rg[%d].reg = %x, rg[%d].def = %x: write error: %d\n", i, rg[i].reg, i, rg[i].def, ret);
		} else {
			printk(KERN_INFO "rg[%d].reg = %x, rg[%d].def = %x: write OK: %d\n", i, rg[i].reg, i, rg[i].def, ret);
		}	
		
	}
}

/* The TAS5825M DSP can't be configured until the I2S clock has been
 * present and stable for 5ms, or else it won't boot and we get no
 * sound.
 */
static int tas5825m_trigger(struct snd_pcm_substream *substream, int cmd,
			    struct snd_soc_dai *dai)
{
	struct snd_soc_component *component = dai->component;
	struct tas5825m_priv *tas5825m =
		snd_soc_component_get_drvdata(component);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		dev_dbg(component->dev, "clock start\n");
		schedule_work(&tas5825m->work);
		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static void do_work(struct work_struct *work)
{
	struct tas5825m_priv *tas5825m =
	       container_of(work, struct tas5825m_priv, work);
	struct regmap *rm = tas5825m->regmap;

	//printk(KERN_INFO "DSP startup\n");

	dev_dbg(&tas5825m->i2c->dev, "DSP startup\n");

	mutex_lock(&tas5825m->lock);
	/* We mustn't issue any I2C transactions until the I2S
	 * clock is stable. Furthermore, we must allow a 5ms
	 * delay after the first set of register writes to
	 * allow the DSP to boot before configuring it.
	 */
	usleep_range(5000, 10000);
	send_cfg(rm, dsp_cfg_preboot, ARRAY_SIZE(dsp_cfg_preboot));
	usleep_range(5000, 15000);

	// TOM TRAN
	if(tas5825m->dsp_cfg_data) {
		send_cfg(rm, tas5825m->dsp_cfg_data, tas5825m->dsp_cfg_len);
	} else {
      int ret;
		//send_cfg(rm, dsp_cfg_firmware_missing, ARRAY_SIZE(dsp_cfg_firmware_missing));
		printk(KERN_ERR "====> SENDING INIT SEQUENCE\n");
		ret = regmap_register_patch(rm, tas5825m_init_sequence, ARRAY_SIZE(tas5825m_init_sequence));
		if (ret != 0)
		{
			printk(KERN_ERR "tas5825m_work_handler: failed to initialize TAS5825M: %d\n",ret);
			return;
		}		
	}
	tas5825m->is_powered = true;
	tas5825m_refresh(tas5825m);
 	mutex_unlock(&tas5825m->lock);
	printk(KERN_CRIT "TAS5825 Powered ON\n");

}

static int tas5825m_dac_event(struct snd_soc_dapm_widget *w,
			      struct snd_kcontrol *kcontrol, int event)
{
	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
	struct tas5825m_priv *tas5825m =
		snd_soc_component_get_drvdata(component);
	struct regmap *rm = tas5825m->regmap;
	int ret;

	//printk(KERN_ERR "%s(): ENTER\n", __func__);
	if (event & SND_SOC_DAPM_PRE_PMD) {
		unsigned int chan, global1, global2;

		dev_dbg(component->dev, "DSP shutdown\n");
		cancel_work_sync(&tas5825m->work);

		mutex_lock(&tas5825m->lock);
		if (tas5825m->is_powered) {
			tas5825m->is_powered = false;

			ret = regmap_write(rm, REG_PAGE, 0x00);
			if(ret != 0) {
				printk(KERN_INFO "REG_PAGE(0x0) write error: %d\n", ret);
			} else {
				printk(KERN_INFO "REG_PAGE(0x0) write OK: %d\n", ret);
			}				
			ret = regmap_write(rm, REG_BOOK, 0x00);

			ret = regmap_read(rm, REG_CHAN_FAULT, &chan);
			if(ret != 0) {
				printk(KERN_INFO "REG_CHAN_FAULT(chan = 0x%x), read error: %d\n", chan, ret);
			} else {
				printk(KERN_INFO "REG_CHAN_FAULT(chan = 0x%x), read OK: %d\n", chan, ret);
			}				

			ret = regmap_read(rm, REG_GLOBAL_FAULT1, &global1);
			if(ret != 0) {
				printk(KERN_INFO "REG_GLOBAL_FAULT1(global1 = 0x%x), read error: %d\n", global1, ret);
			} else {
				printk(KERN_INFO "REG_GLOBAL_FAULT1(global1 = 0x%x), read OK: %d\n", global1, ret);
            //if(global1) {
            //   regmap_write(rm, REG_FAULT, 0x80);
            //}
            //mdelay(10);
            //ret = regmap_read(rm, REG_GLOBAL_FAULT1, &global1);
				//printk(KERN_INFO "After fault clear: REG_GLOBAL_FAULT1(global1 = %d), read OK: %d\n", global1, ret);            
			}	
			ret = regmap_read(rm, REG_GLOBAL_FAULT2, &global2);
			if(ret != 0) {
				printk(KERN_INFO "REG_GLOBAL_FAULT2(global2 = 0x%x), read error: %d\n", global2, ret);
			} else {
				printk(KERN_INFO "REG_GLOBAL_FAULT2(global2 = 0x%x), read OK: %d\n", global2, ret);
         }
         
			dev_dbg(component->dev, "fault regs: CHAN=%02x, "
				"GLOBAL1=%02x, GLOBAL2=%02x\n",
				chan, global1, global2);

			regmap_write(rm, REG_DEVICE_CTRL_2, DCTRL2_MODE_HIZ);
			if(ret != 0) {
				printk(KERN_INFO "REG_DEVICE_CTRL_2(DCTRL2_MODE_HIZ), write error: %d\n", ret);
			} else {
				printk(KERN_INFO "REG_DEVICE_CTRL_2(DCTRL2_MODE_HIZ), write OK: %d\n", ret);
			}	
		}
		mutex_unlock(&tas5825m->lock);
	}
	//printk(KERN_ERR "%s(): EXIT\n", __func__);
	return 0;
}

static const struct snd_soc_dapm_route tas5825m_audio_map[] = {
	{ "DAC", NULL, "DAC IN" },
	{ "OUT", NULL, "DAC" },
};

static const struct snd_soc_dapm_widget tas5825m_dapm_widgets[] = {
	SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0),
	SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0,
		tas5825m_dac_event, SND_SOC_DAPM_PRE_PMD),
	SND_SOC_DAPM_OUTPUT("OUT")
};

static const struct snd_soc_component_driver soc_codec_dev_tas5825m = {
	.controls		= tas5825m_snd_controls,
	.num_controls		= ARRAY_SIZE(tas5825m_snd_controls),
	.dapm_widgets		= tas5825m_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(tas5825m_dapm_widgets),
	.dapm_routes		= tas5825m_audio_map,
	.num_dapm_routes	= ARRAY_SIZE(tas5825m_audio_map),
	.use_pmdown_time	= 1,
	.endianness		= 1,
};

static int tas5825m_mute(struct snd_soc_dai *dai, int mute, int direction)
{
	struct snd_soc_component *component = dai->component;
	struct tas5825m_priv *tas5825m =
		snd_soc_component_get_drvdata(component);

	mutex_lock(&tas5825m->lock);
	dev_dbg(component->dev, "set mute=%d (is_powered=%d)\n",
		mute, tas5825m->is_powered);

	tas5825m->is_muted = mute;
	if (tas5825m->is_powered)
		tas5825m_refresh(tas5825m);
	mutex_unlock(&tas5825m->lock);

	return 0;
}

static const struct snd_soc_dai_ops tas5825m_dai_ops = {
	.trigger		= tas5825m_trigger,
	.mute_stream		= tas5825m_mute,
	.no_capture_mute	= 1,
};

static struct snd_soc_dai_driver tas5825m_dai = {
	.name		= "tas5825m-amplifier",
	.playback	= {
		.stream_name	= "Playback",
		.channels_min	= 2,
		.channels_max	= 2,
		.rates		= SNDRV_PCM_RATE_48000,
		.formats	= SNDRV_PCM_FMTBIT_S32_LE,
	},
	.ops		= &tas5825m_dai_ops,
};

static const struct regmap_config tas5825m_regmap = {
	.reg_bits	= 8,
	.val_bits	= 8,

	/* We have quite a lot of multi-level bank switching and a
	 * relatively small number of register writes between bank
	 * switches.
	 */
	.cache_type	= REGCACHE_NONE,
};

static int tas5825m_i2c_probe(struct i2c_client *i2c)
{
	struct device *dev = &i2c->dev;
	struct regmap *regmap;
	struct tas5825m_priv *tas5825m;
	char filename[128];
	const char *config_name;
	const struct firmware *fw;
	int ret;

	printk(KERN_INFO "%s(): =================================================\n", __func__);

	printk(KERN_INFO "%s(): ENTER\n", __func__);

	regmap = devm_regmap_init_i2c(i2c, &tas5825m_regmap);
	if (IS_ERR(regmap)) {
		printk(KERN_INFO "%s(): init_i2c regmap failed\n", __func__);
	ret = PTR_ERR(regmap);
		dev_err(dev, "unable to allocate register map: %d\n", ret);
		return ret;
	}

	tas5825m = devm_kzalloc(dev, sizeof(struct tas5825m_priv), GFP_KERNEL);
	if (!tas5825m)
		return -ENOMEM;

	tas5825m->i2c = i2c;
	printk(KERN_CRIT "TOM TRAN loading TAS5825M\n");
	printk(KERN_CRIT "I2C address: 0x%.2x\n", i2c->addr);


	tas5825m->pvdd = devm_regulator_get(dev, "pvdd");
	if (IS_ERR(tas5825m->pvdd)) {
		dev_err(dev, "failed to get pvdd supply: %ld\n",
			PTR_ERR(tas5825m->pvdd));
		return PTR_ERR(tas5825m->pvdd);
	}

	dev_set_drvdata(dev, tas5825m);
	tas5825m->regmap = regmap;
	tas5825m->gpio_pdn_n = devm_gpiod_get(dev, "pdn", GPIOD_OUT_LOW);
	if (IS_ERR(tas5825m->gpio_pdn_n)) {
		dev_err(dev, "error requesting PDN gpio: %ld\n",
			PTR_ERR(tas5825m->gpio_pdn_n));
		return PTR_ERR(tas5825m->gpio_pdn_n);
	}


	/* This configuration must be generated by PPC3. The file loaded
	 * consists of a sequence of register writes, where bytes at
	 * even indices are register addresses and those at odd indices
	 * are register values.
	 *
	 * The fixed portion of PPC3's output prior to the 5ms delay
	 * should be omitted.
	 */
	if (device_property_read_string(dev, "ti,dsp-config-name",
					&config_name))
		config_name = "default";

	snprintf(filename, sizeof(filename), "tas5825m_dsp_%s.bin",
		 config_name);
	ret = request_firmware(&fw, filename, dev);
	if (ret)
		goto err;

	if ((fw->size < 2) || (fw->size & 1)) {
		dev_err(dev, "firmware is invalid\n");
		release_firmware(fw);
		goto err;
	}

	tas5825m->dsp_cfg_len = fw->size;
	tas5825m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL);
	if (!tas5825m->dsp_cfg_data) {
		release_firmware(fw);
		goto err;
	}

	release_firmware(fw);

err:

	/* Do the first part of the power-on here, while we can expect
	 * the I2S interface to be quiet. We must raise PDN# and then
	 * wait 5ms before any I2S clock is sent, or else the internal
	 * regulator apparently won't come on.
	 *
	 * Also, we must keep the device in power down for 100ms or so
	 * after PVDD is applied, or else the ADR pin is sampled
	 * incorrectly and the device comes up with an unpredictable I2C
	 * address.
	 */
	tas5825m->vol[0] = TAS5825M_VOLUME_MIN;
	tas5825m->vol[1] = TAS5825M_VOLUME_MIN;


	ret = regulator_enable(tas5825m->pvdd);
	if (ret < 0) {
		dev_err(dev, "failed to enable pvdd: %d\n", ret);
		return ret;
	}
	printk(KERN_CRIT "Enabled PVDD OK\n");

	usleep_range(100000, 150000);
	gpiod_set_value(tas5825m->gpio_pdn_n, 1);
	usleep_range(10000, 15000);

	INIT_WORK(&tas5825m->work, do_work);
	mutex_init(&tas5825m->lock);
#if 0
	usleep_range(5000, 10000);
	send_cfg(tas5825m->regmap, dsp_cfg_preboot, ARRAY_SIZE(dsp_cfg_preboot));
	usleep_range(5000, 15000);

	// TOM TRAN
	printk( KERN_INFO "SENDING CONFIG INFO TO AUDIO DEVICE\n");
	if(tas5825m->dsp_cfg_data) {
		printk( KERN_INFO "SENDING FW DATA NOT MISSING \n");
		send_cfg(tas5825m->regmap, tas5825m->dsp_cfg_data, tas5825m->dsp_cfg_len);
	} else {
		printk( KERN_INFO "SENDING FW MISSING \n");
		ret = regmap_register_patch(tas5825m->regmap, tas5825m_init_sequence, ARRAY_SIZE(tas5825m_init_sequence));
		if (ret != 0)
		{
			printk(KERN_ERR "tas5825m_work_handler: failed to initialize TAS5825M: %d\n",ret);
			return ret;
		}
	}
	tas5825m->is_powered = true;
	tas5825m_refresh(tas5825m);
#endif
	/* Don't register through devm. We need to be able to unregister
	 * the component prior to deasserting PDN#
	 */
	ret = snd_soc_register_component(dev, &soc_codec_dev_tas5825m,
					 &tas5825m_dai, 1);
	if (ret < 0) {
		dev_err(dev, "unable to register codec: %d\n", ret);
		printk(KERN_INFO "%s(): unable to register codec: %d\n", __func__, ret);
		gpiod_set_value(tas5825m->gpio_pdn_n, 0);
		regulator_disable(tas5825m->pvdd);
		return ret;
	}
	printk(KERN_INFO "%s(): DONE \n", __func__);
	printk(KERN_INFO "%s(): =================================================\n", __func__);
	return 0;
}

static int tas5825m_i2c_remove(struct i2c_client *i2c)
{
	struct device *dev = &i2c->dev;
	struct tas5825m_priv *tas5825m = dev_get_drvdata(dev);

	cancel_work_sync(&tas5825m->work);
	snd_soc_unregister_component(dev);
	gpiod_set_value(tas5825m->gpio_pdn_n, 0);
	usleep_range(10000, 15000);
	regulator_disable(tas5825m->pvdd);
	return 0;
}

static const struct i2c_device_id tas5825m_i2c_id[] = {
	{ "tas5825m" },
	{ }
};
MODULE_DEVICE_TABLE(i2c, tas5825m_i2c_id);

#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id tas5825m_of_match[] = {
	{ .compatible = "ti,tas5825m", },
	{ }
};
MODULE_DEVICE_TABLE(of, tas5825m_of_match);
#endif

static struct i2c_driver tas5825m_i2c_driver = {
	.probe_new	= tas5825m_i2c_probe,
	.remove		= tas5825m_i2c_remove,
	.id_table	= tas5825m_i2c_id,
	.driver		= {
		.name		= "tas5825m",
		.of_match_table = of_match_ptr(tas5825m_of_match),
	},
};

module_i2c_driver(tas5825m_i2c_driver);

MODULE_AUTHOR("Andy Liu <andy-liu@ti.com>");
MODULE_AUTHOR("Daniel Beer <daniel.beer@igorinstitute.com>");
MODULE_DESCRIPTION("TAS5825M Audio Amplifier Driver");
MODULE_LICENSE("GPL v2");

  • root@verdin-imx8mp-15289178:~# aplay -v PCM_48_16_8_160000_1_29_jazzshort.wav
    Playing WAVE 'PCM_48_16_8_160000_1_29_jazzshort.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Cha
    nnels 8
    Plug PCM: Route conversion PCM (sformat=S32_LE)
    Transformation table:
    0 <- 0*0.25 + 2*0.25 + 4*0.25 + 6*0.25
    1 <- 1*0.25 + 3*0.25 + 5*0.25 + 7*0.25
    Its setup is:
    stream : PLAYBACK
    access : RW_INTERLEAVED
    format : S16_LE
    subformat : STD
    channels : 8
    rate [ 14.280585] ====> SENDING INIT SEQUENCE
    : 48000
    exact rate : 48000 (48000/1)
    msbits : 16
    buffer_size : 5760
    period_size : 1920
    period_time : 40000
    tstamp_mode : NONE
    tstamp_type : MONOTONIC
    period_step : 1
    avail_min : 1920
    period_event : 0
    start_threshold : 5760
    stop_threshold : 5760
    silence_threshold: 0
    silence_size : 0
    boundary : 6485183463413514240
    Slave: Direct Stream Mixing PCM
    Its setup is:
    stream : PLAYBACK
    access : MMAP_INTERLEAVED
    format : S32_LE
    subformat : STD
    channels : 2
    rate : 48000
    exact rate : 48000 (48000/1)
    msbits : 32
    buffer_size : 5760
    period_size : 1920
    period_time : 40000
    tstamp_mode : NONE
    tstamp_type : MONOTONIC
    period_step : 1
    avail_min : 1920
    period_event : 0
    start_threshold : 5760
    stop_threshold : 5760
    silence_threshold: 0
    silence_size : 0
    boundary : 6485183463413514240
    Hardware PCM card 0 'tas5825m-amp' device 0 subdevice 0
    Its setup is:
    stream : PLAYBACK
    access : MMAP_INTERLEAVED
    format : S32_LE
    subformat : STD
    channels : 2
    rate : 48000
    exact rate : 48000 (48000/1)
    msbits : 32
    buffer_size : 5760
    period_size : 1920
    period_time : 40000
    tstamp_mode : ENABLE
    tstamp_type : MONOTONIC
    period_step : 1
    avail_min : 1920
    period_event : 0
    start_threshold : 1
    stop_threshold : 6485183463413514240
    silence_threshold: 0
    silence_size : 6485183463413514240
    boundary : 6485183463413514240
    appl_ptr : 0
    hw_ptr : 0
    [ 16.635822] REG_PAGE(0x00) write OK: 0
    [ 16.640976] REG_BOOK(0x8c) write OK: 0
    [ 16.645867] REG_PAGE(0x2a) write OK: 0
    [ 16.655867] REG_PAGE(0x00) write OK: 0
    [ 16.660215] REG_BOOK(0x00) write OK: 0
    [ 16.664502] REG_DEVICE_CTRL_2(muted = 0) write OK: 0
    [ 37.585543] kauditd_printk_skb: 12 callbacks suppressed
    [ 37.585552] audit: type=1334 audit(1728345111.300:18): prog-id=12 op=UNLOAD
    [ 37.597855] audit: type=1334 audit(1728345111.300:19): prog-id=11 op=UNLOAD
    [ 44.243575] REG_PAGE(0x00) write OK: 0
    [ 44.247903] REG_BOOK(0x8c) write OK: 0
    [ 44.252169] REG_PAGE(0x2a) write OK: 0
    [ 44.257998] REG_PAGE(0x00) write OK: 0
    [ 44.262260] REG_BOOK(0x00) write OK: 0
    [ 44.266517] REG_DEVICE_CTRL_2(muted = 1) write OK: 0
    root@verdin-imx8mp-15289178:~# [ 49.448643] REG_GLOBAL_FAULT1(global1 = 0x4), read OK: 0

  • The TAS5825M init sequence is the a file generated by the PPC3.

    2311.stereo_flow2_48kHz_default_coldboot_-10dB.h

  • hi Tom

    can you share the photo how you jump the i2s to our evm board?

    it reports the clk fault, maybe checking the i2s signal waveform whether it meets our spec will be better.

    tks

    jesse

  • Hi Jesse,
    The image shows my connection to the TAS5825M.  
    The 3 top clips (left to right), Green (DOUT), Yellow (BCLK), Red (SYNC).
    • DOUT is connected to SDIN1 (TAS5825MEVM)
    • BCLK is connected to SCLK (TAS5825MEVM)
    • SYNC is connected to LRCLK (TAS5825MEVM)
    • Channel 0 is BCLK
    • Channel 1 is SYNC
    • Channel 2 SDOUT
    Initially, all the signals are pulled up HIGH but after power-up, the SDOUT line has activity and is pulled LOW.  (Not sure if there's some initialization that is happening from the PPC3 EVM board which is driving these lines also).  
    After that only the  BCLK, SYNC signals have activities.   The SYNC signal continues to oscillate even after the playback has completed.  
    But that's what I've seen so far.  Hopefully, you have a better insight based on the info above.
    Regards,
    Tom
  • hi Tom

    you fsync seems not correct.

    it might because the conflict between MB's I2s and your i2s.

    after you finish config the amp by ppc3, you need to enter below page to select PSIA.

    its default input is USB in and i2s be inputted to amp.

    tks

    jesse

  • Hi Jesse,

    I changed my board setup because I think the PPC3 board is affecting the I2S signals.  I just powered the TAS5825M EVM board separately with a source of 3.3V for the interfaces.   My new setup look as follows:

    The Top board (Toradex IMX8MP), bottom board (TAS5825M)

  • This is a waveform for a 48kHZ 16bit PCM

    Channel 0 (BCLK):  The signal BCLK isn't uniform which I expect.

    Channel 1(SYNC):  LRCLK is dependent on the bit clock I am assuming.

    I am trying to figure out why the BCLK signal has a small interval which isn't uniform like below

    The 1st transition from LOW-to-HIGH and HIGH-to-LOW seems like a very short period which looks abnormal.   Wondering if there's something internal in the IMX8MP SoC conflicting with this BCLK line.

  • hi,

    the clock from IMX8MP seems strange.

    i am not familiar with the IMX8MP, maybe you need to check how to generate the clock as below:

    tks

    jesse

  • Yeah, the BCLK is abnormal, I need to find out why that is on the IMX8MP.