文章目录
1 IIC
IIC,串行总线,两条数据线:数据线SDA,时钟线SCL。多主机的半双工通信方式。
传输波形示意图:
信号类型:
(1)空闲信号:SDA和SCL同时处于高电平。
(2)起始信号:SCL为高,SDA由高到低的跳变。
(3)结束信号:SCL为高,SDA由低到高的跳变。
(4)响应信号:在第9个时钟接收方接收该字节成功,便会输出一个ACK应答信号,当SDA为高电平,表示为非应答信号NACK,当SDA为低电平,表示为有效应答信号ACK。
2 框架
(1)框架图
(2)结构体
i2c_adapter
:对应于物理上的一个适配器,对应的就是SOC上的I2C控制器
i2c_algorithm
:对应一套通信方法
i2c_driver
:对应一套驱动方法,其主要成员函数是 probe()、 remove()、 suspend()、 resume()等
i2c_client
:对应于真实的物理设备,每个 I2C 设备都需要一个 i2c_client 来描述
一个i2c_adapter需要 i2c_algorithm 中提供的通信函数来控制适配器上产生特定的访问周期。i2c_driver 与 i2c_client 的关系是一对多。
3 源码分析
3.1 总线部分
在drivers/i2c/busses/i2c-s3c2410.c中,初始化
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
},
};
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
注册了平台驱动,当与设备匹配时,调用probe
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
...
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);//分配结构体
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm;//算法
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
...
ret = s3c24xx_i2c_init(i2c);//初始化相关寄存器
if (ret != 0)
goto err_iomap;
...
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);//中断
...
i2c->adap.nr = pdata->bus_num;
ret = i2c_add_numbered_adapter(&i2c->adap);//添加适配器
...
}
i2c_add_numbered_adapter–>i2c_register_adapter,注册适配器
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;//总线
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);//注册设备
...
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
i2c_do_add_adapter);//遍历i2c_bus_type,进行匹配
...
}
遍历i2c_bus_type,匹配driver和adapter,调用i2c_do_add_adapter
static int i2c_do_add_adapter(struct device_driver *d, void *data)
{
struct i2c_driver *driver = to_i2c_driver(d);
struct i2c_adapter *adap = data;
i2c_detect(adap, driver);//探测设备
if (driver->attach_adapter) {
driver->attach_adapter(adap);
}
return 0;
}
看下如何探测设备
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
const struct i2c_client_address_data *address_data;
struct i2c_client *temp_client;
int i, err = 0;
int adap_id = i2c_adapter_id(adapter);//得到adap->nr
...
if (!(adapter->class & driver->class))//如果适配器dapter->class定义为不自动检测类型,函数返回
goto exit_free;
...
for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) {
if (address_data->probe[i] == adap_id
|| address_data->probe[i] == ANY_I2C_BUS) {
temp_client->addr = address_data->probe[i + 1];
err = i2c_detect_address(temp_client, -1, driver);
if (err)
goto exit_free;
}
}
for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) {
int j, ignore;
ignore = 0;
...
temp_client->addr = address_data->normal_i2c[i];
err = i2c_detect_address(temp_client, -1, driver);
if (err)
goto exit_free;
}
exit_free:
kfree(temp_client);
return err;
}
调用i2c_detect_address()函数进行设备地址检测
static int i2c_detect_address(struct i2c_client *temp_client, int kind,
struct i2c_driver *driver)
{
...
if (kind < 0) { //确认总线上挂接有该设备地址的I2C芯片
if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL) < 0)
return 0;
if ((addr & ~0x0f) == 0x50)
i2c_smbus_xfer(adapter, addr, 0, 0, 0,
I2C_SMBUS_QUICK, NULL);
}
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, kind, &info);//调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片
...
client = i2c_new_device(adapter, &info);//创建设备
...
}
i2c_smbus_xfer发送了数据
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
char read_write, u8 command, int protocol,
union i2c_smbus_data *data)
{
...
if (adapter->algo->smbus_xfer) { //如果适配器中指定了,则调用适配器中的
...
} else
res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,
command, protocol, data);
return res;
}
这里的adapter->algo适配器,在s3c24xx_i2c_probe中设置i2c->adap.algo = &s3c24xx_i2c_algorithm。
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
没有smbus_xfer,所以调用i2c_smbus_xfer_emulated
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)
{
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
int num = read_write == I2C_SMBUS_READ?2:1;
struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
{ addr, flags | I2C_M_RD, 0, msgbuf1 }
};
int i;
u8 partial_pec = 0;
int status;
msgbuf0[0] = command;
switch(size) {
...
case I2C_SMBUS_BYTE_DATA:
if (read_write == I2C_SMBUS_READ)
msg[1].len = 1;
else {
msg[0].len = 2;
msgbuf0[1] = data->byte;
}
break;
...
}
...
status = i2c_transfer(adapter, msg, num);///将i2c_msg结构体的内容发送给I2C设备
...
}
构造了i2c_msg结构体,通过i2c_transfer发送
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
...
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
...
}
调用了adapter中algorithm的master_xfer,即s3c24xx_i2c_xfer,s3c24xx_i2c_xfer–>s3c24xx_i2c_doxfer–>s3c24xx_i2c_message_start
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;
} else
stat |= S3C2410_IICSTAT_MASTER_TX;
if (msg->flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
s3c24xx_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);
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);
}
s3c24xx_i2c_message_start进行了硬件时序的操作。
3.2 设备驱动
3.2.1 设备
在/Documentation/i2c/instantiating-devices中,介绍了实例化I2C设备的4种方法,最终都会调用i2c_new_device。
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/* Check for address business */
status = i2c_check_addr(adap, client->addr);
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr);
status = device_register(&client->dev);
...
}
传入的参数i2c_board_info描述I2C设备,在include/linux/i2c.h中
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
int irq;
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr) //type--类型名,addr--设备地址
调用device_register注册设备,与之对应的注册设备会调用i2c_add_driver。
3.2.2 驱动
i2c_add_driver–>i2c_register_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;//总线
res = driver_register(&driver->driver);//注册驱动
if (res)
return res;
INIT_LIST_HEAD(&driver->clients);
mutex_lock(&core_lock);
bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter);//遍历i2c_bus_type,调用__attach_adapter
mutex_unlock(&core_lock);
return 0;
}
__attach_adapter与i2c_do_add_adapter类似,都会调用i2c_detect。
3.3 实例化IIC设备
3.3.1 方法一
在i2c-s3c2410.c中,注册适配器时,调用i2c_add_numbered_adapter–>i2c_register_adapter
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
...
}
__i2c_first_dynamic_bus_num的定义在driver/i2c/i2c_boardinfo.c中,初始化为0。
如果执行了i2c_scan_static_board_info
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) { //取出__i2c_board_list中每一项
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
取出__i2c_board_list每一项,如果devinfo->busnum == adapter->nr,调用i2c_new_device实例化。
__i2c_board_list在哪里添加了:i2c_register_board_info
int __init i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;//
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);//添加到链表中
}
up_write(&__i2c_board_lock);
return status;
}
以mach-mini2440.c中为例
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50),
.platform_data = &at24c08,
},
};
static void __init mini2440_init(void)
{
...
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
...
}
从上述流程可以看出:这种方法必须在 i2c_register_adapter之前调用i2c_register_board_info注册,所以不适合动态加载insmod。
3.3.2 方法二
与方法一类似,也构建i2c_board_info结构体,然后直接调用i2c_new_device或i2c_new_probed_device。
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
对于该函数第一个参数需要的i2c_adapter结构体,可以通过i2c_get_adapter()函数(传入参数0表示想要获得适配器0)得到。
i2c_new_device : 认为设备肯定存在
i2c_new_probed_device:对于"已经识别出来的设备"(probed_device),才会创建(“new”)
3.3.3 方法三
从用户空间直接实例化I2C设备。例如,在用户空间执行
echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device
内核会自动创建一个使用适配器0的设备名为“at24c08”,设备地址为0x50的I2C设备,相当于方法二中构建i2c_board_info结构体,然后直接调用i2c_new_device()函数。
在用户空间执行
echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device
内核会调用i2c_unregister_device()函数删除这个设备地址为0x50的I2C设备。
3.3.4 方法四
前面的3种方法都要事先确定适配器(I2C总线,I2C控制器)
如果事先并不知道这个I2C设备在哪个适配器上,怎么办:去class表示的所有的适配器上查找
如果适配器上一些I2C设备的地址是一样,怎么继续分辨它是哪一款:用detect函数
参考drivers/hwmon/lm90.c。
4 AT24C08
MINI2440开发板上使用的芯片AT24C08,8Kbit的EEPROM。
读写数据时,要发送设备地址->读写地址->数据。
24c08总共有1KB字节存储空间,这个空间分为4个块,每个块有16页,每页16字节(每块共256字节)。故一个存储单元的地址由器件地址(p0p1)+8位地址决定。
5 程序
用第一种方法写at24c08测试程序。
读写的操作方式采用Byte Write方式读,和Random Read方式写。
其中Random Read方式需要对写入的地址进行确认。也就是读操作的时候,需要写一次,再读一次。
(1)修改内核
修改arch/arm/mach-s3c24xx/mach-smdk2440.c。
添加头文件
#include <linux/i2c.h>
#include <linux/i2c/at24.h>
添加设备信息
static struct at24_platform_data at24_platdata = {
.byte_len = 8192,//字节大小
.page_size = 16,//页数大小
};
static struct i2c_board_info mini2440_i2c_devices[] = {
{
I2C_BOARD_INFO("24c08",0x50),//第一个参数是硬件名称,驱动的名字匹配不上的时候,会和这个匹配,第二个参数是IIC硬件地址。
.platform_data = &at24_platdata,
}
};
在smdk2440_machine_init中添加
i2c_register_board_info(0,mini2440_i2c_devices,ARRAY_SIZE(mini2440_i2c_devices));
(1)驱动
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
static struct i2c_driver at24c08_driver;
static struct i2c_client *at24c08_client;
int major; //主设备号
static struct cdev at24c08_cdev;
static struct class *at24c08_class;
static int i2c_read_byte(char *buf,int count)
{
int ret=0;
struct i2c_msg msg;
msg.addr = at24c08_client->addr;//0x05
msg.flags = 1;//1 代表读 0 代表写
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(at24c08_client->adapter,&msg, 1);
if (ret < 0) {
printk("i2c transfer failed!\n");
return -EINVAL;
}
return ret;
}
static int i2c_write_byte(char *buf,int count)
{
int ret=0;
struct i2c_msg msg;
msg.addr = at24c08_client->addr;//0x05
msg.flags = 0; //写
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(at24c08_client->adapter,&msg,1);
if (ret < 0) {
printk("i2c transfer failed!\n");
return -EINVAL;
}
return ret;
}
static size_t at24c08_read(struct file *filep, char __user *buf, size_t size,
loff_t *ppos)
{
int ret = 0;
char *tmp;
tmp = kmalloc(size,GFP_KERNEL);
if(tmp==NULL){
printk("malloc failed!\n");
return -ENOMEM;
}
ret = i2c_read_byte(tmp,size);
if(ret<0){
printk("read byte failed!\n");
ret = -EINVAL;
goto err0;
}
ret = copy_to_user(buf,tmp,size);
if(ret){
printk("copy data failed!\n");
ret =-EINVAL;
goto err0;
}
kfree(tmp);
return size;
err0:
kfree(tmp);
return ret;
}
static ssize_t at24c08_write(struct file *filep, const char __user *buf, size_t size,
loff_t *ppos)
{
int ret = 0;
char *tmp;
tmp = kmalloc(size,GFP_KERNEL);
if(tmp == NULL){
printk("malloc failed!\n");
return -ENOMEM;
goto err0;
}
ret = copy_from_user(tmp, buf, size);
if(ret){
printk("copy data failed!\n");
ret =-EFAULT;
goto err0;
}
ret = i2c_write_byte(tmp, size);
if(ret<0){
printk("write byte failed!\n");
ret = -EINVAL;
goto err0;
}
kfree(tmp);
return size;
err0:
kfree(tmp);
return ret;
}
struct file_operations at24c08_fops = {
.owner =THIS_MODULE,
.read =at24c08_read,
.write =at24c08_write,
};
static int at24c08_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int err;
dev_t devid;
at24c08_client = client;
printk("at24c08 probe !\n");
/* 创建字符设备 */
devid = MKDEV(major, 0); //从主设备号major,次设备号0得到dev_t类型
if (major)
{
err=register_chrdev_region(devid, 1, "at24c08"); //注册字符设备
}
else
{
err=alloc_chrdev_region(&devid, 0, 1, "at24c08"); //注册字符设备
major = MAJOR(devid); //从dev_t类型得到主设备
}
if(err < 0)
return err;
cdev_init(&at24c08_cdev, &at24c08_fops);
cdev_add(&at24c08_cdev, devid, 1);
at24c08_class = class_create(THIS_MODULE, "at24c08");
device_create(at24c08_class, NULL, MKDEV(major, 0), NULL, "at24c08"); /* /dev/at24c08 */
return err;
}
static int at24c08_remove(struct i2c_client *client)
{
printk("at24c08_remove !\n");
device_destroy(at24c08_class, MKDEV(major, 0));
class_destroy(at24c08_class);
cdev_del(&at24c08_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
return 0;
}
struct i2c_device_id at24c08_table[]={
{"24c08",0x50},
{}
};
/*构建一个struct i2c_driver结构体*/
static struct i2c_driver at24c08_driver={
.probe = at24c08_probe,
.remove = at24c08_remove,
.id_table = at24c08_table,//记录此驱动服务于哪些设备
.driver = {
.name = "24c08",//
.owner = THIS_MODULE,
},
};
static int __init at24c08_init(void)
{
i2c_add_driver(&at24c08_driver);
return 0;
}
static void __exit at24c08_exit(void)
{
i2c_del_driver(&at24c08_driver);
}
module_init(at24c08_init);
module_exit(at24c08_exit);
MODULE_LICENSE("GPL");
()测试
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
./i2c_test w data
./i2c_test r
*/
int main(int argc,char **argv)
{
int fd;
char register_addr = 0x78;//要写入的地址
char wbuf[2];//写缓冲区
char rbuf[2];//读缓冲区
int size;
//打开设备
fd = open("/dev/at24c08", O_RDWR);
if (fd < 0)
{
perror("open error\n");
exit(1);
}
if(strcmp(argv[1],"w") == 0)
{//写操作
wbuf[0] = register_addr;
wbuf[1] = atoi(argv[2]);
/*向register_addr地址中写入数据,因为设备地址已经在板级信息中确定了,所以不需要通过ioctl设置设备地址*/
if( size= write(fd, wbuf, 2) != 2)
{
perror("write error\n");
exit(1);
}
}
else
{
//读操作 Random Read
if(write(fd, ®ister_addr, 1) != 1) //验证是否从register_addr地址读出
{
perror("write error\n");
exit(1);
}
if(read(fd, &rbuf, 1) != 1)
{
perror("read error\n");
exit(1);
}
else
{
printf("rbuf[0] = %d\n",rbuf[0]);
}
}
return 0;
}