// SPDX-License-Identifier: GPL-2.0+ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_AVM_FASTIRQ) #include #include #define avm_rte_local_irq_save(flags) firq_local_irq_save(flags) #define avm_rte_local_irq_restore(flags) firq_local_irq_restore(flags) #define avm_rte_spin_lock_irqsave(lock, flags) firq_spin_lock_irqsave(lock, flags) #define avm_rte_spin_unlock_irqrestore(lock, flags) firq_spin_unlock_irqrestore(lock, flags) #else #define avm_rte_local_irq_save(flags) local_irq_save(flags) #define avm_rte_local_irq_restore(flags) local_irq_restore(flags) #define avm_rte_spin_lock_irqsave(lock, flags) spin_lock_irqsave(lock, flags) #define avm_rte_spin_unlock_irqrestore(lock, flags) spin_unlock_irqrestore(lock, flags) #endif/*--- #if defined(CONFIG_AVM_FASTIRQ) ---*/ #include "ipq-avm.h" #include #include #include "ipq-lpass.h" #include "ipq-lpass-cc.h" #include "ipq-lpass-tdm-pcm.h" #define DBG_ERR(args...) pr_err(args) #define DBG_TRC_API(args...) pr_info(args) /*--- #define DBG_TRC(args...) pr_info(args) ---*/ #define DBG_TRC(args...) no_printk(args) /** */ struct _lpass_irqstat { spinlock_t lock; unsigned long long sum_ts; unsigned long last_ts, max_ts, min_ts; unsigned long cnt; }; struct _lpass_dma { uint16_t idx; uint16_t dir; uint16_t ifconfig; uint16_t intr_id; uint8_t num_channels; uint8_t bit_width; uint32_t period_count_in_word32; uint32_t bytes_per_sample; uint8_t *dma_buffer; uint32_t dma_base_address; uint32_t dma_buffer_size; uint32_t watermark; uint32_t no_of_buffers; uint32_t single_buf_size; void __iomem *ipq_lpass_lpaif_base; int irq_enabled; void *refhandle; int (*RxTxData)(void *refhandle, void *p); unsigned long irq_cnt; unsigned long err_cnt; unsigned long udr_ovr; uint32_t last_buffer_idx; uint32_t last_curr_addr; uint32_t max_diff_curr_addr; struct _lpass_irqstat irq_stat; }; struct _lpass_ctrl { void __iomem *ipq_lpass_lpaif_base; void __iomem *sg_ipq_lpass_base; struct platform_device *pcm_pdev; void *DebugHandle; enum _tdm_if_api { tdm_if_unused = 0, tdm_if_api, tdm_if_debugcmd, } tdm_if_api; int irq; int irq_active; int shared_irq; int fiq_cpumode; unsigned int master; unsigned int rate; unsigned int rxdelay; unsigned int txdelay; struct ipq_lpass_pcm_config config; struct _lpass_dma rx_dma, tx_dma; }; static struct _lpass_ctrl *pLpassCtrl; static void cmdlineparse(char *cmdline, void *_plpass_ctrl); static void lpass_pcm_dma_stop(struct _lpass_dma *pdma); static void lpass_pcm_dma_exit(struct _lpass_dma *pdma); #define DMA_RX_INDEX_MASK (0x1 << 0) /* only DMA_CHANNEL0 */ #define DMA_TX_INDEX_MASK (0x1 << 1) /* only DMA_CHANNEL1 */ #define IO_TEXT(text) { .regname = text, .offset = { 0, 0 }, .bitformat = NULL, .type = only_remark } #define IO_ENTRY(off, format) { .regname = #off, .offset = { off, off }, .bitformat = format, .type = use_general_register } #define IO_ENTRY_AUDIO_CORE(off, format) { .regname = #off, .offset = { off, off }, .bitformat = format, .type = use_general_register_sg_lpass } #define IO_ENTRY_PCM_INDEX(off, format) { .regname = #off, .offset = { off(0), off(1) }, .bitformat = format, .type = use_pcm_index, .index_mask = 0x3 } #define IO_ENTRY_DMA_INDEX(off, format, mask) { .regname = #off, .offset = { off(0), off(1) }, .bitformat = format, .type = use_dma_index, .index_mask = mask } #define IO_ENTRY_DMA_INTR(off, format) { .regname = #off, .offset = { off(0), off(1) }, .bitformat = format, .type = use_dma_intr, .index_mask = 0x1 /* DMA_CHANNEL0 */ } #define IO_EOF() { .regname = NULL, .offset = { 0, 0 }, .bitformat = NULL, .type = only_remark } #define IRQ_ENA_STRING() "PER_RDDMA_CH0(0)UNDR_RDDMA_CH0(1)ERR_RDDMA_CH0(2)" \ "PER_RDDMA_CH1(3)UNDR_RDDMA_CH1(4)ERR_RDDMA_CH1(5)" \ "PER_RDDMA_CH2(6)UNDR_RDDMA_CH2(7)ERR_RDDMA_CH2(8)" \ "PER_RDDMA_CH3(9)UNDR_RDDMA_CH3(10)ERR_RDDMA_CH3(11)" \ "PER_RDDMA_CH4(12)UNDR_RDDMA_CH4(13)ERR_RDDMA_CH4(14)" \ "PER_WRDMA_CH0(16)OVR_WRDMA_CH0(17)ERR_WRDMA_CH0(18)" \ "PER_WRDMA_CH1(18)OVR_WRDMA_CH1(19)ERR_WRDMA_CH1(20)" \ "PER_WRDMA_CH2(21)OVR_WRDMA_CH2(22)ERR_WRDMA_CH2(23)" \ "PER_WRDMA_CH3(24)OVR_WRDMA_CH3(25)ERR_WRDMA_CH3(26)" \ "FRM_REF(27)PRI_RD_DIFF_RATE(28)PRI_RD_NO_RATE(29)" \ "SEC_RD_DIFF_RATE(30)SEC_RD_NO_RATE(31)" static const struct _debug_register_tab { const char *regname; unsigned int offset[2]; enum { only_remark, use_general_register, use_general_register_sg_lpass, use_dma_intr, use_pcm_index, use_dma_index, } type; unsigned int index_mask; const char *bitformat; } gLpassReg[] = { IO_TEXT("General:"), IO_ENTRY(HWIO_LPASS_LPAIF_VERSION_OFFS, NULL), IO_ENTRY(HWIO_LPASS_LPAIF_HW_CONFIG_OFFS, NULL), IO_ENTRY(HWIO_LPASS_LPAIF_HW_CONFIG2_OFFS, NULL), IO_TEXT("LPASS_AUDIO_CORE"), IO_ENTRY_AUDIO_CORE(HWIO_LPASS_AUDIO_CORE_LPAIF_PRI_CLK_INV_ADDR(0), "INV_INT_CLK(0)INV_EXT_CLK(1)"), IO_ENTRY_AUDIO_CORE(HWIO_LPASS_AUDIO_CORE_LPAIF_SEC_CLK_INV_ADDR(0), "INV_INT_CLK(0)INV_EXT_CLK(1)"), IO_ENTRY_AUDIO_CORE(HWIO_LPASS_AUDIO_CORE_LPAIF_PRI_MODE_MUXSEL_ADDR(0), NULL), IO_ENTRY_AUDIO_CORE(HWIO_LPASS_AUDIO_CORE_LPAIF_SEC_MODE_MUXSEL_ADDR(0), NULL), IO_TEXT("PCM: 0:LPASS_HW_DMA_SINK 1:LPASS_HW_DMA_SOURCE"), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_I2S_SELa_OFFS, "PCM_SRC(0)I2S_SRC(~0)"), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_CTLa_OFFS, "TPCM_16_BIT(10)TPCM_8_BIT(~10)" "RPCM_16_BIT(11)RPCM_8_BIT(~11)" "AUX_PCM(~12)AUX_AUX(12)" "SYNC_EXT(~13)SYNC_INTERN(13)" "LB(14)RATE(15,17)OE(18)" "SLOT_SYNC_EN(19)SLOT_SYNC_DIS(~19)" "ENA(24)DISABLE(~24)" "TX_ENA(25)TX_DISABLE(~25)" "RX_ENA(26)RX_DISABLE(~26)" "RESET_TX_ENA(27)" "RESET_RX_ENA(28)" "RESET_ENA(31)"), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_TDM_CTL_a_OFFS, "TDM_RATE(0,8)TDM_TPCM_WIDTH(14,18)TDM_DELAY(25,26)" "TDM_INV_RPCM_SYN(27)TDM_INV_TPCM_SYNC(28)" "DIFF_SAMPLE_WIDTH_ENABLE(29)" "TDM_ENA(30) TDM_DISABLE(~30)"), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_TDM_SAMPLE_WIDTH_a_OFFS, "RPCM_SAMPLE_WIDTH(0,4)" "TPCM_SAMPLE_WIDTH(5,10)"), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_RPCM_SLOT_NUM_a_OFFS, NULL), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_TPCM_SLOT_NUM_a_OFFS, NULL), IO_ENTRY_PCM_INDEX(HWIO_LPASS_LPAIF_PCM_LANE_CONFIG_a_OFFS, "LANE0_DIR_MIC(0)LANE0_DIR_SPKR(~0)" "LANE1_DIR_MIC(1)LANE1_DIR_SPKR(~1)" "LANE2_DIR_MIC(2)LANE2_DIR_SPKR(~2)" "LANE3_DIR_MIC(3)LANE3_DIR_SPKR(~3)" "LANE4_DIR_MIC(4)LANE4_DIR_SPKR(~4)" "LANE5_DIR_MIC(5)LANE5_DIR_SPKR(~5)" "LANE6_DIR_MIC(6)LANE6_DIR_SPKR(~6)" "LANE7_DIR_MIC(7)LANE7_DIR_SPKR(~7)" "LANE0_ENABLE(16)LANE0_DISABLE(~16)" "LANE1_ENABLE(17)LANE1_DISABLE(~17)" "LANE2_ENABLE(18)LANE2_DISABLE(~18)" "LANE3_ENABLE(19)LANE3_DISABLE(~19)" "LANE4_ENABLE(20)LANE4_DISABLE(~20)" "LANE5_ENABLE(21)LANE5_DISABLE(~21)" "LANE6_ENABLE(22)LANE6_DISABLE(~22)" "LANE7_ENABLE(23)LANE7_DISABLE(~23)"), IO_TEXT("DMA-INTR:"), IO_ENTRY_DMA_INTR(HWIO_LPASS_LPAIF_IRQ_ENa_OFFS, IRQ_ENA_STRING()), IO_ENTRY_DMA_INTR(HWIO_LPASS_LPAIF_IRQ_STATa_OFFS, IRQ_ENA_STRING()), IO_ENTRY_DMA_INTR(HWIO_LPASS_LPAIF_IRQ_RAW_STATa_OFFS, IRQ_ENA_STRING()), IO_TEXT("DMA-TX-CHANNEL:"), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_CTLa_OFFS, "ENABLE_ON(0)ENABLE_OFF(~0)" "WATERMARK(1,5)" "AUDIO_INTF(12,15)" "WPS_CNT(16,19)" "BURST_EN_INCR4(20)BURST_EN_SINGLE(~20)" "DYNAMIC_CLOCK(21)" "PADDING_EN_ENABLE(22)PADDING_EN_DISABLE(~22)" "PADDING_NUM(23,27)" "BURST8_EN(28)" "BURST16_EN(29)" "DYNBURST_EN(30)" "RESET_ENABLE(31)", DMA_TX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_BASEa_OFFS, NULL, DMA_TX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_BUFF_LENa_OFFS, NULL, DMA_TX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_PER_LENa_OFFS, NULL, DMA_TX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_PER_CNTa_OFFS, "CNT(0,19)WORDCNT(20,25)FORMAT_ERR(30)", DMA_TX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_PERa_OFFS, "PERIOD_FIFO(16,21)", DMA_TX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_RDDMA_CURR_ADDRa_OFFS, NULL, DMA_TX_INDEX_MASK), IO_TEXT("DMA-RX-CHANNEL:"), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_CTLa_OFFS, "ENABLE_ON(0)ENABLE_OFF(~0)" "WATERMARK(1,5)" "AUDIO_INTF(12,16)" "WPS_CNT(17,20)" "BURST_EN_INCR4(21)BURST_EN_SINGLE(~21)" "DYNAMIC_CLOCK(22)" "BURST8_EN(23)" "BURST16_EN(24)" "RESET_ENABLE(31)", DMA_RX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_BASEa_OFFS, NULL, DMA_RX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_BUFF_LENa_OFFS, NULL, DMA_RX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_PER_LENa_OFFS, NULL, DMA_RX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_PER_CNTa_OFFS, "CNT(0,19)WORDCNT(20,25)FORMAT_ERR(26)", DMA_RX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_PERa_OFFS, "PERIOD_FIFO(12,15)", DMA_RX_INDEX_MASK), IO_ENTRY_DMA_INDEX(HWIO_LPASS_LPAIF_WRDMA_CURR_ADDRa_OFFS, NULL, DMA_RX_INDEX_MASK), IO_EOF(), }; /** */ static void __iomem *lpass_reg_addr(struct _lpass_ctrl *plpass_ctrl, const struct _debug_register_tab *preg, int index, unsigned long *phy_addr) { void __iomem *addr = NULL; if (phy_addr) *phy_addr = 0; switch (preg->type) { case only_remark: break; case use_general_register_sg_lpass: addr = plpass_ctrl->sg_ipq_lpass_base + preg->offset[0]; if (phy_addr) *phy_addr = LPASS_CC_REG_BASE + preg->offset[0]; break; case use_general_register: index = 0; /* fallthrough */ case use_dma_intr: case use_pcm_index: case use_dma_index: /*--- pr_err("%s: %s index=%u offset[0] = %x offset[1]=0x%x\n", __func__, preg->regname, index, preg->offset[0], preg->offset[1]); ---*/ addr = plpass_ctrl->ipq_lpass_lpaif_base + preg->offset[index]; if (phy_addr) *phy_addr = LPASS_LPA_IF_REG_BASE + preg->offset[index]; } return addr; } #define REG_PREFIX1 "HWIO_LPASS_LPAIF_" #define REG_PREFIX2 "HWIO_LPASS_AUDIO_CORE_LPAIF_" #define REG_POSTFIX1 "_OFFS" #define REG_POSTFIX2 "_ADDR(0)" static char *short_register_name(char *txt, int txt_size, const char *reg_name) { char *p; if (reg_name == strstr(reg_name, REG_PREFIX1)) { snprintf(txt, txt_size, "%s", ®_name[sizeof(REG_PREFIX1) - 1]); p = strstr(txt, REG_POSTFIX1); if (p && (p == &txt[strlen(txt) - (sizeof(REG_POSTFIX1) - 1)])) { *p = 0; } return txt; } if (reg_name == strstr(reg_name, REG_PREFIX2)) { snprintf(txt, txt_size, "%s", ®_name[sizeof(REG_PREFIX2) - 1]); p = strstr(txt, REG_POSTFIX2); if (p && (p == &txt[strlen(txt) - (sizeof(REG_POSTFIX2) - 1)])) { *p = 0; } } return txt; } static unsigned int snprintf_dump_irqstat(char **_ptxt, unsigned int txt_size, const char *prefix, struct _lpass_irqstat *pirqstat) { struct _lpass_irqstat irqstat; unsigned long flags; char *ptxt = NULL; unsigned long long avg; avm_rte_spin_lock_irqsave(&pirqstat->lock, flags); memcpy(&irqstat, pirqstat, sizeof(irqstat)); pirqstat->sum_ts = 0; pirqstat->max_ts = 0; pirqstat->min_ts = 0; pirqstat->cnt = 0; avm_rte_spin_unlock_irqrestore(&pirqstat->lock, flags); if (irqstat.cnt == 0) return txt_size; if (_ptxt) { ptxt = *_ptxt; } avg = irqstat.sum_ts; do_div(avg, irqstat.cnt); snprintf_add(ptxt, txt_size, "%s: avg=%lu us max=%lu min=%lu us\n", prefix, avm_cycles_to_usec((unsigned long)avg), avm_cycles_to_usec(irqstat.max_ts), avm_cycles_to_usec(irqstat.min_ts)); return txt_size; } /** */ static unsigned int snprintf_dump_slots(char **_ptxt, unsigned int txt_size, struct _lpass_dma *pdma) { char *ptxt = NULL; unsigned int i; if (pdma->dma_buffer == NULL) return txt_size; if (_ptxt) { ptxt = *_ptxt; } snprintf_add(ptxt, txt_size, "%cx:(irqs=%lu err=%lu udr=%lu max-fs-diff=%u):\n", pdma->dir == LPASS_HW_DMA_SOURCE ? 'R' : 'T', pdma->irq_cnt, pdma->err_cnt, pdma->udr_ovr, pdma->max_diff_curr_addr / sizeof(short) / pdma->num_channels); for (i = 0; i < pdma->no_of_buffers; i++) { unsigned short *p = (unsigned short *)pdma->dma_buffer + (pdma->single_buf_size / sizeof(unsigned short) * i); if (p) { snprintf_add(ptxt, txt_size, "%cx:%p[%u]:" "%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", pdma->dir == LPASS_HW_DMA_SOURCE ? 'R' : 'T', p, i, 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]); pdma->max_diff_curr_addr = 0; } } if (_ptxt) { *_ptxt = ptxt; } return txt_size; } /** * reg: NULL print all (documented) register */ unsigned int snprint_lpass_register(char *prefix, char **_ptxt, unsigned int txt_size, struct _lpass_ctrl *plpass_ctrl, void __iomem *reg) { unsigned long phys_addr; char txt[256]; char regname[64]; unsigned int index; int last_type; void __iomem *reg_addr; char *ptxt = NULL; unsigned int i, ii; if (_ptxt) { ptxt = *_ptxt; } if (prefix) snprintf_add(ptxt, txt_size, "%s\n", prefix); for (i = 0; i < ARRAY_SIZE(gLpassReg); i++) { const struct _debug_register_tab *preg = &gLpassReg[i]; if (preg->type == only_remark) { if ((reg == NULL) && preg->regname) snprintf_add(ptxt, txt_size, "%s\n", preg->regname); continue; } else if (preg->type == use_general_register || preg->type == use_general_register_sg_lpass) { reg_addr = lpass_reg_addr(plpass_ctrl, preg, 0, &phys_addr); if ((reg == NULL) || (reg == reg_addr)) { snprint_register_bitformat(txt, sizeof(txt), readl(reg_addr), preg->bitformat); snprintf_add(ptxt, txt_size, "0x%08lx:%-22s %s\n", phys_addr, short_register_name(regname, sizeof(regname), preg->regname), txt); if (reg == reg_addr) return txt_size; } continue; } last_type = preg->type; for (index = 0; index < 2; index++) { for (ii = i; i < ARRAY_SIZE(gLpassReg); ii++) { const struct _debug_register_tab *preg = &gLpassReg[ii]; if (last_type != preg->type) break; if ((reg == NULL) && (preg->index_mask & (1 << index)) == 0) continue; reg_addr = lpass_reg_addr(plpass_ctrl, preg, index, &phys_addr); if ((reg == NULL) || (reg == reg_addr)) { snprint_register_bitformat(txt, sizeof(txt), readl(reg_addr), preg->bitformat); snprintf_add(ptxt, txt_size, "0x%08lx:%-22s[%u] %s\n", phys_addr, short_register_name(regname, sizeof(regname), preg->regname), index, txt); if (reg == reg_addr) return txt_size; } } } i = ii - 1; } if (reg) snprintf_add(ptxt, txt_size, "%p:%08x\n", reg, readl(reg)); return txt_size; } /** */ static uint32_t lpass_addr_to_index(struct _lpass_dma *pdma, uint32_t addr) { uint32_t offset = addr - pdma->dma_base_address; return offset >= pdma->single_buf_size ? 0 : 1; } static void lpass_irq_stat_init(struct _lpass_irqstat *pirqstat) { spin_lock_init(&pirqstat->lock); } /** */ static void lpass_irq_stat(const char *prefix, struct _lpass_irqstat *pirqstat) { unsigned long flags, ts, diff; ts = avm_get_cycles(); diff = ts - pirqstat->last_ts; avm_rte_spin_lock_irqsave(&pirqstat->lock, flags); if (pirqstat->last_ts) { pirqstat->sum_ts += diff; if (diff > pirqstat->max_ts) pirqstat->max_ts = diff; if ((pirqstat->min_ts == 0) || (diff < pirqstat->min_ts)) pirqstat->min_ts = diff; } pirqstat->cnt++; avm_rte_spin_unlock_irqrestore(&pirqstat->lock, flags); pirqstat->last_ts = ts; } /** */ static void lpass_dma_handler(const char *prefix, unsigned int status, struct _lpass_dma *pdma) { uint32_t curr_addr, diff_curr_addr; uint32_t buffer_idx; pdma->irq_cnt++; ipq_lpass_dma_get_curr_addr(pdma->ipq_lpass_lpaif_base, pdma->idx, pdma->dir, &curr_addr); buffer_idx = lpass_addr_to_index(pdma, curr_addr); if (buffer_idx == pdma->last_buffer_idx) { printk_ratelimited("%s:%s[%lu] error same buffer-idx %u curr-addr=%08x last_curr_addr=%08x\n", __func__, prefix, pdma->irq_cnt, buffer_idx, curr_addr, pdma->last_curr_addr); } pdma->last_buffer_idx = buffer_idx; diff_curr_addr = curr_addr > pdma->last_curr_addr ? curr_addr - pdma->last_curr_addr : (pdma->no_of_buffers * pdma->single_buf_size) - (pdma->last_curr_addr - curr_addr); if (diff_curr_addr > pdma->max_diff_curr_addr) pdma->max_diff_curr_addr = diff_curr_addr; pdma->last_curr_addr = curr_addr; lpass_irq_stat(prefix, &pdma->irq_stat); if (pdma->RxTxData) { uint8_t *buf_addr = pdma->dma_buffer + (pdma->single_buf_size * buffer_idx); if (pdma->dir == LPASS_HW_DMA_SOURCE) dmac_inv_range(buf_addr, buf_addr + pdma->single_buf_size - 1); pdma->RxTxData(pdma->refhandle, buf_addr); if (pdma->dir == LPASS_HW_DMA_SINK) dmac_flush_range(buf_addr, buf_addr + pdma->single_buf_size - 1); } } /* */ static irqreturn_t lpass_irq_handler(int intrsrc, void *data) { uint32_t raw_status, status = 0; struct _lpass_ctrl *plpass_ctrl = (struct _lpass_ctrl *)data; struct _lpass_dma *pdma_rx = &plpass_ctrl->rx_dma; struct _lpass_dma *pdma_tx = &plpass_ctrl->tx_dma; ipq_lpass_dma_read_interrupt_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &status); ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &raw_status); while (status) { if (raw_status & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_WRDMA_CH0_BMSK)) lpass_dma_handler("Rx", status, pdma_rx); if (raw_status & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_RDDMA_CH1_BMSK)) lpass_dma_handler("Tx", status, pdma_tx); if (raw_status & (HWIO_LPASS_LPAIF_IRQ_STATa_ERR_RDDMA_CH1_BMSK)) pdma_tx->err_cnt++; if (raw_status & (HWIO_LPASS_LPAIF_IRQ_STATa_UNDR_RDDMA_CH1_BMSK)) pdma_tx->udr_ovr++; if (raw_status & (HWIO_LPASS_LPAIF_IRQ_STATa_ERR_WRDMA_CH0_BMSK)) pdma_rx->err_cnt++; if (raw_status & (HWIO_LPASS_LPAIF_IRQ_STATa_OVR_WRDMA_CH0_BMSK)) pdma_rx->udr_ovr++; ipq_lpass_dma_clear_interrupt(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, status); status = 0; ipq_lpass_dma_read_interrupt_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &status); } return IRQ_HANDLED; } /** */ static void lpass_dma_config_init(struct _lpass_dma *pdma) { uint32_t wps_count = 0; struct lpass_dma_config dma_config; if (pdma->bit_width != 8) wps_count = (pdma->num_channels * pdma->bytes_per_sample) >> 2; if (wps_count == 0) { wps_count = 1; } if (pdma->watermark != 1) { if ((pdma->period_count_in_word32 & PCM_DMA_BUFFER_16BYTE_ALIGNMENT) == 0) { dma_config.burst_size = 16; } else if ((pdma->period_count_in_word32 & PCM_DMA_BUFFER_8BYTE_ALIGNMENT) == 0) { dma_config.burst_size = 8; } else if ((pdma->period_count_in_word32 & PCM_DMA_BUFFER_4BYTE_ALIGNMENT) == 0) { dma_config.burst_size = 4; } else { dma_config.burst_size = 1; } } else { dma_config.burst_size = 1; } dma_config.buffer_len = pdma->dma_buffer_size / sizeof(uint32_t); dma_config.buffer_start_addr = pdma->dma_base_address; dma_config.dma_int_per_cnt = pdma->period_count_in_word32; dma_config.wps_count = wps_count; dma_config.watermark = pdma->watermark; dma_config.ifconfig = pdma->ifconfig; dma_config.idx = pdma->idx; if (dma_config.burst_size >= 8) { dma_config.burst8_en = 1; dma_config.burst_size = 1; } dma_config.burst16_en = 0; dma_config.dir = pdma->dir; dma_config.lpaif_base = pdma->ipq_lpass_lpaif_base; DBG_TRC("%s: buffer_start_addr=%x dma_int_per_cnt=%u buffer_len=%u burst_size=%u\n" "\twps_count=%u watermark=%u ifconfig=%u burst8_en=%u burst16_en=%u\n", __func__, dma_config.buffer_start_addr, dma_config.dma_int_per_cnt, dma_config.buffer_len, dma_config.burst_size, dma_config.wps_count, dma_config.watermark, dma_config.ifconfig, dma_config.burst8_en, dma_config.burst16_en); ipq_lpass_disable_dma_channel(pdma->ipq_lpass_lpaif_base, pdma->idx, pdma->dir); ipq_lpass_config_dma_channel(&dma_config); ipq_lpass_enable_dma_channel(pdma->ipq_lpass_lpaif_base, pdma->idx, pdma->dir); } /** */ static void lpass_dma_enable(struct _lpass_dma *pdma) { ipq_lpass_dma_clear_interrupt_config(pdma->ipq_lpass_lpaif_base, pdma->dir, pdma->idx, pdma->intr_id); if (pdma->irq_enabled) ipq_lpass_dma_enable_interrupt(pdma->ipq_lpass_lpaif_base, pdma->dir, pdma->idx, pdma->intr_id); else ipq_lpass_dma_disable_interrupt(pdma->ipq_lpass_lpaif_base, pdma->dir, pdma->idx, pdma->intr_id); } /** */ static int lpass_setup_bit_clock(uint32_t clk_rate, int master) { /* * set clock rate for PRI & SEC * PRI is slave mode and seondary is master */ ipq_lpass_lpaif_muxsetup(INTERFACE_PRIMARY, TDM_MODE_SLAVE); if (ipq_lpass_set_clk_rate(INTERFACE_SECONDARY, clk_rate) != 0) { DBG_ERR("%s: Bit clk set Failed\n", __func__); return -EINVAL; } ipq_lpass_lpaif_muxsetup(INTERFACE_SECONDARY, master ? TDM_MODE_MASTER : TDM_MODE_SLAVE); return 0; } /** * \brief: * print tdm-interface registers * * txt: NULL -> use printk */ void tdm_if_lpass_print_status(const char *prefix, char *txt, unsigned int txt_size) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; if (plpass_ctrl == NULL) return; if (txt && txt_size) txt[0] = 0; if (prefix) { snprintf_add(txt, txt_size, "%s\n", prefix); } txt_size = snprintf_dump_slots(&txt, txt_size, &plpass_ctrl->rx_dma); txt_size = snprintf_dump_irqstat(&txt, txt_size, "Rx-Irqstat", &plpass_ctrl->rx_dma.irq_stat); txt_size = snprintf_dump_slots(&txt, txt_size, &plpass_ctrl->tx_dma); txt_size = snprintf_dump_irqstat(&txt, txt_size, "Tx-Irqstat", &plpass_ctrl->tx_dma.irq_stat); txt_size = snprint_lpass_register("Register", &txt, txt_size, plpass_ctrl, NULL); } /** * \brief: * initialize tdm-lpass-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] bit_width: 8 or 16 bit * \param[in] master * * \retval 0: ok */ int tdm_if_lpass_config_init(unsigned int slots, unsigned int rxdelay, unsigned int txdelay, unsigned int bit_width, unsigned int master) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; struct ipq_lpass_pcm_config *pconfig = &plpass_ctrl->config; unsigned int slot_mask; DBG_TRC_API("%s(slots=%u, rxdelay=%u txdelay=%u bit_width=%u master=%u)\n", __func__, slots, rxdelay, txdelay, bit_width, master); if (plpass_ctrl == NULL) { DBG_ERR("%s: error: no valid driver set - please check your device-tree!\n", __func__); return -EINVAL; } if (((bit_width != 8) && (bit_width != 16))) { DBG_ERR("%s: Invalid Bitwidth %u\n", __func__, bit_width); return -EINVAL; } if (slots > IPQ_PCM_MAX_SLOTS_PER_FRAME) { DBG_ERR("%s: Invalid slots per frame %u\n", __func__, slots); return -EINVAL; } if (slots == 0 || (slots * bit_width) > 256) { DBG_ERR("%s: Invalid nbits per frame %u\n", __func__, slots * bit_width); return -EINVAL; } slot_mask = (0x1 << slots) - 1; plpass_ctrl->rxdelay = rxdelay; plpass_ctrl->txdelay = txdelay; plpass_ctrl->master = master; plpass_ctrl->rate = PCM_SAMPLE_RATE_8K; pconfig->slot_mask = slot_mask; pconfig->bit_width = bit_width; pconfig->slot_count = slots; pconfig->slot_width = bit_width; pconfig->sync_type = TDM_SHORT_SYNC_TYPE; pconfig->ctrl_data_oe = TDM_CTRL_DATA_OE_ENABLE; pconfig->invert_sync = TDM_LONG_SYNC_NORMAL; plpass_ctrl->tdm_if_api = tdm_if_api; return 0; } /** * \brief: * disable tdm-lpass-interface */ void tdm_if_lpass_config_exit(void) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; DBG_TRC_API("%s()\n", __func__); if (pLpassCtrl == NULL) { DBG_ERR("%s: error: no valid driver set - please check your device-tree!\n", __func__); return; } tdm_if_lpass_dma_stop(); ipq_lpass_pcm_reset(plpass_ctrl->ipq_lpass_lpaif_base, SECONDARY, LPASS_HW_DMA_SINK); ipq_lpass_pcm_reset(plpass_ctrl->ipq_lpass_lpaif_base, PRIMARY, LPASS_HW_DMA_SOURCE); plpass_ctrl->tdm_if_api = tdm_if_unused; } /** */ static int lpass_dma_init(struct _lpass_ctrl *plpass_ctrl, struct _lpass_dma *plpass_dma, unsigned int fs_per_dma, void *refhandle, int (*RxTxData)(void *refhandle, void *p)) { struct ipq_lpass_pcm_config *pconfig = &plpass_ctrl->config; int ret; uint32_t clk_rate; uint32_t bytes_per_sample; uint32_t samples_per_interrupt; uint32_t bytes_per_sample_intr; uint32_t dword_per_sample_intr; uint32_t no_of_buffers; uint32_t watermark = DEAFULT_PCM_WATERMARK; no_of_buffers = 2; /* ping-pong */ bytes_per_sample = BYTES_PER_CHANNEL(pconfig->bit_width); samples_per_interrupt = fs_per_dma * pconfig->slot_count; clk_rate = pconfig->bit_width * plpass_ctrl->rate * pconfig->slot_count; ret = lpass_setup_bit_clock(clk_rate, plpass_ctrl->master); if (ret) { DBG_ERR("%s:Can't set bit_clock error=%d\n", __func__, ret); return -ENODEV; } bytes_per_sample_intr = fs_per_dma * bytes_per_sample * pconfig->slot_count; dword_per_sample_intr = bytes_per_sample_intr / sizeof(uint32_t); if ((dword_per_sample_intr <= DEAFULT_PCM_WATERMARK) || (dword_per_sample_intr & 0x3)) { watermark = 1; } plpass_dma->refhandle = refhandle; plpass_dma->RxTxData = RxTxData; if (plpass_dma == &plpass_ctrl->rx_dma) { /*--- DMA Rx---*/ plpass_dma->idx = DMA_CHANNEL0; plpass_dma->dir = LPASS_HW_DMA_SOURCE; plpass_dma->ifconfig = INTERFACE_PRIMARY; } else { /*--- DMA Tx---*/ plpass_dma->idx = DMA_CHANNEL1; plpass_dma->dir = LPASS_HW_DMA_SINK; plpass_dma->ifconfig = INTERFACE_SECONDARY; } plpass_dma->intr_id = INTERRUPT_CHANNEL0; plpass_dma->bytes_per_sample = bytes_per_sample; plpass_dma->bit_width = pconfig->bit_width; plpass_dma->num_channels = pconfig->slot_count; plpass_dma->single_buf_size = samples_per_interrupt * bytes_per_sample; plpass_dma->period_count_in_word32 = plpass_dma->single_buf_size / sizeof(uint32_t); plpass_dma->dma_buffer_size = plpass_dma->single_buf_size * no_of_buffers; plpass_dma->no_of_buffers = no_of_buffers; plpass_dma->watermark = watermark; plpass_dma->ipq_lpass_lpaif_base = plpass_ctrl->ipq_lpass_lpaif_base; plpass_dma->last_curr_addr = plpass_dma->dma_base_address; plpass_dma->last_buffer_idx = 1; if (plpass_dma->single_buf_size != L1_CACHE_ALIGN(plpass_dma->single_buf_size)) { /* attention - single_buf_size should be multiple of L1_CACHE-size */ pr_err("%s: error: invalid size of single_buf_size %u (no muliple of cache-size)\n", __func__, plpass_dma->single_buf_size); return -EINVAL; } plpass_dma->dma_buffer = kzalloc(plpass_dma->dma_buffer_size, GFP_KERNEL); if (plpass_dma->dma_buffer == NULL) { pr_err("%s: no dma-memory\n", __func__); return -ENOMEM; } plpass_dma->dma_base_address = virt_to_phys(plpass_dma->dma_buffer); DBG_TRC("%s: %cx-DMA: DMA_CHANNEL%u %s ifconfig=%s INTERRUPT_CHANNEL%u num_channels=%u bit_width=%u\n" "\tperiod_count_in_word32=%u bytes_per_sample=%u dma_buffer=%pS dma_buffer_size=%u\n" "\tdma_base_address=%pS watermark=%u no_of_buffers=%u single_buf_size=%u\n", __func__, plpass_dma == &plpass_ctrl->tx_dma ? 'T' : 'R', plpass_dma->idx, plpass_dma->dir == LPASS_HW_DMA_SOURCE ? "LPASS_HW_DMA_SOURCE" : plpass_dma->dir == LPASS_HW_DMA_SINK ? "LPASS_HW_DMA_SINK" : "dir?", plpass_dma->ifconfig == INTERFACE_PRIMARY ? "INTERFACE_PRIMARY" : plpass_dma->ifconfig == INTERFACE_SECONDARY ? "INTERFACE_SECONDARY" : "ifconfig?", plpass_dma->intr_id, plpass_dma->num_channels, plpass_dma->bit_width, plpass_dma->period_count_in_word32, plpass_dma->bytes_per_sample, plpass_dma->dma_buffer, plpass_dma->dma_buffer_size, (void *)plpass_dma->dma_base_address, plpass_dma->watermark, plpass_dma->no_of_buffers, plpass_dma->single_buf_size); lpass_irq_stat_init(&plpass_dma->irq_stat); return 0; } static unsigned int use_fastirq = 1; /** */ int lpass_dma_irq_request(struct _lpass_ctrl *plpass_ctrl, unsigned int cpu) { int rc = -ENODEV; if (plpass_ctrl->irq_active) { DBG_ERR("%s: error: irq_active set\n", __func__); return -EBUSY; } #if defined(CONFIG_AVM_FASTIRQ) if (use_fastirq) { rc = avm_request_fiq_on(cpumask_of(cpu), plpass_ctrl->irq, lpass_irq_handler, 0, "ipq-lpass", (void *)plpass_ctrl); if (rc == 0) { plpass_ctrl->fiq_cpumode = 1 + cpu; avm_gic_fiq_setup(plpass_ctrl->irq, cpumask_of(cpu), FIQ_PRIO_USER, 0, 0); pr_info("%s: %u %pS %p success\n", __func__, cpu, lpass_irq_handler, plpass_ctrl); } } #endif if (rc < 0) rc = request_irq_on(cpu, plpass_ctrl->irq, lpass_irq_handler, 0, "ipq-lpass", plpass_ctrl); if (rc == 0) plpass_ctrl->irq_active = 1; else pr_err("%s: request_irq(%d failed)\n", __func__, plpass_ctrl->irq); return rc; } /** * \brief: * initialize lpass-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] shared_irq: only on irq * \param[in] slots: * \param[in] fs_per_dma: count of framesyncs for one dma-buffer (4 ms == 32) * * \note: * bufsize of dma-buffer for callback: fs_per_dma * slot * sizeof(short) * return of callbacks: > 0: bytes to shrink dma-size for the following descriptor * * \retval 0: ok */ int tdm_if_lpass_dma_init(void *refhandle, int (*TxData)(void *refhandle, void *buf), int (*RxData)(void *refhandle, void *buf), unsigned int cpu, unsigned int shared_irq, unsigned int slots, unsigned int fs_per_dma) { int rc; struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; DBG_TRC_API("%s(cpu=%u ref=%p slots=%u, fs_per_dma=%u)\n", __func__, cpu, refhandle, slots, fs_per_dma); if (plpass_ctrl == NULL) { DBG_ERR("%s: error: no valid driver set - please check your device-tree!\n", __func__); return -EINVAL; } plpass_ctrl->shared_irq = shared_irq; rc = lpass_dma_irq_request(plpass_ctrl, cpu); if (rc == 0) { rc |= lpass_dma_init(plpass_ctrl, &plpass_ctrl->rx_dma, fs_per_dma, refhandle, RxData); rc |= lpass_dma_init(plpass_ctrl, &plpass_ctrl->tx_dma, fs_per_dma, refhandle, TxData); } return rc; } /** */ static void dump_time_diff(char *txt, unsigned int txt_size, unsigned long ts, unsigned long start_ts) { if (ts && start_ts) { ts -= start_ts; snprintf(txt, txt_size, "%lu us", avm_cycles_to_usec(ts)); } else { snprintf(txt, txt_size, "timeout"); } } /** */ static void measure_time_beetween_dmairqs(struct _lpass_ctrl *plpass_ctrl, unsigned long start_ts) { char txt[3][64]; unsigned long rx_ts = 0, tx_ts = 0; unsigned long cycle_freq = avm_get_cyclefreq(); uint32_t status; start_ts |= 1; do { ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &status); if ((rx_ts == 0) && (status & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_WRDMA_CH0_BMSK))) { rx_ts = avm_get_cycles() | 0x1; } if ((tx_ts == 0) && (status & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_RDDMA_CH1_BMSK))) { tx_ts = avm_get_cycles() | 0x1; } if ((avm_get_cycles() - start_ts) > cycle_freq) /* max 1 s */ break; } while ((rx_ts == 0) || (tx_ts == 0)); dump_time_diff(txt[0], sizeof(txt[0]), rx_ts, start_ts); dump_time_diff(txt[1], sizeof(txt[1]), tx_ts, start_ts); dump_time_diff(txt[2], sizeof(txt[2]), tx_ts > rx_ts ? tx_ts : rx_ts, tx_ts > rx_ts ? rx_ts : tx_ts); pr_err("%s: time until rx-irq: %s tx-irq: %s (diff %s)\n", __func__, txt[0], txt[1], txt[2]); } /** * \brief: * start dma */ void tdm_if_lpass_dma_start(void) { unsigned long start_ts; struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; struct ipq_lpass_pcm_config *pconfig = &plpass_ctrl->config; unsigned long flags; DBG_TRC_API("%s()\n", __func__); if (pLpassCtrl == NULL) { DBG_ERR("%s: error: no valid driver set - please check your device-tree!\n", __func__); return; } avm_rte_local_irq_save(flags); lpass_dma_config_init(&plpass_ctrl->rx_dma); lpass_dma_config_init(&plpass_ctrl->tx_dma); ipq_lpass_pcm_reset(plpass_ctrl->ipq_lpass_lpaif_base, SECONDARY, LPASS_HW_DMA_SINK); ipq_lpass_pcm_reset(plpass_ctrl->ipq_lpass_lpaif_base, PRIMARY, LPASS_HW_DMA_SOURCE); /* * Tx mode support in Seconday interface. * configure secondary */ pconfig->sync_src = plpass_ctrl->master ? TDM_MODE_MASTER : TDM_MODE_SLAVE; pconfig->sync_delay = plpass_ctrl->txdelay ? TDM_DATA_DELAY_1_CYCLE : TDM_DATA_DELAY_0_CYCLE; ipq_lpass_pcm_config(pconfig, plpass_ctrl->ipq_lpass_lpaif_base, SECONDARY, LPASS_HW_DMA_SINK); /* * Rx mode support in Primary interface * configure primary as TDM slave mode */ pconfig->sync_src = TDM_MODE_SLAVE; pconfig->sync_delay = plpass_ctrl->rxdelay ? TDM_DATA_DELAY_1_CYCLE : TDM_DATA_DELAY_0_CYCLE; ipq_lpass_pcm_config(pconfig, plpass_ctrl->ipq_lpass_lpaif_base, PRIMARY, LPASS_HW_DMA_SOURCE); ipq_lpass_pcm_reset_release(plpass_ctrl->ipq_lpass_lpaif_base, SECONDARY, LPASS_HW_DMA_SINK); ipq_lpass_pcm_reset_release(plpass_ctrl->ipq_lpass_lpaif_base, PRIMARY, LPASS_HW_DMA_SOURCE); if (plpass_ctrl->shared_irq) { plpass_ctrl->rx_dma.irq_enabled = 1; plpass_ctrl->tx_dma.irq_enabled = 0; } else { plpass_ctrl->rx_dma.irq_enabled = 1; plpass_ctrl->tx_dma.irq_enabled = 1; } start_ts = avm_get_cycles(); lpass_dma_enable(&plpass_ctrl->rx_dma); lpass_dma_enable(&plpass_ctrl->tx_dma); /* tx-interrupt before rx - measure: 517 us */ ipq_lpass_pcm_enable(plpass_ctrl->ipq_lpass_lpaif_base, PRIMARY, LPASS_HW_DMA_SOURCE); ipq_lpass_pcm_enable(plpass_ctrl->ipq_lpass_lpaif_base, SECONDARY, LPASS_HW_DMA_SINK); /* verify: wait for Rx+Tx-DMA in polling-mode */ measure_time_beetween_dmairqs(plpass_ctrl, start_ts); avm_rte_local_irq_restore(flags); } /** * \brief: * stop dma */ void tdm_if_lpass_dma_stop(void) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; DBG_TRC_API("%s()\n", __func__); if (pLpassCtrl == NULL) { DBG_ERR("%s: error: no valid driver set - please check your device-tree!\n", __func__); return; } lpass_pcm_dma_stop(&plpass_ctrl->rx_dma); lpass_pcm_dma_stop(&plpass_ctrl->tx_dma); } /** * \brief: * deinitialize dma-interface */ void tdm_if_lpass_dma_exit(void) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; DBG_TRC_API("%s()\n", __func__); if (pLpassCtrl == NULL) { DBG_ERR("%s: error: no valid driver set - please check your device-tree!\n", __func__); return; } lpass_pcm_dma_stop(&plpass_ctrl->rx_dma); lpass_pcm_dma_stop(&plpass_ctrl->tx_dma); lpass_pcm_dma_exit(&plpass_ctrl->rx_dma); lpass_pcm_dma_exit(&plpass_ctrl->tx_dma); if (plpass_ctrl->irq_active == 0) return; if (plpass_ctrl->fiq_cpumode) { #if defined(CONFIG_AVM_FASTIRQ) avm_free_fiq_on(plpass_ctrl->fiq_cpumode - 1, plpass_ctrl->irq, plpass_ctrl); plpass_ctrl->fiq_cpumode = 0; #endif/*--- #if defined(CONFIG_AVM_FASTIRQ) ---*/ } else { free_irq(plpass_ctrl->irq, plpass_ctrl); } plpass_ctrl->irq_active = 0; } /** * \brief: * get count of interrupts * \param[out] rx_irqcnt * \param[out] tx_irqcnt */ void tdm_if_lpass_dma_irqcnt(unsigned long *rx_irqcnt, unsigned long *tx_irqcnt) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; if (rx_irqcnt && plpass_ctrl) *rx_irqcnt = plpass_ctrl->rx_dma.irq_cnt; if (tx_irqcnt && plpass_ctrl) *tx_irqcnt = plpass_ctrl->tx_dma.irq_cnt; } /** * \brief: * reset count of interrupts */ void tdm_if_lpass_dma_irqcnt_reset(void) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; if (plpass_ctrl) { plpass_ctrl->rx_dma.irq_cnt = 0; plpass_ctrl->tx_dma.irq_cnt = 0; } } /** */ static void lpass_pcm_dma_exit(struct _lpass_dma *pdma) { kfree(pdma->dma_buffer); pdma->dma_buffer = NULL; } /** */ static void lpass_pcm_dma_stop(struct _lpass_dma *pdma) { ipq_lpass_dma_clear_interrupt_config(pdma->ipq_lpass_lpaif_base, pdma->dir, pdma->idx, pdma->intr_id); ipq_lpass_dma_disable_interrupt(pdma->ipq_lpass_lpaif_base, pdma->dir, pdma->idx, pdma->intr_id); ipq_lpass_disable_dma_channel(pdma->ipq_lpass_lpaif_base, pdma->idx, pdma->dir); ipq_lpass_pcm_disable(pdma->ipq_lpass_lpaif_base, pdma->ifconfig, pdma->dir); } /** * debugcmd-stuff */ static unsigned short testpacket[] = { 0xffff, 0x0005, 0x0aff, 0x0002, 0x0214, 0xf600, 0x010e, 0xffff }; static unsigned int send_packet_cnt; static unsigned int send_packet_idx; static unsigned int packet_offset; static unsigned int testval[8]; static unsigned int trace_mode = 1; static unsigned int check_loop; static unsigned int loop_failure, loop_testcnt; static unsigned int loop_val_offset = 5; static unsigned int send_loop_value; static unsigned int rcvd_loop_value; static unsigned int shared_irq = 1; /** */ static int TestRxData(void *refhandle, void *buf) { struct _lpass_dma *pdma = (struct _lpass_dma *)refhandle; unsigned short *p = (unsigned short *)buf; unsigned int i; static unsigned long start_j; for (i = 0; i < pdma->single_buf_size / sizeof(short); i++) { if ((i % pdma->num_channels) == loop_val_offset) { if (check_loop) { loop_testcnt++; if (*p != rcvd_loop_value) { loop_failure++; printk_ratelimited("%s: checkloop error on counter:%p[%u] rcvd %04x want %04x\n", __func__, buf, i, *p, rcvd_loop_value); } rcvd_loop_value = ((*p + 1) & 0xFFFF) | 0x8000; } else if (testval[loop_val_offset] && (*p != testval[loop_val_offset])) { printk_ratelimited("%s: checkloop error on testval :%p[%u] rcvd %04x want %04x\n", __func__, buf, i, *p, testval[loop_val_offset]); } } p++; } if (trace_mode == 0) return 0; if ((jiffies - start_j) >= msecs_to_jiffies(5000)) { p = (unsigned short *)buf; pr_info("rx:%p(irqs=%lu err=%lu udr=%lu max-fs-diff=%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, pdma->irq_cnt, pdma->err_cnt, pdma->udr_ovr, pdma->max_diff_curr_addr / sizeof(short) / pdma->num_channels, 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]); pdma->max_diff_curr_addr = 0; start_j = jiffies; } for (i = 0; i < pdma->single_buf_size; i++) { if (p[i] == 0xAA) { printk_ratelimited("preamble found: ----rx: %*ph\n", pdma->single_buf_size - i, &p[i]); break; } } return 0; } /** */ static unsigned short send_testpacket(void) { if (send_packet_cnt) { if (send_packet_idx >= ARRAY_SIZE(testpacket)) { send_packet_idx = 0; send_packet_cnt--; } return testpacket[send_packet_idx++]; } return 0; } /** */ static int TestTxData(void *refhandle, void *buf) { unsigned short *p = (unsigned short *)buf; struct _lpass_dma *pdma = (struct _lpass_dma *)refhandle; unsigned int i; static unsigned long start_j; for (i = 0; i < pdma->single_buf_size / sizeof(short); i++) { *p = 0; if (ARRAY_SIZE(testval) >= pdma->num_channels) *p = testval[i % pdma->num_channels]; if (check_loop && ((i % pdma->num_channels) == loop_val_offset)) *p = ((send_loop_value++) & 0xFFFF ) | 0x8000; else if (send_packet_cnt && ((i % pdma->num_channels) == packet_offset)) *p = send_testpacket(); p++; } if (trace_mode == 0) return 0; if ((jiffies - start_j) >= msecs_to_jiffies(5000)) { p = (unsigned short *)buf; pr_info("tx:%p (irqs=%lu err=%lu ovr=%lu max-fs-diff=%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, pdma->irq_cnt, pdma->err_cnt, pdma->udr_ovr, pdma->max_diff_curr_addr / sizeof(short) / pdma->num_channels, 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; } return 0; } /** */ static int debugcmd_start_lpass_tdm(unsigned int slots, unsigned int rxdelay, unsigned int txdelay, unsigned int master, unsigned int bit8) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; unsigned int fs_per_dma = 32; int ret; if (plpass_ctrl->tdm_if_api) { pr_err("%s: interface locked\n", __func__); return 0; } if (slots == 0) slots = 8; ret = tdm_if_lpass_config_init(slots, rxdelay, txdelay, bit8 == 0 ? 16 : 8, master); if (ret) return 0; plpass_ctrl->tdm_if_api = tdm_if_debugcmd; pr_err("%s: irq-mode: %s\n", __func__, shared_irq ? "use shared" : "use both"); plpass_ctrl->shared_irq = shared_irq; ret = lpass_dma_irq_request(plpass_ctrl, 1); if (ret == 0) { ret |= lpass_dma_init(plpass_ctrl, &plpass_ctrl->rx_dma, fs_per_dma, &plpass_ctrl->rx_dma, TestRxData); ret |= lpass_dma_init(plpass_ctrl, &plpass_ctrl->tx_dma, fs_per_dma, &plpass_ctrl->tx_dma, TestTxData); tdm_if_lpass_dma_start(); } return ret == 0 ? 1 : 0; } /** */ static int debugcmd_stop_lpass_tdm(void) { struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; if (plpass_ctrl->tdm_if_api == tdm_if_debugcmd) { tdm_if_lpass_dma_stop(); tdm_if_lpass_dma_exit(); tdm_if_lpass_config_exit(); } return 0; } static const struct _debugcmd_profile debug_cmd[] = { { .cmd = "print", .type = 'p', .argc = 0, .ashex = 0, .help = NULL }, { .cmd = "start", .type = 'S', .argc = 5, .ashex = 0, .help = " " }, { .cmd = "stop", .type = 'E', .argc = 0, .ashex = 0, .help = NULL }, { .cmd = "wr", .type = 'W', .argc = 2, .ashex = 1, .help = " " }, { .cmd = "txval", .type = 'T', .argc = 2, .ashex = 1, .help = "txval " }, { .cmd = "loop", .type = 'L', .argc = 2, .ashex = 1, .help = " " }, { .cmd = "packet", .type = 'P', .argc = 1, .ashex = 0, .help = "" }, { .cmd = "trace", .type = 't', .argc = 1, .ashex = 0, .help = "" }, { .cmd = "firq", .type = 'F', .argc = 1, .ashex = 0, .help = "toggle firq-mode" }, { .cmd = "irq", .type = 'I', .argc = 0, .ashex = 0, .help = "toggle irq-mode" }, { .cmd = NULL, .type = 0, .argc = 0, .ashex = 0, .help = NULL }, /* EOF */ }; static int tdm_start; /** */ void __iomem *debugcmd_pyhsreg_to_virt(struct _lpass_ctrl *plpass_ctrl, unsigned long phys_addr) { if ((phys_addr >= LPASS_CC_REG_BASE) && (phys_addr < (LPASS_CC_REG_BASE + LPASS_CC_REG_BASE_SIZE))) { return (plpass_ctrl->sg_ipq_lpass_base + (phys_addr - LPASS_CC_REG_BASE)); } if ((phys_addr >= LPASS_LPA_IF_REG_BASE) && (phys_addr < (LPASS_LPA_IF_REG_BASE + LPASS_LPA_IF_REG_BASE_SIZE))) { return (plpass_ctrl->ipq_lpass_lpaif_base + (phys_addr - LPASS_LPA_IF_REG_BASE)); } switch (phys_addr - LPASS_CC_REG_BASE) { case HWIO_LPASS_AUDIO_CORE_LPAIF_PRI_MODE_MUXSEL_ADDR(0): case HWIO_LPASS_AUDIO_CORE_LPAIF_SEC_MODE_MUXSEL_ADDR(0): case HWIO_LPASS_TCSR_QOS_CTL_ADDR(0): case HWIO_LPASS_AUDIO_CORE_QOS_CTL_ADDR(0): case HWIO_LPASS_AUDIO_CORE_QOS_CORE_CGCR_ADDR(0): return (plpass_ctrl->sg_ipq_lpass_base + phys_addr - LPASS_CC_REG_BASE); } return 0; } /** */ static void cmdlineparse(char *cmdline, void *_plpass_ctrl) { struct _lpass_ctrl *plpass_ctrl = _plpass_ctrl; unsigned int arg[5], scanned; const char *p = cmdline; int type; memset(&arg, 0x0, sizeof(arg)); type = debugcmd_get_param(debug_cmd, &p, arg, ARRAY_SIZE(arg), &scanned); switch (type) { case 'p': /* print */ tdm_if_lpass_print_status("dump_register", NULL, 0); break; case 'S': /* start */ case 'E': /* stop */ if (tdm_start) tdm_start = debugcmd_stop_lpass_tdm(); if (type == 'S') tdm_start = debugcmd_start_lpass_tdm(arg[0], arg[1], arg[2], arg[3], arg[4]); break; case 'T': /* txval */ if (scanned >= 2) { if (arg[1] < ARRAY_SIZE(testval)) { testval[arg[1]] = arg[0]; pr_err("%s: set slot=%u with value %x:\n", __func__, arg[1], arg[0]); } } break; case 'L': /* loop */ if (check_loop) pr_err("%s: loop_failure=%u from %u\n", __func__, loop_failure, loop_testcnt); if (scanned >= 1) { if (check_loop == 0) { loop_failure = 0; loop_testcnt = 0; } check_loop = arg[0]; } if (scanned >= 2) loop_val_offset = arg[1]; break; case 'P': /* packet */ if (scanned >= 1) send_packet_cnt = arg[0]; break; case 'I': /* irq */ shared_irq = !shared_irq; pr_err("%s: toggle irq-mode: %s\n", __func__, shared_irq ? "use shared" : "use both"); break; case 'F': /* firq */ use_fastirq = !use_fastirq; pr_err("%s: toggle firq-mode: %s\n", __func__, use_fastirq ? "use fastirq" : "use irq"); break; case 't': /* trace */ if (scanned >= 1) trace_mode = arg[0]; break; case 'W': /* write */ if (scanned >= 2) { void __iomem *reg; reg = debugcmd_pyhsreg_to_virt(plpass_ctrl, arg[0]); if (reg) { writel(arg[1], reg); snprint_lpass_register(NULL, NULL, 0, pLpassCtrl, reg); } } break; } } /** */ static const struct of_device_id qca_avm_match_table[] = { { .compatible = "qca,ipq5018-lpass-pcm_avm" }, {}, }; int is_IPQ5018(void) { return pLpassCtrl ? 1 : 0; } /* * FUNCTION: ipq_lpass_pcm_driver_probe * * DESCRIPTION: very basic one time activities * * RETURN VALUE: error if any */ static int lpass_pcm_avm_driver_probe(struct platform_device *pdev) { struct _lpass_ctrl *plpass_ctrl; void __iomem *ipq_lpass_lpaif_base, *sg_ipq_lpass_base; uint32_t irq; struct resource *res; const struct of_device_id *match; match = of_match_device(qca_avm_match_table, &pdev->dev); if (!match) return -ENODEV; if (!pdev) return -EINVAL; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ipq_lpass_lpaif_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(ipq_lpass_lpaif_base)) return PTR_ERR(ipq_lpass_lpaif_base); if (!pdev->dev.coherent_dma_mask) pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); pr_info("%s : Lpaif version : 0x%x\n", __func__, readl(ipq_lpass_lpaif_base)); sg_ipq_lpass_base = ipq_lpass_phy_to_virt(LPASS_CC_REG_BASE); if (IS_ERR(sg_ipq_lpass_base)) return PTR_ERR(sg_ipq_lpass_base); /* Allocate memory for rx and tx instance */ plpass_ctrl = kzalloc(sizeof(struct _lpass_ctrl), GFP_KERNEL); if (plpass_ctrl == NULL) { pr_err("%s: Error in allocating mem\n", __func__); return -ENOMEM; } plpass_ctrl->ipq_lpass_lpaif_base = ipq_lpass_lpaif_base; plpass_ctrl->sg_ipq_lpass_base = sg_ipq_lpass_base; plpass_ctrl->pcm_pdev = pdev; /* * Set interrupt */ irq = platform_get_irq_byname(pdev, "out0"); if (irq < 0) { dev_err(&pdev->dev, "Failed to get irq by name (%d)\n", irq); return -ENODEV; } plpass_ctrl->irq = irq; plpass_ctrl->DebugHandle = avm_DebugCallRegister("lpass_", cmdlineparse, plpass_ctrl); pLpassCtrl = plpass_ctrl; platform_set_drvdata(pdev, plpass_ctrl); DBG_TRC("%s: done\n", __func__); return 0; } /* * FUNCTION: ipq_lpass_pcm_avm_driver_remove * * DESCRIPTION: clean up * * RETURN VALUE: error if any */ static int lpass_pcm_avm_driver_remove(struct platform_device *pdev) { struct _lpass_ctrl *plpass_ctrl = platform_get_drvdata(pdev); tdm_if_lpass_config_exit(); pLpassCtrl = NULL; avm_DebugCallUnRegister(plpass_ctrl->DebugHandle); kfree(plpass_ctrl); return 0; } /* * DESCRIPTION OF PCM-LPASS AVM MODULE */ #define DRIVER_NAME "ipq_lpass_pcm_avm" static struct platform_driver ipq_lpass_pcm_avm_driver = { .probe = lpass_pcm_avm_driver_probe, .remove = lpass_pcm_avm_driver_remove, .driver = { .name = DRIVER_NAME, .of_match_table = qca_avm_match_table, }, }; module_platform_driver(ipq_lpass_pcm_avm_driver); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION(DRV_NAME ": qca avm pcm interface"); MODULE_ALIAS(DRIVER_NAME);