1. 程式人生 > >音訊audio/sound音效卡驅動分析

音訊audio/sound音效卡驅動分析

音訊可以播放(可以聽到聲音), 說明音訊解碼和輸出部分基本是正常的, 整個通道已經打通了. 感覺播放速度太快了(或太慢了)說明audio輸出部分的頻率不對, 太高了或者太低了.

audio/sound音訊部分涉及的幾個頻率:

  * 輸出取樣頻率 fs = 44.1KHz.  (也有其它fs的音源, 但加了resampler後, 都變成44.1KHz輸出了). 這是個關鍵頻率.

  * LRCLK, 就等於fs. (L/R聲道訊號)

  * BCLK = 32倍fs = 1411.2KHz = 1.4112MHz. (bit clock). 2聲道16bit, 故32倍fs. 若2聲道24bit, 則48倍fs.

  * MCLK是整個audio模組的工作頻率, 通常選fs的256, 384, 512倍. 比如: 256倍fs = 11289.6KHz = 11.2896MHz.

從頻率設定來說, MCLK是個主要頻率, 它是整個audio模組的工作頻率.

通常MCLK是由某個PLL按一定倍數分頻得到的, 比如6倍.  (因為MCLK頻率只有11.3MHz左右, 如果直接由PLL產生MCLK則頻率太低了, PLL不好做). 那麼, 這個PLL的輸出頻率PLLout = 6倍MCLK = 67.7376MHz.

那麼, 從軟體來說要設定兩個方面的暫存器: 一是該PLL從晶振頻率如何得到PLLout頻率(比如P/M/S/k). 二是PLLout如何分頻得到audio部分的MCLK.

核心audio/sound音效卡驅動分析. \sound\soc\s3c24xx\aries-wm8994.c檔案


module_init(smdkc110_audio_init);

static int __init smdkc110_audio_init(void)
{
 smdkc1xx_snd_device = platform_device_alloc("soc-audio", 0);

 platform_set_drvdata(smdkc1xx_snd_device, &smdkc1xx_snd_devdata);
 smdkc1xx_snd_devdata.dev = &smdkc1xx_snd_device->dev;

 platform_device_add(smdkc1xx_snd_device);
}

/* audio subsystem */
static struct snd_soc_device smdkc1xx_snd_devdata= {
 .card = &smdkc100,
 .codec_dev = &soc_codec_dev_wm8994,
 .codec_data = &smdkc110_wm8994_setup,
};

struct snd_soc_codec_device soc_codec_dev_wm8994= {
 .probe =  wm8994_probe,
 .remove =  wm8994_remove,
#ifdef CONFIG_PM
 .suspend= wm8994_suspend,
 .resume= wm8994_resume,
#endif
};

static struct wm8994_setup_data smdkc110_wm8994_setup = {
 .i2c_address = 0x34,
 .i2c_bus = 2,
};
static struct snd_soc_card smdkc100 = {
 .name = "smdkc110",
 .platform = &s3c_dma_wrapper,
 .dai_link = &smdkc1xx_dai,
 .num_links = 1,
};

struct snd_soc_platform s3c_dma_wrapper = {
 .name  = "samsung-audio",
 .pcm_ops  = &s3c_wrpdma_ops,
 .pcm_new = s3c_wrpdma_pcm_new,
 .pcm_free = s3c_wrpdma_pcm_free,
};

/* digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link smdkc1xx_dai = {
 .name = "WM8994",
 .stream_name = "WM8994 HiFi Playback",
 .cpu_dai = &s3c64xx_i2s_dai[I2S_NUM],  //cpu_dai->ops = &s3c64xx_i2s_dai_ops, 最終指向s5p_i2s_xxxx函式
 .codec_dai= &wm8994_dai,  //codec_dai->ops = &wm8994_ops
 .init = smdkc110_wm8994_init,
 .ops = &smdkc110_ops,  //其中hw_params = smdkc110_hw_params
},
其中cpu_dai為s3c64xx_i2s_dai,其ops為s3c64xx_i2s_dai_ops,由s5p_i2sv5_register_dai最終指向s5p_i2s_xxxx(比如s5p_i2s_set_clkdiv/s5p_i2s_set_sysclk).

補充: epll_set_rate呼叫過程...

smdkc110_hw_params (或者smdkc110_audio_init)

set_epll_rate

clk_set_rate

s5pv210_setup_clocks {

 clk_fout_epll.enable = s5pv210_epll_enable;
 clk_fout_epll.ops = &s5pv210_epll_ops;

 clk_fout_vpll.enable = s5pv210_vpll_enable;
 clk_fout_vpll.ops = &s5pv210_vpll_ops;
}

static struct clk_ops s5pv210_epll_ops = {
 .get_rate = s5pv210_epll_get_rate,
 .set_rate = s5pv210_epll_set_rate,
};

s5pv210_epll_set_rate 會查epll_div表格,並寫 S5P_EPLL_CON, S5P_EPLL_CON_K暫存器. (注意: 若表格未查到則不設定. 這樣感覺不好?)

補充: 各個clksrc_clk的定義與ops關聯, 參考mach-s5pv210\clock.c及plat-samsung\clock-clksrc.c檔案.

各個clksrc_clk定義在clock.c檔案, 比如 /* HCLK_P 133 */
static struct clksrc_clk clk_hclk_133 = {
 .clk = {
  .name  = "hclk_133",
  .id  = -1,
 },
 .sources = &clkset_mout_166,
 .reg_src = { .reg = S5P_CLK_SRC0, .shift = 24, .size = 1 },
 .reg_div = { .reg = S5P_CLK_DIV0, .shift = 24, .size = 4 },
};

各個clksrc_clk歸集在sys_clksrc[] 和clksrcs[]陣列, 每個clk的ops可以在定義時指定, 若未指定則通過s5pv210_register_clocks\ s3c_register_clksrc根據情況關聯到預設ops, 即clksrc_ops/ clksrc_ops_nodiv/ clksrc_ops_nosrc三者之一. 比如:

static struct clk_ops clksrc_ops= {
 .set_parent = s3c_setparent_clksrc,
 .get_rate = s3c_getrate_clksrc,
 .set_rate = s3c_setrate_clksrc,
 .round_rate = s3c_roundrate_clksrc,
};
PC110之monitor clock "XCLKOUT"腳對應之"clk_out"的定義與ops關聯

struct clk xclk_out = {
 .name   = "clk_out",
 .id     = -1,
 .ops = &s5pc11x_clkout_ops,   //最終指向.set_rate = s5pc11x_clk_out_set_rate, .set_parent = s5pc11x_clk_out_set_parent,
};

補充: 背景知識(自己的理解)

audio/sound音訊部分涉及的幾個頻率:

  * 輸出取樣頻率 fs = 44.1KHz.  (也有其它fs的音源, 但加了resampler後, 都變成44.1KHz輸出了). 這是個關鍵頻率.

  * LRCLK, 就等於fs. (L/R聲道訊號)

  * BCLK= 32倍fs = 1411.2KHz = 1.4112MHz. (bit clock). 2聲道16bit, 故32倍fs (也可選48fs只是浪費了時間). 若2聲道24bit, 則48倍fs.  PC100稱SCLK.

  * MCLK是整個audio模組的工作頻率, 通常選fs的256, 384, 512倍. 比如: 256倍fs = 11289.6KHz = 11.2896MHz.  PC100稱RCLK(Root clk).

從頻率設定來說, MCLK是個主要頻率, 它是整個audio模組的工作頻率.

通常MCLK是由某個PLL按一定倍數分頻得到的, 比如6倍.  (因為MCLK頻率只有11.3MHz左右, 如果直接由PLL產生MCLK則頻率太低了, PLL不好做). 那麼, 這個PLL的輸出頻率PLLout = 6倍MCLK = 67.7376MHz.

那麼, 從軟體來說要設定兩個方面的暫存器: 一是該PLL從晶振頻率如何得到PLLout頻率(比如P/M/S/k). 二是PLLout如何分頻得到audio部分的MCLK.

補充: I2S基礎知識PC110

Inter-IC Sound(IIS)是一種digital audio interface. IIS使用4根線: SDI, SDO(serial data input/output), LRCLK(left/right channel select clock), SCLK (serial bit clock).

IIS有master/slave兩種模式, 由master提供LRCLK and SCLK. PC110作master時, 可把RCLK輸出到CDCLK (codec clk)腳.

