1. 工作原理
I2S有5根线,每根线的作用和使用请驱动开发人员参考 《设备驱动程序开发详解》,《嵌入式系统接口设计与LINUX驱动程序开发》,CQ8401 datesheet,相关章节。而且要完成该驱动需要具备DMA,i2s,i2c等相关知识。
1.1 原理理解
CQ8401 i2s 于 codec cs42l51 连接如图:
上图说明如下:
图左边是CQ8401内部集成了I2S控制器,右边是CODEC芯片
SYNC---采样频率
SYS_CLK----给CODEC的时钟频率
BIT_CLK----位时钟频率
上图说明如下:
Pllout:cpu 主频
CFCR2:分频寄存器,得到SYS_CLK
i2sDIV.DV: I2S分频寄存器,得到BIT_CLK
Divider in AIC: 1/64,得到SYNC
1.2 操作过程
音频数据通过DMA方式从RAM到I2S控制器的FIFO中,I2S控制器通过I2S总线将数据传送到CODEC中,然后哦CODEC就能播放出声音了。而对CODEC寄存器的配置通过i2c总线,所以该驱动需要实现I2C设备驱动。
关键字:DMA,I2C,I2S
2. 驱动阅读
该驱动需要具备一下知识:i2c,i2s,dma
2.1 模块的初始化和退出
static int __init init_clx_i2s(void) //模开初始化
{
int errno,ret=0;
//调用kmalloc给结构体 i2s_controller 分配空间
if ((errno = probe_clx_i2s(&i2s_controller)) < 0)
return errno;
//注册dsp , mixer ,初始化i2s,见2.2
attach_clx_i2s(i2s_controller);
//注册一个I2C驱动管理结构体,在 soc_cs42l51_i2c_drv 结构体会初始化CODEC (cs42l51),见2.3分析
ret = i2c_add_driver(&soc_cs42l51_i2c_drv);
if(ret)
{
printk("%s-%d: register i2c driver failed\n",__FUNCTION__,__LINE__);
return ret;
}
printk("Cirrus Logic i2s SoC codec cs42l51 driver verison 1.0 date 090407\n");
return ret;
}
static void __exit cleanup_clx_i2s(void)
{
unload_clx_i2s(i2s_controller);
i2c_del_driver(&soc_cs42l51_i2c_drv);
}
module_init(init_clx_i2s);
module_exit(cleanup_clx_i2s);
2.2 attach_clx_i2s(i2s_controller)
//注册dsp , mixer ,初始化i2s
attach_clx_i2s(i2s_controller);
static void __init attach_clx_i2s(struct clx_i2s_controller_info *controller)
{
char *name;
int i, adev;
name = controller->name;
clx_i2s_initHw(); //i2s初始化,见2.2.1
/* register /dev/audio ? */
adev = register_sound_dsp(&clx_i2s_audio_fops, -1);
if (adev < 0)
goto audio_failed;
/* initialize I2S codec and register /dev/mixer */
if (clx_i2s_codec_init(controller) <= 0)
goto mixer_failed;
DMA通道,BUF申请
******************************************
}
2.2.1 clx_i2s_initHw(); //i2s初始化
static void
clx_i2s_initHw(void)
{
/* 通过读PLL的CFCR2得到SYS_CLK时钟 */
i2s_clk = __cpm_get_i2sclk();
__i2s_disable();
__i2s_as_master();
/* 已知SYNC,通过设置CFCR2和I2SDIV.DV,得到SYS_CLK和 BIT_CLK */
__i2s_set_sample_rate(i2s_clk, SYNC_CLK);
/* 选择i2s 标准帧格式,左对齐格式选择 */
// __i2s_select_left_justified();
// __i2s_select_i2s();//i2s,add by ykz
__i2s_enable();
__i2s_reset();
schedule_timeout(50);
udelay(160);
__i2s_reset_codec();
__i2s_set_sample_size(16);
__i2s_disable_record();
__i2s_disable_replay();
__i2s_disable_loopback();
__i2s_set_transmit_trigger(7);
__i2s_set_receive_trigger(7);
__i2s_select_left_justified(); //i2s left_justified
}
2.3 i2c_add_driver(&soc_cs42l51_i2c_drv) I2C设备驱动
i2c_add_driver被定义在 include/linux/i2c.h文件中。其实质是i2c-core.c文件中的i2c_register_driver函数
该函数的原型如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
*******************
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
/* for new style drivers, when registration returns the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
mutex_lock(&core_lists);
//将该driver的list成员加入到全局的drivers链表尾部,linux中大量存在这种链表的结构体
list_add_tail(&driver->list,&drivers);
pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
/* legacy drivers scan i2c busses directly */
if (driver->attach_adapter) {
struct i2c_adapter *adapter;
//该函数搜索整个adapters链表,item指向每一个链表中的成员,这里实际是一个for循环
list_for_each_entry(adapter, &adapters, list) {
driver->attach_adapter(adapter);
}
}
mutex_unlock(&core_lists);
return 0;
}
EXPORT_SYMBOL(i2c_register_driver);
在这个函数中,首先向内核中注册你的驱动,然后锁信号量。。最关键的一步是:
driver->attach_adapter(adapter);
而attach_adapter就是在clx_i2s.c文件中定义的重要的驱动结构体,定义如下。
static struct i2c_driver soc_cs42l51_i2c_drv = {
.driver = {
.name = CS41L51_DRV_NAME,
.owner = THIS_MODULE,
},
//.id = I2C_DRIVERID_CS42L51, /*this should be defined in linux/i2c-id.h */
.attach_adapter = &i2c_cs42l51_attach,
.detach_adapter = &i2c_cs42l51_detach,
//.command = NULL,
};
static int i2c_cs42l51_attach(struct i2c_adapter *adap)
{
return i2c_probe(adap, &addr_data, soc_cs42l51_i2c_probe); //见2.3.1
}
2.3.1 I2C probe函数分析
调用i2c框架函数i2c_probe来进行适配器的加载。
在下面这个函数中传递参数为
适配器变量:adapter。
i2c_client_address_data结构体
static unsigned short normal_addr[] = { 0x4a, I2C_CLIENT_END };
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr,
.probe = ignore,
.ignore = ignore,
};
这里0x4a 就是I2C设备的地址。
注意:对于I2C设备地址,都要右移一位去掉读写标志位
int i2c_probe(struct i2c_adapter *adapter,
struct i2c_client_address_data *address_data,
int (*found_proc) (struct i2c_adapter *, int, int))
{
int i, err;
int adap_id = i2c_adapter_id(adapter);
/* Force entries are done first, and are not affected by ignore
entries */
// address_data->forces 为空,不执行
if (address_data->forces) {
********************************
}
/* Stop here if we can't use SMBUS_QUICK */
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) {
if (address_data->probe[0] == I2C_CLIENT_END
&& address_data->normal_i2c[0] == I2C_CLIENT_END)
return 0;
dev_warn(&adapter->dev, "SMBus Quick command not supported, "
"can't probe for chips\n");
return -1;
}
/* Probe entries are done second, and are not affected by ignore
entries either */
for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) {
************************
}
/* Normal entries are done last, unless shadowed by an ignore entry */
//执行这部!通过i2c_probe_address函数来回调你编写的soc_cs42l51_i2c_probe (见2.3.2)加载函数。
for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) {
int j, ignore;
ignore = 0;
for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;
j += 2) {
if ((address_data->ignore[j] == adap_id ||
address_data->ignore[j] == ANY_I2C_BUS)
&& address_data->ignore[j + 1]
== address_data->normal_i2c[i]) {
dev_dbg(&adapter->dev, "found ignore "
"parameter for adapter %d, "
"addr 0x%02x\n", adap_id,
address_data->ignore[j + 1]);
ignore = 1;
break;
}
}
if (ignore)
continue;
dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
"addr 0x%02x\n", adap_id,
address_data->normal_i2c[i]);
err = i2c_probe_address(adapter, address_data->normal_i2c[i],
-1, found_proc);
if (err)
return err;
}
return 0;
}
EXPORT_SYMBOL(i2c_probe);
2.3.2 soc_cs42l51_i2c_probe 函数分析
static int soc_cs42l51_i2c_probe(struct i2c_adapter *adap, int addr, int kind)
{
struct i2c_client *client;
int rc,ret=0;
/*现在开始创建client结构, 即使我们还不能完全填充它,但是它允许我们安全地访问几个i2c函数*/
client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if(client==NULL)
{
printk("%s-%d: out of memory\n",__FUNCTION__,__LINE__);
ret = -ENOMEM;
goto err;
}
strncpy(client->name, CS41L51_DRV_NAME, I2C_NAME_SIZE);
client->addr = addr;
client->adapter = adap;
client->driver = &soc_cs42l51_i2c_drv;
//i2c_attach_client(client)向系统注册设备。新的client将依附于adapter;告诉 i2c层一个新的从设备到达,知会I2C核心系统中包含一个新的I2C设备
if ((rc = i2c_attach_client(client)) != 0) {
kfree(client);
return rc;
}
save_client = client;
/* codec 芯片初始化,详细的见源代码和 datasheet */
ret = soc_cs42l51_setup();
if(ret)
{
printk("%s-%d: init codec device failed\n",__FUNCTION__,__LINE__);
goto att_err;
}
return ret;
att_err:
kfree(client);
err:
return ret;
}
2.4 dsp,mixer open,读写 操作
static struct file_operations clx_i2s_audio_fops =
{
owner: THIS_MODULE,
open: clx_audio_open,
release: clx_audio_release,
write: clx_audio_write,
read: clx_audio_read,
poll: clx_audio_poll,
ioctl: clx_audio_ioctl
};
open:主要完成,I2S再次初始化,通过i2c对 codec芯片再次初始化,设置好3个时钟(SYNC,BIT_CLK,SYS_CLK)
write:通过dma方式将数据发送出去
read:通过dma方式接收数据
3. 关于I2S频率问题
SYNC---采样频率
SYS_CLK----给CODEC的时钟频率
BIT_CLK----位时钟频率
3.1 发现问题
在最开始驱动移植好之后,出现只有32KHZ的时候,声音比较清晰,但还是跟正常的有偏差;其他的采样频率如48KHZ,32KHZ都有问题
3.2 解决问题
通过问题的发现,我们根据一下过程最终解决问题
**通过用6pci开发板测试发现
.1 NOR 启动,主频360M,PLL的CFCR:nf=0xc2 (194),nr=0x00,no=ox01;能正常抓到SYNC频率,如(48KHZ)
.2 NOR 启动,改变主频为335M,PLL的CFCR:nf=0xb4 (180),nr=0x00,no=ox01;能正常抓到SYNC频率,如(48KHZ)
.3 NAND 启动,主频362672000 HZ,PLL的CFCR:nf=0xc1 ,nr=0x00,no=ox01;抓到SYNC频率,为固定的(58KHZ),它不随着SYNC的改变而改变
.4 通过上述信息,发现是NAND BOOT有问题,且很有可能跟PLL CFCR寄存器的设置有关。
**于是做以下修改
.1 修改NAND BOOT的src/cq8401.c,pll_init()函数,从U-BOOT (board/mingddie/mingddie.c)中对应函数COPY 代码
.2 从新编译NAND BOOT,烧写,发现现在频率正常了,且声音也对了,说明跟pll_init()函数里设置的cfcr(0x10000000), plcr(0x10000010)有关
.3 考虑到plcr肯定是设置对的,那就跟cfcr有关,把NAND BOOT cfcr修改前后的值 0x4e002220(before),0x61093330(after)对比结合CQ8401数据手册
发现第24位没有正确设置,置1后解决问题,24位描述如下
24 UPE
Update enable. If UPE is 1, writes on CFR, HFR, PFR, MFR, UFR, SSIFR I2SFR will start a
frequency changing sequence immediately. When UPE is 0, writes on CFR, HFR, PFR, MFR,
UFR, SSIFR and I2SFR will not start a frequency changing sequence immediately. The division
ratio is actually updated in PLL multiple ratio changing sequence or PLL Disable Sequence.
0: Division ratios are updated in PLL multiple ratio changing sequence or PLL Disable Sequence
1: Division ratios are updated immediately
3.3 推荐频率的设置
为了声音更接近真实,我们推荐SYNC=FS,BIT_CLK=64FS(CQ8401已固定的),SYS_CLK=256FS(推荐),那么需要设置的寄存器有I2SDIV.DV(0x10020030),CFCR2(0x10000060)
BIT_CLK=2*采样频率*采样格式(8,16,32).
SYS_CLK,BIT_CLK,SYNC的流程图关系详见CQ8401 DATASHEET P267页。
4. 注意的问题
4.1 硬件问题:SN74CBTLV3257D 芯片的片选问题和供电问题
4.2 软件方面:
NAND BOOT中pll_init()函数cfcr(0x10000000), plcr(0x10000010)的设置;
最主要的是CQ8401 I2S控制器与CODEC的数据帧格式要对应,如果不对应出来的声音会有杂音
如: CQ8401(I2SCR 0x10020010 位0)----------CODEC(cs42l51 0x04 位2~5)
0(I2S标准数据帧格式) 对应 0011(I2S, up to 24-bit data,I2S标准数据帧格式)
1(I2S左对齐数据帧格式) 对应 0000(Left-Justified, up to 24-bit data,I2S左对齐数据帧格式)
5. 未解决的问题
由于硬件没有做相关接口,I2S 音频录音问题,没有测试