Linux 下 i2c switch pca9548驱动 作者: 韩大卫 @吉林师范大学 现有的关于 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的用户层和内核层要有所了解,请参考其他资料。 *********** ************************ 如果需要完整的代码,请联系我:handawei@jusontech.com 转载请务必表明出处。 ******* **************************** // 这是需要我们自己添加的函数。使用它来控制 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) return -1; if (data->msgs == NULL || data->nmsgs == 0) return -1; ret = ioctl(fd, I2C_RDWR, (unsigned long)data) ; if(ret < 0) return -1; return 0;
Linux 下 i2c switch(选路芯片mux) --- pca9548
最新推荐文章于 2025-02-12 14:45:53 发布