blob: 5e779a62a9b6248d7429d4afc9140f0c765968c7 [file] [log] [blame]
// 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