‍PC110支援IIS, MSB-justified and LSB-justified三種data format.

S5PC110 IIS模組可選clock sources三個: PCLK, EPLL and external codec.

‍46.5.2 Play Mode (TX mode) with DMA
3. To operate system in stability, the internal TXFIFO should be almost full before transmission. For TXFIFO to be almost full start DMA operation.
46.5.3 Recording Mode (RX mode) with DMA
3. To operate system in stability, the internal RXFIFO should have at least one data before DMA operation. 為什麼???

要寫I2S驅動,對於硬體也要了解。I2S是一種常用的數字音訊介面。匯流排值處理音訊資料,像編碼和控制這樣的其他訊號被轉移分開。I2S介面傳輸或者接受聲音資料來自於外部立體聲音訊編碼器。用於傳輸和接受資料,包括兩個32x16FIFO資料結構。

匯流排特徵:

2通道I2S匯流排用於DMA裝置的音訊介面運作。

序列,8/16位經通道資料傳輸

支援I2S,MSB-justified和LSB-justifed資料格式。

下面是電路原理圖:

左邊的CDCLK,I2SSCLK,I2SLRCK,I2SSDI,I2SDO是i2s匯流排引腳。CDCLK為音訊編碼器提供解碼時鐘,I2SSCLK提供了序列位時鐘,I2SLRCK是聲道控制時鐘,I2SSDI聲音資料輸入,I2SDO聲音資料輸出。 另外的L3MODE,L3CLOCK,L3DATA控制聲音。 I2S主要通過i2s控制暫存器IISCON,i2s模式暫存器IISMOD,預分頻IISPSR,FIFO控制暫存器IISFCON,IISFIFO工作。 I2S驅動是ASoc驅動組成的一部分。它是平臺驅動那部分。這裡就只簡單說下ASoc主要的3部分: (1)Codec驅動。這一部分只關心Codec本身,與CPU平臺相關的特性不由此部分操作 (2)平臺驅動。這一部分只關心CPU本身,它主要處理兩個問題:DMA引擎和SOC整合的PCM,I2S或者ac'97數字介面控制 (3)板驅動。這一部分將平臺驅動和CODEC驅動繫結在一起,描述了板一級的硬體特徵。 具體I2S驅動如下: 這裡提供了I2S驅動的註冊函式snd_soc_register_dai()所要註冊的是snd_soc_dai結構體, 具體為 struct snd_soc_dai s3c24xx_i2s_dai = {
 .name = "s3c24xx-i2s",
 .id = 0,
 .probe = s3c24xx_i2s_probe,初始化
 .suspend = s3c24xx_i2s_suspend,掛起
 .resume = s3c24xx_i2s_resume,恢復 放音功能
 .playback = {
  .channels_min = 2,DMA通道2
  .channels_max = 2,
  .rates = S3C24XX_I2S_RATES,放音速率
  .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 錄音功能
 .capture = {
  .channels_min = 2,
  .channels_max = 2,
  .rates = S3C24XX_I2S_RATES,錄音速率
  .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
 .ops = &s3c24xx_i2s_dai_ops,
}; static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
 .trigger = s3c24xx_i2s_trigger,觸發
 .hw_params = s3c24xx_i2s_hw_params,硬體引數設定
 .set_fmt = s3c24xx_i2s_set_fmt,DAI格式
 .set_clkdiv = s3c24xx_i2s_set_clkdiv,時鐘分頻
 .set_sysclk = s3c24xx_i2s_set_sysclk,系統時鐘
}; .probe完成了I2S暫存器對映,時鐘獲取起用,設定GPIO口得相關引腳為I2S功能引腳。啟動I2S,不傳送不接受狀態。 原始碼如下: ------------------------------------------------------------------------------------------------ #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/gpio.h> #include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h> #include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/regs-clock.h>
#include <plat/audio.h>
#include <asm/dma.h>
#include <mach/dma.h> #include <plat/regs-iis.h> #include "s3c24xx-pcm.h"
#include "s3c24xx-i2s.h" static struct s3c2410_dma_client s3c24xx_dma_client_out = {
 .name = "I2S PCM Stereo out"
}; static struct s3c2410_dma_client s3c24xx_dma_client_in = {
 .name = "I2S PCM Stereo in"
}; static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = {
 .client  = &s3c24xx_dma_client_out,
 .channel = DMACH_I2S_OUT,
 .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
 .dma_size = 2,
}; static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = {
 .client  = &s3c24xx_dma_client_in,
 .channel = DMACH_I2S_IN,
 .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
 .dma_size = 2,
}; struct s3c24xx_i2s_info {
 void __iomem *regs;
 struct clk *iis_clk;
 u32  iiscon;
 u32  iismod;
 u32  iisfcon;
 u32  iispsr;
};
static struct s3c24xx_i2s_info s3c24xx_i2s; static void s3c24xx_snd_txctrl(int on)
{
 u32 iisfcon;
 u32 iiscon;
 u32 iismod;  pr_debug("Entered %s\n", __func__);  iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
 iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
 iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);  pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);  if (on) {
  iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
  iiscon  |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
  iiscon  &= ~S3C2410_IISCON_TXIDLE;
  iismod  |= S3C2410_IISMOD_TXMODE;   writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
  writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
 } else {
     iisfcon &= ~S3C2410_IISFCON_TXENABLE;
  iisfcon &= ~S3C2410_IISFCON_TXDMA;
  iiscon  |=  S3C2410_IISCON_TXIDLE;
  iiscon  &= ~S3C2410_IISCON_TXDMAEN;
  iismod  &= ~S3C2410_IISMOD_TXMODE;   writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
  writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
 }  pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
} static void s3c24xx_snd_rxctrl(int on)
{
 u32 iisfcon;
 u32 iiscon;
 u32 iismod;  pr_debug("Entered %s\n", __func__);  iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
 iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
 iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);  pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);  if (on) {
  iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
  iiscon  |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
  iiscon  &= ~S3C2410_IISCON_RXIDLE;
  iismod  |= S3C2410_IISMOD_RXMODE;   writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
  writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
 } else {
     iisfcon &= ~S3C2410_IISFCON_RXENABLE;
  iisfcon &= ~S3C2410_IISFCON_RXDMA;
  iiscon  |= S3C2410_IISCON_RXIDLE;
  iiscon  &= ~S3C2410_IISCON_RXDMAEN;
  iismod  &= ~S3C2410_IISMOD_RXMODE;   writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
  writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
  writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
 }  pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
}
static int s3c24xx_snd_lrsync(void)
{
 u32 iiscon;
 int timeout = 50;  pr_debug("Entered %s\n", __func__);  while (1) {
  iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
  if (iiscon & S3C2410_IISCON_LRINDEX)
   break;   if (!timeout--)
   return -ETIMEDOUT;
  udelay(100);
 }  return 0;
}
static inline int s3c24xx_snd_is_clkmaster(void)
{
 pr_debug("Entered %s\n", __func__);  return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
}
static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
  unsigned int fmt)
{
 u32 iismod;  pr_debug("Entered %s\n", __func__);  iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
 pr_debug("hw_params r: IISMOD: %x \n", iismod);  switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 case SND_SOC_DAIFMT_CBM_CFM:
  iismod |= S3C2410_IISMOD_SLAVE;
  break;
 case SND_SOC_DAIFMT_CBS_CFS:
  iismod &= ~S3C2410_IISMOD_SLAVE;
  break;
 default:
  return -EINVAL;
 }  switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 case SND_SOC_DAIFMT_LEFT_J:
  iismod |= S3C2410_IISMOD_MSB;
  break;
 case SND_SOC_DAIFMT_I2S:
  iismod &= ~S3C2410_IISMOD_MSB;
  break;
 default:
  return -EINVAL;
 }  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
 pr_debug("hw_params w: IISMOD: %x \n", iismod);
 return 0;
} static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
     struct snd_pcm_hw_params *params,
     struct snd_soc_dai *dai)
{
 struct snd_soc_pcm_runtime *rtd = substream->private_data;
 u32 iismod;  pr_debug("Entered %s\n", __func__);  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out;
 else
  rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in;  
 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
 pr_debug("hw_params r: IISMOD: %x\n", iismod);  switch (params_format(params)) {
 case SNDRV_PCM_FORMAT_S8:
  iismod &= ~S3C2410_IISMOD_16BIT;
  ((struct s3c24xx_pcm_dma_params *)
    rtd->dai->cpu_dai->dma_data)->dma_size = 1;
  break;
 case SNDRV_PCM_FORMAT_S16_LE:
  iismod |= S3C2410_IISMOD_16BIT;
  ((struct s3c24xx_pcm_dma_params *)
    rtd->dai->cpu_dai->dma_data)->dma_size = 2;
  break;
 default:
  return -EINVAL;
 }  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
 pr_debug("hw_params w: IISMOD: %x\n", iismod);
 return 0;
} static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
          struct snd_soc_dai *dai)
{
 int ret = 0;
 struct snd_soc_pcm_runtime *rtd = substream->private_data;
 int channel = ((struct s3c24xx_pcm_dma_params *)
    rtd->dai->cpu_dai->dma_data)->channel;  pr_debug("Entered %s\n", __func__);  switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
 case SNDRV_PCM_TRIGGER_RESUME:
 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  if (!s3c24xx_snd_is_clkmaster()) {
   ret = s3c24xx_snd_lrsync();
   if (ret)
    goto exit_err;
  }   if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
   s3c24xx_snd_rxctrl(1);
  else
   s3c24xx_snd_txctrl(1);   s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
  break;
 case SNDRV_PCM_TRIGGER_STOP:
 case SNDRV_PCM_TRIGGER_SUSPEND:
 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
   s3c24xx_snd_rxctrl(0);
  else
   s3c24xx_snd_txctrl(0);
  break;
 default:
  ret = -EINVAL;
  break;
 } exit_err:
 return ret;
}
static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
 int clk_id, unsigned int freq, int dir)
{
 u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);  pr_debug("Entered %s\n", __func__);  iismod &= ~S3C2440_IISMOD_MPLL;  switch (clk_id) {
 case S3C24XX_CLKSRC_PCLK:
  break;
 case S3C24XX_CLKSRC_MPLL:
  iismod |= S3C2440_IISMOD_MPLL;
  break;
 default:
  return -EINVAL;
 }  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
 return 0;
}
static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
 int div_id, int div)
{
 u32 reg;  pr_debug("Entered %s\n", __func__);  switch (div_id) {
 case S3C24XX_DIV_BCLK:
  reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
  writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
  break;
 case S3C24XX_DIV_MCLK:
  reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
  writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
  break;
 case S3C24XX_DIV_PRESCALER:
  writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
  reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
  writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
  break;
 default:
  return -EINVAL;
 }  return 0;
}
u32 s3c24xx_i2s_get_clockrate(void)
{
 return clk_get_rate(s3c24xx_i2s.iis_clk);
}
EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); static int s3c24xx_i2s_probe(struct platform_device *pdev,
        struct snd_soc_dai *dai)
{
 pr_debug("Entered %s\n", __func__);  s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
 if (s3c24xx_i2s.regs == NULL)
  return -ENXIO;  s3c24xx_i2s.iis_clk = clk_get(&pdev->dev, "iis");
 if (s3c24xx_i2s.iis_clk == NULL) {
  pr_err("failed to get iis_clock\n");
  iounmap(s3c24xx_i2s.regs);
  return -ENODEV;
 }
 clk_enable(s3c24xx_i2s.iis_clk);  
 s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
 s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
 s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
 s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
 s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);  writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);  s3c24xx_snd_txctrl(0);
 s3c24xx_snd_rxctrl(0);  return 0;
} #ifdef CONFIG_PM
static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai)
{
 pr_debug("Entered %s\n", __func__);  s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
 s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
 s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
 s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);  clk_disable(s3c24xx_i2s.iis_clk);  return 0;
} static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai)
{
 pr_debug("Entered %s\n", __func__);
 clk_enable(s3c24xx_i2s.iis_clk);  writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
 writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
 writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
 writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);  return 0;
}
#else
#define s3c24xx_i2s_suspend NULL
#define s3c24xx_i2s_resume NULL
#endif
#define S3C24XX_I2S_RATES \
 (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
 .trigger = s3c24xx_i2s_trigger,
 .hw_params = s3c24xx_i2s_hw_params,
 .set_fmt = s3c24xx_i2s_set_fmt,
 .set_clkdiv = s3c24xx_i2s_set_clkdiv,
 .set_sysclk = s3c24xx_i2s_set_sysclk,
}; struct snd_soc_dai s3c24xx_i2s_dai = {
 .name = "s3c24xx-i2s",
 .id = 0,
 .probe = s3c24xx_i2s_probe,
 .suspend = s3c24xx_i2s_suspend,
 .resume = s3c24xx_i2s_resume,
 .playback = {
  .channels_min = 2,
  .channels_max = 2,
  .rates = S3C24XX_I2S_RATES,
  .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
 .capture = {
  .channels_min = 2,
  .channels_max = 2,
  .rates = S3C24XX_I2S_RATES,
  .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
 .ops = &s3c24xx_i2s_dai_ops,
};
EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai); static int __init s3c24xx_i2s_init(void)
{
 return snd_soc_register_dai(&s3c24xx_i2s_dai);
}
module_init(s3c24xx_i2s_init); static void __exit s3c24xx_i2s_exit(void)
{
 snd_soc_unregister_dai(&s3c24xx_i2s_dai);
}
module_exit(s3c24xx_i2s_exit);
MODULE_AUTHOR("Ben Dooks, <[email protected]>");
MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
MODULE_LICENSE("GPL");
--------------------------------------------------------------------------------------------------

相關推薦

音訊audio/sound音效驅動分析

音訊可以播放(可以聽到聲音), 說明音訊解碼和輸出部分基本是正常的, 整個通道已經打通了. 感覺播放速度太快了(或太慢了)說明audio輸出部分的頻率不對, 太高了或者太低了. audio/sound音訊部分涉及的幾個頻率:   * 輸出取樣頻率 fs = 44.1KHz.

alsa音效驅動分析總結 (二)

alsa音效卡驅動分析總結  來自:http://blog.chinaunix.net/uid-20672559-id-3515392.html 現在我們開始分析ASOC: ASoC被分為Machine、Platform和Codec三大部分。其中的Machine驅動負責

解決win10音效驅動不相容問題和成功安裝戰神k650-i5-d2上的Sound Blaster Cinema2在win10系統上

安裝win10後,偶爾會出現藍屏,經過我的發現,每次聽歌用揚聲器並且長時間。都會發生藍屏 1 . 所以懷疑音效卡驅動VIA HD Audio(Win 8.1)與系統不相容。 2 .乾脆重新安裝音效卡驅動。在網上找到與win10相容的VIAHDAud_v11_1100e_01

android 音訊系統/音效驅動 codec

0. 專用術語 1. 物理結構 2. 系統架構 本文基於Freescale IMX平臺Codec ALC5625為例。 0. 專用術語  ASLA - Advanced Sound Linux Architecture  OSS - 以前的Linux音訊體系結構,被ASL

Linux ALSA音效驅動之六:ASoC架構中的Machine

前面一節的內容我們提到,ASoC被分為Machine、Platform和Codec三大部分,其中的Machine驅動負責Platform和Codec之間的耦合以及部分和裝置或板子特定的程式碼,再次引用上一節的內容:Machine驅動負責處理機器特有的一些控制元件和音訊

ALSA音效驅動中的DAPM詳解之二:widget-具備路徑和電源管理資訊的kcontrol

上一篇文章中,我們介紹了音訊驅動中對基本控制單元的封裝:kcontrol。利用kcontrol,我們可以完成對音訊系統中的mixer,mux,音量控制,音效控制,以及各種開關量的控制,通過對各種kcontrol的控制,使得音訊硬體能夠按照我們預想的結果進行工作。同時我

ALSA音效驅動中的DAPM詳解之七:dapm事件機制(dapm event)

前面的六篇文章,我們已經討論了dapm關於動態電源管理的有關知識,包括widget的建立和初始化,widget之間的連線以及widget的上下電順序等等。本章我們準備討論dapm框架中的另一個機制:事件機制。通過dapm事件機制,widget可以對它所關心的dapm事

關於音效驅動後的madplay安裝問題以及解決

安裝madplay所需要的庫檔案的時候 遇到了zlib.h找不到的原因 按照上面的內容執行會出現libz找不到的問題 後經過網上的查詢發現是Makefile中編譯器名字出現的問題 //////////////////////////////////////////////////////

音效驅動除錯過程

 ALSA音效卡驅動 https://blog.csdn.net/droidphone/article/category/1118446 PCM(Pulse-code modulation)脈衝編碼調製,把聲音從模擬轉換成數字訊號的一種技術 https://blog.csdn.

android下除錯音效驅動之概述

      在Android中音訊系統使用的是ALSA系統架構。ASoC--ALSA System on Chip ,是建立在標準ALSA驅動層上,為了更好地支援 嵌入式處理器和移動裝置中的音訊Cod

Linux ALSA音效驅動之八:ASoC架構中的Platform

1.  Platform驅動在ASoC中的作用 前面幾章內容已經說過,ASoC被分為Machine,Platform和Codec三大部件,Platform驅動的主要作用是完成音訊資料的管理,最終通過CPU的數字音訊介面(DAI)把音訊資料傳送給Codec進行處理,最終由Co

ALSA音效驅動中的DAPM詳解之五:建立widget之間的連線關係

前面我們主要著重於codec、platform、machine驅動程式中如何使用和建立dapm所需要的widget,route,這些是音訊驅動開發人員必須要了解的內容,經過前幾章的介紹,我們應該知道如何在alsa音訊驅動的3大部分(codec、platform、machin

AM335x(TQ335x)學習筆記——WM8960音效驅動移植

經過一段時間的除錯,終於調好了TQ335x的音效卡驅動。TQ335x採用的Codec是WM8960,本文來總結下WM8960驅動在AM335x平臺上的移植方法。Linux音效卡驅動架構有OSS和ALSA兩種架構,目前最常用的架構是ALSA,本文也使用ALSA架構對WM

Linux ALSA音效驅動之三:PCM裝置的建立

1. PCM是什麼         PCM是英文Pulse-code modulation的縮寫,中文譯名是脈衝編碼調製。我們知道在現實生活中,人耳聽到的聲音是模擬訊號,PCM就是要把聲音從模擬轉換成數字訊號的一種技術,他的原理簡單地說就是利用一個固定的頻率對模擬訊號進行取

android下除錯音效驅動之總結

1、在除錯中出現問題後,首先看I2C通訊有沒有問題,wm8960暫存器的設定是靠I2C來完成,另外I2C通訊不成功在開發板中是沒      有音效卡相關的裝置節點。       a、首先檢視Machi

android下除錯音效驅動之wm8960介紹二

三、LINPUT輸入通道介紹             有關LINPUT的主要配置如下:            R32的bit8(LMN1)置1:LINPUT1連線PGA;            R0

vmware Dos音效驅動安裝說明

Vmware虛擬機器中dos\win3.1音效卡驅動,Vmware使用虛擬創新SB16音效卡來實現的。在dos下執行install.exe就可以開始安裝了。同時注意檢視虛擬機器的vmx檔案,是不是有這樣幾條,如果沒有就要新增進去: sound.present = "TRU

ALSA音效驅動中的DAPM詳解之一:kcontrol

DAPM是Dynamic Audio Power Management的縮寫,直譯過來就是動態音訊電源管理的意思,DAPM是為了使基於linux的移動裝置上的音訊子系統,在任何時候都工作在最小功耗狀態下。DAPM對使用者空間的應用程式來說是透明的,所有與電源相關的開關都在A

Linux ALSA音效驅動之五:移動裝置中的ALSA

1.  ASoC的由來 ASoC--ALSA System on Chip ,是建立在標準ALSA驅動層上,為了更好地支援嵌入式處理器和移動裝置中的音訊Codec的一套軟體體系。在ASoc出現之前,核心對於SoC中的音訊已經有部分的支援,不過會有一些侷限性:  

Linux ALSA 音效驅動之一:ALSA架構簡介

一.  概述     ALSA是Advanced Linux Sound Architecture 的縮寫,目前已經成為了linux的主流音訊體系結構,想了解更多的關於ALSA的這一開源專案的資訊和知識,請檢視以下網址:http://www.alsa-project.org/。     在核心裝置驅動層,