#pragma GCC push_options
#include <linux/init.h>
#pragma GCC diagnostic ignored "-Wunused-function"
#include <linux/iommu.h>
#pragma GCC pop_options
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/bootmem.h>
#include <linux/reset.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include <linux/clk.h>
#include <avm/sammel/debug.h>
#include <linux/clk-provider.h>
#include <linux/of_irq.h>
#include <asm/cacheflush.h>
#include <linux/dma-mapping.h>

#include "ipq-adss.h"
#include "ipq-avm.h"
#include <asm/mach_avm.h>
#include <avm/enh/irq_on.h>

#define DBG_TDM_RESIZE_CHECK

#if defined(CONFIG_AVM_FASTIRQ)
#include <asm/avm_enh/avm_fiq_groups.h>
#include <asm/avm_enh/avm_fiq.h>
#include <asm/avm_enh/avm_fiq_os.h>
#include <asm/avm_enh/avm_gic_fiq.h>
#endif/*--- #if defined(CONFIG_AVM_FASTIRQ) ---*/

static void __iomem *avm_adss_audio_local_base;
static void __iomem *adss_pcm_base;
static struct reset_control *audio_blk_rst;
static struct clk *pcm_clk;

static void *DebugHandle;
static void cmdlineparse(char *cmdline, void *dummy __attribute__((unused)));
static char *snprint_bitformat(char *erg, int erg_len, unsigned int value, const char *bitformat);
static unsigned int snprint_all_register(char **txt, unsigned int tx_size, unsigned int id_mask);

static struct ipq_configs ipq4019_cfgs = {
	.txd_oe = {
		.reg = ADSS_GLB_AUDIO_MODE_REG,
		.mask = GLB_AUDIO_MODE_I2S0_TXD_OE,
	},
	.rxd_oe = {
		.reg = ADSS_GLB_AUDIO_MODE_REG,
		.mask = GLB_AUDIO_MODE_I2S3_RXD_OE,
	},
	.i2s0_fs_oe = {
		.reg = ADSS_GLB_AUDIO_MODE_REG,
		.mask = GLB_AUDIO_MODE_I2S0_FS_OE,
	},
	.i2s3_fs_oe = {
		.reg = ADSS_GLB_AUDIO_MODE_REG,
		.mask = GLB_AUDIO_MODE_I2S3_FS_OE,
	},
	.i2s_reset_val = {
		.reg = ADSS_GLB_I2S_RST_REG,
		.mask = GLB_I2S_RESET_VAL_4019,
	},
	.spdif_enable = 1,
};

static struct ipq_configs ipq8074_cfgs = {
	.txd_oe = {
		.reg = ADSS_GLB_CLK_I2S_CTRL_REG,
		.mask = GLB_CLK_I2S_CTRL_I2S0_TXD_OE,
	},
	.rxd_oe = {
		.reg = ADSS_GLB_CLK_I2S_CTRL_REG,
		.mask = GLB_CLK_I2S_CTRL_I2S3_RXD_OE,
	},
	.i2s0_fs_oe = {
		.reg = ADSS_GLB_CLK_I2S_CTRL_REG,
		.mask = GLB_CLK_I2S_CTRL_I2S0_FS_OE,
	},
	.i2s3_fs_oe = {
		.reg = ADSS_GLB_CLK_I2S_CTRL_REG,
		.mask = GLB_CLK_I2S_CTRL_I2S3_FS_OE,
	},
	.i2s_reset_val = {
		.reg = ADSS_GLB_I2S_RST_REG,
		.mask = GLB_I2S_RESET_VAL_8074,
	},
	.spdif_enable = 0,
};
static struct ipq_configs *ipq_cfgs;

#define MBOX_PHYS_DMA_ADDRESS(pmbox, a) (((dma_addr_t)(a)) & pmbox->mbox_mask)

/*--- #define DBG_REGISTER ---*/

/*--- #define DBG_TRC_API(args...) pr_info(args) ---*/
#define DBG_TRC_API(args...)	   no_printk(args)

/*--- #define DBG_TRC(args...) pr_info(args) ---*/
#define DBG_TRC(args...)	   no_printk(args)

/*--- #define DBG_TRC_DESC(args...) pr_info(args) ---*/
#define DBG_TRC_DESC(args...)	    no_printk(args)

#if defined(DBG_REGISTER)
static void trace_reg(const char *basename, const char *regname, int id, unsigned int offset, int read_flag,
			unsigned int towrite, unsigned int read_val);
#else /*--- #if defined(DBG_REGISTER) ---*/
#define trace_reg(basename, regname, id, offset, read_flag, towrite, read_val)
#endif
struct _mbox_desc {
	unsigned int length : 12,	/* bit 11-00 */
	size    : 12,	/* bit 23-12 */
	vuc     : 1,	/* bit 24 */
	ei      : 1,	/* bit 25 */
	rsvd1   : 4,	/* bit 29-26 */
	EOM     : 1,	/* bit 30 */
	OWN     : 1;	/* bit 31 */
	unsigned int BufPtr;
	unsigned int NextPtr;
	unsigned int vuc_dword[36];
};

struct _mbox_dma {
	/*--- dma-alloc-stuff ---*/
	void *dma_pool;
	dma_addr_t phys_desc;
	dma_addr_t phys_buf[2];

	/* Desc array in virtual space */
	struct _mbox_desc *virt_desc;
	unsigned int desc_size;
	unsigned int mbox_mask;
	unsigned int ndescs;
	unsigned char *virt_buf[2];

	struct device *dev;
	unsigned long status;
	uint32_t err_stats;
	unsigned int irq;
	unsigned int fiq_cpumode;
	unsigned int size;
	unsigned int slots;
	enum {irq_uninitialized = 0, irq_initialized, irq_progress} irq_active;
	unsigned int err_cnt;
	unsigned long irq_cnt;
	unsigned int own_udr;
	unsigned int own_ovr;
	int dir;
	uint32_t dma_id;
	struct _mbox_dma *irqcontext_mbox; /*--- behandle diese mbox im irq-context mit ---*/
	/*--- Schnittstelle nach oben: ---*/
	void *refhandle;
	/*--- Rx: empfangenen Buffer abliefern - Tx: diesen Buffer fuellen, ret: 0 ok sonst DMA-Size reduzieren ---*/
	int (*RxTxData)(void *refhandle, void *p);
	unsigned int reoffset;
	unsigned int force_resize;
#if defined(DBG_TDM_RESIZE_CHECK)
	unsigned int dbg_force_reoffset;
#endif/*--- #if defined(DBG_TDM_RESIZE_CHECK) ---*/
} mbox_dma[2];

static struct _mbox_data {
	void __iomem *reg_base;
	unsigned int irq;
} mbox_data[5];

static struct dai_priv_st {
	int stereo_tx;
	int stereo_rx;
	int mbox_tx;
	int mbox_rx;
	int tx_enabled;
	int rx_enabled;
	struct platform_device *pdev;
	struct clk *audio_tx_bclk;
	struct clk *audio_tx_mclk;
	struct clk *audio_rx_bclk;
	struct clk *audio_rx_mclk;
	unsigned int clk_set;
} tdm_priv;

static void __iomem *stereo_base[MAX_STEREO_ENTRIES];
enum _trace_mode {
	trace_off     = 0,
	trace_pcm     = (1 << 0),
	trace_tdm     = (1 << 1),
	trace_audio   = (1 << 2),
	trace_stereo  = (1 << 3),
	trace_mbox    = (1 << 4),
	trace_glb     = (1 << 5),
	trace_ipq4019 = (1 << 30), /* dem 4019 (dakota) vorbehalten */
	trace_enh     = (1 << 31), /*--- include bit-data ---*/
};

static enum _trace_mode trace_mode =
					trace_enh    |
					/*--- trace_pcm    |    ---*/
					trace_glb    |
					trace_tdm    |
					trace_audio  |
					trace_stereo |
					trace_mbox   |
					0;
static unsigned int snprintf_dump_descriptors(char **_ptxt, unsigned int txt_size, struct _mbox_dma *pmbox_dma);
static unsigned int snprintf_dump_slots(char **_ptxt, unsigned int txt_size, struct _mbox_dma *pmbox_dma);
static void adss_glb_audio_mode_B1K(void);
static void adss_glb_i2s_interface_en(int enable);
static void adss_stereo_config_reset(struct _mbox_dma *pmbox_dma, uint32_t reset);
static void adss_stereo_config_enable(struct _mbox_dma *pmbox_dma, uint32_t enable);

static void mbox_dma_exit(struct _mbox_dma *pmbox_dma);
/**
 * ptxt == NULL: use printk
 */
#define snprintf_add(ptxt, txtlen, args...)					\
	do {									\
		if (ptxt) {							\
			int local_add_len = snprintf(ptxt, txtlen, args);	\
			if (local_add_len > 0) {				\
				int tail = min_t(int, txtlen, local_add_len);	\
				(ptxt) += tail, (txtlen) -= tail;		\
			}							\
		} else								\
			pr_err(args);						\
	} while (0)

#define ADSS_AUDIO_PCM_REGS(off)            (volatile unsigned int *)(adss_pcm_base + off)
#define ADSS_AUDIO_GLB_REGS(off)            (volatile unsigned int *)(avm_adss_audio_local_base + off)
#define ADSS_AUDIO_MBOX_REGS(idx, off)      (volatile unsigned int *)(mbox_data[idx].reg_base + off)
#define ADSS_AUDIO_STEREO_REGS(idx, off)    (volatile unsigned int *)(stereo_base[idx] + off)

/**
 * Hawkeye
 */
static int is_IPQ807x(void)
{
	return ipq_cfgs == &ipq8074_cfgs;
}
/*
 * Dakota
 */
static int is_IPQ4019(void)
{
	return ipq_cfgs == &ipq4019_cfgs;
}

/**
 */
static void _mbox_write(int dma_id, unsigned int val, unsigned int off, const char *regname __maybe_unused)
{
	if ((dma_id >= ARRAY_SIZE(mbox_data)) || (mbox_data[dma_id].reg_base == NULL)) {
		return;
	}
	writel(val, ADSS_AUDIO_MBOX_REGS(dma_id, off));
	trace_reg("MBOX", regname, dma_id, off, 0, val, readl(ADSS_AUDIO_MBOX_REGS(dma_id, off)));
}

/**
 */
static unsigned int _mbox_read(int dma_id, unsigned int off, const char *regname __maybe_unused)
{
	unsigned int val;

	if ((dma_id >= ARRAY_SIZE(mbox_data)) || (mbox_data[dma_id].reg_base == NULL)) {
		return (unsigned int)-1;
	}
	val = readl(ADSS_AUDIO_MBOX_REGS(dma_id, off));
	trace_reg("MBOX", regname, dma_id, off, 1, 0, val);
	return val;
}

/**
 */
static void _adss_pcm_write(unsigned int val, unsigned int off, const char *regname __maybe_unused)
{
	writel(val, ADSS_AUDIO_PCM_REGS(off));
	trace_reg("ADSS_PCM_BASE", regname, -1, off, 0, val, readl(ADSS_AUDIO_PCM_REGS(off)));
}
/**
 */
static unsigned int _adss_pcm_read(unsigned int off, const char *regname __maybe_unused)
{
	unsigned int val;

	val = readl(ADSS_AUDIO_PCM_REGS(off));
	trace_reg("ADSS_PCM_BASE", regname, -1, off, 1, 0, val);
	return val;
}

/**
 */
void _adss_audio_write(unsigned int val, unsigned int off, const char *regname __maybe_unused)
{
	writel(val, ADSS_AUDIO_GLB_REGS(off));
	trace_reg("ADSS_AUDIO_GLB_BASE", regname, -1, off, 0, val, readl(ADSS_AUDIO_GLB_REGS(off)));
}
EXPORT_SYMBOL(_adss_audio_write);

/**
 */
unsigned int _adss_audio_read(unsigned int off, const char *regname __maybe_unused)
{
	unsigned int val;

	val = readl(ADSS_AUDIO_GLB_REGS(off));
	trace_reg("ADSS_AUDIO_GLB_REGS", regname, -1, off, 1, 0, val);
	return val;
}
EXPORT_SYMBOL(_adss_audio_read);

/**
 */
static void _adss_stereo_write(int stereo_id, unsigned int val, unsigned int off, const char *regname __maybe_unused)
{
	if ((stereo_id >= ARRAY_SIZE(stereo_base)) || (stereo_base[stereo_id] == NULL)) {
		return;
	}
	writel(val, ADSS_AUDIO_STEREO_REGS(stereo_id, off));
	trace_reg("STEREO", regname, stereo_id, off, 0, val, readl(ADSS_AUDIO_STEREO_REGS(stereo_id, off)));
}

/**
 */
static unsigned int _adss_stereo_read(int stereo_id, unsigned int off, const char *regname __maybe_unused)
{
	unsigned int val;

	if ((stereo_id >= ARRAY_SIZE(stereo_base)) || (stereo_base[stereo_id] == NULL)) {
		return (unsigned int)-1;
	}
	val = readl(ADSS_AUDIO_STEREO_REGS(stereo_id, off));
	trace_reg("STEREO", regname, stereo_id, off, 1, 0, val);
	return val;
}

#define PCM_IO(off, flag, format)    { trace_flag:flag, regname:#off, offset:off, write:_adss_pcm_write, read:_adss_pcm_read, bitformat:format }
#define GLB_IO(off, flag, format)    { trace_flag:flag, regname:#off, offset:off, write:_adss_audio_write, read:_adss_audio_read, bitformat:format }
#define STEREO_IO(off, flag, format) { trace_flag:flag, regname:#off, offset:off, stereo_write:_adss_stereo_write, stereo_read:_adss_stereo_read, bitformat:format }
#define MBOX_IO(off, flag, format)   { trace_flag:flag, regname:#off, offset:off, _mbox_write:_mbox_write, _mbox_read:_mbox_read, bitformat:format }

/**
 * wenn trace_flag mit trace_ipq4019 verodert:  nur bei Dakota auslesen
 * anhand des Prefixes "8074_" bzw.  "4019_" werden die Bitfelder entsprechend anders ausgewertet
 */
