i2c是常见的通信协议,协议比较简单,只有数据和时钟两条线(SDA和SCL),i2c的通信分为主机和从机,主机一般占主导地位,从机可以有多个。
i2c通信的数据格式为(SDA上的数据):开始的7位里面指定了设备地址(因为有多个从机),第8位是读或写信号,表示此次传输是读还是写,第9位是ack信号,也就是当某个从机收到信号好需要发ack信号(低电平)确认。
接下来就是发数据了,这个过程是由8个数据位和一个ack位组成,如果是读,这8位数据由从机驱动,第9位ack由主机发出(因为此时是主机向从机读数据,主机需要确认是否收到数据),如果是写,则这8位由主机驱动,第9位ack由从机发出,发数据这个过程是可以反复进行的,一旦发送或者读取完成,主机就会发出一个停止信号(SDA由低变高)来结束通信,这所有的过程都是在SDA上进行的。
i2c通信可以分为读和写,读比写的过程更复杂,要读则先写,读取某设备内某地址的数据过程为:先发送设备地址,然后还要写入设备内要读取的地址,最后再发送设备地址后才可进行数据读取。写入数据则少了再发设备地址这一步,写是直接发送设备地址和设备内要写入的地址后就可以发送要写入的数据了。
写:
读:
内核驱动框架分析:
在开发i2c驱动程序时,我们可以自己根据芯片手册进行相关寄存器和时序等设置,这种开发比较原始,完全从底层开始,工作量大并且出错概率大,这种其实就是裸机开发了。如果是在Linux内核上进行开发,则完全可以利用内核提供的i2c框架进行开发。i2c框架主要分为两大部分,一部分是用平台设备总线驱动模型实现的,主要封装了主机(s3c2440芯片)i2c控制器的寄存器和时序设置等操作,另一部分就是设备驱动(相当于从机),主要提供主机部分具体操作所需要的数据信息,如果用总线设备驱动模型来看,这一部分就相当于提供设备信息,所以这两大部分又可以看成总线设备驱动模型。
i2c框架代码在/linux-2.6.22.6/drivers/i2c这个目录下,这里面又有algos、busses、chips三个部分,algos主要是协议算法相关的东西,busses里是主机(s3c2440芯片)i2c控制器的寄存器和时序设置等操作部分,chips里就是具体的设备驱动(相当于从机),框架入口就在busses下i2c-s3c2410.c这个文件里。
先分析i2c-s3c2410.c
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{struct s3c24xx_i2c *i2c = &s3c24xx_i2c;struct resource *res;int ret;/* find the clock and enable it */i2c->dev = &pdev->dev;i2c->clk = clk_get(&pdev->dev, "i2c");if (IS_ERR(i2c->clk)) {dev_err(&pdev->dev, "cannot get clock\n");ret = -ENOENT;goto err_noclk;}dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);clk_enable(i2c->clk);/* map the registers */res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (res == NULL) {dev_err(&pdev->dev, "cannot find IO resource\n");ret = -ENOENT;goto err_clk;}i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,pdev->name);if (i2c->ioarea == NULL) {dev_err(&pdev->dev, "cannot request IO\n");ret = -ENXIO;goto err_clk;}i2c->regs = ioremap(res->start, (res->end-res->start)+1);if (i2c->regs == NULL) {dev_err(&pdev->dev, "cannot map IO\n");ret = -ENXIO;goto err_ioarea;}dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res);/* setup info block for the i2c core */i2c->adap.algo_data = i2c;i2c->adap.dev.parent = &pdev->dev;/* initialise the i2c controller */ret = s3c24xx_i2c_init(i2c);if (ret != 0)goto err_iomap;/* find the IRQ for this unit (note, this relies on the init call to* ensure no current IRQs pending */res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);if (res == NULL) {dev_err(&pdev->dev, "cannot find IRQ\n");ret = -ENOENT;goto err_iomap;}ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);if (ret != 0) {dev_err(&pdev->dev, "cannot claim IRQ\n");goto err_iomap;}i2c->irq = res;dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,(unsigned long)res->start);ret = i2c_add_adapter(&i2c->adap);if (ret < 0) {dev_err(&pdev->dev, "failed to add bus to i2c core\n");goto err_irq;}platform_set_drvdata(pdev, i2c);dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id);return 0;err_irq:free_irq(i2c->irq->start, i2c);err_iomap:iounmap(i2c->regs);err_ioarea:release_resource(i2c->ioarea);kfree(i2c->ioarea);err_clk:clk_disable(i2c->clk);clk_put(i2c->clk);err_noclk:return ret;
}static struct platform_driver s3c2440_i2c_driver = {.probe = s3c24xx_i2c_probe,.remove = s3c24xx_i2c_remove,.resume = s3c24xx_i2c_resume,.driver = {.owner = THIS_MODULE,.name = "s3c2440-i2c",},
};static int __init i2c_adap_s3c_init(void)
{int ret;ret = platform_driver_register(&s3c2410_i2c_driver);if (ret == 0) {ret = platform_driver_register(&s3c2440_i2c_driver);if (ret)platform_driver_unregister(&s3c2410_i2c_driver);}return ret;
}static struct resource s3c_i2c_resource[] = {[0] = {.start = S3C24XX_PA_IIC,.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_IIC,.end = IRQ_IIC,.flags = IORESOURCE_IRQ,}};
devs.c
static struct resource s3c_i2c_resource[] = {[0] = {.start = S3C24XX_PA_IIC,.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_IIC,.end = IRQ_IIC,.flags = IORESOURCE_IRQ,}};struct platform_device s3c_device_i2c = {.name = "s3c2410-i2c",.id = -1,.num_resources = ARRAY_SIZE(s3c_i2c_resource),.resource = s3c_i2c_resource,
};
devs.c和i2c-s3c2410.c是标准的总线设备驱动模型,主要提供主机(s3c2440芯片)i2c控制器的寄存器和时序设置等操作,当设备和驱动匹配后,会调用s3c24xx_i2c_probe,s3c24xx_i2c_probe里又会通过ret = i2c_add_adapter(&i2c->adap);去调剂一个adapter,这个adapter就是给设备驱动(从机部分)开了一个口子,使得这两大部分又构成一个设备驱动模型。
分析i2c_add_adapter(i2c-core.c)
int i2c_add_adapter(struct i2c_adapter *adapter)
{int id, res = 0;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lists);/* "above" here means "above or equal to", sigh */res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);mutex_unlock(&core_lists);if (res < 0) {if (res == -EAGAIN)goto retry;return res;}adapter->nr = id;return i2c_register_adapter(adapter);
}static int i2c_register_adapter(struct i2c_adapter *adap)
{list_add_tail(&adap->list, &adapters);........list_for_each(item,&drivers) {driver = list_entry(item, struct i2c_driver, list);if (driver->attach_adapter)/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}}
i2c_add_adapter里调用了i2c_register_adapter,i2c_register_adapter里先把自己放入adapters数组,然后会循环遍历drivers这个链表,drivers存放的是设备驱动(从机部分),这个操作会调用
drivers里的每个driver的attach_adapter函数,这个函数会检测这个driver对应的设备是否匹配,这个后面会讲到。
接下来看下设备驱动(从机部分)代码,以chips下eeprom.c为例:
static int __init eeprom_init(void)
{return i2c_add_driver(&eeprom_driver);
}
static inline int i2c_add_driver(struct i2c_driver *driver)
{return i2c_register_driver(THIS_MODULE, driver);
}int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{......list_add_tail(&driver->list,&drivers);list_for_each_entry(adapter, &adapters, list) {driver->attach_adapter(adapter);}...
...
}
eeprom.c里也会去注册一个driver,在i2c_register_driver里先把自己加入到drivers这个链表里,然后遍历adapters,取出每一个adapter然后调用driver的attach_adapter,这个步骤和前面注册adapter时处理一样,下面就看下attach_adapter这个方法。
static struct i2c_driver eeprom_driver = {.driver = {.name = "eeprom",},.id = I2C_DRIVERID_EEPROM,.attach_adapter = eeprom_attach_adapter,.detach_client = eeprom_detach_client,
};static int eeprom_attach_adapter(struct i2c_adapter *adapter)
{return i2c_probe(adapter, &addr_data, eeprom_detect);
}
attach_adapter就是eeprom_driver 的attach_adapter 方法,这个方法会调用i2c_probe(adapter, &addr_data, eeprom_detect);addr_data是从设备地址,如果检测到从设备匹配成功则会触发eeprom_detect函数的执行。
下面分析i2c_probe:
int i2c_probe(struct i2c_adapter *adapter,struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int))
{...err = i2c_probe_address(adapter, address_data->normal_i2c[i],-1, found_proc);...}static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int))
{...if (kind < 0) {if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)return 0;/* prevent 24RF08 corruption */if ((addr & ~0x0f) == 0x50)i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL);}...
}s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data)
{...res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);...
}static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data)
{
...if (i2c_transfer(adapter, msg, num) < 0)return -1;...}int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
...ret = adap->algo->master_xfer(adap,msgs,num);...
}
i2c_probe里面的层级调用关系如下:
i2c_probe
i2c_probe_address
i2c_smbus_xfer
i2c_smbus_xfer_emulated
i2c_transfer
adap->algo->master_xfer(adap,msgs,num);
可以看到,i2c_probe最终会调用到adapter里面 algo结构体的 master_xfer函数,adapter是在i2c-s3c2410.c 里构造的,也就是在设置是主机(s3c2440芯片)i2c控制器的寄存器和时序等操作时构造的。也就是说,master_xfer是一个真正i2c通信时数据传输函数,最终这个函数里会去发出中断和开始信号,并设置相关寄存器发起数据传输。
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {.master_xfer = s3c24xx_i2c_xfer,.functionality = s3c24xx_i2c_func,
};static struct s3c24xx_i2c s3c24xx_i2c = {.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),.tx_setup = 50,.adap = {.name = "s3c2410-i2c",.owner = THIS_MODULE,.algo = &s3c24xx_i2c_algorithm,.retries = 2,.class = I2C_CLASS_HWMON,},
};
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;int retry;int ret;for (retry = 0; retry < adap->retries; retry++) {ret = s3c24xx_i2c_doxfer(i2c, msgs, num);if (ret != -EAGAIN)return ret;dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);udelay(100);}return -EREMOTEIO;
}
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num)
{unsigned long timeout;int ret;ret = s3c24xx_i2c_set_master(i2c);if (ret != 0) {dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);ret = -EAGAIN;goto out;}spin_lock_irq(&i2c->lock);i2c->msg = msgs;i2c->msg_num = num;i2c->msg_ptr = 0;i2c->msg_idx = 0;i2c->state = STATE_START;s3c24xx_i2c_enable_irq(i2c);s3c24xx_i2c_message_start(i2c, msgs);spin_unlock_irq(&i2c->lock);timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);ret = i2c->msg_idx;/* having these next two as dev_err() makes life very * noisy when doing an i2cdetect */if (timeout == 0)dev_dbg(i2c->dev, "timeout\n");else if (ret != num)dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);/* ensure the stop has been through the bus */msleep(1);out:return ret;
}static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg)
{unsigned int addr = (msg->addr & 0x7f) << 1;unsigned long stat;unsigned long iiccon;stat = 0;stat |= S3C2410_IICSTAT_TXRXEN;if (msg->flags & I2C_M_RD) {stat |= S3C2410_IICSTAT_MASTER_RX;addr |= 1;} elsestat |= S3C2410_IICSTAT_MASTER_TX;if (msg->flags & I2C_M_REV_DIR_ADDR)addr ^= 1;// todo - check for wether ack wanted or nots3c24xx_i2c_enable_ack(i2c);iiccon = readl(i2c->regs + S3C2410_IICCON);writel(stat, i2c->regs + S3C2410_IICSTAT);dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);writeb(addr, i2c->regs + S3C2410_IICDS);/* delay here to ensure the data byte has gotten onto the bus* before the transaction is started */ndelay(i2c->tx_setup);dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);writel(iiccon, i2c->regs + S3C2410_IICCON);stat |= S3C2410_IICSTAT_START;writel(stat, i2c->regs + S3C2410_IICSTAT);
}
总结:i2c驱动框架也是一个总线设备驱动模型,它可以分为两大部分,第一部分就是标准的总线设备驱动模型,这一部分主要封装对主机(s3c2440芯片)i2c控制器的相关操作,这些操作就是对应芯片手册里的相关寄存器设置等操作,并且还会去注册一个adapter链表,这个adapter里封装一些真正数据传输函数,这样的好处时我们在开发一个具体从设备驱动时,不用关系主机控制器设置这些复杂操作了,我们只需要调用它提供的传输函数就可完成数据的收发。
开发一个i2c驱动时,我们只需关心从设备部分,主要提供主机控制器操作所需要的从设备信息,这就是另一部分。在注册adapter和从设备driver时,两变都会把自己添加进自己的链表里,然后遍历对方链表去调用driver的attach_adapter方法进行匹配。从这个层面看,这两大部分又构成了一个总线设备驱动模型,而从设备驱动类型设备,主要提供主机部分所需操作信息。
i2c驱动实例:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 *//* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};static struct i2c_client_address_data addr_data = {.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */.probe = ignore,.ignore = ignore,//.forces = forces, /* 强制认为存在这个设备 */
};static struct i2c_driver at24cxx_driver;static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{unsigned char address;unsigned char data;struct i2c_msg msg[2];int ret;/* address = buf[0] * data = buf[1]*/if (size != 1)return -EINVAL;copy_from_user(&address, buf, 1);/* 数据传输三要素: 源,目的,长度 *//* 读AT24CXX时,要先把要读的存储空间的地址发给它 */msg[0].addr = at24cxx_client->addr; /* 目的 */msg[0].buf = &address; /* 源 */msg[0].len = 1; /* 地址=1 byte */msg[0].flags = 0; /* 表示写 *//* 然后启动读操作 */msg[1].addr = at24cxx_client->addr; /* 源 */msg[1].buf = &data; /* 目的 */msg[1].len = 1; /* 数据=1 byte */msg[1].flags = I2C_M_RD; /* 表示读 */ret = i2c_transfer(at24cxx_client->adapter, msg, 2);if (ret == 2){copy_to_user(buf, &data, 1);return 1;}elsereturn -EIO;
}static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{unsigned char val[2];struct i2c_msg msg[1];int ret;/* address = buf[0] * data = buf[1]*/if (size != 2)return -EINVAL;copy_from_user(val, buf, 2);/* 数据传输三要素: 源,目的,长度 */msg[0].addr = at24cxx_client->addr; /* 目的 */msg[0].buf = val; /* 源 */msg[0].len = 2; /* 地址+数据=2 byte */msg[0].flags = 0; /* 表示写 */ret = i2c_transfer(at24cxx_client->adapter, msg, 1);if (ret == 1)return 2;elsereturn -EIO;
}static struct file_operations at24cxx_fops = {.owner = THIS_MODULE,.read = at24cxx_read,.write = at24cxx_write,
};static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{ printk("at24cxx_detect\n");/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);at24cxx_client->addr = address;at24cxx_client->adapter = adapter;at24cxx_client->driver = &at24cxx_driver;strcpy(at24cxx_client->name, "at24cxx");i2c_attach_client(at24cxx_client);major = register_chrdev(0, "at24cxx", &at24cxx_fops);cls = class_create(THIS_MODULE, "at24cxx");class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */return 0;
}static int at24cxx_attach(struct i2c_adapter *adapter)
{return i2c_probe(adapter, &addr_data, at24cxx_detect);
}static int at24cxx_detach(struct i2c_client *client)
{printk("at24cxx_detach\n");class_device_destroy(cls, MKDEV(major, 0));class_destroy(cls);unregister_chrdev(major, "at24cxx");i2c_detach_client(client);kfree(i2c_get_clientdata(client));return 0;
}/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {.driver = {.name = "at24cxx",},.attach_adapter = at24cxx_attach,.detach_client = at24cxx_detach,
};static int at24cxx_init(void)
{i2c_add_driver(&at24cxx_driver);return 0;
}static void at24cxx_exit(void)
{i2c_del_driver(&at24cxx_driver);
}module_init(at24cxx_init);
module_exit(at24cxx_exit);MODULE_LICENSE("GPL");
在这个驱动程序里,首先会注册at24cxx_driver,这个注册会触发at24cxx_attach函数的执行,这个函数会通过 i2c_probe(adapter, &addr_data, at24cxx_detect)去触发内核框架函数检测设备地址addr_data是否能匹配成功,如果匹配成功则会调用at24cxx_detect函数。
在at24cxx_detect里,进行了字符设备驱动类和设备的创建,然后就是字符设备驱动那一套东西了,在at24cxx_read里构造好数据调用框架函数i2c_transfer进行数据读取,在at24cxx_write里构造好数据调用框架函数i2c_transfer进行数据写入。