I2C驅動程序設計(3)—I2C用戶態驅動程序設計

之前已經說過,有2種i2c驅動程序的設計,比如說針對EEPROM的驅動程序。我們可以專門編寫一個針對EEPROM的驅動程序。另一種方式就是通過i2c-dev,即通過i2c通用通用驅動,來編寫一個應用程序,來完成對設備的控制。我們現在就來實現i2c用戶態驅動程序的設計。

1.通用設備驅動分析

  • 首先需要分析i2c-dev,先打開i2c-dev.c這個文件,找到i2c_dev_init函數。
  • register_chrdev用於創建註冊一個字符設備,class_create用於生產一個字符類的設備文件,i2c_add_driver這是用來向Linux系統註冊一個i2c設備驅動。
/* ------------------------------------------------------------------------- */
 
/*
 * module load/unload record keeping
 */
 
static int __init i2c_dev_init(void)
{
	int res;
 
	printk(KERN_INFO "i2c /dev entries driver\n");
 
	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;
 
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
 
	res = i2c_add_driver(&i2cdev_driver);
	if (res)
		goto out_unreg_class;
 
	return 0;
 
out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}
  • 接下來分析操作函數
static const struct file_operations i2cdev_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .read        = i2cdev_read,
    .write        = i2cdev_write,
    .unlocked_ioctl    = i2cdev_ioctl,
    .open        = i2cdev_open,
    .release    = i2cdev_release,
};
  • 這裏包含很多操作,我們重點分析i2cdev_ioctl,因爲在用戶態中,主要通過這個函數來實現對設備的操作。
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = (struct i2c_client *)file->private_data;
	unsigned long funcs;
 
	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);
 
	switch ( cmd ) {
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:
		/* NOTE:  devices set up to work with "new style" drivers
		 * can't use I2C_SLAVE, even when the device node is not
		 * bound to a driver.  Only I2C_SLAVE_FORCE will work.
		 *
		 * Setting the PEC flag here won't affect kernel drivers,
		 * which will be using the i2c_client node registered with
		 * the driver model core.  Likewise, when that client has
		 * the PEC flag already set, the i2c-dev driver won't see
		 * (or use) this setting.
		 */
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
		client->addr = arg;
		return 0;
	case I2C_TENBIT:
		if (arg)
			client->flags |= I2C_M_TEN;
		else
			client->flags &= ~I2C_M_TEN;
		return 0;
	case I2C_PEC:
		if (arg)
			client->flags |= I2C_CLIENT_PEC;
		else
			client->flags &= ~I2C_CLIENT_PEC;
		return 0;
	case I2C_FUNCS:
		funcs = i2c_get_functionality(client->adapter);
		return put_user(funcs, (unsigned long __user *)arg);
 
	case I2C_RDWR:
		return i2cdev_ioctl_rdrw(client, arg);
 
	case I2C_SMBUS:
		return i2cdev_ioctl_smbus(client, arg);
 
	case I2C_RETRIES:
		client->adapter->retries = arg;
		break;
	case I2C_TIMEOUT:
		/* For historical reasons, user-space sets the timeout
		 * value in units of 10 ms.
		 */
		client->adapter->timeout = msecs_to_jiffies(arg * 10);
		break;
	default:
		/* NOTE:  returning a fault code here could cause trouble
		 * in buggy userspace code.  Some old kernel bugs returned
		 * zero in this case, and userspace code might accidentally
		 * have depended on that bug.
		 */
		return -ENOTTY;
	}
	return 0;
}
  • 裏面實現了很多操作,我們主要關心的又是I2C_RDWR這個操作,即讀和寫。我們看看這個函數i2cdev_ioctl_rdrw.