static const struct _debug_register_tab {
	enum _trace_mode trace_flag;
	const char *regname;
	unsigned int offset;
	void (*write)(unsigned int val, unsigned int off, const char *regname);
	unsigned int (*read)(unsigned int off, const char *regname);
	void (*_mbox_write)(int dma_id, unsigned int val, unsigned int off, const char *regname);
	unsigned int (*_mbox_read)(int dma_id, unsigned int off, const char *regname);
	void (*stereo_write)(int stereo_id, unsigned int val, unsigned int off, const char *regname);
	unsigned int (*stereo_read)(int stereo_id, unsigned int off, const char *regname);
	const char *bitformat;
} gReg[] = {
	PCM_IO(AADSS_PCM_BITMAP_REG,                         trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_CTRL_REG,                           trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_OFFSET_REG,                         trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_START_REG,                          trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_INT_STATUS_REG,                     trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_INT_ENABLE_REG,                     trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_DIVIDER_REG,                        trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_TH_REG,                             trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_FIFO_CNT_REG,                       trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_FIFO_ERR_SLOT_REG,                  trace_pcm,     NULL),
	PCM_IO(AADSS_PCM_RX_DATA_8BIT_REG,                   0,             NULL),
	PCM_IO(AADSS_PCM_TX_DATA_8BIT_REG,                   0,             NULL),
	PCM_IO(AADSS_PCM_RX_DATA_16BIT_REG,                  0,             NULL),
	PCM_IO(AADSS_PCM_TX_DATA_16BIT_REG,                  0,             NULL),
	GLB_IO(ADSS_GLB_PCM_RST_REG,                         trace_glb,     NULL),
	GLB_IO(ADSS_GLB_PCM_MBOX_CTRL_REG,                   trace_glb,     NULL),
	GLB_IO(ADSS_GLB_CHIP_CTRL_I2S_REG,                   trace_glb,     "I2S_STEREO2_GLB_EN(3)I2S_STEREO1_GLB_EN(2)I2S_STEREO0_GLB_EN(1)I2S_INTERFACE_EN(0)"),
	GLB_IO(ADSS_GLB_I2S_RST_REG,			     trace_glb,     "8074_RXBCLK(7)8074_RXMCLK(6)8074_TXBCLK(5)8074_TXMCLK(4)I2S3(3)MBOX3(2)I2S0(1)MBOX0(0)"),
	GLB_IO(ADSS_GLB_CLK_I2S_CTRL_REG,                    trace_glb,     "TX_BCLK_OE(28)RX_BCLK_OE(27)TX_MCLK_OE(17)RX_MCLK_OE(16)8074_I2S3_FS_OE(15)8074_I2S0_FS_OE(14)8074_I2S3_D_OE(13)8074_I2S0_D_OE(12)"),
	GLB_IO(ADSS_GLB_TDM_CTRL_REG,                        trace_glb,     "RX_DELAY(26)TX_DELAY(25)RX_SYNC_NUM(20,24)RX_CHAN_NUM(16,19)TX_SYNC_NUM(4,8)TX_CHAN_NUM(0,3)"),
	GLB_IO(ADSS_GLB_PWM0_CTRL_REG,                       trace_glb,     NULL),
	GLB_IO(ADSS_GLB_PWM1_CTRL_REG,                       trace_glb,     NULL),
	GLB_IO(ADSS_GLB_PWM2_CTRL_REG,                       trace_glb,     NULL),
	GLB_IO(ADSS_GLB_PWM3_CTRL_REG,                       trace_glb,     NULL),
	GLB_IO(ADSS_GLB_AUDIO_MODE_REG,                      trace_glb,     "B1K(28)8074_I2S3_WR_SWAP(23)8074_I2S3_RD_SWAP(22)8074_I2S0_WR_SWAP(17)8074_I2S0_RD_SWAP(16)4019_SPDIF_OUT_OE(10)4019_I2S3_RXD_OE(9)4019_I2S3_FS_OE(8)4019_I2S0_FS_OE(7)4019_I2S0_TXD_OE(4)RECV_TDM(2)RECV_I2S(~2)XMIT_TDM(0)XMIT_I2S(~0)"),
	GLB_IO(ADSS_GLB_AUDIO_MODE2_REG,                     trace_glb,     "8074_LOOPBACK_EN(30)8074_RD_ADDR_MAP(2,3)8074_WR_ADDR_MAP(0,1)"),
	GLB_IO(ADSS_AUDIO_PLL_CONFIG_REG,                    trace_audio | trace_ipq4019, "POSTPLLDIV(7,9)PLL_PWD(5)REFDIV(0,2)"),
	GLB_IO(ADSS_AUDIO_PLL_MODULATION_REG,                trace_audio | trace_ipq4019, "INT_DIV(1,10)FRAC_DIV(11,29)"),
	GLB_IO(ADSS_AUDIO_PLL_MOD_STEP_REG,                  trace_audio | trace_ipq4019, NULL),
	GLB_IO(ADSS_CURRENT_AUDIO_PLL_MODULATION_REG,        trace_audio | trace_ipq4019, NULL),
	GLB_IO(ADSS_AUDIO_PLL_CONFIG1_REG,                   trace_audio | trace_ipq4019, NULL),
	GLB_IO(ADSS_AUDIO_ATB_SETTING_REG,                   trace_audio,   "8074_ATB_ID(8,14)8074_ATB_SEL(0,7)"),
	GLB_IO(ADSS_AUDIO_RXB_CFG_MUXR_REG,                  trace_audio,   "MUXR_SRC_SEL(8,16)"),
	GLB_IO(ADSS_AUDIO_RXB_MISC_REG,                      trace_audio,   "B_MDIV(1,31)"),
	GLB_IO(ADSS_AUDIO_RXB_CBCR_REG,                      trace_audio,   "8074_CLK_OFF(31)ENABLE(0)"),
	GLB_IO(ADSS_AUDIO_RXM_CMD_RCGR_REG,                  trace_audio,   "8074_ROOT_OFF(31)8074_DIRTY_CFG(4)ROOT_EN(1)UPDATE(0)"),
	GLB_IO(ADSS_AUDIO_RXM_CFG_RCGR_REG,                  trace_audio,   "M_RDIV(0,7)SRC_SEL(8,10)8074_SRC_DIV(0,4)"),
	GLB_IO(ADSS_AUDIO_RXM_MISC_REG,                      trace_audio,   "4019_M_MDIV(4,31)AUTO_SCALE_DIV(0,8)"),
	GLB_IO(ADSS_AUDIO_RXM_CBCR_REG,                      trace_audio,   "8074_CLK_OFF(31)ENABLE(0)"),
	GLB_IO(ADSS_AUDIO_TXB_CFG_MUXR_REG,                  trace_audio,   "MUXR_SRC_SEL(8,16)"),
	GLB_IO(ADSS_AUDIO_TXB_MISC_REG,                      trace_audio,   "B_MDIV(1,31)"),
	GLB_IO(ADSS_AUDIO_TXB_CBCR_REG,                      trace_audio,   "8074_CLK_OFF(31)ENABLE(0)"),
	GLB_IO(ADSS_AUDIO_TXM_CMD_RCGR_REG,                  trace_audio,   "8074_ROOT_OFF(31)8074_DIRTY_CFG(4)ROOT_EN(1)UPDATE(0)"),
	GLB_IO(ADSS_AUDIO_TXM_CFG_RCGR_REG,                  trace_audio,   "M_RDIV(0,7)SRC_SEL(8,10)"),
	GLB_IO(ADSS_AUDIO_TXM_MISC_REG,                      trace_audio,   "M_MDIV(4,31)"),
	GLB_IO(ADSS_AUDIO_TXM_CBCR_REG,                      trace_audio,   "8074_CLK_OFF(31)ENABLE(0)"),
	GLB_IO(ADSS_AUDIO_SAMPLE_CBCR_REG,                   trace_audio | trace_ipq4019, "ENABLE(0)"),
	STEREO_IO(ADSS_STEREOn_STEREO0_CONFIG_REG,           trace_stereo,  "MIC_SWAP(24)SPDIF_ENA(23)ENA(21)MIC_RESET(19)RESET(20)I2S_DELAY(~18)PCM_SWAP(17)MIC_W32(16)MIC_W16(~16)MONO(14)STEREO(~14)WS(12,13)I2S_W32(11)I2S_W16(~11)MCK_SEL(10)CLR_CNT(9)MASTER(8)"),
	/*--- STEREO_IO(ADSS_STEREOn_STEREO0_VOLUME_REG,            trace_stereo,  "CHAN1(8,12)CHAN0(0,4))"), ---*/
	STEREO_IO(ADSS_STEREOn_STEREO0_MASTER_CLOCK_REG,      trace_stereo,  NULL),
	/*--- STEREO_IO(ADSS_STEREOn_STEREO0_TX_SAMPLE_CNT_LSB_REG, trace_stereo,  NULL), ---*/
	/*--- STEREO_IO(ADSS_STEREOn_STEREO0_TX_SAMPLE_CNT_MSB_REG, trace_stereo,  NULL), ---*/
	/*--- STEREO_IO(ADSS_STEREOn_STEREO0_RX_SAMPLE_CNT_LSB_REG, trace_stereo,  NULL), ---*/
	/*--- STEREO_IO(ADSS_STEREOn_STEREO0_RX_SAMPLE_CNT_MSB_REG, trace_stereo,  NULL), ---*/
	STEREO_IO(ADSS_STEREOn_STEREO0_UNDERRUN_CNT_REG,       trace_stereo,  NULL),
	STEREO_IO(ADSS_STEREOn_STEREO0_OVERRUN_CNT_REG,       trace_stereo,  NULL),
	STEREO_IO(ADSS_STEREOn_STEREO0_TX_COMPLETE_CNT_REG,   trace_stereo,  NULL),
	STEREO_IO(ADSS_STEREOn_STEREO0_RX_COMPLETE_CNT_REG,   trace_stereo,  NULL),
	MBOX_IO(ADSS_MBOXn_MBOX_FIFO0_REG,                   trace_mbox,    NULL),
	MBOX_IO(ADSS_MBOXn_MBOX_FIFO_STATUS0_REG,            trace_mbox,    NULL),
	MBOX_IO(ADSS_MBOXn_MBOX_DMA_POLICY_REG,              trace_mbox,    "SW_RESET(31)TX_INT_TYPE(17)RX_INT_TYPE(16)RXD_16BIT_SWAP(10)RXD_END_SWAP(8)SRAM_AC(12,15)TX_FIFO_THRESHOLD(4,7)"),
	MBOX_IO(ADSS_MBOXn_MBOXn_DMA_RX_DESCRIPTOR_BASE_REG, trace_mbox,    NULL),
	MBOX_IO(ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG,         trace_mbox,    "RESUME(2)START(1)STOP(0)"),
	MBOX_IO(ADSS_MBOXn_MBOXn_DMA_TX_DESCRIPTOR_BASE_REG, trace_mbox,    NULL),
	MBOX_IO(ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG,         trace_mbox,    "RESUME(2)START(1)STOP(0)"),
	MBOX_IO(ADSS_MBOXn_MBOX_FRAME_REG,                   trace_mbox,    NULL),
	MBOX_IO(ADSS_MBOXn_FIFO_TIMEOUT_REG,                 trace_mbox,    NULL),
	MBOX_IO(ADSS_MBOXn_MBOX_INT_STATUS_REG,              trace_mbox,    "8074_ERROR_RESP(15)8074_BI_TX_RX(14)TX_FIFO_OVERFLOW(13)RX_FIFO_UNDERFLOW(12)8074_OUT_OF_FSYNC(11)RX_DMA_COMPLETE(10)8074_DMA_EOM_COMPLETE(8)TX_DMA_COMPLETE(6)TX_OVERFLOW(5)RX_UNDERFLOW(4)8074_TX_NOT_EMPTY(2)8074_RX_NOT_FULL(0)"),
	MBOX_IO(ADSS_MBOXn_MBOX_INT_ENABLE_REG,              trace_mbox,    "8074_ERROR_RESP(15)8074_BI_TX_RX(14)TX_FIFO_OVERFLOW(13)RX_FIFO_UNDERFLOW(12)8074_OUT_OF_FSYNC(11)RX_DMA_COMPLETE(10)8074_DMA_EOM_COMPLETE(8)TX_DMA_COMPLETE(6)TX_OVERFLOW(5)RX_UNDERFLOW(4)8074_TX_NOT_EMPTY(2)8074_RX_NOT_FULL(0)"),
	MBOX_IO(ADSS_MBOXn_MBOX_FIFO_RESET_REG,              0,             NULL),
};
#if defined(DBG_REGISTER)
#define DBG_TRC_REG(args...) pr_info(args)

/**
 */
static const struct _debug_register_tab *find_regname(const char *regname)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(gReg); i++) {
		const struct _debug_register_tab *preg = &gReg[i];

		if (strcmp(regname, preg->regname) == 0) {
			return preg;
		}
	}
	return NULL;
}

/**
 */
static void trace_reg(const char *basename, const char *regname, int id, unsigned int offset, int read_flag,
			unsigned int towrite, unsigned int read_val)
{
	if (regname == NULL || (trace_mode == 0)) {
		return;
	}
	if ((trace_mode & trace_enh) == 0) {
		if (read_flag) {
			pr_err("%s *(%s%c+0x%x) = 0x%x\n", regname, basename, id < 0 ? ' ' : id + '0',
				offset, read_val);
		} else {
			pr_err("%s *(%s%c+0x%x) = 0x%x => 0x%x\n", regname, basename, id < 0 ? ' ' : id + '0',
				offset, towrite, read_val);
		}
	} else {
		const struct _debug_register_tab *preg;
		char txt1[196], txt2[196];

		preg = find_regname(regname);
		if (read_flag) {
			snprint_bitformat(txt1, sizeof(txt1), read_val, preg ? preg->bitformat : NULL);
			pr_err("%s:(%s%c+0x%02x)%s\n", regname, basename, id < 0 ? ' ' : id + '0',
				offset, txt1);
		} else {
			snprint_bitformat(txt1, sizeof(txt1), towrite, preg ? preg->bitformat : NULL);
			snprint_bitformat(txt2, sizeof(txt2), read_val, preg ? preg->bitformat : NULL);
			pr_err("%s:(%s%c+0x%02x)%s -> %s\n", regname, basename, id < 0 ? ' ' : id + '0',
				offset, txt1, txt2);
		}
	}
}
#endif/*--- #if defined(DBG_REGISTER)  ---*/

#define mbox_read(dma_id, off)                  _mbox_read(dma_id, off, #off)
#define mbox_write(dma_id, val, off)            _mbox_write(dma_id, val, off, #off)
#define adss_pcm_read(off)		        _adss_pcm_read(off, #off)
#define adss_pcm_write(val, off)                _adss_pcm_write(val, off, #off)
#define adss_audio_read(off)		        _adss_audio_read(off, #off)
#define adss_audio_write(val, off)              _adss_audio_write(val, off, #off)
#define adss_audio_read(off)                    _adss_audio_read(off, #off)
#define adss_stereo_read(stereo_id, off)        _adss_stereo_read(stereo_id, off, #off)
#define adss_stereo_write(stereo_id, val, off)	_adss_stereo_write(stereo_id, val, off, #off)

#define CHN_STATUS_DISABLE 0xFF
/**
 */
enum _interface_id {
	intf_audio_adss_ipq4019,
	intf_audio_adss_ipq8074,
	intf_pcm,
	intf_mbox,
	intf_tdm,
	intf_stereo,
	intf_tdm_pins,
};

/**
 */
static const struct of_device_id pcm_avm_id_table[] = {
		{ .compatible = "qca,ipq4019-audio-adss", .data = (void *)intf_audio_adss_ipq4019},
		{ .compatible = "qca,ipq4019-pcm",        .data = (void *)intf_pcm        },
		{ .compatible = "qca,ipq4019-mbox",       .data = (void *)intf_mbox       },
		{ .compatible = "qca,ipq4019-tdm",        .data = (void *)intf_tdm        },
		{ .compatible = "qca,ipq4019-stereo",     .data = (void *)intf_stereo     },

		{ .compatible = "qca,ipq8074-audio-adss", .data = (void *)intf_audio_adss_ipq8074},
		{ .compatible = "qca,ipq8074-pcm",        .data = (void *)intf_pcm        },
		{ .compatible = "qca,ipq8074-mbox",       .data = (void *)intf_mbox       },
		{ .compatible = "qca,ipq8074-tdm",        .data = (void *)intf_tdm        },
		{ .compatible = "qca,ipq8074-stereo",     .data = (void *)intf_stereo     },
		{ .compatible = "qca,ipq8074-audio",	  .data = (void *)intf_tdm_pins   },
		{},
};
MODULE_DEVICE_TABLE(of, pcm_avm_id_table);

/**
 */
static void gcc_audio_blk_rst(void)
{
	DBG_TRC("%s\n", __func__);
	if (!audio_blk_rst) {
		return;
	}
	reset_control_assert(audio_blk_rst);
	usleep_range(20000, 24000);
	reset_control_deassert(audio_blk_rst);
}

