Linux驅動--IIC驅動框架以及底層收發API函式核心分析
IIC驅動框架:
在Linux平臺外設中有不少的IIC外設,例如基於Linux系統的Android觸控式螢幕獲取觸控座標、內部陀螺儀等等外設都是在IIC總線上工作,如果需要新增自己的IIC外設那必不可少需要了解IIC框架的運用。
開發環境:
PC :VMworkstation 12.0 Ubuntu 12 32bit
開發版:Tiny 4412 (三星獵戶CPU4412,Cortex-A9)
Linux核心版本:Linux 3.5
PC核心閱讀器:SourceInsight
一、Linux下核心IIC體系檔案架構:
檔案路徑:/linux-3.5/drivers/i2c
i2c-core.c:核心功能API函式檔案。
busses 資料夾:這個檔案中包含了一些 I2C 匯流排的驅動。
algos 資料夾:實現了一些 I2C 匯流排介面卡的 algorithm。 此外,核心中的 i2c.h 這個標頭檔案對 i2c_driver、i2c_client、i2c_adapter 和i2c_algorithm 這 4 個數據結構進行了定義。
在i2c-core.c檔案裡面包括了所有的IIC的API函式,以下是常用的API:
1、註冊/登出函式:i2c_add_driver/i2c_del_driver
2、收發一體函式:i2c_transfer
3、smbus指定長度讀取/傳送函式:i2c_smbus_write/read_i2c_block_data
4、獲取i2c_adapter 記憶體地址:i2c_get_adapter
5、註冊/登出IIC裝置:i2c_new_probed_device/i2c_unregister_device
6、新增IIC介面卡:i2c_add_adapter
7、新增IIC裝置:i2c_add_driver
二、驅動框架結構:
主要分成三個層次:介面卡層、裝置層和驅動層;介面卡層使用者或獲取(i2c_add_adapter)匯流排號並且裡面提供最底層的IIC時序讀寫函式直接用於控制硬體IO收發;裝置層主要是通過獲取介面卡註冊成功的匯流排使用權(i2c_get_adapter),並且建立一個IIC裝置(i2c_new_probed_device
1、介面卡:int i2c_add_adapter(struct i2c_adapter *adapter);
根據函式的傳參,先定義i2c_adapter 結構,先了解下i2c_adapter 這個結構內容:
struct i2c_adapter
{ struct module *owner;/*所屬模組一般為: THIS_MODULE*/ unsigned int class; /* classes to allow probing for 一般為: I2C_CLASS_HWMON | I2C_CLASS_SPD */ const struct i2c_algorithm *algo;/*匯流排通訊方法結構體指標 ,實現底層通訊*/ void *algo_data;/* algorithm 資料 */
/* data fields that are valid for all devices */ struct rt_mutex bus_lock;
int timeout; /* in jiffies*/ int retries; /* 超時重試次數*/ struct device dev; /* the adapter device 介面卡裝置結構*/
int nr; /* 匯流排編號*/ char name[48];/*介面卡名稱*/ struct completion dev_released;
struct mutex userspace_clients_lock; struct list_head userspace_clients; };
以上加粗的是需要賦值的,裡面有一個struct i2c_algorithm結構,顯然還需要再定義這個結構體賦值於i2c_adapter結構體內部,這個結構:
struct i2c_algorithm
{ 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); u32 (*functionality) (struct i2c_adapter *); };
master_xfer:這個介面但是底層收發的函式,num的值為1表示寫,為2表示讀,原因將在後面分析核心函式時說明。並且這個函式我們暫時還不能實現,因為傳參msg還沒有知道是什麼。將在後面介紹。
也就是說,首先得知道是傳送還是接收,其次要傳送完資料首先得知道傳送地址,傳送長度,傳送的緩衝區這三點或者是讀 取時的讀取地址,讀取長度,讀取緩衝區。
functionality:呼叫時返回I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART |I2C_FUNC_PROTOCOL_MANGLING
介面卡初始化程式碼:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/io.h>
/*
具體的硬體時序程式碼
*/
static u32 IIC_ADAPTER_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C |I2C_FUNC_SMBUS_EMUL|I2C_FUNC_NOSTART|I2C_FUNC_PROTOCOL_MANGLING;
}
static int IIC_ADAPTER_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
/*呼叫具體的傳送與接收函式,在後面分析*/
return 0;
}
static const struct i2c_algorithm IIC_ADAPTER_i2c_algorithm = {
.master_xfer = IIC_ADAPTER_i2c_xfer,
.functionality = IIC_ADAPTER_i2c_func,
};
static int __init i2c_adapter_init(void)
{
/*
GPIO硬體初始化
*/
adapter.owner = THIS_MODULE;
adapter.algo = &IIC_ADAPTER_i2c_algorithm;
adapter.retries = 2;
adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
strlcpy(adapter.name,"IIC_ADAPTER_i2c",sizeof(adapter.name));
i2c_add_adapter(&adapter);
printk("介面卡分配成功的匯流排編號: %d\n",adapter.nr);
return 0;
}
static void __exit i2c_adapter_exit(void)
{
iounmap(GPD1CON);
iounmap(GPD1DAT);
i2c_del_adapter(&adapter);
printk(" IIC介面卡登出成功!\n");
}
module_init(i2c_adapter_init);
module_exit(i2c_adapter_exit);
MODULE_LICENSE("GPL");
2、裝置層:
獲取匯流排使用權:struct i2c_adapter *i2c_get_adapter(int nr) /*要使用匯流排,匯流排編號是通過介面卡註冊並且打印出來的*/
建立一個IIC裝置:struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,/*返回獲取的匯流排介面卡結構*/ struct i2c_board_info *info,/* 板級資訊 */ unsigned short const *addr_list,/*外設地址*/ int (*probe)(struct i2c_adapter *, unsigned short addr));
故在呼叫之前還需要傳入兩個結構:struct i2c_board_info 與 unsigned short const *addr_list,其中struct i2c_board_info為一個結構體型別用於存放板級的資訊:
struct i2c_board_info { char type[I2C_NAME_SIZE];/* i2c裝置端名稱,匹配用,名稱指定20 */ unsigned short flags; unsigned short addr; /* i2c裝置地址*/ void *platform_data; /* 平臺裝置私有資料 */ struct dev_archdata *archdata; struct device_node *of_node; int irq; /* 中斷號 */ };
要與驅動端匹配,起碼一點至少要將type賦值,這是能否匹配的到的必要條件。i2c_new_probed_device函式到底幹了些什麼,下面分析:
前期主要是對地址的檢查與核實,將addr_list器件地址儲存到板級資訊結構體裡面,其中addr_list結構如下:
static unsigned short i2c_addr_list[0xFF]= { 0x50, /*實際的外設地址,低七位有效 AT24C02:廠商地址:1010 硬體連線:000 ---->01010000*/
I2C_CLIENT_END /*I2C_CLIENT_END為結束符,通過這個來判斷地址已經結束*/ };
最後還是呼叫最後一個函式:i2c_new_device這個函式又執行了什麼:
除去一些錯誤的保護機制,主要將i2c_adapter與i2c_board_info進行進一步的打包,將所有的資料都儲存在最終的結構體裡面,最終將資料匹配成功後傳送到驅動層。最後再呼叫i2c_put_adapter設定模組資訊記錄模組的使用計數。
裝置層程式碼:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
static struct i2c_client *i2cClient = NULL;
static struct i2c_adapter *i2c_adap;
static struct i2c_board_info i2c_info;
static unsigned short i2c_addr_list[0xFF]=
{
0x50,I2C_CLIENT_END
};
static int __init i2c_dev_init(void)
{
i2c_adap = i2c_get_adapter(9); /*要使用匯流排*/
if(i2c_adap==NULL)
{
printk("介面卡獲取失敗!\n");
return 0;
}
memset(&i2c_info,0,sizeof(struct i2c_board_info));//清空陣列資訊
strlcpy(i2c_info.type,"touch_name",I2C_NAME_SIZE);//陣列成員賦值
i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL);
if(i2cClient==NULL)
{
printk("地址檢查錯誤!\n");
return 0;
}
i2c_put_adapter(i2c_adap);
printk("i2c_dev_init!!\n");
return 0;
}
static void __exit i2c_dev_exit(void)
{
printk(" i2c_dev_exit ok!!\n");
if(i2c_adap!=NULL)
{
i2c_unregister_device(i2cClient);
i2c_release_client(i2cClient);
}
}
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
MODULE_LICENSE("GPL");
3、驅動層:
註冊一個IIC裝置:i2c_add_driver(struct i2c_driver *driver);
實際在核心裡面上面的函式只是一個巨集:#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver),實質是呼叫i2c_register_driver來註冊的。註冊之前要定義一個i2c_driver 結構體:
struct i2c_driver { unsigned int class; int (*attach_adapter)(struct i2c_adapter *) __deprecated; int (*detach_adapter)(struct i2c_adapter *) __deprecated; int (*probe)(struct i2c_client *, const struct i2c_device_id *);//匹配成功呼叫的函式 int (*remove)(struct i2c_client *);//移除陣列清理函式 void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); void (*alert)(struct i2c_client *, unsigned int data); int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; const struct i2c_device_id *id_table; int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients; };
struct device_driver driver:結構體主要對 .name和 .owner (固定THIS_MODULE),其中.name用於與裝置端匹配的i2c_board_info裡面的.type成員一致,如果i2c_device_id 定義了就以id_table為準。當匹配成功了,就可以呼叫核心的收發函式來發送資料。
驅動層程式碼:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/delay.h>
static const struct i2c_device_id i2c_id[] =
{
{"touch_name",0},//裝置端的名字為"myiic",0表示不需要私有資料
{}
};
static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功時呼叫
{
printk("驅動端IIC匹配的地址=0x%x\n",client->addr);
u8 buff_read[10];
u8 buff_write[10];
strcpy(buff_write,"houjunzui");
i2c_smbus_write_i2c_block_data(client,1,10,buff_write);//寫資料
mdelay(10);
i2c_smbus_read_i2c_block_data(client,1,10,buff_read);//讀資料
printk("read: %s \n",buff_read);
return 0;
}
static int i2c_remove(struct i2c_client *client)
{
printk("i2c_remove!!!\n");
return 0;
}
struct i2c_driver i2c_drv =
{
.driver = //這個不新增會報錯,實際上沒作用
{
.name = "iic_drv",
.owner = THIS_MODULE,
},
.probe = i2c_probe, //探測函式
.remove = i2c_remove, //資源解除安裝
.id_table = i2c_id, //裡面有一個名字的引數用來匹配裝置端名字
};
/*iic驅動端*/
static int __init i2c_drv_init(void)
{
i2c_add_driver(&i2c_drv);//向iic匯流排註冊一個驅動
return 0;
}
static void __exit i2c_drv_exit(void)//平臺裝置端的出口函式
{
i2c_del_driver(&i2c_drv);
}
module_init(i2c_drv_init);
module_exit(i2c_drv_exit);
MODULE_LICENSE("GPL");
三、傳送/接收函式分析:
先介紹傳送函式:i2c_smbus_read_i2c_block_data
傳入引數:第一個為來自probe函式的傳參,第二個為可以傳遞到底層的一個暫存器地址,比如使用AT24C02可以為讀取寫入資料暫存器的地址,第三個引數是讀取資料的長度,最後一個是讀取的資料存放的緩衝區。很顯然,這是個上層函式,對所有的功能已經完成了一定的封裝。用於通過IIC裝置從某一塊地址讀取某一長度的資料到緩衝區。上面的函式結構可以看出,主要只是對length變數進行了一次判斷,並沒有實際讀取功能,最終將所有傳入的引數傳參給函式:i2c_smbus_xfer,當這個函式返回後進行記憶體拷貝,將data.block裡面的資料拷貝到讀取緩衝區,可以看出,i2c_smbus_xfer函式將讀取的資料放在data.block裡面然後再拷貝到讀取緩衝區,並且可以看出有效資料是從data.block[1]開始,data.block[0]存放的是讀取的長度。之後便要分析i2c_smbus_xfer函式:
很顯然,我們在介面卡裡面並沒有實現smbus_xfer函式,if後面的命題為假,執行else後面的程式碼,又將所有的傳入引數傳遞到下一個函式:i2c_smbus_xfer_emulated,由於這個函式有點長,我們簡單起來分析。先了解傳入的引數傳遞關係:
以下是對i2c_smbus_xfer_emulated進行簡寫,將無用的程式碼刪掉後:
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];//定義一個寫緩衝區,用於IIC裝置讀取的緩衝區 unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];//定義一個讀緩衝區,用於IIC裝置讀取的緩衝區 int num = read_write == I2C_SMBUS_READ ? 2 : 1;//讀呼叫時為2,寫為1.此引數用於返回給介面卡的註冊時i2c_algorithm結構體的.master_xfer變數的第三個引數:static int IIC_ADAPTER_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num),用於區分操作是讀還是寫。由此可知IIC_ADAPTER_i2c_xfer裡面的簡單模板:
static int IIC_ADAPTER_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
if(num==1)
{
//底層寫函式,在介面卡層
}
else if(num==2)
{
//底層讀函式,在介面卡層
}
return 0;
}
現在知道了是讀操作,剩下讀取的長度與讀取到的緩衝區位置和讀取地址。 struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 }, { addr, flags | I2C_M_RD, 0, msgbuf1 } };//用於存放讀取傳送緩衝資訊,裡面的引數: addr:器件地址;flags:訊息額 外的標誌位;len:傳輸資料的位元組數;buf:接收/傳送緩衝區
msgbuf0[0] = command;/*暫存器地址,i2c_smbus_read_i2c_block_data函式的第2個引數,用於傳遞給硬體的讀取寫入地址*/ switch (size) //呼叫i2c_smbus_read_i2c_block_data或者i2c_smbus_write_i2c_block_data時,size都是等於I2C_SMBUS_BLOCK_DATA { case I2C_SMBUS_BLOCK_DATA: if (read_write == I2C_SMBUS_READ) /*讀時事件,對msg[1]操作*/ { msg[1].flags |= I2C_M_RECV_LEN; msg[1].len = 1; /* block length will be added by the underlying bus driver */ } else/*寫事件*/ {
/*寫的長度賦值,在前面的函式裡儲存長度的變數是: data->block[0],現在給msg[0].len,只有寫操作這個變數 有效。讀操作msg[1].len並非是讀取的長度而是1,見上面對 msg[1].len的複製。讀操作控制長度由data->block[0]決定,後面分析 */ msg[0].len = data->block[0] + 2; if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) { dev_err(&adapter->dev,"Invalid block write size %d\n",data->block[0]); return -EINVAL; } for (i = 1; i < msg[0].len; i++) msgbuf0[i] = data->block[i-1];
/*這裡為啥要將data->block[0]的資料存放在寫緩衝區,原因是i2c_smbus_write_i2c_block_data函式將要寫的資料儲存在 data->block[0],然後再將要寫入的資料存放在msgbuf0[1]開始,通過i2c_transfer函式傳送*/
} break; } i2c_transfer(adapter, msg, num);/*收發一體函式,num=2表示傳送,為1表示讀取訊息*/ if(read_write == I2C_SMBUS_READ)//用於提取msg裡面讀取的資訊,只有讀取訊息函式有效。
{ switch (size)
{ case I2C_SMBUS_I2C_BLOCK_DATA: for (i = 0; i < data->block[0]; i++) data->block[i+1] = msgbuf1[i]; /*讀完的資料存放在msgbuf1裡面,block[0]被佔用了從block[1]開始賦值, i2c_smbus_read_i2c_block_data呼叫i2c_smbus_xfer再呼叫i2c_smbus_xfer_emulated再間接呼叫i2c_transfer將資料讀取在msgbuf後在這裡將資料存放在block[1]開始的記憶體 後返回,通過memcpy將block[1]開始的資料複製到讀取資料緩衝區裡面 */ break; } }
總體概述:
i2c_smbus_read_i2c_block_data和i2c_smbus_write_i2c_block_data對長度判斷後將所有的資料轉發給了另一個函式:i2c_smbus_xfer,然後i2c_smbus_xfer又做了一箇中轉,將所有的引數傳遞給i2c_smbus_xfer_emulated,由上面的函式體可以知道,i2c_smbus_xfer_emulated函式主要負責對msg結構的封裝以及對傳送資料與接收資料的裝載,通過傳送函式與接收函式來改變read_write這個函式傳參的巨集來判斷到底是讀操作還是寫操作,當前是讀操作時num的值為1,data->block[0]存放的是讀取的長度,i2c_transfer函式將msg與num傳入,這個函式通過IIC讀取裝置資料,將讀取的資料儲存在msgbuf1[0]開始,通過將msgbuf1資料複製到data->block[1] 開始的記憶體裡面,複製的長度是由data->block[0] 來決定,讀取的資料還有一個command引數,賦值在msg[0].buf[0]變數裡面,可以用作傳遞給底層硬體的命令或者裝置暫存器的地址等(讀寫位置),當函式i2c_smbus_xfer_emulated返回鍵讀取的資料拷貝到上層讀取緩衝區。同理,噹噹前的操作時寫時num的值為2,data->block[0]存放的是讀取的長度,但是在i2c_smbus_xfer_emulated裡面將長度值轉存在msg[0].len,這是於讀取操作有區別的地方,然後將要寫的資料從data->block[1]開始複製到msg[0].len長度到msg[0].buf[1]開始的記憶體空間,因為msg[0].buf[0]儲存的是上層的command。
以上闡述了msg訊息的結構存放位置,下面看看收發一體函式:i2c_transfer:
主要是呼叫我們在介面卡裡面填充的結構體裡面實現的函式指標,經過上面的分析,我們可以知道具體怎麼填充介面卡資料獲取函式的msg與num的作用了,實際使用介面卡註冊的匯流排編號的裝置在裝置端獲取的並且註冊後,使用的是介面卡裡面的函式,這個介面的呼叫關係由上述的總結概括可以瞭解到驅動層使用i2c_smbus_read_i2c_block_data和i2c_smbus_write_i2c_block_data函式最終是將讀取長度,讀取位置,讀取到哪或者寫的長度,寫的位置,寫的資料存放在msg結構裡面然後傳遞到介面卡層,也就是說真正處理的地方是介面卡,裝置層以及驅動層對底層已經遮蔽起來了,這樣我們只要在是介面卡裡面新增對裝置初始化以及讀取寫入函式便可以操作IIC。
為了提供例子,我們採用AT24C02的讀寫函式來作為例子,其讀寫函式的函式定義為:
void IIC_ADAPTER_Write_Byte(u8 addr,u8 len,u8 *buff);
void IIC_ADAPTER_Write_Byte(u8 addr,u8 len,u8 *buff);
函式引數:addr表示讀寫在EEPROM裡面的儲存地址
len表示讀取的長度
buff讀寫緩衝區
可知上面的引數都在msg裡面,addr並非msg結構裡面成員的addr,這裡指的是AT24C02的儲存地址,不是器件地址。addr通過驅動的command引數來傳遞,位於msg[0].buf[0],len在msg結構的成員的len裡面,buff對應msg結構的buf,記住讀取時是從msg[1].buf[0]開始讀取,將資料讀取的資料複製到msg[1].buf[0]開始的地址,而寫入的資料從msg[0].buf[1]地址開始,因為msg[0].buf[0]被command佔用。通過這裡我們知道具體的IIC_ADAPTER_i2c_xfer函式的填引數:
static int IIC_ADAPTER_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
//不管是讀操作還是寫操作mags都有兩個結構體,msgs[0]與msgs[1]。
if(num==1)
{
IIC_ADAPTER_Write_Byte(msgs[0].buf[0],msgs[0].len,msgs[0].buf);
//這裡雖然傳入的是msgs[0].buf,這個函式在裡面處理了,從msgs[0].buf[1]開始存放
printk("adapter write data\n");
}
else if(num==2)
{
//讀取到的資料存放在msgs[1].buf裡面
IIC_ADAPTER_Read_Byte(msgs[0].buf[0],msgs[1].len,msgs[1].buf);
printk("adapter read data\n");
}
return 0;
}
通過上面的分析附上整個框架的程式碼:
介面卡:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/io.h>
#define GPD1CON_addr (0x11400000+0x00C0)
#define GPD1DAT_addr (0x11400000+0x00C4)
//八位,前四位為廠商自帶的地址1010 第一位到第三位為硬體地址,均連線0,最後一位為讀寫為,0:讀,1:寫
#define DEV_write_addr 0xA0 //write
#define DEV_read_addr 0xA1 //read
//SDA:GPD1_0
//SCL:GPD1_1
static volatile unsigned int *GPD1CON ;
static volatile unsigned int *GPD1DAT ;
#define SDA(x) if(x){*GPD1DAT |=1<<0;}else {*GPD1DAT&=~(1<<0);}
#define SCL(x) if(x){*GPD1DAT |=1<<1;}else {*GPD1DAT&=~(1<<1);}
#define SDA_STATE (*GPD1DAT&1<<0)
#define SDA_INPUT_MODE() {*GPD1CON&=~(0xF<<0*4);}//SDA輸入模式
#define SDA_OUTPUT_MODE() {*GPD1CON&=~(0xF<<0*4);*GPD1CON|=0x1<<0*4;}//SDA輸出模式
static struct i2c_adapter adapter;
static void IIC_Init(void)
{
*GPD1CON&=~(0xF<<0*4);//SDA初始化
*GPD1CON|=0x1<<0*4;
*GPD1CON&=~(0xF<<1*4);//SCL初始化
*GPD1CON|=0x1<<1*4;
SCL(1);
SDA(1);
}
static void START_condition()
{
SDA_OUTPUT_MODE();
SCL(1);
SDA(1);//start 條件之前
udelay(3);
SDA(0);
udelay(3);
SCL(0);
}
static void STOP_condition()
{
SDA_OUTPUT_MODE();
SCL(1);
SDA(0);//stop 條件之前
udelay(3);
SDA(1);
}
static u8 IIC_ADAPTER_BYTE_READ()
{
int i;
u8 data=0;
SDA_INPUT_MODE();
SCL(0);
for(i=0;i<8;i++)
{
SCL(1);
udelay(3);
data<<=1;
if(SDA_STATE)
{
data|=1;
}
SCL(0);
udelay(3);
}
SCL(0);//可不要 確保不是進入stop狀態
return data;
}
static void IIC_ADAPTER_BYTE_WRITE(char data)
{
SDA_OUTPUT_MODE();
int i;
for(i=0;i<8;i++)
{
SCL(0);
if(data&0x80)
{
SDA(1);
}
else
{
SDA(0);
}
udelay(3);
SCL(1);
udelay(3);
data<<=1;
}
SCL(0);
}
static int RECIVE_ACK()//主機發送資料後接收來自從機的ack
{
SDA_INPUT_MODE();
SDA(1);//******上拉
int i=0;
SCL(0);
udelay(3);
SCL(1);
while(SDA_STATE)
{
i++;
udelay(1);
if(i>=100)
{
printk("超時\n");
return 1;
}
}
SCL(0);
return 0;
}
static void SEND_ACK(int statu)//主機接收資料後傳送給從機的ack
{
SDA_OUTPUT_MODE();
SCL(0);
if(statu==0)//ack
{
SDA(0);
udelay(3);
}
else
if(statu==1)// no ack
{
SDA(1);
udelay(3);
}
SCL(1);//告訴從機讀
udelay(3);
SCL(0);
}
void IIC_ADAPTER_Read_Byte(u8 addr,u8 len,u8 *buff)
{
int i=0;
START_condition();
IIC_ADAPTER_BYTE_WRITE(DEV_write_addr);//讀操作,但是告訴主機從機要執行的是讀操作,先要寫入寫命令
RECIVE_ACK();
IIC_ADAPTER_BYTE_WRITE(addr);//然後寫入要讀取的地址
RECIVE_ACK();
START_condition();
IIC_ADAPTER_BYTE_WRITE(DEV_read_addr);//告訴從機開始讀資料了
RECIVE_ACK();
for(i=0;i<len;i++)
{
buff[i]=IIC_ADAPTER_BYTE_READ();
SEND_ACK(0);
}
SEND_ACK(1);
STOP_condition();//
}
void IIC_ADAPTER_Write_Byte(u8 addr,u8 len,u8 *buff)
{
int i=0;
START_condition();
IIC_ADAPTER_BYTE_WRITE(DEV_write_addr);
RECIVE_ACK();
IIC_ADAPTER_BYTE_WRITE(addr);
RECIVE_ACK();
for(i=1;i<=len;i++)//從msgs[0].buf[1]開始寫,msgs[0].buf[0]存放的是地址
{
IIC_ADAPTER_BYTE_WRITE(buff[i]);
RECIVE_ACK();
}
STOP_condition();
}
static u32 IIC_ADAPTER_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART |I2C_FUNC_PROTOCOL_MANGLING;
}
static int IIC_ADAPTER_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
//不管是讀操作還是寫操作mags都有兩個結構體,msgs[0]與msgs[1]。
if(num==1)
{
IIC_ADAPTER_Write_Byte(msgs[0].buf[0],msgs[0].len,msgs[0].buf);
printk("adapter write data\n");
}
else if(num==2)
{
//讀取到的資料存放在msgs[1].buf裡面
IIC_ADAPTER_Read_Byte(msgs[0].buf[0],msgs[1].len,msgs[1].buf);
printk("adapter read data\n");
}
return 0;
}
static const struct i2c_algorithm IIC_ADAPTER_i2c_algorithm = {
.master_xfer = IIC_ADAPTER_i2c_xfer,
.functionality = IIC_ADAPTER_i2c_func,
};
static int __init i2c_adapter_init(void)
{
GPD1CON=ioremap(GPD1CON_addr,4);
GPD1DAT=ioremap(GPD1DAT_addr,4);
IIC_Init();
adapter.owner = THIS_MODULE;
adapter.algo = &IIC_ADAPTER_i2c_algorithm;
adapter.retries = 2;
adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
strlcpy(adapter.name,"IIC_ADAPTER_i2c",sizeof(adapter.name));
i2c_add_adapter(&adapter);
printk("介面卡分配成功的匯流排編號: %d\n",adapter.nr);
return 0;
}
static void __exit i2c_adapter_exit(void)
{
iounmap(GPD1CON);
iounmap(GPD1DAT);
i2c_del_adapter(&adapter);
printk(" IIC介面卡登出成功!\n");
}
module_init(i2c_adapter_init);
module_exit(i2c_adapter_exit);
MODULE_LICENSE("GPL");
裝置層:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
static struct i2c_client *i2cClient = NULL;
static struct i2c_adapter *i2c_adap;
static struct i2c_board_info i2c_info;
static unsigned short i2c_addr_list[0xFF]=
{
0x50,I2C_CLIENT_END
};
static int __init i2c_dev_init(void)
{
i2c_adap = i2c_get_adapter(9); /*要使用匯流排*/
if(i2c_adap==NULL)
{
printk("介面卡獲取失敗!\n");
return 0;
}
memset(&i2c_info,0,sizeof(struct i2c_board_info));
strlcpy(i2c_info.type,"touch_name",I2C_NAME_SIZE);
i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL);
if(i2cClient==NULL)
{
printk("地址檢查錯誤!\n");
return 0;
}
i2c_put_adapter(i2c_adap);
printk("i2c_dev_init!!\n");
return 0;
}
static void __exit i2c_dev_exit(void)
{
printk(" i2c_dev_exit ok!!\n");
if(i2c_adap!=NULL)
{
i2c_unregister_device(i2cClient);
i2c_release_client(i2cClient);
}
}
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
MODULE_LICENSE("GPL");
驅動層:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/delay.h>
static const struct i2c_device_id i2c_id[] =
{
{"touch_name",0},//裝置端的名字為"myiic",0表示不需要私有資料
{}
};
static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)//匹配成功時呼叫
{
printk("驅動端IIC匹配的地址=0x%x\n",client->addr);
u8 buff_read[10];
u8 buff_write[10];
strcpy(buff_write,"houjunzui");
i2c_smbus_write_i2c_block_data(client,1,10,buff_write);
mdelay(10);
i2c_smbus_read_i2c_block_data(client,1,10,buff_read);
printk("read: %s \n",buff_read);
return 0;
}
static int i2c_remove(struct i2c_client *client)
{
printk("i2c_remove!!!\n");
return 0;
}
struct i2c_driver i2c_drv =
{
.driver = //這個不新增會報錯,實際上沒作用
{
.name = "iic_drv",
.owner = THIS_MODULE,
},
.probe = i2c_probe, //探測函式
.remove = i2c_remove, //資源解除安裝
.id_table = i2c_id, //裡面有一個名字的引數用來匹配裝置端名字
};
/*iic驅動端*/
static int __init i2c_drv_init(void)
{
i2c_add_driver(&i2c_drv);//向iic匯流排註冊一個驅動
return 0;
}
static void __exit i2c_drv_exit(void)//平臺裝置端的出口函式
{
i2c_del_driver(&i2c_drv);
}
module_init(i2c_drv_init);
module_exit(i2c_drv_exit);
MODULE_LICENSE("GPL");