本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记
由“Linux音频驱动之一:音频驱动注册流程” 这篇文章可知,pcm device调用的是snd_pcm_dev_register函数注册的。
snd_pcm_dev_register
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,
&snd_pcm_f_ops[cidx],pcm, str, dev);
当注册播放pcm设备时,fops是snd_pcm_f_ops[0];
当注册录制pcm设备时,fops是snd_pcm_f_ops[1];
现在以播放pcm设备为例,看一下snd_pcm_f_ops[0]。
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = dummy_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = dummy_get_unmapped_area,
}
};
当打开pcm device设备时,就会调用上面的snd_pcm_playback_open函数。
进入snd_pcm_playback_open函数分析。
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
struct snd_pcm *pcm;
pcm = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
}
#define CONFIG_SND_MAJOR 116
dev_t = 116 << 20 | minor
最后把dev_t注册进了内核。
现在open时根据次设备号找到snd_minors[minor],得到snd_pcm数据。
err = snd_card_file_add(pcm->card, file);
err = snd_pcm_open_file(file, pcm, stream, &pcm_file);
err = snd_pcm_open_substream(pcm, stream, file, &substream);
2.申请一个snd_pcm_file结构体,把获取到的snd_pcm_substream数据保存到snd_pcm_file.substream。
然后把snd_pcm_file赋值给file->private_data。
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
pstr = &pcm->streams[stream];
for (substream = pstr->substream; substream; substream = substream->next)
if (!SUBSTREAM_BUSY(substream))
break;
根据snd_pcm.streams[0]得到播放的snd_pcm_str。
循环查找snd_pcm_str的substream,找到substream->ref_count == 0 的那个substream。这个substream就是我们我们上面想要的snd_pcm_substream数据。
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = snd_malloc_pages(size, GFP_KERNEL);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = snd_malloc_pages(size, GFP_KERNEL);
申请一个snd_pcm_runtime类型的数据runtime,然后给runtime->status和runtime->control分配空间。
填充substream数据。
substream->runtime = runtime;
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->f_flags = file->f_flags;
pstr->substream_opened++;
struct snd_pcm_hw_constraints {
struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
unsigned int rules_num;
unsigned int rules_all;
struct snd_pcm_hw_rule *rules;
};
SNDRV_PCM_HW_PARAM_FIRST_MASK = 0,SNDRV_PCM_HW_PARAM_LAST_MASK = 2;
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = 8, SNDRV_PCM_HW_PARAM_LAST_INTERVAL = 19
因此:
struct snd_pcm_hw_constraints {
struct snd_mask masks[3];
struct snd_interval intervals[12];
unsigned int rules_num;
unsigned int rules_all;
struct snd_pcm_hw_rule *rules;
};
#define SNDRV_MASK_MAX 256
struct snd_mask
{
__u32 bits[(SNDRV_MASK_MAX+31)/32];
};
struct snd_interval {
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
};
因此:
struct snd_pcm_hw_constraints
{
struct snd_mask masks[3] =
{
int bits[8]; //设置Access type
int bits[8]; //设置Format
int bits[8]; //设置Subformat
};
struct snd_interval intervals[12]=
{
//设置采样数据位数
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置每帧数据位数
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置通道
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置采样率
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置中断之间的时间间隔
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置中断之间的数据帧(不知道是个啥意思)
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置终端之间的bytes(不知道是个啥意思)
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置中断个数
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置缓冲时间
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置缓冲大小
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置缓冲大小
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
//设置Approx tick duration
{
unsigned int min,
unsigned int max,
unsigned int openmin:1,
openmax:1,
integer:1,
empty:1;
},
}
unsigned int rules_num;
unsigned int rules_all;
struct snd_pcm_hw_rule *rules;
};
masks元素的前两个设置成0xff
for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) {
snd_mask_any(constrs_mask(constrs, k));
}
设置后:
struct snd_mask masks[3] =
{
int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ; //设置Access type
int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ; //设置Format
int bits[8] = {0xffffffff,0xffffffff,0,0,0,0,0,0} ; //设置Subformat
};
初始化intervals[12]的所有元素,元素里面的变量全部设置为0。
for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
snd_interval_any(constrs_interval(constrs, k));
}
static inline void snd_interval_any(struct snd_interval *i)
{
i->min = 0;
i->openmin = 0;
i->max = UINT_MAX;
i->openmax = 0;
i->integer = 0;
i->empty = 0;
}
初始化intervals[12]的整数标记,就是intervals[12]中的元素哪几个要设置成整数的。
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS));
snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS));
通道,缓冲大小,采样位数,帧位数等要设置成整数。
添加rule规则,就是给snd_pcm_hw_constraints.rules赋值。
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,snd_pcm_hw_rule_format, NULL,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond,
int var,
snd_pcm_hw_rule_func_t func, void *private,
int dep, ...)
{
struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
struct snd_pcm_hw_rule *c;
unsigned int k;
va_list args;
va_start(args, dep);
if (constrs->rules_num >= constrs->rules_all) {
struct snd_pcm_hw_rule *new;
unsigned int new_rules = constrs->rules_all + 16;
new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL);
if (!new)
return -ENOMEM;
if (constrs->rules) {
memcpy(new, constrs->rules,
constrs->rules_num * sizeof(*c));
kfree(constrs->rules);
}
constrs->rules = new;
constrs->rules_all = new_rules;
}
c = &constrs->rules[constrs->rules_num];
c->cond = cond;
c->func = func;
c->var = var;
c->private = private;
k = 0;
while (1) {
if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps)))
return -EINVAL;
c->deps[k++] = dep;
if (dep < 0)
break;
dep = va_arg(args, int);
}
constrs->rules_num++;
va_end(args);
return 0;
}
其中va_start,va_arg,va_end是传入可变参数用的。
va_start(args, dep):也就是让args指针指向dep变量所在的地址;
va_arg(args, int):让args指针偏移到下一个地址,同时返回里面的值,int类型。偏移结束的条件是取出来的值小于0。
va_end(args):args = 0
上面的代码把args指针里面的取出来的值保存在c->deps数组中。把上面的snd_pcm_hw_rule_add函数翻译一下得到:
constrs.rules[0] =
{
cond = 0;
func = snd_pcm_hw_rule_format;
var = SNDRV_PCM_HW_PARAM_FORMAT
deps[0] = SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8;
private = NULL;
}
下面添加了很多的规则,就不一一介绍了。
soc_pcm_ops.open = soc_pcm_open,
soc_pcm_ops.close = soc_codec_close,
soc_pcm_ops.hw_params = soc_pcm_hw_params,
soc_pcm_ops.hw_free = soc_pcm_hw_free,
soc_pcm_ops.prepare = soc_pcm_prepare,
soc_pcm_ops.trigger = soc_pcm_trigger,
soc_pcm_ops.mmap = platform->pcm_ops->mmap;
soc_pcm_ops.pointer = platform->pcm_ops->pointer;
soc_pcm_ops.ioctl = platform->pcm_ops->ioctl;
soc_pcm_ops.copy = platform->pcm_ops->copy;
soc_pcm_ops.silence = platform->pcm_ops->silence;
soc_pcm_ops.ack = platform->pcm_ops->ack;
soc_pcm_ops.page = platform->pcm_ops->page;
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
可以知道substream->ops->open = soc_pcm_open函数。
这一步就是调用soc_pcm_open函数。
if (cpu_dai->ops->startup)
{
ret = cpu_dai->ops->startup(substream, cpu_dai);
if (ret < 0) {
printk(KERN_ERR "asoc: can't open interface %s\n",
cpu_dai->name);
goto out;
}
}
cpu_dai = s3c24xx_i2s_dai,s3c24xx_i2s_dai->ops->open = NULL,这里不执行。
2.调用platform->pcm_ops->open
if (platform->pcm_ops->open)
{
ret = platform->pcm_ops->open(substream);
if (ret < 0) {
printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
goto platform_err;
}
}
platform = s3c24xx_soc_platform,s3c24xx_soc_platform->pcm_ops->open = s3c24xx_pcm_open
进入s3c24xx_pcm_open函数。
static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct s3c24xx_runtime_data *prtd;
pr_debug("Entered %s\n", __func__);
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
spin_lock_init(&prtd->lock);
runtime->private_data = prtd;
return 0;
}
设置SNDRV_PCM_HW_PARAM_PERIODS项时,要用整形数据。
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
对snd_pcm_runtime.hw赋值。
runtime->hw.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME;
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |SNDRV_PCM_FMTBIT_S8;
runtime->hw.period_bytes_min = 4Kb;
runtime->hw.period_bytes_max = 8Kb;
runtime->hw.periods_min = 2;
runtime->hw.periods_max =128 ;
runtime->hw.buffer_bytes_max =128Kb ;
runtime->hw.fifo_size = 32;
3.调用codec_dai->ops->startup函数。
if (codec_dai->ops->startup)
{
ret = codec_dai->ops->startup(substream, codec_dai);
if (ret < 0) {
printk(KERN_ERR "asoc: can't open codec %s\n",
codec_dai->name);
goto codec_dai_err;
}
}
codec_dai = uda134x_dai,codec_dai->ops->startup = uda134x_startup,分析一下uda134x_startup函数。
static int uda134x_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->card->codec;
struct uda134x_priv *uda134x = codec->private_data;
struct snd_pcm_runtime *master_runtime;
if (uda134x->master_substream) {
master_runtime = uda134x->master_substream->runtime;
pr_debug("%s constraining to %d bits at %d\n", __func__,
master_runtime->sample_bits,
master_runtime->rate);
snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_RATE,
master_runtime->rate,
master_runtime->rate);
snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
master_runtime->sample_bits,
master_runtime->sample_bits);
uda134x->slave_substream = substream;
} else
uda134x->master_substream = substream;
//
uda134x_write(codec, 2, 2|(5U<<2));
return 0;
}
设置采样率
snd_pcm_hw_constraint_minmax(substream->runtime,SNDRV_PCM_HW_PARAM_RATE,
master_runtime->rate,master_runtime->rate);
设置采样位数,8位或者16位或者其他
snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,master_runtime->sample_bits,
master_runtime->sample_bits);
设置mic的灵敏度,以及录制通道
uda134x_write(codec, 2, 2|(5U<<2));
4.调用machine->ops->startup函数
machine = s3c24xx_uda134x_dai_link,machine->ops->startup = s3c24xx_uda134x_startup
这个函数的作用是通过pclk和xtal得到采样率。
for (i = 0; i < 2; i++)
{
int fs = i ? 256 : 384;
rates[i*33] = clk_get_rate(xtal) / fs;
for (j = 1; j < 33; j++)
rates[i*33 + j] = clk_get_rate(pclk) / (j * fs);
}
5.根据codec_dai和cpu_dai重新计算runtime->hw的一些值,需要同时满足codec_dai和cpu_dai
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
{
runtime->hw.rate_min =max(codec_dai->playback.rate_min,cpu_dai->playback.rate_min);
runtime->hw.rate_max =min(codec_dai->playback.rate_max,cpu_dai->playback.rate_max);
runtime->hw.channels_min =max(codec_dai->playback.channels_min,cpu_dai->playback.channels_min);
runtime->hw.channels_max =min(codec_dai->playback.channels_max,cpu_dai->playback.channels_max);
runtime->hw.formats =codec_dai->playback.formats & cpu_dai->playback.formats;
runtime->hw.rates =codec_dai->playback.rates & cpu_dai->playback.rates;
}