static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,
		unsigned long arg)
{
	struct i2c_rdwr_ioctl_data rdwr_arg;
	struct i2c_msg *rdwr_pa;
	u8 __user **data_ptrs;
	int i, res;
 
	if (copy_from_user(&rdwr_arg,
			   (struct i2c_rdwr_ioctl_data __user *)arg,
			   sizeof(rdwr_arg)))
		return -EFAULT;
 
	/* Put an arbitrary limit on the number of messages that can
	 * be sent at once */
	if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
		return -EINVAL;
 
	rdwr_pa = (struct i2c_msg *)
		kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),
		GFP_KERNEL);
	if (!rdwr_pa)
		return -ENOMEM;
 
	if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
			   rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
		kfree(rdwr_pa);
		return -EFAULT;
	}
 
	data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
	if (data_ptrs == NULL) {
		kfree(rdwr_pa);
		return -ENOMEM;
	}
 
	res = 0;
	for (i = 0; i < rdwr_arg.nmsgs; i++) {
		/* Limit the size of the message to a sane amount;
		 * and don't let length change either. */
		if ((rdwr_pa[i].len > 8192) ||
		    (rdwr_pa[i].flags & I2C_M_RECV_LEN)) {
			res = -EINVAL;
			break;
		}
		data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
		rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
		if (rdwr_pa[i].buf == NULL) {
			res = -ENOMEM;
			break;
		}
		if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i],
				   rdwr_pa[i].len)) {
				++i; /* Needs to be kfreed too */
				res = -EFAULT;
			break;
		}
	}
	if (res < 0) {
		int j;
		for (j = 0; j < i; ++j)
			kfree(rdwr_pa[j].buf);
		kfree(data_ptrs);
		kfree(rdwr_pa);
		return res;
	}
 
	res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
	while (i-- > 0) {
		if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
			if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,
					 rdwr_pa[i].len))
				res = -EFAULT;
		}
		kfree(rdwr_pa[i].buf);
	}
	kfree(data_ptrs);
	kfree(rdwr_pa);
	return res;
}
  • 先來分析這個函數的參數,參數有2個client和arg,client應該是需要操作的設備,arg則是需要讀寫的參數,這個參數首先被強制轉化爲struct i2c_rdwr_ioctl_data這種結構體類型
/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
	struct i2c_msg __user *msgs;	/* pointers to i2c_msgs */
	__u32 nmsgs;			/* number of i2c_msgs */
};
  • 這裏有2個成員,一個是消息指針,另一個是消息的數量,消息數量很好理解,我們看看消息指針的類型:
struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};
  • 裏面包含了設備的地址addr,flags(0爲寫,1爲讀)讀寫標誌,消息的字節數,消息的數據指針,一次讀操作和寫操作可以視爲一條消息。
  • 接着來分析這個函數,做一些判斷之後,接下來肯定就是讀取消息數據了,它通過一個大循環for (i = 0; i < rdwr_arg.nmsgs; i++) 來讀取參數裏面的數據,然後使用i2c_transfer來傳輸這些數據。這個函數是屬於i2c-croe裏面的一個函數,但是這個函數並不會直接讀寫,而是找到掛載i2c總線控制器,通過總線控制器上面的算法來真正實現數據的傳輸。這個數據傳輸的線路和上面一節的數據流程圖一摸一樣。

2.用戶態驅動設計

我們先分析一下程序大概的流程:

(1)打開通用的字符設備文件

  • 依然是使用open打開設備文件,在開發板的/dev/下面我們可以找到一個叫做i2c-0的設備文件,我們以讀寫的方式打開這個設備文件。
// 1.打開通用設備文件
fd = open("/dev/i2c-0", O_RDWR);

(2)構造需要寫入到EEPROM中的消息

  • 我們首先需要賦值消息的定義到我們的程序中。即i2c_msgi2c_rdwr_ioctl_data,可以把一些不需要的數據刪掉。
struct i2c_msg 
{
	unsigned short addr;	
	unsigned short flags;
	unsigned short len;		
	unsigned char *buf;		
};

struct i2c_rdwr_ioctl_data 
{
	struct i2c_msg *msgs; // 消息指針
	unsigned int nmsgs;	// 消息數量		
};
  • 然後定義一個消息結構,i2c_rdwr_ioctl_data eeprom_data,然後初始化這個結構(別忘了給指針分配空間)。特別要注意的是對應消息的數量讀和寫肯定是不一樣的,因爲對於寫只需要一個消息,而對於讀只需要2個消息,因爲先做了一次寫,然後在做了一次讀。因此我們按最大的長度2,來給i2c_msg分配空間。
  • 接下來可以初始化寫的消息,寫的信息有2個字節,所以len=2,第一個是偏移地址,第二個是需要寫入的數據。初始化後如下:
// 因爲對於寫只需要一個消息
// 對於讀只需要2個消息,因爲先做了一次寫,然後在做了一次讀
// 因此我們按最大的長度2,來給i2c_msg分配空間 
e2prom_data.msgs = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg)); 
	
// 2.構造寫數據到eeprom的消息
e2prom_data.nmsgs = 1;  // 寫只有一條消息
(e2prom_data.msgs[0]).len = 2;  // 偏移地址+數據
(e2prom_data.msgs[0]).addr = 0x50;  // eeprom設備地址
(e2prom_data.msgs[0]).flags = 0;  // 0爲寫,1爲讀
(e2prom_data.msgs[0]).buf = (unsigned char *)malloc(2);
(e2prom_data.msgs[0]).buf[0] = 0x10;  // 寫入到EEPROM的偏移地址
(e2prom_data.msgs[0]).buf[1] = 0x60;  // 寫入到偏移地址的數據
  •  補充:EEPROM 芯片的設備地址一共有 7 位,其中高 4 位固定爲:1010b,低 3 位則由 A0/A1/A2 信號線的電平決定,見圖 24-10,圖中的 R/W 是讀寫方向位,與地址無關。

 (3)使用ioctl寫入數據

  • ioctl的第一個參數是fd,第二個參數是操作類型,這裏是I2C_RDWR,我們需要拷貝I2C_RDWR到自己的程序中,第三個是參數就是eeprom_data了,我們在取地址之後需要進行類型轉換,因爲i2cdev_ioctl_rdrw的參數是unsigned long.
