linux I2C 裝置驅動學習筆記
I2C是philips提出的外設匯流排.使用SCL時鐘線,SDA序列資料線這兩根訊號線就實現了裝置之間的資料互動,被非常廣泛地應用在CPU與EEPROM,實時鐘,小型LCD等裝置通訊中。
二:在linux下的驅動思路
linux系統下編寫I2C驅動,有兩種方法:
一是把I2C裝置當作一個普通的字元裝置來處理,用i2c-dev.c檔案提供的API,封裝裝置時序資料,直接操作i2c介面卡驅動對應的裝置檔案,實現與裝置的通訊。屬於使用者態驅動。
二是利用linux下I2C子系統框架體系來實現。屬於核心態度驅動,使用者空間的應用程式直接操作從器件對應的裝置檔案,既可用通用的API實現與從器件的資料互動。
以下分別說明兩種方式。
三:使用者態實現裝置驅動方式。
1. i2c_dev
對於註冊的i2c介面卡,使用者空間也可以使用它們。在Linux核心程式碼檔案/include/linux/i2c-dev.c中實現了I2C介面卡裝置檔案的功能,針對每個介面卡生成一個主裝置號為89的裝置節點(次裝置號為0-255),I2c-dev.c並沒有針對特定的裝置而設計,只是提供了通用的read(),write(),和ioctl()等檔案操作介面,在使用者空間的應用層就可以借用這些介面訪問掛接在介面卡上的I2C裝置的儲存空間或暫存器,並控制I2C裝置的工作方式。
i2c介面卡的裝置節點是/dev/i2c-x,其中x是數字,代表介面卡的編號。由於介面卡編號是動態分配的(和註冊次序有關),所以想了解哪一個介面卡對應什麼編號,可以檢視/sys/class/i2c-dev/目錄下的檔案內容。如:[ [email protected] /]# cat /sys/class/i2c-dev/i2c-0/name
PNX4008-I2C0
[[email protected] /]# cat /sys/class/i2c-dev/i2c-1/name
PNX4008-I2C1
[[email protected] /]# cat /sys/class/i2c-dev/i2c-2/name
USB-I2C
如果我們想開啟第2個介面卡,剛好它的編號是1,對應的裝置節點是/dev/i2c-1。那麼可以用下面的方法開啟它:
說明:int fd; if ((fd = open("/dev/i2c-1",O_RDWR))< 0) { /* 錯誤處理 */ exit(1); }
開啟介面卡對應的裝置節點,i2c-dev為開啟的執行緒建立一個i2c_client,但是這個i2c_client並不加到i2c_adapter的client連結串列當中。當用戶關閉裝置節點時,它自動被釋放。
2.ioctl()
檢視include/linux/i2c-dev.h檔案,可以看到i2c-dev支援的IOCTL命令。如i2c-dev IOCTL命令
#define I2C_RETRIES 0x0701 /*設定收不到ACK時的重試次數*/
#define I2C_TIMEOUT 0x0702 /* 設定超時時限的jiffies */
#define I2C_SLAVE 0x0703 /*設定從機地址 */
#define I2C_SLAVE_FORCE 0x0706 /* 強制設定從機地址 */
#define I2C_TENBIT 0x0704 /*選擇地址位長:=0 for 7bit , != 0 for 10 bit */
#define I2C_FUNCS 0x0705 /*獲取介面卡支援的功能 */
#define I2C_RDWR 0x0707 /*Combined R/W transfer (one STOP only) */
#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */
#define I2C_SMBUS 0x0720 /*SMBus transfer */
如:
a. 設定重試次數3.與從器件的通訊資料幀。
ioctl(fd, I2C_RETRIES,m);
這句話設定介面卡收不到ACK時重試的次數為m。預設的重試次數為1。
b. 設定超時
ioctl(fd, I2C_TIMEOUT,m);
設定SMBus的超時時間為m,單位為jiffies。
c. 設定從機地址
ioctl(fd, I2C_SLAVE,addr);
ioctl(fd, #defineI2C_SLAVE_FORCE, addr);
在呼叫read()和write()函式之前必須設定從機地址。這兩行都可以設定從機的地址,區別是第二行無論核心中是否已有驅動在使用這個地址都會成功,第一行則只在該地址空閒的情況下成功。由於i2c-dev建立的i2c_client不加入i2c_adapter的client列表,所以不能防止其它執行緒使用同一地址,也不能防止驅動模組佔用同一地址。
d. 設定地址模式
ioctl(file,I2C_TENBIT,select)
如果select不等於0選擇10位元地址模式,如果等於0選擇7位元模式,預設7位元。只有介面卡支援I2C_FUNC_10BIT_ADDR,這個請求才是有效的。e. 獲取介面卡功能ioctl(file,I2C_FUNCS,(unsignedlong *)funcs)
i2c傳送或者接收一次資料都以資料包 struct i2c_msg 封裝
struct i2c_msg {
__u16 addr; // 從機地址
__u16 flags; // 標誌
#define I2C_M_TEN 0x0010 // 十位地址標誌
#define I2C_M_RD 0x0001 // 接收資料標誌
__u16 len; // 資料長度
__u8 *buf; // 資料指標
};
其中addr為從機地址;flags則是這次通訊的標誌,傳送資料為0,接收資料則為 I2C_M_RD;len為此次通訊的資料位元組數;buf 為傳送或接收資料的指標。在裝置驅動中我們通常呼叫 i2c-core 定義的介面 i2c_master_send 和 i2c_master_recv 來發送或接收一次資料。
---------注意 i2c_master_send 和 i2c_master_recv 都是對 i2c_transfer的封裝。
------具體呼叫時的差異.------
int i2c_master_send(struct i2c_client *client,const char *buf ,int count){
int ret;
struct i2c_adapter *adap=client->adapter; // 獲取adapter資訊
struct i2c_msg msg; // 定義一個臨時的資料包
msg.addr = client->addr; // 將從機地址寫入資料包
msg.flags = client->flags & I2C_M_TEN; // 將從機標誌併入資料包
msg.len = count; // 將此次傳送的資料位元組數寫入資料包
msg.buf = (char *)buf; // 將傳送資料指標寫入資料包
ret = i2c_transfer(adap, &msg, 1); // 呼叫平臺介面傳送資料
/* If everything went ok (i.e. 1 msg transmitted), return #bytes
transmitted, else error code. */
return (ret == 1) ? count : ret; // 如果傳送成功就返回位元組數
}
EXPORT_SYMBOL(i2c_master_send);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
{
struct i2c_adapter *adap=client->adapter; // 獲取adapter資訊
struct i2c_msg msg; // 定義一個臨時的資料包
int ret;
msg.addr = client->addr; // 將從機地址寫入資料包
msg.flags = client->flags & I2C_M_TEN; // 將從機標誌併入資料包
msg.flags |= I2C_M_RD; // 將此次通訊的標誌併入資料包
msg.len = count; // 將此次接收的資料位元組數寫入資料包
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1); // 呼叫平臺介面接收資料
/* If everything went ok (i.e. 1 msg transmitted), return #bytes
transmitted, else error code. */
return (ret == 1) ? count : ret; // 如果接收成功就返回位元組數
}
EXPORT_SYMBOL(i2c_master_recv);
於是。讀一個暫存器的介面可以按照如下方式封裝:
static int read_reg(struct i2c_client *client, unsigned char reg, unsigned char *data)
{
int ret;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = ®, // 暫存器地址
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = 1,
.buf = data, // 暫存器的值
},
};
ret = i2c_transfer(client->adapter, msgs, 2); // 這裡 num = 2,通訊成功 ret = 2
if (ret < 0)
tp_err("%s error: %d\n", __func__, ret);
return ret;
}
等價於:
static unsigned char read_reg(struct i2c_client *client, unsigned char reg)
{
unsigned char buf;
i2c_master_send(client, ®, 1); // 傳送暫存器地址
i2c_master_recv(client, &buf, 1); // 接收暫存器的值
return buf;
}
------------------------------------
4. i2c-dev使用例程
#include <stdio.h>
#include <linux/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
int main()
{
intfd, ret;
unsignedchar rdwr_addr = 0x42; /* e2prom 讀寫地址 */
unsignedchar device_addr = 0x50; /* e2prom 裝置地址 */
unsignedchar data = 0x12; /* 向e2prom寫的資料 */
structi2c_rdwr_ioctl_data e2prom_data;
fd= open("/dev/i2c/0", O_RDWR);
if(fd < 0) {
perror("openerror");
exit(1);
}
e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \
sizeof(structi2c_msg));
if(e2prom_data.msgs == NULL) {
perror("mallocerror");
exit(1);
}
ioctl(fd,I2C_TIMEOUT, 1); /* 設定超時 */
ioctl(fd,I2C_RETRIES, 2); /* 設定重試次數 */
/*向e2prom的rdwr_addr地址寫入資料data*/
e2prom_data.nmsgs= 1;
e2prom_data.msgs[0].len= 2;
e2prom_data.msgs[0].addr= device_addr;
e2prom_data.msgs[0].flags= 0; /* write */
e2prom_data.msgs[0].buf= (unsigned char *)malloc(2);
e2prom_data.msgs[0].buf[0]= rdwr_addr; /* write address */
e2prom_data.msgs[0].buf[1]= data; /* write data */
ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret < 0) {
perror("writedata error");
exit(1);
}
printf("writedata: %d to address: %#x\n", data, rdwr_addr);
data= 0; /* be zero*/
/*從e2prom的rdwr_addr地址讀取資料存入buf*/
e2prom_data.nmsgs= 2;
e2prom_data.msgs[0].len= 1;
e2prom_data.msgs[0].addr= device_addr;
// e2prom_data.msgs[0].flags= 0; /* write */
e2prom_data.msgs[0].buf= &rdwr_addr;
e2prom_data.msgs[1].len= 1;
e2prom_data.msgs[1].addr= device_addr;
e2prom_data.msgs[1].flags= 1; /* read */
e2prom_data.msgs[1].buf= &data;
ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret < 0) {
perror("readerror");
exit(1);
}
printf("read data: %d from address: %#x\n", data,rdwr_addr);
free(e2prom_data.msgs);
close(fd);
return0;
}
四:核心態實現裝置驅動方式。
畫了一個流程圖框圖 大致概括了 Linux i2c 子系統驅動框架。通常只需要實現i2c裝置驅動層。i2c控制器驅動層大部分情況核心已經提供。
編寫具體的I2C驅動時,工程師需要處理的主要工作如下:
- 提供I2C介面卡的硬體驅動,探測,初始化I2C介面卡(如申請I2C的I/O地址和中斷號),驅動CPU控制的I2C介面卡從硬體上產生。
- 提供I2C控制的algorithm, 用具體介面卡的xxx_xfer()函式填充i2c_algorithm的master_xfer指標,並把i2c_algorithm指標賦給i2c_adapter的algo指標。
- 註冊I2C裝置板級資訊。該資訊會在i2c_adapter註冊時賦給i2c_client結構體。
- 實現I2C裝置驅動中的i2c_driver,在probe函式中完成i2c裝置所對應的具體驅動結構註冊。
- 在實現I2C裝置所對應型別的具體驅動操作API(注:i2c_driver只是實現裝置與匯流排的掛接)。
(1)板級資訊註冊,系統會根據該資訊生成對應的i2c_client結構。
static struct i2c_board_info i2c_devices[] __initdata = {
{I2C_BOARD_INFO("24c02", 0x50), },
{}
};
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
(2) 裝置驅動實現
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/smp_lock.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#define DEBUG 1
#ifdef DEBUG
#define dbg(x...) printk(x)
#else
#define dbg(x...) (void)(0)
#endif
#define I2C_MAJOR 89
#define DEVICE_NAME "at24c02"
static struct class *my_dev_class;
static struct i2c_client *my_client;
static struct i2c_driver my_i2c_driver;
static struct i2c_device_id my_ids[] = {
{"24c01",0x50},
{"24c02",0x50},
{"24c08",0x50},
{}
};
MODULE_DEVICE_TABLE(i2c,my_ids);
static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int res;
struct device *dev;
dbg("probe:name = %s,flag =%d,addr = %d,adapter = %d,driver = %s\n",client->name,
client->flags,client->addr,client->adapter->nr,client->driver->driver.name );
dev = device_create(my_dev_class, &client->dev,
MKDEV(I2C_MAJOR, 0), NULL,
DEVICE_NAME);
if (IS_ERR(dev))
{
dbg("device create error\n");
goto out;
}
my_client = client;
return 0;
out:
return -1;
}
static int my_i2c_remove(struct i2c_client *client)
{
dbg("remove\n");
return 0;
}
static ssize_t at24c02_read(struct file *fd, char *buf, ssize_t count, loff_t *offset)
{
char *tmp;
int ret;
char data_byte;
char reg_addr = 0,i;
struct i2c_client *client = (struct i2c_client*) fd->private_data;
struct i2c_msg msgs[2];
dbg("read:count = %d,offset = %ld\n",count,*offset);
tmp = kmalloc(count,GFP_KERNEL);
if (!tmp)
{
dbg("malloc error in read function\n");
goto out;
}
reg_addr = *offset;
msgs[0].addr = client->addr;
msgs[0].flags = client->flags & (I2C_M_TEN|I2C_CLIENT_PEC) ;
msgs[0].len = 1;
msgs[0].buf = (char *)®_addr;
msgs[1].addr= client->addr;
msgs[1].flags = client->flags & (I2C_M_TEN|I2C_CLIENT_PEC);
msgs[1].flags |= I2C_M_RD;
msgs[1].len = count;
msgs[1].buf = (char*)tmp;
ret = i2c_transfer(client->adapter,&msgs,2);
if (ret != 2)
goto out;
if (copy_to_user(buf, tmp, count))
goto out;
kfree(tmp);
return count;
out:
kfree(tmp);
return -1;
}
static int at24c02_ioctl(struct file *fd, unsigned int cmd, unsigned long arg)
{
dbg("ioctl code ...\n");
return 0;
}
static ssize_t at24c02_write(struct file *fd, char *buf, ssize_t count, loff_t *offset)
{
int ret,i;
char *tmp;
int errflg;
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client*) fd->private_data;
char tmp_data[2];
dbg("write:count = %d,offset = %ld\n",count,*offset);
tmp = kmalloc(count, GFP_KERNEL);
if (!tmp)
goto out;
if (copy_from_user(tmp, buf, count))
goto out;
msg.addr = client->addr;
msg.flags = client->flags & (I2C_M_TEN | I2C_CLIENT_PEC);
for (i = 0; i < count; i++) {
msg.len = 2;
tmp_data[0] = *offset + i;
tmp_data[1] = tmp[i];
msg.buf = tmp_data;
ret = i2c_transfer(client->adapter,&msg,1);
if (ret != 1)
goto out;
msleep(1);
}
kfree(tmp);
return ((ret == 1) ? count:ret);
out:
kfree(tmp);
return -1;
}
static int at24c02_open(struct inode *inode, struct file *fd)
{
fd->private_data =(void*)my_client;
return 0;
}
static int at24c02_release(struct inode *inode, struct file *fd)
{
dbg("release\n");
fd->private_data = NULL;
return 0;
}
static const struct file_operations i2c_fops = {
.owner = THIS_MODULE,
.open = at24c02_open,
.read = at24c02_read,
.write = at24c02_write,
.unlocked_ioctl = at24c02_ioctl,
.release = at24c02_release,
};
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "i2c_demo",
.owner = THIS_MODULE,
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_ids,
};
static int __init my_i2c_init(void)
{
int res;
res = register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);
if (res)
{
dbg("register_chrdev error\n");
return -1;
}
my_dev_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(my_dev_class))
{
dbg("create class error\n");
unregister_chrdev(I2C_MAJOR, DEVICE_NAME);
return -1;
}
return i2c_add_driver(&my_i2c_driver);
}
static void __exit my_i2c_exit(void)
{
unregister_chrdev(I2C_MAJOR, DEVICE_NAME);
class_destroy(my_dev_class);
i2c_del_driver(&my_i2c_driver);
}
MODULE_AUTHOR("itspy<[email protected]>");
MODULE_DESCRIPTION("i2c client driver demo");
MODULE_LICENSE("GPL");
module_init(my_i2c_init);
module_exit(my_i2c_exit);
參考部落格:
http://blog.csdn.net/bingqingsuimeng/article/details/7937898
http://blog.csdn.net/onetwothreef/article/details/50275237
http://blog.chinaunix.net/uid-27041925-id-3671725.html 等等。