i2c&i2s cs42l51 驱动分析 转载

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 音频录音问题,没有测试
posted @ 2015-12-14 16:07  苍月代表我  阅读(721)  评论(0编辑  收藏  举报