展訊智能機平臺代碼i2c設備驅動解讀(上)
接觸linux有兩年多的時間了,以前覺得懂linux的人很牛B,學會了linux還可以裝B,現在回想起來,有這種想法真的是很傻很天真,圖樣圖森破。真正裝B的人是那些一邊呼喚公平正義一邊利用各種關係上位,一邊呼喊保護環境藍天白雲一邊開着大排量的SUV,一邊高呼民主自由佔中一邊斷了別人的行路的自由上學的自由工作掙錢的自由購物的自由。對於這樣的裝B,我只能羨慕妒忌恨,臣妾做不到,臣妾真的做不到啊。
我琢磨着這輩子都必須和linux相依爲伴,不離不棄了,不然,我還能幹什麼?這些年來,對我來說,生存總是那麼困難,生活的壓力總是那麼大,不好好碼代碼,還能有什麼出路。
閒話少說,媽咪說,上鐘的時間到了。今天,要碼的豬腳,就是linux i2c設備驅動。理論知識上的諸多描述,摘抄自網絡上的多位大牛,而所有的代碼,都來源於展訊發佈的7715平臺代碼版本。
前面已經說過,linux設備驅動模型分爲3個重要部分,總線bus,設備device,驅動driver。現在,具體問題具體分析。在Linux驅動中I2C系統中主要包含以下幾個成員:
I2C adapter 即I2C適配器,依附於I2C總線,可以理解爲I2C總線控制器。
I2C driver 某個I2C設備的設備驅動。
I2C device, 某個I2C設備的設備本身,在代碼中以i2c_client指代。
下面,咱們開始具體介紹:
I2C adapter
目前一般的手機平臺都集成了I2C適配器,用來控制各種I2C從設備,我們可以理解爲I2C總線控制器。
i2c_adapter結構體如下:
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* 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_bus_recovery_info *bus_recovery_info;
};
實現適配器的最主要的工作是需要完成i2c_algorithm結構體。這個結構體包含了此I2C控制器的數據傳輸具體實現,以及對外上報此設備所支持的功能類型。i2c_algorithm結構體如下:
/*
* The following structs are for those who like to implement new bus drivers:
* i2c_algorithm is the interface to a class of hardware solutions which can
* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
* to name two of the most common.
*/
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer to NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulated using common I2C messages */
/* master_xfer should return the number of messages successfullyprocessed, or a negative value on error */
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);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
代碼不多,而且大部分都還是註釋。註釋也很簡單:如果一個I2C適配器不支持I2C通道,那麼就將master_xfer成員設爲NULL。如果適配器支持SMBUS協議,那麼需要去實現smbus_xfer,如果smbus_xfer指針被設爲NULL,那麼當使用SMBUS協議的時候將會通過I2C通道進行仿真。master_xfer指向的函數的返回值應該是已經成功處理的消息數,或者返回負數表示出錯了。functionality指針很簡單,告訴詢問着這個I2C主控器都支持什麼功能。
在展訊平臺,這個i2c_algorithm被定義如下:
/* i2c bus registration info */
static const struct i2c_algorithm sprd_i2c_algorithm = {
.master_xfer = sprd_i2c_xfer,
.functionality = sprd_i2c_func,
};
具體的I2C通訊實現,都依靠這個sprd_i2c_xfer來執行。支持的功能,則在prd_i2c_func裏聲明。
I2C driver
具體的I2C設備驅動,如相機、傳感器、觸摸屏、背光控制器常見硬件設備大多都有或都是通過I2C協議與主機進行數據傳輸、控制。結構體如下:
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
一個具體的I2C設備驅動使用如下:
static const struct i2c_device_id gslX680_ts_id[] = {
{ GSLX680_NAME, 0 },{ }
};
MODULE_DEVICE_TABLE(i2c, gslX680_ts_id);
static struct i2c_driver gslX680_ts_driver = {
.probe = gslX680_ts_probe,
.remove = gslX680_ts_remove,
.id_table = gslX680_ts_id,
.driver = {
.name = GSLX680_NAME,
.owner = THIS_MODULE,
},
};
module_i2c_driver(gslX680_ts_driver);
gslX680_ts_id內定義了該I2C DRIVER驅動支持設備名爲GSLX680_NAME。需要注意的是,如同一個普通的USB驅動可以支持多個USB存儲設備一樣,一個I2C driver也可以對應支持多個I2C設備。可以支持的I2C設備在該driver的i2c_device_id內一一列明。我們之前已經說過,Linux內核依靠i2c_device_id所列內容將設備與驅動進行配對。這就好比人類的相親,條件先列好來:有房有車,上無老下無小,條件符合就繼續進行,等不急的直接拿證;條件不符合的,你不是我的菜,該幹嘛幹嘛去,接下來繼續尋找條件符合的去。在Linux的世界裏,一就是一,二就是二,一切都寫的明明白白,不像咱們人類世界,暗箱操作,潛規則橫行,兄弟我尤其羨慕那些導演的潛規則……
MODULE_DEVICE_TABLE(i2c, gslX680_ts_id),將該driver支持的設備列表加入i2c總線設備列表中。
gslX680_ts_probe,驅動匹配函數,driver通過該函數與設備進行進一步的匹配確認,通過後該設備才真正可用。
.owner = THIS_MODULE,驅動擁有者爲THIS_MODULE,當前模塊。別問我爲什麼這麼寫,大家都這麼用,我也不知道。就像大家都說民主自由後就可以買得起房,看得起病,養得起孩紙,讀得起書,食物無毒,水無毒,空氣無毒。Oh,My god,民主真是萬能的包治百病的靈丹妙藥。民主的美利堅萬歲,民主的印度萬歲,請和我乾了這杯聖潔的帶着自由靈光的恆河水,不吐不歸。
module_i2c_driver(gslX680_ts_driver),我們看一下module_i2c_driver這個宏是如何定義:
/**
* module_i2c_driver() - Helper macro for registering a I2C driver
* @__i2c_driver: i2c_driver struct
*
* Helper macro for I2C drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit()
*/
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)
簡單解釋下:module_i2c_driver宏用於註冊一個I2C驅動,用戶使用該宏來代替module_init() and module_exit()這兩個函數。一言以蔽之,用了module_i2c_driver,就不再需要使用module_init() and module_exit()。
這一句宏就解決了模塊module安裝卸載的複雜代碼。這樣驅動開發者在實現I2C驅動時只要將i2c_driver結構體填充進來就可以了,無需關心設備的註冊與反註冊過程。
將module_driver展開,驗證了這個說法:
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
I2C device
在代碼裏用I2C client來指代,字面上的意思是I2C客戶端,也即I2C device設備。在《linux內核學習—總線,設備,驅動》裏我們有提到,I2C設備的註冊一般在板級代碼中,在展訊7715平臺這個板級代碼的名字叫board-sp7715ga.c,讓我們來看看代碼:
static struct i2c_board_info i2c1_boardinfo[] = {
#ifdef CONFIG_TOUCHSCREEN_FOCALTECH
{
I2C_BOARD_INFO(FOCALTECH_TS_NAME, FOCALTECH_TS_ADDR),
.platform_data = &ft5x0x_ts_info,
},
#endif
#ifdef CONFIG_TOUCHSCREEN_GSLX680
{
I2C_BOARD_INFO(GSLX680_TS_DEVICE, GSLX680_TS_ADDR),
.platform_data = &gslx680_ts_info,
},
#endif
}
…
i2c_register_board_info(1, i2c1_boardinfo, ARRAY_SIZE(i2c1_boardinfo));
看到了嗎,GSLX680這個I2C TP設備的設備名和地址在此被註冊,當名字與i2c_driver中的id_table中的成員匹配時就能夠執行probe匹配函數並最終和其驅動勾搭上了。那麼i2c_board_info這個結構體如何定義?
/**
* struct i2c_board_info - template for device creation
* @type: chip type, to initialize i2c_client.name
* @flags: to initialize i2c_client.flags
* @addr: stored in i2c_client.addr
* @platform_data: stored in i2c_client.dev.platform_data
* @archdata: copied into i2c_client.dev.archdata
* @of_node: pointer to OpenFirmware device node
* @acpi_node: ACPI device node
* @irq: stored in i2c_client.irq
*
* I2C doesn't actually support hardware probing, although controllers and
* devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's
* a device at a given address. Drivers commonly need more information than
* that, such as chip type, configuration, associated IRQ, and so on.
*
* i2c_board_info is used to build tables of information listing I2C devices
* that are present. This information is used to grow the driver model tree.
* For mainboards this is done statically using i2c_register_board_info();
* bus numbers identify adapters that aren't yet available. For add-on boards,
* i2c_new_device() does this dynamically with the adapter already known.
*/
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct acpi_dev_node acpi_node;
int irq;
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
結構體不長,註釋卻很多。幸好有這些註釋,讓我們在學習linux代碼時沒那麼痛苦。將註釋加上後如下:
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //設備名,最長20個字符,最終安裝到client的name上
unsigned short flags; //最終安裝到client.flags
unsigned short addr; //設備從地址slave address,最終安裝到client.addr上
void *platform_data; //設備數據,最終存儲到i2c_client.dev.platform_data上
struct dev_archdata *archdata;
struct device_node *of_node; //OpenFirmware設備節點指針
struct acpi_dev_node acpi_node;
int irq; //設備採用的中斷號,最終存儲到i2c_client.irq上
};
其實不加上中文註釋,你也很容易理解。只是在這裏,言必稱i2c_client,漫漫人生路,我們需要再理解下i2c_client。
/**
* struct i2c_client - represent an I2C slave device
* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
* I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
* @addr: Address used on the I2C bus connected to the parent adapter.
* @name: Indicates the type of the device, usually a chip name that's
* generic enough to hide second-sourcing and compatible revisions.
* @adapter: manages the bus segment hosting this I2C device
* @driver: device's driver, hence pointer to access routines
* @dev: Driver model device node for the slave.
* @irq: indicates the IRQ generated by this device (if any)
* @detected: member of an i2c_driver.clients list or i2c-core's
* userspace_devices list
*
* An i2c_client identifies a single device (i.e. chip) connected to an
* i2c bus. The behaviour exposed to Linux is defined by the driver
* managing the device.
*/
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
結合上下文,你是否有一種豁然開朗的感覺,其實i2c_board_info的信息被用來填充i2c_client了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.