1. 程式人生 > >UDA1341 SSI音訊驅動

UDA1341 SSI音訊驅動

SSI音訊驅動

音訊驅動的文章網上有很多,分析的也很具體,這裡只記錄本人在除錯音訊驅動的過程和理解。除錯一個驅動,最主要的還是從本質上去理解它的工作原理,包括時鐘,資料,中斷,暫存器配置等。或許並不需要全部知道,但是追根到底會對以後的驅動有很大幫助。

音訊驅動

linux音訊驅動的結構我個人感覺做得很漂亮,有面向物件程式設計的人不難看出這個結構與類圖很想,將變化的東西封裝了起來,層次清晰,方便理解和除錯:

cpu 驅動:即匯流排驅動,如I2S,SSI,AC97,ESAI,SPDIF等。封裝了對匯流排的配置,如主從模式的設定。
codec驅動:編碼器驅動,對應的是解碼晶片,封裝了對解碼晶片的操作,包括設定暫存器等。
machine驅動:將cpu驅動和codec驅動糅合起來,有人把machine驅動比作插座,我覺得很形象,machine驅動就是將對應的cpu和codec插到板子上。

1、board檔案

在板級檔案中新增音訊支援。
static struct platform_device mxc_uda134x_device = {
    .name = "imx-3stack-uda134x",
};
static struct mxc_audio_platform_data uda134x_data = {
    .ssi_num = 1,
    .src_port = 2,
    .ext_port = 5,
    .init = mxc_uda134x_init,
};
static int mxc_uda134x_init(void)
{
    struct
clk *ssi_ext1; struct clk *ssi_sys_clk; u32 rate; ssi_ext1 = clk_get(NULL, "ssi_ext1_clk"); if (IS_ERR(ssi_ext1)) return -1; rate = clk_round_rate(ssi_ext1, 3000000); if (rate < 2000000 || rate > 24000000) { printk(KERN_ERR "Error: UDA134x mclk freq %d out of range!\n"
, rate); clk_put(ssi_ext1); return -1; } clk_set_rate(ssi_ext1, rate); clk_enable(ssi_ext1); uda134x_data.sysclk = rate; return 0; } 這裡的ssi_num對應的是SSI的通道號,SSI匯流排功能功能配置 MX53_PAD_KEY_COL0__AUDMUX_AUD5_TXC, MX53_PAD_KEY_ROW0__AUDMUX_AUD5_TXD, MX53_PAD_KEY_COL1__AUDMUX_AUD5_TXFS, MX53_PAD_KEY_ROW1__AUDMUX_AUD5_RXD,

AUDMUX5是指AUDMUX的外部埠(即與codec相連的埠)號,也就是ext_num,而src_num是指內部埠(即與cpu相連的埠)號。埠的連線規則可以從晶片手冊上查到。很奇怪,系統時鐘設定為3M就可以正常播放,4M就沒有聲音了,然後20M又有聲音,但是噪音很大,有知道原因的還請告知。
連線示意圖如下:
這裡寫圖片描述

2、時鐘:

SSI的時鐘示意圖:
這裡寫圖片描述

可以看到SSI_EXT1_CLK可以由pll3,pll2,ssi_lp_apm_clk產生,而與它相關的暫存器是CCM_CCGR3。時鐘的配置並不需要我們處理,我們只需要瞭解就好。SSI的配置如下

    _REGISTER_CLOCK("mxc_ssi.0", NULL, ssi1_clk[0]),
    _REGISTER_CLOCK("mxc_ssi.1", NULL, ssi2_clk[0]),
    _REGISTER_CLOCK("mxc_ssi.2", NULL, ssi3_clk[0]),
    _REGISTER_CLOCK(NULL, "ssi_ext1_clk", ssi_ext1_clk),
    _REGISTER_CLOCK(NULL, "ssi_ext2_clk", ssi_ext2_clk),

這裡的_REGISTER_CLOCK巨集我沒太看懂它前兩個引數的意思。如果有知道的麻煩告訴我。

3、machine驅動

machine驅動大部分都可以參考其他的音訊驅動,因為主體基本都一樣,而uda1341有一點點的區別,它有一個L3匯流排,L3匯流排驅動linux已經有了,我們需要做的只是指定對應的引腳(可以參考s3c24xx_uda1341.c)。
需要特別指出的是2個函式:

1、imx_3stack_init_dam

static void imx_3stack_init_dam(int ssi_port, int dai_port)
{
    unsigned int ssi_ptcr = 0;
    unsigned int dai_ptcr = 0;
    unsigned int ssi_pdcr = 0;
    unsigned int dai_pdcr = 0;
    /* UDA134X uses SSI1 via AUDMUX port dai_port for audio */

    /* reset port ssi_port & dai_port */
    __raw_writel(0, DAM_PTCR(ssi_port));
    __raw_writel(0, DAM_PTCR(dai_port));
    __raw_writel(0, DAM_PDCR(ssi_port));
    __raw_writel(0, DAM_PDCR(dai_port));

    /* set to synchronous */
    ssi_ptcr |= AUDMUX_PTCR_SYN;
    dai_ptcr |= AUDMUX_PTCR_SYN;

    /* set Rx sources ssi_port <--> dai_port */
    ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
    dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);

    /* set Tx frame direction and source  dai_port --> ssi_port output */
    dai_ptcr |= AUDMUX_PTCR_TFSDIR;
    dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);

    /* set Tx Clock direction and source dai_port--> ssi_port output */
    dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
    dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);

    __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
    __raw_writel(dai_ptcr, DAM_PTCR(dai_port));
    __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
    __raw_writel(dai_pdcr, DAM_PDCR(dai_port));
}

