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這個從設備規定的: