Chinaunix首页 | 论坛 | 博客
  • 博客访问: 104024
  • 博文数量: 35
  • 博客积分: 28
  • 博客等级: 民兵
  • 技术积分: 136
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-03 16:08
文章分类
文章存档

2014年(2)

2013年(9)

2012年(24)

分类: LINUX

2013-09-18 11:54:38

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 音频录音问题,没有测试

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yinkaizhong/archive/2009/05/19/4202212.aspx
阅读(1597) | 评论(0) | 转发(0) |
0

上一篇:linux gpio模拟i2c

下一篇:I2S详解

给主人留下些什么吧!~~