SSI匯流排是相容I2S的,所以也需要設定主從模式。而uda1341只能工作在從模式,所以我們需要將SSI設定成主模式。上面的函式其實是在配置AUDMUX的資料流方向。
The default port-to-port connections are as follows:
• Port 1 to Port 6
• Port 6 provides the clock and frame sync.
• Port 2 to Port 5
• Port 5 provides the clock and frame sync.
• Port 3 to Port 4
• Port 4 provides the clock and frame sync.
• Port 7 to Port 7 (in data loopback mode)
• Clock and frame syncs are inputs.
從文件中我們看到BCLK和frame sync等訊號都是由port5提供,也就是說我們需要將port5配置成輸出模式,而port2配置成輸入模式。就是配置PTCR暫存器。

2、imx_3stack_audio_hw_params


static int imx_3stack_audio_hw_params(struct snd_pcm_substream *substream,
                      struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    struct snd_soc_dai_link *machine = rtd->dai;
    struct snd_soc_dai *cpu_dai = machine->cpu_dai;
    struct snd_soc_dai *codec_dai = machine->codec_dai;
    unsigned int rate = params_rate(params);
    struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
    int ret = 0;

    u32 dai_format;

    int bits;
    int channels = params_channels(params);

    dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
        SND_SOC_DAIFMT_CBS_CFS;

    ssi_mode->sync_mode = 1;
    ssi_mode->network_mode = 1;

    /* set cpu DAI configuration */
    ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
    if (ret < 0)
        return ret;

    /* set i.MX active slot mask */
    snd_soc_dai_set_tdm_slot(cpu_dai,
                 channels == 1 ? 0xfffffffe : 0xfffffffc,
                 channels == 1 ? 0xfffffffe : 0xfffffffc,
                 2, 32);


    /* set the SSI clock div  */
//  snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_2, 0);
//  snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PSR, 0);
//  snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PM, 23);
//  snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_RX_DIV_2, 0);
//  snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_RX_DIV_PSR, 0);
//  snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_RX_DIV_PM, 23);

    /* set the SSI system clock as output  */
    snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_OUT);

    /* set codec DAI configuration */
    snd_soc_dai_set_fmt(codec_dai, dai_format);

    /* Set codec clock*/
    snd_soc_dai_set_sysclk(codec_dai, UDA134X_SYSCLK,rate*384, SND_SOC_CLOCK_OUT);

    return 0;
}
這個函式的功能主要是在播放之前配置SSI和CODEC,要理解它們的含義需要結合imx_ssi.c和uda1341.c。首先SSI部分配置模式,SND_SOC_DAIFMT_CBS_CFS在imx_ssi.c中其實是配置成主模式。
然後是codec的系統時鐘,需要檢視手冊,uda1341的系統時鐘是256fs、384fs、512fs。fs表示取樣率。

這個音訊驅動斷斷續續搞了許久,要完全弄懂需要結合手冊一點點的看。不過最後發現其實只是移植的話還是很簡單的,只要注意幾個地方就夠了。
1、板級檔案中音訊結構體,包括埠的設定,ssi的編號。codec系統時鐘的設定。
2、AUDMUX埠功能的配置。注意資料流的方向,以及主從模式的區別。
3、hw_param函式中dai的配置。需要結合cpu_dai和codec_dai原始碼來對照。

補充:對於音訊驅動幾個時鐘的理解
1、imx_ssi.c:
在probe函式中 priv->ssi_clk = clk_get(&pdev->dev, “ssi_clk”);
這個時鐘我的理解是AUDMUX的時鐘,用來分配給port5,然後通過分頻引數產生BCLK和frame sync這兩個時鐘訊號。它的值讀出來固定是12M。
2、uda1341.c:
在machine驅動中會設定codec的sysclk
snd_soc_dai_set_sysclk(codec_dai, 0,rate*384, 0);這個時鐘我開始有點疑問。這裡設定了codec的sysclk,那與cpu相連的sysclk線是幹嘛的?後來找uda1341的晶片手冊上發現,它的系統時鐘就是256fs、384fs、512fs。也就是說這裡確實是在設定它的系統時鐘,而這個系統時鐘可以理解為內部的解碼時鐘。
3、board.c中設定的sysclk:
在board.c中初始化了一個時鐘ssi_ext1_clk,這個時鐘才是I2S的時鐘,這個時鐘的作用應該理解成給DATAI/DATAO提供的,用來控制資料的輸入輸出速度。SSI相容I2S,但是AUDMUX只是給codec提供了3個介面BCLK、Frame sync、DATAO/DATAI。至於I2S的sysclk直接由CPU提供,就是SSI_ext1_clk。所以為什麼當我設定20M的時候,聲音出現了噪音,應該是DATAI上資料輸入太快,導致codec解碼速度跟不上資料的速度,然後就出現了音訊輸出資料不對的問題。