Linux驱动之i2c

如果应聘Linux驱动的工作岗位,面试官一般就会问你i2c驱动程序框架,下面就以我个人的理解来谈谈i2c驱动程序框架。

Linux驱动中的 i2c驱动主要有 四个结构体,要弄清Linux下i2c驱动,必须理解一下四个结构体的关系,

struct i2c_adapter {
	struct module *owner;             //所属模块
	unsigned int id;                  //algorithm 类型,定义在i2c-id.h中,以 I2C_ALGO_开始
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; //主要是i2c通信函数
	void *algo_data;                  //私有数据,自己定义的数据一般指向这里

	/* data fields that are valid for all devices	*/
	u8 level; 			/* nesting level for lockdep */
	struct mutex bus_lock;

	int timeout;			/* in jiffies */
	int retries;                    //重复传输的次数,一般是1,
	struct device dev;		/* the adapter device */

	int nr;
	char name[48];
	struct completion dev_released;
};

</pre><pre class="cpp" name="code">struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);
};


struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	低7位*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct i2c_driver *driver;	/* and our access routines	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
};

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this if you can, it will probably
	 * be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *);
	int (*detach_adapter)(struct i2c_adapter *);

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
	const struct i2c_client_address_data *address_data;
	struct list_head clients;
};


理解以上四个结构体非常重要,i2c_adapter是适配器,即cpu里的i2c控制器,i2c_algorithm 是传输算法,即CPU中的I2C控制器怎样传输,i2c_client对应的是外设,比如e2prom,i2c_driver对应的则是外设驱动,就驱动client的方法,

这四个结构体里各指针指向关系如下图,


如下是AT24CXX的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};
/*如果将.normal_i2c注释掉,将.force打开,则强制认为这个设备存在*/				/						
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;
	}
	else
		return -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;
	else
		return -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");


i2c在Linux中是总线的形势来管理adapter和client的,如下


                                      

当调用i2c_add_adapter的时候,就会在i2c总线上挂载一个i2c_adapter,当注册一个client的时候就会在总线链表上挂一个i2c_client

每次注册一 i2c_driverr的时候就会遍历总线上的i2c_adapter链表,取每个adapter,用adapter中的传输函数来探测外设,如果有应答,则将该client中的adapter指针指向这个i2c_adapter结构体,


i2c_add_driver

    i2c_register_drvier

        driver->driver.owner = owner;
        driver->driver.bus = &i2c_bus_type;

        bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter); //遍历i2c上的adapter,取每个adapter,然后用该adapter中的传输函数来探测该adapter是否匹配该client

           __attach_adapter 

              adapter = to_i2c_adapter(dev);

              i2c_detect(adapter, driver);

                   temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); //构造 i2c_client

                   i2c_detect_address(temp_client, -1, driver);

                       i2c_smbus_xfer

                            adapter->algo->smbus_xfer //该传输函数对应i2c-s3c2410.c中的s3c24xx_i2c_xfer

我们编写i2c的时候,只需要定义i2c_driver就可以,当然前提是对应的处理器是已经定义了i2c_adapter并且已经注册了,比如s3c24xx处理器,在Linux里就已经有对应的adapter,所以我们只需要编写client端就可以,

注册i2c_adapter的时候也一样,会遍历i2c总线上的client,取每个client,然后探测该client是否能够利用该adapter中的传输函数来探测,


at24cxx的测试函数如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}

	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/at24cxx\n");
		return -1;
	}

	if (strcmp(argv[1], "r") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if (strcmp(argv[1], "w") == 0)
	{
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		write(fd, buf, 2);
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}



                  






  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值