// 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 #include #if defined(CONFIG_AVM_FASTIRQ) #include #include #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 "ipq-lpass.h" #include "ipq-lpass-cc.h" #include "ipq9574-lpass-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) /* #define DBG_IPQ_MEASURE_IRQ */ /** */ 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; dma_addr_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_dma_config dma_config; struct ipq_lpass_pcm_config pcm_config; }; struct _lpass_ctrl { void __iomem *ipq_lpass_lpaif_base; void __iomem *sg_ipq_lpass_base; struct platform_device *pcm_pdev; struct gpio_chip *tdm_check_gpiochip; void *DebugHandle; enum _tdm_if_api { tdm_if_unused = 0, tdm_if_api, tdm_if_debugcmd, } tdm_if_api; int irq; int fiq_disabled; const char *irqname; int irq_active; int shared_irq; int fiq_cpumode; unsigned int master; unsigned int rate; 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 = 0x3 } #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=%zu):\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) { size_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, 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); } } struct _lpass_interrupt_status { uint32_t status_chan0; uint32_t raw_status_chan0; uint32_t status_chan1; uint32_t raw_status_chan1; }; /** */ static void lpass_read_int_status(struct _lpass_ctrl *plpass_ctrl, struct _lpass_interrupt_status *pint_status) { ipq_lpass_dma_read_interrupt_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &pint_status->status_chan0); ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &pint_status->raw_status_chan0); ipq_lpass_dma_read_interrupt_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL1, &pint_status->status_chan1); ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL1, &pint_status->raw_status_chan1); } static void lpass_clear_int_status(struct _lpass_ctrl *plpass_ctrl, struct _lpass_interrupt_status *pint_status) { ipq_lpass_dma_clear_interrupt(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, pint_status->raw_status_chan0); ipq_lpass_dma_clear_interrupt(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL1, pint_status->raw_status_chan1); } /* */ static irqreturn_t lpass_irq_handler(int intrsrc, void *data) { struct _lpass_interrupt_status status; 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; lpass_read_int_status(plpass_ctrl, &status); while (status.status_chan0 || status.status_chan1) { if (status.raw_status_chan0 & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_WRDMA_CH0_BMSK)) lpass_dma_handler("Rx", pdma_rx); if (status.raw_status_chan1 & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_RDDMA_CH1_BMSK)) lpass_dma_handler("Tx", pdma_tx); if (status.raw_status_chan1 & (HWIO_LPASS_LPAIF_IRQ_STATa_ERR_RDDMA_CH1_BMSK)) pdma_tx->err_cnt++; if (status.raw_status_chan1 & (HWIO_LPASS_LPAIF_IRQ_STATa_UNDR_RDDMA_CH1_BMSK)) pdma_tx->udr_ovr++; if (status.raw_status_chan0 & (HWIO_LPASS_LPAIF_IRQ_STATa_ERR_WRDMA_CH0_BMSK)) pdma_rx->err_cnt++; if (status.raw_status_chan0 & (HWIO_LPASS_LPAIF_IRQ_STATa_OVR_WRDMA_CH0_BMSK)) pdma_rx->udr_ovr++; lpass_clear_int_status(plpass_ctrl, &status); lpass_read_int_status(plpass_ctrl, &status); } return IRQ_HANDLED; } /** * preset ipq_lpass_pcm_config-structure (no hwio-access) */ static void lpass_pcm_config_preset(struct ipq_lpass_pcm_config *ppcm_config, unsigned int slots, unsigned int bit_width, unsigned int master, unsigned int delay) { unsigned int slot_mask; slot_mask = (0x1 << slots) - 1; ppcm_config->slot_mask = slot_mask; ppcm_config->bit_width = bit_width; ppcm_config->slot_count = slots; ppcm_config->slot_width = bit_width; ppcm_config->sync_type = TDM_SHORT_SYNC_TYPE; ppcm_config->ctrl_data_oe = TDM_CTRL_DATA_OE_ENABLE; ppcm_config->invert_sync = TDM_LONG_SYNC_NORMAL; ppcm_config->sync_src = master ? TDM_MODE_MASTER : TDM_MODE_SLAVE; ppcm_config->sync_delay = delay ? TDM_DATA_DELAY_1_CYCLE : TDM_DATA_DELAY_0_CYCLE; } /** * preset lpass_dma_config-structure (no hwio-access) * presets used in lpass_dma_config_hw() */ static void lpass_dma_config_preset(struct _lpass_dma *pdma) { struct lpass_dma_config *pdma_config = &pdma->dma_config; uint32_t wps_count = 0; memset(pdma_config, 0, sizeof(*pdma_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) { pdma_config->burst_size = 16; } else if ((pdma->period_count_in_word32 & PCM_DMA_BUFFER_8BYTE_ALIGNMENT) == 0) { pdma_config->burst_size = 8; } else if ((pdma->period_count_in_word32 & PCM_DMA_BUFFER_4BYTE_ALIGNMENT) == 0) { pdma_config->burst_size = 4; } else { pdma_config->burst_size = 1; } } else { pdma_config->burst_size = 1; } pdma_config->buffer_len = pdma->dma_buffer_size / sizeof(uint32_t); pdma_config->buffer_start_addr = pdma->dma_base_address; pdma_config->dma_int_per_cnt = pdma->period_count_in_word32; pdma_config->wps_count = wps_count; pdma_config->watermark = pdma->watermark; pdma_config->ifconfig = pdma->ifconfig; pdma_config->idx = pdma->idx; if (pdma_config->burst_size >= 8) { pdma_config->burst8_en = 1; pdma_config->burst_size = 1; } pdma_config->burst16_en = 0; pdma_config->dir = pdma->dir; pdma_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__, pdma_config->buffer_start_addr, pdma_config->dma_int_per_cnt, pdma_config->buffer_len, pdma_config->burst_size, pdma_config->wps_count, pdma_config->watermark, pdma_config->ifconfig, pdma_config->burst8_en, pdma_config->burst16_en); } /** * initialize dma-interface (hwio-access) */ static void lpass_dma_config_hw(struct _lpass_dma *pdma) { struct lpass_dma_config *pdma_config = &pdma->dma_config; 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__, pdma_config->buffer_start_addr, pdma_config->dma_int_per_cnt, pdma_config->buffer_len, pdma_config->burst_size, pdma_config->wps_count, pdma_config->watermark, pdma_config->ifconfig, pdma_config->burst8_en, pdma_config->burst16_en); ipq_lpass_disable_dma_channel(pdma->ipq_lpass_lpaif_base, pdma->idx, pdma->dir); ipq_lpass_config_dma_channel(pdma_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); } #define TDM_MODE_SLAVE_VALUE 1 #define TDM_MODE_SLAVE_SRC 1 #define TDM_MODE_MASTER_VALUE 3 #define TDM_MODE_MASTER_SRC 0 /** */ static int lpass_setup_bit_clock(uint32_t clk_rate, int master) { uint32_t val, src, mode; /* * set clock rate for PRI & SEC * PRI is slave mode and secondary can be slave or master */ ipq_lpass_lpaif_muxsetup(INTERFACE_PRIMARY, TDM_MODE_SLAVE, TDM_MODE_SLAVE_VALUE, TDM_MODE_SLAVE_SRC); if (master) { mode = TDM_MODE_MASTER; val = TDM_MODE_MASTER_VALUE; src = TDM_MODE_MASTER_SRC; if (ipq_lpass_set_clk_rate(INTERFACE_SECONDARY, clk_rate) != 0) { DBG_ERR("%s: Bit clk set Failed\n", __func__); return -EINVAL; } } else { mode = TDM_MODE_SLAVE; val = TDM_MODE_SLAVE_VALUE; src = TDM_MODE_SLAVE_SRC; } ipq_lpass_lpaif_muxsetup(INTERFACE_SECONDARY, mode, val, src); 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; 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; } plpass_ctrl->master = master; plpass_ctrl->rate = PCM_SAMPLE_RATE_8K; /* * Tx mode support in Secondary interface. * configure secondary depend on master */ lpass_pcm_config_preset(&plpass_ctrl->tx_dma.pcm_config, slots, bit_width, master, txdelay); /* * Rx mode support in Primary interface * configure primary as TDM slave mode */ lpass_pcm_config_preset(&plpass_ctrl->rx_dma.pcm_config, slots, bit_width, 0, rxdelay); 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_preset(struct _lpass_ctrl *plpass_ctrl, struct _lpass_dma *plpass_dma, unsigned int fs_per_dma, void *refhandle, int (*RxTxData)(void *refhandle, void *p)) { 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(plpass_dma->pcm_config.bit_width); samples_per_interrupt = fs_per_dma * plpass_dma->pcm_config.slot_count; clk_rate = plpass_dma->pcm_config.bit_width * plpass_ctrl->rate * plpass_dma->pcm_config.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 * plpass_dma->pcm_config.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; plpass_dma->intr_id = INTERRUPT_CHANNEL0; } 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_CHANNEL1; } plpass_dma->bit_width = plpass_dma->pcm_config.bit_width; plpass_dma->num_channels = plpass_dma->pcm_config.slot_count; plpass_dma->bytes_per_sample = bytes_per_sample; 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=%pad 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, &plpass_dma->dma_base_address, plpass_dma->watermark, plpass_dma->no_of_buffers, plpass_dma->single_buf_size); lpass_dma_config_preset(plpass_dma); lpass_irq_stat_init(&plpass_dma->irq_stat); return 0; } /** */ int lpass_dma_irq_request(struct _lpass_ctrl *plpass_ctrl, unsigned int cpu) { int rc = -ENODEV; #if defined(CONFIG_AVM_FASTIRQ) int hwirq; struct irq_desc *irq_desc; #endif if (plpass_ctrl->irq_active) { DBG_ERR("%s: error: irq_active set\n", __func__); return -EBUSY; } #if defined(CONFIG_AVM_FASTIRQ) if (!plpass_ctrl->fiq_disabled) { rc = avm_request_fiq_on(cpumask_of(cpu), plpass_ctrl->irq, lpass_irq_handler, 0, plpass_ctrl->irqname, (void *)plpass_ctrl); if (rc == 0) { irq_desc = irq_to_desc(plpass_ctrl->irq); BUG_ON(!irq_desc); hwirq = irq_desc->irq_data.hwirq; plpass_ctrl->fiq_cpumode = 1 + cpu; avm_gic_fiq_setup(hwirq, 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, plpass_ctrl->irqname, 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_preset(plpass_ctrl, &plpass_ctrl->rx_dma, fs_per_dma, refhandle, RxData); rc |= lpass_dma_preset(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[2]; start_ts |= 1; do { ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL0, &status[0]); ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, INTERRUPT_CHANNEL1, &status[1]); if ((rx_ts == 0) && (status[0] & (HWIO_LPASS_LPAIF_IRQ_STATa_PER_WRDMA_CH0_BMSK))) { rx_ts = avm_get_cycles() | 0x1; } if ((tx_ts == 0) && (status[1] & (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]); } struct _lpass_interrupt_statistic { unsigned long ts; unsigned long dts; uint32_t raw_status; uint32_t dma_idx; uint32_t curr_addr; }; #if defined(DBG_IPQ_MEASURE_IRQ) /** */ static void measure_time_debug(struct _lpass_ctrl *plpass_ctrl, struct _lpass_dma *pdma) { uint32_t curr_addr, dma_idx, old_curr_idx = -1; unsigned int raw_status, i; struct _lpass_interrupt_statistic stat[20]; unsigned int idx = 0; unsigned long start_ts, old_status_ts, old_dma_ts; unsigned long cycle_freq = avm_get_cyclefreq(); start_ts = old_status_ts = old_dma_ts = avm_get_cycles(); for (;;) { unsigned long ts = avm_get_cycles(); if (idx >= ARRAY_SIZE(stat)) break; if ((avm_get_cycles() - start_ts) > cycle_freq) /* max 1 s */ break; ipq_lpass_dma_get_curr_addr(pdma->ipq_lpass_lpaif_base, pdma->idx, pdma->dir, &curr_addr); ipq_lpass_dma_read_interrupt_raw_status(plpass_ctrl->ipq_lpass_lpaif_base, pdma->intr_id, &raw_status); dma_idx = lpass_addr_to_index(pdma, curr_addr); if (dma_idx != old_curr_idx) { stat[idx].raw_status = raw_status; stat[idx].dts = ts - old_status_ts; stat[idx].ts = ts - start_ts; stat[idx].dma_idx = dma_idx; stat[idx].curr_addr = curr_addr; old_curr_idx = dma_idx; old_status_ts = ts; idx++; continue; } if (raw_status) { stat[idx].raw_status = raw_status; stat[idx].dts = ts - old_dma_ts; stat[idx].ts = ts - start_ts; stat[idx].dma_idx = dma_idx; stat[idx].curr_addr = curr_addr; old_dma_ts = ts; idx++; ipq_lpass_dma_clear_interrupt(pdma->ipq_lpass_lpaif_base, pdma->intr_id, raw_status); } } for (i = 0 ; i < idx; i++) { pr_info("[%2u]%cx:%6lu: dt=%6lu us: dma-idx=%x raw_status=%08x (curr_addr=%x)\n", i, pdma->intr_id ? 'T' : 'R', avm_cycles_to_usec(stat[i].ts), avm_cycles_to_usec(stat[i].dts), stat[i].dma_idx, stat[i].raw_status, stat[i].curr_addr - pdma->dma_base_address); } } #endif /** * Initial wird nach dem ipq_lpass_probe() eine CLK rausgeroutet (25 MHz) * Um zu verhindern, dass diese gegen die CLK des DECT-Chips treibt und somit * die CLK und FS-Messung verfälscht wird hier komplett auf SLAVE gemuxt */ static void lpass_no_clockout(struct _lpass_ctrl *plpass_ctrl) { /* pr_info("%s\n", __func__); */ ipq_lpass_lpaif_muxsetup(INTERFACE_PRIMARY, TDM_MODE_SLAVE, TDM_MODE_SLAVE_VALUE, TDM_MODE_SLAVE_SRC); ipq_lpass_lpaif_muxsetup(INTERFACE_SECONDARY, TDM_MODE_SLAVE, TDM_MODE_SLAVE_VALUE, TDM_MODE_SLAVE_SRC); } /** * \brief: * start dma */ void tdm_if_lpass_dma_start(void) { unsigned long start_ts; struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; 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_hw(&plpass_ctrl->rx_dma); lpass_dma_config_hw(&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 Secondary interface. * configure secondary */ DBG_TRC("%s:Secondary(Source)-PCMConfig: invert_sync=%u sync_src=%u bit_width=%u slot_count=%u " "sync_type=%u dir=%u pcm_index=%u sync_delay=%u slot_width=%u slot_mask=0x%x " "ctrl_data_oe=%u\n", __func__, plpass_ctrl->tx_dma.pcm_config.invert_sync, plpass_ctrl->tx_dma.pcm_config.sync_src, plpass_ctrl->tx_dma.pcm_config.bit_width, plpass_ctrl->tx_dma.pcm_config.slot_count, plpass_ctrl->tx_dma.pcm_config.sync_type, plpass_ctrl->tx_dma.pcm_config.dir, plpass_ctrl->tx_dma.pcm_config.pcm_index, plpass_ctrl->tx_dma.pcm_config.sync_delay, plpass_ctrl->tx_dma.pcm_config.slot_width, plpass_ctrl->tx_dma.pcm_config.slot_mask, plpass_ctrl->tx_dma.pcm_config.ctrl_data_oe); ipq_lpass_pcm_config(&plpass_ctrl->tx_dma.pcm_config, plpass_ctrl->ipq_lpass_lpaif_base, SECONDARY, LPASS_HW_DMA_SINK); DBG_TRC("%s:Primary(Sink)-PCMConfig: invert_sync=%u sync_src=%u bit_width=%u slot_count=%u " "sync_type=%u dir=%u pcm_index=%u sync_delay=%u slot_width=%u slot_mask=0x%x " "ctrl_data_oe=%u\n", __func__, plpass_ctrl->rx_dma.pcm_config.invert_sync, plpass_ctrl->rx_dma.pcm_config.sync_src, plpass_ctrl->rx_dma.pcm_config.bit_width, plpass_ctrl->rx_dma.pcm_config.slot_count, plpass_ctrl->rx_dma.pcm_config.sync_type, plpass_ctrl->rx_dma.pcm_config.dir, plpass_ctrl->rx_dma.pcm_config.pcm_index, plpass_ctrl->rx_dma.pcm_config.sync_delay, plpass_ctrl->rx_dma.pcm_config.slot_width, plpass_ctrl->rx_dma.pcm_config.slot_mask, plpass_ctrl->rx_dma.pcm_config.ctrl_data_oe); ipq_lpass_pcm_config(&plpass_ctrl->rx_dma.pcm_config, 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); #if defined(DBG_IPQ_MEASURE_IRQ) measure_time_debug(plpass_ctrl, &plpass_ctrl->rx_dma); measure_time_debug(plpass_ctrl, &plpass_ctrl->tx_dma); #endif 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; } /** * \brief: * get gpio_chip for clk and fs-pins */ struct gpio_chip *tdm_if_lpass_get_gpio_chip(void) { return pLpassCtrl ? pLpassCtrl->tdm_check_gpiochip : 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 const unsigned short testpacket[] = { 0xffff, 0x0005, 0x0aff, 0x0002, 0x0214, 0xf600, 0x010e, 0xffff }; static struct _tdm_cmd_debug { unsigned int send_packet_cnt; unsigned int send_packet_idx; unsigned int packet_offset; unsigned int testval[8]; unsigned int trace_mode; unsigned int check_loop; unsigned int pre_initialized; unsigned int fsmask; unsigned int loop_failure, loop_testcnt; unsigned int loop_val_offset; unsigned int send_loop_value; unsigned int rcvd_loop_value; unsigned int shared_irq; unsigned int tdm_start; unsigned long rx_start_j; unsigned long tx_start_j; } tdmdebug = { .trace_mode = 1, .loop_val_offset = 5, .shared_irq = 1 }; /** */ static int TestRxData(void *refhandle, void *buf) { struct _tdm_cmd_debug *pdbg = &tdmdebug; struct _lpass_dma *pdma = (struct _lpass_dma *)refhandle; unsigned short *p = (unsigned short *)buf; unsigned int i; for (i = 0; i < pdma->single_buf_size / sizeof(short); i++) { if ((i % pdma->num_channels) == pdbg->loop_val_offset) { if (pdbg->check_loop) { pdbg->loop_testcnt++; if (*p != pdbg->rcvd_loop_value) { pdbg->loop_failure++; printk_ratelimited("%s: checkloop error on counter:%p[%u] rcvd %04x want %04x\n", __func__, buf, i, *p, pdbg->rcvd_loop_value); } pdbg->rcvd_loop_value = ((*p + 1) & 0xFFFF) | 0x8000; } else if (pdbg->testval[pdbg->loop_val_offset] && (*p != pdbg->testval[pdbg->loop_val_offset])) { printk_ratelimited("%s: checkloop error on testval :%p[%u] rcvd %04x want %04x\n", __func__, buf, i, *p, pdbg->testval[pdbg->loop_val_offset]); } } p++; } if (pdbg->trace_mode == 0) return 0; if ((jiffies - pdbg->rx_start_j) >= msecs_to_jiffies(5000)) { p = (unsigned short *)buf; pr_info("rx:%p(irqs=%lu err=%lu udr=%lu max-fs-diff=%zu):\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; pdbg->rx_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(struct _tdm_cmd_debug *pdbg) { if (pdbg->send_packet_cnt) { if (pdbg->send_packet_idx >= ARRAY_SIZE(testpacket)) { pdbg->send_packet_idx = 0; pdbg->send_packet_cnt--; } return testpacket[pdbg->send_packet_idx++]; } return 0; } /** */ static int TestTxData(void *refhandle, void *buf) { struct _tdm_cmd_debug *pdbg = &tdmdebug; unsigned short *p = (unsigned short *)buf; struct _lpass_dma *pdma = (struct _lpass_dma *)refhandle; unsigned int i; for (i = 0; i < pdma->single_buf_size / sizeof(short); i++) { if (pdbg->pre_initialized == 0) { *p = 0; if (ARRAY_SIZE(pdbg->testval) >= pdma->num_channels) *p = pdbg->testval[i % pdma->num_channels]; } if (pdbg->check_loop && ((i % pdma->num_channels) == pdbg->loop_val_offset)) *p = ((pdbg->send_loop_value++) & 0xFFFF) | 0x8000; else if (pdbg->send_packet_cnt && ((i % pdma->num_channels) == pdbg->packet_offset)) *p = send_testpacket(pdbg); p++; } if (pdbg->trace_mode == 0) return 0; if ((jiffies - pdbg->tx_start_j) >= msecs_to_jiffies(5000)) { p = (unsigned short *)buf; pr_info("tx:%p (irqs=%lu err=%lu ovr=%lu max-fs-diff=%zu):\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]); pdbg->tx_start_j = jiffies; pdma->max_diff_curr_addr = 0; } return 0; } /** * fs_mask: durch bitpos vorgegebenen fs setzen */ static int debugcmd_preinit_txdma_buf(unsigned int arg[], unsigned int el, unsigned int fs_mask) { char txtbuf[756]; unsigned int i, ii, fs_count; unsigned short *p; struct _lpass_ctrl *plpass_ctrl = pLpassCtrl; struct _lpass_dma *pdma = &plpass_ctrl->tx_dma; if (pdma->dma_buffer == NULL) return 0; if (el == 0) { pr_err("%s: reset\n", __func__); return 0; } if (fs_mask == 0) fs_mask = ~fs_mask; p = (unsigned short *)pdma->dma_buffer; for (ii = 0; ii < 2; ii++) { fs_count = 0; for (i = 0; i < pdma->single_buf_size / sizeof(short); i++, p++) { if (fs_mask & (1 << fs_count)) *p = (unsigned short)arg[i % el]; if ((i % pdma->num_channels) == (pdma->num_channels - 1)) fs_count++; } } p = (unsigned short *)pdma->dma_buffer; pr_err("%s: tx-buffer: %04x %04x %04x %04x %04x %04x %04x %04x ...\n", __func__, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); p = (unsigned short *)pdma->dma_buffer; for (ii = 0; ii < 2; ii++) { char *txt; unsigned int txt_size = sizeof(txtbuf); txt = txtbuf; txtbuf[0] = 0; for (i = 0; i < pdma->single_buf_size / sizeof(short); i++) { snprintf_add(txt, txt_size, " %04x ", *p++); } pr_info("%s[%u]%s\n", __func__, ii, txtbuf); } return el; } /** */ static int debugcmd_start_lpass_tdm(unsigned int shared_irq, 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_preset(plpass_ctrl, &plpass_ctrl->rx_dma, fs_per_dma, &plpass_ctrl->rx_dma, TestRxData); ret |= lpass_dma_preset(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(); tdm_if_lpass_dma_irqcnt_reset(); } 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 = "pre", .type = 'V', .argc = 8, .ashex = 1, .help = " ... "}, { .cmd = "fsmask", .type = 'M', .argc = 1, .ashex = 1, .help = " (for cmd pre)"}, { .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 */ }; /** */ 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 _tdm_cmd_debug *pdbg = &tdmdebug; struct _lpass_ctrl *plpass_ctrl = _plpass_ctrl; unsigned int arg[8], 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 (pdbg->tdm_start) pdbg->tdm_start = debugcmd_stop_lpass_tdm(); if (type == 'S') pdbg->tdm_start = debugcmd_start_lpass_tdm(pdbg->shared_irq, arg[0], arg[1], arg[2], arg[3], arg[4]); break; case 'T': /* txval */ if (scanned >= 2) { if (arg[1] < ARRAY_SIZE(pdbg->testval)) { pdbg->testval[arg[1]] = arg[0]; pr_err("%s: set slot=%u with value %x:\n", __func__, arg[1], arg[0]); } } break; case 'V': /* pre */ pdbg->pre_initialized = debugcmd_preinit_txdma_buf(arg, scanned, pdbg->fsmask); break; case 'M': /* fs_mask */ if (scanned >= 1) { pdbg->fsmask = arg[0]; pr_err("%s; set fs-mask to %x (need as follow cmd 'pre')", __func__, pdbg->fsmask); } break; case 'L': /* loop */ if (pdbg->check_loop) pr_err("%s: loop_failure=%u from %u\n", __func__, pdbg->loop_failure, pdbg->loop_testcnt); if (scanned >= 1) { if (pdbg->check_loop == 0) { pdbg->loop_failure = 0; pdbg->loop_testcnt = 0; } pdbg->check_loop = arg[0]; } if (scanned >= 2) pdbg->loop_val_offset = arg[1]; break; case 'P': /* packet */ if (scanned >= 1) pdbg->send_packet_cnt = arg[0]; break; case 'I': /* irq */ pdbg->shared_irq = !pdbg->shared_irq; pr_err("%s: toggle irq-mode: %s\n", __func__, pdbg->shared_irq ? "use shared" : "use both"); break; case 'F': /* firq */ plpass_ctrl->fiq_disabled = !plpass_ctrl->fiq_disabled; pr_err("%s: toggle firq-mode: %s\n", __func__, plpass_ctrl->fiq_disabled ? "off" : "on"); break; case 't': /* trace */ if (scanned >= 1) pdbg->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", .data = (void *)IPQ5018 }, { .compatible = "qca,ipq9574-lpass-pcm_avm", .data = (void *)IPQ9574 }, { .compatible = "qca,ipq5332-lpass-pcm_avm", .data = (void *)IPQ5332 }, {}, }; int is_IPQ5018_or_IPQ9574(void) { return pLpassCtrl ? 1 : 0; } /** */ static int lpass_pcmipq5018_probe(struct platform_device *pdev, struct _lpass_ctrl *plpass_ctrl) { int32_t irq; /* * 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->irqname = "ipq-lpass"; plpass_ctrl->irq = irq; return 0; } /** */ static int gpiochip_match_name(struct gpio_chip *chip, void *data) { const char *name = data; return !strcmp(chip->label, name); } /** */ static int lpass_pcmipq9574_probe(struct platform_device *pdev, struct _lpass_ctrl *plpass_ctrl) { const char *pinctrl_dt_name[IPQ_LPASS_MAX_PCM_INTERFACE] = { "primary", "secondary" }; const char *irq_dt_name[IPQ_LPASS_MAX_PCM_INTERFACE] = { "out0", "out1" }; int32_t irq = -1; int pcm_index = 0; struct device_node *node; struct pinctrl *pinctrl; struct pinctrl_state *pin_state; pinctrl = devm_pinctrl_get(&pdev->dev); if (IS_ERR(pinctrl)) { dev_err(&pdev->dev, "audio pinctrl not available\n"); return PTR_ERR(pinctrl); } for_each_available_child_of_node(pdev->dev.of_node, node) { pcm_index = node->name[strlen(node->name)-1] - '0'; DBG_TRC("%s : get node=%p\n", __func__, node); if (!(pcm_index >= 0 && pcm_index < IPQ_LPASS_MAX_PCM_INTERFACE)) { dev_err(&pdev->dev, "%s : Failed to extract a pcm index\n", __func__); return -EINVAL; } pin_state = pinctrl_lookup_state(pinctrl, pinctrl_dt_name[pcm_index]); if (IS_ERR(pin_state)) { dev_err(&pdev->dev, "audio %s pinctrl state not available\n", pinctrl_dt_name[pcm_index]); return PTR_ERR(pin_state); } if (pinctrl_select_state(pinctrl, pin_state)) dev_err(&pdev->dev, "audio %s pinctrl select state failed\n", pinctrl_dt_name[pcm_index]); if (pcm_index == 0) { irq = of_irq_get_byname(node, irq_dt_name[pcm_index]); DBG_TRC("%s: irq by name %s: %d\n", __func__, irq_dt_name[pcm_index], irq); } } if (irq < 0) { dev_err(&pdev->dev, "Failed to get irq by name (%d)\n", irq); return -ENOENT; } plpass_ctrl->tdm_check_gpiochip = gpiochip_find((void *)"tdmclkfs-gpio", gpiochip_match_name); if (plpass_ctrl->tdm_check_gpiochip == NULL) { dev_err(&pdev->dev, "Failed to get '%s'\n", "tdmclkfs-gpio"); return -ENOENT; } lpass_no_clockout(plpass_ctrl); plpass_ctrl->irqname = "ipq-lpass"; plpass_ctrl->irq = irq; return 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) { int ret; struct _lpass_ctrl *plpass_ctrl; void __iomem *ipq_lpass_lpaif_base, *sg_ipq_lpass_base; struct resource *res; const struct of_device_id *match; enum ipq_hw_type hw_id; match = of_match_device(qca_avm_match_table, &pdev->dev); if (!match) return -ENODEV; if (!pdev) return -EINVAL; hw_id = (enum ipq_hw_type)match->data; 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 hw_id=%u\n", __func__, readl(ipq_lpass_lpaif_base), hw_id); 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) { dev_err(&pdev->dev, "%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; if (of_property_read_bool(pdev->dev.of_node, "avm,fiq-lpass-disabled")) { plpass_ctrl->fiq_disabled = 1; pr_info("lpass: fiqs disabled via device tree\n"); } if (hw_id == IPQ5018) ret = lpass_pcmipq5018_probe(pdev, plpass_ctrl); else ret = lpass_pcmipq9574_probe(pdev, plpass_ctrl); if (ret) { kfree(plpass_ctrl); return ret; } 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);