作者: 韩大卫 @吉林师范大学
现有的关于 i2c switch 资料非常少。即使阅读完官方的datasheet.也不能写出完全正确的操作。
因为内核中的驱动本身不是那么完善的。还有一些资料是单片机编程的,可惜在linux上并不能成功执行。
pca954x 系列是一种 i2c switch 芯片,比如 pca9548 可实现8个开关, 进而实现多了8条i2c通道。
这样可以在有限的i2c 资源上扩展出足够多的接口。解决了在使用 i2c总线容量的问题。
Pca954x 内部只有一个控制寄存器。属于无子地址设备。做I/O 访问时,只需要向0x00 地址处做写操作即可,
这就可实现pca954x 下挂设备 i2c bus 的选路。
但是在现有的pca954x 驱动函数中,没有实现自动对内部控制寄存器进行相应配置,这样的话就需要额外的写一个附加的配置函数,实现这种功能。
如果没有这种配置函数,只是使用现有的内核代码,那么会出现有一些难以发现的问题,但是还是被我遇到了。
在我看来,这种问题暂且不能算bug,但至少应该去优化,毕竟,如果每次在访问不同的i2c bus 时,
需要我们手动的去操作i2c switch 的开关,这多少会影响执行效率,代码量变大。还有一点,
我们自己编写的配置函数,是严重依赖于硬件的,即我们的开关位置打开的位置需要自己判断,
在代码上固定写出, 可移植性差。稍后可以在我的代码中看到这种缺陷。
基于以上原因, 我认为pca954x 的驱动应该修改。有时间的话我会整理出自己的代码,融入到内核代码中去,再提供出API 供大家使用。
I2C 1 地址 0x71,0x72,0x73 上都是pca9548 , 每个pca9548上 挂了 8 个 千兆以太网光模块sfp。 这样 我们系统上就可以同时挂载 24 个 千兆以太网光模块sfp。
I2C 0 地址 0x70 也是pca9548, 挂了2个万兆以太网光模块XFP,还有3个温度传感器TMP411.
*********** ***************
下面的内容是i2c bus 选路函数。之后是从内核代码入手的分析过程,以证明我的判断,阅读起来肯定是
有些难度,因为驱动的工作本身亦如此。如果不是从事嵌入式linux驱动的,就不必深究。
阅读本文前提是在linux的用户层和内核层要有所了解,请参考其他资料。
*********** ************************
如果需要完整的代码,请联系我:[email protected]
转载请务必表明出处。
******* ****************************
// 这是需要我们自己添加的函数。使用它来控制 i2c bus 的选路。
// 0x70 在 i2c 0上 , 0x71 0x72 0x73 在 i2c 1 上。
// 如果是操作的 i2c bus 是/dev/i2c-10 ,程序根据 10 来判断i2c bus 的选路。
// /dev/i2c-2 到 /dev/i2c-9 属于 0x70 的pca9548
// /dev/i2c-10 到 /dev/i2c-17 属于 0x71 的pca9548
// /dev/i2c-18 到 /dev/i2c-25 属于0x72 的pca9548
// /dev/i2c-26 到 /dev/i2c-33 属于 0x73 的pca9548
inline int i2c_bus_chan_enable(char* argv,int flag){
int ret,tmp;
unsigned char val = 0;
unsigned short addr;
char *s = argv;
while(*s++ != '-' && *s);
if(*s)
ret = atoi(s);
if(ret < 10 && ret != 1)
addr = 0x70;
else
addr = ret < 18 ? 0x71 : (ret > 25 ? 0x73 : 0x72);
if(addr != 0x70){
tmp = ( addr == 0x71 ? 10 : (addr == 0x72 ? 18 : 25));
val = 1 << ((ret - tmp) % 8 ) ;
}
else{
// 给相应的 i2c bus 置1
if( ret == 2 )
val = 1 << 1;
else if( ret == 3 )
val = 1 << 2;
else if( ret == 4 )
val = 1 << 3;
else if( ret == 9 )
val = 1 << 7;
else if( ret == 8 )
val = 1 << 6;
}
// 先向 pca9548 的 i2c 地址 写相应的数值,打开相应的i2c bus
ret = i2c_write_data(addr,0x00,val);
if(ret < 0){
printf("i2c switch init error!\n");
return -1;
}
return 0;
}
********* *******************
下面是在此函数的使用:
main.c{
…..
int ret,tmp;
unsigned char val = 0;
char cmd_buf[1024] = {0};
unsigned short addr ;
i2c_path(argv[2],0);
i2c_bus_chan_enable(argv[2],1);
printf("offset = 0x%x\n",offset);
for(addr = 0x00; addr < 0xff ;addr++){
ret = i2c_read_data(addr,0x00,&val);
if(!ret)
printf("addr = %x,val = %x\n",addr,val);
}
}else
error_info();
}
…..
}
inline int
i2c_read_data(u16 addr, u8 offset, u8 *val)
{
int ret = 0;
struct i2c_rdwr_ioctl_data *data;
if ((data = (struct i2c_rdwr_ioctl_data *)malloc(sizeof(struct i2c_rdwr_ioctl_data))) == NULL)
return -1;
data->nmsgs = 2;
if ((data->msgs = (struct i2c_msg *)malloc(data->nmsgs * sizeof(struct i2c_msg))) == NULL) {
ret = -1;
goto errexit3;
}
if ((data->msgs[0].buf = (unsigned char *)malloc(sizeof(unsigned char))) == NULL) {
ret = -1;
goto errexit2;
}
if ((data->msgs[1].buf = (unsigned char *)malloc(sizeof(unsigned char))) == NULL) {
ret = -1;
goto errexit1;
}
data->msgs[0].addr = addr;
data->msgs[0].flags = 0;
data->msgs[0].len = 1;
data->msgs[0].buf[0] = offset;
data->msgs[1].addr = addr;
data->msgs[1].flags = I2C_M_RD;
data->msgs[1].len = 1;
data->msgs[1].buf[0] = 0;
if ((ret = __i2c_send(fd, data)) < 0)
goto errexit0;
*val = data->msgs[1].buf[0];
errexit0:
free(data->msgs[1].buf);
errexit1:
free(data->msgs[0].buf);
errexit2:
free(data->msgs);
errexit3:
free(data);
return ret;
}
static int
__i2c_send(int fd, struct i2c_rdwr_ioctl_data *data)
{
int ret;
if (fd < 0)
return -1;
if (data == NULL)
retu