TERES/SOFTWARE/A64-TERES/linux-a64/sound/soc/sunxi/sunxi_tdm_utils.c
Dimitar Gamishev f9b0e7a283 linux
2017-10-13 14:07:04 +03:00

585 lines
15 KiB
C
Executable File

/*
* sound\soc\sunxi\sunxi_tdm_utils.c
* (C) Copyright 2014-2016
* Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com>
* huangxin <huangxin@Reuuimllatech.com>
* Liu shaohua <liushaohua@allwinnertech.com>
*
* some simple description for this code
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/jiffies.h>
#include <linux/io.h>
#include <linux/pinctrl/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <asm/dma.h>
#include <linux/gpio.h>
#include "sunxi_dma.h"
#include "sunxi_tdm_utils.h"
static bool daudio0_loop_en = false;
int txctrl_tdm(int on,int hub_en,struct sunxi_tdm_info *sunxi_tdm)
{
u32 reg_val;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
/*flush TX FIFO*/
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val |= SUNXI_DAUDIOFCTL_FTX;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
/*clear TX counter*/
writel(0, tdm->regs + SUNXI_DAUDIOTXCNT);
if (on) {
/* enable DMA DRQ mode for play */
reg_val = readl(tdm->regs + SUNXI_DAUDIOINT);
reg_val |= SUNXI_DAUDIOINT_TXDRQEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOINT);
} else {
/* DISBALE dma DRQ mode */
reg_val = readl(tdm->regs + SUNXI_DAUDIOINT);
reg_val &= ~SUNXI_DAUDIOINT_TXDRQEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOINT);
/*DISABLE TXEN*/
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val &= ~SUNXI_DAUDIOCTL_TXEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
}
if (hub_en) {
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val |= SUNXI_DAUDIOFCTL_HUBEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
} else {
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val &= ~SUNXI_DAUDIOFCTL_HUBEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
}
return 0;
}
EXPORT_SYMBOL(txctrl_tdm);
int rxctrl_tdm(int on,struct sunxi_tdm_info *sunxi_tdm)
{
u32 reg_val;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
if (on) {
/* enable DMA DRQ mode for record */
reg_val = readl(tdm->regs + SUNXI_DAUDIOINT);
reg_val |= SUNXI_DAUDIOINT_RXDRQEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOINT);
} else {
/*DISABLE DAUDIO RX */
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val &= ~SUNXI_DAUDIOCTL_RXEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
/* DISBALE dma DRQ mode */
reg_val = readl(tdm->regs + SUNXI_DAUDIOINT);
reg_val &= ~SUNXI_DAUDIOINT_RXDRQEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOINT);
/*flush RX FIFO*/
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val |= SUNXI_DAUDIOFCTL_FRX;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
/*clear RX counter*/
writel(0, tdm->regs + SUNXI_DAUDIORXCNT);
#ifdef CONFIG_ARCH_SUN50I
/*
* while flush RX FIFO, must read RXFIFO DATA three times.
* or it wouldn't flush RX FIFO clean; and it will let record data channel reverse!
*/
{
int i = 0;
for (i = 0; i < 9;i++) {
reg_val = readl(tdm->regs + SUNXI_DAUDIORXFIFO);
}
}
#endif
}
return 0;
}
EXPORT_SYMBOL(rxctrl_tdm);
int tdm_set_fmt(unsigned int fmt,struct sunxi_tdm_info *sunxi_tdm)
{
u32 reg_val = 0;
u32 reg_val1 = 0;
u32 reg_val2 = 0;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
/* master or slave selection */
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
switch(fmt & SND_SOC_DAIFMT_MASTER_MASK){
case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master, ap is slave*/
reg_val &= ~SUNXI_DAUDIOCTL_LRCKOUT;
reg_val &= ~SUNXI_DAUDIOCTL_BCLKOUT;
break;
case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave,ap is master*/
reg_val |= SUNXI_DAUDIOCTL_LRCKOUT;
reg_val |= SUNXI_DAUDIOCTL_BCLKOUT;
break;
default:
pr_err("unknwon master/slave format\n");
return -EINVAL;
}
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
/* pcm or tdm mode selection */
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val1 = readl(tdm->regs + SUNXI_DAUDIOTX0CHSEL);
reg_val2 = readl(tdm->regs + SUNXI_DAUDIORXCHSEL);
reg_val &= ~SUNXI_DAUDIOCTL_MODESEL;
reg_val1 &= ~(SUNXI_DAUDIOTXn_OFFSET(3));
reg_val2 &= ~(SUNXI_DAUDIORXCHSEL_RXOFFSET(3));
switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S: /* i2s mode */
reg_val |= (1<<4);
reg_val1 |= SUNXI_DAUDIOTXn_OFFSET(1);
reg_val2 |= SUNXI_DAUDIORXCHSEL_RXOFFSET(1);
break;
case SND_SOC_DAIFMT_RIGHT_J: /* Right Justified mode */
reg_val |= (2<<4);
break;
case SND_SOC_DAIFMT_LEFT_J: /* Left Justified mode */
reg_val |= (1<<4);
reg_val1 |= SUNXI_DAUDIOTXn_OFFSET(0);
reg_val2 |= SUNXI_DAUDIORXCHSEL_RXOFFSET(0);
break;
case SND_SOC_DAIFMT_DSP_A: /* L data msb after FRM LRC */
reg_val |= (0<<4);
break;
case SND_SOC_DAIFMT_DSP_B: /* L data msb during FRM LRC */
reg_val |= (0<<4);
break;
default:
return -EINVAL;
}
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
writel(reg_val1, tdm->regs + SUNXI_DAUDIOTX0CHSEL);
writel(reg_val2, tdm->regs + SUNXI_DAUDIORXCHSEL);
/* DAI signal inversions */
reg_val1 = readl(tdm->regs + SUNXI_DAUDIOFAT0);
switch(fmt & SND_SOC_DAIFMT_INV_MASK){
case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
reg_val1 &= ~SUNXI_DAUDIOFAT0_BCLK_POLAYITY;
reg_val1 &= ~SUNXI_DAUDIOFAT0_LRCK_POLAYITY;
break;
case SND_SOC_DAIFMT_NB_IF: /* normal bclk + inv frm */
reg_val1 |= SUNXI_DAUDIOFAT0_LRCK_POLAYITY;
reg_val1 &= ~SUNXI_DAUDIOFAT0_BCLK_POLAYITY;
break;
case SND_SOC_DAIFMT_IB_NF: /* invert bclk + nor frm */
reg_val1 &= ~SUNXI_DAUDIOFAT0_LRCK_POLAYITY;
reg_val1 |= SUNXI_DAUDIOFAT0_BCLK_POLAYITY;
break;
case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */
reg_val1 |= SUNXI_DAUDIOFAT0_LRCK_POLAYITY;
reg_val1 |= SUNXI_DAUDIOFAT0_BCLK_POLAYITY;
break;
}
writel(reg_val1, tdm->regs + SUNXI_DAUDIOFAT0);
return 0;
}
EXPORT_SYMBOL(tdm_set_fmt);
int tdm_hw_params(struct snd_pcm_hw_params *params,struct sunxi_tdm_info *sunxi_tdm)
{
u32 reg_val = 0;
//u32 sample_resolution = 0;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
switch (params_format(params))
{
case SNDRV_PCM_FORMAT_S16_LE:
tdm->samp_res = 16;
break;
case SNDRV_PCM_FORMAT_S20_3LE:
tdm->samp_res = 24;
break;
case SNDRV_PCM_FORMAT_S24_LE:
tdm->samp_res = 24;
break;
case SNDRV_PCM_FORMAT_S32_LE:
tdm->samp_res = 24;
break;
default:
return -EINVAL;
}
reg_val = readl(tdm->regs + SUNXI_DAUDIOFAT0);
reg_val &= ~SUNXI_DAUDIOFAT0_SR;
if(tdm->samp_res == 16)
reg_val |= (3<<4);
else if(tdm->samp_res == 20)
reg_val |= (4<<4);
else
reg_val |= (5<<4);
writel(reg_val, tdm->regs + SUNXI_DAUDIOFAT0);
if (tdm->samp_res == 24) {
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val &= ~SUNXI_DAUDIOFCTL_TXIM;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
} else {
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val |= SUNXI_DAUDIOFCTL_TXIM;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
}
return 0;
}
EXPORT_SYMBOL(tdm_hw_params);
int tdm_trigger(struct snd_pcm_substream *substream,int cmd, struct sunxi_tdm_info *sunxi_tdm)
{
s32 ret = 0;
u32 reg_val = 0;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
rxctrl_tdm(1,tdm);
} else {
txctrl_tdm(1,0,tdm);
}
if (daudio0_loop_en) {
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val |= SUNXI_DAUDIOCTL_LOOP; /*for test*/
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
rxctrl_tdm(0,tdm);
} else {
txctrl_tdm(0,0,tdm);
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
EXPORT_SYMBOL(tdm_trigger);
module_param_named(daudio0_loop_en, daudio0_loop_en, bool, S_IRUGO | S_IWUSR);
int tdm_set_sysclk(unsigned int freq,struct sunxi_tdm_info *sunxi_tdm)
{
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
if (clk_set_rate(tdm->tdm_pllclk, freq)) {
pr_err("try to set the tdm_pll2clk failed!\n");
}
return 0;
}
EXPORT_SYMBOL(tdm_set_sysclk);
int tdm_set_clkdiv(int sample_rate,struct sunxi_tdm_info *sunxi_tdm)
{
u32 reg_val = 0;
u32 mclk_div = 0;
u32 bclk_div = 0;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
reg_val = readl(tdm->regs + SUNXI_DAUDIOCLKD);
/*i2s mode*/
if (tdm->tdm_config) {
switch (sample_rate) {
case 192000:
case 96000:
case 48000:
case 32000:
case 24000:
case 12000:
case 16000:
case 8000:
bclk_div = ((24576000/sample_rate)/(2*tdm->pcm_lrck_period));
mclk_div = 1;
break;
default:
bclk_div = ((22579200/sample_rate)/(2*tdm->pcm_lrck_period));
mclk_div = 1;
break;
}
} else {/*pcm mode*/
bclk_div = ((24576000/sample_rate)/(tdm->pcm_lrck_period));
mclk_div = 1;
}
switch(mclk_div)
{
case 1: mclk_div = 1;
break;
case 2: mclk_div = 2;
break;
case 4: mclk_div = 3;
break;
case 6: mclk_div = 4;
break;
case 8: mclk_div = 5;
break;
case 12: mclk_div = 6;
break;
case 16: mclk_div = 7;
break;
case 24: mclk_div = 8;
break;
case 32: mclk_div = 9;
break;
case 48: mclk_div = 10;
break;
case 64: mclk_div = 11;
break;
case 96: mclk_div = 12;
break;
case 128: mclk_div = 13;
break;
case 176: mclk_div = 14;
break;
case 192: mclk_div = 15;
break;
}
reg_val &= ~(0xf<<0);
reg_val |= mclk_div<<0;
switch(bclk_div)
{
case 1: bclk_div = 1;
break;
case 2: bclk_div = 2;
break;
case 4: bclk_div = 3;
break;
case 6: bclk_div = 4;
break;
case 8: bclk_div = 5;
break;
case 12: bclk_div = 6;
break;
case 16: bclk_div = 7;
break;
case 24: bclk_div = 8;
break;
case 32: bclk_div = 9;
break;
case 48: bclk_div = 10;
break;
case 64: bclk_div = 11;
break;
case 96: bclk_div = 12;
break;
case 128: bclk_div = 13;
break;
case 176: bclk_div = 14;
break;
case 192:bclk_div = 15;
}
reg_val &= ~(0xf<<4);
reg_val |= bclk_div<<4;
writel(reg_val, tdm->regs + SUNXI_DAUDIOCLKD);
reg_val = readl(tdm->regs + SUNXI_DAUDIOFAT0);
reg_val &= ~(0x3ff<<20);
reg_val &= ~(0x3ff<<8);
reg_val |= (tdm->pcm_lrck_period-1)<<8;
reg_val |= (tdm->pcm_lrckr_period-1)<<20;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFAT0);
reg_val = readl(tdm->regs + SUNXI_DAUDIOFAT0);
reg_val &= ~SUNXI_DAUDIOFAT0_SW;
if(tdm->slot_width_select == 16)
reg_val |= (3<<0);
else if(tdm->slot_width_select == 20)
reg_val |= (4<<0);
else if(tdm->slot_width_select == 24)
reg_val |= (5<<0);
else if(tdm->slot_width_select == 28)
reg_val |= (6<<0);
else
reg_val |= (7<<0);
/*pcm mode
* (Only apply in PCM mode) LRCK width
* 0: LRCK = 1 BCLK width(short frame)
* 1: LRCK = 2 BCLK width(long frame)
*/
if(tdm->frametype)
reg_val |= SUNXI_DAUDIOFAT0_LRCK_WIDTH;
else
reg_val &= ~SUNXI_DAUDIOFAT0_LRCK_WIDTH;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFAT0);
reg_val = readl(tdm->regs + SUNXI_DAUDIOFAT1);
reg_val |= tdm->pcm_lsb_first<<7;
reg_val |= tdm->pcm_lsb_first<<6;
/*linear or u/a-law*/
reg_val &= ~(0xf<<0);
reg_val |= (tdm->tx_data_mode)<<2;
reg_val |= (tdm->rx_data_mode)<<0;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFAT1);
return 0;
}
EXPORT_SYMBOL(tdm_set_clkdiv);
int tdm_perpare(struct snd_pcm_substream *substream,
struct sunxi_tdm_info *sunxi_tdm)
{
u32 reg_val;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL)
tdm = sunxi_tdm;
else
return -1;
/* play or record */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
reg_val = readl(tdm->regs + SUNXI_CHCFG);
reg_val &= ~(SUNXI_TXCHCFG_TX_SLOT_NUM<<0);
reg_val |= (substream->runtime->channels-1)<<0;
writel(reg_val, tdm->regs + SUNXI_CHCFG);
reg_val = readl(tdm->regs + SUNXI_DAUDIOTX0CHSEL);
reg_val &= ~SUNXI_DAUDIOTXn_CHEN(CHEN_MASK);
reg_val &= ~SUNXI_DAUDIOTXn_CHSEL(CHSEL_MASK);
reg_val |= SUNXI_DAUDIOTXn_CHEN((CHEN_MASK>>(CH_MAX-substream->runtime->channels)));
reg_val |= SUNXI_DAUDIOTXn_CHSEL(substream->runtime->channels-1);
writel(reg_val, tdm->regs + SUNXI_DAUDIOTX0CHSEL);
#ifdef CONFIG_ARCH_SUN8IW10
reg_val = SUNXI_TXCHANMAP0_DEFAULT;
writel(reg_val, tdm->regs + SUNXI_DAUDIOTX0CHMAP0);
reg_val = SUNXI_TXCHANMAP1_DEFAULT;
writel(reg_val, tdm->regs + SUNXI_DAUDIOTX0CHMAP1);
#else
reg_val = SUNXI_TXCHANMAP_DEFAULT;
writel(reg_val, tdm->regs + SUNXI_DAUDIOTX0CHMAP);
#endif
/*clear TX counter*/
writel(0, tdm->regs + SUNXI_DAUDIOTXCNT);
/* DAUDIO TX ENABLE */
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val |= SUNXI_DAUDIOCTL_TXEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
} else {
reg_val = readl(tdm->regs + SUNXI_CHCFG);
reg_val &= ~SUNXI_TXCHCFG_RX_SLOT_NUM;
reg_val |= (substream->runtime->channels-1)<<4;
writel(reg_val, tdm->regs + SUNXI_CHCFG);
reg_val = readl(tdm->regs + SUNXI_DAUDIORXCHSEL);
reg_val &= ~SUNXI_DAUDIORXCHSEL_RXCHSET(CHSEL_MASK);
reg_val |= SUNXI_DAUDIORXCHSEL_RXCHSET(substream->runtime->channels-1);
writel(reg_val, tdm->regs + SUNXI_DAUDIORXCHSEL);
#ifdef CONFIG_ARCH_SUN8IW10
reg_val = SUNXI_RXCHANMAP0_DEFAULT;
writel(reg_val, tdm->regs + SUNXI_DAUDIORXCHMAP0);
reg_val = SUNXI_RXCHANMAP1_DEFAULT;
writel(reg_val, tdm->regs + SUNXI_DAUDIORXCHMAP1);
#else
reg_val = SUNXI_RXCHANMAP_DEFAULT;
writel(reg_val, tdm->regs + SUNXI_DAUDIORXCHMAP);
#endif
reg_val = readl(tdm->regs + SUNXI_DAUDIOFCTL);
reg_val |= SUNXI_DAUDIOFCTL_RXOM;
writel(reg_val, tdm->regs + SUNXI_DAUDIOFCTL);
/*clear RX counter*/
writel(0, tdm->regs + SUNXI_DAUDIORXCNT);
/* DAUDIO RX ENABLE */
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val |= SUNXI_DAUDIOCTL_RXEN;
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
}
return 0;
}
EXPORT_SYMBOL(tdm_perpare);
int tdm_global_enable(struct sunxi_tdm_info *sunxi_tdm,bool on)
{
u32 reg_val = 0;
u32 reg_val1 = 0;
struct sunxi_tdm_info *tdm = NULL;
if (sunxi_tdm != NULL) {
tdm = sunxi_tdm;
} else {
return -1;
}
reg_val = readl(tdm->regs + SUNXI_DAUDIOCTL);
reg_val1 = readl(tdm->regs + SUNXI_DAUDIOCLKD);
if (!on) {
reg_val &= ~SUNXI_DAUDIOCTL_GEN;
reg_val &= ~SUNXI_DAUDIOCTL_SDO0EN;
reg_val1 &= ~SUNXI_DAUDIOCLKD_MCLKOEN;
} else {
reg_val |= SUNXI_DAUDIOCTL_GEN;
reg_val |= SUNXI_DAUDIOCTL_SDO0EN;
reg_val1 |= SUNXI_DAUDIOCLKD_MCLKOEN;
reg_val1 |= SUNXI_DAUDIOCLKD_MCLKDIV(1);
}
writel(reg_val, tdm->regs + SUNXI_DAUDIOCTL);
writel(reg_val1, tdm->regs + SUNXI_DAUDIOCLKD);
return 0;
}
EXPORT_SYMBOL(tdm_global_enable);
MODULE_LICENSE("GPL");