了解I2C的同志都知道,常规I2C驱动中的最重点就是这4个结构体:i2c_driver、i2c_client、i2c_adapter 和i2c_algorithm,而且他们之间的关系错综复杂,我看了好长一段时间,也没看出多少头绪来,而且代码的可移植性很差劲,换个平台,要该的地方一大堆,改了之后还不一定对呢,所以,下面介绍的通用I2C驱动就很必要了。
大家都知道, I2C总线仅仅使用SCL、SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用。因此,I2C总线应用非常的广泛。而工作中,我需要通过I2C实现Hi35XX控制视频芯片CX25828,我是这样做的,首先,把I2C当做一个字符设备,然后用2个GPIO管脚模拟数据线和时钟线,一个管脚控制方向,实现电平的高低控制,这样就可以模拟I2C通信了(CPU与设备间的通信)。
1.当做一个字符设备
看到这个,相信大家一定很熟悉了,对了,这就是我们入门时再熟悉不过的东东了,上层可直接对dev下的“gpioi2c”的这个节点open、write、read、close(不过,就目前这个,无read、write接口,只有ioctl了),接下来,你懂的。(在网上有很多比较成熟的I2C驱动,也是同我这个原理实现的,改改GPIO口地址就可以了)。
- <span style="font-size:14px;">
- int gpioi2c_open(struct inode * inode, struct file * file)
- {
- return 0;
- }
- int gpioi2c_close(struct inode * inode, struct file * file)
- {
- return 0;
- }
-
-
-
- static struct file_operations gpioi2c_fops =
- {
- .owner = THIS_MODULE,
- .ioctl = gpioi2c_ioctl,
- .open = gpioi2c_open,
- .release = gpioi2c_close
- };
-
- static struct miscdevice gpioi2c_dev =
- {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "gpioi2c",
- .fops = &gpioi2c_fops,
- };
-
-
- static int __init gpio_i2c_init(void)
- {
- int ret;
- printk("hello dvr iic\n");
-
- ret = misc_register(&gpioi2c_dev);
- if(0 != ret)
- return -1;
-
- i2c_set(SCL | SDA);
-
- return 0;
- }
- static void __exit gpio_i2c_exit(void)
- {
-
- misc_deregister(&gpioi2c_dev);
- }
-
-
- module_init(gpio_i2c_init);
- module_exit(gpio_i2c_exit);
-
- MODULE_INFO(build, UTS_VERSION);
- MODULE_LICENSE("GPL");</span>
2.对字符设备的控制:ioctl函数
提供给上层的接口,arg参数其实是一个地址,这个地址里面必须存放三个信息:设备地址+寄存器地址+data。通过设备地址找到设备,然后通过寄存器地址配置相应的寄存器。关于每次通信的信息量,这要看上层接口函数ioctl:int ioctl(int file_operations, int cmd, void *addr)时arg地址内存放的内容了,如果内容是int类型即4个字节,就如下。如果这个地址内存放的是结构体,而且sizeof这个结构体的大小为12byte(即设备地址、寄存器地址、以及数据各占4个字节),就不需要移动了,直接填进去。
- <span style="font-size:14px;">int gpioi2c_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
- {
- unsigned int val;
- char device_addr, reg_addr;
- short reg_val;
-
- switch(cmd)
- {
- case GPIO_I2C_READ:
- val = *(unsigned int *)arg;
- device_addr = (val&0xff000000)>>24;
- reg_addr = (val&0xffff00)>>8;
-
- reg_val = gpio_i2c_read(device_addr, reg_addr);
- *(unsigned int *)arg = (val&0xffffff00)|reg_val;
- break;
-
- case GPIO_I2C_WRITE:
- val = *(unsigned int *)arg;
- device_addr = (val&0xff000000)>>24;
- reg_addr = (val&0xffff00)>>8;
-
- reg_val = val&0xff;
- gpio_i2c_write(device_addr, reg_addr, reg_val);
- break;
-
- default:
- return -1;
- }
- return 0;
- }
- </span>
3.对设备的读写:read、write
CX25828中,寄存器的地址为16位,设备地址为8位,所有,I2C每次只能read、write1个字节的数据。。总线空闲时,上拉电阻使SDA和SCL线都保持高电平。当 SCL 稳定在高电平时,SDA 由高到低的变化将产生一个开始位,而由低到高的变化则产生一个停止位。开始位和停止位都由 I2C 主设备产生。在选择从设备时,如果从设备采用 7 位地址,则主设备在发起传输过程前,需先发送1字节的地址信息,前 7 位为设备地址,最后 1 位为读写标志。之后,每次传输的数据也是 1 个字节,从 MSB 位开始传输。每个字节传完后,在SCL的第9个上升沿到来之前,接收方应该发出 1 个 ACK 位。SCL上的时钟脉冲由I2C主控方发出, 在第8个时钟周期之后。
- <span style="font-size:14px;">
- unsigned short gpio_i2c_read(unsigned char devaddress, unsigned int address)
- {
- unsigned short rxdata=0
- unsigned int tmp=0;
- int dev_addr=devaddress;
-
-
- i2c_start_bit();
- i2c_send_byte((unsigned char)(dev_addr));
- i2c_receive_ack();
-
-
- tmp=address&0xff00;
- tmp=tmp>>8;
- i2c_send_byte((unsigned char)tmp);
- i2c_receive_ack();
- tmp=address&0xff;
- i2c_send_byte((unsigned char)tmp);
- i2c_receive_ack();
-
-
- i2c_start_bit();
- i2c_send_byte((unsigned char)(dev_addr) | 1);
- i2c_receive_ack();
-
-
- rxdata = i2c_receive_byte();
-
-
- i2c_stop_bit();
- rxdata=rxdata&0xff;
-
- return rxdata;
- }
-
-
- void gpio_i2c_write(unsigned char devaddress, unsigned int address, unsigned int data)
- {
- int tmp;
- unsigned short devaddr=devaddress&0xfe;
-
-
- i2c_start_bit();
- i2c_send_byte((unsigned char)(devaddr));
- i2c_receive_ack();
-
-
- tmp=address&0xff00;
- tmp=tmp>>8;
- i2c_send_byte((unsigned char)tmp);
- i2c_receive_ack();
- tmp=address&0xff;
- i2c_send_byte((unsigned char)tmp);
- i2c_receive_ack();
-
-
- tmp=data&0xff;
- i2c_send_byte((unsigned char)tmp);
- i2c_receive_ack();
-
-
- i2c_stop_bit();
- }</span>
4.模拟数据线时钟线以及需要调用的一些函数
5.上层调用
-
- void IIC_Write(int devAddr, int regAddr, int val)
- {
- unsigned long writeval, ret;
- writeval = (((devAddr & 0xff) << 24) | ((regAddr & 0xffff) << 8) | (val & 0xff));
- ret = ioctl(i2cfd, I2C_WRITE, &writeval);
- if(0 > ret)
- {
- _ERROR("write iic ERROR");
- }
- }
-
-
- unsigned int IIC_Read(int devAddr, int regAddr)
- {
- unsigned long readval, ret;
-
- readval = (((devAddr & 0xff) << 24) | ((regAddr & 0xffff) << 8));
- ret = ioctl(i2cfd, I2C_READ, &readval);
- if(0 > ret)
- {
- _ERROR("read iic ERROR");
- }
-
- readval &= 0xff;
- return readval;
- }