| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 Amlogic, Inc. All rights reserved. |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| |
| #include <sound/pcm_params.h> |
| |
| #include <linux/amlogic/pm.h> |
| |
| #include "loopback.h" |
| #include "loopback_hw.h" |
| #include "loopback_match_table.h" |
| #include "ddr_mngr.h" |
| #include "tdm_hw.h" |
| #include "pdm_hw.h" |
| |
| #include "vad.h" |
| #include "pdm.h" |
| #include "tdm.h" |
| |
| #define DRV_NAME "loopback" |
| |
| struct lb_src_table { |
| enum datalb_src src; |
| char *name; |
| }; |
| |
| struct data_src_table { |
| enum datain_src src; |
| char *name; |
| }; |
| |
| struct data_src_table loopback_data_table[DATAIN_MAX] = { |
| {DATAIN_TDMA, "tdmin_a"}, |
| {DATAIN_TDMB, "tdmin_b"}, |
| {DATAIN_TDMC, "tdmin_c"}, |
| {DATAIN_SPDIF, "spdifin"}, |
| {DATAIN_PDM, "pdmin"}, |
| {DATAIN_TDMD, "tdmin_d"}, |
| {DATAIN_PDMB, "pdmin_b"}, |
| }; |
| |
| struct lb_src_table tdmin_lb_src_table[TDMINLB_SRC_MAX] = { |
| {TDMINLB_TDMOUTA, "tdmout_a"}, |
| {TDMINLB_TDMOUTB, "tdmout_b"}, |
| {TDMINLB_TDMOUTC, "tdmout_c"}, |
| {TDMINLB_PAD_TDMINA, "tdmin_a"}, |
| {TDMINLB_PAD_TDMINB, "tdmin_b"}, |
| {TDMINLB_PAD_TDMINC, "tdmin_c"}, |
| {TDMINLB_PAD_TDMINA_D, "tdmind_a"}, |
| {TDMINLB_PAD_TDMINB_D, "tdmind_b"}, |
| {TDMINLB_PAD_TDMINC_D, "tdmind_c"}, |
| {TDMINLB_HDMIRX, "hdmirx"}, |
| {TDMINLB_ACODEC, "acodec_adc"}, |
| {SPDIFINLB_SPDIFOUTA, "spdifout_a"}, |
| {SPDIFINLB_SPDIFOUTB, "spdifout_b"}, |
| {TDMINLB_TDMOUTD, "tdmout_d"}, |
| |
| }; |
| |
| static char *tdmin_lb_src2str(enum datalb_src src) |
| { |
| int i = 0; |
| if (src >= TDMINLB_SRC_MAX) |
| src = TDMINLB_TDMOUTA; |
| for (i = 0; i < TDMINLB_SRC_MAX; i++) { |
| if (tdmin_lb_src_table[i].src == src) |
| break; |
| } |
| if (i >= TDMINLB_SRC_MAX) |
| i = 0; |
| return tdmin_lb_src_table[i].name; |
| } |
| |
| static char *loopback_src_table(enum datain_src src) |
| { |
| int i = 0; |
| if (src >= DATAIN_MAX) |
| src = DATAIN_PDM; |
| for (i = 0; i < DATAIN_MAX; i++) { |
| if (loopback_data_table[i].src == src) |
| break; |
| } |
| if (i >= DATAIN_MAX) |
| i = 0; |
| return loopback_data_table[i].name; |
| } |
| struct loopback { |
| struct device *dev; |
| struct aml_audio_controller *actrl; |
| unsigned int id; |
| |
| /* |
| * datain |
| */ |
| |
| /* PDM clocks */ |
| struct clk *pdm_clk_gate; |
| struct clk *pdm_sysclk_srcpll; |
| struct clk *pdm_dclk_srcpll; |
| struct clk *pdm_sysclk; |
| struct clk *pdm_dclk; |
| unsigned int dclk_idx; |
| |
| /* datain info */ |
| enum datain_src datain_src; |
| unsigned int datain_chnum; |
| unsigned int datain_chmask; |
| unsigned int datain_lane_mask; /* related with data lane */ |
| |
| /* |
| * datalb |
| */ |
| |
| /* TDMIN_LB clocks */ |
| struct clk *tdminlb_mpll; |
| struct clk *tdminlb_mclk; |
| unsigned int mclk_fs_ratio; |
| |
| /* datalb info */ |
| enum datalb_src datalb_src; |
| unsigned int datalb_chnum; |
| unsigned int datalb_chmask; |
| unsigned int datalb_lane_mask; /* related with data lane */ |
| unsigned int lb_format; |
| unsigned int lb_lane_chmask; |
| unsigned int sysclk_freq; |
| |
| struct toddr *tddr; |
| |
| struct loopback_chipinfo *chipinfo; |
| bool vad_running; |
| |
| /* Hibernation for vad, suspended or not */ |
| bool vad_hibernation; |
| /* whether vad buffer is used, for xrun */ |
| bool vad_buf_occupation; |
| bool vad_buf_recovery; |
| |
| void *mic_src; |
| enum trigger_state loopback_trigger_state; |
| }; |
| |
| static struct loopback *loopback_priv[2]; |
| |
| /* this works because the caller resample probe later than loopback */ |
| unsigned int loopback_get_lb_channel(int id) |
| { |
| /* default lb stereo input */ |
| if (id < 0 || id > 1 || !loopback_priv[id]) |
| return 2; |
| |
| return loopback_priv[id]->datalb_chnum; |
| } |
| |
| #define LOOPBACK_BUFFER_BYTES (512 * 1024) |
| #define LOOPBACK_RATES (SNDRV_PCM_RATE_8000_192000) |
| #define LOOPBACK_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ |
| SNDRV_PCM_FMTBIT_S24_LE |\ |
| SNDRV_PCM_FMTBIT_S32_LE) |
| |
| static const struct snd_pcm_hardware loopback_hardware = { |
| .info = |
| SNDRV_PCM_INFO_MMAP | |
| SNDRV_PCM_INFO_MMAP_VALID | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| SNDRV_PCM_INFO_PAUSE, |
| |
| .formats = LOOPBACK_FORMATS, |
| |
| .period_bytes_max = 256 * 64, |
| .buffer_bytes_max = LOOPBACK_BUFFER_BYTES, |
| .period_bytes_min = 64, |
| .periods_min = 1, |
| .periods_max = 1024, |
| |
| .rate_min = 8000, |
| .rate_max = 192000, |
| .channels_min = 1, |
| .channels_max = 32, |
| }; |
| |
| static irqreturn_t loopback_ddr_isr(int irq, void *data) |
| { |
| struct snd_pcm_substream *ss = (struct snd_pcm_substream *)data; |
| struct snd_soc_pcm_runtime *rtd = ss->private_data; |
| struct device *dev = rtd->cpu_dai->dev; |
| struct loopback *p_loopback = (struct loopback *)dev_get_drvdata(dev); |
| unsigned int status; |
| bool vad_running = vad_lb_is_running(p_loopback->id); |
| |
| if (!snd_pcm_running(ss)) |
| return IRQ_NONE; |
| |
| if (p_loopback->vad_running != vad_running) { |
| if (p_loopback->vad_running) |
| snd_pcm_stop_xrun(ss); |
| |
| p_loopback->vad_running = vad_running; |
| } |
| |
| status = aml_toddr_get_status(p_loopback->tddr) & MEMIF_INT_MASK; |
| if (status & MEMIF_INT_COUNT_REPEAT) { |
| snd_pcm_period_elapsed(ss); |
| aml_toddr_ack_irq(p_loopback->tddr, MEMIF_INT_COUNT_REPEAT); |
| } else { |
| dev_dbg(dev, "unexpected irq - STS 0x%02x\n", |
| status); |
| } |
| |
| return !status ? IRQ_NONE : IRQ_HANDLED; |
| } |
| |
| static int loopback_open(struct snd_pcm_substream *ss) |
| { |
| struct snd_pcm_runtime *runtime = ss->runtime; |
| struct snd_soc_pcm_runtime *rtd = ss->private_data; |
| struct device *dev = rtd->cpu_dai->dev; |
| struct loopback *p_loopback = (struct loopback *)dev_get_drvdata(dev); |
| int ret = 0; |
| |
| snd_soc_set_runtime_hwparams(ss, &loopback_hardware); |
| snd_pcm_lib_preallocate_pages(ss, SNDRV_DMA_TYPE_DEV, |
| dev, LOOPBACK_BUFFER_BYTES / 2, LOOPBACK_BUFFER_BYTES); |
| |
| p_loopback->tddr = aml_audio_register_toddr(dev, |
| p_loopback->actrl, |
| loopback_ddr_isr, ss); |
| if (!p_loopback->tddr) { |
| ret = -ENXIO; |
| dev_err(dev, "failed to claim to ddr\n"); |
| goto err_ddr; |
| } |
| |
| runtime->private_data = p_loopback; |
| |
| return 0; |
| |
| err_ddr: |
| snd_pcm_lib_preallocate_free(ss); |
| return ret; |
| } |
| |
| static int loopback_close(struct snd_pcm_substream *ss) |
| { |
| struct snd_pcm_runtime *runtime = ss->runtime; |
| struct loopback *p_loopback = runtime->private_data; |
| |
| aml_audio_unregister_toddr(p_loopback->dev, ss); |
| |
| runtime->private_data = NULL; |
| snd_pcm_lib_preallocate_free(ss); |
| |
| return 0; |
| } |
| |
| static int loopback_hw_params(struct snd_pcm_substream *ss, |
| struct snd_pcm_hw_params *hw_params) |
| { |
| return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); |
| } |
| |
| static int loopback_hw_free(struct snd_pcm_substream *ss) |
| { |
| return snd_pcm_lib_free_pages(ss); |
| } |
| |
| static int loopback_prepare(struct snd_pcm_substream *ss) |
| { |
| struct snd_pcm_runtime *runtime = ss->runtime; |
| struct loopback *p_loopback = runtime->private_data; |
| unsigned int start_addr, end_addr, int_addr; |
| unsigned int period, threshold; |
| struct toddr *to = p_loopback->tddr; |
| |
| if (p_loopback->loopback_trigger_state == TRIGGER_START_ALSA_BUF || |
| p_loopback->loopback_trigger_state == TRIGGER_START_VAD_BUF) { |
| pr_err("%s, trigger state is %d\n", __func__, |
| p_loopback->loopback_trigger_state); |
| return 0; |
| } |
| start_addr = runtime->dma_addr; |
| end_addr = start_addr + runtime->dma_bytes - FIFO_BURST; |
| period = frames_to_bytes(runtime, runtime->period_size); |
| int_addr = period / FIFO_BURST; |
| |
| /* |
| * Contrast minimum of period and fifo depth, |
| * and set the value as half. |
| */ |
| threshold = min(period, to->chipinfo->fifo_depth); |
| threshold /= 2; |
| aml_toddr_set_fifos(to, threshold); |
| |
| aml_toddr_set_buf(to, start_addr, end_addr); |
| aml_toddr_set_intrpt(to, int_addr); |
| |
| return 0; |
| } |
| |
| static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *ss) |
| { |
| struct snd_pcm_runtime *runtime = ss->runtime; |
| struct loopback *p_loopback = runtime->private_data; |
| unsigned int addr, start_addr; |
| snd_pcm_uframes_t frames = 0; |
| |
| start_addr = runtime->dma_addr; |
| addr = aml_toddr_get_position(p_loopback->tddr); |
| |
| frames = bytes_to_frames(runtime, addr - start_addr); |
| if (frames > runtime->buffer_size) |
| frames = 0; |
| |
| return frames; |
| } |
| |
| static int loopback_mmap(struct snd_pcm_substream *substream, |
| struct vm_area_struct *vma) |
| { |
| return snd_pcm_lib_default_mmap(substream, vma); |
| } |
| |
| static struct snd_pcm_ops loopback_ops = { |
| .open = loopback_open, |
| .close = loopback_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = loopback_hw_params, |
| .hw_free = loopback_hw_free, |
| .prepare = loopback_prepare, |
| .pointer = loopback_pointer, |
| .mmap = loopback_mmap, |
| }; |
| |
| static int datain_pdm_startup(struct loopback *p_loopback) |
| { |
| int ret; |
| |
| /* enable clock gate */ |
| ret = clk_prepare_enable(p_loopback->pdm_clk_gate); |
| |
| /* enable clock */ |
| ret = clk_prepare_enable(p_loopback->pdm_sysclk_srcpll); |
| if (ret) { |
| pr_err("Can't enable pcm pdm_sysclk_srcpll clock: %d\n", ret); |
| goto err; |
| } |
| |
| ret = clk_prepare_enable(p_loopback->pdm_dclk_srcpll); |
| if (ret) { |
| pr_err("Can't enable pcm pdm_dclk_srcpll clock: %d\n", ret); |
| goto err; |
| } |
| |
| ret = clk_prepare_enable(p_loopback->pdm_sysclk); |
| if (ret) { |
| pr_err("Can't enable pcm pdm_sysclk clock: %d\n", ret); |
| goto err; |
| } |
| |
| ret = clk_prepare_enable(p_loopback->pdm_dclk); |
| if (ret) { |
| pr_err("Can't enable pcm pdm_dclk clock: %d\n", ret); |
| goto err; |
| } |
| |
| return 0; |
| err: |
| pr_err("failed enable pdm clock\n"); |
| return -EINVAL; |
| } |
| |
| static void datain_pdm_shutdown(struct loopback *p_loopback) |
| { |
| /* disable clock and gate */ |
| clk_disable_unprepare(p_loopback->pdm_dclk); |
| clk_disable_unprepare(p_loopback->pdm_sysclk); |
| clk_disable_unprepare(p_loopback->pdm_sysclk_srcpll); |
| clk_disable_unprepare(p_loopback->pdm_dclk_srcpll); |
| clk_disable_unprepare(p_loopback->pdm_clk_gate); |
| } |
| |
| static int loopback_dai_startup(struct snd_pcm_substream *ss, |
| struct snd_soc_dai *dai) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| int ret = 0; |
| |
| pr_info("%s\n", __func__); |
| |
| /* datain */ |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| case DATAIN_PDMB: |
| ret = datain_pdm_startup(p_loopback); |
| if (ret < 0) |
| goto err; |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| break; |
| } |
| } |
| /* datalb */ |
| if (p_loopback->datalb_chnum > 0) { |
| switch (p_loopback->datalb_src) { |
| case TDMINLB_TDMOUTA ... TDMINLB_PAD_TDMINC_D: |
| /*tdminlb_startup(p_loopback);*/ |
| break; |
| case SPDIFINLB_SPDIFOUTA ... SPDIFINLB_SPDIFOUTB: |
| break; |
| default: |
| break; |
| } |
| } |
| return ret; |
| err: |
| pr_err("Failed to enable datain clock\n"); |
| return ret; |
| } |
| |
| static void loopback_dai_shutdown(struct snd_pcm_substream *ss, |
| struct snd_soc_dai *dai) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| |
| pr_info("%s\n", __func__); |
| |
| /* datain */ |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| case DATAIN_PDMB: |
| datain_pdm_shutdown(p_loopback); |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| break; |
| } |
| } |
| /* datalb */ |
| if (p_loopback->datalb_chnum > 0) { |
| switch (p_loopback->datalb_src) { |
| case TDMINLB_TDMOUTA ... TDMINLB_PAD_TDMINC_D: |
| /*tdminlb_shutdown(p_loopback);*/ |
| break; |
| case SPDIFINLB_SPDIFOUTA ... SPDIFINLB_SPDIFOUTB: |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static void loopback_set_clk(struct loopback *p_loopback, |
| int rate, bool enable) |
| { |
| /* unsigned int mul = 2; */ |
| /* unsigned int mpll_freq, mclk_freq; */ |
| /* assume datain_lb in i2s format, 2ch, 32bit */ |
| unsigned int bit_depth = 32, i2s_ch = 2; |
| unsigned int sclk_div = 4 - 1; |
| unsigned int ratio = i2s_ch * bit_depth - 1; |
| |
| /* lb_datain clk is set |
| * prepare clocks for tdmin_lb |
| */ |
| #ifdef __PTM_TDM_CLK__ |
| ratio = 18 * 2; |
| #endif |
| |
| tdminlb_set_clk(p_loopback->datalb_src, sclk_div, ratio, enable); |
| } |
| |
| static int loopback_set_ctrl(struct loopback *p_loopback, int bitwidth) |
| { |
| unsigned int datain_toddr_type, datalb_toddr_type; |
| unsigned int datain_msb, datain_lsb, datalb_msb, datalb_lsb; |
| struct data_cfg datain_cfg; |
| struct data_cfg datalb_cfg; |
| char *src_str = NULL; |
| struct mux_conf *conf = NULL; |
| |
| if (!p_loopback) |
| return -EINVAL; |
| |
| datain_cfg.ch_ctrl_switch = p_loopback->chipinfo->ch_ctrl; |
| datalb_cfg.ch_ctrl_switch = p_loopback->chipinfo->ch_ctrl; |
| |
| if (p_loopback->datain_chnum > 0) { |
| lb_set_mode(p_loopback->id, MIC_RATE); |
| |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| case DATAIN_PDM: |
| case DATAIN_PDMB: |
| datain_toddr_type = 0; |
| datain_msb = 32 - 1; |
| datain_lsb = 0; |
| break; |
| case DATAIN_SPDIF: |
| datain_toddr_type = 3; |
| datain_msb = 27; |
| datain_lsb = 4; |
| if (bitwidth <= 24) |
| datain_lsb = 28 - bitwidth; |
| else |
| datain_lsb = 4; |
| break; |
| default: |
| pr_err("unsupport data in source:%d\n", |
| p_loopback->datain_src); |
| return -EINVAL; |
| } |
| |
| datain_cfg.ext_signed = 0; |
| datain_cfg.chnum = p_loopback->datain_chnum; |
| datain_cfg.chmask = p_loopback->datain_chmask; |
| datain_cfg.type = datain_toddr_type; |
| datain_cfg.m = datain_msb; |
| datain_cfg.n = datain_lsb; |
| datain_cfg.src = p_loopback->datain_src; |
| datain_cfg.srcs = p_loopback->chipinfo->srcs; |
| |
| lb_set_datain_cfg(p_loopback->id, &datain_cfg); |
| /*p1 add tdm d and pdm b*/ |
| src_str = loopback_src_table(p_loopback->datain_src); |
| conf = p_loopback->chipinfo->srcs; |
| for (; conf->name[0]; conf++) { |
| if (strncmp(conf->name, src_str, strlen(src_str)) == 0) |
| break; |
| } |
| pr_info("data name:%s\n", conf->name); |
| if (p_loopback->chipinfo->multi_bits_lbsrcs == 1) |
| loopback_src_set(p_loopback->id, conf, 1); |
| else |
| loopback_src_set(p_loopback->id, conf, 0); |
| } |
| |
| if (p_loopback->datalb_chnum > 0) { |
| /* if src num is 0, set the lb out rate to lb rate */ |
| if (p_loopback->datain_chnum == 0) |
| lb_set_mode(p_loopback->id, LB_RATE); |
| |
| switch (p_loopback->datalb_src) { |
| case TDMINLB_TDMOUTA ... TDMINLB_PAD_TDMINC_D: |
| if (bitwidth == 24) { |
| datalb_toddr_type = 4; |
| datalb_msb = 32 - 1; |
| datalb_lsb = 32 - bitwidth; |
| } else { |
| datalb_toddr_type = 0; |
| datalb_msb = 32 - 1; |
| datalb_lsb = 0; |
| } |
| break; |
| default: |
| pr_err("unsupport data lb source:%d\n", |
| p_loopback->datalb_src); |
| return -EINVAL; |
| } |
| |
| datalb_cfg.ext_signed = 0; |
| datalb_cfg.chnum = p_loopback->datalb_chnum; |
| datalb_cfg.chmask = p_loopback->datalb_chmask; |
| datalb_cfg.type = datalb_toddr_type; |
| datalb_cfg.m = datalb_msb; |
| datalb_cfg.n = datalb_lsb; |
| datalb_cfg.loopback_src = 0; /* todo: tdmin_LB */ |
| datalb_cfg.tdmin_lb_srcs = p_loopback->chipinfo->tdmin_lb_srcs; |
| /* get resample B status */ |
| datalb_cfg.resample_enable = |
| (unsigned int)get_resample_enable(RESAMPLE_B); |
| lb_set_datalb_cfg(p_loopback->id, |
| &datalb_cfg, |
| p_loopback->chipinfo->multi_bits_lbsrcs); |
| } |
| |
| tdminlb_set_format(p_loopback->lb_format == SND_SOC_DAIFMT_I2S); |
| tdminlb_set_lanemask_and_chswap(0x76543210, |
| p_loopback->datalb_lane_mask, |
| p_loopback->lb_lane_chmask); |
| |
| src_str = tdmin_lb_src2str(p_loopback->datalb_src); |
| conf = p_loopback->chipinfo->tdmin_lb_srcs; |
| for (; conf->name[0]; conf++) { |
| if (strncmp(conf->name, src_str, strlen(src_str)) == 0) |
| break; |
| } |
| tdminlb_set_ctrl(conf->val); |
| |
| return 0; |
| } |
| |
| static void datatin_pdm_cfg(struct snd_pcm_runtime *runtime, |
| struct loopback *p_loopback) |
| { |
| unsigned int bit_depth = snd_pcm_format_width(runtime->format); |
| unsigned int osr; |
| int pdm_id = 0; |
| struct pdm_info info; |
| struct aml_pdm *pdm = (struct aml_pdm *)p_loopback->mic_src; |
| int gain_index = 0; |
| |
| if (pdm) |
| gain_index = pdm->pdm_gain_index; |
| if (pdm && pdm->chipinfo) |
| pdm_id = pdm->chipinfo->id; |
| if (p_loopback->datain_src == DATAIN_PDM) |
| pdm_id = 0; |
| else if (p_loopback->datain_src == DATAIN_PDMB) |
| pdm_id = 1; |
| info.bitdepth = bit_depth; |
| info.channels = p_loopback->datain_chnum; |
| info.lane_masks = p_loopback->datain_lane_mask; |
| info.dclk_idx = p_loopback->dclk_idx; |
| info.bypass = 0; |
| info.sample_count = pdm_get_sample_count(0, p_loopback->dclk_idx); |
| aml_pdm_ctrl(&info, pdm_id); |
| |
| /* filter for pdm */ |
| osr = pdm_get_ors(p_loopback->dclk_idx, runtime->rate); |
| |
| aml_pdm_filter_ctrl(gain_index, osr, 1, pdm_id); |
| } |
| |
| static int loopback_dai_prepare(struct snd_pcm_substream *ss, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_pcm_runtime *runtime = ss->runtime; |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| unsigned int bit_depth = snd_pcm_format_width(runtime->format); |
| |
| struct toddr *to = p_loopback->tddr; |
| unsigned int msb = 32 - 1; |
| unsigned int lsb = 32 - bit_depth; |
| unsigned int toddr_type; |
| struct toddr_fmt fmt; |
| unsigned int src; |
| |
| if (p_loopback->loopback_trigger_state == TRIGGER_START_ALSA_BUF || |
| p_loopback->loopback_trigger_state == TRIGGER_START_VAD_BUF) { |
| pr_err("%s, trigger state is %d\n", __func__, |
| p_loopback->loopback_trigger_state); |
| return 0; |
| } |
| |
| if (p_loopback->id == 0) |
| src = LOOPBACK_A; |
| else |
| src = LOOPBACK_B; |
| |
| pr_info("%s Expected toddr src:%s\n", |
| __func__, |
| toddr_src_get_str(src)); |
| |
| switch (bit_depth) { |
| case 8: |
| case 16: |
| case 32: |
| toddr_type = 0; |
| break; |
| case 24: |
| toddr_type = 4; |
| break; |
| default: |
| pr_err("invalid bit_depth: %d\n", bit_depth); |
| return -EINVAL; |
| } |
| |
| fmt.type = toddr_type; |
| fmt.msb = msb; |
| fmt.lsb = lsb; |
| fmt.endian = 0; |
| fmt.bit_depth = bit_depth; |
| fmt.ch_num = runtime->channels; |
| fmt.rate = runtime->rate; |
| |
| aml_toddr_select_src(to, src); |
| aml_toddr_set_format(to, &fmt); |
| |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| aml_tdmin_set_src(p_loopback->mic_src); |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| case DATAIN_PDMB: |
| datatin_pdm_cfg(runtime, p_loopback); |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| pr_err("loopback unexpected datain src 0x%02x\n", |
| p_loopback->datain_src); |
| return -EINVAL; |
| } |
| } |
| |
| if (p_loopback->datalb_chnum > 0) { |
| switch (p_loopback->datalb_src) { |
| case TDMINLB_TDMOUTA: |
| case TDMINLB_TDMOUTB: |
| case TDMINLB_TDMOUTC: |
| case TDMINLB_TDMOUTD: |
| break; |
| case TDMINLB_PAD_TDMINA: |
| case TDMINLB_PAD_TDMINB: |
| case TDMINLB_PAD_TDMINC: |
| case TDMINLB_PAD_TDMIND: |
| break; |
| case TDMINLB_PAD_TDMINA_D: |
| case TDMINLB_PAD_TDMINB_D: |
| case TDMINLB_PAD_TDMINC_D: |
| break; |
| case SPDIFINLB_SPDIFOUTA: |
| case SPDIFINLB_SPDIFOUTB: |
| break; |
| default: |
| pr_err("loopback unexpected datalb src 0x%02x\n", |
| p_loopback->datalb_src); |
| return -EINVAL; |
| } |
| } |
| /* config for loopback, datain, datalb */ |
| loopback_set_ctrl(p_loopback, bit_depth); |
| |
| return 0; |
| } |
| |
| static void loopback_mic_src_trigger(struct loopback *p_loopback, |
| int stream, |
| bool enable) |
| { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| aml_tdm_trigger(p_loopback->mic_src, |
| stream, enable); |
| break; |
| |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| pdm_enable(enable, 0); |
| break; |
| case DATAIN_PDMB: |
| pdm_enable(enable, 1); |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| dev_err(p_loopback->dev, |
| "unexpected datain src 0x%02x\n", |
| p_loopback->datain_src); |
| } |
| } |
| |
| static void loopback_mic_src_fifo_reset(struct loopback *p_loopback, |
| int stream) |
| { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| aml_tdm_fifo_reset(p_loopback->actrl, |
| stream, p_loopback->datain_src); |
| break; |
| |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| pdm_fifo_reset(0); |
| break; |
| case DATAIN_PDMB: |
| pdm_fifo_reset(1); |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| dev_err(p_loopback->dev, |
| "unexpected datain src 0x%02x\n", |
| p_loopback->datain_src); |
| } |
| } |
| |
| static int loopback_dai_trigger(struct snd_pcm_substream *ss, |
| int cmd, struct snd_soc_dai *dai) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| bool toddr_stopped = false; |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| case SNDRV_PCM_TRIGGER_RESUME: |
| case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| dev_info(ss->pcm->card->dev, "Loopback Capture enable\n"); |
| if (p_loopback->loopback_trigger_state == |
| TRIGGER_START_VAD_BUF) { |
| /* VAD switch to alsa buffer */ |
| vad_update_buffer(false); |
| audio_toddr_irq_enable(p_loopback->tddr, true); |
| } else { |
| if (p_loopback->datain_chnum > 0) |
| loopback_mic_src_fifo_reset(p_loopback, |
| ss->stream); |
| |
| tdminlb_fifo_enable(true); |
| |
| aml_toddr_enable(p_loopback->tddr, true); |
| /* loopback */ |
| if (p_loopback->chipinfo) |
| lb_enable(p_loopback->id, |
| true, |
| p_loopback->chipinfo->chnum_en); |
| else |
| lb_enable(p_loopback->id, true, true); |
| /* tdminLB */ |
| tdminlb_enable(p_loopback->datalb_src, true); |
| /* pdm */ |
| if (p_loopback->datain_chnum > 0) |
| loopback_mic_src_trigger(p_loopback, |
| ss->stream, true); |
| } |
| p_loopback->loopback_trigger_state = TRIGGER_START_ALSA_BUF; |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| case SNDRV_PCM_TRIGGER_SUSPEND: |
| case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| dev_info(ss->pcm->card->dev, "Loopback Capture disable\n"); |
| if (vad_lb_is_running(p_loopback->id) && |
| pm_audio_is_suspend() && |
| p_loopback->loopback_trigger_state == |
| TRIGGER_START_ALSA_BUF) { |
| /* switch to VAD buffer */ |
| vad_update_buffer(true); |
| audio_toddr_irq_enable(p_loopback->tddr, false); |
| p_loopback->loopback_trigger_state = |
| TRIGGER_START_VAD_BUF; |
| break; |
| } |
| if (p_loopback->loopback_trigger_state == TRIGGER_STOP) |
| break; |
| |
| if (p_loopback->datain_chnum > 0) |
| loopback_mic_src_trigger(p_loopback, |
| ss->stream, false); |
| |
| /* loopback */ |
| if (p_loopback->chipinfo) |
| lb_enable(p_loopback->id, |
| false, |
| p_loopback->chipinfo->chnum_en); |
| else |
| lb_enable(p_loopback->id, false, true); |
| /* tdminLB */ |
| tdminlb_fifo_enable(false); |
| tdminlb_enable(p_loopback->datalb_src, false); |
| |
| toddr_stopped = |
| aml_toddr_burst_finished(p_loopback->tddr); |
| if (toddr_stopped) |
| aml_toddr_enable(p_loopback->tddr, false); |
| else |
| pr_err("%s(), toddr may be stuck\n", __func__); |
| p_loopback->loopback_trigger_state = TRIGGER_STOP; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void datain_pdm_set_clk(struct loopback *p_loopback) |
| { |
| unsigned int pdm_sysclk_srcpll_freq, pdm_dclk_srcpll_freq; |
| char *clk_name = NULL; |
| |
| pdm_sysclk_srcpll_freq = clk_get_rate(p_loopback->pdm_sysclk_srcpll); |
| pdm_dclk_srcpll_freq = clk_get_rate(p_loopback->pdm_dclk_srcpll); |
| |
| #ifdef __PTM_PDM_CLK__ |
| clk_set_rate(p_loopback->pdm_sysclk, 133333351); |
| clk_set_rate(p_loopback->pdm_dclk_srcpll, 24576000 * 15); /* 350m */ |
| clk_set_rate(p_loopback->pdm_dclk, 3072000); |
| #else |
| clk_set_rate(p_loopback->pdm_sysclk, 133333351); |
| |
| clk_name = (char *)__clk_get_name(p_loopback->pdm_dclk_srcpll); |
| if (!strcmp(clk_name, "hifipll") || !strcmp(clk_name, "t5_hifi_pll")) { |
| pr_info("hifipll set 1806336*1000\n"); |
| clk_set_rate(p_loopback->pdm_dclk_srcpll, 1806336 * 1000); |
| } else { |
| if (pdm_dclk_srcpll_freq == 0) |
| clk_set_rate(p_loopback->pdm_dclk_srcpll, 24576000); |
| else |
| pr_info("pdm pdm_dclk_srcpll:%lu\n", |
| clk_get_rate(p_loopback->pdm_dclk_srcpll)); |
| } |
| #endif |
| |
| clk_set_rate(p_loopback->pdm_dclk, |
| pdm_dclkidx2rate(p_loopback->dclk_idx)); |
| |
| pr_info("pdm pdm_sysclk:%lu pdm_dclk:%lu\n", |
| clk_get_rate(p_loopback->pdm_sysclk), |
| clk_get_rate(p_loopback->pdm_dclk)); |
| } |
| |
| static int loopback_dai_hw_params(struct snd_pcm_substream *ss, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *dai) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| unsigned int rate, channels; |
| snd_pcm_format_t format; |
| int ret = 0; |
| |
| rate = params_rate(params); |
| channels = params_channels(params); |
| format = params_format(params); |
| |
| pr_debug("%s:rate:%d, sysclk:%d\n", |
| __func__, |
| rate, |
| p_loopback->sysclk_freq); |
| |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| aml_tdm_hw_setting_init(p_loopback->mic_src, |
| rate, p_loopback->datain_chnum, ss->stream); |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| case DATAIN_PDMB: |
| datain_pdm_set_clk(p_loopback); |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* datalb */ |
| if (p_loopback->datalb_chnum > 0) { |
| switch (p_loopback->datalb_src) { |
| case TDMINLB_TDMOUTA ... TDMINLB_PAD_TDMINC_D: |
| /*datalb_tdminlb_set_clk(p_loopback);*/ |
| break; |
| case SPDIFINLB_SPDIFOUTA ... SPDIFINLB_SPDIFOUTB: |
| break; |
| default: |
| break; |
| } |
| } |
| loopback_set_clk(p_loopback, rate, true); |
| |
| return ret; |
| } |
| |
| int loopback_dai_hw_free(struct snd_pcm_substream *ss, |
| struct snd_soc_dai *dai) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| |
| pr_debug("%s\n", __func__); |
| |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| aml_tdm_hw_setting_free(p_loopback->mic_src, ss->stream); |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int loopback_dai_set_fmt(struct snd_soc_dai *dai, |
| unsigned int fmt) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| |
| pr_info("asoc %s, %#x, %p\n", __func__, fmt, p_loopback); |
| |
| return 0; |
| } |
| |
| static int loopback_dai_set_sysclk(struct snd_soc_dai *dai, |
| int clk_id, unsigned int freq, int dir) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| |
| p_loopback->sysclk_freq = freq; |
| pr_info("\n%s, %d, %d, %d\n", |
| __func__, |
| clk_id, freq, dir); |
| |
| return 0; |
| } |
| |
| static int loopback_dai_mute_stream(struct snd_soc_dai *dai, |
| int mute, int stream) |
| { |
| struct loopback *p_loopback = snd_soc_dai_get_drvdata(dai); |
| |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| tdm_mute_capture(p_loopback->mic_src, mute); |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static struct snd_soc_dai_ops loopback_dai_ops = { |
| .startup = loopback_dai_startup, |
| .shutdown = loopback_dai_shutdown, |
| .prepare = loopback_dai_prepare, |
| .trigger = loopback_dai_trigger, |
| .hw_params = loopback_dai_hw_params, |
| .hw_free = loopback_dai_hw_free, |
| .set_fmt = loopback_dai_set_fmt, |
| .set_sysclk = loopback_dai_set_sysclk, |
| .mute_stream = loopback_dai_mute_stream, |
| }; |
| |
| static struct snd_soc_dai_driver loopback_dai[] = { |
| { |
| .name = "LOOPBACK-A", |
| .id = 0, |
| .capture = { |
| .channels_min = 1, |
| .channels_max = 32, |
| .rates = LOOPBACK_RATES, |
| .formats = LOOPBACK_FORMATS, |
| }, |
| .ops = &loopback_dai_ops, |
| }, |
| { |
| .name = "LOOPBACK-B", |
| .id = 1, |
| .capture = { |
| .channels_min = 1, |
| .channels_max = 32, |
| .rates = LOOPBACK_RATES, |
| .formats = LOOPBACK_FORMATS, |
| }, |
| .ops = &loopback_dai_ops, |
| }, |
| }; |
| |
| static const char *const datain_src_texts[] = { |
| "TDMIN_A", |
| "TDMIN_B", |
| "TDMIN_C", |
| "SPDIFIN", |
| "PDMIN", |
| }; |
| |
| static const struct soc_enum datain_src_enum = |
| SOC_ENUM_SINGLE(EE_AUDIO_LB_CTRL0, 0, ARRAY_SIZE(datain_src_texts), |
| datain_src_texts); |
| |
| static int datain_src_get_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct loopback *p_loopback = dev_get_drvdata(component->dev); |
| |
| if (!p_loopback) |
| return 0; |
| |
| ucontrol->value.enumerated.item[0] = p_loopback->datain_src; |
| |
| return 0; |
| } |
| |
| static int datain_src_set_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct loopback *p_loopback = dev_get_drvdata(component->dev); |
| |
| if (!p_loopback) |
| return 0; |
| |
| p_loopback->datain_src = ucontrol->value.enumerated.item[0]; |
| |
| lb_set_datain_src(p_loopback->id, p_loopback->datain_src); |
| |
| return 0; |
| } |
| |
| static const char *const datalb_tdminlb_texts[] = { |
| "TDMOUT_A", |
| "TDMOUT_B", |
| "TDMOUT_C", |
| "TDMIN_A", |
| "TDMIN_B", |
| "TDMIN_C", |
| "TDMIN_A_D", |
| "TDMIN_B_D", |
| "TDMIN_C_D", |
| }; |
| |
| static const struct soc_enum datalb_tdminlb_enum = |
| SOC_ENUM_SINGLE(EE_AUDIO_TDMIN_LB_CTRL, |
| 20, |
| ARRAY_SIZE(datalb_tdminlb_texts), |
| datalb_tdminlb_texts); |
| |
| static int datalb_tdminlb_get_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct loopback *p_loopback = dev_get_drvdata(component->dev); |
| |
| if (!p_loopback) |
| return 0; |
| |
| ucontrol->value.enumerated.item[0] = p_loopback->datalb_src; |
| |
| return 0; |
| } |
| |
| static int datalb_tdminlb_set_enum(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct loopback *p_loopback = dev_get_drvdata(component->dev); |
| |
| if (!p_loopback) |
| return 0; |
| |
| p_loopback->datalb_src = ucontrol->value.enumerated.item[0]; |
| |
| tdminlb_set_src(p_loopback->datalb_src); |
| |
| return 0; |
| } |
| |
| static const struct snd_kcontrol_new snd_loopback_controls[] = { |
| /* loopback data in source */ |
| SOC_ENUM_EXT("Loopback datain source", |
| datain_src_enum, |
| datain_src_get_enum, |
| datain_src_set_enum), |
| |
| /* loopback data tdmin lb */ |
| SOC_ENUM_EXT("Loopback tdmin lb source", |
| datalb_tdminlb_enum, |
| datalb_tdminlb_get_enum, |
| datalb_tdminlb_set_enum), |
| }; |
| |
| static const struct snd_soc_component_driver loopback_component = { |
| .name = DRV_NAME, |
| .ops = &loopback_ops, |
| .controls = snd_loopback_controls, |
| .num_controls = ARRAY_SIZE(snd_loopback_controls), |
| }; |
| |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| static void loopback_platform_early_suspend(struct early_suspend *h) |
| { |
| struct platform_device *pdev = h->param; |
| |
| if (pdev) { |
| struct loopback *p_loopback = dev_get_drvdata(&pdev->dev); |
| |
| if (!p_loopback) |
| return; |
| |
| p_loopback->vad_hibernation = true; |
| } |
| } |
| |
| static void loopback_platform_late_resume(struct early_suspend *h) |
| { |
| struct platform_device *pdev = h->param; |
| |
| if (pdev) { |
| struct loopback *p_loopback = dev_get_drvdata(&pdev->dev); |
| |
| if (!p_loopback) |
| return; |
| |
| p_loopback->vad_hibernation = false; |
| p_loopback->vad_buf_occupation = false; |
| p_loopback->vad_buf_recovery = false; |
| } |
| }; |
| |
| #define loopback_es_hdr(idx) \ |
| static struct early_suspend loopback_es_hdr_##idx = { \ |
| .suspend = loopback_platform_early_suspend, \ |
| .resume = loopback_platform_late_resume, \ |
| } |
| |
| loopback_es_hdr(0); |
| loopback_es_hdr(1); |
| |
| static void loopback_register_early_suspend_hdr(int idx, void *pdev) |
| { |
| struct early_suspend *pes = NULL; |
| |
| if (idx == LOOPBACKA) |
| pes = &loopback_es_hdr_0; |
| else if (idx == LOOPBACKB) |
| pes = &loopback_es_hdr_1; |
| else |
| return; |
| |
| pes->param = pdev; |
| register_early_suspend(pes); |
| } |
| #endif |
| |
| static int datain_pdm_parse_of(struct device *dev, |
| struct loopback *p_loopback) |
| { |
| int ret; |
| |
| /* clock gate */ |
| p_loopback->pdm_clk_gate = devm_clk_get(dev, "pdm_gate"); |
| if (IS_ERR(p_loopback->pdm_clk_gate)) { |
| dev_err(dev, |
| "Can't get pdm gate\n"); |
| return PTR_ERR(p_loopback->pdm_clk_gate); |
| } |
| |
| p_loopback->pdm_sysclk_srcpll = devm_clk_get(dev, "pdm_sysclk_srcpll"); |
| if (IS_ERR(p_loopback->pdm_sysclk_srcpll)) { |
| dev_err(dev, |
| "Can't retrieve pll clock\n"); |
| ret = PTR_ERR(p_loopback->pdm_sysclk_srcpll); |
| goto err; |
| } |
| |
| p_loopback->pdm_dclk_srcpll = devm_clk_get(dev, "pdm_dclk_srcpll"); |
| if (IS_ERR(p_loopback->pdm_dclk_srcpll)) { |
| dev_err(dev, |
| "Can't retrieve data src clock\n"); |
| ret = PTR_ERR(p_loopback->pdm_dclk_srcpll); |
| goto err; |
| } |
| |
| p_loopback->pdm_sysclk = devm_clk_get(dev, "pdm_sysclk"); |
| if (IS_ERR(p_loopback->pdm_sysclk)) { |
| dev_err(dev, |
| "Can't retrieve pdm_sysclk clock\n"); |
| ret = PTR_ERR(p_loopback->pdm_sysclk); |
| goto err; |
| } |
| |
| p_loopback->pdm_dclk = devm_clk_get(dev, "pdm_dclk"); |
| if (IS_ERR(p_loopback->pdm_dclk)) { |
| dev_err(dev, |
| "Can't retrieve pdm_dclk clock\n"); |
| ret = PTR_ERR(p_loopback->pdm_dclk); |
| goto err; |
| } |
| |
| ret = clk_set_parent(p_loopback->pdm_sysclk, |
| p_loopback->pdm_sysclk_srcpll); |
| if (ret) { |
| dev_err(dev, |
| "Can't set pdm_sysclk parent clock\n"); |
| ret = PTR_ERR(p_loopback->pdm_sysclk); |
| goto err; |
| } |
| |
| ret = clk_set_parent(p_loopback->pdm_dclk, |
| p_loopback->pdm_dclk_srcpll); |
| if (ret) { |
| dev_err(dev, |
| "Can't set pdm_dclk parent clock\n"); |
| ret = PTR_ERR(p_loopback->pdm_dclk); |
| goto err; |
| } |
| |
| return 0; |
| |
| err: |
| return ret; |
| } |
| |
| static int datain_parse_of(struct device_node *node, |
| struct loopback *p_loopback) |
| { |
| struct platform_device *pdev; |
| int ret; |
| |
| pdev = of_find_device_by_node(node); |
| if (!pdev) { |
| dev_err(&pdev->dev, |
| "failed to find platform device\n"); |
| return -EINVAL; |
| } |
| |
| if (p_loopback->datain_chnum > 0) { |
| switch (p_loopback->datain_src) { |
| case DATAIN_TDMA: |
| case DATAIN_TDMB: |
| case DATAIN_TDMC: |
| case DATAIN_TDMD: |
| break; |
| case DATAIN_SPDIF: |
| break; |
| case DATAIN_PDM: |
| case DATAIN_PDMB: |
| ret = datain_pdm_parse_of(&pdev->dev, p_loopback); |
| if (ret < 0) |
| goto err; |
| break; |
| case DATAIN_LOOPBACK: |
| break; |
| default: |
| break; |
| } |
| } |
| return 0; |
| err: |
| pr_err("%s, error:%d\n", __func__, ret); |
| return -EINVAL; |
| } |
| |
| static int datalb_tdminlb_parse_of(struct device_node *node, |
| struct loopback *p_loopback) |
| { |
| struct platform_device *pdev; |
| int ret; |
| |
| pdev = of_find_device_by_node(node); |
| if (!pdev) { |
| dev_err(&pdev->dev, |
| "failed to find platform device\n"); |
| return -EINVAL; |
| } |
| |
| /* mpll used for tdmin_lb */ |
| p_loopback->tdminlb_mpll = devm_clk_get(&pdev->dev, "tdminlb_mpll"); |
| if (IS_ERR(p_loopback->tdminlb_mpll)) { |
| dev_err(&pdev->dev, |
| "Can't retrieve tdminlb_mpll clock\n"); |
| return PTR_ERR(p_loopback->tdminlb_mpll); |
| } |
| p_loopback->tdminlb_mclk = devm_clk_get(&pdev->dev, "tdminlb_mclk"); |
| if (IS_ERR(p_loopback->tdminlb_mclk)) { |
| dev_err(&pdev->dev, |
| "Can't retrieve tdminlb_mclk clock\n"); |
| return PTR_ERR(p_loopback->tdminlb_mclk); |
| } |
| |
| ret = clk_set_parent(p_loopback->tdminlb_mclk, |
| p_loopback->tdminlb_mpll); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "Can't set tdminlb_mclk parent clock\n"); |
| ret = PTR_ERR(p_loopback->tdminlb_mpll); |
| goto err; |
| } |
| |
| ret = of_property_read_u32(node, "mclk-fs", |
| &p_loopback->mclk_fs_ratio); |
| if (ret) { |
| pr_warn_once("failed to get mclk-fs node, set it default\n"); |
| p_loopback->mclk_fs_ratio = 256; |
| ret = 0; |
| } |
| |
| return 0; |
| err: |
| pr_err("%s, error:%d\n", __func__, ret); |
| return -EINVAL; |
| } |
| |
| static unsigned int loopback_parse_format(struct device_node *node) |
| { |
| unsigned int format = 0; |
| int ret = 0; |
| const char *str; |
| struct { |
| char *name; |
| unsigned int val; |
| } fmt_table[] = { |
| {"i2s", SND_SOC_DAIFMT_I2S}, |
| {"dsp_a", SND_SOC_DAIFMT_DSP_A}, |
| {"dsp_b", SND_SOC_DAIFMT_DSP_B} |
| }; |
| |
| ret = of_property_read_string(node, "datalb-format", &str); |
| if (ret == 0) { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(fmt_table); i++) { |
| if (strcmp(str, fmt_table[i].name) == 0) { |
| format |= fmt_table[i].val; |
| break; |
| } |
| } |
| } |
| |
| /* default format is I2S */ |
| if (format == 0) |
| format = SND_SOC_DAIFMT_I2S; |
| |
| return format; |
| } |
| |
| static int loopback_parse_of(struct device_node *node, |
| struct loopback *p_loopback) |
| { |
| struct platform_device *pdev; |
| int ret; |
| |
| pdev = of_find_device_by_node(node); |
| if (!pdev) { |
| dev_err(&pdev->dev, "failed to find platform device\n"); |
| ret = -EINVAL; |
| goto fail; |
| } |
| |
| ret = of_property_read_u32(node, "datain_src", |
| &p_loopback->datain_src); |
| if (ret) { |
| pr_err("failed to get datain_src\n"); |
| ret = -EINVAL; |
| goto fail; |
| } |
| ret = of_property_read_u32(node, "datain_chnum", |
| &p_loopback->datain_chnum); |
| if (ret) |
| pr_info("datain_chnum = 0, only record output data\n"); |
| ret = of_property_read_u32(node, "datain_chmask", |
| &p_loopback->datain_chmask); |
| if (ret) { |
| pr_err("failed to get datain_chmask\n"); |
| ret = -EINVAL; |
| goto fail; |
| } |
| ret = snd_soc_of_get_slot_mask(node, "datain-lane-mask-in", |
| &p_loopback->datain_lane_mask); |
| if (ret < 0) { |
| ret = -EINVAL; |
| dev_err(&pdev->dev, "datain lane-slot-mask should be set\n"); |
| goto fail; |
| } |
| |
| ret = of_property_read_u32(node, "datalb_src", |
| &p_loopback->datalb_src); |
| if (ret) { |
| pr_err("failed to get datalb_src\n"); |
| ret = -EINVAL; |
| goto fail; |
| } |
| ret = of_property_read_u32(node, "datalb_chnum", |
| &p_loopback->datalb_chnum); |
| if (ret) { |
| pr_err("failed to get datalb_chnum\n"); |
| ret = -EINVAL; |
| goto fail; |
| } |
| ret = of_property_read_u32(node, "datalb_chmask", |
| &p_loopback->datalb_chmask); |
| if (ret) { |
| pr_err("failed to get datalb_chmask\n"); |
| ret = -EINVAL; |
| goto fail; |
| } |
| |
| ret = snd_soc_of_get_slot_mask(node, "datalb-lane-mask-in", |
| &p_loopback->datalb_lane_mask); |
| if (ret < 0) { |
| ret = -EINVAL; |
| pr_err("datalb lane-slot-mask should be set\n"); |
| goto fail; |
| } |
| |
| p_loopback->lb_format = loopback_parse_format(node); |
| snd_soc_of_get_slot_mask |
| (node, |
| "datalb-channels-mask", |
| &p_loopback->lb_lane_chmask); |
| if (p_loopback->lb_lane_chmask == 0) { |
| /* default format is I2S and mask two channels */ |
| p_loopback->lb_lane_chmask = 0x3; |
| } |
| pr_info("\tdatain_src:%d, datain_chnum:%d, datain_chumask:%x\n", |
| p_loopback->datain_src, |
| p_loopback->datain_chnum, |
| p_loopback->datain_chmask); |
| pr_info("\tdatalb_src:%d, datalb_chnum:%d, datalb_chmask:%x\n", |
| p_loopback->datalb_src, |
| p_loopback->datalb_chnum, |
| p_loopback->datalb_chmask); |
| pr_info("\tdatain_lane_mask:0x%x, datalb_lane_mask:0x%x\n", |
| p_loopback->datain_lane_mask, |
| p_loopback->datalb_lane_mask); |
| pr_info("datalb_format: %d, chmask for lanes: %#x\n", |
| p_loopback->lb_format, p_loopback->lb_lane_chmask); |
| |
| ret = datain_parse_of(node, p_loopback); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "failed to parse datain\n"); |
| return ret; |
| } |
| ret = datalb_tdminlb_parse_of(node, p_loopback); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "failed to parse datalb\n"); |
| return ret; |
| } |
| |
| return 0; |
| |
| fail: |
| return ret; |
| } |
| |
| static int loopback_platform_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *node = pdev->dev.of_node; |
| struct loopback *p_loopback = NULL; |
| struct loopback_chipinfo *p_chipinfo = NULL; |
| struct device_node *node_prt = NULL; |
| struct platform_device *pdev_parent = NULL; |
| struct device_node *np_src = NULL; |
| struct platform_device *dev_src = NULL; |
| struct aml_audio_controller *actrl = NULL; |
| int ret = 0; |
| |
| p_loopback = devm_kzalloc(&pdev->dev, |
| sizeof(struct loopback), |
| GFP_KERNEL); |
| if (!p_loopback) |
| return -ENOMEM; |
| |
| np_src = of_parse_phandle(node, "mic-src", 0); |
| if (np_src) { |
| dev_src = of_find_device_by_node(np_src); |
| of_node_put(np_src); |
| p_loopback->mic_src = platform_get_drvdata(dev_src); |
| pr_debug("%s(), mic_src found\n", __func__); |
| } |
| |
| /* match data */ |
| p_chipinfo = (struct loopback_chipinfo *) |
| of_device_get_match_data(dev); |
| p_loopback->chipinfo = p_chipinfo; |
| p_loopback->id = p_chipinfo->id; |
| |
| loopback_priv[p_loopback->id] = p_loopback; |
| |
| /* 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_loopback->actrl = actrl; |
| |
| ret = loopback_parse_of(dev->of_node, p_loopback); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "failed to parse loopback info\n"); |
| return ret; |
| } |
| |
| p_loopback->dev = &pdev->dev; |
| dev_set_drvdata(&pdev->dev, p_loopback); |
| |
| ret = devm_snd_soc_register_component(&pdev->dev, |
| &loopback_component, |
| &loopback_dai[p_loopback->id], |
| 1); |
| if (ret) { |
| dev_err(&pdev->dev, |
| "snd_soc_register_component failed\n"); |
| return ret; |
| } |
| |
| pr_info("%s, p_loopback->id:%d register soc platform\n", |
| __func__, |
| p_loopback->id); |
| |
| #ifdef CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND |
| loopback_register_early_suspend_hdr(p_loopback->id, pdev); |
| #endif |
| |
| return 0; |
| } |
| |
| static int loopback_platform_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct loopback *p_loopback = dev_get_drvdata(&pdev->dev); |
| |
| pr_info("%s\n", __func__); |
| |
| /* whether in freeze */ |
| if (/* is_pm_freeze_mode() && */vad_lb_is_running(p_loopback->id)) { |
| if (p_loopback->chipinfo) |
| lb_set_chnum_en(p_loopback->id, |
| true, |
| p_loopback->chipinfo->chnum_en); |
| else |
| lb_set_chnum_en(p_loopback->id, true, true); |
| vad_lb_force_two_channel(true); |
| |
| pr_info("%s, Entry in freeze, p_loopback:%p\n", |
| __func__, p_loopback); |
| } |
| |
| return 0; |
| } |
| |
| static int loopback_platform_resume(struct platform_device *pdev) |
| { |
| struct loopback *p_loopback = dev_get_drvdata(&pdev->dev); |
| |
| pr_info("%s\n", __func__); |
| |
| /* whether in freeze mode */ |
| if (/* is_pm_freeze_mode() && */vad_lb_is_running(p_loopback->id)) { |
| pr_info("%s, Exist from freeze, p_loopback:%p\n", |
| __func__, p_loopback); |
| if (p_loopback->chipinfo) |
| lb_set_chnum_en(p_loopback->id, |
| false, |
| p_loopback->chipinfo->chnum_en); |
| else |
| lb_set_chnum_en(p_loopback->id, false, true); |
| vad_lb_force_two_channel(false); |
| } |
| |
| return 0; |
| } |
| |
| static struct platform_driver loopback_platform_driver = { |
| .driver = { |
| .name = DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(loopback_device_id), |
| }, |
| .probe = loopback_platform_probe, |
| .suspend = loopback_platform_suspend, |
| .resume = loopback_platform_resume, |
| }; |
| |
| int __init loopback_init(void) |
| { |
| return platform_driver_register(&(loopback_platform_driver)); |
| } |
| |
| void __exit loopback_exit(void) |
| { |
| platform_driver_unregister(&loopback_platform_driver); |
| } |
| |
| #ifndef MODULE |
| module_init(loopback_init); |
| module_exit(loopback_exit); |
| MODULE_AUTHOR("AMLogic, Inc."); |
| MODULE_DESCRIPTION("Amlogic Loopback driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:" DRV_NAME); |
| #endif |