/**
 */
static int intf_audio_adss_probe(struct platform_device *pdev)
{
	struct resource *res;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	avm_adss_audio_local_base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(avm_adss_audio_local_base)) {
		pr_err("%s: error adss_audio_local_base\n", __func__);
		return PTR_ERR(avm_adss_audio_local_base);
	}
	audio_blk_rst = devm_reset_control_get(&pdev->dev, "blk_rst");
	if (IS_ERR(audio_blk_rst))  {
		pr_err("%s: error audio_blk_rst\n", __func__);
		return PTR_ERR(audio_blk_rst);
	}
	gcc_audio_blk_rst();

	adss_glb_i2s_interface_en(ENABLE);
	adss_glb_audio_mode_B1K();
	DBG_TRC("%s: adss_audio_local_base=%p(%x-%x)\n", __func__, avm_adss_audio_local_base, res->start, res->end);
	return 0;
}

/**
 */
static int intf_tdm_pins_probe(struct platform_device *pdev, int slave)
{
	struct dev_pin_info *pins;
	struct pinctrl_state *pin_state;

	pins = pdev->dev.pins;

	pr_err("%s: %p\n", __func__, pins);

	if (slave)
		pin_state = pinctrl_lookup_state(pins->p, "audio_slave");
	else
		pin_state = pinctrl_lookup_state(pins->p, "audio");

	if (IS_ERR(pin_state)) {
		pr_err("audio pinctrl state not available\n");
		return PTR_ERR(pin_state);
	}
	pinctrl_select_state(pins->p, pin_state);
	return 0;
}

/**
 */
static int intf_audio_adss_remove(struct platform_device *pdev)
{
	DBG_TRC("%s:\n", __func__);
	adss_glb_i2s_interface_en(DISABLE);
	avm_adss_audio_local_base = NULL;
	audio_blk_rst         = NULL;
	return 0;
}

/**
 */
static int intf_pcm_driver_probe(struct platform_device *pdev)
{
	uint32_t tx_channel;
	uint32_t rx_channel;
	struct device_node *np = NULL;
	struct resource *res;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	adss_pcm_base = devm_ioremap_resource(&pdev->dev, res);

	if (IS_ERR(adss_pcm_base)) {
		pr_err("%s: error adss_pcm_base\n", __func__);
		return PTR_ERR(adss_pcm_base);
	}
	np = of_node_get(pdev->dev.of_node);

	if (of_property_read_u32(np, "dma-tx-channel", &tx_channel)) {
		pr_err("%s: error reading critical device node properties\n", np->name);
		return -EINVAL;
	}

	if (of_property_read_u32(np, "dma-rx-channel", &rx_channel)) {
		pr_err("%s: error reading critical device node properties\n",
				np->name);
		return -EINVAL;
	}

	pcm_clk = devm_clk_get(&pdev->dev, "audio_pcm_clk");
	if (IS_ERR(pcm_clk)) {
		pr_err("%s: Error in getting PCM Clk\n", __func__);
		return PTR_ERR(pcm_clk);
	}
	DBG_TRC("%s: adss_pcm_base=%p(%x-%x) tx_channel=%u rx_channel=%u\n", __func__, adss_pcm_base,
		res->start, res->end, tx_channel, rx_channel);
	return 0;
}

/**
 */
static int intf_pcm_driver_remove(struct platform_device *pdev)
{
	DBG_TRC("%s:\n", __func__);
	devm_clk_put(&pdev->dev, pcm_clk);
	return 0;
}

/**
 */
static uint32_t mbox_get_desc_size(void)
{
	struct _mbox_desc *desc;
	uint32_t ipq_desc_size;

	if (is_IPQ4019())
		ipq_desc_size = (sizeof(desc[0]));
	else
		ipq_desc_size = (sizeof(desc[0]) - sizeof(desc[0].vuc_dword));

	return ipq_desc_size;
}

/**
 */
struct _mbox_desc *ipq_desc_ptr(struct _mbox_dma *pmbox_dma, void *desc_ptr, int no_of_desc)
{
	return (struct _mbox_desc *)((uint8_t *)desc_ptr + (pmbox_dma->desc_size * no_of_desc));
}

/**
 */
static uint32_t mbox_get_mask(void)
{
	if (is_IPQ4019())
		return DMA_BIT_MASK(28);
	else
		return DMA_BIT_MASK(32);
}
/**
 */
static void mbox_interrupt_ack(int dma_id, unsigned int status, unsigned int mask)
{
	if (is_IPQ807x()) {
		status &= mask;
	} else {
		status &= ~mask;
	}
	_mbox_write(dma_id, status, ADSS_MBOXn_MBOX_INT_STATUS_REG, NULL);
}

/**
 * call the installed rx- and tx-callbacks
 * also manage cache-validation
 */
static inline void mbox_ack_desc(struct _mbox_dma *pmbox_dma)
{
	unsigned int i, own_cnt = 0;
	struct _mbox_desc *desc;

	desc = pmbox_dma->virt_desc;
	for (i = 0; i < pmbox_dma->ndescs; i++) {
		if (desc->OWN == 0) {
			unsigned int index = (MBOX_PHYS_DMA_ADDRESS(pmbox_dma, pmbox_dma->phys_buf[0]) ==
					      MBOX_PHYS_DMA_ADDRESS(pmbox_dma, desc->BufPtr)) ? 0 : 1;
			int resize = 0;

			if (pmbox_dma->RxTxData && (own_cnt == 0)) {
				if (pmbox_dma->dir == CAPTURE) {
					dmac_inv_range(pmbox_dma->virt_buf[index],
						       pmbox_dma->virt_buf[index] + pmbox_dma->size - 1);
				}
				resize = pmbox_dma->RxTxData(pmbox_dma->refhandle, pmbox_dma->virt_buf[index]);
#if defined(DBG_TDM_RESIZE_CHECK)
				if (pmbox_dma->dbg_force_reoffset) {
					resize = min(pmbox_dma->dbg_force_reoffset, pmbox_dma->size / 2);
					pmbox_dma->dbg_force_reoffset = 0;
				}
#endif/*--- #if defined(DBG_TDM_RESIZE_CHECK) ---*/
				if (resize < 0) {
					DBG_TRC("[tdmlink]%s: error: no slot-synchronize -> reinit dma(%d)\n",
						__func__, resize);
					resize = 0;
				}
				if (pmbox_dma->force_resize) {
					/**
					 * Trittbrettfahrer-Irq(Tx) muss DMA-Ack IMMER vor Hw-Irq(Rx) liefern -
					 * sonst wird der ACK erst mit folgenden Rx-Irq bestaetigt...
					 * und dma 4 ms zu spaet!
					 * Also hier den DMA-Buffer um einen FS verkuerzen
					 */
					resize += pmbox_dma->force_resize;
					pmbox_dma->force_resize = 0;
				}
				if (resize) {
					pmbox_dma->reoffset += resize;
				}
				if (pmbox_dma->dir == PLAYBACK) {
					dmac_flush_range(pmbox_dma->virt_buf[index],
							 pmbox_dma->virt_buf[index] + pmbox_dma->size - 1);
				}

			}
			desc->size    = pmbox_dma->size - resize;
			desc->length  = desc->size;
			desc->OWN = 1;
			desc->ei  = 1;
			own_cnt++;
		}
		desc  = ipq_desc_ptr(pmbox_dma, desc, 1);
	}
	dmac_flush_range(pmbox_dma->virt_desc, ((unsigned char *)desc) - 1);
	if (own_cnt > 1) {
		pmbox_dma->own_ovr += own_cnt - 1;
	} else if (own_cnt == 0) {
		pmbox_dma->own_udr++;
	}
}

/**
 */
static irqreturn_t mbox_dma_irq(int irq, void *dev_id)
{
	struct _mbox_dma *pmbox_dma = (struct _mbox_dma *)dev_id;
	uint32_t ret = IRQ_NONE;
	unsigned int status;
	unsigned int dma_id = pmbox_dma->dma_id;

	status = _mbox_read(dma_id, ADSS_MBOXn_MBOX_INT_STATUS_REG, NULL);

	if (status & MBOX_INT_STATUS_RX_DMA_COMPLETE) {
		mbox_interrupt_ack(dma_id, status, MBOX_INT_STATUS_RX_DMA_COMPLETE);
		mbox_ack_desc(pmbox_dma);
		pmbox_dma->irq_cnt++;
		ret = IRQ_HANDLED;
	}
	if (status & MBOX_INT_STATUS_TX_DMA_COMPLETE) {
		mbox_interrupt_ack(dma_id, status, MBOX_INT_STATUS_TX_DMA_COMPLETE);
		mbox_ack_desc(pmbox_dma);
		pmbox_dma->irq_cnt++;
		ret = IRQ_HANDLED;
	}
	if ((status & MBOX_INT_STATUS_RX_UNDERFLOW) || (status & MBOX_INT_STATUS_RX_FIFO_UNDERFLOW)) {
		mbox_interrupt_ack(dma_id, status,
				   status & (MBOX_INT_STATUS_RX_UNDERFLOW | MBOX_INT_STATUS_RX_FIFO_UNDERFLOW));
		pmbox_dma->err_cnt++;
		ret = IRQ_HANDLED;
	}
	if ((status & MBOX_INT_STATUS_TX_OVERFLOW) || (status & MBOX_INT_STATUS_TX_FIFO_OVERFLOW)) {
		mbox_interrupt_ack(dma_id, status,
				   status & (MBOX_INT_STATUS_TX_OVERFLOW | MBOX_INT_STATUS_TX_FIFO_OVERFLOW));
		pmbox_dma->err_cnt++;
		ret = IRQ_HANDLED;
	}
	mbox_interrupt_ack(dma_id, status, status);
	if (pmbox_dma->irqcontext_mbox) {
		unsigned int status2;
		struct _mbox_dma *pmbox_dma2 = (struct _mbox_dma *)pmbox_dma->irqcontext_mbox;

		if (pmbox_dma2->reoffset > pmbox_dma->reoffset) {
			unsigned int cdir, cdir2;

			pmbox_dma->force_resize = pmbox_dma->slots * sizeof(short);

			cdir = pmbox_dma->dir == CAPTURE ? 'R' : 'T';
			cdir2 = pmbox_dma2->dir == CAPTURE ? 'R' : 'T';
			status2 = _mbox_read(pmbox_dma2->dma_id, ADSS_MBOXn_MBOX_INT_STATUS_REG, NULL);
			pr_err("[tdm_link][%lu/%lu] %cx-Reoffset %u < %cx-Reoffset %u: force resize to sync %cx(%sCOMPLETED) and %cx(%sCOMPLETED)\n",
			       pmbox_dma->irq_cnt,
			       pmbox_dma2->irq_cnt,
			       cdir,
			       pmbox_dma->reoffset,
			       cdir2,
			       pmbox_dma2->reoffset,
			       cdir,
			       (status  & (MBOX_INT_STATUS_TX_DMA_COMPLETE | MBOX_INT_STATUS_RX_DMA_COMPLETE)) ? "" : "NOT ",
			       cdir2,
			       (status2 & (MBOX_INT_STATUS_TX_DMA_COMPLETE | MBOX_INT_STATUS_RX_DMA_COMPLETE)) ? "" : "NOT ");
		}
		return mbox_dma_irq(irq, (void *)pmbox_dma2);
	}
	return ret;
}

/**
 */
static void mbox_dma_policy_reset(int dma_id)
{
	unsigned int val;

	val   = mbox_read(dma_id, ADSS_MBOXn_MBOX_DMA_POLICY_REG);
	val  |= MBOX_DMA_POLICY_SW_RESET;

	/*--- val |= MBOX_DMA_POLICY_RXD_16BIT_SWAP | MBOX_DMA_POLICY_RXD_END_SWAP; ??? ---*/
	mbox_write(dma_id, val, ADSS_MBOXn_MBOX_DMA_POLICY_REG);
	usleep_range(4000, 5000);

	val &= ~(MBOX_DMA_POLICY_SW_RESET);
	mbox_write(dma_id, val, ADSS_MBOXn_MBOX_DMA_POLICY_REG);
}

/**
 * nur 16 Bit
 * irq < 0: kein IRQ aufsetzen
 */
static int mbox_dma_init(struct _mbox_dma *pmbox_dma,
			 void *refhandle,
			 int (*RxTxData)(void *refhandle, void *p),
			 struct device *dev, int dma_id, int dir,
			 int irq, int cpu,
			 unsigned int slots,
			 unsigned int fs_per_dma,
			 unsigned int n_desc
			)
{
	unsigned int DescriptorPoolSize, DMABufferSize, AlignedBufferSize, DMAPoolSize;
	unsigned int i;
	int rc = 0;
	dma_addr_t dmaPhy;
	void *dmaVirt;

	if (pmbox_dma->irq_active > irq_uninitialized) {
		return -1;
	}
	memset(pmbox_dma, 0, sizeof(struct _mbox_dma));

	pmbox_dma->desc_size = mbox_get_desc_size();
	pmbox_dma->ndescs    = n_desc;
	pmbox_dma->mbox_mask = mbox_get_mask();

	DMABufferSize = slots * sizeof(short) * fs_per_dma;
	DescriptorPoolSize = L1_CACHE_ALIGN(pmbox_dma->ndescs * pmbox_dma->desc_size);
	AlignedBufferSize  = L1_CACHE_ALIGN((DMABufferSize));		/*--- aligned dma-buffersize ---*/
	DMAPoolSize = DescriptorPoolSize + 2 * AlignedBufferSize;	/*--- ping pong buffer ---*/
	DBG_TRC("%s: dma_id=%d, dir=%d(%s), irq=%d cpu=%d slots=%u fs_per_dma=%u n_desc=%u DMABufferSize=%u DescriptorPoolSize=%u DMAPoolSize=%u desc_size=%u mbox_mask=%x\n",
		__func__, dma_id,
		dir,
		dir == PLAYBACK ? "tx" :
		dir == CAPTURE ?  "rx" : "?", irq, cpu,
		slots, fs_per_dma, n_desc,
		DMABufferSize, DescriptorPoolSize, DMAPoolSize,
		pmbox_dma->desc_size,
		pmbox_dma->mbox_mask);
	pmbox_dma->dma_pool = kzalloc(DMAPoolSize, GFP_KERNEL);
	if (pmbox_dma->dma_pool == NULL) {
		pr_err("%s: no dma-memory\n", __func__);
		return -ENOMEM;
	}
	dmaVirt = pmbox_dma->dma_pool;
	dmaPhy  = virt_to_phys(pmbox_dma->dma_pool);

	pmbox_dma->slots      = slots;
	pmbox_dma->irq        = irq;
	pmbox_dma->dma_id     = dma_id;
	pmbox_dma->dir        = dir;
	pmbox_dma->dev        = dev;
	pmbox_dma->size       = DMABufferSize;
	pmbox_dma->refhandle  = refhandle;
	pmbox_dma->RxTxData   = RxTxData;
	pmbox_dma->virt_desc  = dmaVirt;
	pmbox_dma->phys_desc  = dmaPhy;

	dmaVirt += DescriptorPoolSize;
	dmaPhy  += DescriptorPoolSize;

	if (!pmbox_dma->virt_desc) {
		pr_err("%s: no dma-memory\n", __func__);
		return -ENOMEM;
	}
	for (i = 0; i < ARRAY_SIZE(pmbox_dma->virt_buf); i++) {
		pmbox_dma->virt_buf[i] = dmaVirt;
		pmbox_dma->phys_buf[i] = dmaPhy;
		dmaVirt += AlignedBufferSize;
		dmaPhy  += AlignedBufferSize;

		dmac_flush_range(pmbox_dma->virt_buf[i], pmbox_dma->virt_buf[i] + pmbox_dma->size - 1);
	}
	mbox_dma_policy_reset(dma_id);
	if (irq >= 0) {
		uint32_t hwirq = irq;
		struct irq_desc *irq_desc = irq_to_desc(irq);

		if (irq_desc)
			hwirq = irq_desc->irq_data.hwirq;
		pr_info("%s: irq=%d hwirq=%d\n", __func__, irq, hwirq);

		if (cpu < 0)
			cpu = raw_smp_processor_id();
#if defined(CONFIG_AVM_FASTIRQ)
		rc = avm_request_fiq_on(cpu, hwirq, mbox_dma_irq, FIQ_HWIRQ,
				dir == CAPTURE ? "ipq40xx-mbox-rx" : "ipq40xx-mbox-tx",
				(void *)pmbox_dma);
		if (rc == 0) {

			pmbox_dma->fiq_cpumode = 1 + cpu;
			avm_gic_fiq_setup(hwirq, 1 << cpu, FIQ_PRIO_USER, 0, 0);
			pr_info("%s: %u %pS %p success\n", __func__, cpu, mbox_dma_irq, pmbox_dma);
		} else
#endif
			rc = request_irq_on(cpu, irq, mbox_dma_irq, IRQF_TRIGGER_HIGH,
						dir == CAPTURE ? "ipq40xx-mbox-rx" : "ipq40xx-mbox-tx",
						(void *)pmbox_dma);
	}
	if (rc == 0) {
		pmbox_dma->irq_active = irq_initialized;
	} else {
		pr_err("%s: request_irq(%d failed)\n", __func__, irq);
	}
	return rc;
}