#define I2C_RDWR	0x0707	
// 3.使用ioctl寫入數據
ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);

(4)構造從EEPROM讀數據的消息

  • 讀消息的構造也類似,不過這裏需要2個消息,第一個實現寫,第二個實現讀:

// 4.構造從EEPROM讀數據的消息
eeprom_data.nmsgs = 2;  // 讀有二條消息
(eeprom_data.msgs[0]).addr = 0x50;// 先寫入需要開始讀取的偏移地址,然後開始讀
(eeprom_data.msgs[0]).flags = 0;
(eeprom_data.msgs[0]).len = 1;
(eeprom_data.msgs[0]).buf[0] = 0x10;

(eeprom_data.msgs[1]).addr = 0x50;
(eeprom_data.msgs[1]).flags = 1;
(eeprom_data.msgs[1]).len = 1;
(eeprom_data.msgs[1]).buf = (unsigned char *)malloc(2);
(eeprom_data.msgs[1]).buf[0] = 0;  // 先把讀取緩衝清0

(5)使用ioctl讀出消息

ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);

// 讀取到的消息會保存在以buf[0]爲起始地址的存儲空間中。

(6)關閉字符設備

  • close(fd)

完整代碼:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>

#define I2C_RDWR 0x0707	

struct i2c_msg 
{
	unsigned short addr;	
	unsigned short flags;
	unsigned short len;		
	unsigned char *buf;		
};

struct i2c_rdwr_ioctl_data 
{
	struct i2c_msg *msgs;	// 消息指針
	unsigned int nmsgs;	// 消息數量		
};

int main()
{
	int fd;	
	struct i2c_rdwr_ioctl_data e2prom_data;
	
	// 1.打開通用設備文件
	fd = open("/dev/i2c-0", O_RDWR);
	
	// 因爲對於寫只需要一個消息
	// 對於讀只需要2個消息,因爲先做了一次寫,然後在做了一次讀
	// 因此我們按最大的長度2,來給i2c_msg分配空間 
	e2prom_data.msgs = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg)); 
	
	// 2.構造寫數據到eeprom的消息
	e2prom_data.nmsgs = 1;  // 寫只有一條消息
	(e2prom_data.msgs[0]).len = 2;  // 偏移地址+數據
	(e2prom_data.msgs[0]).addr = 0x50;  // eeprom設備地址
	(e2prom_data.msgs[0]).flags = 0;  // //0爲寫,1爲讀
	(e2prom_data.msgs[0]).buf = (unsigned char *)malloc(2);
	(e2prom_data.msgs[0]).buf[0] = 0x10;  // 寫入到EEPROM的偏移地址
	(e2prom_data.msgs[0]).buf[1] = 0x60;  // 寫入到偏移地址的數據
	
	// 3.使用ioctl寫入數據
	ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
	
	// 4.構造從EEPROM讀數據的消息
	eeprom_data.nmsgs = 2;  // 讀有二條消息
	(eeprom_data.msgs[0]).addr = 0x50;// 先寫入需要開始讀取的偏移地址,然後開始讀
	(eeprom_data.msgs[0]).flags = 0;
	(eeprom_data.msgs[0]).len = 1;
	(eeprom_data.msgs[0]).buf[0] = 0x10;

	(eeprom_data.msgs[1]).addr = 0x50;
	(eeprom_data.msgs[1]).flags = 1;
	(eeprom_data.msgs[1]).len = 1;
	(eeprom_data.msgs[1]).buf = (unsigned char *)malloc(2);
	(eeprom_data.msgs[1]).buf[0] = 0;  // 先把讀取緩衝清0

	
	// 5.使用ioctl讀出數據	
	ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
	printf("buffer[0] = %x\n", (e2prom_data.msgs[1]).buf[0]);
	
	// 6.關閉設備
	close(fd);
}

3.編譯

  • arm-linux-gcc -static i2c-app-drv.c -o i2c-app-drv
  • 拷貝到開發板上,執行./i2c-app-drv: 

   

發佈了222 篇原創文章 · 獲贊 226 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章