1. 程式人生 > >ALSA音效卡09_從零編寫之引數設定_學習筆記

ALSA音效卡09_從零編寫之引數設定_學習筆記

1、引數設定分析

(1)open: soc_pcm_open 依次呼叫cpu_dai, dma, codec_dai, machine的open或startup函式


只在dma的open函式裡新增引數相關的程式碼


(2)SNDRV_PCM_IOCTL_HW_PARAMS: soc_pcm_hw_params 依次呼叫machine,codec_dai,cpu_dai,platform(dma)的hw_params函式
    
在uda1341.c, s3c2440-iis.c裡實現hw_params函式(把裸板程式裡面的相關程式碼移過來)


(s3c2440-dma.c 主要涉及資料傳輸,在下一節實現hw_params函式)


(3)開啟音效卡的時候會呼叫到machine部分的dma.c的snd_pcm_ops的dma_open函式)

2、open函式

(1)s3c2440_dma.c(Platform)

static int s3c2440_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
    int ret;
    /* 設定屬性 */
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
snd_soc_set_runtime_hwparams(substream, &s3c2440_dma_hardware

);
    }
return 0;
}

(2)snd_pcm_hardware結構體

static const struct snd_pcm_hardware s3c2440_dma_hardware = {
.info= SNDRV_PCM_INFO_INTERLEAVED | //資料的排列方式(左右左右左右還是左左左右右右)
   SNDRV_PCM_INFO_BLOCK_TRANSFER |
   SNDRV_PCM_INFO_MMAP |
   SNDRV_PCM_INFO_MMAP_VALID |
   SNDRV_PCM_INFO_PAUSE |
   SNDRV_PCM_INFO_RESUME,
.formats

= SNDRV_PCM_FMTBIT_S16_LE |  //所支援的音訊資料格式
   SNDRV_PCM_FMTBIT_U16_LE |
   SNDRV_PCM_FMTBIT_U8 |
   SNDRV_PCM_FMTBIT_S8,
.channels_min= 2,//通道數
.channels_max= 2,
.buffer_bytes_max= 128*1024,
.period_bytes_min= PAGE_SIZE,
.period_bytes_max= PAGE_SIZE*2,
.periods_min= 2,
.periods_max= 128,
.fifo_size= 32,
};

3、hw_params函式(硬體引數)

(1)uda1341.c(codec)

static int uda1341_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
    /* 根據params的值,設定UDA1341的暫存器 
     * 比如時鐘設定,格式
     */
    /* 為了簡單, 在uda1341_init_regs裡就設定好時鐘、格式等引數 */

    return 0;
}

Uda1341的初始化

static void uda1341_init_regs(struct snd_soc_codec *codec)
{


/* GPB 4: L3CLOCK */
/* GPB 3: L3DATA */
/* GPB 2: L3MODE */

    *gpbcon &= ~((3<<4) | (3<<6) | (3<<8));
    *gpbcon |= ((1<<4) | (1<<6) | (1<<8));

    uda1341_write_reg(codec, UDA1341_STATUS0, 0x40 | STAT0_SC_384FS | STAT0_DC_FILTER); // reset uda1341
    uda1341_write_reg(codec, UDA1341_STATUS1, STAT1_ADC_ON | STAT1_DAC_ON);


    uda1341_write_reg(codec, UDA1341_DATA00, DATA0_VOLUME(0x0)); // maximum volume
    uda1341_write_reg(codec, UDA1341_DATA01, DATA1_BASS(0)| DATA1_TREBLE(0));
    uda1341_write_reg(codec, UDA1341_DATA10, 0);  // not mute
}


驅動程式函式uda1341_soc_probe呼叫uda1341_init_regs函式


/* 所有暫存器的預設值 */
static const char
uda1341_reg[UDA1341_REG_NUM] = {
 
  /* DATA0 */
    0x00, 0x40, 0x80,

/* Extended address registers */
0x04, 0x04, 0x04, 0x00, 0x00, 0x00,


  /* data1 */
    0x00,

/* status regs */
    0x00, 0x83,
};