/**
 */
static void mbox_interrupt_enable(struct _mbox_dma *pmbox_dma, unsigned int mask)
{
	unsigned int val;

	val = mbox_read(pmbox_dma->dma_id, ADSS_MBOXn_MBOX_INT_ENABLE_REG);
	val |= mask;
	mbox_write(pmbox_dma->dma_id, val, ADSS_MBOXn_MBOX_INT_ENABLE_REG);
}

/**
 */
static void mbox_interrupt_disable(struct _mbox_dma *pmbox_dma, unsigned int mask)
{
	unsigned int val;

	val = mbox_read(pmbox_dma->dma_id, ADSS_MBOXn_MBOX_INT_ENABLE_REG);
	val &= ~mask;
	mbox_write(pmbox_dma->dma_id, val, ADSS_MBOXn_MBOX_INT_ENABLE_REG);
}

/**
 */
static void mbox_dma_exit(struct _mbox_dma *pmbox_dma)
{
	unsigned int i;

	DBG_TRC("%s(%s)\n", __func__, pmbox_dma->dir == CAPTURE ? "rx" : "tx");
	if (pmbox_dma->irq_active) {
		if (pmbox_dma->irq >= 0) {
#if defined(CONFIG_AVM_FASTIRQ)
			if (pmbox_dma->fiq_cpumode) {
				avm_free_fiq_on(pmbox_dma->fiq_cpumode - 1, pmbox_dma->irq, pmbox_dma);
			} else
#endif/*--- #if defined(CONFIG_AVM_FASTIRQ) ---*/
				free_irq(pmbox_dma->irq, pmbox_dma);
		}
		pmbox_dma->irq_active = irq_uninitialized;
	}
	kfree(pmbox_dma->dma_pool);
	pmbox_dma->virt_desc = NULL;
	pmbox_dma->dma_pool = NULL;
	for (i = 0; i < ARRAY_SIZE(pmbox_dma->virt_buf); i++) {
		pmbox_dma->virt_buf[i] = NULL;
	}
}

/**
 */
static void mbox_dma_stop(struct _mbox_dma *pmbox_dma)
{
	DBG_TRC("%s(%s active=%d)\n", __func__, pmbox_dma->dir == CAPTURE  ? "rx" :
		pmbox_dma->dir == PLAYBACK ? "tx" : "?", pmbox_dma->irq_active);
	if (pmbox_dma->irq_active != irq_progress) {
		return;
	}
	pmbox_dma->irq_active = irq_initialized;
	if (pmbox_dma->dir == PLAYBACK) {
		mbox_interrupt_disable(pmbox_dma, MBOX_INT_ENABLE_RX_DMA_COMPLETE);
		mbox_write(pmbox_dma->dma_id, ADSS_MBOXn_DMA_RX_CONTROL_STOP, ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG);
	} else if (pmbox_dma->dir == CAPTURE) {
		mbox_interrupt_disable(pmbox_dma, MBOX_INT_ENABLE_TX_DMA_COMPLETE);
		mbox_write(pmbox_dma->dma_id, ADSS_MBOXn_DMA_TX_CONTROL_STOP, ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG);
	}
	/* Delay for the dynamically calculated max time based on
	 * sample size, channel, sample rate + margin to ensure that the
	 * DMA engine will be truly idle.
	 */
	usleep_range(10000, 12000);
	adss_stereo_config_enable(pmbox_dma, DISABLE);
	pmbox_dma->reoffset = 0;
}

/**
 * interne Clock:
   REFDIV=5
   POSTPLLDIV=1
   INT_DIV=0x31
   FRAC_DIV=0x9ba6
   fref=48e6
   fvco= fref / (REFDIV+1) *(INT_DIV + (FRAC_DIV:17,5)/(1<<13) + (FRAC_DIV:4,0) / (25 * (1<<13)))

   fpll=fvco/ (2 *POSTPLLDIV)

   M_RDIV=5
   M_MDIV=15
   fmclk= (fpll / ((M_RDIV + 1) /2)) / (M_MDIV +1)
   B_MDIV=191
   fbclk=fpll/(B_MDIV+1)
 */
static void adss_config_clksel(unsigned int mux)
{
	unsigned int muxr_cfg, mmisc_cfg, bmisc_cfg, xm_cfg;

	DBG_TRC("%s(mux=%u)\n", __func__, mux);
	if (mux == 0) {
		/*--- use refclock 48 MHz ---*/
		muxr_cfg  = 0 << 8;
		bmisc_cfg = (46 << 1);       /*--- B_MDIV ---*/
		mmisc_cfg = (0 << 4);        /*--- M_MDIV ---*/
		xm_cfg    = (22 << 8)  | 0;  /*--- SRC_SEL | M_RDIV ---*/
	} else if (mux == 2) {
		/*--- use external clock 10.368 MHz ---*/
		muxr_cfg  = 2 << 8;          /*--- B_SRC_SEL ---*/
		xm_cfg    = (2 << 8) | 0;    /*--- M_SRC_SEL | M_RDIV ---*/

		bmisc_cfg = (0 << 1);        /*--- B_MDIV ---*/
		mmisc_cfg = (0 << 4);        /*--- M_MDIV ---*/
	} else if (mux == 1) {
		/*--- use pll 196 MHz ---*/
		muxr_cfg  = 1 << 8;
		bmisc_cfg = (191 << 1);     /*--- B_MDIV ---*/
		mmisc_cfg = (15 << 4);      /*--- M_MDIV ---*/
		xm_cfg    = (1 << 8)  | 5;  /*--- SRC_SEL | M_RDIV ---*/
	}
	adss_audio_write(0, ADSS_AUDIO_RXB_CBCR_REG);
	adss_audio_write(0, ADSS_AUDIO_RXM_CBCR_REG);

	adss_audio_write(0, ADSS_AUDIO_TXB_CBCR_REG);
	adss_audio_write(0, ADSS_AUDIO_TXM_CBCR_REG);
	usleep_range(10000, 12000);

	adss_audio_write(bmisc_cfg, ADSS_AUDIO_RXB_MISC_REG);
	adss_audio_write(bmisc_cfg, ADSS_AUDIO_TXB_MISC_REG);

	adss_audio_write(mmisc_cfg, ADSS_AUDIO_RXM_MISC_REG);
	adss_audio_write(mmisc_cfg, ADSS_AUDIO_TXM_MISC_REG);

	adss_audio_write(muxr_cfg, ADSS_AUDIO_RXB_CFG_MUXR_REG);
	adss_audio_write(muxr_cfg, ADSS_AUDIO_TXB_CFG_MUXR_REG);

	adss_audio_write(xm_cfg, ADSS_AUDIO_RXM_CFG_RCGR_REG);
	adss_audio_write(xm_cfg, ADSS_AUDIO_TXM_CFG_RCGR_REG);

	usleep_range(10000, 12000);
	adss_audio_write(1, ADSS_AUDIO_RXB_CBCR_REG);
	adss_audio_write(1, ADSS_AUDIO_RXM_CBCR_REG);

	adss_audio_write(1, ADSS_AUDIO_TXB_CBCR_REG);
	adss_audio_write(1, ADSS_AUDIO_TXM_CBCR_REG);
}

/**
 *	Enable the I2S Stereo block for operation
 */
static void adss_stereo_config_enable(struct _mbox_dma *pmbox_dma, uint32_t enable)
{
	int stereo_id = 0;
	uint32_t cfg;

	DBG_TRC("%s(%s enable=%u)\n", __func__, pmbox_dma->dir == CAPTURE  ? "rx" :
		pmbox_dma->dir == PLAYBACK ? "tx" : "?", enable);
	if (pmbox_dma->dir == PLAYBACK) {
		stereo_id = tdm_priv.stereo_tx;
	} else if (pmbox_dma->dir == CAPTURE) {
		stereo_id = tdm_priv.stereo_rx;
	} else {
		return;
	}
	cfg = adss_stereo_read(stereo_id, ADSS_STEREOn_STEREO0_CONFIG_REG);
	cfg &= ~(STEREOn_CONFIG_ENABLE);
	if (enable) {
		cfg |= STEREOn_CONFIG_ENABLE;
	}
	adss_stereo_write(stereo_id, cfg, ADSS_STEREOn_STEREO0_CONFIG_REG);
}

/**
 */
static void adss_stereo_config_reset(struct _mbox_dma *pmbox_dma, uint32_t reset)
{
	int stereo_id = 0;
	uint32_t cfg;

	DBG_TRC("%s(%s reset=%u)\n", __func__, pmbox_dma->dir == CAPTURE  ? "rx" :
		pmbox_dma->dir == PLAYBACK ? "tx" : "?", reset);
	if (pmbox_dma->dir == PLAYBACK) {
		stereo_id = tdm_priv.stereo_tx;
	} else if (pmbox_dma->dir == CAPTURE) {
		stereo_id = tdm_priv.stereo_rx;
	} else {
		return;
	}
	cfg = adss_stereo_read(stereo_id, ADSS_STEREOn_STEREO0_CONFIG_REG);
	cfg &= ~(STEREOn_CONFIG_RESET | STEREOn_CONFIG_MIC_RESET);
	if (reset) {
		cfg |= (STEREOn_CONFIG_RESET | STEREOn_CONFIG_MIC_RESET);
	}
	adss_stereo_write(stereo_id, cfg, ADSS_STEREOn_STEREO0_CONFIG_REG);
}

/**
 */
static void adss_stereo_cnt_reset(struct _mbox_dma *pmbox_dma)
{
	int stereo_id = 0;

	if (pmbox_dma->dir == PLAYBACK) {
		stereo_id = tdm_priv.stereo_tx;
	} else if (pmbox_dma->dir == CAPTURE) {
		stereo_id = tdm_priv.stereo_rx;
	} else {
		return;
	}
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_TX_SAMPLE_CNT_LSB_REG);
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_TX_SAMPLE_CNT_MSB_REG);
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_RX_SAMPLE_CNT_LSB_REG);
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_RX_SAMPLE_CNT_MSB_REG);

	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_RX_COMPLETE_CNT_REG);
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_TX_COMPLETE_CNT_REG);
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_UNDERRUN_CNT_REG);
	adss_stereo_write(stereo_id, 0, ADSS_STEREOn_STEREO0_OVERRUN_CNT_REG);
}
/**
 */
static void mbox_dma_prepare_desc(struct _mbox_dma *pmbox_dma)
{
	struct _mbox_desc *virt_desc, *phys_desc;
	unsigned int i;

	if (pmbox_dma->irq_active != irq_initialized) {
		return;
	}
	phys_desc = (struct _mbox_desc *)pmbox_dma->phys_desc;
	virt_desc = pmbox_dma->virt_desc;
	for (i = 0; i < pmbox_dma->ndescs; i++) {
		virt_desc->OWN     = 1;
		virt_desc->ei      = 1;
		virt_desc->rsvd1   = virt_desc->EOM = 0;
		virt_desc->BufPtr  = MBOX_PHYS_DMA_ADDRESS(pmbox_dma, pmbox_dma->phys_buf[i & 1]);
		virt_desc->NextPtr = MBOX_PHYS_DMA_ADDRESS(pmbox_dma, ipq_desc_ptr(pmbox_dma, phys_desc, (i + 1) % pmbox_dma->ndescs));
		virt_desc->size    = pmbox_dma->size;
		virt_desc->length  = virt_desc->size;
		DBG_TRC_DESC("[%u]desc[%p] BufPtr=%08x size=%u length=%u next_desc[%08x]\n", i,
				&phys_desc[i],
				MBOX_PHYS_DMA_ADDRESS(pmbox_dma, virt_desc->BufPtr),
				virt_desc->size,
				virt_desc->length,
				virt_desc->NextPtr);
		virt_desc  = ipq_desc_ptr(pmbox_dma, virt_desc, 1);
	}
	dmac_flush_range(pmbox_dma->virt_desc, ((unsigned char *)virt_desc) - 1);
	pmbox_dma->irq_cnt = 0;
	pmbox_dma->err_cnt = 0;
	pmbox_dma->own_udr = 0;
	pmbox_dma->own_ovr = 0;
}

/**
 */
static void mbox_dma_prepare_reg(struct _mbox_dma *pmbox_dma)
{
	unsigned int val;

	mbox_dma_policy_reset(pmbox_dma->dma_id);

	adss_stereo_config_enable(pmbox_dma, DISABLE);
	adss_stereo_cnt_reset(pmbox_dma);
	adss_stereo_config_reset(pmbox_dma, ENABLE);
	usleep_range(5000, 6000);
	mbox_write(pmbox_dma->dma_id, MBOX_FIFO_RESET_TX_INIT | MBOX_FIFO_RESET_RX_INIT, ADSS_MBOXn_MBOX_FIFO_RESET_REG);
	adss_stereo_config_reset(pmbox_dma, DISABLE);

	val  = mbox_read(pmbox_dma->dma_id, ADSS_MBOXn_MBOX_DMA_POLICY_REG);

	if (pmbox_dma->dir == PLAYBACK) {
		/* Request the DMA channel to the controller */
		val &= ~ADSS_MBOX_DMA_POLICY_TX_FIFO_THRESHOLD(0xF);
		val &= ~ADSS_MBOX_DMA_POLICY_SRAM_AC(-1);
		val |= MBOX_DMA_POLICY_RX_INT_TYPE;
		val |= ADSS_MBOX_DMA_POLICY_SRAM_AC((unsigned long)pmbox_dma->phys_desc);
		val |= ADSS_MBOX_DMA_POLICY_TX_FIFO_THRESHOLD(8);
		mbox_write(pmbox_dma->dma_id, val, ADSS_MBOXn_MBOX_DMA_POLICY_REG);

		mbox_write(pmbox_dma->dma_id, MBOX_PHYS_DMA_ADDRESS(pmbox_dma, pmbox_dma->phys_desc),
			   ADSS_MBOXn_MBOXn_DMA_RX_DESCRIPTOR_BASE_REG);
	} else if (pmbox_dma->dir == CAPTURE) {
		/* Request the DMA channel to the controller */
		val &= ~ADSS_MBOX_DMA_POLICY_TX_FIFO_THRESHOLD(0xF);
		val &= ~ADSS_MBOX_DMA_POLICY_SRAM_AC(-1);
		val |= MBOX_DMA_POLICY_TX_INT_TYPE;
		val |= ADSS_MBOX_DMA_POLICY_SRAM_AC((unsigned long)pmbox_dma->phys_desc);
		val |= ADSS_MBOX_DMA_POLICY_TX_FIFO_THRESHOLD(8);
		mbox_write(pmbox_dma->dma_id, val, ADSS_MBOXn_MBOX_DMA_POLICY_REG);

		mbox_write(pmbox_dma->dma_id, MBOX_PHYS_DMA_ADDRESS(pmbox_dma, pmbox_dma->phys_desc),
			   ADSS_MBOXn_MBOXn_DMA_TX_DESCRIPTOR_BASE_REG);
	}
}

