Linux音頻設備驅動-4【轉】

 

轉:http://hi.baidu.com/geyangshun/blog/item/99e0b251da45411e377abe3a.html

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 }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章