Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2224136
  • 博文数量: 668
  • 博客积分: 10016
  • 博客等级: 上将
  • 技术积分: 8588
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-29 19:22
文章分类

全部博文(668)

文章存档

2011年(1)

2010年(2)

2009年(273)

2008年(392)

分类:

2009-09-20 11:09:32

4、put()函数
put()用于从用户空间写入值,如果值被改变,该函数返回1,否则返回0;如果发生错误,该函数返回1个错误码。代码清单17.22给出了1个put()函数的范例。
代码清单17.22 snd_ctl_elem_info结构体中put()函数范例
1 static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, struct
2    snd_ctl_elem_value *ucontrol)
3 {
4    //从snd_kcontrol获得xxxchip指针
5    struct xxxchip *chip = snd_kcontrol_chip(kcontrol);
6    int changed = 0;//缺省返回值为0
7    //值被改变
8    if (chip->current_value != ucontrol->value.integer.value[0])
9    {
10     change_current_value(chip, ucontrol->value.integer.value[0]);
11     changed = 1;//返回值为1
12   }
13   return changed;
14 }
对于get()和put()函数而言,如果control有多于1个元素,即count>1,则每个元素都需要被返回或写入。
5、构造control
当所有事情准备好后,我们需要创建1个control,调用snd_ctl_add()和snd_ctl_new1()这2个函数来完成,这2个函数的原型为:
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);

struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
      void *private_data);
snd_ctl_new1()函数用于创建1个snd_kcontrol并返回其指针,snd_ctl_add()函数用于将创建的snd_kcontrol添加到对应的card中。
6、变更通知
如果驱动中需要在中断服务程序中改变或更新1个control,可以调用snd_ctl_notify()函数,此函数原型为:
void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id);
该函数的第2个参数为事件掩码(event-mask),第3个参数为该通知的control元素id指针。
例如,如下语句定义的事件掩码SNDRV_CTL_EVENT_MASK_VALUE意味着control值的改变被通知:
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);
17.4.4 AC97 API接口
ALSA AC97编解码层被很好地定义,利用它,驱动工程师只需编写少量底层的控制函数。
1、AC97实例构造
为了创建1个AC97实例,首先需要调用snd_ac97_bus()函数构建AC97总线及其操作,这个函数的原型为:
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,
   void *private_data, struct snd_ac97_bus **rbus);
该函数的第3个参数ops是1个snd_ac97_bus_ops结构体,其定义如代码清单17.23。
代码清单17.23 snd_ac97_bus_ops结构体
1 struct snd_ac97_bus_ops
2 {
3    void(*reset)(struct snd_ac97 *ac97); //复位函数
4    //写入函数
5    void(*write)(struct snd_ac97 *ac97, unsigned short reg, unsigned short val);
6    //读取函数
7    unsigned short(*read)(struct snd_ac97 *ac97, unsigned short reg);
8    void(*wait)(struct snd_ac97 *ac97);
9    void(*init)(struct snd_ac97 *ac97);
10 };
接下来,调用snd_ac97_mixer()函数注册混音器,这个函数的原型为:
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);
代码清单17.24演示了AC97实例的创建过程。
代码清单17.24 AC97实例的创建过程范例
1 struct snd_ac97_bus *bus;
2 //AC97总线操作
3 static struct snd_ac97_bus_ops ops =
4 {
5    .write = snd_mychip_ac97_write,
6    .read = snd_mychip_ac97_read,
7 };
8 //AC97总线与操作创建
9 snd_ac97_bus(card, 0, &ops, NULL, &bus);
10 //AC97模板
11 struct snd_ac97_template ac97;
12 int err;
13 memset(&ac97, 0, sizeof(ac97));
14 ac97.private_data = chip;//私有数据
15 //注册混音器
16 snd_ac97_mixer(bus, &ac97, &chip->ac97);
上述代码第1行的snd_ac97_bus结构体指针bus的指针被传入第9行的snd_ac97_bus()函数并被赋值,chip->ac97的指针被传入第16行的snd_ac97_mixer()并被赋值,chip->ac97将成员新创建AC97实例的指针。
如果1个声卡上包含多个编解码器,这种情况下,需要多次调用snd_ac97_mixer()并对snd_ac97的num成员(编解码器序号)赋予相应的序号。驱动中可以为不同的编解码器编写不同的snd_ac97_bus_ops成员函数中,或者只是在相同的一套成员函数中通过ac97.num获得序号后再区分进行具体的操作。
2、snd_ac97_bus_ops成员函数
snd_ac97_bus_ops结构体中的read()和write()成员函数完成底层的硬件访问,reset()函数用于复位编解码器,wait()函数用于编解码器标准初始化过程中的特定等待,如果芯片要求额外的等待时间,应实现这个函数,init()用于完成编解码器附加的初始化。代码清单17.25给出了read()和write()函数的范例。
代码清单17.25 snd_ac97_bus_ops结构体中read()和write()函数范例
1 static unsigned short snd_xxxchip_ac97_read(struct snd_ac97 *ac97, unsigned
2    short reg)
3 {
4    struct xxxchip *chip = ac97->private_data;
5    ...
6    return the_register_value; //返回寄存器值
7 }
8
9 static void snd_xxxchip_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
10   unsigned short val)
11 {
12   struct xxxchip *chip = ac97->private_data;
13   ...
14   // 将被给的寄存器值写入codec
15 }
3、修改寄存器
如果需要在驱动中访问编解码器,可使用如下函数:
void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value);

unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
snd_ac97_update()与void snd_ac97_write()的区别在于前者在值已经设置的情况下不会再设置,而后者则会再写一次。snd_ac97_update_bits()用于更新寄存器的某些位,由mask决定。
除此之外,还有1个函数可用于设置采样率:
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
这个函数的第2个参数reg可以是AC97_PCM_MIC_ADC_RATE、AC97_PCM_FRONT_DAC_RATE、 AC97_PCM_LR_ADC_RATE和AC97_SPDIF,对于AC97_SPDIF而言,寄存器并非真地被改变了,只是相应的IEC958状态位将被更新。
4、时钟调整
在一些芯片上,编解码器的时钟不是48000而是使用PCI时钟以节省1个晶体,在这种情况下,我们应该改变bus->clock为相应的值,例如intel8x0和es1968包含时钟的自动测量函数。
5、proc文件
ALSA AC97接口会创建如/proc/asound/card0/codec97#0/ac97#0-0和ac97#0-0+regs这样的proc文件,通过这些文件可以察看编解码器目前的状态和寄存器。
如果1个chip上有多个codecs,可多次调用snd_ac97_mixer()。
17.4.5 ALSA用户空间编程
ALSA驱动的声卡在用户空间不宜直接使用文件接口,而应使用alsa-lib,代码清单17.26给出了基于ALSA音频驱动的最简单的放音应用程序。
代码清单17.26 ALSA用户空间放音程序
1 #include
2 #include
3 #include
4
5 main(int argc, char *argv[])
6 {
7    int i;
8    int err;
9    short buf[128];
10   snd_pcm_t *playback_handle;   //PCM设备句柄
11   snd_pcm_hw_params_t *hw_params; //硬件信息和PCM流配置
12   //打开PCM,最后1个参数为0意味着标准配置
13   if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)
14     ) < 0)
15   {
16     fprintf(stderr, "cannot open audio device %s (%s)\n", argv[1], snd_strerror
17       (err));
18     exit(1);
19   }
20   //分配snd_pcm_hw_params_t结构体
21   if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
22   {
23     fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
24       snd_strerror(err));
25     exit(1);
26   }
27   //初始化hw_params
28   if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
29   {
30     fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
31       snd_strerror(err));
32     exit(1);
33   }
34   //初始化访问权限
35   if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,
36     SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
37   {
38     fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
39     exit(1);
40   }
41   //初始化采样格式
42   if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,
43     SND_PCM_FORMAT_S16_LE)) < 0)
44   {
45     fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
46     exit(1);
47   }
48   //设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的
49   if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,
50     0)) < 0)
51   {
52     fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
53     exit(1);
54   }
55   //设置通道数量
56   if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0)
57   {
58     fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
59     exit(1);
60   }
61   //设置hw_params
62   if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
63   {
64     fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
65     exit(1);
66   }
67   //释放分配的snd_pcm_hw_params_t结构体
68   snd_pcm_hw_params_free(hw_params);
69   //完成硬件参数设置,使设备准备好
70   if ((err = snd_pcm_prepare(playback_handle)) < 0)
71   {
72     fprintf(stderr, "cannot prepare audio interface for use (%s)\n",
73       snd_strerror(err));
74     exit(1);
75   }
76
77   for (i = 0; i < 10; ++i)
78   {
79     //写音频数据到PCM设备
80     if ((err = snd_pcm_writei(playback_handle, buf, 128)) != 128)
81     {
82       fprintf(stderr, "write to audio interface failed (%s)\n", snd_strerror
83         (err));
84       exit(1);
85     }
86   }
87   //关闭PCM设备句柄
88   snd_pcm_close(playback_handle);
89   exit(0);
90 }
由上述代码可以看出,ALSA用户空间编程的流程与17.3.4节给出的OSS驱动用户空间编程的流程基本是一致的,都经过了“打开――设置参数――读写音频数据”的过程,不同在于OSS打开的是设备文件,设置参数使用的是Linux ioctl()系统调用,读写音频数据使用的是Linux read()、write()文件API,而ALSA则全部使用alsa-lib中的API。
把上述代码第80行的snd_pcm_writei()函数替换为snd_pcm_readi()就编程了1个最简单的录音程序。
代码清单17.27的程序打开1个音频接口,配置它为立体声、16位、44.1khz采样和基于interleave的读写。它阻塞等待直接接口准备好接收放音数据,这时候将数据拷贝到缓冲区。这种设计方法使得程序很容易移植到类似JACK、LADSPA、Coreaudio、VST等callback机制驱动的系统。
代码清单17.27 ALSA用户空间放音程序(基于“中断”)
1   #include
2   #include
3   #include
4   #include
5   #include
6
7   snd_pcm_t *playback_handle;
8   short buf[4096];
9
10 int playback_callback(snd_pcm_sframes_t nframes)
11 {
12    int err;
13    printf("playback callback called with %u frames\n", nframes);
14    /* 填充缓冲区 */
15    if ((err = snd_pcm_writei(playback_handle, buf, nframes)) < 0)
16    {
17      fprintf(stderr, "write failed (%s)\n", snd_strerror(err));
18    }
19
20    return err;
21 }
22
23 main(int argc, char *argv[])
24 {
25
26    snd_pcm_hw_params_t *hw_params;
27    snd_pcm_sw_params_t *sw_params;
28    snd_pcm_sframes_t frames_to_deliver;
29    int nfds;
30    int err;
31    struct pollfd *pfds;
32
33    if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)
34      ) < 0)
35    {
36      fprintf(stderr, "cannot open audio device %s (%s)\n", argv[1], snd_strerror
37        (err));
38      exit(1);
39    }
40
41    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
42    {
43      fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
44        snd_strerror(err));
45      exit(1);
46    }
47
48    if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0)
49    {
50      fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
51        snd_strerror(err));
52      exit(1);
53    }
54
55    if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,
56      SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
57    {
58      fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
59      exit(1);
60    }
61
62    if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,
63      SND_PCM_FORMAT_S16_LE)) < 0)
64    {
65      fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
66      exit(1);
67    }
68
69    if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,
70      0)) < 0)
71    {
72      fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
73      exit(1);
74    }
75
76    if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0)
77    {
78      fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
79      exit(1);
80    }
81
82    if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0)
83    {
84      fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
85      exit(1);
86    }
87
88    snd_pcm_hw_params_free(hw_params);
89
90    /* 告诉ALSA当4096个以上帧可以传递时唤醒我们 */
91    if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0)
92    {
93      fprintf(stderr, "cannot allocate software parameters structure (%s)\n",
94        snd_strerror(err));
95      exit(1);
96    }
97    if ((err = snd_pcm_sw_params_current(playback_handle, sw_params)) < 0)
98    {
99      fprintf(stderr, "cannot initialize software parameters structure (%s)\n",
100       snd_strerror(err));
101     exit(1);
102   }
103   /* 设置4096帧传递一次数据 */
104   if ((err = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params, 4096))
105     < 0)
106   {
107     fprintf(stderr, "cannot set minimum available count (%s)\n", snd_strerror
108       (err));
109     exit(1);
110   }
111   /* 一旦有数据就开始播放 */
112   if ((err = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params,
113     0U)) < 0)
114   {
115     fprintf(stderr, "cannot set start mode (%s)\n", snd_strerror(err));
116     exit(1);
117   }
118   if ((err = snd_pcm_sw_params(playback_handle, sw_params)) < 0)
119   {
120     fprintf(stderr, "cannot set software parameters (%s)\n", snd_strerror(err));
121     exit(1);
122   }
123
124   /* 每4096帧接口将中断内核,ALSA将很快唤醒本程序 */
125
126   if ((err = snd_pcm_prepare(playback_handle)) < 0)
127   {
128     fprintf(stderr, "cannot prepare audio interface for use (%s)\n",
129       snd_strerror(err));
130     exit(1);
131   }
132
133   while (1)
134   {
135
136     /* 等待,直到接口准备好传递数据,或者1秒超时发生 */
137     if ((err = snd_pcm_wait(playback_handle, 1000)) < 0)
138     {
139       fprintf(stderr, "poll failed (%s)\n", strerror(errno));
140       break;
141     }
142
143     /* 查出有多少空间可放置playback数据 */
144     if ((frames_to_deliver = snd_pcm_avail_update(playback_handle)) < 0)
145     {
146       if (frames_to_deliver == - EPIPE)
147       {
148         fprintf(stderr, "an xrun occured\n");
149         break;
150       }
151       else
152       {
153         fprintf(stderr, "unknown ALSA avail update return value (%d)\n",
154           frames_to_deliver);
155         break;
156       }
157     }
158
159     frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
160
161     /* 传递数据 */
162     if (playback_callback(frames_to_deliver) != frames_to_deliver)
163     {
164       fprintf(stderr, "playback callback failed\n");
165       break;
166     }
167   }
168
169   snd_pcm_close(playback_handle);
170   exit(0);
171 }
17.5实例1:S3C2410+UDA1341 OSS驱动
17.5.1 S3C2410与UDA1341接口硬件描述
如图17.7,S3C2410处理器内置了IIS总线接口,S3C2410的IIS总线时钟信号SCK与Philip公司的UDA1341的BCK连接,字段选择连接于WS引脚。UDA1341提供两个音频通道,分别用于输入和输出,对应的引脚连接:IIS总线的音频输出IISSDO对应于UDA1341的音频输入;IIS总线的音频输入IISSDI对应于UDA1341的音频输出。UDA1341的L3接口相当于一个混音器控制接口,可以用来控制输入/输出音频信号的音量大小、低音等。L3接口的引脚L3MODE、L3DATA、L3CLOCK分别连接到S3C2410的3个GPIO来控制。

   
图17.7 S3C2410与UDA1341 IIS接口连接
Philips 公司的UDA1341支持IIS总线数据格式,采用位元流转换技术进行信号处理,完成声音信号的模数转换,具有可编程增益放大器和数字自动增益控制器,其低功耗、低电压的特点使其非常适合用于MD/CD、笔记本电脑等便携式设备。UDA1341对外提供2组音频信号输入接口,每组包括左右2个声道。

