I2C總線僅僅使用 SCL 、 SDA 兩根信號線就實現(xiàn)了設(shè)備之間的數(shù)據(jù)交互。
由于各種SOC都有自己的I2C總線,為了上層能統(tǒng)一接口,采用這種三層I2C架構(gòu).
I2C總線驅(qū)動主要實現(xiàn)了適用于特定I2C控制器的總線讀寫方法,并注冊到Linux內(nèi)核的I2C架構(gòu),I2C外設(shè)就可以通過I2C架構(gòu)完成設(shè)備和總線的適配。但是總線驅(qū)動本身并不會進(jìn)行任何的通訊,它只是提供通訊的實現(xiàn),等待設(shè)備驅(qū)動來調(diào)用其函數(shù)。
I2C Core的管理正好屏蔽了I2C總線驅(qū)動的差異,使得I2C設(shè)備驅(qū)動可以忽略各種總線控制器的不同,不用考慮其如何與硬件設(shè)備通訊的細(xì)節(jié)。
Linux的I2C構(gòu)架分為三個部分:
1)I2C core框架
提供了核心數(shù)據(jù)結(jié)構(gòu)的定義和相關(guān)接口函數(shù),用來實現(xiàn)I2C適配器
驅(qū)動和設(shè)備驅(qū)動的注冊、注銷管理,以及I2C通信方法上層的、與具體適配器無關(guān)的代碼,為系統(tǒng)中每個I2C總線增加相應(yīng)的讀寫方法。
kernel抽象出I2C bus(/sys/bus/i2c),用于掛載和I2C adapter通過I2C總線連接的各個I2C slave device。
I2C Bus 并不是通訊上的總線,而是linux系統(tǒng)為了管理設(shè)備和驅(qū)動而虛擬出來的,在I2C Bus用來掛載后面將會使用到的I2C 適配器(adapter)和I2C設(shè)備(client)
2) I2C總線驅(qū)動 (i2c_adapter)
定義描述具體I2C總線適配器的i2c_adapter數(shù)據(jù)結(jié)構(gòu)、實現(xiàn)在具體I2C適配器上的I2C總線通信方法,并由i2c_algorithm數(shù)據(jù)結(jié) 構(gòu)進(jìn)行描述。
封裝了 struct device ,因此它是作為一個設(shè)備注冊到內(nèi)核中去的(是注冊到i2c_bus_type里),此外非常重要的一個成員struct i2c_algorithm *algo ,這就是我們上邊提到的 i2c 控制器收發(fā)數(shù)據(jù)的方法。
經(jīng)過I2C總線驅(qū)動的的代碼,可以為我們控制I2C產(chǎn)生開始位、停止位、讀寫周期以及從設(shè)備的讀寫、產(chǎn)生ACK等。
I2C總線驅(qū)動具體實現(xiàn)在/drivers/i2c目錄下busses文件夾。
例如:
Linux I2C GPIO總線驅(qū)動為i2c_gpio.c.
全志 drivers/i2c/busses/i2c-sunxi.c
I2C總線算法在/drivers/i2c目錄下algos文件夾。
例如:Linux I2C GPIO總線驅(qū)動算法實現(xiàn)在i2c_algo_bit.c.
針對不同類型的I2C控制器,實現(xiàn)對I2C總線訪問的具體方法.(各種SOC不一樣)
3) I2C 設(shè)備驅(qū)動(I2C client driver)
是對具體I2C硬件驅(qū)動的實現(xiàn)。I2C 設(shè)備驅(qū)動通過I2C適配器與CPU通信。
其中主要包含i2c_driver和i2c_client數(shù)據(jù)結(jié)構(gòu)。
i2c_driver結(jié)構(gòu)對應(yīng)一套具體的驅(qū)動 方法,例如:probe、remove、suspend等,需要自己申明。
i2c_client數(shù)據(jù)結(jié)構(gòu)由內(nèi)核根據(jù)具體的設(shè)備注冊信息自動生成,設(shè)備驅(qū)動 根據(jù)硬件具體情況填充。
I2C 設(shè)備驅(qū)動具體實現(xiàn)放在在/drivers/i2c目錄下chips文件夾。

