在嵌入式Linux的开发中,我们经常会更换codec芯片,这就需要我们添加codec驱动,下面就介绍下如何添加音频codec驱动。
1 codec驱动的数据结构
struct snd_soc_codec_driver {
/* driver ops */
int (*probe)(struct snd_soc_codec *);
int (*remove)(struct snd_soc_codec *);
int (*suspend)(struct snd_soc_codec *);
int (*resume)(struct snd_soc_codec *);
struct snd_soc_component_driver component_driver;
/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls;
int num_controls;
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
/* codec wide operations */
int (*set_sysclk)(struct snd_soc_codec *codec,
int clk_id, int source, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
/* codec IO */
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
int (*display_register)(struct snd_soc_codec *, char *,
size_t, unsigned int);
int (*volatile_register)(struct snd_soc_codec *, unsigned int);
int (*readable_register)(struct snd_soc_codec *, unsigned int);
int (*writable_register)(struct snd_soc_codec *, unsigned int);
unsigned int reg_cache_size;
short reg_cache_step;
short reg_word_size;
const void *reg_cache_default;
/* codec bias level */
int (*set_bias_level)(struct snd_soc_codec *,
enum snd_soc_bias_level level);
bool idle_bias_off;
bool suspend_bias_off;
void (*seq_notifier)(struct snd_soc_dapm_context *,
enum snd_soc_dapm_type, int);
/* codec stream completion event */
int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */
/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
};
主要成员变量说明如下:
.probe : codec 的probe函数,在驱动中执行snd_soc_register_codec函数时,由snd_soc_instantiate_card回调
.remove:驱动卸载的时候调用,执行snd_soc_unregister_codec的时候调用
.suspend .resume :电源管理,休眠唤醒的时候调用
.controls :codec控制接口的指针,例如控制音量的调节、通道的选择等等
.num_controls:codec控制接口的个数。也就是snd_kcontrol_new 的数量。
.dapm_widgets : dapm部件指针
.num_dapm_widgets : dapm部件指针的个数
.dapm_routes : dapm路由指针
.num_dapm_routes : dapm路由指针的个数
.set_sysclk :设置时钟函数指针
.set_pll :设置锁相环的函数指针
.set_bias_level : 设置偏置电压。
.read :读codec寄存器的函数
.write:些codec寄存器的函数
.volatile_register : 用于判定某一寄存器是否是volatile
.readable_register : 用于判定某一寄存器是否可读
.writable_register : 用于判定某一寄存器是否可写
struct snd_soc_dai_driver {
/* DAI description */
const char *name;
unsigned int id;
int ac97_control;
unsigned int base;
/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* compress dai */
bool compress_dai;
/* ops */
const struct snd_soc_dai_ops *ops;
/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits:1;
/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
};
主要的成员如下:
.name :dai的名字,这个名字很重要,ALSA驱动machine设备就是通过这个名字来匹配CODEC的
.ac97_control :是否支出AC97
.probe :回调函数,分别在声卡加载时被调用;
.remove :回调函数,分别在声卡卸载时被调用;
.suspend .resume: 分别在休眠唤醒的时候被调用
.ops :指向snd_soc_dai_ops结构,用于配置和控制该dai;
.playback: snd_soc_pcm_stream结构,用于说明播放时支持的声道数,码率,数据格式等能力;如果不支持可以不需要初始化
.capture : snd_soc_pcm_stream结构,用于说明录音时支持的声道数,码率,数据格式等能力;如果不支持可以不需要初始化
struct snd_soc_pcm_stream {
const char *stream_name;
u64 formats; /* SNDRV_PCM_FMTBIT_* */
unsigned int rates; /* SNDRV_PCM_RATE_* */
unsigned int rate_min; /* min rate */
unsigned int rate_max; /* max rate */
unsigned int channels_min; /* min channels */
unsigned int channels_max; /* max channels */
unsigned int sig_bits; /* number of bits of content */
};
主要的成员如下:
stream_name:stream的名字,例如"Playback","Capture",
formats:支持的数据格式的集合,例如SNDRV_PCM_FMTBIT_S16_LE、SNDRV_PCM_FMTBIT_S24_LE。如果支持多个格式可以将各个格式或起来,如SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE
rates: 支持的采样率的集合,例如SNDRV_PCM_RATE_44100、SNDRV_PCM_RATE_48000,如果支持多种采样率可以将各个采样率或起来,如SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200
rate_min:支持的最小采样率
rate_max:支持的最大采样率
channels_min:支持的最小采样率
channels_max:支持的最大采样率
struct snd_soc_dai_ops {
/*
* DAI clocking configuration, all optional.
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_sysclk)(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
/*
* DAI format configuration
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
/*
* DAI digital mute - optional.
* Called by soc-core to minimise any pops.
*/
int (*digital_mute)(struct snd_soc_dai *dai, int mute);
int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
/*
* ALSA PCM audio operations - all optional.
* Called by soc-core during audio PCM operations.
*/
int (*startup)(struct snd_pcm_substream *,
struct snd_soc_dai *);
void (*shutdown)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
int (*hw_free)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*prepare)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/*
* NOTE: Commands passed to the trigger function are not necessarily
* compatible with the current state of the dai. For example this
* sequence of commands is possible: START STOP STOP.
* So do not unconditionally use refcounting functions in the trigger
* function, e.g. clk_enable/disable.
*/
int (*trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
int (*bespoke_trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
/*
* For hardware based FIFO caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
};
主要的成员如下:
.set_sysclk : 设置dai的主时钟;
.set_pll : 设置PLL参数;
.set_clkdiv : 设置分频系数;
.set_fmt :设置dai的数据格式;
.set_tdm_slot : 如果dai支持时分复用,用于设置时分复用的slot;
.set_channel_map :声道的时分复用映射设置;
.set_tristate :设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;
.sunxi_i2s_hw_params:设置硬件的相关参数。
.startup :打开设备,设备开始工作的时候回调。
.shutdown:关闭设备前调用。
.trigger: DAM开始时传输,结束传输,暂停传输,恢复传输的时候被回调。
2 主要的函数
int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *codec_drv, struct snd_soc_dai_driver *dai_drv, int num_dai);
函数功能:注册一个codec
入口参数:dev:device设备指针
codec_drv:snd_soc_codec_driver结构的指针
dai_drv:snd_soc_dai_driver结构的指针
num_dai:snd_soc_dai_driver结构的个数,通常我们都是设置1
返回值: 0 成功,非0 失败
void snd_soc_unregister_codec(struct device *dev);
函数功能:注销一个codec
入口参数:dev:device设备指针
3 创建一个codec驱动的方法
codec驱动通常都是一个平台设备,所以我们首先需要注册一个平台设备。
然后根据codec IC的硬件定义,定义好snd_soc_codec_driver和snd_soc_dai_driver结构
然后在平台设备的probe函数中调用snd_soc_register_codec注册一个codec
在平台设备的remove函数中调用snd_soc_unregister_codec注销
4 示例代码,下面以CS4344为例子给出示例代码
cs4344.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define AUDIO_NAME "CS4344"
#ifdef CS4344_DEBUG
#define dbg(format, arg...) \
printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)
#else
#define dbg(format, arg...) do {} while (0)
#endif
#define err(format, arg...) \
printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
#define info(format, arg...) \
printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
#define warn(format, arg...) \
printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
/* There are no software controls for DAC so they need to be faked */
#define CS4344_DUMMY_CTRL 0x00 /* DAC Channel Dummy Control */
static struct snd_soc_codec *cs4344_codec = NULL;
static int cs4344_soc_probe(struct snd_soc_codec *codec);
static int cs4344_remove(struct platform_device *pdev);
static int cs4344_resume(struct snd_soc_codec *codec);
static int cs4344_suspend(struct snd_soc_codec *codec);
static int cs4344_soc_remove(struct snd_soc_codec *codec);
struct snd_kcontrol_new cs4344_snd_controls[] = {
SOC_SINGLE("Control",
CS4344_DUMMY_CTRL, 0, 0xFF, 1)
};
static unsigned int cs4344_read_reg(struct snd_soc_codec *codec, unsigned int reg)
{
return 0;
}
static int cs4344_write_reg(struct snd_soc_codec *codec, unsigned int reg, unsigned int value)
{
return 0;
}
static int cs4344_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return 0;
}
static int cs4344_mute(struct snd_soc_dai *dai, int mute)
{
return 0;
}
static int cs4344_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
return 0;
}
static int cs4344_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
return 0;
}
static int cs4344_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
int div_id, int div)
{
return 0;
}
#define CS4344_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
#define CS4344_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE)
static struct snd_soc_dai_ops cs4344_dai_ops = {
.hw_params = cs4344_pcm_hw_params,
.set_fmt = cs4344_set_dai_fmt,
.set_fmt = cs4344_set_dai_fmt,
.set_clkdiv = cs4344_set_dai_clkdiv,
.set_sysclk = cs4344_set_dai_sysclk,
};
struct snd_soc_dai_driver cs4344_dai = {
.name = "CS4344",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = CS4344_RATES,
.formats = CS4344_FORMATS,
},
.ops = &cs4344_dai_ops,
};
EXPORT_SYMBOL_GPL(cs4344_dai);
struct snd_soc_codec_driver soc_codec_dev_cs4344 = {
.probe = cs4344_soc_probe,
.remove = cs4344_soc_remove,
.suspend = cs4344_suspend,
.resume = cs4344_resume,
.controls = cs4344_snd_controls,
.num_controls = ARRAY_SIZE(cs4344_snd_controls),
};
static int cs4344_suspend(struct snd_soc_codec *codec)
{
return 0;
}
static int cs4344_resume(struct snd_soc_codec *codec)
{
return 0;
}
static int cs4344_soc_probe(struct snd_soc_codec *codec)
{
printk(KERN_ALERT"cs4344_soc_probe called \n");
return 0;
}
static int cs4344_probe(struct platform_device *pdev)
{
int ret ;
ret = snd_soc_register_codec(&(pdev->dev), &soc_codec_dev_cs4344, &cs4344_dai, 1);
printk(KERN_ALERT"cs4344_probe ret=%d \n",ret);
return ret;
}
static int cs4344_remove(struct platform_device *pdev)
{
/* can't turn off device */
snd_soc_unregister_codec(&(pdev->dev));
return 0;
}
static int cs4344_soc_remove(struct snd_soc_codec *codec)
{
return 0;
}
static const struct of_device_id cs4344_of_match[] = {
{ .compatible = "codec,cs4344", },
{ }
};
static struct platform_driver cs4344_device = {
.probe = cs4344_probe,
.remove = cs4344_remove,
.driver = {
.name = "cs4344",
.owner = THIS_MODULE,
.of_match_table = cs4344_of_match,
}
};
static int __init cs4344_mod_init(void)
{
return platform_driver_register(&cs4344_device);
}
static void __exit cs4344_exit(void)
{
platform_driver_unregister(&cs4344_device);
}
module_init(cs4344_mod_init);
module_exit(cs4344_exit);
EXPORT_SYMBOL_GPL(soc_codec_dev_cs4344);
MODULE_DESCRIPTION("ASoC CS4344 driver");
MODULE_AUTHOR("lisen");
MODULE_LICENSE("GPL");
然后在dts设备树种加上
codec4344: CS4344 {
compatible = "codec,cs4344";
。。。。
status = "okay";
};
重新编译后,下载到目标板后,我们可以看到codec驱动加载成功了。
5 实现Codec和Platform设备的关联
完成了上面的工作后,我们会发现codec驱动虽然加载成功了,但是ALSA却还是没有正常工作。
这是因为,对于嵌入式CPU,linux在标准的ALSA驱动上建立了ASoC(ALSA System on Chip),
ASoC音频系统可以被划分为Machine、Platform、Codec三大部分。
Codec驱动主要是针对音频CODEC的驱动,主要是进行AD、DA转换,对音频通路的控制,音量控制、EQ控制等等。
Platform驱动主要是针对CPU端的驱动,主要包括DMA的设置,数据音频接口的配置,时钟频率、数据格式等等。
Machine驱动主要是针对设备的,实现Codec和Platform耦合。
我们之前的工作只是加了codec的驱动,但是Codec和Platform并没有关联所以ALSA还是没有正常工作。
下面介绍如何实现关联
Platform驱动主要是针对CPU端的驱动,通常芯片厂家已经做好了,这里不需要详细的说明了。
Machine驱动通常是调用devm_snd_soc_register_card,或者snd_soc_register_card来注册一个声卡的。
devm_snd_soc_register_card和snd_soc_register_card 函数原型如下:
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card);
int snd_soc_register_card(struct snd_soc_card *card);
在这两个函数的入口参数struct snd_soc_card *card结构中有一个成员变量codec_dai_name,这个就是定义的codec_dai_name的名字,这个名字是和struct snd_soc_dai_driver cs4344_dai的name匹配的,
所以我们要把struct snd_soc_card *card结构的name改为CS4344。
然后在Machine驱动对应的设备树种对应的项,
将audio-codec的值改为codec4344就可以了(audio-codec = <&codec4344>; )(codec4344是4344驱动的DTS项)
然后重新编译,就大功告成了。