1. 程式人生 > >Linux下的I2S驅動

Linux下的I2S驅動

1、I2S概述

既然要學習I2S,就要想、首先知道他是幹什麼用的。

I2S(InterIC Sound)匯流排又稱 積體電路內建音訊匯流排,是飛利浦公司為數字音訊裝置之間的音訊資料傳輸而制定的一種匯流排標準,該匯流排專責於音訊裝置之間的資料傳輸,廣泛應用於各種多媒體系統。它採用了沿獨立的導線傳輸時鐘與資料訊號的設計,通過將資料和時鐘訊號分離,避免了因時差誘發的失真,為使用者節省了購買抵抗音訊抖動的專業裝置的費用。

2、I2S的匯流排規範

1.序列時鐘SCLK,也叫位時鐘(BCLK),即對應數字音訊的每一位資料,SCLK都有1個脈衝。SCLK的頻率=2×取樣頻率×取樣位數。

2. 幀時鐘LRCK

(也稱WS),用於切換左右聲道的資料。LRCK為“1”表示正在傳輸的是右聲道的資料,為“0”則表示正在傳輸的是左聲道的資料。LRCK的頻率等於取樣頻率。

3.序列資料SDATA,就是用二進位制補碼錶示的音訊資料。

3、I2S4線,包括序列資料輸入IISDI,序列資料輸出IISDO,左右通道選擇IISLRCK,和穿行位時鐘IISCLK。生成IISLRCKIISCLK的裝置是主裝置。

I2S驅動是作為介面驅動,供linux音訊驅動使用的,因此它的程式碼中,必然要有音訊驅動的一些東西。分析的時候適當的結合一下音訊驅動就好看了。

下面來看看I2S驅動

使用平臺設備註冊IIS驅動。

module_platform_driver(s3c24xx_iis_driver);

module_platform_driver()巨集的作用就是定義指定名稱的平臺裝置驅動註冊函式和平臺裝置驅動登出函式,並且在函式體內分別通過platform_driver_register()函式和platform_driver_unregister()函式註冊和登出該平臺裝置驅動。

跟蹤到s3c24xx_iis_driver,發現主要是做了兩個事,一個是probe函式,還有一個就是remove

  1. staticstruct platform_driver s3c24xx_iis_driver = {  
  2. .probe  = s3c24xx_iis_dev_probe,  
  3. .remove = __devexit_p(s3c24xx_iis_dev_remove),  
  4. .driver = {  
  5. .name = "s3c24xx-iis",  
  6. .owner = THIS_MODULE,  
  7. },  
  8. };  

s3c24xx_iis_dev_probe中,通過snd_soc_register_dai註冊一個ASOC核心的DAI介面,DAI是數字音訊介面。

  1. static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)  
  2. {  
  3. return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);  
  4. }  

s3c24xx_iis_dev_removes3c24xx_iis_dev_probe相反,不在贅述。

snd_soc_register_dais函式顯示為每個snd_soc_dai例項分配記憶體,確定dai的名字,用snd_soc_dai_driver例項的欄位對它進行必要初始化,最後把該dai連結到全域性連結串列dai_list

dai驅動通常對應cpu的一個或幾個I2S/PCM介面,與snd_soc_platform一樣,dai驅動也是實現為一個platform driver,實現一個dai驅動大致可以分為以下幾個步驟:

1.定義一個snd_soc_dai_driver結構的例項;

2.在對應的platform_driver中的probe回撥中通過APIsnd_soc_register_dai或者snd_soc_register_dais,註冊snd_soc_dai例項;

3.實現snd_soc_dai_driver結構中的probesuspend等回撥;

4.實現snd_soc_dai_driver結構中的snd_soc_dai_ops欄位中的回撥函式;

snd_soc_register_dai  

定義一個snd_soc_dai_driver結構

  1. staticstruct snd_soc_dai_driver s3c24xx_i2s_dai = {  
  2. .probe = s3c24xx_i2s_probe,  
  3. .suspend = s3c24xx_i2s_suspend,  
  4. .resume = s3c24xx_i2s_resume,  
  5. .playback = {                          //播放
  6. .channels_min = 2,  
  7. .channels_max = 2,  
  8. .rates = S3C24XX_I2S_RATES,  
  9. .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},  
  10. .capture = {                         //錄音
  11. .channels_min = 2,  
  12. .channels_max = 2,  
  13. .rates = S3C24XX_I2S_RATES,  
  14. .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},  
  15. .ops = &s3c24xx_i2s_dai_ops,  
  16. };  