重要的結(jié)構(gòu)體
i2c_driver
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函數(shù)指針
int (*detach_adapter)(struct i2c_adapter *);//脫離i2c_adapter函數(shù)指針
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;
13 const struct i2c_device_id *id_table;//該驅(qū)動所支持的設(shè)備ID表
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
i2c_client
struct i2c_client {
unsigned short flags;//標(biāo)志
unsigned short addr; //低7位為芯片地址
char name[I2C_NAME_SIZE];//設(shè)備名稱
struct i2c_adapter *adapter;//依附的i2c_adapter
struct i2c_driver *driver;//依附的i2c_driver
struct device dev;//設(shè)備結(jié)構(gòu)體
int irq;//設(shè)備所使用的結(jié)構(gòu)體
struct list_head detected;//鏈表頭
};
struct i2c_adapter {
struct module *owner;//所屬模塊
unsigned int id;//algorithm的類型,定義于i2c-id.h,
unsigned int class;
const struct i2c_algorithm *algo; //總線通信方法結(jié)構(gòu)體指針
void *algo_data;//algorithm數(shù)據(jù)
struct rt_mutex bus_lock;//控制并發(fā)訪問的自旋鎖
int timeout;
int retries;//重試次數(shù)
struct device dev; //適配器設(shè)備
int nr;
char name[48];//適配器名稱
struct completion dev_released;//用于同步
struct list_head userspace_clients;//client鏈表頭
15 };
i2c_adapter與i2c_algorithm
i2c_adapter對應(yīng)與物理上的一個適配器,而i2c_algorithm對應(yīng)一套通信方法,一個i2c適配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下層與硬件相關(guān)的代碼提供)通信函數(shù)來控制適配器上產(chǎn)生特定的訪問周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指針。
i2c_algorithm中的關(guān)鍵函數(shù)master_xfer()用于產(chǎn)生i2c訪問周期需要的start stop ack信號,以i2c_msg(即i2c消息)為單位發(fā)送和接收通信數(shù)據(jù)。
i2c_msg也非常關(guān)鍵,調(diào)用驅(qū)動中的發(fā)送接收函數(shù)需要填充該結(jié)構(gòu)體
i2c_driver和i2c_client
i2c_driver對應(yīng)一套驅(qū)動方法,其主要函數(shù)是attach_adapter()和detach_client()
i2c_client對應(yīng)真實的i2c物理設(shè)備device,每個i2c設(shè)備都需要一個i2c_client來描述
i2c_driver與i2c_client的關(guān)系是一對多。一個i2c_driver上可以支持多個同等類型的i2c_client.
i2c_adapter和i2c_client
i2c_adapter和i2c_client的關(guān)系與i2c硬件體系中適配器和設(shè)備的關(guān)系一致,即i2c_client依附于i2c_adapter,由于一個適配器上可以連接多個i2c設(shè)備,所以i2c_adapter中包含依附于它的i2c_client的鏈表。
struct list_head userspace_clients;//client鏈表頭
重要的接口函數(shù)
注冊一個驅(qū)動
【函數(shù)原型】:i2c_add_driver
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
【功能描述】:注冊一個I2C設(shè)備驅(qū)動。從代碼可以看帶i2c_add_driver()是一個宏,由函數(shù)i2c_register_driver()實現(xiàn)。
【參數(shù)說明】:driver,i2c_driver類型的指針,其中包含了I2C設(shè)備的名稱、probe、detect等接口信息。
注冊一個設(shè)備
【函數(shù)原型】:i2c_register_board_info
int i2c_register_board_info(int busnum, struct i2c_board_info const *info,
unsigned n)
【功能描述】:向某個I2C總線注冊I2C設(shè)備信息,I2C子系統(tǒng)通過此接口保存I2C總線和I2C設(shè)備的適配關(guān)系。
busnum 通過總線號指定這個(些)設(shè)備屬于哪個總線
info i2c設(shè)備的數(shù)組集合i2c_board_info格式
i2c_register_board_info具體實現(xiàn): 相關(guān)信息放到鏈表中就算完事
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock); //i2c設(shè)備信息讀寫鎖,鎖寫操作,其他只讀
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num) //與動態(tài)分配的總線號相關(guān),動態(tài)分配的總線號應(yīng)該是從已經(jīng)現(xiàn)有最大總線號基礎(chǔ)上+1的,這樣能夠保證動態(tài)分配出的總線號與板級總線號不會產(chǎn)生沖突
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) { //處理info數(shù)組中每個成員
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum; //組裝總線號
devinfo->board_info = *info; //組裝設(shè)備信息
list_add_tail(&devinfo->list, &__i2c_board_list); //加入到__i2c_board_list鏈表中(尾部)
}
up_write(&__i2c_board_lock); //釋放讀鎖,其他可讀可寫
return status;
}
調(diào)用i2c_register_board_info的I2C設(shè)備注冊過程應(yīng)該在板級代碼初始化期間,也就是arch_initcall前后的時間,
在I2C適配器驅(qū)動注冊前完成。
如果在I2C適配器注冊完后還想要添加I2C設(shè)備的話,就要通過新方式?。磇2c_new_device)
【函數(shù)原型】: i2c_new_device
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
adap 此設(shè)備所依附的I2C適配器指針
info 此設(shè)備描述,i2c_board_info格式,bus_num成員是被忽略的
struct i2c_board_info info={
.type = SENSOR=NAME,
.addr = SENSOR_I2C_ADDR,
}
adapter = i2c_get_adapter(0); //參數(shù)代表i2c num
client = i2c_new_device(adapter, &info);
數(shù)據(jù)傳輸
I2C設(shè)備驅(qū)動使用"struct i2c_msg"向I2C總線請求讀寫I/O。
一個i2c_msg中包含了一個I2C操作,通過調(diào)用i2c_transfer()接口觸發(fā)I2C總線的數(shù)據(jù)收發(fā)。
【函數(shù)原型】:i2c_transfer
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
【功能描述】:完成I2C總線和I2C設(shè)備之間的一定數(shù)目的I2C message交互。
【函數(shù)原型】:i2c_master_recv
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
【功能描述】:通過封裝i2c_transfer()完成一次I2c接收操作。
【函數(shù)原型】:i2c_master_send
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
【功能描述】:通過封裝i2c_transfer()完成一次I2c發(fā)送操作。
【函數(shù)原型】:i2c_smbus_read_byte
s32 i2c_smbus_read_byte(const struct i2c_client *client)
【功能描述】:從I2C總線讀取一個字節(jié)。(內(nèi)部是通過i2c_transfer()實現(xiàn),以下幾個接口同。)
【函數(shù)原型】:i2c_smbus_write_byte
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
【功能描述】:從I2C總線寫入一個字節(jié)。
驅(qū)動代碼例子
設(shè)備
1)BSP文件中靜態(tài)聲明一個I2C設(shè)備
static struct i2c_board_info i2c_devices[] __initdata = {
{I2C_BOARD_INFO("24c02", 0x50), },
{}
};
2)向總線注冊I2C設(shè)備信息:
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
驅(qū)動
1)模塊初始化時添加/撤銷時刪除i2c_driver
module_init(mma7660_init); //模塊入口
module_exit(mma7660_exit); //模塊出口
2)
init中和驅(qū)動框架相關(guān)的就一句話ret = i2c_add_driver(&mma7660_driver)而這一句話表示向I2C總線注冊一個驅(qū)動,根據(jù)宏定義i2c_driver結(jié)構(gòu)并完成其相應(yīng)函數(shù):
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,
};
3)使用/dev entry 訪問方法
register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);
創(chuàng)建類class_create(THIS_MODULE, DEVICE_NAME);
在/dev下創(chuàng)建設(shè)備節(jié)點
device_create(my_dev_class, &client->dev,MKDEV(I2C_MAJOR, 0), NULL, DEVICE_NAME);
I2c detect的方法
必須要定義address_list
static unsigned short s_Normal_I2c[] = {0x35, I2C_CLIENT_END}; // 芯片地址
static const struct i2c_device_id i2c_detect_id[] = {
{LMX_I2C_DETECT_DEVICE_NAME, 0},
{}
};
static struct i2c_driver i2c_detect_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.owner = THIS_MODULE,
.name = LMX_I2C_DETECT_DEVICE_NAME,
},
.detect = i2c_detect_Detect, // 會往所有的 I2C 控制器上尋指定的 I2C 設(shè)備地址的 I2C 設(shè)備,若有 ACK 回應(yīng)則會調(diào)用該函數(shù)
.id_table = i2c_detect_id,
.address_list = s_Normal_I2c, // 地址列表
};
i2c_core.c 里面有一個 i2c_detect函數(shù)
if(driver->detect || !address_list)
return 0;