[转]基于uda34x的ALSA声卡驱动之s3c24xx_uda134x.c

来源:互联网 发布:淘宝卖家网页版电脑版 编辑:程序博客网 时间:2024/05/15 01:13

#include <linux/module.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/s3c24xx_uda134x.h>
#include <sound/uda134x.h>

#include <asm/plat-s3c24xx/regs-iis.h>

#include "s3c24xx-pcm.h"
#include "s3c24xx-i2s.h"
#include "../codecs/uda134x.h"


/* #define ENFORCE_RATES 1 */
/*
  Unfortunately the S3C24XX in master mode has a limited capacity of
  generating the clock for the codec. If you define this only rates
  that are really available will be enforced. But be careful, most
  user level application just want the usual sampling frequencies (8,
  11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
  operation for embedded systems. So if you aren't very lucky or your
  hardware engineer wasn't very forward-looking it's better to leave
  this undefined. If you do so an approximate value for the requested
  sampling rate in the range -/+ 5% will be chosen. If this in not
  possible an error will be returned.
*/

static struct clk *xtal;//iis支持两种时钟PCLK和MPLLin
static struct clk *pclk;
/* this is need because we don't have a place where to keep the
 * pointers to the clocks in each substream. We get the clocks only
 * when we are actually using them so we don't block stuff like
 * frequency change or oscillator power-off */
static int clk_users;
static DEFINE_MUTEX(clk_lock);//定义并初始化互斥锁

static unsigned int rates[33 * 2];//256fs,384fs,32分频
#ifdef ENFORCE_RATES
static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
 .count = ARRAY_SIZE(rates),
 .list = rates,
 .mask = 0,
};
#endif

static struct platform_device *s3c24xx_uda134x_snd_device;//很重要

static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
{
 int ret = 0;
#ifdef ENFORCE_RATES
 struct snd_pcm_runtime *runtime = substream->runtime;;
#endif

 mutex_lock(&clk_lock);
 pr_debug("%s %d/n", __func__, clk_users);
 if (clk_users == 0) {
  xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal");
  if (!xtal) {
   printk(KERN_ERR "%s cannot get xtal/n", __func__);
   ret = -EBUSY;
  } else {
   pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
           "pclk");
   if (!pclk) {
    printk(KERN_ERR "%s cannot get pclk/n",
           __func__);
    clk_put(xtal);
    ret = -EBUSY;
   }
  }
  if (!ret) {
   int i, j;

   for (i = 0; i < 2; i++) {
    int fs = i ? 256 : 384;
//256fs和384fs时所支持的32种频率
    rates[i*33] = clk_get_rate(xtal) / fs;
    for (j = 1; j < 33; j++)
     rates[i*33 + j] = clk_get_rate(pclk) /
      (j * fs);
   }
  }
 }
 clk_users += 1;//使用计数
 mutex_unlock(&clk_lock);
 if (!ret) {
#ifdef ENFORCE_RATES
  ret = snd_pcm_hw_constraint_list(runtime, 0,
       SNDRV_PCM_HW_PARAM_RATE,
       &hw_constraints_rates);//添加所支持的频率
  if (ret < 0)
   printk(KERN_ERR "%s cannot set constraints/n",
          __func__);
#endif
 }
 return ret;
}

static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
{
 mutex_lock(&clk_lock);
 pr_debug("%s %d/n", __func__, clk_users);
 clk_users -= 1;
 if (clk_users == 0) {
  clk_put(xtal);
  xtal = NULL;
  clk_put(pclk);
  pclk = NULL;
 }
 mutex_unlock(&clk_lock);
}

static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
     struct snd_pcm_hw_params *params)
{//rtd的初始化在文件udal34x.c中,函数snd_soc_new_pcms-->soc_new_pcm,
//挂在substream->private_data上
 struct snd_soc_pcm_runtime *rtd = substream->private_data;
 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;//该结构在udal34x.c中
 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;//该结构在s3c24xx-i2s.c中
 unsigned int clk = 0;
 int ret = 0;
 int clk_source, fs_mode;
 /*在snd_pcm_hw_params中有一个数组,包含10个结构体
 元素intervals,用于存储各参数的最大最小值。由用户空间设置。
 */
 unsigned long rate = params_rate(params);//获取采样率
 long err, cerr;
 unsigned int div;
 int i, bi;

