| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 Amlogic, Inc. All rights reserved. |
| * |
| */ |
| |
| #define DEBUG |
| #undef pr_fmt |
| #define pr_fmt(fmt) "audio_ddr_mngr: " fmt |
| |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/of_device.h> |
| |
| #include <linux/notifier.h> |
| #include <linux/suspend.h> |
| |
| #include "regs.h" |
| #include "ddr_mngr.h" |
| |
| #include "resample.h" |
| #include "resample_hw.h" |
| #include "effects_hw.h" |
| #include "effects_hw_v2.h" |
| #include "effects_v2.h" |
| #include "pwrdet_hw.h" |
| #include "vad.h" |
| #include "extn.h" |
| #include "frhdmirx_hw.h" |
| #include "earc.h" |
| #include "../common/debug.h" |
| |
| #define DRV_NAME "audio-ddr-manager" |
| |
| static DEFINE_MUTEX(ddr_mutex); |
| |
| static struct frddr frddrs[DDRMAX]; |
| static struct toddr toddrs[DDRMAX]; |
| |
| struct src_enum_table { |
| enum toddr_src src; |
| char *name; |
| }; |
| |
| struct frddr_enum_table { |
| enum frddr_dest dest; |
| char *name; |
| }; |
| |
| struct src_enum_table toddr_src_table[TODDR_SRC_MAX] = { |
| {TDMIN_A, "tdmin_a"}, |
| {TDMIN_B, "tdmin_b"}, |
| {TDMIN_C, "tdmin_c"}, |
| {SPDIFIN, "spdifin"}, |
| {PDMIN, "pdmin"}, |
| {FRATV, "fratv"}, |
| {TDMIN_LB, "tdmin_lb"}, |
| {LOOPBACK_A, "loopback_a"}, |
| {FRHDMIRX, "frhdmirx"}, |
| {LOOPBACK_B, "loopback_b"}, |
| {SPDIFIN_LB, "spdifin_lb"}, |
| {EARCRX_DMAC, "earc_rx_dmac"}, |
| {FRHDMIRX_PAO, "frhdmirx_pao"}, |
| {RESAMPLEA, "resample_a"}, |
| {RESAMPLEB, "resample_b"}, |
| {VAD, "vad"}, |
| {PDMIN_B, "pdmin_b"}, |
| {TDMINB_LB, "tdminb_lb"}, |
| {TDMIN_D, "tdmin_d"}, |
| }; |
| |
| struct frddr_enum_table frddr_src_table[FRDDR_MAX] = { |
| {TDMOUT_A, "tdmout_a"}, |
| {TDMOUT_B, "tdmout_b"}, |
| {TDMOUT_C, "tdmout_c"}, |
| {TDMOUT_D, "tdmout_d"}, |
| {SPDIFOUT_A, "spdif_a"}, |
| {SPDIFOUT_B, "spdif_b"}, |
| {EARCTX_DMAC, "earctx"}, |
| |
| }; |
| /* resample */ |
| static struct toddr_attach attach_resample_a; |
| static struct toddr_attach attach_resample_b; |
| static void aml_check_resample(struct toddr *to, bool enable); |
| |
| /* power detect */ |
| static struct toddr_attach attach_pwrdet; |
| static void aml_check_pwrdet(bool enable); |
| static bool aml_check_pwrdet_module(int src); |
| |
| /* VAD */ |
| static struct toddr_attach attach_vad; |
| static void aml_check_vad(struct toddr *to, bool enable); |
| |
| /* Audio EQ DRC */ |
| static struct frddr_attach attach_aed; |
| |
| static irqreturn_t aml_ddr_isr(int irq, void *devid) |
| { |
| (void)devid; |
| return IRQ_WAKE_THREAD; |
| } |
| |
| /* to DDRS */ |
| static struct toddr *register_toddr_l(struct device *dev, |
| struct aml_audio_controller *actrl, |
| irq_handler_t handler, void *data) |
| { |
| struct toddr *to; |
| unsigned int mask_bit; |
| int i, ret; |
| |
| /* lookup unused toddr */ |
| for (i = 0; i < DDRMAX; i++) { |
| if (!toddrs[i].in_use) |
| break; |
| } |
| |
| if (i >= DDRMAX) |
| return NULL; |
| |
| to = &toddrs[i]; |
| |
| /* irqs request */ |
| ret = request_threaded_irq(to->irq, aml_ddr_isr, handler, |
| IRQF_SHARED, dev_name(dev), data); |
| if (ret) { |
| dev_err(dev, "failed to claim irq %u\n", to->irq); |
| return NULL; |
| } |
| /* enable audio ddr arb */ |
| mask_bit = i; |
| /*aml_audiobus_update_bits(actrl, EE_AUDIO_ARB_CTRL,*/ |
| /* (1 << 31)|(1 << mask_bit),*/ |
| /* (1 << 31)|(1 << mask_bit));*/ |
| |
| to->dev = dev; |
| to->in_use = true; |
| pr_debug("toddrs[%d] registered by device %s\n", i, dev_name(dev)); |
| return to; |
| } |
| |
| static int unregister_toddr_l(struct device *dev, void *data) |
| { |
| struct toddr *to; |
| struct aml_audio_controller *actrl; |
| unsigned int mask_bit; |
| unsigned int value; |
| int i; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (toddrs[i].dev == dev && toddrs[i].in_use) |
| break; |
| } |
| |
| if (i >= DDRMAX) |
| return -EINVAL; |
| |
| to = &toddrs[i]; |
| |
| /* disable audio ddr arb */ |
| mask_bit = i; |
| actrl = to->actrl; |
| /*aml_audiobus_update_bits(actrl, EE_AUDIO_ARB_CTRL,*/ |
| /* 1 << mask_bit, 0 << mask_bit);*/ |
| |
| /* no ddr active, disable arb switch */ |
| value = aml_audiobus_read(actrl, EE_AUDIO_ARB_CTRL) & 0x77; |
| /*if (value == 0)*/ |
| /* aml_audiobus_update_bits(actrl, EE_AUDIO_ARB_CTRL,*/ |
| /* 1 << 31, 0 << 31);*/ |
| |
| free_irq(to->irq, data); |
| to->dev = NULL; |
| to->in_use = false; |
| pr_info("toddrs[%d] released by device %s\n", i, dev_name(dev)); |
| |
| return 0; |
| } |
| |
| int fetch_toddr_index_by_src(int toddr_src) |
| { |
| int i; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (toddrs[i].in_use && toddrs[i].src == toddr_src) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| struct toddr *fetch_toddr_by_src(int toddr_src) |
| { |
| int i; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (toddrs[i].in_use && toddrs[i].src == toddr_src) |
| return &toddrs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| struct toddr *aml_audio_register_toddr(struct device *dev, |
| struct aml_audio_controller *actrl, |
| irq_handler_t handler, void *data) |
| { |
| struct toddr *to = NULL; |
| |
| mutex_lock(&ddr_mutex); |
| to = register_toddr_l(dev, actrl, |
| handler, data); |
| mutex_unlock(&ddr_mutex); |
| return to; |
| } |
| |
| int aml_audio_unregister_toddr(struct device *dev, void *data) |
| { |
| int ret; |
| |
| mutex_lock(&ddr_mutex); |
| ret = unregister_toddr_l(dev, data); |
| mutex_unlock(&ddr_mutex); |
| return ret; |
| } |
| |
| void audio_toddr_irq_enable(struct toddr *to, bool en) |
| { |
| if (!to || !to->in_use || to->irq < 0) |
| return; |
| |
| mutex_lock(&ddr_mutex); |
| if (en) |
| enable_irq(to->irq); |
| else |
| disable_irq_nosync(to->irq); |
| mutex_unlock(&ddr_mutex); |
| } |
| |
| static inline unsigned int |
| calc_toddr_address(unsigned int reg, unsigned int base) |
| { |
| return base + reg - EE_AUDIO_TODDR_A_CTRL0; |
| } |
| |
| int aml_toddr_set_buf(struct toddr *to, unsigned int start, |
| unsigned int end) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| to->start_addr = start; |
| to->end_addr = end; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_START_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, start); |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_FINISH_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, end); |
| |
| /* int address */ |
| if (to->chipinfo && !to->chipinfo->int_start_same_addr) { |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_INIT_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, start); |
| } |
| |
| return 0; |
| } |
| |
| int aml_toddr_set_buf_startaddr(struct toddr *to, unsigned int start) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| to->start_addr = start; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_START_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, start); |
| |
| /* int address */ |
| if (to->chipinfo && !to->chipinfo->int_start_same_addr) { |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_INIT_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, start); |
| } |
| |
| return 0; |
| } |
| |
| int aml_toddr_set_buf_endaddr(struct toddr *to, unsigned int end) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| to->end_addr = end; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_FINISH_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, end); |
| |
| return 0; |
| } |
| |
| int aml_toddr_set_intrpt(struct toddr *to, unsigned int intrpt) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_INT_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, intrpt); |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0xff << 16, 0x4 << 16); |
| |
| return 0; |
| } |
| |
| unsigned int aml_toddr_get_position(struct toddr *to) |
| { |
| return aml_toddr_read_status2(to); |
| } |
| |
| unsigned int aml_toddr_get_addr(struct toddr *to, enum status_sel sel) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg_sel, reg, addr; |
| |
| reg_sel = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg_sel, |
| 0xf << 8, |
| sel << 8); |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_STATUS2, reg_base); |
| addr = aml_audiobus_read(actrl, reg); |
| |
| if (sel == VAD_WAKEUP_ADDR) { |
| /* clear VAD addr/cnt */ |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0x1 << 1, 0x1 << 1); |
| } |
| |
| /* reset to default, current write addr */ |
| aml_audiobus_update_bits(actrl, reg_sel, |
| 0xf << 8, |
| 0x2 << 8); |
| |
| return addr; |
| } |
| |
| void aml_toddr_enable(struct toddr *to, bool enable) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 31, enable << 31); |
| |
| /* check resample */ |
| aml_check_resample(to, enable); |
| |
| if (to->chipinfo && to->chipinfo->wakeup) { |
| if (to->chipinfo->wakeup == 1) { |
| /* check power detect */ |
| if (aml_check_pwrdet_module(to->src)) |
| aml_check_pwrdet(enable); |
| } else if (to->chipinfo->wakeup == 2) |
| /* check VAD */ |
| aml_check_vad(to, enable); |
| } |
| |
| if (!enable) { |
| aml_audiobus_write(actrl, reg, 0x0); |
| /* clear ctrl1 register */ |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| aml_audiobus_write(actrl, reg, 0x0); |
| } |
| } |
| |
| static char *toddr_src2str(enum toddr_src tsrc) |
| { |
| if (tsrc >= TODDR_SRC_MAX) |
| tsrc = TDMIN_A; |
| |
| return toddr_src_table[tsrc].name; |
| } |
| |
| static char *frddr_src2str(enum frddr_dest fsrc) |
| { |
| if (fsrc >= FRDDR_MAX) |
| fsrc = TDMOUT_A; |
| |
| return frddr_src_table[fsrc].name; |
| } |
| |
| void aml_toddr_select_src(struct toddr *to, enum toddr_src src) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| struct toddr_src_conf *conf; |
| char *src_str = toddr_src2str(src); |
| |
| /* store to check toddr num */ |
| to->src = src; |
| |
| conf = to->chipinfo->to_srcs; |
| for (; conf->name[0]; conf++) { |
| if (strncmp(conf->name, src_str, strlen(src_str)) == 0) |
| break; |
| } |
| |
| reg = calc_toddr_address(conf->reg, reg_base); |
| aml_audiobus_update_bits(actrl, reg, |
| conf->mask << conf->shift, |
| conf->val << conf->shift); |
| } |
| |
| void aml_toddr_set_fifos(struct toddr *to, unsigned int threshold) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg, mask, val; |
| |
| if (threshold < FIFO_BURST) { |
| pr_warn("%s, please check threshold:%d less than burst\n", |
| __func__, threshold); |
| threshold = FIFO_BURST; |
| } |
| |
| to->threshold = threshold; |
| |
| /* |
| * the threshold in bytes, register value is: |
| * val = (threshold / burst) - 1 |
| */ |
| threshold /= FIFO_BURST; |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| |
| if (to->chipinfo && to->chipinfo->src_sel_ctrl) { |
| mask = 0xfff << 12 | 0xf << 8; |
| val = (threshold - 1) << 12 | 2 << 8; |
| } else { |
| mask = 0xff << 16 | 0xf << 8; |
| val = (threshold - 1) << 16 | 2 << 8; |
| } |
| |
| aml_audiobus_update_bits(actrl, reg, mask, val); |
| |
| if (to->chipinfo && to->chipinfo->ugt) { |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0x1, 0x1); |
| } |
| } |
| |
| void aml_toddr_force_finish(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 25, 0 << 25); |
| aml_audiobus_update_bits(actrl, reg, 1 << 25, 1 << 25); |
| aml_audiobus_update_bits(actrl, reg, 1 << 25, 0 << 25); |
| } |
| |
| static void aml_toddr_chsync_enable(int fifo_id, int chnum_max, bool enable) |
| { |
| unsigned int reg, offset; |
| |
| offset = EE_AUDIO_TODDR_B_CHSYNC_CTRL - EE_AUDIO_TODDR_A_CHSYNC_CTRL; |
| reg = EE_AUDIO_TODDR_A_CHSYNC_CTRL + offset * fifo_id; |
| |
| if (enable) { |
| audiobus_update_bits(reg, |
| 0xFF << 0, |
| chnum_max << 0); |
| } |
| |
| /* bit 31: enable */ |
| audiobus_update_bits(reg, |
| 0x1 << 31, |
| enable << 31); |
| } |
| |
| void aml_toddr_set_format(struct toddr *to, struct toddr_fmt *fmt) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| to->fmt.type = fmt->type; |
| to->fmt.msb = fmt->msb; |
| to->fmt.lsb = fmt->lsb; |
| to->fmt.endian = fmt->endian; |
| to->fmt.bit_depth = fmt->bit_depth; |
| to->fmt.ch_num = fmt->ch_num; |
| to->fmt.rate = fmt->rate; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, |
| 0x1 << 27 | 0x7 << 24 | 0x1fff << 3, |
| 0x1 << 27 | fmt->endian << 24 | fmt->type << 13 | |
| fmt->msb << 8 | fmt->lsb << 3); |
| |
| /* bit 0-7: chnum_max, same with record channels */ |
| if (to->chipinfo && to->chipinfo->chnum_sync) { |
| bool chsync_enable = true; |
| |
| if (to->src == EARCRX_DMAC && !get_earcrx_chnum_mult_mode() && fmt->ch_num > 2) |
| chsync_enable = false; |
| aml_toddr_chsync_enable(to->fifo_id, |
| fmt->ch_num - 1, |
| chsync_enable); |
| } |
| } |
| |
| unsigned int aml_toddr_get_status(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_STATUS1, reg_base); |
| |
| return aml_audiobus_read(actrl, reg); |
| } |
| |
| unsigned int aml_toddr_get_fifo_cnt(struct toddr *to) |
| { |
| return (aml_toddr_get_status(to) & TODDR_FIFO_CNT) >> 8; |
| } |
| |
| void aml_toddr_ack_irq(struct toddr *to, int status) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| |
| aml_audiobus_update_bits(actrl, reg, MEMIF_INT_MASK, status); |
| aml_audiobus_update_bits(actrl, reg, MEMIF_INT_MASK, 0); |
| } |
| |
| void aml_toddr_insert_chanum(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 24, 1 << 24); |
| } |
| |
| unsigned int aml_toddr_read(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| |
| return aml_audiobus_read(actrl, reg); |
| } |
| |
| void aml_toddr_write(struct toddr *to, unsigned int val) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| |
| aml_audiobus_write(actrl, reg, val); |
| } |
| |
| unsigned int aml_toddr_read1(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| |
| return aml_audiobus_read(actrl, reg); |
| } |
| |
| void aml_toddr_write1(struct toddr *to, unsigned int val) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| |
| aml_audiobus_write(actrl, reg, val); |
| } |
| |
| unsigned int aml_toddr_read_status2(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_STATUS2, reg_base); |
| |
| return aml_audiobus_read(actrl, reg); |
| } |
| |
| static bool aml_toddr_check_status_flag(struct toddr *to) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg, status, arb_status; |
| int i; |
| bool ret = false; |
| |
| /* |
| * reg_stop_ddr; if set from 0 to 1, will: |
| * step1: stop write data to FIFO; |
| * step2: stop sending request to DDR; |
| * step3: keep receiving data from DDR; |
| * step4: compare request count and receive count; |
| * step5: done if two count matched; |
| */ |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL2, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 30, 0 << 30); |
| aml_audiobus_update_bits(actrl, reg, 1 << 30, 1 << 30); |
| |
| /* max 200us delay */ |
| for (i = 0; i < 200; i++) { |
| udelay(1); |
| /* STATUS1 bit 23, stop_ddr_done */ |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_STATUS1, reg_base); |
| status = (aml_audiobus_read(actrl, reg) & 0x800000) >> 23; |
| if (status) { |
| udelay(1); |
| arb_status = aml_audiobus_read(actrl, EE_AUDIO_ARB_STS); |
| |
| pr_debug("toddr stop success, fifo id %d, regbase:0x%x, arb sts:0x%x\n", |
| to->fifo_id, reg_base, arb_status); |
| |
| if (arb_status & 0x80000000) { |
| if (arb_status & (1 << to->fifo_id)) { |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0xff, |
| 0x0); |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0x1 << 29, |
| 0x1 << 29); |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0x1 << 29, |
| 0x0 << 29); |
| pr_info("toddr sts1 0x%x, arb sts 0x%x\n", |
| aml_audiobus_read(actrl, reg), |
| aml_audiobus_read(actrl, EE_AUDIO_ARB_STS)); |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0xff, |
| 0xff); |
| } |
| } |
| ret = true; |
| break; |
| } |
| |
| if ((i % 20) == 0) |
| pr_debug("toddr:delay:[%dus];fifo id %d,reg_base 0x%x,sts1 0x%x,arb sts 0x%x\n", |
| i, to->fifo_id, reg_base, |
| aml_audiobus_read(actrl, reg), |
| aml_audiobus_read(actrl, EE_AUDIO_ARB_STS)); |
| } |
| |
| if (!ret) |
| pr_err("Error: 200us time out, TODDR_STATUS1 bit 23: %u\n", |
| status); |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL2, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 30, 0 << 30); |
| |
| return ret; |
| } |
| |
| static bool aml_toddr_check_fifo_count(struct toddr *to) |
| { |
| unsigned int addr_request, addr_reply, i = 0; |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| bool fifo_stop = false; |
| |
| /* This is a SW workaround. |
| * If not wait until the fifo stops, |
| * DDR will stuck and could not recover unless reboot. |
| */ |
| for (i = 0; i < 10; i++) { |
| unsigned int cnt0, cnt1, cnt2; |
| |
| cnt0 = aml_toddr_get_fifo_cnt(to); |
| udelay(10); |
| cnt1 = aml_toddr_get_fifo_cnt(to); |
| udelay(10); |
| cnt2 = aml_toddr_get_fifo_cnt(to); |
| pr_debug("i: %d, fifo cnt:[%d] cnt1:[%d] cnt2:[%d]\n", |
| i, cnt0, cnt1, cnt2); |
| |
| /* fifo stopped */ |
| if (cnt0 == cnt1 && cnt0 == cnt2 && (cnt0 < (0x40 - 2))) { |
| pr_debug("%s(), i (%d) cnt(%d) break out\n", |
| __func__, i, cnt2); |
| fifo_stop = true; |
| break; |
| } |
| } |
| |
| /* max 200us delay */ |
| for (i = 0; i < 200; i++) { |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0xf << 8, 0x0 << 8); |
| addr_request = aml_toddr_get_position(to); |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0xf << 8, 0x2 << 8); |
| addr_reply = aml_toddr_get_position(to); |
| |
| if (addr_request == addr_reply) { |
| pr_debug("%s(), fifo_stop %d\n", __func__, fifo_stop); |
| return true; |
| } |
| |
| udelay(1); |
| if ((i % 20) == 0) |
| pr_info("delay:[%dus]; FRDDR_STATUS2: [0x%x] [0x%x]\n", |
| i, addr_request, addr_reply); |
| } |
| pr_err("Error: 200us time out, TODDR_STATUS2: [0x%x] [0x%x]\n", |
| addr_request, addr_reply); |
| return false; |
| } |
| |
| bool aml_toddr_burst_finished(struct toddr *to) |
| { |
| if (to->chipinfo->burst_finished_flag) |
| return aml_toddr_check_status_flag(to); |
| else |
| return aml_toddr_check_fifo_count(to); |
| } |
| |
| /* not for tl1 */ |
| static void aml_toddr_set_resample(struct toddr *to, bool enable) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 30, !!enable << 30); |
| } |
| |
| /* tl1 after */ |
| static void aml_toddr_set_resample_ab(struct toddr *to, |
| enum resample_idx index, bool enable) |
| { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL1, reg_base); |
| if (index == RESAMPLE_A) |
| aml_audiobus_update_bits(actrl, reg, 1 << 27, !!enable << 27); |
| |
| } |
| |
| static void aml_resample_enable(struct toddr *to, struct toddr_attach *p_attach_resample) |
| { |
| int bitwidth = 16; |
| bool enable = false; |
| |
| if (!to || !p_attach_resample || !to->chipinfo) { |
| pr_err("%s(), NULL pointer.", __func__); |
| return; |
| } |
| |
| bitwidth = to->fmt.bit_depth; |
| mutex_lock(&p_attach_resample->lock); |
| /* channels and bit depth for resample */ |
| /*&& (to->src == SPDIFIN)*/ |
| if (to->chipinfo && to->chipinfo->asrc_only_left_j && bitwidth == 32) { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg; |
| unsigned int endian, toddr_type; |
| |
| /* TODO: fixed me */ |
| pr_info("Warning: Not support 32bit sample rate for axg chipset\n"); |
| bitwidth = 24; |
| endian = 5; |
| toddr_type = 4; |
| |
| /* FIX ME */ |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, |
| 0x7 << 24 | 0x7 << 13, |
| endian << 24 | toddr_type << 13); |
| } |
| |
| if (p_attach_resample->resample_version >= SM1_RESAMPLE) { |
| if (p_attach_resample->id == RESAMPLE_A) |
| new_resampleA_set_format(p_attach_resample->id, to->fmt.ch_num, bitwidth); |
| |
| if (p_attach_resample->resample_version == SM1_RESAMPLE) { |
| new_resample_src_select(p_attach_resample->id, to->fifo_id); |
| } else { |
| struct toddr_src_conf *conf = NULL; |
| char *src_str = NULL; |
| |
| src_str = toddr_src2str(p_attach_resample->attach_module); |
| conf = to->chipinfo->to_srcs; |
| for (; conf->name[0]; conf++) { |
| if (strncmp(conf->name, src_str, strlen(src_str)) == 0) |
| break; |
| } |
| new_resample_src_select_v2(p_attach_resample->id, conf->val); |
| } |
| } else if (p_attach_resample->resample_version == AXG_RESAMPLE) { |
| /* toddr index for resample */ |
| if (to->chipinfo && to->chipinfo->asrc_src_sel_ctrl) |
| resample_src_select_ab(p_attach_resample->id, to->fifo_id); |
| else |
| resample_src_select(to->fifo_id); |
| |
| resample_format_set(p_attach_resample->id, to->fmt.ch_num, bitwidth); |
| } |
| |
| enable = get_resample_enable(p_attach_resample->id); |
| if (p_attach_resample->resample_version >= T5_RESAMPLE && |
| p_attach_resample->id == RESAMPLE_A) { |
| aml_toddr_select_src(to, RESAMPLEA); |
| } else { |
| /* select reample data */ |
| if (to->chipinfo && to->chipinfo->asrc_src_sel_ctrl) |
| aml_toddr_set_resample_ab(to, p_attach_resample->id, enable); |
| else |
| aml_toddr_set_resample(to, enable); |
| } |
| |
| /* resample enable or disable */ |
| if (p_attach_resample->resample_version >= SM1_RESAMPLE) |
| new_resample_enable(p_attach_resample->id, enable, to->fmt.ch_num); |
| else if (p_attach_resample->resample_version == AXG_RESAMPLE) |
| resample_enable(p_attach_resample->id, enable); |
| mutex_unlock(&p_attach_resample->lock); |
| pr_debug("toddr %d selects data to %s resample_%c for module:%s\n", |
| to->fifo_id, |
| enable ? "enable" : "disable", |
| (p_attach_resample->id == RESAMPLE_A) ? 'a' : 'b', |
| toddr_src_get_str(p_attach_resample->attach_module) |
| ); |
| } |
| |
| void aml_set_resample(enum resample_idx id, |
| bool enable, enum toddr_src resample_module) |
| { |
| struct toddr_attach *p_attach_resample; |
| struct toddr *to; |
| enum toddr_src tosrc = resample_module; |
| |
| if (id == RESAMPLE_A) |
| p_attach_resample = &attach_resample_a; |
| else |
| p_attach_resample = &attach_resample_b; |
| |
| p_attach_resample->enable = enable; |
| p_attach_resample->id = id; |
| p_attach_resample->attach_module = resample_module; |
| p_attach_resample->resample_version = get_resample_version_id(id); |
| |
| mutex_lock(&ddr_mutex); |
| /* toddr src to resample after T5 */ |
| if (p_attach_resample->resample_version >= T5_RESAMPLE && |
| id == RESAMPLE_A) |
| tosrc = RESAMPLEA; |
| |
| to = fetch_toddr_by_src(tosrc); |
| if (!to) { |
| pr_debug("%s(), toddr NULL\n", __func__); |
| goto exit; |
| } |
| |
| if (p_attach_resample->status == RUNNING) |
| aml_resample_enable(to, p_attach_resample); |
| |
| exit: |
| mutex_unlock(&ddr_mutex); |
| } |
| |
| /* |
| * when try to enable resample, if toddr is not in used, |
| * set resample status as ready |
| */ |
| static void aml_check_resample(struct toddr *to, bool enable) |
| { |
| struct toddr_attach *p_attach_resample; |
| int i; |
| |
| p_attach_resample = &attach_resample_a; |
| |
| for (i = 0; i < get_resample_module_num(); i++) { |
| if (to->src == p_attach_resample->attach_module) { |
| /* save toddr status */ |
| if (enable) |
| p_attach_resample->status = RUNNING; |
| else |
| p_attach_resample->status = DISABLED; |
| |
| if (p_attach_resample->resample_version >= T5_RESAMPLE && |
| p_attach_resample->id == RESAMPLE_A) { |
| aml_toddr_select_src(to, RESAMPLEA); |
| } |
| |
| aml_resample_enable(to, p_attach_resample); |
| } |
| p_attach_resample = &attach_resample_b; |
| } |
| } |
| |
| static void aml_set_pwrdet(struct toddr *to, |
| bool enable) |
| { |
| if (enable) { |
| struct aml_audio_controller *actrl = to->actrl; |
| unsigned int reg_base = to->reg_base; |
| unsigned int reg, val; |
| unsigned int toddr_type, msb, lsb; |
| |
| reg = calc_toddr_address(EE_AUDIO_TODDR_A_CTRL0, reg_base); |
| val = aml_audiobus_read(actrl, reg); |
| toddr_type = (val >> 13) & 0x7; |
| msb = (val >> 8) & 0x1f; |
| lsb = (val >> 3) & 0x1f; |
| |
| aml_pwrdet_format_set(toddr_type, msb, lsb); |
| } |
| pwrdet_src_select(enable, to->src); |
| } |
| |
| void aml_pwrdet_enable(bool enable, int pwrdet_module) |
| { |
| attach_pwrdet.enable = enable; |
| attach_pwrdet.attach_module = pwrdet_module; |
| if (enable) { |
| if (attach_pwrdet.status == DISABLED || attach_pwrdet.status == READY) { |
| struct toddr *to = fetch_toddr_by_src(pwrdet_module); |
| |
| if (!to) { |
| attach_pwrdet.status = READY; |
| } else { |
| attach_pwrdet.status = RUNNING; |
| aml_set_pwrdet(to, enable); |
| pr_info("Capture with power detect\n"); |
| } |
| } |
| } else { |
| if (attach_pwrdet.status == RUNNING) { |
| struct toddr *to = fetch_toddr_by_src(pwrdet_module); |
| |
| if (to) |
| aml_set_pwrdet(to, enable); |
| } |
| attach_pwrdet.status = DISABLED; |
| } |
| } |
| |
| static bool aml_check_pwrdet_module(int src) |
| { |
| bool is_module_pwrdet = false; |
| |
| if (attach_pwrdet.enable && src == attach_pwrdet.attach_module) |
| is_module_pwrdet = true; |
| |
| return is_module_pwrdet; |
| } |
| |
| static void aml_check_pwrdet(bool enable) |
| { |
| /* power detect in enable */ |
| if (attach_pwrdet.enable) { |
| if (enable) { |
| /* check whether ready ? */ |
| if (attach_pwrdet.status == READY) |
| aml_pwrdet_enable(true, |
| attach_pwrdet.attach_module); |
| } else { |
| if (attach_pwrdet.status == RUNNING) |
| attach_pwrdet.status = READY; |
| } |
| } |
| } |
| |
| static void aml_vad_enable(struct toddr_attach *p_attach_vad, |
| bool enable) |
| { |
| struct toddr *to = fetch_toddr_by_src(p_attach_vad->attach_module); |
| |
| if (!to) |
| return; |
| |
| vad_set_toddr_info(enable ? to : NULL); |
| |
| /* vad enable or not */ |
| vad_enable(enable); |
| } |
| |
| void aml_set_vad(bool enable, int module) |
| { |
| struct toddr_attach *p_attach_vad = &attach_vad; |
| bool update_running = false; |
| |
| p_attach_vad->enable = enable; |
| p_attach_vad->attach_module = module; |
| |
| if (enable) { |
| if (p_attach_vad->status == DISABLED || p_attach_vad->status == READY) { |
| struct toddr *to = fetch_toddr_by_src(p_attach_vad->attach_module); |
| |
| if (!to) { |
| p_attach_vad->status = READY; |
| } else { |
| p_attach_vad->status = RUNNING; |
| update_running = true; |
| pr_info("Capture with VAD\n"); |
| } |
| } |
| } else { |
| if (p_attach_vad->status == RUNNING) |
| update_running = true; |
| |
| p_attach_vad->status = DISABLED; |
| } |
| |
| if (update_running) |
| aml_vad_enable(p_attach_vad, enable); |
| } |
| |
| /* |
| * when try to enable vad, if toddr is not in used, |
| * set vad status as ready |
| */ |
| static void aml_check_vad(struct toddr *to, bool enable) |
| { |
| struct toddr_attach *p_attach_vad = &attach_vad; |
| bool is_vad = false; |
| |
| if (p_attach_vad->enable && to->src == p_attach_vad->attach_module) |
| is_vad = true; |
| |
| /* vad in enable */ |
| if (is_vad) { |
| if (enable) |
| p_attach_vad->status = RUNNING; |
| else |
| p_attach_vad->status = DISABLED; |
| |
| aml_vad_enable(p_attach_vad, enable); |
| } |
| } |
| |
| /* from DDRS */ |
| static struct frddr *register_frddr_l(struct device *dev, |
| struct aml_audio_controller *actrl, |
| irq_handler_t handler, void *data, bool rvd_dst) |
| { |
| struct frddr *from; |
| unsigned int mask_bit; |
| int i, ret; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| /* lookup reserved frddr */ |
| if (!frddrs[i].in_use && |
| frddrs[i].reserved && |
| rvd_dst) |
| break; |
| /* lookup unused frddr */ |
| if (!frddrs[i].in_use && !frddrs[i].reserved && !rvd_dst) |
| break; |
| } |
| |
| if (i >= DDRMAX) |
| return NULL; |
| |
| from = &frddrs[i]; |
| |
| /* enable audio ddr arb */ |
| mask_bit = i + 4; |
| /*aml_audiobus_update_bits(actrl, EE_AUDIO_ARB_CTRL,*/ |
| /* (1 << 31)|(1 << mask_bit),*/ |
| /* (1 << 31)|(1 << mask_bit));*/ |
| |
| /* irqs request */ |
| ret = request_threaded_irq(from->irq, aml_ddr_isr, handler, |
| IRQF_SHARED, dev_name(dev), data); |
| if (ret) { |
| dev_err(dev, "failed to claim irq %u\n", from->irq); |
| return NULL; |
| } |
| from->dev = dev; |
| from->in_use = true; |
| pr_info("frddrs[%d] registered by device %s\n", i, dev_name(dev)); |
| return from; |
| } |
| |
| static int unregister_frddr_l(struct device *dev, void *data) |
| { |
| struct frddr *from; |
| struct aml_audio_controller *actrl; |
| unsigned int mask_bit; |
| unsigned int value; |
| int i; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (frddrs[i].dev == dev && frddrs[i].in_use) |
| break; |
| } |
| |
| if (i >= DDRMAX) |
| return -EINVAL; |
| |
| from = &frddrs[i]; |
| |
| /* disable audio ddr arb */ |
| mask_bit = i + 4; |
| actrl = from->actrl; |
| /*aml_audiobus_update_bits(actrl, EE_AUDIO_ARB_CTRL,*/ |
| /* 1 << mask_bit, 0 << mask_bit);*/ |
| |
| /* no ddr active, disable arb switch */ |
| value = aml_audiobus_read(actrl, EE_AUDIO_ARB_CTRL) & 0x77; |
| /*if (value == 0)*/ |
| /* aml_audiobus_update_bits(actrl, EE_AUDIO_ARB_CTRL,*/ |
| /* 1 << 31, 0 << 31);*/ |
| |
| free_irq(from->irq, data); |
| from->dev = NULL; |
| from->in_use = false; |
| pr_info("frddrs[%d] released by device %s\n", i, dev_name(dev)); |
| return 0; |
| } |
| |
| int fetch_frddr_index_by_src(int frddr_src) |
| { |
| int i; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (frddrs[i].in_use && frddrs[i].dest == frddr_src) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| struct frddr *fetch_frddr_by_src(int frddr_src) |
| { |
| int i; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (frddrs[i].in_use && frddrs[i].dest == frddr_src) |
| return &frddrs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| struct frddr *aml_audio_register_frddr(struct device *dev, |
| struct aml_audio_controller *actrl, |
| irq_handler_t handler, void *data, bool rvd_dst) |
| { |
| struct frddr *fr = NULL; |
| |
| mutex_lock(&ddr_mutex); |
| fr = register_frddr_l(dev, actrl, handler, data, rvd_dst); |
| mutex_unlock(&ddr_mutex); |
| return fr; |
| } |
| |
| int aml_audio_unregister_frddr(struct device *dev, void *data) |
| { |
| int ret; |
| |
| mutex_lock(&ddr_mutex); |
| ret = unregister_frddr_l(dev, data); |
| mutex_unlock(&ddr_mutex); |
| return ret; |
| } |
| |
| static inline unsigned int |
| calc_frddr_address(unsigned int reg, unsigned int base) |
| { |
| return base + reg - EE_AUDIO_FRDDR_A_CTRL0; |
| } |
| |
| void aml_frddr_select_src(struct frddr *fr, enum frddr_dest src) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| struct toddr_src_conf *conf; |
| char *src_str = frddr_src2str(src); |
| |
| /* store to check toddr num */ |
| |
| conf = fr->chipinfo->fr_srcs; |
| for (; conf->name[0]; conf++) { |
| if (strncmp(conf->name, src_str, strlen(src_str)) == 0) |
| break; |
| } |
| |
| reg = calc_toddr_address(conf->reg, reg_base); |
| aml_audiobus_update_bits(actrl, reg, |
| conf->mask << conf->shift, |
| conf->val << conf->shift); |
| } |
| /* |
| * check frddr_src is used by other frddr for sharebuffer |
| * if used, disabled the other share frddr src, the module would |
| * for current frddr, and the checked frddr |
| */ |
| int aml_check_sharebuffer_valid(struct frddr *fr, int ss_sel) |
| { |
| int current_fifo_id = fr->fifo_id; |
| unsigned int i; |
| int ret = 1; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| if (frddrs[i].in_use && |
| frddrs[i].fifo_id != current_fifo_id && |
| frddrs[i].dest == ss_sel) { |
| ret = 0; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* select dst for same source |
| * lvl: share buffer req_sel 1~2 |
| * lvl 0 is aleardy used for reg_frddr_src_sel1 |
| * lvl 1 is for reg_frddr_src_sel2 |
| * lvl 2 is for reg_frddr_src_sel3 |
| */ |
| static void frddr_set_sharebuffer_enable(struct frddr *fr, int dst, int lvl, bool enable) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| int s_v = 0, s_m = 0; |
| |
| if (fr->chipinfo->src_sel_ctrl) { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, |
| reg_base); |
| |
| switch (lvl) { |
| case 1: |
| s_m = 0x17 << 8; |
| s_v = enable ? |
| (dst << 8 | 1 << 12) : 0 << 8; |
| fr->ss_dest = enable ? dst : 0; |
| fr->ss_en = enable; |
| break; |
| case 2: |
| s_m = 0x17 << 16; |
| s_v = enable ? |
| (dst << 16 | 1 << 20) : 0 << 16; |
| fr->ss2_dest = enable ? dst : 0; |
| fr->ss2_en = enable; |
| break; |
| default: |
| pr_warn_once("share lvl: %d is not supported\n", |
| lvl); |
| break; |
| } |
| } else { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL0, |
| reg_base); |
| |
| switch (lvl) { |
| case 1: |
| s_m = 0xf << 4; |
| s_v = enable ? |
| (dst << 4 | 1 << 7) : 0 << 4; |
| fr->ss_dest = enable ? dst : 0; |
| fr->ss_en = enable; |
| break; |
| case 2: |
| s_m = 0xf << 8; |
| s_v = enable ? |
| (dst << 8 | 1 << 11) : 0 << 8; |
| fr->ss2_dest = enable ? dst : 0; |
| fr->ss2_en = enable; |
| break; |
| default: |
| pr_warn_once("share lvl: %d is not supported\n", |
| lvl); |
| break; |
| } |
| } |
| pr_debug("%s share lvl: %d, dst_src: %d\n", |
| __func__, lvl, dst); |
| |
| aml_audiobus_update_bits(actrl, reg, s_m, s_v); |
| } |
| |
| /* |
| * check frddr_src is used by other frddr for sharebuffer |
| * if used for share frddr src, release from sharebuffer |
| * and used for new frddr |
| */ |
| int aml_check_and_release_sharebuffer(struct frddr *fr, enum frddr_dest ss_sel) |
| { |
| unsigned int i; |
| int ret = -EINVAL; |
| |
| for (i = 0; i < DDRMAX; i++) { |
| struct frddr *from = &frddrs[i]; |
| |
| if (from->in_use && from != fr) { |
| if (from->ss_en && from->ss_dest == ss_sel) { |
| frddr_set_sharebuffer_enable(from, |
| ss_sel, 1, false); |
| |
| if (from->ss2_en) |
| frddr_set_sharebuffer_enable(from, |
| ss_sel, 2, false); |
| |
| pr_debug("%s, release share buffer src:%d\n", |
| __func__, ss_sel); |
| ret = 0; |
| break; |
| } |
| |
| if (from->ss2_en && from->ss2_dest == ss_sel) { |
| frddr_set_sharebuffer_enable(from, |
| ss_sel, 2, false); |
| ret = 0; |
| break; |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| int aml_frddr_set_buf(struct frddr *fr, unsigned int start, |
| unsigned int end) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_START_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, start); |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_FINISH_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, end); |
| |
| /* int address */ |
| if (fr->chipinfo && !fr->chipinfo->int_start_same_addr) { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_INIT_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, start); |
| } |
| |
| return 0; |
| } |
| |
| int aml_frddr_set_intrpt(struct frddr *fr, unsigned int intrpt) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_INT_ADDR, reg_base); |
| aml_audiobus_write(actrl, reg, intrpt); |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0xff << 16, 4 << 16); |
| |
| return 0; |
| } |
| |
| unsigned int aml_frddr_get_position(struct frddr *fr) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_STATUS2, reg_base); |
| return aml_audiobus_read(actrl, reg); |
| } |
| |
| static bool aml_frddr_burst_finished(struct frddr *fr) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg, status, arb_status; |
| int i; |
| bool ret = false; |
| |
| /* |
| * reg_stop_ddr; if set from 0 to 1, will: |
| * step1: stop write data to FIFO; |
| * step2: stop sending request to DDR; |
| * step3: keep receiving data from DDR; |
| * step4: compare request count and receive count; |
| * step5: done if two count matched; |
| */ |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 21, 0 << 21); |
| aml_audiobus_update_bits(actrl, reg, 1 << 21, 1 << 21); |
| |
| /* max 200us delay */ |
| for (i = 0; i < 200; i++) { |
| udelay(1); |
| /* STATUS1 bit 17, stop_ddr_done */ |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_STATUS1, reg_base); |
| status = (aml_audiobus_read(actrl, reg) & 0x20000) >> 17; |
| if (status) { |
| udelay(1); |
| arb_status = aml_audiobus_read(actrl, EE_AUDIO_ARB_STS); |
| |
| pr_debug("frddr stop success, fifo id %d, regbase:0x%x, arb sts:0x%x\n", |
| fr->fifo_id, reg_base, arb_status); |
| |
| if (arb_status & 0x80000000) { |
| if (arb_status & (1 << (fr->fifo_id + 4))) { |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0xff, |
| 0x0); |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0x1 << 29, |
| 0x1 << 29); |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0x1 << 29, |
| 0x0 << 29); |
| pr_info("sts1 0x%x, arb sts 0x%x\n", |
| aml_audiobus_read(actrl, reg), |
| aml_audiobus_read(actrl, EE_AUDIO_ARB_STS)); |
| aml_audiobus_update_bits(actrl, |
| EE_AUDIO_ARB_CTRL, |
| 0xff, |
| 0xff); |
| } |
| } |
| ret = true; |
| break; |
| } |
| |
| if ((i % 20) == 0) |
| pr_debug("frddr:delay:[%dus]; id %d, reg_base 0x%x, sts1 0x%x, arb sts 0x%x\n", |
| i, fr->fifo_id, reg_base, |
| aml_audiobus_read(actrl, reg), |
| aml_audiobus_read(actrl, EE_AUDIO_ARB_STS)); |
| } |
| |
| if (!ret) |
| pr_err("Error: 200us time out, FRDDR_STATUS1 bit 17: %u\n", |
| status); |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 1 << 21, 0 << 21); |
| |
| return ret; |
| } |
| |
| void aml_frddr_enable(struct frddr *fr, bool enable) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg, value; |
| unsigned int reg1, value1; |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL0, reg_base); |
| |
| value = aml_audiobus_read(actrl, reg); |
| if (fr->chipinfo && |
| fr->chipinfo->burst_finished_flag && |
| !enable && |
| (value & 0x80000000)) |
| aml_frddr_burst_finished(fr); |
| |
| if (enable) { |
| /* before enable frddr, must disable src_sel_en */ |
| reg1 = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, reg_base); |
| value1 = aml_audiobus_read(actrl, reg1); |
| aml_audiobus_update_bits(actrl, reg1, |
| 0x1 << 20 | 0x1 << 12 | 0x1 << 4, |
| 0 << 20 | 0 << 12 | 0 << 4); |
| } |
| |
| /* ensure disable before enable frddr */ |
| aml_audiobus_update_bits(actrl, reg, 1 << 31, enable << 31); |
| |
| if (!enable) { |
| aml_audiobus_write(actrl, reg, 0x0); |
| |
| /* clr src sel and its en */ |
| if (fr->chipinfo && fr->chipinfo->src_sel_ctrl) { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, reg_base); |
| aml_audiobus_write(actrl, reg, 0x0); |
| } |
| } else { |
| /* after enable frddr, enable src_sel_en */ |
| aml_audiobus_write(actrl, reg1, value1); |
| } |
| } |
| |
| void aml_frddr_select_dst(struct frddr *fr, enum frddr_dest dst) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg, src_sel_en; |
| |
| fr->dest = dst; |
| |
| if (fr->chipinfo && fr->chipinfo->src_sel_ctrl) { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, reg_base); |
| src_sel_en = 4; |
| /*update frddr channel*/ |
| aml_audiobus_update_bits(actrl, reg, |
| 0xff << 24, (fr->channels - 1) << 24); |
| } else { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL0, reg_base); |
| src_sel_en = 3; |
| } |
| |
| /* if sharebuffer in use, release it */ |
| if (fr->chipinfo && fr->chipinfo->same_src_fn) |
| aml_check_and_release_sharebuffer(fr, dst); |
| |
| aml_audiobus_update_bits(actrl, reg, 0x7, dst & 0x7); |
| |
| /* same source en */ |
| if (fr->chipinfo && fr->chipinfo->same_src_fn) |
| aml_audiobus_update_bits(actrl, reg, 1 << src_sel_en, 1 << src_sel_en); |
| } |
| |
| /* select dst for same source |
| * sel: share buffer req_sel 1~2 |
| * sel 0 is aleardy used for reg_frddr_src_sel1 |
| * sel 1 is for reg_frddr_src_sel2 |
| * sel 2 is for reg_frddr_src_sel3 |
| */ |
| void aml_frddr_select_dst_ss(struct frddr *fr, |
| enum frddr_dest dst, int lvl, bool enable) |
| { |
| unsigned int ss_valid = aml_check_sharebuffer_valid(fr, dst); |
| |
| /* same source en */ |
| if (fr->chipinfo && fr->chipinfo->same_src_fn && ss_valid) |
| frddr_set_sharebuffer_enable(fr, dst, lvl, enable); |
| } |
| |
| void aml_frddr_set_fifos(struct frddr *fr, |
| unsigned int depth, unsigned int threshold) |
| { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| |
| if (depth < FIFO_BURST) { |
| pr_warn("%s, please check depth:%d less than burst\n", |
| __func__, depth); |
| depth = FIFO_BURST; |
| } |
| if (threshold < FIFO_BURST) { |
| pr_warn("%s, please check threshold:%d less than burst\n", |
| __func__, threshold); |
| threshold = FIFO_BURST; |
| } |
| |
| depth /= FIFO_BURST; |
| threshold /= FIFO_BURST; |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, |
| 0xffff << 16 | 0xf << 8, |
| (depth - 1) << 24 | (threshold - 1) << 16 | 2 << 8); |
| |
| if (fr->chipinfo && fr->chipinfo->ugt) { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL0, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0x1, 0x1); |
| } |
| } |
| |
| unsigned int aml_frddr_get_fifo_id(struct frddr *fr) |
| { |
| return fr->fifo_id; |
| } |
| |
| void aml_frddr_set_format(struct frddr *fr, |
| unsigned int chnum, |
| unsigned int rate, |
| unsigned int msb, |
| unsigned int frddr_type) |
| { |
| fr->channels = chnum; |
| fr->rate = rate; |
| fr->msb = msb; |
| fr->type = frddr_type; |
| } |
| |
| static void aml_aed_enable(struct frddr_attach *p_attach_aed, bool enable) |
| { |
| struct frddr *fr = fetch_frddr_by_src(p_attach_aed->attach_module); |
| int aed_version = check_aed_version(); |
| |
| if (aed_version > VERSION1) { |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL2, reg_base); |
| if (enable) { |
| aml_audiobus_update_bits(actrl, |
| reg, 0x1 << 3, enable << 3); |
| if (aed_version > VERSION2) { |
| aed_set_ctrl(enable, 0, |
| p_attach_aed->attach_module, 1); |
| aed_set_format(fr->msb, |
| fr->type, fr->fifo_id, 1); |
| } else { |
| aed_set_ctrl(enable, 0, |
| p_attach_aed->attach_module, 0); |
| aed_set_format(fr->msb, |
| fr->type, fr->fifo_id, 0); |
| } |
| |
| if (aed_version >= VERSION4) |
| aed_reload_config(); |
| |
| aed_enable(enable); |
| } else { |
| aed_enable(enable); |
| if (aed_version > VERSION2) { |
| aed_set_ctrl(enable, 0, |
| p_attach_aed->attach_module, 1); |
| } else { |
| aed_set_ctrl(enable, 0, |
| p_attach_aed->attach_module, 0); |
| } |
| aml_audiobus_update_bits(actrl, |
| reg, 0x1 << 3, enable << 3); |
| } |
| } else if (aed_version == VERSION1) { |
| if (enable) { |
| /* frddr type and bit depth for AED */ |
| aml_aed_format_set(fr->dest); |
| } |
| aed_src_select(enable, fr->dest, fr->fifo_id); |
| } |
| } |
| |
| static bool aml_check_aed_module(int dst) |
| { |
| bool is_module_aed = false; |
| |
| if (attach_aed.enable && dst == attach_aed.attach_module) |
| is_module_aed = true; |
| |
| return is_module_aed; |
| } |
| |
| void aml_set_aed(bool enable, int aed_module) |
| { |
| attach_aed.enable = enable; |
| attach_aed.attach_module = aed_module; |
| } |
| |
| void aml_aed_top_enable(struct frddr *fr, bool enable) |
| { |
| if (aml_check_aed_module(fr->dest)) |
| aml_aed_enable(&attach_aed, enable); |
| } |
| |
| void aml_aed_set_frddr_reserved(void) |
| { |
| frddrs[DDR_A].reserved = true; |
| } |
| |
| void get_toddr_bits_config(enum toddr_src src, |
| int bit_depth, int *msb, int *lsb) |
| { |
| switch (src) { |
| case FRHDMIRX: |
| if (get_hdmirx_mode() == HDMIRX_MODE_PAO) { |
| *msb = 24 - 1; |
| *lsb = (bit_depth > 24) ? 0 : 24 - bit_depth; |
| } else { |
| *msb = 28 - 1; |
| *lsb = (bit_depth <= 24) ? 28 - bit_depth : 4; |
| } |
| break; |
| case SPDIFIN: |
| *msb = 28 - 1; |
| *lsb = (bit_depth <= 24) ? 28 - bit_depth : 4; |
| break; |
| default: |
| *msb = 31; |
| *lsb = 32 - bit_depth; |
| break; |
| } |
| } |
| |
| void aml_frddr_check(struct frddr *fr) |
| { |
| unsigned int tmp, tmp1, i = 0; |
| struct aml_audio_controller *actrl = fr->actrl; |
| unsigned int reg_base = fr->reg_base; |
| unsigned int reg; |
| |
| /*max 200us delay*/ |
| for (i = 0; i < 200; i++) { |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0xf << 8, 0x0 << 8); |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_STATUS2, reg_base); |
| tmp = aml_audiobus_read(actrl, reg); |
| |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_CTRL1, reg_base); |
| aml_audiobus_update_bits(actrl, reg, 0xf << 8, 0x2 << 8); |
| reg = calc_frddr_address(EE_AUDIO_FRDDR_A_STATUS2, reg_base); |
| tmp1 = aml_audiobus_read(actrl, reg); |
| |
| if (tmp == tmp1) |
| return; |
| |
| udelay(1); |
| pr_debug("delay:[%dus]; FRDDR_STATUS2: [0x%x] [0x%x]\n", |
| i, tmp, tmp1); |
| } |
| pr_err("Error: 200us time out, FRDDR_STATUS2: [0x%x] [0x%x]\n", |
| tmp, tmp1); |
| return; |
| } |
| |
| void aml_frddr_reset(struct frddr *fr, int offset) |
| { |
| unsigned int reg = 0, val = 0; |
| |
| if (!fr) { |
| pr_err("%s(), frddr NULL pointer\n", __func__); |
| return; |
| } |
| |
| if (offset && offset != 1) { |
| pr_err("%s(), invalid offset = %d\n", __func__, offset); |
| return; |
| } |
| |
| if (fr->fifo_id == 0) { |
| reg = EE_AUDIO_SW_RESET0(offset); |
| val = REG_BIT_RESET_FRDDRA; |
| } else if (fr->fifo_id == 1) { |
| reg = EE_AUDIO_SW_RESET0(offset); |
| val = REG_BIT_RESET_FRDDRB; |
| } else if (fr->fifo_id == 2) { |
| reg = EE_AUDIO_SW_RESET0(offset); |
| val = REG_BIT_RESET_FRDDRC; |
| } else if (fr->fifo_id == 3) { |
| reg = EE_AUDIO_SW_RESET1(offset); |
| val = REG_BIT_RESET_FRDDRD; |
| } else { |
| pr_err("invalid frddr id %d\n", fr->fifo_id); |
| return; |
| } |
| |
| audiobus_update_bits(reg, val, val); |
| audiobus_update_bits(reg, val, 0); |
| } |
| |
| void frddr_init_without_mngr(unsigned int frddr_index, unsigned int src0_sel) |
| { |
| unsigned int offset, reg; |
| unsigned int start_addr, end_addr, int_addr; |
| static int buf[256]; |
| |
| memset(buf, 0x0, sizeof(buf)); |
| start_addr = virt_to_phys(buf); |
| end_addr = start_addr + sizeof(buf) - 1; |
| int_addr = sizeof(buf) / 64; |
| |
| offset = EE_AUDIO_FRDDR_B_START_ADDR - EE_AUDIO_FRDDR_A_START_ADDR; |
| reg = EE_AUDIO_FRDDR_A_START_ADDR + offset * frddr_index; |
| audiobus_write(reg, start_addr); |
| |
| offset = EE_AUDIO_FRDDR_B_INIT_ADDR - EE_AUDIO_FRDDR_A_INIT_ADDR; |
| reg = EE_AUDIO_FRDDR_A_INIT_ADDR + offset * frddr_index; |
| audiobus_write(reg, start_addr); |
| |
| offset = EE_AUDIO_FRDDR_B_FINISH_ADDR - EE_AUDIO_FRDDR_A_FINISH_ADDR; |
| reg = EE_AUDIO_FRDDR_A_FINISH_ADDR + offset * frddr_index; |
| audiobus_write(reg, end_addr); |
| |
| offset = EE_AUDIO_FRDDR_B_INT_ADDR - EE_AUDIO_FRDDR_A_INT_ADDR; |
| reg = EE_AUDIO_FRDDR_A_INT_ADDR + offset * frddr_index; |
| audiobus_write(reg, int_addr); |
| |
| offset = EE_AUDIO_FRDDR_B_CTRL1 - EE_AUDIO_FRDDR_A_CTRL1; |
| reg = EE_AUDIO_FRDDR_A_CTRL1 + offset * frddr_index; |
| audiobus_write(reg, |
| (0x40 - 1) << 24 | (0x20 - 1) << 16 | 2 << 8 | 0 << 0); |
| |
| offset = EE_AUDIO_FRDDR_B_CTRL0 - EE_AUDIO_FRDDR_A_CTRL0; |
| reg = EE_AUDIO_FRDDR_A_CTRL0 + offset * frddr_index; |
| audiobus_write(reg, |
| 1 << 31 |
| | 0 << 24 |
| | 4 << 16 |
| | 1 << 3 /* src0 enable */ |
| | src0_sel << 0 /* src0 sel */ |
| ); |
| } |
| |
| void frddr_deinit_without_mngr(unsigned int frddr_index) |
| { |
| aml_frddr_enable(frddrs + frddr_index, false); |
| } |
| |
| static enum toddr_src toddr_src_idx = TODDR_INVAL; |
| |
| static const char *const toddr_src_sel_texts[] = { |
| "TDMIN_A", "TDMIN_B", "TDMIN_C", "SPDIFIN", |
| "PDMIN", "FRATV", "TDMIN_LB", "LOOPBACK_A", |
| "FRHDMIRX", "LOOPBACK_B", "SPDIFIN_LB", |
| "EARCRX_DMAC", "RESERVED_0", "RESERVED_1", "RESERVED_2", |
| "VAD" |
| }; |
| |
| static const struct soc_enum toddr_input_source_enum = |
| SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(toddr_src_sel_texts), |
| toddr_src_sel_texts); |
| |
| enum toddr_src toddr_src_get(void) |
| { |
| return toddr_src_idx; |
| } |
| |
| const char *toddr_src_get_str(enum toddr_src idx) |
| { |
| if (idx < TDMIN_A || idx > VAD) |
| return NULL; |
| |
| return toddr_src_sel_texts[idx]; |
| } |
| |
| static int toddr_src_enum_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.enumerated.item[0] = toddr_src_idx; |
| |
| return 0; |
| } |
| |
| static int toddr_src_enum_set(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| toddr_src_idx = ucontrol->value.enumerated.item[0]; |
| /* also update to resample src */ |
| //set_resample_source(toddr_src_idx); |
| |
| return 0; |
| } |
| |
| static int frddr_src_idx = -1; |
| |
| static const char *const frddr_src_sel_texts[] = { |
| "TDMOUT_A", "TDMOUT_B", "TDMOUT_C", |
| "SPDIFOUT_A", "SPDIFOUT_B", "EARCTX_DMAC", "TDMOUT_D" |
| }; |
| |
| static const struct soc_enum frddr_output_source_enum = |
| SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(frddr_src_sel_texts), |
| frddr_src_sel_texts); |
| |
| int frddr_src_get(void) |
| { |
| return frddr_src_idx; |
| } |
| |
| const char *frddr_src_get_str(int idx) |
| { |
| if (idx < 0 || idx >= FRDDR_MAX) |
| return NULL; |
| |
| return frddr_src_sel_texts[idx]; |
| } |
| |
| static int frddr_src_enum_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.enumerated.item[0] = frddr_src_idx; |
| |
| return 0; |
| } |
| |
| static int frddr_src_enum_set(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| frddr_src_idx = ucontrol->value.enumerated.item[0]; |
| |
| return 0; |
| } |
| |
| #ifdef DEBUG_IRQ |
| static int ddr_debug_set(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| set_ddr_debug(ucontrol->value.enumerated.item[0]); |
| return 0; |
| } |
| |
| static int ddr_debug_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.enumerated.item[0] = get_ddr_debug(); |
| return 0; |
| } |
| #endif |
| |
| static const struct snd_kcontrol_new snd_ddr_controls[] = { |
| SOC_ENUM_EXT("Audio In Source", |
| toddr_input_source_enum, |
| toddr_src_enum_get, |
| toddr_src_enum_set), |
| SOC_ENUM_EXT("Audio Out Sink", |
| toddr_input_source_enum, |
| frddr_src_enum_get, |
| frddr_src_enum_set), |
| #ifdef DEBUG_IRQ |
| SOC_SINGLE_BOOL_EXT("Audio DDR DEBUG", |
| 0, |
| ddr_debug_get, |
| ddr_debug_set), |
| #endif |
| }; |
| |
| int card_add_ddr_kcontrols(struct snd_soc_card *card) |
| { |
| unsigned int idx; |
| int err; |
| |
| for (idx = 0; idx < ARRAY_SIZE(snd_ddr_controls); idx++) { |
| err = snd_ctl_add(card->snd_card, |
| snd_ctl_new1(&snd_ddr_controls[idx], |
| NULL)); |
| if (err < 0) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| #define TODDR_SRC_CONFIG(_name, _val, _reg, _shift, _mask) \ |
| { .name = (_name), .val = (_val), .reg = (_reg),\ |
| .shift = (_shift), .mask = (_mask)} |
| |
| struct toddr_src_conf toddr_srcs_v1[] = { |
| TODDR_SRC_CONFIG("tdmin_a", 0, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| TODDR_SRC_CONFIG("tdmin_b", 1, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| TODDR_SRC_CONFIG("tdmin_c", 2, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| TODDR_SRC_CONFIG("spdifin", 3, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| TODDR_SRC_CONFIG("pdmin", 4, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| TODDR_SRC_CONFIG("tdmin_lb", 6, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| TODDR_SRC_CONFIG("loopback_a", 7, EE_AUDIO_TODDR_A_CTRL0, 0, 0x7), |
| { /* sentinel */ } |
| }; |
| |
| struct toddr_src_conf toddr_srcs_v2[] = { |
| TODDR_SRC_CONFIG("tdmin_a", 0, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("tdmin_b", 1, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("tdmin_c", 2, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("spdifin", 3, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("pdmin", 4, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("fratv", 5, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("tdmin_lb", 6, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("loopback_a", 7, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("frhdmirx", 8, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("loopback_b", 9, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("spdifin_lb", 10, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("earc_rx_dmac", 11, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("frhdmi_pao", 12, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| TODDR_SRC_CONFIG("vad", 15, EE_AUDIO_TODDR_A_CTRL1, 28, 0xf), |
| { /* sentinel */ } |
| }; |
| |
| struct toddr_src_conf toddr_srcs_v3[] = { |
| TODDR_SRC_CONFIG("tdmin_a", 0, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("tdmin_b", 1, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("tdmin_c", 2, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("spdifin", 3, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("pdmin", 4, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("fratv", 5, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("tdmin_lb", 6, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("loopback_a", 7, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("frhdmirx", 8, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("earc_rx_dmac", 11, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), /* for t7 earcrx */ |
| TODDR_SRC_CONFIG("frhdmirx_pao", 12, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("resample_a", 13, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("resample_b", 14, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("vad", 15, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("pdmin_b", 16, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("tdminb_lb", 17, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| TODDR_SRC_CONFIG("tdmin_d", 18, EE_AUDIO_TODDR_A_CTRL1, 26, 0x1f), |
| { /* sentinel */ } |
| }; |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| static struct ddr_chipinfo axg_ddr_chipinfo = { |
| .int_start_same_addr = true, |
| .asrc_only_left_j = true, |
| .wakeup = 1, |
| .fifo_num = 3, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .to_srcs = &toddr_srcs_v1[0], |
| }; |
| |
| static struct ddr_chipinfo tl1_ddr_chipinfo = { |
| .same_src_fn = true, |
| .ugt = true, |
| .src_sel_ctrl = true, |
| .asrc_src_sel_ctrl = true, |
| .wakeup = 2, |
| .fifo_num = 4, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .to_srcs = &toddr_srcs_v2[0], |
| }; |
| |
| static struct ddr_chipinfo a1_ddr_chipinfo = { |
| .same_src_fn = true, |
| .src_sel_ctrl = true, |
| .asrc_src_sel_ctrl = true, |
| .wakeup = 2, |
| .fifo_num = 2, |
| .fifo_depth = FIFO_DEPTH_512, |
| .to_srcs = &toddr_srcs_v2[0], |
| }; |
| #endif |
| |
| static struct ddr_chipinfo g12a_ddr_chipinfo = { |
| .same_src_fn = true, |
| .asrc_only_left_j = true, |
| .wakeup = 1, |
| .fifo_num = 3, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .to_srcs = &toddr_srcs_v1[0], |
| }; |
| |
| static struct ddr_chipinfo sm1_ddr_chipinfo = { |
| .same_src_fn = true, |
| .ugt = true, |
| .src_sel_ctrl = true, |
| .asrc_src_sel_ctrl = true, |
| .wakeup = 2, |
| .fifo_num = 4, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .to_srcs = &toddr_srcs_v2[0], |
| }; |
| |
| static struct ddr_chipinfo tm2_revb_ddr_chipinfo = { |
| .same_src_fn = true, |
| .ugt = true, |
| .src_sel_ctrl = true, |
| .asrc_src_sel_ctrl = true, |
| .wakeup = 2, |
| .fifo_num = 4, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .chnum_sync = true, |
| .burst_finished_flag = true, |
| .to_srcs = &toddr_srcs_v2[0], |
| }; |
| |
| static struct ddr_chipinfo t5_ddr_chipinfo = { |
| .same_src_fn = true, |
| .ugt = true, |
| .src_sel_ctrl = true, |
| .asrc_src_sel_ctrl = true, |
| .wakeup = 2, |
| .fifo_num = 4, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .chnum_sync = true, |
| .burst_finished_flag = true, |
| .to_srcs = &toddr_srcs_v3[0], |
| }; |
| |
| static struct ddr_chipinfo p1_ddr_chipinfo = { |
| .same_src_fn = true, |
| .ugt = true, |
| .src_sel_ctrl = true, |
| .asrc_src_sel_ctrl = true, |
| .wakeup = 2, |
| .fifo_num = 4, |
| .fifo_depth = FIFO_DEPTH_1K, |
| .chnum_sync = true, |
| .burst_finished_flag = true, |
| .to_srcs = &toddr_srcs_v3[0], |
| }; |
| static const struct of_device_id aml_ddr_mngr_device_id[] = { |
| #ifndef CONFIG_AMLOGIC_REMOVE_OLD |
| { |
| .compatible = "amlogic, axg-audio-ddr-manager", |
| .data = &axg_ddr_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, tl1-audio-ddr-manager", |
| .data = &tl1_ddr_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, a1-audio-ddr-manager", |
| .data = &a1_ddr_chipinfo, |
| }, |
| #endif |
| { |
| .compatible = "amlogic, g12a-audio-ddr-manager", |
| .data = &g12a_ddr_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, sm1-audio-ddr-manager", |
| .data = &sm1_ddr_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, tm2-revb-audio-ddr-manager", |
| .data = &tm2_revb_ddr_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, t5-audio-ddr-manager", |
| .data = &t5_ddr_chipinfo, |
| }, |
| { |
| .compatible = "amlogic, p1-audio-ddr-manager", |
| .data = &p1_ddr_chipinfo, |
| }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, aml_ddr_mngr_device_id); |
| |
| static bool pm_audio_in_suspend; |
| |
| void pm_audio_set_suspend(bool is_suspend) |
| { |
| pm_audio_in_suspend = is_suspend; |
| } |
| |
| bool pm_audio_is_suspend(void) |
| { |
| return pm_audio_in_suspend; |
| } |
| |
| /* Detects a suspend and resume event */ |
| static int ddr_pm_event(struct notifier_block *notifier, |
| unsigned long pm_event, void *unused) |
| { |
| pr_debug("%s, pm_event:%lu\n", __func__, pm_event); |
| |
| switch (pm_event) { |
| case PM_SUSPEND_PREPARE: |
| pm_audio_set_suspend(true); |
| break; |
| case PM_POST_SUSPEND: |
| pm_audio_set_suspend(false); |
| break; |
| default: |
| break; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block ddr_pm_notifier_block = { |
| .notifier_call = ddr_pm_event, |
| }; |
| |
| /* table Must in order */ |
| static struct ddr_info ddr_info[] = { |
| {EE_AUDIO_TODDR_A_CTRL0, EE_AUDIO_FRDDR_A_CTRL0, "toddr_a", "frddr_a"}, |
| {EE_AUDIO_TODDR_B_CTRL0, EE_AUDIO_FRDDR_B_CTRL0, "toddr_b", "frddr_b"}, |
| {EE_AUDIO_TODDR_C_CTRL0, EE_AUDIO_FRDDR_C_CTRL0, "toddr_c", "frddr_c"}, |
| {EE_AUDIO_TODDR_D_CTRL0, EE_AUDIO_FRDDR_D_CTRL0, "toddr_d", "frddr_d"}, |
| }; |
| |
| static int ddr_get_toddr_base_addr_by_idx(int idx) |
| { |
| return ddr_info[idx].toddr_addr; |
| } |
| |
| static int ddr_get_frddr_base_addr_by_idx(int idx) |
| { |
| return ddr_info[idx].frddr_addr; |
| } |
| |
| static char *ddr_get_toddr_name_by_idx(int idx) |
| { |
| return ddr_info[idx].toddr_name; |
| } |
| |
| static char *ddr_get_frddr_name_by_idx(int idx) |
| { |
| return ddr_info[idx].frddr_name; |
| } |
| |
| static int aml_ddr_mngr_platform_probe(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct device_node *node_prt = NULL; |
| struct platform_device *pdev_parent; |
| struct aml_audio_controller *actrl = NULL; |
| struct ddr_chipinfo *p_ddr_chipinfo; |
| int ddr_num = 3; /* early chipset support max 3 ddr num */ |
| int i, ret; |
| |
| /* get audio controller */ |
| node_prt = of_get_parent(node); |
| if (!node_prt) |
| return -ENXIO; |
| |
| pdev_parent = of_find_device_by_node(node_prt); |
| of_node_put(node_prt); |
| actrl = (struct aml_audio_controller *) |
| platform_get_drvdata(pdev_parent); |
| |
| p_ddr_chipinfo = (struct ddr_chipinfo *) |
| of_device_get_match_data(&pdev->dev); |
| if (!p_ddr_chipinfo) { |
| dev_err(&pdev->dev, |
| "check to update ddr_mngr chipinfo\n"); |
| return -EINVAL; |
| } |
| |
| if (p_ddr_chipinfo->fifo_num == 2) |
| ddr_num = p_ddr_chipinfo->fifo_num; |
| else if (p_ddr_chipinfo->fifo_num == 4) |
| ddr_num = p_ddr_chipinfo->fifo_num; |
| |
| for (i = 0; i < ddr_num; i++) { |
| toddrs[i].irq = |
| platform_get_irq_byname(pdev, |
| ddr_get_toddr_name_by_idx(i)); |
| toddrs[i].reg_base = ddr_get_toddr_base_addr_by_idx(i); |
| toddrs[i].fifo_id = i; |
| toddrs[i].chipinfo = p_ddr_chipinfo; |
| toddrs[i].actrl = actrl; |
| |
| frddrs[i].irq = |
| platform_get_irq_byname(pdev, |
| ddr_get_frddr_name_by_idx(i)); |
| frddrs[i].reg_base = ddr_get_frddr_base_addr_by_idx(i); |
| frddrs[i].fifo_id = i; |
| frddrs[i].chipinfo = p_ddr_chipinfo; |
| frddrs[i].actrl = actrl; |
| |
| dev_info(&pdev->dev, "%d, irqs toddr %d, frddr %d\n", |
| i, toddrs[i].irq, frddrs[i].irq); |
| |
| if (toddrs[i].irq <= 0 || frddrs[i].irq <= 0) { |
| dev_err(&pdev->dev, "%s, get irq failed\n", __func__); |
| return -ENXIO; |
| } |
| } |
| mutex_init(&attach_resample_a.lock); |
| mutex_init(&attach_resample_b.lock); |
| |
| ret = register_pm_notifier(&ddr_pm_notifier_block); |
| if (ret) |
| pr_debug("[%s] failed to register PM notifier %d\n", |
| __func__, ret); |
| |
| return 0; |
| } |
| |
| struct platform_driver aml_audio_ddr_manager = { |
| .driver = { |
| .name = DRV_NAME, |
| .of_match_table = aml_ddr_mngr_device_id, |
| }, |
| .probe = aml_ddr_mngr_platform_probe, |
| }; |
| |
| int __init audio_ddr_init(void) |
| { |
| return platform_driver_register(&(aml_audio_ddr_manager)); |
| } |
| |
| void __exit audio_ddr_exit(void) |
| { |
| platform_driver_unregister(&aml_audio_ddr_manager); |
| } |
| |
| #ifndef MODULE |
| module_init(audio_ddr_init); |
| module_exit(audio_ddr_exit); |
| /* Module information */ |
| MODULE_AUTHOR("Amlogic, Inc."); |
| MODULE_DESCRIPTION("ALSA Soc Aml Audio DDR Manager"); |
| MODULE_LICENSE("GPL v2"); |
| #endif |