主要包括s3c24xx_i2s_probe,和 &s3c24xx_i2s_dai_ops,

s3c24xx_i2s_probe進行填充

  1. staticint s3c24xx_i2s_probe(struct snd_soc_dai *dai)  
  2. {  
  3. pr_debug("Entered %s\n", __func__);  
  4. s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);  
  5. if (s3c24xx_i2s.regs == NULL)  
  6. return -ENXIO;  
  7. s3c24xx_i2s.iis_clk = clk_get(dai->dev, "iis");  
  8. if (IS_ERR(s3c24xx_i2s.iis_clk)) {  
  9. pr_err("failed to get iis_clock\n");  
  10. iounmap(s3c24xx_i2s.regs);  
  11. return PTR_ERR(s3c24xx_i2s.iis_clk);  
  12. }  
  13. clk_enable(s3c24xx_i2s.iis_clk);  
  14. /* 配置I2S的管腳在正確的模式 */
  15. s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);  
  16. s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);  
  17. s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);  
  18. s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);  
  19. s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);  
  20. writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);  
  21. s3c24xx_snd_txctrl(0);  
  22. s3c24xx_snd_rxctrl(0);  
  23. return 0;  
  24. }  

對於ioremapIoremap巨集定義在asm/io.h要訪問s3c2410平臺上的I2S暫存器檢視datasheet 知道IIS實體地址為0x55000000,我們把它定義為巨集S3C2410_PA_IIS,如下:

#define S3C2410_PA_IIS    (0x55000000)

若要在核心空間(iis驅動)中訪問這段I/O暫存器(IIS)資源需要先建立到核心地址空間的對映:

s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);

建立好了之後,我們就可以通過readl(s3c24xx_i2s.regs)writel(value, s3c24xx_i2s.regs)IO介面函式去訪問它。

再看s3c24xx_i2s_dai_ops

  1. staticconststruct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {  
  2. .trigger= s3c24xx_i2s_trigger,            //觸發
  3. .hw_params= s3c24xx_i2s_hw_params,    //設定硬體引數
  4. .set_fmt= s3c24xx_i2s_set_fmt,           //設定dai的格式
  5. .set_clkdiv= s3c24xx_i2s_set_clkdiv,     //設定分頻係數
  6. .set_sysclk= s3c24xx_i2s_set_sysclk,    //設定dai的主時鐘
  7. };  

首先來看看I2S的觸發,有六種情況

  1. staticint s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  
  2.        struct snd_soc_dai *dai)  
  3. {  
  4. int ret = 0;  
  5. struct s3c_dma_params *dma_data =  
  6. snd_soc_dai_get_dma_data(dai, substream);  //DMA獲取到的資料是播放的還是錄音
  7. pr_debug("Entered %s\n", __func__);  
  8. switch (cmd) {  
  9. case SNDRV_PCM_TRIGGER_START: //此處case後無語句,則即便cmd為SNDRV_PCM_TRIGGER_START
  10. case SNDRV_PCM_TRIGGER_RESUME: //依然會一直執行後面的語句,直到出現break
  11. case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  
  12. if (!s3c24xx_snd_is_clkmaster()) {  
  13. ret = s3c24xx_snd_lrsync();  
  14. if (ret)  
  15. goto exit_err;  
  16. }  
  17. if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)  
  18. s3c24xx_snd_rxctrl(1);         //錄音
  19. else
  20. s3c24xx_snd_txctrl(1);         //播放
  21. s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); //改變通道的狀態
  22. break;  
  23. case SNDRV_PCM_TRIGGER_STOP:  
  24. case SNDRV_PCM_TRIGGER_SUSPEND:  
  25. case SNDRV_PCM_TRIGGER_PAUSE_PUSH:  
  26. if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)  
  27. s3c24xx_snd_rxctrl(0);  
  28. else
  29. s3c24xx_snd_txctrl(0);  
  30. break;  
  31. default:  
  32. ret = -EINVAL;  
  33. break;  
  34. }  
  35. exit_err:  
  36. return ret;  
  37. }  