/**
 */
static void mbox_dma_start(struct _mbox_dma *pmbox_dma)
{
	DBG_TRC("%s(%s)\n", __func__,
		pmbox_dma->dir == CAPTURE  ? "rx" :
		pmbox_dma->dir == PLAYBACK ? "tx" : "?");

	if (pmbox_dma->irq_active != irq_initialized) {
		return;
	}
	pmbox_dma->irq_active   = irq_progress;
	/* The direction is indicated from the DMA engine perspective
	 * i.e. we'll be using the RX registers for Playback and
	 * the TX registers for capture
	 */
	if (pmbox_dma->dir == PLAYBACK) {
		mbox_interrupt_enable(pmbox_dma, MBOX_INT_ENABLE_RX_DMA_COMPLETE);
		mbox_write(pmbox_dma->dma_id, ADSS_MBOXn_DMA_RX_CONTROL_START, ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG);
	} else if (pmbox_dma->dir == CAPTURE) {
		mbox_interrupt_enable(pmbox_dma, MBOX_INT_ENABLE_TX_DMA_COMPLETE);
		mbox_write(pmbox_dma->dma_id, ADSS_MBOXn_DMA_TX_CONTROL_START, ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG);
	}
	adss_stereo_config_enable(pmbox_dma, ENABLE);
}

/**
 */
static int intf_mbox_probe(struct platform_device *pdev)
{
	struct device_node *np = NULL;
	int irq;
	uint32_t tx_channel;
	uint32_t rx_channel;
	uint32_t id;
	void __iomem *reg_base;
	struct resource *res;
	int rc = 0;

	np = of_node_get(pdev->dev.of_node);
	if ((of_property_read_u32(np, "dma-index", &id))) {
		pr_err("%s: error reading critical device node properties\n", np->name);
		rc = -EINVAL;
		goto init_err;
	}
	if (id >= ARRAY_SIZE(mbox_data)) {
		rc = -EINVAL;
		goto init_err;
	}
	if ((of_property_read_u32(np, "tx-channel", &tx_channel))) {
		tx_channel = CHN_STATUS_DISABLE;
	}
	if ((of_property_read_u32(np, "rx-channel", &rx_channel))) {
		rx_channel = CHN_STATUS_DISABLE;
	}
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "%s: %d: Error getting mbox resource\n", __func__, __LINE__);
		return -ENODEV;
	}
	/* Read interrupt and store */
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "%s: MBOX %d IRQ is not provided\n", __func__, id);
		rc = -EINVAL;
		goto init_err;
	}
	reg_base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(reg_base)) {
		of_node_put(pdev->dev.of_node);
		return PTR_ERR(reg_base);
	}
	DBG_TRC("%s: reg_base=%p(%x-%x) irq=%u tx_channel=%u rx_channel=%u dma_id=%u\n", __func__,
		reg_base, res->start, res->end, irq, tx_channel, rx_channel, id);

	mbox_data[id].reg_base = reg_base;
	mbox_data[id].irq      = irq;

init_err:
	of_node_put(pdev->dev.of_node);
	return rc;
}

/**
 */
static int intf_mbox_remove(struct platform_device *pdev)
{
	struct device_node *np = NULL;
	uint32_t id;
	int rc = 0;

	np = of_node_get(pdev->dev.of_node);
	if ((of_property_read_u32(np, "dma-index", &id))) {
		pr_err("%s: error reading critical device node properties\n", np->name);
		rc = -EINVAL;
		goto remove_err;
	}
	if (id >= ARRAY_SIZE(mbox_data)) {
		rc = -EINVAL;
		goto remove_err;
	}
	DBG_TRC("%s: id==%u\n", __func__, id);
remove_err:
	of_node_put(pdev->dev.of_node);
	return rc;
}

/**
 */
static void _tdm_audio_clk_disable(struct clk **clk, struct device *dev)
{
	if (!IS_ERR_OR_NULL(*clk)) {
		DBG_TRC("%s(%s)\n", __func__, __clk_get_name(*clk));
		if (__clk_is_enabled(*clk)) {
			clk_disable_unprepare(*clk);
		}
		devm_clk_put(dev, *clk);
		*clk = NULL;
	}
}

/**
 */
static int intf_tdm_remove(struct platform_device *pdev)
{
	if (tdm_priv.clk_set) {
		DBG_TRC("%s:\n", __func__);
		tdm_priv.clk_set = 0; /*--- workarround  da __clk_is_enabled() inkonsistent! ---*/
		_tdm_audio_clk_disable(&tdm_priv.audio_tx_mclk, &pdev->dev);
		_tdm_audio_clk_disable(&tdm_priv.audio_tx_bclk, &pdev->dev);
		_tdm_audio_clk_disable(&tdm_priv.audio_rx_mclk, &pdev->dev);
		_tdm_audio_clk_disable(&tdm_priv.audio_rx_bclk, &pdev->dev);
	}
	return 0;
}

/**
 */
static int intf_tdm_probe(struct platform_device *pdev)
{
	struct device_node *np = NULL;
	int ret = 0;

	DBG_TRC("%s(%s)\n", __func__, pdev->name);
	np = of_node_get(pdev->dev.of_node);
	/* TX is enabled only when both DMA and Stereo TX channel
	 * is specified in the DTSi
	 */
	if (!(of_property_read_u32(np, "dma-tx-channel", &tdm_priv.mbox_tx)
		|| of_property_read_u32(np, "stereo-tx-port", &tdm_priv.stereo_tx))) {
		tdm_priv.tx_enabled = ENABLE;
	}
	/* RX is enabled only when both DMA and Stereo RX channel
	 * is specified in the DTSi, except in case of SPDIF RX
	 */
	if (!(of_property_read_u32(np, "dma-rx-channel", &tdm_priv.mbox_rx))) {
		if (!(of_property_read_u32(np, "stereo-rx-port", &tdm_priv.stereo_rx))) {
			tdm_priv.rx_enabled = ENABLE;
		}
	}
	/* Either TX or Rx should have been enabled for a DMA/Stereo Channel */
	if (!(tdm_priv.tx_enabled || tdm_priv.rx_enabled)) {
		pr_err("%s: error reading critical device node properties\n", np->name);
		ret = -EFAULT;
	} else {
		tdm_priv.audio_tx_bclk = devm_clk_get(&pdev->dev, "audio_tx_bclk");
		tdm_priv.audio_tx_mclk = devm_clk_get(&pdev->dev, "audio_tx_mclk");
		tdm_priv.audio_rx_bclk = devm_clk_get(&pdev->dev, "audio_rx_bclk");
		tdm_priv.audio_rx_mclk = devm_clk_get(&pdev->dev, "audio_rx_mclk");
		if (IS_ERR(tdm_priv.audio_tx_bclk) ||
		   IS_ERR(tdm_priv.audio_tx_mclk) ||
		   IS_ERR(tdm_priv.audio_rx_bclk) ||
		   IS_ERR(tdm_priv.audio_rx_mclk)) {
			pr_err("%s error on get clock\n", __func__);
		} else {
			tdm_priv.pdev = pdev;
			pr_err("%s: %s dma-tx-channel=%u stereo-tx-port=%u %s dma-rx-channel=%u stereo-rx-port=%u\n",
				__func__,
				tdm_priv.tx_enabled ? "ena:" : "dis",
				tdm_priv.mbox_tx, tdm_priv.stereo_tx,
				tdm_priv.rx_enabled ? "ena:" : "dis",
				tdm_priv.mbox_rx, tdm_priv.stereo_rx);
		}
	}
	of_node_put(pdev->dev.of_node);
	return ret;
}

/**
 */
static int intf_stereo_probe(struct platform_device *pdev)
{
	uint32_t stereo_port_id = 0;
	struct resource *res;
	struct device_node *np = NULL;
	int ret = 0;

	DBG_TRC("%s\n", __func__);
	np = of_node_get(pdev->dev.of_node);

	if (!(of_property_read_u32(np, "stereo-index", &stereo_port_id))) {
		if (stereo_port_id >= MAX_STEREO_ENTRIES) {
			of_node_put(pdev->dev.of_node);
			pr_err("%s: error: stereo_port_id=%u\n", np->name, stereo_port_id);
			ret = -EFAULT;
		} else {
			res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
			stereo_base[stereo_port_id] = devm_ioremap_resource(&pdev->dev, res);
			if (IS_ERR(stereo_base[stereo_port_id])) {
				ret = PTR_ERR(stereo_base[stereo_port_id]);
			}
		}
	} else {
		pr_err("%s: error reading critical device node properties\n", np->name);
		ret = -EFAULT;
	}
	of_node_put(pdev->dev.of_node);
	return ret;
}

/**
 */
static int intf_stereo_remove(struct platform_device *pdev)
{
	DBG_TRC("%s: todo\n", __func__);
	return 0;
}

/**
 */
static int qca_pcm_avm_probe(struct platform_device *pdev)
{
	const struct of_device_id *match;
	int intf, ret = -EINVAL;

	DBG_TRC("%s\n", __func__);
	match = of_match_device(pcm_avm_id_table, &pdev->dev);
	if (!match) {
		pr_err("%s: match error\n", __func__);
		return -ENODEV;
	}
	intf = (uint32_t)match->data;
	DBG_TRC("%s %u\n", __func__, intf);
	switch (intf) {
	case intf_audio_adss_ipq4019:
		ret = intf_audio_adss_probe(pdev);
		ipq_cfgs = &ipq4019_cfgs;
		break;
	case intf_audio_adss_ipq8074:
		ret = intf_audio_adss_probe(pdev);
		ipq_cfgs = &ipq8074_cfgs;
		break;
	case intf_pcm:
		ret = intf_pcm_driver_probe(pdev);
		break;
	case intf_mbox:
		ret = intf_mbox_probe(pdev);
		break;
	case intf_tdm:
		ret = intf_tdm_probe(pdev);
		break;
	case intf_stereo:
		ret = intf_stereo_probe(pdev);
		break;
	case intf_tdm_pins:
		ret = intf_tdm_pins_probe(pdev, 1);
		break;
	}
	if (ret && DebugHandle) {
		avm_DebugCallUnRegister(DebugHandle);
		DebugHandle = NULL;
	} else if (DebugHandle == NULL) {
		DebugHandle = avm_DebugCallRegister("pcm_", cmdlineparse, NULL);
	}
	return ret;
}

/**
 */
static int qca_pcm_avm_remove(struct platform_device *pdev)
{
	const struct of_device_id *match;
	int intf, ret = -EINVAL;

	if (DebugHandle) {
		avm_DebugCallUnRegister(DebugHandle);
		DebugHandle = NULL;
	}
	match = of_match_device(pcm_avm_id_table, &pdev->dev);
	if (!match) {
		pr_err("%s: match error\n", __func__);
		return -ENODEV;
	}
	intf = (uint32_t)match->data;
	/*--- DBG_TRC("%s %u\n", __func__, intf); ---*/
	switch (intf) {
	case intf_audio_adss_ipq4019:
	case intf_audio_adss_ipq8074:
		ret = intf_audio_adss_remove(pdev);
		break;
	case intf_pcm:
		ret = intf_pcm_driver_remove(pdev);
		break;
	case intf_mbox:
		ret  = intf_mbox_remove(pdev);
		break;
	case intf_tdm:
		ret  = intf_tdm_remove(pdev);
		break;
	case intf_stereo:
		ret  = intf_stereo_remove(pdev);
		break;
	}
	return ret;
}

#define PCM_MULT_FACTOR		4
/**
 */
static int adss_set_clk_rate(unsigned int slot_count, unsigned int bit_width, unsigned int rate)
{
	unsigned int clk_rate = slot_count * bit_width * rate * PCM_MULT_FACTOR;
	int ret = clk_set_rate(pcm_clk, clk_rate);

	pr_err("%s(slot_count=%u bit_width=%u rate=%u) -> clk_rate=%x\n", __func__, slot_count, bit_width,
			rate, clk_rate);
	if (ret) {
		pr_err("%s : clk_set_rate failed for pcm clock\n", __func__);
		return ret;
	}
	/*--- ret = clk_prepare_enable(pcm_clk); ---*/
	return ret;
}

/**
 *	Enable TDM
 */
static void adss_glb_tdm_mode(void)
{
	uint32_t cfg;

	DBG_TRC("%s()\n", __func__);
	cfg = adss_audio_read(ADSS_GLB_AUDIO_MODE_REG);
	cfg &= ~GLB_AUDIO_MODE_XMIT_MASK;
	cfg |= GLB_AUDIO_MODE_XMIT_TDM;
	cfg &= ~GLB_AUDIO_MODE_RECV_MASK;
	cfg |= GLB_AUDIO_MODE_RECV_TDM;

	adss_audio_write(cfg, ADSS_GLB_AUDIO_MODE_REG);
}

/**
 *	FSYNC Hi Duration for Transmitter/Receiver
 */
static void adss_glb_tdm_ctrl_sync_num(uint32_t val, uint32_t dir)
{
	uint32_t cfg;

	DBG_TRC("%s(%u, dir=%s)\n", __func__, val, dir == CAPTURE ? "rx" : dir == PLAYBACK ? "tx" : "?");
	cfg = adss_audio_read(ADSS_GLB_TDM_CTRL_REG);

	if (dir == PLAYBACK) {
		cfg &= ~(GLB_TDM_CTRL_TX_SYNC_NUM_MASK);
		cfg |= GLB_TDM_CTRL_TX_SYNC_NUM(val);
	} else if (dir == CAPTURE) {
		cfg &= ~(GLB_TDM_CTRL_RX_SYNC_NUM_MASK);
		cfg |= GLB_TDM_CTRL_RX_SYNC_NUM(val);
	}
	adss_audio_write(cfg, ADSS_GLB_TDM_CTRL_REG);
}

/**
 *	Serial Data Delay for transmitter/receiver
 */
static void adss_glb_tdm_ctrl_delay(uint32_t delay, uint32_t dir)
{
	uint32_t cfg;

	DBG_TRC("%s(delay=%u, dir=%s)\n", __func__, delay, dir == CAPTURE ? "rx" : dir == PLAYBACK ? "tx" : "?");
	cfg = adss_audio_read(ADSS_GLB_TDM_CTRL_REG);

	if (dir == PLAYBACK) {
		cfg &= ~(GLB_TDM_CTRL_TX_DELAY);
		cfg |= delay ? GLB_TDM_CTRL_TX_DELAY : 0;
	} else if (dir == CAPTURE) {
		cfg &= ~(GLB_TDM_CTRL_RX_DELAY);
		cfg |= delay ? GLB_TDM_CTRL_RX_DELAY : 0;
	}
	adss_audio_write(cfg, ADSS_GLB_TDM_CTRL_REG);
}

