I2C驅動程序

I2C驅動程序框架

       I2C的協議流程在I2C協議原理簡述一文中寫過,這裏就不再講解。爲了更好的書寫I2C客戶驅動程序,我們先理一理I2C驅動程序的框架,才能更好的寫出屬於我們自己的驅動程序。

       上圖中的I2C總線驅動程序(也就是I2C適配器驅動程序)是芯片商爲我們實現的,知道如何收發數據;而需要我們自己實現的I2C客戶驅動程序,則知道數據的具體含義。

        那麼我們書寫的客戶驅動程序什麼時候會被調用呢?這個問題要先從分析I2C適配器驅動程序開始講起。下面給出一個分析流程:

結合linux-2.6.22.6\drivers\i2c\busses\i2c-s3c2410.c,分析一下由芯片商實現的adapter的驅動程序
i2c_adap_s3c_init
	ret = platform_driver_register(&s3c2440_i2c_driver);
		s3c24xx_i2c_probe
			clk_enable(i2c->clk); //使能I2C
			ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED, pdev->name, i2c); //註冊中斷
			ret = i2c_add_adapter(&i2c->adap); //添加adapter結構體
				i2c_register_adapter(adapter);
					/* let legacy drivers scan this bus for matching devices */
					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); //這個driver就是我們I2C客戶驅動程序註冊時,掛載在drivers鏈表上的
					}

我們可以看到,最後會調用I2C客戶驅動程序的driver->attach_adapter函數,而這個driver就是在初始化是就註冊進系統的。這樣我們就知道了什麼時候會調用我們書寫的I2C驅動程序了,那下面講講怎麼寫我們自己的I2C客戶驅動程序。

I2C客戶驅動程序示例

先給出一個已經寫好的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>


static unsigned short ignore[] = { I2C_CLIENT_END }; /*顧名思義就是忽略的意思*/
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /*地址值爲7位:我們的I2C設備地址爲0x50*/

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	= ignore, /*要發出start信號和設備地址,並且收到ACK信號後,才能確定存在這個設備*/
	.probe		= ignore,
	.ignore		= ignore,
	.forces     = forces, /*強制認爲存在設備,不用通過發送start信號和設備地址來確認*/
};

static struct i2c_driver at24cxx_driver;
struct i2c_client *at24cxx_client;
static struct class *at24cxx_class;
static struct class_device *at24cxx_class_dev;
static int major = 0;

static ssize_t at24cxx_read (struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	int ret = 0;
	unsigned char address;
	unsigned char data;
	struct i2c_msg msg[2];

	if (size != 1)
		return -EINVAL;

	copy_from_user(&address, buf, 1);

	/*數據傳輸3要素:源,目的,長度*/

	/*在讀取AT24CXX的數據前,需要將存儲數據的空間的地址告訴它*/
	msg[0].addr  = at24cxx_client->addr;  /*目的*/
	msg[0].buf	 = &address;			  /*源*/
	msg[0].len	 = 1;					  /*地址 = 1bytes*/
	msg[0].flags = 0;					  /*寫*/

	/*然後啓動讀操作*/
	msg[1].addr  = at24cxx_client->addr;  /*源*/
	msg[1].buf	 = &data;			   /*目的*/
	msg[1].len	 = 1;					  /*地址 = 1bytes*/
	msg[1].flags = I2C_M_RD;			  /*讀*/
	
	ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
	if (2 == ret) {
		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 *ppos)
{
	int ret = 0;
	unsigned char val[2];
	struct i2c_msg msg[1];

	/* address = buf[0]
	 * data    = buf[1]
	 */

	copy_from_user(val, buf, 2);

	/*數據傳輸3要素:源,目的,長度*/
	msg[0].addr  = at24cxx_client->addr;  /*目的*/
	msg[0].buf   = val;                   /*源*/
	msg[0].len   = 2;                     /*地址+數據 = 2bytes*/
	msg[0].flags = 0;                     /*寫*/
	
	ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
	if (1 == ret)
		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)
{
	int err = 0;

	if (!(at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}

	at24cxx_client->addr    = address;
	at24cxx_client->adapter = adapter;
	at24cxx_client->driver  = &at24cxx_driver;

	major = register_chrdev(0, "at24cxx_drv", &at24cxx_fops);
	at24cxx_class = class_create(THIS_MODULE, "at24cxx_drv");
	at24cxx_class_dev = class_device_create(at24cxx_class, NULL, MKDEV(major, 0), NULL, "at24cxx_dev");
	
	/* Fill in the remaining client fields */
	strlcpy(at24cxx_client->name, "at24cxx", I2C_NAME_SIZE);

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(at24cxx_client)))
		goto exit_kfree;
	
	printk("at24cxx_detect\n");
	return 0;

exit_kfree:
	kfree(at24cxx_client);

exit:
	return err;

}