在看s3c24xx_i2s_hw_params

  1. staticint s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,  
  2.  struct snd_pcm_hw_params *params,  
  3.  struct snd_soc_dai *dai)  
  4. {  
  5. struct snd_soc_pcm_runtime *rtd = substream->private_data;  
  6. struct s3c_dma_params *dma_data;  
  7. u32 iismod;  
  8. pr_debug("Entered %s\n", __func__);  
  9. if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)  
  10. dma_data = &s3c24xx_i2s_pcm_stereo_out;  
  11. else
  12. dma_data = &s3c24xx_i2s_pcm_stereo_in;  
  13. snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);    
  14. /* Working copies of register */
  15. iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);  
  16. pr_debug("hw_params r: IISMOD: %x\n", iismod);  
  17. switch (params_format(params)) {  
  18. case SNDRV_PCM_FORMAT_S8:  
  19. iismod &= ~S3C2410_IISMOD_16BIT;  
  20. dma_data->dma_size = 1;  
  21. break;  
  22. case SNDRV_PCM_FORMAT_S16_LE:  
  23. iismod |= S3C2410_IISMOD_16BIT;  
  24. dma_data->dma_size = 2;  
  25. break;  
  26. default:  
  27. return -EINVAL;  
  28. }  

snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);通過(substream->stream == SNDRV_PCM_STREAM_PLAYBACK)進行判斷是錄音還是播放。

通過switch (params_format(params))進行判斷是選擇8位還是16位的傳輸通道,並設定相應的IISMODdma寬度。

設定傳輸的格式和主從模式的選擇。

  1. staticint s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,  
  2. unsigned int fmt)  
  3. {  
  4. u32 iismod;  
  5. pr_debug("Entered %s\n", __func__);  
  6. iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);  
  7. pr_debug("hw_params r: IISMOD: %x \n", iismod);  
  8. switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {  
  9. case SND_SOC_DAIFMT_CBM_CFM:                             //設定主or從模式當IISMOD的
  10. iismod |= S3C2410_IISMOD_SLAVE;                   //第8位為0時是master mode
  11. break;                                                                 //為1時是slave mode
  12. case SND_SOC_DAIFMT_CBS_CFS:  
  13. iismod &= ~S3C2410_IISMOD_SLAVE;  
  14. break;  
  15. default:  
  16. return -EINVAL;  
  17. }  
  18. switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {  
  19. case SND_SOC_DAIFMT_LEFT_J:  
  20. iismod |= S3C2410_IISMOD_MSB;        //#define S3C2410_IISMOD_MSB(1 << 4)               
  21. break;                                                   //IISMOD的第4位為1時是 MSB (Left)-justified format
  22. case SND_SOC_DAIFMT_I2S:                        //IISMOD的第4位為0時是IIS compatible format
  23. iismod &= ~S3C2410_IISMOD_MSB;  
  24. break;  
  25. default:  
  26. return -EINVAL;  
  27. }  
  28. writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);    //將IISMOD的狀態寫入IISMOD的地址
  29. pr_debug("hw_params w: IISMOD: %x \n", iismod);  
  30. return 0;  
  31. }  

下面讓我們再來看看I2S對時鐘分頻的操作

首先主裝置時鐘頻率MCLK=PCLK/預分頻值,I2SLRCK頻率=主裝置時鐘頻率/CODECLK

CODECLK的取樣頻率型別為256fs384fs

序列為採用頻率BCLK型別有16/32/48fs,可以通過設定序列位數和CODECLK取樣頻率完成。

序列位時鐘頻率=CODECLK的採用型別/序列資料位數

  1. staticint s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,  
  2. int div_id, int div)  
  3. {  
  4. u32 reg;  
  5. pr_debug("Entered %s\n", __func__);  
  6. switch (div_id) {  
  7. case S3C24XX_DIV_BCLK:                          //序列時鐘頻率
  8. reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;  
  9. writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);  
  10. break;  
  11. case S3C24XX_DIV_MCLK:                           //主時鐘頻率
  12. reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);  
  13. writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);  
  14. break;  
  15. case S3C24XX_DIV_PRESCALER:                //預分頻值設定
  16. writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);  
  17. reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);  
  18. writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);  
  19. break;  
  20. default:  
  21. return -EINVAL;  
  22. }  
  23. return 0;  
  24. }  


 

reg | S3C2410_IISCON_PSCEN使能預分頻

 