/**
 *	Channel Number Per Frame for Transmitter/Receiver
 *	Real value = val + 1
 */
static void adss_glb_tdm_ctrl_ch_num(uint32_t val)
{
	uint32_t cfg;

	DBG_TRC("%s(%u)\n", __func__, val);
	cfg = adss_audio_read(ADSS_GLB_TDM_CTRL_REG);

	cfg &= ~(GLB_TDM_CTRL_TX_CHAN_NUM_MASK);
	cfg |= GLB_TDM_CTRL_TX_CHAN_NUM(val);

	cfg &= ~(GLB_TDM_CTRL_RX_CHAN_NUM_MASK);
	cfg |= GLB_TDM_CTRL_RX_CHAN_NUM(val);

	adss_audio_write(cfg, ADSS_GLB_TDM_CTRL_REG);
}

static void adss_glb_data_port_en(uint32_t enable, uint32_t dir)
{
	uint32_t cfg;
	uint32_t reg, val;

	if (dir == PLAYBACK)  {
		reg = ipq_cfgs->txd_oe.reg;
		val = ipq_cfgs->txd_oe.mask;
	} else {
		reg = ipq_cfgs->rxd_oe.reg;
		val = ipq_cfgs->rxd_oe.mask;
	}

	cfg = adss_audio_read(reg);
	cfg &= ~val;
	if (enable) {
		cfg |= val;
	}
	adss_audio_write(cfg, reg);
}

/**
 *	I2S0 TX Data Port Enable
 *	I2S3 RX Data Port Enable
 *	Todo : Check if bits 6:4 configures only
 *		I2S0 or other channels as well
 */
static void adss_glb_rxtx_data_port_en(uint32_t enable)
{
	DBG_TRC("%s(%u)\n", __func__, enable);

	adss_glb_data_port_en(enable, CAPTURE);
	adss_glb_data_port_en(enable, PLAYBACK);
}

/**
 */
static void adss_glb_framesync_port_en(uint32_t enable, uint32_t dir)
{
	uint32_t cfg;
	uint32_t reg, val;

	if (dir == PLAYBACK)  {
		reg = ipq_cfgs->i2s0_fs_oe.reg;
		val = ipq_cfgs->i2s0_fs_oe.mask;
	} else {
		reg = ipq_cfgs->i2s3_fs_oe.reg;
		val = ipq_cfgs->i2s3_fs_oe.mask;
	}
	cfg = adss_audio_read(reg);
	cfg &= ~val;
	if (enable) {
		cfg |= val;
	}
	adss_audio_write(cfg, reg);
}

/**
 *	Frame Sync Port Enable for I2S0 TX
 *	Frame Sync Port Enable for I2S3 RX
 */
static void adss_glb_rxtx_framesync_port_en(uint32_t enable)
{
	DBG_TRC("%s(%u)\n", __func__, enable);
	adss_glb_framesync_port_en(enable, CAPTURE);
	adss_glb_framesync_port_en(enable, PLAYBACK);
}

/**
 */
static void adss_glb_clk_enable_oe(int enable, uint32_t dir)
{
	uint32_t cfg;
	uint32_t mask = 0;

	DBG_TRC("%s(enable=%u)\n", __func__, enable);
	cfg = adss_audio_read(ADSS_GLB_CLK_I2S_CTRL_REG);
	if (dir == PLAYBACK) {
		mask = (GLB_CLK_I2S_CTRL_TX_BCLK_OE | GLB_CLK_I2S_CTRL_TX_MCLK_OE);
	} else {
		mask = (GLB_CLK_I2S_CTRL_RX_BCLK_OE | GLB_CLK_I2S_CTRL_RX_MCLK_OE);
	}
	if (enable) {
		cfg |= mask;
	} else {
		cfg &= ~mask;
	}
	adss_audio_write(cfg, ADSS_GLB_CLK_I2S_CTRL_REG);
}

/**
 *	I2S Interface Enable
 */
static void adss_glb_i2s_interface_en(int enable)
{
	uint32_t cfg;

	DBG_TRC("%s()\n", __func__);

	cfg = adss_audio_read(ADSS_GLB_CHIP_CTRL_I2S_REG);
	cfg &= ~(GLB_CHIP_CTRL_I2S_INTERFACE_EN);
	if (enable) {
		cfg |=  GLB_CHIP_CTRL_I2S_INTERFACE_EN;
	}
	adss_audio_write(cfg, ADSS_GLB_CHIP_CTRL_I2S_REG);
	usleep_range(4000, 5000);
}
EXPORT_SYMBOL(adss_glb_i2s_interface_en);

/**
 *	Cross 1K Boundary
 */
static void adss_glb_audio_mode_B1K(void)
{
	uint32_t cfg;

	DBG_TRC("%s()\n", __func__);
	cfg = adss_audio_read(ADSS_GLB_AUDIO_MODE_REG);
	cfg |=  GLB_AUDIO_MODE_B1K;
	adss_audio_write(cfg, ADSS_GLB_AUDIO_MODE_REG);
}

/**
 *	I2S Module Reset
 */
static void adss_glb_i2s_reset(void)
{
	DBG_TRC("%s()\n", __func__);
	adss_audio_write(ipq_cfgs->i2s_reset_val.mask, ipq_cfgs->i2s_reset_val.reg);
	usleep_range(4000, 5000);
	adss_audio_write(0x0, ipq_cfgs->i2s_reset_val.reg);
}

/**
 */
static int _clk_set(struct clk *clk, struct device *dev, uint32_t val)
{
	int ret;

	DBG_TRC("%s(%s %u) clk_ena=%u\n", __func__, __clk_get_name(clk), val, __clk_is_enabled(clk));
#if 0
	if (__clk_is_enabled(clk)) {
		clk_disable_unprepare(clk);
	}
#endif
	ret = clk_set_rate(clk, val);
	if (ret != 0) {
		dev_err(dev, "%s: Error in setting %s\n", __func__, __clk_get_name(clk));
		return ret;
	}
	ret = clk_prepare_enable(clk);
	if (ret != 0) {
		dev_err(dev, "%s: Error in enable %s\n", __func__, __clk_get_name(clk));
		return ret;
	}
	return 0;
}

/**
 */
static void adss_config_clk(unsigned int ext_clk,
			    unsigned int bclk,   unsigned int mclk)
{
	DBG_TRC("%s(ext_clk=%u bclk=%u mclk=%u)\n", __func__, ext_clk, bclk, mclk);

	adss_glb_i2s_reset();
	if (is_IPQ4019()) {
		/**
		 * JZ-73366: funktioniert bei Dakota zuverlaessiger ?
		 */
		if (ext_clk == 0) {
			tdm_priv.clk_set = 1; /*--- workarround  da __clk_is_enabled() inkonsistent! ---*/
			_clk_set(tdm_priv.audio_tx_bclk, &tdm_priv.pdev->dev, bclk);
			_clk_set(tdm_priv.audio_tx_mclk, &tdm_priv.pdev->dev, mclk);
			_clk_set(tdm_priv.audio_rx_bclk, &tdm_priv.pdev->dev, bclk);
			_clk_set(tdm_priv.audio_rx_mclk, &tdm_priv.pdev->dev, mclk);

			usleep_range(8000, 10000);
			/*--- Workaround bad clk-interface ? ---*/
			if (mclk == 4096000) {
				if (adss_audio_read(ADSS_AUDIO_PLL_CONFIG_REG) != 0x85) {
					adss_audio_write(0x85, ADSS_AUDIO_PLL_CONFIG_REG);
					usleep_range(8000, 10000);
				}
				if (adss_audio_read(ADSS_AUDIO_PLL_MODULATION_REG) != 0x04dd3062) {
					adss_audio_write(0x04dd3062, ADSS_AUDIO_PLL_MODULATION_REG);
					usleep_range(8000, 10000);
				}
			}
			usleep_range(12000, 16000);
		} else {
			adss_config_clksel(2);
		}
	} else {
		tdm_priv.clk_set = 1; /*--- workarround  da __clk_is_enabled() inkonsistent! ---*/
		_clk_set(tdm_priv.audio_tx_mclk, &tdm_priv.pdev->dev, mclk);
		_clk_set(tdm_priv.audio_tx_bclk, &tdm_priv.pdev->dev, bclk);
		_clk_set(tdm_priv.audio_rx_mclk, &tdm_priv.pdev->dev, mclk);
		_clk_set(tdm_priv.audio_rx_bclk, &tdm_priv.pdev->dev, bclk);
		usleep_range(12000, 16000);
	}
}

/**
 */
static void adss_config_tdm(unsigned int enable, unsigned int master, unsigned int ext_clk)
{
	unsigned int reset_mask, cfg_mode, cfg;

	DBG_TRC("%s(ena=%u, master=%u ext_clk=%u)\n", __func__, enable, master, ext_clk);
	reset_mask =	STEREOn_CONFIG_MASTER |
			STEREOn_CONFIG_ENABLE |
			STEREOn_CONFIG_DATA_WORD_SIZE_MASK |
			STEREOn_CONFIG_I2S_WORD_SIZE_32 |
			STEREOn_CONFIG_MIC_WORD_SIZE_32 |
			STEREOn_CONFIG_MCK_SEL |
			0;

	/*--- only 16 Bit-Mode: ---*/
	cfg_mode   =	STEREOn_CONFIG_DATA_WORD_SIZE(1) |
			STEREOn_CONFIG_I2S_WORD_SIZE_16 |
			STEREOn_CONFIG_MIC_WORD_SIZE_16 |
			/*--- STEREOn_CONFIG_MONO_MODE |  ---*/
			0;
	cfg_mode |= master  ? STEREOn_CONFIG_MASTER  : 0;
	cfg_mode |= enable  ? STEREOn_CONFIG_ENABLE  : 0;
	cfg_mode |= ext_clk ? STEREOn_CONFIG_MCK_SEL : 0; /*--- wenn dieses Bit nicht gesetzt wird, dann wird extclk "zerstoert" ---*/

	cfg = adss_stereo_read(tdm_priv.stereo_tx, ADSS_STEREOn_STEREO0_CONFIG_REG);
	cfg &= ~reset_mask;
	cfg |=  cfg_mode;
	adss_stereo_write(tdm_priv.stereo_tx, cfg, ADSS_STEREOn_STEREO0_CONFIG_REG);

	cfg = adss_stereo_read(tdm_priv.stereo_rx, ADSS_STEREOn_STEREO0_CONFIG_REG);
	cfg &= ~reset_mask;
	cfg |=  cfg_mode;
	adss_stereo_write(tdm_priv.stereo_rx, cfg, ADSS_STEREOn_STEREO0_CONFIG_REG);
}

static struct _tdm_if_config {
	unsigned int tdm_if_api;
	unsigned int slots;
	unsigned int rxdelay;
	unsigned int txdelay;
	unsigned int master;
} tdm_config;

static int __tdm_if_config_init(struct _tdm_if_config *pconf)
{
	unsigned int bclk = 0, mclk = 0;
	unsigned long flags;
	int extclk;

	DBG_TRC_API("%s(slots=%u, rxdelay=%u txdelay=%u master=%u)\n",
		    __func__, pconf->slots, pconf->rxdelay, pconf->txdelay, pconf->master);

	if (pconf->master == 1) {
		bclk = pconf->slots *  BITS_PER_BYTE * sizeof(short) * 8000;
		mclk = bclk * 4;
		extclk = 0;
	} else {
		/*
		 * 252 and 254 are dummy frequency values which will make clock
		 * framework to select external pad clock as source. We need to
		 * configure the mclk and bclk to these fixed values when in
		 * slave mode in order to accept clock from master dev.
		 *
		 * mclk should be set to 252 (mclk_pad_in) and bclk can be set
		 * to 252 (mclk_pad_in) or 254 (bclk_pad_in).
		 */
		if (is_IPQ807x()) {
			mclk   = 252;
		} else {
			mclk   = 255;
		}
		bclk   = 254;
		extclk = 1;
	}
	adss_config_clk(extclk, bclk, mclk);

	local_irq_save(flags);
	adss_glb_tdm_ctrl_ch_num(pconf->slots - 1);
	adss_glb_clk_enable_oe(pconf->master, CAPTURE);
	adss_glb_clk_enable_oe(pconf->master, PLAYBACK);
	adss_glb_rxtx_data_port_en(1);
	adss_glb_rxtx_framesync_port_en(1);

	adss_glb_tdm_mode();
	adss_config_tdm(DISABLE, pconf->master, extclk);
	adss_glb_tdm_ctrl_sync_num(0, CAPTURE);
	adss_glb_tdm_ctrl_sync_num(0, PLAYBACK);
	adss_glb_tdm_ctrl_delay(pconf->rxdelay, CAPTURE);
	adss_glb_tdm_ctrl_delay(pconf->txdelay, PLAYBACK);
	local_irq_restore(flags);
	return 0;
}

/**
 * \brief:
 * initialize tdm-interface
 * \param[in] slots      only 8 tested
 * \param[in] rxdelay 1: serial Data Delay for receiver (1 clock)
 * \param[in] txdelay 1: serial Data Delay for transmitter (1 clock)
 * \param[in] master
 *
 * \retval 0: ok
 */
int tdm_if_config_init(unsigned int slots, unsigned int rxdelay, unsigned int txdelay, unsigned int master)
{
	struct _tdm_if_config *pconfig = &tdm_config;

	if (IS_ERR_OR_NULL(avm_adss_audio_local_base) || IS_ERR_OR_NULL(adss_pcm_base)) {
		pr_err("%s: error: no valid base set - please check your device-tree!\n", __func__);
		return -EINVAL;
	}
	pconfig->slots    = slots;
	pconfig->rxdelay  = rxdelay;
	pconfig->txdelay  = txdelay;
	pconfig->master   = master;
	pconfig->tdm_if_api  = 1;

	return __tdm_if_config_init(pconfig);
}
EXPORT_SYMBOL(tdm_if_config_init);

static void workarround_reset_tdm_interface(void)
{
	struct _tdm_if_config *pconfig = &tdm_config;

	if (pconfig->tdm_if_api == 0)
		return;

	/*--- pr_err("%s\n", __func__); ---*/
	__tdm_if_config_init(pconfig);
}

/**
 * \brief:
 *  disable tdm-interface
 */
void tdm_if_config_exit(void)
{
	DBG_TRC_API("%s()\n", __func__);
	adss_config_tdm(DISABLE, 0, 0);
	adss_glb_tdm_ctrl_ch_num(0);
	adss_glb_clk_enable_oe(0, CAPTURE);
	adss_glb_clk_enable_oe(0, PLAYBACK);
	adss_glb_rxtx_data_port_en(0);
	adss_glb_rxtx_framesync_port_en(0);
	tdm_config.tdm_if_api = 0;
}
EXPORT_SYMBOL(tdm_if_config_exit);

/**
 */
static const char *mbox_dir_name(unsigned int dma_id)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(mbox_dma); i++) {
		if (mbox_dma[i].dma_id == dma_id) {
			return mbox_dma[i].dir == PLAYBACK ? " PLAYBACK" :
			       mbox_dma[i].dir == CAPTURE ?  " CAPTURE"   : "?";
		}
	}
	return "";
}

