Audio codec FPGA 验证时I2C读写寄存器小结

Audio codec由于涉及到D/A,A/D等模拟相关的操作,所以在FPGA上无法实现其功能,但是其内部的寄存器,是可以通过I2C对其读写的。
ZX2000内置CODEC是挂在芯片的I2C2上,所以第一步需要在dts中添加I2C2,注意zx2000内置audio codec的i2c slave addr为0x8。


zx2000.dtsi:
  i2c2:
i2c@d83a0000 {
   #address-cells = <1>;
   #size-cells = <0>;
   compatible =  "zx,zx2000-i2c";
   linux,i2c-index = <2>;
   reg = < 0xd83a0000 0x10000 >;
   interrupts = <0 46 0x04>;
  };
zx2000-fpga.dts:
  i2c2:
i2c@d83a0000 {
   zx2kdac:
zx2kdac@8 {
    compatible = "zx,zx2kdac";
    reg = <0x8>;
    };
  };
  sound-i2s {
   compatible = "zhaoxin,zx2000-i2s-audio";
   zx,model = "zx2000-i2s-audio";
   zx,i2s-controller = <&i2s0>;
   zx,audio-codec = <&zx2kdac>;
  };
zx2000内置codec的i2c时序如下:

写:
‘slave_addr[6:0] + R/W’ + ‘register_addr[6:0] + 0’ + ‘data[7:0]’
读:
 ‘slave_addr[6:0] + R/W’ + ‘register_addr[6:0] + 0’ + ‘slave_addr[6:0] + R/W’+ data[7:0]
也就是,上面图的‘左 + 中 + 右’是写的流程;上面图的‘左 + 中’+ 下面图是读的流程。

具体实现到代码里,写如下:
static int zx2kdac_hw_write(void *context, unsigned int reg, unsigned int val)
{
 struct i2c_client *client = context;
 u8 data[2];
 int ret;

 data[0] = ((reg & 0x7f) << 1); // 要写入的寄存器地址左移一位, bit0写0。register_addr[6:0] + 0
 data[1] = val & 0xff; //要写入8位的数据。

 ret = i2c_master_send(client, data, 2);
 if(ret == 2)
  return 0;
 if (ret < 0)
  return ret;
 else
  return -EIO;
}
如果数据是16bit, 该怎么办?
static int zx2kdac_hw_write(void *context, unsigned int reg, unsigned int val)
{
 struct i2c_client *client = context;
 u8 data[3];
 int ret;

 data[0] = ((reg & 0x7f) << 1);
 data[1] = (0xff00 & val) >> 8; //高8位数据放在data[1]
 data[2] = val & 0xff; //低8位数据放在data[2]

 ret = i2c_master_send(client, data, 3);//传输3个data
 if (ret == 3)
  return 0;
 if (ret < 0)
  return ret;
 else
  return -EIO;
}
实际上,在drivers/i2c/busses/i2c-nomadik.c中,对于写,还提供了另外一种方法:
写两个字节(0x23, 0x46),从地址0x1:
Wr_buff[0] = 0x1; //reg_addr 地址
Wr_buff[1] = 0x23;//数据
Wr_buff[2] = 0x46; //数据
Msg[0].buf = Wr_buff;
Msg[0].flags = 0x0;
Msg[0].len = 3;
Msg[0].addr = client->addr; //slave_addr
I2c_transfer(client->adapter, msg, 1);
由于zx2000内置codec只是每次写8bit(1byte)数据所以应该写成这样:
Wr_buff[0] = 0x1; //reg_addr 地址
Wr_buff[1] = 0x23;//数据
Msg[0].buf = Wr_buff;
Msg[0].flags = 0x0;
Msg[0].len = 2;
Msg[0].addr = client->addr; //slave_addr
I2c_transfer(client->adapter, msg, 1);