static int at24cxx_attach_adapter(struct i2c_adapter *adapter)
{
	printk("in %s\n", __FUNCTION__);
	return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detach_client(struct i2c_client *client)
{
	int err;
	printk("in %s\n", __FUNCTION__);
	err = i2c_detach_client(client);
	if (err)
		return err;

	kfree(i2c_get_clientdata(client));
	class_device_unregister(at24cxx_class_dev);
	class_destroy(at24cxx_class);
	unregister_chrdev(major, "at24cxx_drv");
	return 0;
}


/*1. 分配一個i2c_driver結構體*/
static struct i2c_driver at24cxx_driver = {
	.driver = {
		.name	= "at24cxx",
	},
	.attach_adapter	= at24cxx_attach_adapter,
	.detach_client	= at24cxx_detach_client,
};

static int at24cxx_init(void)
{
	return i2c_add_driver(&at24cxx_driver);
}

static void at24cxx_exit(void)
{
	i2c_del_driver(&at24cxx_driver);	
}


module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");

由上面的代碼,可以得出一個編寫I2C驅動程序的步驟:

(1)分配一個i2c_driver結構體

/*1. 分配一個i2c_driver結構體*/
static struct i2c_driver at24cxx_driver = {
	.driver = {
		.name	= "at24cxx",
	},
	.attach_adapter	= at24cxx_attach_adapter,
	.detach_client	= at24cxx_detach_client,
};

(2)有了i2c_driver結構體後,在入口函數將它註冊進系統

static int at24cxx_init(void)
{
	return i2c_add_driver(&at24cxx_driver);
}

(3)在分析I2C驅動程序的框架時,我們知道適配器驅動程序會調用我們寫的attach_adapter,也就是at24cxx_attach_adapter函數。這個函數裏面接着調用了i2c_probe函數。

(4)我們可以看到i2c_probe的第二個參數addr_data結構體其實就是從設備的地址,一般normal_i2c這一項是填入從設備真正的地址,也就是.normal_i2c    = normal_addr。但是我們這裏強制設置.forces     = forces,也就是不管設備是否存在,都認爲其存在了。

static unsigned short ignore[] = { I2C_CLIENT_END }; /*顧名思義就是忽略的意思*/
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /*地址值爲7位:我們的I2C設備地址爲0x50*/

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	= ignore, /*要發出start信號和設備地址,並且收到ACK信號後,才能確定存在這個設備*/
	.probe		= ignore,
	.ignore		= ignore,
	.forces     = forces, /*強制認爲存在設備,不用通過發送start信號和設備地址來確認*/
};

      如果設置了真正的從設備地址後,i2c_probe調用的過程,也是向從設備發起一個探測命令的過程,如果從設備迴應了一個ACK,那就判斷從設備存在。接着就調用i2c_probe的第三個參數指向的at24cxx_detect函數。

(5)

我們可以看到在at24cxx_detect函數裏面,設置了一下at24cxx_client這個結構體,包括一些從設備的地址,後面會用到

at24cxx_client->addr    = address;

以及創建一個字符設備節點,方便後面和用戶進程交互

        major = register_chrdev(0, "at24cxx_drv", &at24cxx_fops);
	at24cxx_class = class_create(THIS_MODULE, "at24cxx_drv");
	at24cxx_class_dev = class_device_create(at24cxx_class, NULL, MKDEV(major, 0), NULL, "at24cxx_dev");

(6)

        後面就是字符設備常見的操作了,用戶進程調用驅動程序的write和read函數來和I2C設備進行通信。不過這裏要說明一下驅動程序是怎麼發送數據的,以及數據的結構是怎麼樣的。

        比如write函數裏面就分配了一個i2c_msg結構體,然後填充裏面的內容,再利用i2c_transfer函數進行發送。

        struct i2c_msg msg[1];

	/* address = buf[0]
	 * data    = buf[1]
	 */

	copy_from_user(val, buf, 2);

	/*數據傳輸3要素:源,目的,長度*/
	msg[0].addr  = at24cxx_client->addr;  /*目的*/
	msg[0].buf   = val;                   /*源*/
	msg[0].len   = 2;                     /*地址+數據 = 2bytes*/
	msg[0].flags = 0;                     /*寫*/
	
	ret = i2c_transfer(at24cxx_client->adapter, msg, 1);

這裏說明下,爲什麼傳輸的內容是:地址+數據,這個是at24cxx這個從設備規定的:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章