probe函式

static int uda1341_soc_probe(struct snd_soc_codec *codec)
{
    int ret;
    uda1341_init_regs(codec);
    return ret;
}


(2)s3c2440_iis.c(Platform)

static int s3c2440_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
  /* 根據params設定IIS控制器 */


    int tmp_fs;
    int i;
    int min = 0xffff;
    int pre = 0;
    unsigned int fs;
    struct clk *clk = clk_get(NULL, "pclk");

入口函式對映暫存器(出口函式取消對映)
    /*gpecon   = ioremap(0x56000040, 4);//0x56000040是實體地址,大小是4位元組
    iis_regs = ioremap(0x55000000, sizeof(struct s3c2440_iis_regs));*/

    /* 配置GPIO用於IIS */ 

    *gpecon &= ~((3<<0) | (3<<2) | (3<<4) | (3<<6) | (3<<8));
    *gpecon |= ((2<<0) | (2<<2) | (2<<4) | (2<<6) | (2<<8));


    /* bit[9] : Master clock select, 0-PCLK
     * bit[8] : 0 = Master mode
     * bit[7:6] : 10 = Transmit mode
     * bit[4] : 0-IIS compatible format
     * bit[2] : 384fs, 確定了MASTER CLOCK之後, fs = MASTER CLOCK/384
     * bit[1:0] : Serial bit clock frequency select, 32fs
     */

暫存器對映(寫入一個結構體後再對映)

static volatile struct s3c2440_iis_regs *iis_regs;

struct s3c2440_iis_regs {
    unsigned int iiscon ; 
    unsigned int iismod ; 
    unsigned int iispsr ; 
    unsigned int iisfcon; 
    unsigned int iisfifo; 
};
    iis_regs = ioremap(0x55000000, sizeof(struct s3c2440_iis_regs));//暫存器對映

    if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)//每個取樣值佔據的位數(是8位還是16位根據params決定)
        iis_regs->iismod = (2<<6) | (0<<4) | (1<<3) | (1<<2) | (1);
    else if (params_format(params) == SNDRV_PCM_FORMAT_S8)
        iis_regs->iismod = (2<<6) | (0<<4) | (0<<3) | (1<<2) | (1);
    else
        return -EINVAL;//其他值是錯誤的
    struct clk *clk = clk_get(NULL, "pclk");

PCLK=clk_get_rate(clk);最後要用clk_put(clk);
    /* Master clock = PCLK/(n+1)
     * fs = Master clock / 384//fs是取樣率
     * fs = PCLK / (n+1) / 384
     */

    fs = params_rate(params);//取樣頻率根據params得到
    for (i = 0; i <= 31; i++)
    {
        tmp_fs = clk_get_rate(clk)/384/(i+1);
        if (ABS(tmp_fs, fs) < min)
        {
            min = ABS(tmp_fs, fs);
            pre = i;
        }
    }
    iis_regs->iispsr = (pre << 5) | (pre);


    /*
     * bit15 : Transmit FIFO access mode select, 1-DMA
     * bit13 : Transmit FIFO, 1-enable
     */
    iis_regs->iisfcon = (1<<15) | (1<<13);

    /*
     * bit[5] : Transmit DMA service request, 1-enable
     * bit[1] : IIS prescaler, 1-enable
     */
    iis_regs->iiscon = (1<<5) | (1<<1) ;


    clk_put(clk);

    return 0;
}
(3)s3c2440_dma.c(Platform)

static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long totbytes = params_buffer_bytes(params);

/* 根據params設定DMA */
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);


    /* s3c2440_dma_new分配了很大的DMA BUFFER
     * params決定使用多大
     */
runtime->dma_bytes            = totbytes;
    playback_dma_info.buffer_size = totbytes;
    playback_dma_info.period_size = params_period_bytes(params);


    return 0;
}

4、uda1341.c

codec部分