/**
 * \brief:
 * initialize dma-interface
 * \param[in] refhandle:  see callback
 * \param[in] TxData:     callback for data to send
 * \param[in] RxData:     callback for data receive
 * \param[in] cpu:	  bind on cpu
 * \param[in] only_rxirq: use shared interrupt for rx and tx
 * \param[in] slots:
 * \param[in] fs_per_dma: count of framesyncs for one dma-buffer (4 ms == 32)
 * \param[in] desc_num:   count of descriptors (>2: prevent irq-lost by long-locks)
 *
 * \note:
 * maybe exist more than 2 descriptors but only ping-pong-dma-buffer (small latency!)
 * bufsize of dma-buffer for callback:  fs_per_dma * slot * sizeof(short)
 * return of callbacks: > 0: bytes to shrink dma-size for the following decriptor
 *
 * \retval 0: ok
 */
int tdm_if_dma_init(void *refhandle,
		    int (*TxData)(void *refhandle, void *buf),
		    int (*RxData)(void *refhandle, void *buf),
		    unsigned int cpu,
		    unsigned int only_rxirq,
		    unsigned int slots,
		    unsigned int fs_per_dma,
		    unsigned int desc_num
		   )
{
	int res;

	DBG_TRC_API("%s(%s cpu=%u ref=%p slots=%u, fs_per_dma=%u desc_num=%u)\n", __func__, only_rxirq ? "shared" : "",
			cpu, refhandle, slots, fs_per_dma, desc_num);
	if (desc_num < 2) {
		desc_num = 2;
	}
	res  = mbox_dma_init(&mbox_dma[PLAYBACK],
				refhandle,
				TxData,
				&tdm_priv.pdev->dev,
				tdm_priv.stereo_tx, PLAYBACK,
				only_rxirq ? -1 : mbox_data[tdm_priv.stereo_tx].irq,
				cpu,
				slots, fs_per_dma, desc_num);
	res |= mbox_dma_init(&mbox_dma[CAPTURE],
				refhandle,
				RxData,
				&tdm_priv.pdev->dev,
				tdm_priv.stereo_rx, CAPTURE,
				mbox_data[tdm_priv.stereo_rx].irq,
				cpu,
				slots, fs_per_dma, desc_num);
	if ((res == 0) && only_rxirq) {
		mbox_dma[CAPTURE].irqcontext_mbox = &mbox_dma[PLAYBACK];
	}
	if (res) {
		mbox_dma_exit(&mbox_dma[PLAYBACK]);
		mbox_dma_exit(&mbox_dma[CAPTURE]);
	}
	return res;
}
EXPORT_SYMBOL(tdm_if_dma_init);

/**
 * \brief:
 * start dma
 */
void tdm_if_dma_start(void)
{
	unsigned long flags;

	DBG_TRC_API("%s()\n", __func__);

	workarround_reset_tdm_interface();

	mbox_dma_prepare_desc(&mbox_dma[PLAYBACK]);
	mbox_dma_prepare_reg(&mbox_dma[PLAYBACK]);

	mbox_dma_prepare_desc(&mbox_dma[CAPTURE]);
	mbox_dma_prepare_reg(&mbox_dma[CAPTURE]);

	local_irq_save(flags);
	/*--- sehr zeitnah starten, damit dma-buffer zur gleichen Zeit fertig! ---*/
	mbox_dma_start(&mbox_dma[PLAYBACK]);
	udelay(250); /*--- ca. 2 Framsesyncs spaeter erst den Rx-DMA aufsetzen ---*/
	mbox_dma_start(&mbox_dma[CAPTURE]);
	local_irq_restore(flags);
}
EXPORT_SYMBOL(tdm_if_dma_start);

/**
 * \brief:
 * stop dma
 */
void tdm_if_dma_stop(void)
{
	DBG_TRC_API("%s()\n", __func__);
	mbox_dma_stop(&mbox_dma[PLAYBACK]);
	mbox_dma_stop(&mbox_dma[CAPTURE]);
}
EXPORT_SYMBOL(tdm_if_dma_stop);

/**
 * \brief:
 *  deinitialize dma-interface
 */
void tdm_if_dma_exit(void)
{
	DBG_TRC_API("%s()\n", __func__);
	mbox_dma_stop(&mbox_dma[PLAYBACK]);
	mbox_dma_stop(&mbox_dma[CAPTURE]);
	mbox_dma_exit(&mbox_dma[PLAYBACK]);
	mbox_dma_exit(&mbox_dma[CAPTURE]);
}
EXPORT_SYMBOL(tdm_if_dma_exit);

/**
 * \brief:
 * get count of interrupts
 * \param[out] rx_irqcnt
 * \param[out] tx_irqcnt
 */
void tdm_if_dma_irqcnt(unsigned long *rx_irqcnt, unsigned long *tx_irqcnt)
{
	if (rx_irqcnt)
		*rx_irqcnt = mbox_dma[CAPTURE].irq_cnt;
	if (tx_irqcnt)
		*tx_irqcnt = mbox_dma[PLAYBACK].irq_cnt;
	/*--- DBG_TRC_API("%s(%lu %lu)\n", __func__, mbox_dma[CAPTURE].irq_cnt, mbox_dma[PLAYBACK].irq_cnt); ---*/
}
EXPORT_SYMBOL(tdm_if_dma_irqcnt);

/**
 * \brief:
 * reset count of interrupts
 */
void tdm_if_dma_irqcnt_reset(void)
{
	mbox_dma[PLAYBACK].irq_cnt = 0;
	mbox_dma[CAPTURE].irq_cnt  = 0;
}
EXPORT_SYMBOL(tdm_if_dma_irqcnt_reset);

/**
 * \brief:
 * print tdm-interface registers
 *
 * txt: NULL -> use printk
 */
void tdm_if_print_status(const char *prefix, char *txt, unsigned int txt_size)
{
	struct _mbox_dma *pmbox_dma;

	pmbox_dma = &mbox_dma[CAPTURE];

	snprintf_add(txt, txt_size, "%s\n", prefix);
	snprintf_add(txt, txt_size, "Rx(%s):Irqs=%lu ErrCnt=%u OWN-UDR=%u OWN-OVR=%u ReOffset=%u\n",
				 pmbox_dma->irq_active == irq_uninitialized ? "off"  :
				 pmbox_dma->irq_active == irq_initialized   ? "init" :
				 pmbox_dma->irq_active == irq_progress      ? "run"  :  "?",
				 pmbox_dma->irq_cnt, pmbox_dma->err_cnt, pmbox_dma->own_udr, pmbox_dma->own_ovr,
				 pmbox_dma->reoffset);
	txt_size = snprintf_dump_slots(txt ? &txt : NULL, txt_size, pmbox_dma);

	pmbox_dma = &mbox_dma[PLAYBACK];
	snprintf_add(txt, txt_size, "Tx(%s):Irqs=%lu ErrCnt=%u OWN-UDR=%u OWN-OVR=%u ReOffset=%u\n",
				 pmbox_dma->irq_active == irq_uninitialized ? "off"  :
				 pmbox_dma->irq_active == irq_initialized   ? "init" :
				 pmbox_dma->irq_active == irq_progress      ? "run"  :  "?",
				 pmbox_dma->irq_cnt, pmbox_dma->err_cnt, pmbox_dma->own_udr, pmbox_dma->own_ovr,
				 pmbox_dma->reoffset);
	txt_size = snprintf_dump_slots(txt ? &txt : NULL, txt_size, pmbox_dma);

	snprint_all_register(txt ? &txt : NULL, txt_size, (1 << 0) | (1 << 3));
}
EXPORT_SYMBOL(tdm_if_print_status);

#define SKIP_UNTIL_CHAR(a, character) { while (*(a) && *(a) != (character)) (a)++; }
#define SKIP_SPACE(a)		      { while (*(a) && (*(a) == ' ' || *(a) == '\t')) (a)++; }

/**
 * format <name>([~]<startbit>,<endbit>)...
 */
static const char *extract_param(const char *bitformat, char *name, int name_len,
					unsigned int *start_bit, unsigned int *end_bit, unsigned int *neg)
{
	const char *p, *end_seperate;
	int len;

	if (bitformat == NULL) {
		return NULL;
	}
	SKIP_SPACE(bitformat);
	p            = bitformat;
	end_seperate = p;
	SKIP_UNTIL_CHAR(end_seperate, ')');
	if (*end_seperate != ')') {
		return NULL;
	}
	SKIP_UNTIL_CHAR(p, '(');
	if (p > end_seperate || *p != '(') {
		return NULL;
	}
	len = min(p - bitformat, name_len - 1);
	if (len) {
		memcpy(name, bitformat, len);
		name[len] = 0;
	}
	p++;
	SKIP_SPACE(p);
	if (*p == '~') {
		*neg = 1;
		p++;
	} else {
		*neg = 0;
	}
	sscanf(p, "%u", start_bit);
	SKIP_UNTIL_CHAR(p, ',');
	if (p > end_seperate) {
		*end_bit = *start_bit;
	} else {
		p++;
		SKIP_SPACE(p);
		sscanf(p, "%u", end_bit);
		if (*end_bit < *start_bit) {
			unsigned int tmp;

			tmp = *end_bit;
			*end_bit   = *start_bit;
			*start_bit = tmp;
		}
	}
	return end_seperate + 1;
}

#define PREFIX_IPQ8074_STRING	"8074_"
#define PREFIX_IPQ4019_STR	"4019_"
/**
 */
static char *snprint_bitformat(char *erg, int erg_len, unsigned int value, const char *bitformat)
{
	unsigned int start_bit, end_bit, neg;
	char *start = erg;
	char name[64], *pname;

	snprintf_add(erg, erg_len, "0x%08x", value);
	while ((bitformat = extract_param(bitformat, name, sizeof(name), &start_bit, &end_bit, &neg))) {
		/*--- pr_info("%s %u %u %u\n", name, start_bit, end_bit, neg); ---*/
		pname = name;
		if (strstr(name, PREFIX_IPQ4019_STR) == name) {
			if (!is_IPQ4019())
				continue;
			pname = name + sizeof(PREFIX_IPQ4019_STR) - 1;
		} else if (strstr(name, PREFIX_IPQ8074_STRING) == name) {
			if (!is_IPQ807x())
				continue;
			pname = name + sizeof(PREFIX_IPQ8074_STRING) - 1;
		}
		if (end_bit == start_bit) {
			if ((value & (1 << start_bit)) == ((!neg) << start_bit)) {
				snprintf_add(erg, erg_len, " %s", pname);
			}
		} else {
			snprintf_add(erg, erg_len, " %s=0x%x", pname,
				     (value >> start_bit) & ((1 << (end_bit - start_bit + 1)) - 1));
		}
	}
	return start;
}

/**
 */
static unsigned int snprintf_dump_descriptors(char **_ptxt, unsigned int txt_size, struct _mbox_dma *pmbox_dma)
{
	char *ptxt = NULL;
	struct _mbox_desc *virt_desc, *phys_desc;
	unsigned int i;

	if (pmbox_dma->irq_active < irq_initialized) {
		return txt_size;
	}
	if (_ptxt) {
		ptxt = *_ptxt;
	}
	phys_desc = (struct _mbox_desc *)pmbox_dma->phys_desc;
	virt_desc = pmbox_dma->virt_desc;
	for (i = 0; i < pmbox_dma->ndescs; i++) {
		snprintf_add(ptxt, txt_size,
			     "\t[%u]DESC(Virt %p Phy %p): bufPhy 0x%x NextPtr 0x%x length=%u size=%u vuc=%x ei=%x rcvd1=%x EOM=%x OWN=%x\n",
			     i,
			     virt_desc,
			     phys_desc,
			     virt_desc->BufPtr,
			     virt_desc->NextPtr,
			     virt_desc->length,
			     virt_desc->size,
			     virt_desc->vuc,
			     virt_desc->ei,
			     virt_desc->rsvd1,
			     virt_desc->EOM,
			     virt_desc->OWN);
		virt_desc  = ipq_desc_ptr(pmbox_dma, virt_desc, 1);
		phys_desc  = ipq_desc_ptr(pmbox_dma, phys_desc, 1);
	}
	if (_ptxt) {
		*_ptxt = ptxt;
	}
	return txt_size;
}
/**
 */