下面我們來設定系統的時鐘的時鐘源

  1. staticint s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,  
  2. int clk_id, unsigned int freq, int dir)  
  3. {  
  4. u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);    //讀取IISMOD的狀態
  5. pr_debug("Entered %s\n", __func__);  
  6. iismod &= ~S3C2440_IISMOD_MPLL;          //初始化選擇PCLK 外設時鐘
  7. switch (clk_id) {  
  8. case S3C24XX_CLKSRC_PCLK:  //如果clk_id為0,則使用PCLK
  9. break;  
  10. case S3C24XX_CLKSRC_MPLL:  //如果clk_id為1,則選擇MPLL
  11. iismod |= S3C2440_IISMOD_MPLL;  
  12. break;  
  13. default:  
  14. return -EINVAL;  
  15. }  
  16. writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);  
  17. return 0;  
  18. }  

這裡要解釋一下MPLLphase locked loop),S3C2440有兩個PLLphase locked loop)一個是MPLL,一個是UPLLMPLL用於CPU及其他外圍器件,UPLL用於USB。從S3C2440DATASHEET裡可以看到,S3C2440最大支援400MHz的主頻,但是這並不意味著一定工作在400MHz下面,可以通過設定MPLL, UPLL暫存器來設定CPU的工作頻率。 

到此 s3c24xx_i2s_dai_ops的分析結束。

相關推薦

linux驅動控制gpio的方法

arm linux下寫驅動控制gpio時,有兩種方法: 1. 用ioremap()得到暫存器的地址,然後用iowrite32()或writel()函式寫暫存器控制gpio 1)查datashee

linuxi2c驅動筆記

1. 幾個基本概念 1.1. 裝置模型 由 匯流排(bus_type) + 裝置(device) + 驅動(device_driver) 組成,在該模型下,所有的裝置通過匯流排連線起來,即使有些裝置沒有連線到一根物理總線上,linux為其設定了一個內部的、虛擬的platf

LinuxDMA驅動框架

啟動傳輸 dmaengine_issue_pending呼叫會從第一個描述符開始進行傳輸。如果DMA 裝置驅動有回撥函式的話,會在傳輸完成後執行。 下面介紹一下獲得傳輸描述符的三種方式。 device_prep_dma_memcpy(),明顯是DMA記憶體到記憶體的拷貝 有些DMA支援分散集合模式,即記憶

Linux wifi 驅動開發(三)—— SDIO介面WiFi驅動淺析

      SDIO-Wifi模組是基於SDIO介面的符合wifi無線網路標準的嵌入式模組,內建無線網路協議IEEE802.11協議棧以及TCP/IP協議棧,能夠實現使用者主平臺數據通過SDIO口到無線網路之間的轉換。SDIO具有傳輸資料快,相容SD、MMC介面等特點。  

Linux wifi 驅動開發(一)—— WiFi基礎知識解析

 一、WiFi相關基礎概念 1、什麼是wifi        我們看一下百度百科是如何定義的:       Wi-Fi是一種可以將個人電腦、手持裝置(如pad、手機)等終端以無線方式互相連線的技術,事實上它是一個高頻無線電訊號。[1]  無線保真是一個無線網路通訊技術的品牌

很好的linuxGPIO驅動詳解文章

打算跟著友善之臂的《mini2440 linux移植開發指南》來做個LED驅動,雖然LED的原理簡單得不能再簡單了,但是要把kernel中針對於s3c24**的GPIO的一些資料結構,還有函式搞清楚也不是那麼輕鬆的事,所以本文主要簡單地說明下LED驅動中的相關資料結構以及

LinuxPCIe驅動以及DMA機制

1. 驅動程式作用: ·        裝置驅動程式嚮應用程式遮蔽了硬體在實現上的細節,使得應用程式可以像操作普通檔案一樣操作外部裝置。Linux作業系統抽象了對硬體的處理,可以使用和操作檔案相同的,標準的系統呼叫介面來完成開啟,關閉,讀寫喝I/O控制操作,而驅動程式主要任

linuxIIC驅動開發分析

1.IIC規範 IIC(Inter-Integrated Circuit)匯流排是一種由PHILIPS公司開發的兩線式序列匯流排,用於連線微控制器及其外圍裝置。IIC匯流排產生於在80年代,最初為音訊和視訊裝置開發,如今主要在伺服器管理中使用,其中包括單個元件狀態的通訊。例如管理員可對各個元件進行查詢,以

