音频audio/sound声卡驱动分析

来源:互联网 发布:淘宝商品规格怎么设置 编辑:程序博客网 时间:2024/04/28 03:26

音频可以播放(可以听到声音), 说明音频解码和输出部分基本是正常的, 整个通道已经打通了. 感觉播放速度太快了(或太慢了)说明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数据格式。

下面是电路原理图:

linuxmini2440I2S驱动
左边的CDCLK,I2SSCLK,I2SLRCK,I2SSDI,I2SDO是i2s总线引脚。CDCLK为音频编码器提供解码时钟,I2SSCLK提供了串行位时钟,I2SLRCK是声道控制时钟,I2SSDI声音数据输入,I2SDO声音数据输出。
另外的L3MODE,L3CLOCK,L3DATA控制声音。
linuxmini2440I2S驱动
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, intdiv)
{
 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, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
MODULE_LICENSE("GPL");
--------------------------------------------------------------------------------------------------


 

0 1