 err = 999999;
 bi = 0;
 for (i = 0; i < 2*33; i++) {//在所支持的频率中找一个与所设采样率rate最接近的一个
  cerr = rates[i] - rate;
  if (cerr < 0)
   cerr = -cerr;
  if (cerr < err) {
   err = cerr;
   bi = i;
  }
 }
 if (bi / 33 == 1)//0~32为384fs
  fs_mode = S3C2410_IISMOD_256FS;
 else
  fs_mode = S3C2410_IISMOD_384FS;
 if (bi % 33 == 0) {//iis的时钟选择PCLK和MPLLin
  clk_source = S3C24XX_CLKSRC_MPLL;
  div = 1;
 } else {
  clk_source = S3C24XX_CLKSRC_PCLK;
  div = bi % 33;
 }
 pr_debug("%s desired rate %lu, %d/n", __func__, rate, bi);

 clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;//计算分频值
 pr_debug("%s will use: %s %s %d sysclk %d err %ld/n", __func__,
   fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
   clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
   div, clk, err);

 if ((err * 100 / rate) > 5) {
  printk(KERN_ERR "S3C24XX_UDA134X: effective frequency "
         "too different from desired (%ld%%)/n",
         err * 100 / rate);
  return -EINVAL;
 }
//iis工作模式设置调用函数dai->ops.set_fmt(dai, fmt);
 ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
   SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
 if (ret < 0)
  return ret;

 ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
   SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
 if (ret < 0)
  return ret;
//iis主设备时钟选择
 ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
   SND_SOC_CLOCK_IN);
 if (ret < 0)
  return ret;

 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
 if (ret < 0)
  return ret;

 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
   S3C2410_IISMOD_32FS);
 if (ret < 0)
  return ret;
//设置分频值
 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
   S3C24XX_PRESCALE(div, div));
 if (ret < 0)
  return ret;

 /* set the codec system clock for DAC and ADC */
 ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
   SND_SOC_CLOCK_OUT);
 if (ret < 0)
  return ret;

 return 0;
}

static struct snd_soc_ops s3c24xx_uda134x_ops = {
 .startup = s3c24xx_uda134x_startup,
 .shutdown = s3c24xx_uda134x_shutdown,
 .hw_params = s3c24xx_uda134x_hw_params,
};

static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
 .name = "UDA134X",
 .stream_name = "UDA134X",
 //uda134x_dai包含一些录音和播放的参数,以及对uda134x操作的函数(设置音量,静音等)
 //uda134x.C中的初始化函数包含在soc_codec_dev_uda134x中,包含一些很重要的初始化。
 .codec_dai = &uda134x_dai,
 //s3c24xx_i2s_dai包含初始化函数,录音播放参数,以及对iis寄存器的设置。
 .cpu_dai = &s3c24xx_i2s_dai,
 .ops = &s3c24xx_uda134x_ops,
};
/*
snd_soc_card是大部分情况下使用的card,不是结构体snd_card(codec->card),
在uda134x.C中奖构造一个snd_card的card。在驱动soc_driver的初始化函数soc_probe
中,有这样一句card->dev = &pdev->dev;(在文件soc-core.c中)将让card->dev指向平台设备
s3c24xx_uda134x_snd_device的dev。这将是所有probe函数的参数struct platform_device *pdev
的来源。结构体s3c24xx_soc_platform包含PCM流的操作函数,实现录音播放等操作。
结构体s3c24xx_uda134x_dai_link包含uda34x和iis的操作函数,和一个PCM实体。
num_links即为PCM的实例数。PCM实例将在uda134x.C中的probe函数中构造。
     每个声卡最多可有四个PCM实例。PCM实例由pcm播放流和录音流组成。每个pcm流又由
     多个pcm子流组成。
*/
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
 .name = "S3C24XX_UDA134X",
 .platform = &s3c24xx_soc_platform,
 .dai_link = &s3c24xx_uda134x_dai_link,
 .num_links = 1,
};

