linux音频框架分析,Alsa音频子系统Codec---al5623.c内核代码框架分析
驅動代碼位于: sound/soc/codec/alc5623.c
隨便找個Linux內核都會有。
1、首先進行i2c總線驅動加載在:
static int __init alc5623_modinit(void)
在該函數中:
i2c_add_driver(&alc5623_i2c_driver);
alc5623_i2c_driver是一個結構體變量,并且已經被初始化,我們來看看它做了什么?
static struct i2c_driver alc5623_i2c_driver = {
.driver = {
.name = "alc562x-codec",? //這是要注冊的驅動的名字
.owner = THIS_MODULE,? ? //該驅動屬于哪個模塊
},
.probe = alc5623_i2c_probe,? ? ? //驅動執行的probe函數,也就是說,驅動從這個函數開始加載相關的操作,類似第一個步驟里的__init
.remove =? __devexit_p(alc5623_i2c_remove),?//驅動模塊刪除函數
.id_table = alc5623_i2c_table,? ? //對應的id_table--->其實就是該驅動支持的芯片類型。
};
2、i2c_driver結構體.
//這就是將該設備相關的平臺注冊到i2c總線上,該結構體變量已經做了初始化的操作.因為音頻子系統alsa codec是與平臺無關的,故可以這么來實現。
(1)id_table
我們先來看看id_table:
這是一個結構體數組,分別支持的音頻芯片是alc5621,alc5622,alc5623---->對應的芯片ID就是0x21、0x22、0x23
static const struct i2c_device_id alc5623_i2c_table[] = {
{"alc5621", 0x21},
{"alc5622", 0x22},
{"alc5623", 0x23},
{}
};
(2)alc5623_i2c_remove
接下來看看該結構體中__devexit_p(alc5623_i2c_remove)的alc5623_i2c_remove函數:
這里實現很簡單:
static int alc5623_i2c_remove(struct i2c_client *client)
{
//通過客戶端傳回來的結構體指針變量--->獲取i2c客戶端的數據,因為這個驅動是i2c驅動。
struct alc5623_priv *alc5623 = i2c_get_clientdata(client);
//注銷codec平臺相關的設備
snd_soc_unregister_codec(&client->dev);
//釋放內存
kfree(alc5623);
return 0;
}
(3)alc5623_i2c_probe
來看看我們驅動在probe函數的實現。
以下就是probe函數,作為一個支持i2c協議的芯片平臺
static int alc5623_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
//定義一個獲取芯片數據的指針
struct alc5623_platform_data *pdata;
//
struct alc5623_priv *alc5623;
int ret, vid1, vid2;
//通過i2c總線獲取芯片供應商的第一個ID--->下面就是這個宏的相關定義
//#define ALC5623_VENDOR_ID1? ?0x7C
vid1 = i2c_smbus_read_word_data(client, ALC5623_VENDOR_ID1);
//出錯處理
if (vid1 < 0) {
dev_err(&client->dev, "failed to read I2C\n");
return -EIO;
}
//將ID的高低八位互換再組合
vid1 = ((vid1 & 0xff) << 8) | (vid1 >> 8);
//通過i2c總線獲取芯片供應商的第二個ID--->下面就是這個宏的相關定義---->通過芯片手冊查閱到
//#define ALC5623_VENDOR_ID2? ?0x7E
vid2 = i2c_smbus_read_byte_data(client, ALC5623_VENDOR_ID2);
if (vid2 < 0) {
dev_err(&client->dev, "failed to read I2C\n");
return -EIO;
}
//出錯處理
if ((vid1 != 0x10ec) || (vid2 != id->driver_data)) {
dev_err(&client->dev, "unknown or wrong codec\n");
dev_err(&client->dev, "Expected %x:%lx, got %x:%x\n",
0x10ec, id->driver_data,
vid1, vid2);
return -ENODEV;
}
//通過以上的操作組合我們可以得知我們需要操作的芯片是alc56xxx
dev_dbg(&client->dev, "Found codec id : alc56%02x\n", vid2);
//開始對這個設備分配內核空間,根據GFP_KERNEL來分配
alc5623 = kzalloc(sizeof(struct alc5623_priv), GFP_KERNEL);
//如果分配為空,返回錯誤碼
if (alc5623 == NULL)
return -ENOMEM;
//獲取數據
pdata = client->dev.platform_data;
if (pdata) {
alc5623->add_ctrl = pdata->add_ctrl;
alc5623->jack_det_ctrl = pdata->jack_det_ctrl;
}
//這里就獲取到了我們要操作的芯片的id
alc5623->id = vid2;
//根據id號會給alc5623_dai.name賦值,dai就是一些音頻組建相關的一些操作
switch (alc5623->id) {
case 0x21:
alc5623_dai.name = "alc5621-hifi";
break;
case 0x22:
alc5623_dai.name = "alc5622-hifi";
break;
case 0x23:
alc5623_dai.name = "alc5623-hifi";
break;
default:
kfree(alc5623);
return -EINVAL;
}
//設置客戶端數據,設置對應的芯片設備
i2c_set_clientdata(client, alc5623);
//設置控制數據
alc5623->control_data = client;
//設置控制類型
alc5623->control_type = SND_SOC_I2C;
mutex_init(&alc5623->mutex);
//將設備注冊到codec平臺
//到這里,芯片平臺的注冊工作就完成了,我們可以根據相關注冊的soc_codec_device_alc5623和alc5623_dai(數字音頻接口)提供的函數通過上層的系統調用來操作音頻設備
//接下來看第三部分,我們來分析&soc_codec_device_alc5623, &alc5623_dai這兩個究竟做了什么操作。
ret =? snd_soc_register_codec(&client->dev,
&soc_codec_device_alc5623, &alc5623_dai, 1);
if (ret != 0) {
dev_err(&client->dev, "Failed to register codec: %d\n", ret);
kfree(alc5623);
}
return ret;
}
3、soc_codec_device_alc5623和alc5623_dai
(1)alc5623_dai
我們先來看看數字音頻接口做了什么?
以下具體的就是就是音頻接口的操作,根據PCM音頻和playback來進行設置
同時,也加載了alc5623_dai_ops這個操作集合
static struct snd_soc_dai_driver alc5623_dai = {
.name = "alc5623-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rate_min =?8000,
.rate_max =?48000,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ALC5623_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rate_min =?8000,
.rate_max =?48000,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = ALC5623_FORMATS,},
.ops = &alc5623_dai_ops,
};
(1)alc5623_dai_ops操作集合的實現
以下這些得找具體的調音頻的工程師才知道怎么設置相應的操作,具體我也不是特別懂,有興趣可以看看函數的實現,對著數據手冊的寄存器一個個看
static struct snd_soc_dai_ops alc5623_dai_ops = {
.hw_params = alc5623_pcm_hw_params,
.digital_mute = alc5623_mute,
.set_fmt = alc5623_set_dai_fmt,
.set_sysclk = alc5623_set_dai_sysclk,
.set_pll = alc5623_set_dai_pll,
};
(2)soc_codec_device_alc5623
static struct snd_soc_codec_driver soc_codec_device_alc5623 = {
.probe = alc5623_probe,
.remove = alc5623_remove,
.suspend = alc5623_suspend,? //? 1
.resume = alc5623_resume,? ? //? 2? 1和2是電源管理的回調函數,分別在聲卡加載和卸載的時候被調用
.set_bias_level = alc5623_set_bias_level, //設置偏置電壓配置函數
.reg_cache_size = ALC5623_VENDOR_ID2+2,
.reg_word_size = sizeof(u16),
.reg_cache_step = 2,
};
<1> alc5623_probe函數分析
//這部分我們不詳細進行分析,拿出最主要的東西來講,比如后面的snd_soc_add_codec_controls,主要說說它的作用
static int alc5623_probe(struct snd_soc_codec *codec)
{
struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec); //獲取數據
struct snd_soc_dapm_context *dapm = &codec->dapm;? ? ? ? ? ? ? ? //dapm相當于音頻的一個組件
int ret;
ret = snd_soc_codec_set_cache_io(codec, 8, 16, alc5623->control_type);
if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
}
alc5623_reset(codec);? //復位芯片
alc5623_fill_cache(codec);
/* power on device */
alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
if (alc5623->add_ctrl) {
snd_soc_write(codec, ALC5623_ADD_CTRL_REG,
alc5623->add_ctrl);
}
if (alc5623->jack_det_ctrl) {
snd_soc_write(codec, ALC5623_JACK_DET_CTRL,
alc5623->jack_det_ctrl);
}
//根據芯片的id執行對應的snd_soc_add_codec_controls,這個controls就是給上層提供相應的操作接口
//比如調節音量,耳機模式等等。
//這里是alc5621,我們找到了這個controls的集合
switch (alc5623->id) {
case 0x21:
snd_soc_add_codec_controls(codec, alc5621_vol_snd_controls,
ARRAY_SIZE(alc5621_vol_snd_controls));
break;
case 0x22:
snd_soc_add_codec_controls(codec, alc5622_vol_snd_controls,
ARRAY_SIZE(alc5622_vol_snd_controls));
break;
case 0x23:
snd_soc_add_codec_controls(codec, alc5623_vol_snd_controls,
ARRAY_SIZE(alc5623_vol_snd_controls));
break;
default:
return -EINVAL;
}
snd_soc_add_codec_controls(codec, alc5623_snd_controls,
ARRAY_SIZE(alc5623_snd_controls));
snd_soc_dapm_new_controls(dapm, alc5623_dapm_widgets,
ARRAY_SIZE(alc5623_dapm_widgets));
/* set up audio path interconnects */
snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
switch (alc5623->id) {
case 0x21:
case 0x22:
snd_soc_dapm_new_controls(dapm, alc5623_dapm_amp_widgets,
ARRAY_SIZE(alc5623_dapm_amp_widgets));
snd_soc_dapm_add_routes(dapm, intercon_amp_spk,
ARRAY_SIZE(intercon_amp_spk));
break;
case 0x23:
snd_soc_dapm_add_routes(dapm, intercon_spk,
ARRAY_SIZE(intercon_spk));
break;
default:
return -EINVAL;
}
return ret;
}
上面看到了snd_soc_add_codec_controls這個函數,具體做了什���事情,就是將要給上層控制的命令寫到這個結構體數組里
上面發相應的命令,比如Speaker Playback Volume,后面再加相應的參數就可以實現音量的控制,其它也是類似的,可以參考手冊去添加。
/*
static const struct snd_kcontrol_new alc5621_vol_snd_controls[] = {
SOC_DOUBLE_TLV("Speaker Playback Volume",
ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv),
SOC_DOUBLE("Speaker Playback Switch",
ALC5623_SPK_OUT_VOL, 15, 7, 1, 1),
SOC_DOUBLE_TLV("Headphone Playback Volume",
ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
SOC_DOUBLE("Headphone Playback Switch",
ALC5623_HP_OUT_VOL, 15, 7, 1, 1),
};
*/
而snd_soc_dapm_new_controls這個函數又提供什么功能呢?它其實是一個動態音頻電源管理,主要是描述dapm的信息,同時也處理一些dapm的事件。
相關的結構體也是和上面的類似的,大家可以對著數據手冊去參考一下。
至此,整個alc5623的芯片的框架就已經剖析完畢。
那么,我們如何來測試這個音頻是否可以用呢?在linux終端提供了amixer和aplay這樣的命令可以去操作,當然也可以去用alsa庫,調函數寫一個C程序去測試。
這里我們主要來說說amixer和aplay的使用。到開發板的linux終端。
1、用以下命令查看amixer支持哪些命令:
amixer --help
2、查看驅動里面已經提供了多少接口可以去操作:
amixer contents
例如:
顯示:numid=5,iface=MIXER,name='Line In Volume'
3、獲取當前參數的值---->Speaker Playback Volume這個就是剛剛我們看到的controls組件提供的控制參數
amixer cget? numid=1,iface=MIXER,name='Speaker Playback Volume'
numid=1,iface=MIXER,name='Speaker Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=20,20
| dBscale-min=-46.50dB,step=1.50dB,mute=0
從這上面得知的結果是聲音的值最小為0,最大為31
4、設置某個參數
比如就設置上面音量的大小:
amixer cset? numid=1,iface=MIXER,name='Speaker Playback Volume'? 25(這個值就是我們設置調節音量的值)
alc5623.c和alc相關的數據手冊網上有,搜下來對著框架分析就可以看懂了。
總結
以上是生活随笔為你收集整理的linux音频框架分析,Alsa音频子系统Codec---al5623.c内核代码框架分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux将变量保存生成txt,linu
- 下一篇: linux下测试个人主页,一键建站集成软