static unsigned int snprintf_dump_slots(char **_ptxt, unsigned int txt_size, struct _mbox_dma *pmbox_dma)
{
	char *ptxt = NULL;
	unsigned int i;

	if (pmbox_dma->irq_active < irq_initialized) {
		return txt_size;
	}
	if (_ptxt) {
		ptxt = *_ptxt;
	}
	for (i = 0; i < ARRAY_SIZE(pmbox_dma->virt_buf); i++) {
		unsigned short *p = (unsigned short *)pmbox_dma->virt_buf[i];

		if (p) {
			snprintf_add(ptxt, txt_size,
				     "\t[%lu]%cx[%u] 0x%p:%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x\n",
				     pmbox_dma->irq_cnt,
				     pmbox_dma->dir == CAPTURE ? 'R' : 'T',
				     i, p,
				     p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
				     p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
		}
	}
	if (_ptxt) {
		*_ptxt = ptxt;
	}
	return txt_size;
}
/**
 */
static unsigned int snprint_all_register(char **_ptxt, unsigned int txt_size, unsigned int id_mask)
{
	char txt[256];
	char *ptxt = NULL;
	unsigned int i, id;

	if (_ptxt) {
		ptxt = *_ptxt;
	}
	for (i = 0; i < ARRAY_SIZE(gReg); i++) {
		const struct _debug_register_tab *preg = &gReg[i];

		if ((preg->trace_flag & trace_mode) == 0) {
			continue;
		}
		if (!is_IPQ4019() && (preg->trace_flag & trace_ipq4019)) {
			continue;
		}
		if (preg->read) {
			unsigned int val = preg->read(preg->offset, NULL);

			snprint_bitformat(txt, sizeof(txt), val, preg->bitformat);
			snprintf_add(ptxt, txt_size, "%-50s:%s\n", preg->regname, txt);
		}
	}
	for (id = 0; id < ARRAY_SIZE(stereo_base); id++) {
		if ((id_mask & (1 << id)) == 0) {
			continue;
		}
		if (stereo_base[id] == NULL) {
			continue;
		}
		for (i = 0; i < ARRAY_SIZE(gReg); i++) {
			const struct _debug_register_tab *preg = &gReg[i];

			if ((preg->trace_flag & trace_mode) == 0) {
				continue;
			}
			if (!is_IPQ4019() && (preg->trace_flag & trace_ipq4019)) {
				continue;
			}
			if (preg->stereo_read) {
				unsigned int val = preg->stereo_read(id, preg->offset, NULL);

				snprint_bitformat(txt, sizeof(txt), val, preg->bitformat);
				snprintf_add(ptxt, txt_size, "%-47s[%u]:%s\n", preg->regname, id, txt);
			}
		}
	}
	for (id = 0; id < ARRAY_SIZE(mbox_data); id++) {
		if ((id_mask & (1 << id)) == 0) {
			continue;
		}
		if (mbox_data[id].reg_base == NULL) {
			continue;
		}
		snprintf_add(ptxt, txt_size, "MBOX[%u]%s\n", id, mbox_dir_name(id));
		for (i = 0; i < ARRAY_SIZE(gReg); i++) {
			const struct _debug_register_tab *preg = &gReg[i];

			if ((preg->trace_flag & trace_mode) == 0) {
				continue;
			}
			if (!is_IPQ4019() && (preg->trace_flag & trace_ipq4019)) {
				continue;
			}
			if (preg->_mbox_read) {
				unsigned int val = preg->_mbox_read(id, preg->offset, NULL);

				snprint_bitformat(txt, sizeof(txt), val, preg->bitformat);
				snprintf_add(ptxt, txt_size, "%-47s[%u]:%s\n", preg->regname, id, txt);
			}
		}
	}
	if (_ptxt) {
		*_ptxt = ptxt;
	}
	return txt_size;
}

/**
 */
static void write_register(char *name, unsigned int val, unsigned int id)
{
	unsigned int i;
	enum _trace_mode old_trace_mode;
/*--- pr_info("%s: '%s' val=%u id=%d\n", __func__, name, val, id); ---*/

	for (i = 0; i < ARRAY_SIZE(gReg); i++) {
		const struct _debug_register_tab *preg = &gReg[i];

		if (strstr(name, preg->regname)) {
			old_trace_mode = trace_mode;
			trace_mode = preg->trace_flag | trace_enh;

			if (!is_IPQ4019() && (preg->trace_flag & trace_ipq4019)) {
				/*--- do nothing ---*/
			} else if (preg->write) {
				preg->write(val, preg->offset, preg->regname);
			} else if (preg->_mbox_write) {
				preg->_mbox_write(id, val, preg->offset, preg->regname);
			} else if (preg->stereo_write) {
				preg->stereo_write(id, val, preg->offset, preg->regname);
			}
			trace_mode     = old_trace_mode;
			return;
		}
	}
}

#define SKIP_UNTIL_SPACE(a) { while (*(a) && *(a) != ' ' && *(a) != '\t') (a)++; }
/**
 */
const char *cpy_name(char *name, int name_len, const char *cmd)
{
	const char *cmd_end;
	int len;

	SKIP_SPACE(cmd);
	cmd_end = cmd;
	SKIP_UNTIL_SPACE(cmd_end);
	len = min(name_len - 1, cmd_end - cmd);

	if (len == 0) {
		return NULL;
	}
	memcpy(name, cmd, len);
	name[len] = 0;
	return cmd_end;
}

/**
 */
static int generic_scan_args(const char *buf, unsigned int args[], int argc, int as_hex)
{
	int i, scanned = 0;

	if (buf == NULL) {
		return 0;
	}
	for (i = 0; i < argc; i++) {
		if (*buf) {
			const char *format;

			SKIP_SPACE(buf);
			if (as_hex) {
				format = "%x";
			} else {
				format = "%u";
			}
			sscanf(buf, format, &args[i]);
			scanned++;
			SKIP_UNTIL_SPACE(buf);
		}
	}
	return scanned;
}

static unsigned short testpacket[] = { 0xffff, 0x0005, 0x0aff, 0x0002, 0x0214, 0xf600, 0x010e, 0xffff };
static unsigned int send_test_packet = 0xFF;
static unsigned int packet_offset    = 0x1;

/**
 */
static unsigned short send_testpacket(void)
{
	if (send_test_packet < ARRAY_SIZE(testpacket)) {
		return testpacket[send_test_packet++];
	}
	return 0;
}

static unsigned int glb_mclk, glb_bclk, glb_master = 0, glb_extclk = 1;
static unsigned int glb_syncslots;
static unsigned int testval = 0x80A0;
static unsigned int testval_offset = 4;
static unsigned int testval_rx_resize;
static unsigned int testval_tx_resize;
static unsigned int only_rx_irq;
static unsigned int start_tdm;
/**
 */
int TestTxData(void *refhandle, void *buf)
{
	unsigned int i;
	static unsigned int start_j;
	struct _mbox_dma *pmbox_dma = (struct _mbox_dma *)refhandle;
	unsigned short *p = (unsigned short *)buf;
	unsigned int resize = testval_tx_resize;

	for (i = 0; i < pmbox_dma->size / sizeof(short); i++) {
		*p = 0;
		if (glb_syncslots) {
			*p = (glb_syncslots | ((i % pmbox_dma->slots) << 4)) & 0xFFFF;
		} else if ((i % pmbox_dma->slots) == testval_offset) {
			*p = testval & 0xFFFF;
		} else if ((i % pmbox_dma->slots) == packet_offset) {
			*p = send_testpacket();
		}
		p++;
	}
	p = (unsigned short *)buf;
	if (jiffies - start_j > 5 * HZ) {
		unsigned short *p = (unsigned short *)buf;

		pr_info("tx:%p(%x) (irqs=%lu/err=%u udr=%u ovr=%u):\n"
			"%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x "
			"%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x\n",
			buf, virt_to_phys(buf),
			pmbox_dma->irq_cnt, pmbox_dma->err_cnt, pmbox_dma->own_udr, pmbox_dma->own_ovr,
			p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11],
			p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], p[20], p[21], p[22], p[23], p[24],
			p[25], p[26], p[27], p[28], p[29], p[30], p[31]);
		start_j = jiffies;
	}
	testval_tx_resize = 0;
	return resize;
}
/**
 */
int TestRxData(void *refhandle, void *buf)
{
	static unsigned int start_j;
	unsigned int i;
	unsigned char *p = buf;
	struct _mbox_dma *pmbox_dma = (struct _mbox_dma *)refhandle;
	unsigned int resize = testval_rx_resize;

	if (jiffies - start_j > 5 * HZ) {
		unsigned short *p = (unsigned short *)buf;

		pr_info("rx:%p(%x) (irqs=%lu/err=%u udr=%u ovr=%u):\n"
			"%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x "
			"%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x\n",
			buf, virt_to_phys(buf),
			pmbox_dma->irq_cnt, pmbox_dma->err_cnt, pmbox_dma->own_udr, pmbox_dma->own_ovr,
			p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11],
			p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], p[20], p[21], p[22], p[23], p[24],
			p[25], p[26], p[27], p[28], p[29], p[30], p[31]);
		start_j = jiffies;
	}
	for (i = 0; i < pmbox_dma->size; i++) {
		if (p[i] == 0xAA) {
			printk_ratelimited("preamble found: ----rx: %*ph\n", pmbox_dma->size - i, &p[i]);
			break;
		}
	}
	testval_rx_resize = 0;
	return resize;
}

/**
 */
static int startup_tdm(unsigned int bclk, unsigned int mclk, unsigned int slots)
{
	int res = 0;
	unsigned int rxdelay = 1, txdelay = 1;

	if (tdm_config.tdm_if_api) {
		pr_info("tdm-interface locked\n");
		return 1;
	}
	if (slots == 0) {
		slots = 8;
	}
	if (mclk == 0) {
		bclk = 1024 * 1000;
		mclk = bclk * 4;
	} else {
		mclk *= 1000;
		bclk *= 1000;
	}
	glb_bclk = bclk, glb_mclk = mclk;
	if (glb_master)  {
		rxdelay = 0;
		txdelay = 0;
	}
	adss_config_clk(glb_extclk, bclk, mclk);
	adss_glb_tdm_ctrl_ch_num(slots - 1);
	adss_glb_clk_enable_oe(glb_master, CAPTURE);
	adss_glb_clk_enable_oe(glb_master, PLAYBACK);
	adss_glb_rxtx_data_port_en(1);
	adss_glb_rxtx_framesync_port_en(1);

	adss_glb_tdm_mode();
	adss_config_tdm(DISABLE, glb_master, glb_extclk);
	adss_glb_tdm_ctrl_sync_num(0, CAPTURE);
	adss_glb_tdm_ctrl_sync_num(0, PLAYBACK);
	adss_glb_tdm_ctrl_delay(rxdelay, CAPTURE);
	adss_glb_tdm_ctrl_delay(txdelay, PLAYBACK);

	res  = mbox_dma_init(&mbox_dma[PLAYBACK],
				&mbox_dma[PLAYBACK],
				TestTxData,
				&tdm_priv.pdev->dev,
				tdm_priv.stereo_tx, PLAYBACK,
				only_rx_irq ? -1 : mbox_data[tdm_priv.stereo_tx].irq, -1, slots, 8 * 4, 8);
	res |= mbox_dma_init(&mbox_dma[CAPTURE],
				&mbox_dma[CAPTURE],
				TestRxData,
				&tdm_priv.pdev->dev,
				tdm_priv.stereo_rx, CAPTURE,
				mbox_data[tdm_priv.stereo_rx].irq, -1, slots, 8 * 4, 8);
	if (only_rx_irq) {
		mbox_dma[CAPTURE].irqcontext_mbox = &mbox_dma[PLAYBACK];
	}
	if (res) {
		mbox_dma_exit(&mbox_dma[PLAYBACK]);
		mbox_dma_exit(&mbox_dma[CAPTURE]);
	} else {
		unsigned long flags;
		enum _trace_mode old_trace_mode;

		mbox_dma_prepare_desc(&mbox_dma[PLAYBACK]);
		mbox_dma_prepare_reg(&mbox_dma[PLAYBACK]);

		mbox_dma_prepare_desc(&mbox_dma[CAPTURE]);
		mbox_dma_prepare_reg(&mbox_dma[CAPTURE]);

		local_irq_save(flags);
		old_trace_mode = trace_mode;
		trace_mode     = 0;
		mbox_dma_start(&mbox_dma[PLAYBACK]);
		mbox_dma_start(&mbox_dma[CAPTURE]);
		trace_mode = old_trace_mode;
		local_irq_restore(flags);
	}
	return res;
}

/**
 */
static void shutdown_tdm(void)
{
	if (tdm_config.tdm_if_api) {
		pr_info("tdm-interface locked\n");
		return;
	}
	mbox_dma_stop(&mbox_dma[PLAYBACK]);
	mbox_dma_stop(&mbox_dma[CAPTURE]);
	mbox_dma_exit(&mbox_dma[PLAYBACK]);
	mbox_dma_exit(&mbox_dma[CAPTURE]);

	adss_glb_tdm_ctrl_ch_num(0);
	adss_glb_clk_enable_oe(0, CAPTURE);
	adss_glb_clk_enable_oe(0, PLAYBACK);

	adss_glb_rxtx_data_port_en(0);
	adss_glb_rxtx_framesync_port_en(0);
}

/**
 */
static unsigned char get_cmd(const char **_p, unsigned int args[], unsigned int *scanned)
{
	unsigned int i;
	const char *p;
	static const struct _priv_cmd {
		const char *cmd;
		int type;
		int argc;
		int ashex;
	} priv_cmd[] = {
		{ cmd:"print",  type:'p', argc:0, ashex:0 },
		{ cmd:"clk",    type:'C', argc:3, ashex:0 },
		{ cmd:"master", type:'M', argc:1, ashex:0 },
		{ cmd:"ext",    type:'E', argc:1, ashex:0 },
		{ cmd:"start",  type:'S', argc:3, ashex:0 },
		{ cmd:"stop",   type:'H', argc:0, ashex:0 },
		{ cmd:"txval",  type:'T', argc:2, ashex:1 },
		{ cmd:"sync",   type:'X', argc:0, ashex:1 },
		{ cmd:"resize", type:'R', argc:2, ashex:1 },
		{ cmd:"irq",    type:'I', argc:0, ashex:0 },
		{ cmd:"wr",     type:'W', argc:0, ashex:0 },
		{ cmd:"packet", type:'P', argc:1, ashex:1 },
		{ cmd:"reset",  type:'r', argc:0, ashex:0 },
	};

	for (i = 0; i < ARRAY_SIZE(priv_cmd); i++) {
		const struct _priv_cmd *pcmd = &priv_cmd[i];

		p = strstr(*_p, pcmd->cmd);
		if (p) {
			p += strlen(pcmd->cmd);
			*scanned = generic_scan_args(p, args, pcmd->argc, pcmd->ashex);
			return pcmd->type;
		}
	}
	return '?';
}

/**
 */
static void cmdlineparse(char *cmdline, void *dummy __attribute__((unused)))
{
	char name[64];
	unsigned int arg[3], scanned;
	const char *p = cmdline;
	int type, i;

	memset(&arg, 0x0, sizeof(arg));

	type = get_cmd(&p, arg, &scanned);
	switch (type) {
	case 'p': /* print */
		snprint_all_register(NULL, 0, 0xff);
		for (i = 0; i < ARRAY_SIZE(mbox_dma); i++) {
			snprintf_dump_descriptors(NULL, 0,  &mbox_dma[i]);
			snprintf_dump_slots(NULL, 0, &mbox_dma[i]);
		}
		break;
	case 'C': /* clk */
		if (scanned == 3) {
			adss_set_clk_rate(arg[0], arg[1], arg[2]);
		}
		break;
	case 'M': /* master */
		if (scanned) {
			glb_master = arg[0];
			pr_info("set master=%u\n", glb_master);
		}
		break;
	case 'E': /* ext */
		if (scanned) {
			glb_extclk = arg[0];
			pr_info("set extclk=%u\n", glb_extclk);
		}
		break;
	case 'X': /* sync */
		if (scanned) {
			glb_syncslots = arg[0];
		} else if (glb_syncslots) {
			glb_syncslots = 0;
		} else {
			glb_syncslots = 0x8001;
		}
		pr_info("%sset slot-sync-mode (%x)\n", glb_syncslots ? "" : "un", glb_syncslots);
		break;
	case 'S': /* start */
		if (start_tdm == 0) {
			if (startup_tdm(arg[0], arg[1], arg[2]) == 0) {
				start_tdm = 1;
			}
		} else {
			start_tdm = 0;
			shutdown_tdm();
		}
		break;
	case 'H': /* stop */
		if (start_tdm) {
			start_tdm = 0;
			shutdown_tdm();
		}
		break;
	case 'T': /* txval */
		if (scanned) {
			testval        = arg[0];
			testval_offset = arg[1];
			glb_syncslots  = 0;
		}
		break;
	case 'R': /* resize */
		if (scanned) {
			testval_tx_resize = arg[0];
			testval_rx_resize = arg[1];
#if defined(DBG_TDM_RESIZE_CHECK)
			mbox_dma[PLAYBACK].dbg_force_reoffset = arg[0];
			mbox_dma[CAPTURE].dbg_force_reoffset  = arg[1];
			pr_info("set tx_resize=%u rx_resize=%u\n", arg[0], arg[1]);
#else
			if (start_tdm)
				pr_info("set tx_resize=%u rx_resize=%u\n", arg[0], arg[1]);
#endif
		}
		break;
	case 'I': /* irq */
		only_rx_irq = !only_rx_irq;
		pr_err("%s irq-mode\n", only_rx_irq ? "shared" : "normal");
		break;
	case 'W': /* wr */
		SKIP_UNTIL_SPACE(p);
		p = cpy_name(name, sizeof(name), p);
		scanned = generic_scan_args(p, arg, 2, 1);
		if (scanned) {
			write_register(name, arg[0], arg[1]);
		}
		break;
	case 'P': /* packet */
		if (scanned) {
			packet_offset    = arg[0];
		}
		pr_err("send packet: offset=%u\n", packet_offset);
		send_test_packet = 0;
		break;
	case 'r': /* tdm-reset */
		workarround_reset_tdm_interface();
		break;
	case '?':
		pr_err("\tstart <bclk> <mclk> <slots>\n"
			"\tstop\n"
			"\text    <0|1>\n"
			"\tmaster <0|1>\n"
			"\tclk  <slotcount> <bitwidth> <rate\n"
			"\ttxval <val> <slot_offset>\n"
			"\tsync [<val>] val != 0: set all slots to: <val> | slot << 4)\n"
			"\tresize <rx> <tx>\n"
			"\tirq-mode (toggle shared/normal)\n"
			"\tpcm_wr <regname> <value> <id>\n"
			"\tpacket <offset>\n"
			);
	}
}

/**
 */
static struct platform_driver qca_pcm_avm_driver = {
	.remove		= qca_pcm_avm_remove,
	.probe		= qca_pcm_avm_probe,
	.driver		= {
		.name	= "qca-pcm-avm",
		.of_match_table = pcm_avm_id_table,
	},
};
module_platform_driver(qca_pcm_avm_driver);

MODULE_DESCRIPTION(DRV_NAME ": qca avm pcm interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:qca_pcm_avm");