Linux wifi 驅動開發(二)—— WiFi模組淺析

一、什麼是wifi 模組         百度百科上這樣定義:         Wi-Fi模組又名串列埠Wi-Fi模組,屬於物聯網傳輸層,功能是將串列埠或TTL電平轉為符合Wi-Fi無線網路通訊標準的嵌入式模組,內建無線網路協議IEEE802.11b.g.n協議棧以及TCP

LinuxI2C驅動分析(一)

        最近在做一個基於全志A33晶片的android移植時發現嵌入式裝置很多都用到了I2C匯流排通訊,比如說攝像頭,G-sensor,觸控式螢幕等,為此我覺得很好的理解I2C裝置驅動在今後的嵌入式開發中是非常有好處的,而目前我也是處於學習階段,便將這些學習的過程給

Linuxspi驅動開發(2)

Linux下spi驅動開發之m25p10驅動測試 目標:在華清遠見的FS_S5PC100平臺上編寫一個簡單的spi驅動模組,在probe階段實現對m25p10的ID號探測、flash擦除、flash狀態讀取、flash寫入、flash讀取等操作。程式碼已經經過測試,運行於

Linux編譯驅動程式碼時smp_lock.h檔案找不到的解決方案

專案中有個驅動程式碼之前在老版本linux系統中編寫的 在移植到linux核心3.2.0後,編譯時出現一處錯誤“fatal error: linux/smp_lock.h: No such file or directory” 解決辦法是將 #include <linux/smp_lock.h&g

Linuxnorflash驅動編寫方法

                                                                      Linux下norflash驅動編寫步驟 1. 分配map_info結構體 2. 設定: 物理基地址(phys), 大小(size),

Linux 攝像頭驅動支援情況(…

我要做一個s3c6410 的攝像頭視訊採集的專案,由於我接手的這套開發板,核心編譯的使用可能沒有配置攝像頭頭驅動程式,所以我嘗試了好多時間安裝攝像頭驅動,因為是新手,所以吃的苦頭比較多,在前期的諸多嘗試之後,經一學長幫助,立馬入門了。我這套板子原本自帶了一個2X10插口的攝像頭模組,是ov9650但是

Linux列印驅動的問與答

第一波 > 1>. 通過上面demo可知,這個適用於HP印表機,那我這裡想使用epson印表機,想使用ijsgutenprint,這裡也就要求移植gutenprint, 是的,是這樣的。需要移植gutenprint. >   那請問在這裡,gutenpri

Linux安裝驅動的兩種方法

linux 編譯安裝驅動有兩種,動態載入與靜態載入 動態載入 一,編譯,在指點核心樹下編譯,生成.o檔案或.ko檔案 二,將生成的.o或.ko檔案拷到相應目錄,一般是/lib/module/kernel下面 三,用insmod命令載入,用rmmod命令解除安裝 靜態載入 靜態

Linux RT3070 驅動移植

RT3070驅動移植 無線網絡卡為RT3070,驅動分為STA驅動和SoftAP驅動兩種,STA驅動支援無線網絡卡工作在STA模式下,而SoftAP的驅動支援無線網絡卡工作在軟AP的模式下,可以作為一個軟的接入點。STA驅動為2010_0831_RT3070_Linux_S

LinuxI2S驅動學習

原文連結:http://blog.csdn.NET/gotowu/article/details/46329809 1、I2S概述 既然要學習I2S,就要想、首先知道他是幹什麼用的。 I2S(Inter—IC Sound)匯流排, 又稱 積體電路內建音訊匯流排,是飛利浦公司為數字

LinuxI2S驅動

1、I2S概述 既然要學習I2S,就要想、首先知道他是幹什麼用的。 I2S(Inter—IC Sound)匯流排, 又稱 積體電路內建音訊匯流排,是飛利浦公司為數字音訊裝置之間的音訊資料傳輸而制定的一種匯流排標準,該匯流排專責於音訊裝置之間的資料傳輸,廣泛應用於各種多媒體系

linux網卡驅動安裝全過程

linux網卡驅動方法一,用RPM包安裝驅動程序方法:1.將驅動程序文件bcm5700-.src.rpm復制到一個臨時目錄中,並在此目錄中運行以下命令:rpm –ivh bcm5700-.src.rpm2.運行以下命令切換到驅動目錄中:cd /usr/src/redhat/SPECS/3.此目錄中會生成一個名