图17.8 UDA1341 内部结构
如图17.8所示,2组音频输入在UDA1341内部的处理存在很大差别:第一组音频信号输入后经过1个0 dB/6 dB开关后采样送入数字混音器:第二组音频信号输入后先经过可编程增益放大器(PGA),然后再进行采样,采样后的数据要再经过数字自动增益控制器(AGC)送入数字混音器。设计硬件电路时选用第二组输入音频信号,这样可以通过软件的方法实现对系统输入音量大小的调节。显然选用第二组可以通过L3总线接口控制AGC来实现。另外,选择通道2还可以通过PGA对从MIC输入的信号进行片内放大。
S3C2410与UDA1341之间的IIS接口有3种工作方式:
• 正常传输模式。该模式下使用IISCON寄存器对FIFO进行控制,CPU通过轮询方式访问FIFO寄存器,以完成对FIFO缓存传输或接收的处理。
• DMA模式。通过设置IISFCON寄存器使IIS接口工作于这种模式。在该模式下,FIFO寄存器组的控制权掌握在DMA控制器上,当FIFO满时,由DMA控制器对FIFO中的数据进行处理。DMA模式的选择由IISCON寄存器的第4和第5位控制。
• 传输/接收模式。该模式下,IIS数据线将通过双通道DMA同时接收和发送音频数据。在OSS驱动中,将使用此模式。
17.5.2注册dsp和mixer接口
如代码清单17.28,在UDA1341 OSS驱动的模块加载函数中,将完成如下工作:
• 初始化IIS接口硬件,设置L3总线对应的GPIO。
• 申请用于音频数据传输的DMA通道。
• 初始化UDA1341到恰当的工作模式。
• 注册dsp和mixer接口。
代码清单17.28 UDA1341 OSS驱动模块加载函数
1 //音频(dsp)文件操作
2 static struct file_operations smdk2410_audio_fops =
3 {
4    llseek: smdk2410_audio_llseek,
5    write: smdk2410_audio_write,
6    read: smdk2410_audio_read,
7    poll: smdk2410_audio_poll,
8    ioctl: smdk2410_audio_ioctl,
9    open: smdk2410_audio_open,
10   release: smdk2410_audio_release
11 };
12 //混音器文件操作
13 static struct file_operations smdk2410_mixer_fops =
14 {
15   ioctl: smdk2410_mixer_ioctl,
16   open: smdk2410_mixer_open,
17   release: smdk2410_mixer_release
18 };
19
20 int __init s3c2410_uda1341_init(void)                                       
21 {                                                                           
22   unsigned long flags;                                                      
23                                                                             
24   local_irq_save(flags);                                                    
25                                                                             
26   /* 设置IIS接口引脚GPIO */                                                 
27                                                                             
28   set_gpio_ctrl(GPIO_L3CLOCK); // GPB 4: L3CLOCK, 输出                      
29   set_gpio_ctrl(GPIO_L3DATA); // GPB 3: L3DATA, 输出                       
30   set_gpio_ctrl(GPIO_L3MODE); // GPB 2: L3MODE, 输出                       
31                                                                             
32                                                                             
33   set_gpio_ctrl(GPIO_E3 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 3: IISSDI
34   set_gpio_ctrl(GPIO_E0 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 0: IISLRCK
35   set_gpio_ctrl(GPIO_E1 | GPIO_PULLUP_EN | GPIO_MODE_IISSCLK); //GPE 1:IISSCLK
36   set_gpio_ctrl(GPIO_E2 | GPIO_PULLUP_EN | GPIO_MODE_CDCLK); //GPE 2: CDCLK
37   set_gpio_ctrl(GPIO_E4 | GPIO_PULLUP_EN | GPIO_MODE_IISSDO); //GPE 4: IISSDO
38                                                                             
39   local_irq_restore(flags);                                                 
40                                                                             
41   init_uda1341();                                                           
42                                                                             
43   /* 输出流采样DMA通道2 */                                                  
44   output_stream.dma_ch = DMA_CH2;                                           
45                                                                             
46   if (audio_init_dma(&output_stream, "UDA1341 out"))                        
47   {                                                                         
48     audio_clear_dma(&output_stream);                                        
49     printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n");
50     return - EBUSY;                                                        
51   }                                                                         
52   /* 输入流采样DMA通道1 */                                                  
53   input_stream.dma_ch = DMA_CH1;                                            
54                                                                             
55   if (audio_init_dma(&input_stream, "UDA1341 in"))                          
56   {                                                                         
57     audio_clear_dma(&input_stream);                                         
58     printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n");
59     return - EBUSY;                                                        
60   }                                                                         
61                                                                             
62   /* 注册dsp和mixer设备接口 */                                              
63   audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, - 1);           
64   audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, - 1);       
65                                                                             
66   printk(AUDIO_NAME_VERBOSE " initialized\n");                              
67                                                                             
68   return 0;                                                                 
69 }   
UDA1341 OSS驱动的模块卸载函数中,将完成与模块加载函数相反的工作,如代码清单17.29。
代码清单17.29 UDA1341 OSS驱动模块卸载函数
1 void __exit s3c2410_uda1341_exit(void)
2 {
3   //注销dsp和mixer设备接口
4   unregister_sound_dsp(audio_dev_dsp);
5   unregister_sound_mixer(audio_dev_mixer);
6  
7   //注销DMA通道
8   audio_clear_dma(&output_stream);
9   audio_clear_dma(&input_stream); /* input */
10 printk(AUDIO_NAME_VERBOSE " unloaded\n");
11 }
17.5.3 mixer接口IO控制函数
UDA1341 OSS驱动的ioctl()函数处理多个mixer命令,如SOUND_MIXER_INFO、 SOUND_MIXER_READ_STEREODEVS、SOUND_MIXER_WRITE_VOLUME等,用于获得或设置音量和增益等信息,如代码清单17.30所示。
代码清单17.30 UDA1341 OSS驱动ioctl()函数
1 static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,
2    unsigned int cmd, unsigned long arg)
3 {
4    int ret;
5    long val = 0;
6
7    switch (cmd)
8    {
9      case SOUND_MIXER_INFO:   //获得mixer信息
10       {
11         mixer_info info;
12         strncpy(info.id, "UDA1341", sizeof(info.id));
13         strncpy(info.name, "Philips UDA1341", sizeof(info.name));
14         info.modify_counter = audio_mix_modcnt;
15         return copy_to_user((void*)arg, &info, sizeof(info));
16       }
17
18     case SOUND_OLD_MIXER_INFO:
19       {
20         _old_mixer_info info;
21         strncpy(info.id, "UDA1341", sizeof(info.id));
22         strncpy(info.name, "Philips UDA1341", sizeof(info.name));
23         return copy_to_user((void*)arg, &info, sizeof(info));
24       }
25
26     case SOUND_MIXER_READ_STEREODEVS://获取设备对立体声的支持
27       return put_user(0, (long*)arg);
28
29     case SOUND_MIXER_READ_CAPS: //获取声卡能力
30       val = SOUND_CAP_EXCL_INPUT;
31       return put_user(val, (long*)arg);
32
33     case SOUND_MIXER_WRITE_VOLUME:   //设置音量
34       ret = get_user(val, (long*)arg);
35       if (ret)
36         return ret;
37       uda1341_volume = 63-(((val &0xff) + 1) *63) / 100;
38       uda1341_l3_address(UDA1341_REG_DATA0);
39       uda1341_l3_data(uda1341_volume);
40       break;
41
42     case SOUND_MIXER_READ_VOLUME:   //获取音量
43       val = ((63-uda1341_volume) *100) / 63;
44       val |= val << 8;
45       return put_user(val, (long*)arg);
46
47     case SOUND_MIXER_READ_IGAIN:   //获得增益
48       val = ((31-mixer_igain) *100) / 31;
49       return put_user(val, (int*)arg);
50
51     case SOUND_MIXER_WRITE_IGAIN: //设置增益
52       ret = get_user(val, (int*)arg);
53       if (ret)
54         return ret;
55       mixer_igain = 31-(val *31 / 100);
56       /* 使用mixer增益通道1 */
57       uda1341_l3_address(UDA1341_REG_DATA0);
58       uda1341_l3_data(EXTADDR(EXT0));
59       uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));
60       break;
61
62     default:
63       DPRINTK("mixer ioctl %u unknown\n", cmd);
64       return - ENOSYS;
65   }
66
67   audio_mix_modcnt++;
68   return 0;
69 }    
17.5.4 dsp接口音频数据传输
OSS声卡驱动中,dsp接口的读写函数是核心中的核心,直接对应着录音和放音的流程。
OSS 的读函数存在一个与普通字符设备驱动读函数不同的地方,那就是一般而言,对于普通字符设备驱动,如果用户要求读count个字节,而实际上只有 count1字节可获得(count1< count)时,它会将这count1字节拷贝给用户后即返回count1;而dsp接口的读函数会分次拷贝,如果第1次不能满足,它会等待第2次,直到 “count1 + count2 + ... = count”为止再返回count。这种设计是合理的,因为OSS驱动应该负责音频数据的流量控制。代码清单17.31给出了UDA1341 OSS驱动的读函数实现。
代码清单17.31 UDA1341 OSS驱动的读函数
1 static ssize_t smdk2410_audio_read(struct file *file, char *buffer, size_t
2    count, loff_t *ppos)
3 {
4    const char *buffer0 = buffer;
5    audio_stream_t *s = &input_stream; //得到数据区的指针
6    int chunksize, ret = 0;
7
8    DPRINTK("audio_read: count=%d\n", count);
9
10   if (ppos != &file->f_pos)
11     return - ESPIPE;
12
13   if (!s->buffers)
14   {
15     int i;
16
17     if (audio_setup_buf(s))
18       return - ENOMEM;
19     //依次从缓存区读取数据
20     for (i = 0; i < s->nbfrags; i++)
21     {
22       audio_buf_t *b = s->buf;
23       down(&b->sem);
24       s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, s->fragsize,
25         DMA_BUF_RD);
26       NEXT_BUF(s, buf);
27     }
28   }
29
30   //满足用户的所有读需求
31   while (count > 0)
32   {
33     audio_buf_t *b = s->buf;
34
35     if (file->f_flags &O_NONBLOCK) //非阻塞
36     {
37       ret = - EAGAIN;
38       if (down_trylock(&b->sem))
39         break;
40     }
41     else
42     {
43       ret = - ERESTARTSYS;
44       if (down_interruptible(&b->sem))
45         break;
46     }
47
48     chunksize = b->size;
49     //从缓存区读取数据
50     if (chunksize > count)
51       chunksize = count;
52     DPRINTK("read %d from %d\n", chunksize, s->buf_idx);
53     if (copy_to_user(buffer, b->start + s->fragsize - b->size, //调用拷贝函数
54     chunksize))
55     {
56       up(&b->sem);
57       return - EFAULT;
58     }
59     b->size -= chunksize;
60
61     buffer += chunksize;
62     count -= chunksize; //已经给用户拷贝了一部分,count减少
63     if (b->size > 0)
64     {
65       up(&b->sem);
66       break;
67     }
68     //将缓存区释放
69     s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, s->fragsize,
70       DMA_BUF_RD);
71
72     NEXT_BUF(s, buf);
73   }
74
75   if ((buffer - buffer0))
76     ret = buffer - buffer0;
77
78   return ret;
79 }     
OSS驱动dsp接口的写函数与读函数类似,一般来说,它也应该满足用户的所有写需求后再返回,如代码清单17.32。
代码清单17.32 UDA1341 OSS驱动的写函数
1 static ssize_t smdk2410_audio_write(struct file *file, const char *buffer,
2    size_t count, loff_t *ppos)
3 {
4    const char *buffer0 = buffer;
5    audio_stream_t *s = &output_stream;
6    int chunksize, ret = 0;
7
8    DPRINTK("audio_write : start count=%d\n", count);
9
10   switch (file->f_flags &O_ACCMODE)
11   {
12     case O_WRONLY: //只写
13     case O_RDWR: //读写
14       break;
15     default: //只读不合法
16       return - EPERM;
17   }
18   //设置DMA缓冲区
19   if (!s->buffers && audio_setup_buf(s))
20     return - ENOMEM;
21
22   count &= ~0x03;
23
24   while (count > 0) //直到满足用户的所有写需求
25   {
26     audio_buf_t *b = s->buf;
27     //非阻塞访问
28     if (file->f_flags &O_NONBLOCK)
29     {
30       ret = - EAGAIN;
31       if (down_trylock(&b->sem))
32         break;
33     }
34     else
35     {
36       ret = - ERESTARTSYS;
37       if (down_interruptible(&b->sem))
38         break;
39     }
40     //从用户空间拷贝音频数据
41     if (audio_channels == 2)
42     {
43       chunksize = s->fragsize - b->size;
44       if (chunksize > count)
45         chunksize = count;
46       DPRINTK("write %d to %d\n", chunksize, s->buf_idx);
47       if (copy_from_user(b->start + b->size, buffer, chunksize))
48       {
49         up(&b->sem);
50         return - EFAULT;
51       }
52       b->size += chunksize;
53     }
54     else
55     {
56       chunksize = (s->fragsize - b->size) >> 1;
57
58       if (chunksize > count)
59         chunksize = count;
60       DPRINTK("write %d to %d\n", chunksize *2, s->buf_idx);
61       if (copy_from_user_mono_stereo(b->start + b->size, buffer, chunksize))
62       {
63         up(&b->sem);
64         return - EFAULT;
65       }
66
67       b->size += chunksize * 2;
68     }
69
70     buffer += chunksize;
71     count -= chunksize; //已经从用户拷贝了一部分,count减少
72     if (b->size < s->fragsize)
73     {
74       up(&b->sem);
75       break;
76     }
77     //发起DMA操作
78     s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, b->size,
79       DMA_BUF_WR);
80     b->size = 0;
81     NEXT_BUF(s, buf);
82   }
83
84   if ((buffer - buffer0))
85     ret = buffer - buffer0;
86
87   DPRINTK("audio_write : end count=%d\n\n", ret);
88
89   return ret;
90 }
17.6实例2:SA1100+ UDA1341 ALSA驱动
17.6.1 card注册与注销
同样是UDA1341芯片,如果以ALSA体系结构来实现它的驱动,会和OSS大不一样。如17.4.1节所言,在模块初始化和卸载的时候,需要注册和注销card,另外在模块加载的时候,也会注册mixer和pcm组件,如代码清单17.33。
代码清单17.33 UDA1341 ALSA驱动模块初始化与卸载
1 static int __init sa11xx_uda1341_probe(struct platform_device *devptr)
2 {
3   int err;
4   struct snd_card *card;
5   struct sa11xx_uda1341 *chip;
6
7   /* 新建card */
8   card = snd_card_new(-1, id, THIS_MODULE, sizeof(struct sa11xx_uda1341));
9   if (card == NULL)
10   return -ENOMEM;
11
12 chip = card->private_data;
13 spin_lock_init(&chip->s[0].dma_lock);
14 spin_lock_init(&chip->s[1].dma_lock);
15
16 card->private_free = snd_sa11xx_uda1341_free;//card私有数据释放
17 chip->card = card;
18 chip->samplerate = AUDIO_RATE_DEFAULT;
19
20 // 注册control(mixer)接口
21 if ((err = snd_chip_uda1341_mixer_new(card, &chip->uda1341)))
22   goto nodev;
23
24 // 注册PCM接口
25 if ((err = snd_card_sa11xx_uda1341_pcm(chip, 0)) < 0)
26   goto nodev;
27       
28 strcpy(card->driver, "UDA1341");
29 strcpy(card->shortname, "H3600 UDA1341TS");
30 sprintf(card->longname, "Compaq iPAQ H3600 with Philips UDA1341TS");
31       
32 snd_card_set_dev(card, &devptr->dev);
33   //注册card
34 if ((err = snd_card_register(card)) == 0) {
35   printk( KERN_INFO "iPAQ audio support initialized\n" );
36   platform_set_drvdata(devptr, card);
37   return 0;
38 }
39       
40 nodev:
41 snd_card_free(card);
42 return err;
43 }   
44
45 static int __devexit sa11xx_uda1341_remove(struct platform_device *devptr)
46 {
47 //释放card
48 snd_card_free(platform_get_drvdata(devptr));
49 platform_set_drvdata(devptr, NULL);
50 return 0;
51 }
17.6.2 PCM设备的实现
PCM组件直接对应着ALSA驱动的录音和放音,从17.4.2节的描述可知,驱动从需要定义对应相应的snd_pcm_hardware结构体进行PCM设备硬件描述,如代码清单17.34。
代码清单17.34 UDA1341 ALSA驱动PCM接口snd_pcm_hardware结构体
1 static struct snd_pcm_hardware snd_sa11xx_uda1341_capture =
2 {
3   .info   = (SNDRV_PCM_INFO_INTERLEAVED |
4         SNDRV_PCM_INFO_BLOCK_TRANSFER |
5         SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
6         SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
7   .formats = SNDRV_PCM_FMTBIT_S16_LE,
8   .rates   = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
9         SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |\
10        SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
11        SNDRV_PCM_RATE_KNOT),
12 .rate_min = 8000,
13 .rate_max = 48000,
14 .channels_min = 2,
15 .channels_max = 2,
16 .buffer_bytes_max = 64*1024,
17 .period_bytes_min = 64,
18 .period_bytes_max = DMA_BUF_SIZE,
19 .periods_min = 2,
20 .periods_max = 255,
21 .fifo_size = 0,
22 };
23
24 static struct snd_pcm_hardware snd_sa11xx_uda1341_playback =
25 {
26 .info   = (SNDRV_PCM_INFO_INTERLEAVED |
27        SNDRV_PCM_INFO_BLOCK_TRANSFER |
28        SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
29        SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
30 .formats = SNDRV_PCM_FMTBIT_S16_LE,
31 .rates   = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
32              SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |\
33        SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
34        SNDRV_PCM_RATE_KNOT),
35 .rate_min = 8000,
36 .rate_max = 48000,
37 .channels_min = 2,
38 .channels_max = 2,
39 .buffer_bytes_max = 64*1024,
40 .period_bytes_min = 64,
41 .period_bytes_max = DMA_BUF_SIZE,
42 .periods_min = 2,
43 .periods_max = 255,
44 .fifo_size = 0,
45 };
PCM接口的主要函数被封装在snd_pcm_ops结构体内,UDA1341 ALSA驱动对snd_pcm_ops结构体的定义如代码清单17.35。
代码清单17.35 UDA1341 ALSA驱动PCM接口snd_pcm_ops结构体
1 static struct snd_pcm_ops snd_card_sa11xx_uda1341_playback_ops =
2 {
3   .open   = snd_card_sa11xx_uda1341_open,
4   .close   = snd_card_sa11xx_uda1341_close,
5   .ioctl   = snd_pcm_lib_ioctl,
6   .hw_params         = snd_sa11xx_uda1341_hw_params,
7   .hw_free         = snd_sa11xx_uda1341_hw_free,
8   .prepare = snd_sa11xx_uda1341_prepare,
9   .trigger = snd_sa11xx_uda1341_trigger,
10 .pointer = snd_sa11xx_uda1341_pointer,
11 };
12
13 static struct snd_pcm_ops snd_card_sa11xx_uda1341_capture_ops =
14 {
15 .open   = snd_card_sa11xx_uda1341_open,
16 .close   = snd_card_sa11xx_uda1341_close,
17 .ioctl   = snd_pcm_lib_ioctl,
18 .hw_params         = snd_sa11xx_uda1341_hw_params,
19 .hw_free         = snd_sa11xx_uda1341_hw_free,
20 .prepare = snd_sa11xx_uda1341_prepare,
21 .trigger = snd_sa11xx_uda1341_trigger,
22 .pointer = snd_sa11xx_uda1341_pointer,
23 };
代码清单17.33第25行调用的snd_card_sa11xx_uda1341_pcm()即是PCM组件的“构造函数”,其实现如代码清单17.36。
代码清单17.36 UDA1341 ALSA驱动PCM组件构造函数
1 static int __init snd_card_sa11xx_uda1341_pcm(struct sa11xx_uda1341 *sa11xx_uda1341, int device)
2 {
3   struct snd_pcm *pcm;
4   int err;
5   /* 新建pcm设备,playback和capture都为1个 */
6   if ((err = snd_pcm_new(sa11xx_uda1341->card, "UDA1341 PCM", device, 1, 1, &pcm)) < 0)
7    return err;
8
9   /* 建立初始缓冲区并设置dma_type为isa */
10 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
11            snd_dma_isa_data(),
12            64*1024, 64*1024);
13 /* 设置pcm的操作 */
14 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, snd_card_sa11xx_uda1341_playback_ops);
15 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_sa11xx_uda1341_capture_ops);
16 pcm->private_data = sa11xx_uda1341;
17 pcm->info_flags = 0;
18 strcpy(pcm->name, "UDA1341 PCM");
19
20 sa11xx_uda1341_audio_init(sa11xx_uda1341);
21
22 /* 设置DMA控制器 */
23 audio_dma_request(&sa11xx_uda1341->s[SNDRV_PCM_STREAM_PLAYBACK], audio_dma_callback);
24 audio_dma_request(&sa11xx_uda1341->s[SNDRV_PCM_STREAM_CAPTURE], audio_dma_callback);
25
26 sa11xx_uda1341->pcm = pcm;
27
28 return 0;
29 }
在snd_pcm_ops结构体的打开成员函数中,需要根据具体的子流赋值snd_pcm_runtime的hw,如代码清单17.37。
代码清单17.37 UDA1341 ALSA驱动snd_pcm_ops结构体open/close成员函数
1 static int snd_card_sa11xx_uda1341_open(struct snd_pcm_substream *substream)
2 {
3   struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);
4   struct snd_pcm_runtime *runtime = substream->runtime;
5   int stream_id = substream->pstr->stream;
6   int err;
7
8   chip->s[stream_id].stream = substream;
9
10 if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) //播放子流
11   runtime->hw = snd_sa11xx_uda1341_playback;
12 else    //录音子流
13   runtime->hw = snd_sa11xx_uda1341_capture;
14 if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
15   return err;
16 if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,&hw_constraints_rates)) < 0)
17   return err;
18       
19 return 0;
20 }
21
22 static int snd_card_sa11xx_uda1341_close(struct snd_pcm_substream *substream)
23 {
24 struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);
25
26 chip->s[substream->pstr->stream].stream = NULL;
27 return 0;
28 }
在snd_pcm_ops结构体的trigger()成员函数中,控制播放和录音的启/停、挂起/恢复,如代码清单17.38。
代码清单17.38 UDA1341 ALSA驱动snd_pcm_ops结构体trigger成员函数
1   static int snd_sa11xx_uda1341_trigger(struct snd_pcm_substream *substream, int
2     cmd)
3   {
4     struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);
5     int stream_id = substream->pstr->stream;
6     struct audio_stream *s = &chip->s[stream_id];
7     struct audio_stream *s1 = &chip->s[stream_id ^ 1];
8     int err = 0;
9
10    /* 注意本地中断已经被中间层代码禁止 */
11    spin_lock(&s->dma_lock);
12    switch (cmd)
13    {
14      case SNDRV_PCM_TRIGGER_START://开启PCM
15        if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active) //开启录音,不在播放
16        {
17          s1->tx_spin = 1;
18          audio_process_dma(s1);
19        }
20        else
21        {
22          s->tx_spin = 0;
23        }
24
25        /* 被请求的流启动 */
26        s->active = 1;
27        audio_process_dma(s);
28        break;
29      case SNDRV_PCM_TRIGGER_STOP:
30        /* 被请求的流关闭 */
31        audio_stop_dma(s);
32        if (stream_id == SNDRV_PCM_STREAM_PLAYBACK && s1->active) //在录音时开启播放
33        {
34          s->tx_spin = 1;
35          audio_process_dma(s); //启动DMA
36        }
37        else
38        {
39          if (s1->tx_spin)
40          {
41            s1->tx_spin = 0;
42            audio_stop_dma(s1); //停止DMA
43          }
44        }
45
46        break;
47      case SNDRV_PCM_TRIGGER_SUSPEND: //挂起
48        s->active = 0;
49        #ifdef HH_VERSION
50          sa1100_dma_stop(s->dmach); //停止DMA
51        #endif
52        s->old_offset = audio_get_dma_pos(s) + 1;
53        #ifdef HH_VERSION
54          sa1100_dma_flush_all(s->dmach);
55        #endif
56        s->periods = 0;
57        break;
58      case SNDRV_PCM_TRIGGER_RESUME:   //恢复
59        s->active = 1;
60        s->tx_spin = 0;
61        audio_process_dma(s); //开启DMA
62        if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active)
63        {
64          s1->tx_spin = 1;
65          audio_process_dma(s1);
66        }
67        break;
68      case SNDRV_PCM_TRIGGER_PAUSE_PUSH:   //暂停
69        #ifdef HH_VERSION
70          sa1100_dma_stop(s->dmach); //停止DMA
71        #endif
72        s->active = 0;
73        if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
74        {
75          if (s1->active)
76          {
77            s->tx_spin = 1;
78            s->old_offset = audio_get_dma_pos(s) + 1;
79            #ifdef HH_VERSION   
80              sa1100_dma_flush_all(s->dmach);
81            #endif
82            audio_process_dma(s); //开启DMA
83          }
84        }
85        else
86        {
87          if (s1->tx_spin)
88          {
89            s1->tx_spin = 0;
90            #ifdef HH_VERSION   
91              sa1100_dma_flush_all(s1->dmach);
92            #endif
93          }
94        }
95        break;
96      case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:   //暂停释放
97        s->active = 1;
98        if (s->old_offset)
99        {
100         s->tx_spin = 0;
101         audio_process_dma(s);
102         break;
103       }
104       if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active)
105       {
106         s1->tx_spin = 1;
107         audio_process_dma(s1);
108       }
109       #ifdef HH_VERSION
110         sa1100_dma_resume(s->dmach);
111       #endif
112       break;
113     default:
114       err = - EINVAL;
115       break;
116   }
117   spin_unlock(&s->dma_lock);
118   return err;
119 }
snd_pcm_ops结构体中其它的hw_params()、prepare()、pointer()等成员函数实现较为简单,这里不再赘述。
17.6.3 控制接口的实现
代码清单17.33第21行调用的snd_chip_uda1341_mixer_new()可以认为是UDA1341 ALSA驱动mixer控制组件的“构造函数”,其中会创建的控制元素的定义如代码清单17.39,包括一些枚举和单值元素。
代码清单17.39 UDA1341 ALSA驱动控制接口snd_kcontrol_new结构体
1 #define UDA1341_SINGLE(xname, where, reg, shift, mask, invert) \
2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_uda1341_info_single, \
3    .get = snd_uda1341_get_single, .put = snd_uda1341_put_single, \
4    .private_value = where | (reg << 5) | (shift << 9) | (mask << 12) | (invert << 18) \
5 }
6
7 #define UDA1341_ENUM(xname, where, reg, shift, mask, invert) \
8 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_uda1341_info_enum, \
9    .get = snd_uda1341_get_enum, .put = snd_uda1341_put_enum, \
10   .private_value = where | (reg << 5) | (shift << 9) | (mask << 12) | (invert << 18) \
11 }
12
13 static struct snd_kcontrol_new snd_uda1341_controls[] =
14 {
15 UDA1341_SINGLE("Master Playback Switch", CMD_MUTE, data0_2, 2, 1, 1),
16 UDA1341_SINGLE("Master Playback Volume", CMD_VOLUME, data0_0, 0, 63, 1),
17
18 UDA1341_SINGLE("Bass Playback Volume", CMD_BASS, data0_1, 2, 15, 0),
19 UDA1341_SINGLE("Treble Playback Volume", CMD_TREBBLE, data0_1, 0, 3, 0),
20
21 UDA1341_SINGLE("Input Gain Switch", CMD_IGAIN, stat1, 5, 1, 0),
22 UDA1341_SINGLE("Output Gain Switch", CMD_OGAIN, stat1, 6, 1, 0),
23
24 UDA1341_SINGLE("Mixer Gain Channel 1 Volume", CMD_CH1, ext0, 0, 31, 1),
25 UDA1341_SINGLE("Mixer Gain Channel 2 Volume", CMD_CH2, ext1, 0, 31, 1),
26
27 UDA1341_SINGLE("Mic Sensitivity Volume", CMD_MIC, ext2, 2, 7, 0),
28
29 UDA1341_SINGLE("AGC Output Level", CMD_AGC_LEVEL, ext6, 0, 3, 0),
30 UDA1341_SINGLE("AGC Time Constant", CMD_AGC_TIME, ext6, 2, 7, 0),
31 UDA1341_SINGLE("AGC Time Constant Switch", CMD_AGC, ext4, 4, 1, 0),
32
33 UDA1341_SINGLE("DAC Power", CMD_DAC, stat1, 0, 1, 0),
34 UDA1341_SINGLE("ADC Power", CMD_ADC, stat1, 1, 1, 0),
35
36 UDA1341_ENUM("Peak detection", CMD_PEAK, data0_2, 5, 1, 0),
37 UDA1341_ENUM("De-emphasis", CMD_DEEMP, data0_2, 3, 3, 0),
38 UDA1341_ENUM("Mixer mode", CMD_MIXER, ext2, 0, 3, 0),
39 UDA1341_ENUM("Filter mode", CMD_FILTER, data0_2, 0, 3, 0),
40
41 UDA1341_2REGS("Gain Input Amplifier Gain (channel 2)", CMD_IG, ext4, ext5, 0, 0, 3, 31, 0),
42 };
从上述代码中宏UDA1341_SINGLE和UDA1341_ENUM的定义可知,单值元素的info()、get()、put()成员函数分别为 snd_uda1341_info_single()、snd_uda1341_get_single()和 snd_uda1341_put_single();枚举元素的info()、get()、put()成员函数分别为 snd_uda1341_info_enum()、snd_uda1341_get_enum()、和snd_uda1341_put_enum()。作为例子,代码清单17.40给出了单值元素相关函数的实现。
代码清单17.40 UDA1341 ALSA驱动控制接口单值元素info/get/put函数
1 static int snd_uda1341_info_single(struct snd_kcontrol *kcontrol, struct
2    snd_ctl_elem_info *uinfo)
3 {
4    int mask = (kcontrol->private_value >> 12) &63;
5
6    uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
7      SNDRV_CTL_ELEM_TYPE_INTEGER;
8    uinfo->count = 1; //数量为1
9    uinfo->value.integer.min = 0; //最小值
10   uinfo->value.integer.max = mask; //最大值
11   return 0;
12 }
13
14 static int snd_uda1341_get_single(struct snd_kcontrol *kcontrol, struct
15   snd_ctl_elem_value *ucontrol)
16 {
17   struct l3_client *clnt = snd_kcontrol_chip(kcontrol);
18   struct uda1341 *uda = clnt->driver_data;
19   int where = kcontrol->private_value &31;
20   int mask = (kcontrol->private_value >> 12) &63;
21   int invert = (kcontrol->private_value >> 18) &1;
22
23   ucontrol->value.integer.value[0] = uda->cfg[where]; //返回给ucontrol
24   if (invert) //如果反转
25     ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
26
27   return 0;
28 }
29
30 static int snd_uda1341_put_single(struct snd_kcontrol *kcontrol, struct
31   snd_ctl_elem_value *ucontrol)
32 {
33   struct l3_client *clnt = snd_kcontrol_chip(kcontrol);
34   struct uda1341 *uda = clnt->driver_data;
35   int where = kcontrol->private_value &31;
36   int reg = (kcontrol->private_value >> 5) &15;
37   int shift = (kcontrol->private_value >> 9) &7;
38   int mask = (kcontrol->private_value >> 12) &63;
39   int invert = (kcontrol->private_value >> 18) &1;
40   unsigned short val;
41
42   val = (ucontrol->value.integer.value[0] &mask);//从ucontrol获得值
43   if (invert) //如果反转
44     val = mask - val;
45
46   uda->cfg[where] = val;
47   return snd_uda1341_update_bits(clnt, reg, mask, shift, val, FLUSH);//更新位
48 }
17.7实例3:PXA255+AC97 ALSA驱动
Intel 公司的XScale PXA255是一款基于ARM5TE内核技术的嵌入式处理器。它提供了符合AC97 rev2.0标准的AC97控制单元(ACUNIT)和音频控制连接(AC-Link)。ACUNIT就是CODEC控制器,它通过AC-Link连接和控制AC97 CODEC芯片,例如Philips公司出品的一款符合AC97标准的多功能CODEC芯片UCB1400。它不仅是一枚CODEC芯片,还集成了触摸和能量管理两个功能模块,在嵌入式系统中应用广泛。
AC-Link 连接了ACUNIT 和UCB1400Codec,只要对ACUNIT寄存器操作就可以实现同UCBI400之间的数据传输。通过ACUNIT还可读写CODEC内部寄存器,实现对音频采样和混音处理的控制。当然这些读写操作也是经由AC-Link传输的。
Intel Xscale PXA255提供了16个DMA通道,可以很方便的为外围设备提供数据传送。ACUNIT也为CODEC的立体声输入输出和话筒输入提供了独立的16 bit数据通道。每个通道都有一个专门的FIFO。
由于它是一个标准的AC97设备,因此,其驱动的控制部分实现可以说是非常的简单,按照17.4.4节的要求,需要实现AC97 codec寄存器读写的硬件级函数pxa2xx_ac97_read()和pxa2xx_ac97_write(),并在模块初始化时注册相关的AC97 组件,如代码清单17.41。
代码清单17.41 PXA255连接AC97 codec ALSA驱动控制组件
1   static unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, unsigned short
2     reg)
3   {
4     unsigned short val = - 1;
5     volatile u32 *reg_addr;
6
7     down(&car_mutex);
8
9     /* 设置首/次codec空间 */
10    reg_addr = (ac97->num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;
11    reg_addr += (reg >> 1);
12
13    /* 通过ac97 link读 */
14    GSR = GSR_CDONE | GSR_SDONE;
15    gsr_bits = 0;
16    val = *reg_addr;
17    if (reg == AC97_GPIO_STATUS)
18      goto out;
19    if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_SDONE, 1) <= 0
20      && !((GSR | gsr_bits) &GSR_SDONE))
21    //等待
22    {
23      printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n",
24        __FUNCTION__,reg, GSR | gsr_bits);
25      val = - 1;
26      goto out;
27    }
28
29    /* 置数据有效 */
30    GSR = GSR_CDONE | GSR_SDONE;
31    gsr_bits = 0;
32    val = *reg_addr;
33    /* 但是我们已经开启另一个周期... */
34    wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_SDONE, 1);
35
36    out: up(&car_mutex);
37    return val;
38 }
39
40 static void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
41    unsigned short val)
42 {
43    volatile u32 *reg_addr;
44
45    down(&car_mutex);
46
47    /*设置首/次codec空间*/
48    reg_addr = (ac97->num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;
49    reg_addr += (reg >> 1);
50
51    GSR = GSR_CDONE | GSR_SDONE;
52    gsr_bits = 0;
53    *reg_addr = val; //通过ac97 link写
54    if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_CDONE, 1) <= 0
55      && !((GSR | gsr_bits) &GSR_CDONE))
56      printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n",
57        __FUNCTION__,reg, GSR | gsr_bits);
58
59    up(&car_mutex);
60 }
61
62 static int pxa2xx_ac97_probe(struct platform_device *dev)
63 {
64    struct snd_card *card;
65    struct snd_ac97_bus *ac97_bus;
66    struct snd_ac97_template ac97_template;
67    int ret;
68
69    ret = - ENOMEM;
70    /* 新建card */
71    card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0);
72    if (!card)
73      goto err;
74    card->dev = &dev->dev;
75    strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver));
76  
77    /* 构造pcm组件 */
78    ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
79    if (ret)
80      goto err;
81
82    /* 申请中断 */
83    ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, 0, "AC97", NULL);
84    if (ret < 0)
85      goto err;
86
87    ...
88
89    /* 初始化ac97 bus */
90    ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
91    if (ret)
92      goto err;
93    memset(&ac97_template, 0, sizeof(ac97_template));
94    ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
95    if (ret)
96      goto err;
97    ...
98  
99    /* 注册card */
100   ret = snd_card_register(card);
101   if (ret == 0)
102   {
103     platform_set_drvdata(dev, card);
104     return 0;
105   }
106
107   err: if (card)
108     snd_card_free(card);
109   ...
110
111   returns ret;
112 }
17.8总结
音频设备接口包括PCM、IIS和AC97几种,分别适用于不同的应用场合。针对音频设备,Linux内核中包含了2类音频设备驱动框架,OSS和 ALSA,前者包含dsp和mixer字符设备接口,在用户空间的编程中,完全使用文件操作;后者以card和组件(pcm、mixer等)为主线,在用户空间的编程中不使用文件接口而使用alsalib。
在音频设备驱动中,几乎必须使用DMA,而DMA的缓冲区会被分割成一个一个的段,每次 DMA操作进行其中的一段。OSS驱动的阻塞读写具有流控能力,在用户空间不需要进行流量方面的定时工作,但是它需要及时的写(播放)和读(录音),以免出现缓冲区的underflow或overflow。
阅读(2024) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~