读的代码:
static int zx2kdac_hw_read(void *context, unsigned int reg, unsigned int *val) //读出的值,放到val中
{
 struct i2c_client *client = context;
 struct i2c_msg xfer[2];
 u8 r[1];
 u8 data[1];
 u8 data2;
 int ret;

 /* Write register */
 r[0] = ((reg & 0x7f) << 1); // 要写入的寄存器地址左移一位, bit0写0。register_addr[6:0] + 0
 xfer[0].addr = client->addr; //i2c slave_addr
 xfer[0].flags = 0; //flags = 0 为写
 xfer[0].len = 1; //1 byte, 8 bit
 xfer[0].buf = &r[0];//register_addr要写入到i2c_msg.buf中

 /* Read data */
 xfer[1].addr = client->addr;//从这个i2c slave addr器件
 xfer[1].flags = I2C_M_RD;//读
 xfer[1].len = 1; //1个byte, 8个bit
 xfer[1].buf = &data[0]; //放到i2c_msg[1].buf中,数据可以从data[0]读出来。

 ret = i2c_transfer(client->adapter, xfer, 2);
 if (ret != 2) {
  dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
  return -EIO;
 }

 *val = data[0];

 printk("read %02x => %04x\n", reg, *val);

 return 0;
}
如果是16比特数据,该怎么读:
static int zx2kdac_hw_read(void *context, unsigned int reg, unsigned int *val)
{
 struct i2c_client *client = context;
 struct i2c_msg xfer[2];
 u8 r[1];
 u8 data[2];
 int ret;

 /* Write register */
 r[0] = ((reg & 0x7f) << 1);
 xfer[0].addr = client->addr;
 xfer[0].flags = 0;
 xfer[0].len = 1;
 xfer[0].buf = &r[0];

 /* Read data */
 xfer[1].addr = client->addr;
 xfer[1].flags = I2C_M_RD;
 xfer[1].len = 2; //读2个byte, 16bit
 xfer[1].buf = data;

 ret = i2c_transfer(client->adapter, xfer, 2); //两个 i2c_msg
 if (ret != 2) {
  dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
  return -EIO;
 }

 *val = (data[0] << 8) | data[1];//两个byte的前一个byte放在data[0], 后一个byte放在data[1]

 printk("read %02x => %04x\n", reg, *val);

 return 0;
}
在代码里,可以通过调用上面的读和写来操作codec中的寄存器,如:
zx2kdac_hw_write(i2c, 0x1, 0x70);
zx2kdac_hw_read(i2c, 0x1, &val);//val中保存返回值。
上面的读和写可以进一步被封装:
static const struct regmap_config zx2kdac_regmap = {
 .reg_bits = 8, //register_addr 8位
 .val_bits = 8, //register_val值也是8位
 .max_register = 19+1, //zx2000 codec总共20个寄存器,
 .reg_defaults = zx2kdac_reg,
 .num_reg_defaults = ARRAY_SIZE(zx2kdac_reg),
 .writeable_reg = zx2kdac_writeable_register,
// .volatile_reg = zx2kdac_volatile_register,
 .readable_reg = zx2kdac_readable_register,
 .reg_read = zx2kdac_read,//读被赋到这里
 .reg_write = zx2kdac_write,//写被赋到这里
 .cache_type = REGCACHE_RBTREE,
};
zx2kdac_regmap可以被下面两个函数接口调用:
1: zx2kdac->regmap = devm_regmap_init_i2c(i2c, &zx2kdac_regmap);
 devm_regmap_init_i2c ---〉devm_regmap_init(&i2c->dev, &regmap_i2c, &i2c->dev, config)
 regmap_i2c中会定义.write/.read操作, 和上面的write/read操作差不多,只不过里面针对的是标准的i2c的操作, 对于zx2000 7位register_addr这种情况是不适用的。
有一种方法,将7位地址转成8位地址之后,可以调用这个接口:
比如,原来是要读codec中0x1寄存器的值,用
regmap_read(zx2kdac->regmap, 0x1, &val);
 需要替换成
regmap_read(zx2kdac->regmap, 0x2, &val); // 0x1<<1 = 0x2, 7位register_addr转成8位,最后一位为0
 在regmap_read/regmap_write中会根据是否定义了regmap_i2c来决定调用哪种形式的.write/.read操作。如果定义了regmap_i2c,就用regmap_i2c中的.write/.read操作,如果没有定义regmap_i2c, 就用config中的.write/.read操作,也就是我们codec中regmap_config中的.reg_read/.reg_write操作。
2: zx2kdac->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &zx2kdac_regmap);
 由于zx2000的codec i2c时序比较特殊, 所以,没有用内核中的devm_regmap_init_i2c这个接口,而是调用里面更深一级的接口,同时将regmap_i2c设置成NULL ,这样,regmap_read/write就会调用我自己在codec中定义的.reg_read/.reg_write操作。

 

上面介绍的是在codec的驱动中,通过i2c接口来读写寄存器。
有些情况下, 为了调试的方便, 可以通过应用层代码的调用(ioctl),来实时的改变i2c外设里面寄存器的值,思路和上面是一样的,就是构造i2c msg,通过ioctl将i2c msg传递到底层。

这个文件时我在网上找到的:
http://blog.csdn.net/21cnbao/article/details/7919055
使用下面命令执行:
./i2c /dev/i2c-2 0x8 0x4 0 //读
./i2c /dev/i2c-2 0x8 0x4 1 0x7c //写
注释:
/dev/i2c-2 //设备挂在i2c总线2上,对应的节点‘/dev/i2c-2’
0x8 //该器件的i2c slave_addr 为8
0x4 //操作i2c外设内部的这个地址
0/1 //读/写
0x7c //写进去的值为0x7c
我用i2c_baohua.c测试的时候,发现是可以读寄存器值的,但是写一个新值进去之后,再读,读出来的就都是0。很奇怪,后来进一步调试发现,是由于新值被当成0写进去了,原因是没有按照格式写新值。
代码中原来是:
sscanf(argv[5], "%d", &w_val);
新值是十进制的格式,我用了16进制的格式。
所以需要改为:
sscanf(argv[5], "%x", &w_val);

注意:
1: Machine driver那边对应的codec_name应该是zx2kdac.2-0008(name.i2c_bus-i2c_salve_addr)这种形式。
2: 要首先让i2s那边正确设置audio DAC 之后,才可以通过i2c在线读写codec寄存器的值。
两种方法:
1):在I2S hw_params 操作的时候,进行audio DAC I2C相关寄存器的设置,然后在播放音乐的过程中使用上面的测试代码在线读写audio DAC 寄存器值的。
2):在I2S 做init的时候,进行audio DAC I2C相关寄存器的设置。这样,系统起来之后,不需要播放音乐也可以在线读写audio DAC 寄存器的值。
另外,在调试过程中遇到一个问题:在codec i2c的probe中可以通过i2c读写寄存器,但是在codec的probe中却不可以通过i2c读写寄存器,这个需要在调用codec probe之前,在i2s代码中设置audio DAC。可以通过上面的第2种方法解决。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值