static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins;
//几个管脚的状态设置函数
static void setdat(int v)
{
 gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0);
}

static void setclk(int v)
{
 gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0);
}

static void setmode(int v)
{
 gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0);
}
//l3_data,l3_clk,l3_mode为与编解码芯片uda134x相连的几个管脚,
static struct uda134x_platform_data s3c24xx_uda134x = {
 .l3 = {
  .setdat = setdat,
  .setclk = setclk,
  .setmode = setmode,
  .data_hold = 1,
  .data_setup = 1,
  .clock_high = 1,
  .mode_hold = 1,
  .mode = 1,
  .mode_setup = 1,
 },
};
/*
 结构体soc_codec_dev_uda134x中为文件uda134x.C中的probe函数,进行
 一些重要的初始化,如构造结构体snd_card,构造pcm流实例等。
 s3c24xx_uda134x为管理与芯片uda134x相连的几个管脚的结构体。
*/
static struct snd_soc_device s3c24xx_uda134x_snd_devdata = {
 .card = &snd_soc_s3c24xx_uda134x,
 .codec_dev = &soc_codec_dev_uda134x,
 .codec_data = &s3c24xx_uda134x,
};

static int s3c24xx_uda134x_setup_pin(int pin, char *fun)
{
 if (gpio_request(pin, "s3c24xx_uda134x") < 0) {//获取该管脚的使用权
  printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
         "l3 %s pin already in use", fun);
  return -EBUSY;
 }
 gpio_direction_output(pin, 0);//配置管脚为是输出,且初始化管脚状态为0
 return 0;
}

static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
 int ret;

 printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver/n");

 s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;//驱动移植时手动添加
 if (s3c24xx_uda134x_l3_pins == NULL) {
  printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
         "unable to find platform data/n");
  return -ENODEV;
 }
 s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
 s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;

 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
          "data") < 0)
  return -EBUSY;
 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
          "clk") < 0) {
  gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
  return -EBUSY;
 }
 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
          "mode") < 0) {
  gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
  gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
  return -EBUSY;
 }
//分配一个平台设备内存,并初始化。匹配文件soc-core.c中建立的驱动soc_driver
 s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
 if (!s3c24xx_uda134x_snd_device) {
  printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
         "Unable to register/n");
  return -ENOMEM;
 }
//将重要结构体s3c24xx_uda134x_snd_devdata设为平台设备的drvdata
 platform_set_drvdata(s3c24xx_uda134x_snd_device,
        &s3c24xx_uda134x_snd_devdata);
 //将结构体snd_soc_device(其他地方多以socdev出现)的dev指向平台设备的dev
 s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev;
 ret = platform_device_add(s3c24xx_uda134x_snd_device);//添加平台设备到内核
 if (ret) {
  printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add/n");
  platform_device_put(s3c24xx_uda134x_snd_device);
 }

 return ret;
}

static int s3c24xx_uda134x_remove(struct platform_device *pdev)
{
 platform_device_unregister(s3c24xx_uda134x_snd_device);
 gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
 gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
 gpio_free(s3c24xx_uda134x_l3_pins->l3_mode);
 return 0;
}
//在驱动移植时手动加入名为"s3c24xx_uda134x"的平台设备
static struct platform_driver s3c24xx_uda134x_driver = {
 .probe  = s3c24xx_uda134x_probe,
 .remove = s3c24xx_uda134x_remove,
 .driver = {
  .name = "s3c24xx_uda134x",
  .owner = THIS_MODULE,
 },
};

static int __init s3c24xx_uda134x_init(void)
{
 return platform_driver_register(&s3c24xx_uda134x_driver);
}

static void __exit s3c24xx_uda134x_exit(void)
{
 platform_driver_unregister(&s3c24xx_uda134x_driver);
}


module_init(s3c24xx_uda134x_init);
module_exit(s3c24xx_uda134x_exit);

MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
MODULE_LICENSE("GPL");

原创粉丝点击