static struct snd_soc_codec_driver soc_codec_dev_uda1341 = {
    .probe = uda1341_soc_probe,
  /* UDA1341的暫存器不支援讀操作,只支援寫操作
     * 要知道某個暫存器的當前值,
     * 只能在寫入時儲存起來(cache)
     */
.reg_cache_size = sizeof(uda1341_reg),
 //存放的是暫存器的值(cache有多大,看看暫存器個數)
.reg_word_size = sizeof(u8),//每個暫存器佔的資料位數
.reg_cache_default = uda1341_reg,//預設值
.reg_cache_step = 1,
.read  = uda1341_read_reg_cache,//讀暫存器的函式
.write = uda1341_write_reg,  /* 寫暫存器 */
};

(1)讀暫存器(支援暫存器的讀操作的編解碼晶片可用讀暫存器函式)

/*
 * The codec has no support for reading its registers except for peak level...
 */

對於uda1341智慧在cache中讀出來
static inline unsigned int uda1341_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
u8 *cache = codec->reg_cache;//對於uda1341智慧在cache中讀出來


if (reg >= UDA1341_REG_NUM)//暫存器的個數大於某個值,返回-1
return -1;
return cache[reg];
}
(2)寫暫存器

static int uda1341_write_reg(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
u8 *cache = codec->reg_cache;//把寫暫存器的值寫到cache裡面去(備份下來,因為uda1341不允許讀暫存器操作)


    /* 先儲存 */
if (reg >= UDA1341_REG_NUM)//暫存器個數為12
return -1;
cache[reg] = value;//儲存值


    /* 再寫入硬體 */
    
    /* 對於EA(擴充套件暫存器),需要呼叫2次l3_write */先把EA地址值作為資料傳送出去,再把ED作為資料值傳送出去
    if ((reg >= UDA1341_EA000) && (reg <= UDA1341_EA110))
    { //左邊的引數是地址(data0),右邊的引數是資料  (uda1341_reg_addr[reg]對應的是擴充套件地址),最高兩位或上1
        l3_write(UDA1341_DATA0_ADDR, uda1341_reg_addr[reg] | UDA1341_EXTADDR_PREFIX);
        l3_write(UDA1341_DATA0_ADDR, value | UDA1341_EXTDATA_PREFIX);
    }
    else
    {
        l3_write(uda1341_reg_addr[reg], value | uda1341_data_bit[reg]);//我們訪問某個暫存器的時候,資料值要或上某一些位,才能定位到uda1341的某一類下的某個暫存器
    }


    return 0;
}

dai部分

static struct snd_soc_dai_driver uda1341_dai = {
.ops = &uda1341_dai_ops,
};

static const struct snd_soc_dai_ops uda1341_dai_ops = {
.hw_params = uda1341_hw_params,
};

5、uda1341硬體分析

(1)2440通過3條線連線uda1341,想寫uda1341的暫存器,肯定需要地址和資料,L3MODE等於0時表示線L3DATA線上傳輸的是地址,為1時傳輸的是資料。L3CLOCK表示每一個時鐘 傳輸1位。


(2)看晶片手冊(UDA1341TS.pdf)有多少個暫存器

資料位7~2代表裝置地址(表示uda1341),資料位0和1表示地址


(3)L3介面

先發出地址,再發出資料。地址裡bit7~bit2用於表示uda1341,資料位0和1表示訪問哪類暫存器,有3類


data0類能訪問多少個暫存器

可認為第1、2、3三行代表的是暫存器,第4、5行不是暫存器,是擴充套件地址,發出的資料是data0這1類,並且前面兩個資料是11的話,後面的3位表示擴充套件地址。


想訪問某個擴充套件地址,如訪問EA,先是地址,後是資料(資料是擴充套件暫存器的地址(高兩位是1))

下面的1、2、3、4、5、6、7代表暫存器,對於data0這1類有9個暫存器



data1這1類只有1個

status這1類,先發出地址,再發出資料,如果資料的最高位是0,選第1行資料,否則選第2行資料。


所以uda1341會根據第2個週期的某些位來分辨訪問哪些暫存器,共有3類12個暫存器。