轉載請標明出處floater的csdn blog,http://blog.csdn.net/flaoter
1 體系結構
linux的i2c體系由以下三部分組成:
1.1 i2c核心
由linux內核提供,定義基本數據結構,實現i2c總線,驅動和設備的註冊、註銷,通信方法等。與設備無關。
1.2 i2c控制器驅動
由i2c控制器廠商提供,目前i2c控制器大多集中在soc芯片中,所以大多由soc廠商提供。代表i2c master,主要實現控制器的設備註冊和驅動註冊。
1.3 i2c設備驅動
一般由外設廠商提供,代表i2c slave,主要實現從設備的註冊和驅動註冊。
2 數據結構
i2c_adapter對應一個適配器設備,用於描述i2c master控制器
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總線傳輸方法,總線傳輸協議的就是在此結構體成員函數中實現的。
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, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, 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_adpter, i2c_algorithm都需要在控制器驅動中進行實現。
i2c_client是i2c slave設備的數據結構
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 device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
i2c設備驅動數據結構,是i2c_client設備的驅動。
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_driver需要在i2c設備驅動中進行實現。
3 控制器驅動
內核源碼目錄結構及config配置如下:
drivers/i2c/
├── algos
│ ├── i2c-algo-bit.c
│ ├── i2c-algo-pca.c
│ ├── i2c-algo-pcf.c
│ ├── i2c-algo-pcf.h
│ ├── Kconfig
│ └── Makefile
├── busses
│ ├── i2c-gpio.c
│ ├── i2c-vendor.c
│ ├── Kconfig
│ ├── Makefile
├── i2c-boardinfo.c
├── i2c-core.c
├── i2c-core.h
├── i2c-dev.c
├── i2c-mux.c
├── i2c-slave-eeprom.c
├── i2c-smbus.c
├── i2c-stub.c
├── Kconfig
├── Makefile
└── muxes
├── Kconfig
└── Makefile
//gpio模擬
obj-$(CONFIG_I2C) += i2c-core.o
obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-y += algos/ busses/ muxes/
obj-$(CONFIG_I2C_VENDORNAME) += i2c-vendor.o //控制器驅動,隱藏了廠商名
obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o
控制器驅動部分包括控制器設備註冊,控制器驅動註冊,總線傳輸和用戶層接口四個部分,下面分別對它們進行說明。
3.1 控制器設備註冊
該部分代碼在dt解析中實現,請參見kernel初始化時dt解析的代碼。i2c controller是platform device。
dts中定義如下:
i2c2: i2c@50d00000 {
compatible = "vendor-i2c";
reg = <0 0x70d00000 0 0x1000>;
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "i2c";
clocks = <&clk_i2c0>;
clock-frequency = <400000>;
};
3.2 控制器驅動註冊
一般SOC廠商都會自己定義專有控制器結構體,不同廠商的數據結構成員不同,但肯定與自身控制器的使用密切相關的內容,比如內存基地址,clock,中斷號等。linux驅動與設備是一對多的關係,在i2c_adapter設備註冊時,控制器的結構體信息一般會提供給i2c_adapter作爲私有數據。,本文中的結構體如下:
struct vendor_i2c {
struct i2c_msg *msg;
struct i2c_adapter adap;
void __iomem *membase;
struct clk *clk;
unsigned int src_clk;
int irq;
struct vendor_platform_i2c *pdata;
};
static struct of_device_id vendor_i2c_of_match[] = {
{ .compatible = "vendor-i2c", },
};
static struct platform_driver vendor_i2c_driver = {
.probe = vendor_i2c_probe,
.remove = vendor_i2c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "vendor-i2c",
.of_match_table = of_match_ptr(vendor_i2c_of_match),
.pm = &vendor_i2c_pm_ops,
},
};
static int __init vendor_i2c_init(void)
{
return platform_driver_register(&vendor_i2c_driver);
}
arch_initcall_sync(vendor_i2c_init);
i2c controller是platform_device,因此驅動通過platform_driver_register進行註冊。由於compatible屬性爲”vendor-i2c”的platform device已經在3.1中註冊完成,vendor_i2c_probe會被加載執行。
static int vendor_i2c_probe(struct platform_device *pdev)
{
struct vendor_i2c *pi2c;
struct resource *res;
struct device_node *np = pdev->dev.of_node;
...
pi2c = devm_kzalloc(&pdev->dev, sizeof(struct vendor_i2c), GFP_KERNEL);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //dt中reg資源
//i2c_adapter數據結構的填充
i2c_set_adapdata(&pi2c->adap, pi2c); //adapter->dev->drvdata=pi2c
snprintf(pi2c->adap.name, sizeof(pi2c->adap.name), "%s", "vendor-i2c");
pi2c->adap.owner = THIS_MODULE;
pi2c->adap.retries = 3;
pi2c->adap.algo = &vendor_i2c_algo;
pi2c->adap.algo_data = pi2c;
pi2c->adap.dev.parent = &pdev->dev;
pi2c->adap.nr = pdev->id;
pi2c->adap.dev.of_node = pdev->dev.of_node;
pi2c->membase = devm_ioremap_nocache(&pdev->dev, res->start,
res->end - res->start);
//clock相關
pi2c->pdata = devm_kzalloc(&pdev->dev,
sizeof(struct vendor_platform_i2c), GFP_KERNEL);
memcpy(pi2c->pdata, &vendor_platform_i2c_default,
sizeof(struct vendor_platform_i2c));
if (!of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&prop))
pi2c->pdata->bus_freq = prop;
ret = vendor_i2c_clk_init(pi2c);
clk_prepare_enable(pi2c->clk);
vendor_i2c_enable(pi2c);
clk_disable(pi2c->clk);
ret = i2c_add_numbered_adapter(&pi2c->adap); //adapter的註冊過程在下面進行了展開
...
}
其中,最後的adapter註冊如下代碼,在i2c-core.c實現。
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
return __i2c_add_numbered_adapter(adap);
}
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
return i2c_register_adapter(adap);
}
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
dev_set_name(&adap->dev, "i2c-%d", adap->nr); //adapter->dev的name是i2c-0,1,2等,設備註冊後udev會在/dev下創建對應的設備名節點
adap->dev.bus = &i2c_bus_type; //i2c_bus_type對adpater作用??
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev); //adapter設備註冊
exit_recovery:
/* create pre-declared device nodes */
of_i2c_register_devices(adap); //(1)
acpi_i2c_register_devices(adap);
acpi_i2c_install_space_handler(adap);
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap); //(2)
}
(1)通過對dt進行解析,對掛在i2c controller下的i2c從設備進行註冊。i2c從設備在linux中是通過i2c_client數據結構進行描述的,因此註冊過程主要是對i2c_client數據結構的填充並將它註冊到內核中。dt中的描述如下,本例中的i2c從設備是BOSCH的加速度傳感器:
&i2c2 {
status = "okay";
clock-frequency = <400000>;
bma2x2@19{
compatible = "BOSCH,bma2x2";
reg = <0x19>;
gpios = <&ap_gpio 92 0>;
};
}
繼續i2c_client設備的註冊,代碼如下:
static void of_i2c_register_devices(struct i2c_adapter *adap)
{
struct device_node *node;
/* Only register child devices if the adapter has a node pointer set */
if (!adap->dev.of_node)
return;
dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");
for_each_available_child_of_node(adap->dev.of_node, node) {
if (of_node_test_and_set_flag(node, OF_POPULATED))
continue;
of_i2c_register_device(adap, node); //調用of_i2c_register_device進行設備註冊,下面有展開
}
}
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
struct device_node *node)
{
struct i2c_client *result;
struct i2c_board_info info = {};
struct dev_archdata dev_ad = {};
const __be32 *addr_be;
u32 addr;
int len;
dev_dbg(&adap->dev, "of_i2c: register %s\n", node->full_name);
if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) { //解析dt中設備compiatble屬性填充info.type
dev_err(&adap->dev, "of_i2c: modalias failure on %s\n",
node->full_name);
return ERR_PTR(-EINVAL);
}
addr_be = of_get_property(node, "reg", &len); //從設備地址
if (!addr_be || (len < sizeof(*addr_be))) {
dev_err(&adap->dev, "of_i2c: invalid reg on %s\n",
node->full_name);
return ERR_PTR(-EINVAL);
}
addr = be32_to_cpup(addr_be);
if (addr & I2C_TEN_BIT_ADDRESS) {
addr &= ~I2C_TEN_BIT_ADDRESS;
info.flags |= I2C_CLIENT_TEN;
}
if (addr & I2C_OWN_SLAVE_ADDRESS) {
addr &= ~I2C_OWN_SLAVE_ADDRESS;
info.flags |= I2C_CLIENT_SLAVE;
}
if (i2c_check_addr_validity(addr, info.flags)) {
dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s\n",
info.addr, node->full_name);
return ERR_PTR(-EINVAL);
}
info.addr = addr;
info.of_node = of_node_get(node);
info.archdata = &dev_ad;
if (of_get_property(node, "wakeup-source", NULL))
info.flags |= I2C_CLIENT_WAKE;
result = i2c_new_device(adap, &info); //註冊i2c device,在下面會進行展開
if (result == NULL) {
dev_err(&adap->dev, "of_i2c: Failure registering %s\n",
node->full_name);
of_node_put(node);
return ERR_PTR(-EINVAL);
}
return result;
}
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
client->adapter = adap; //client對應的adapter,建立client與adapter的對應關係
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name)); //client->name的賦值,它來源於dt中的slave設備的compatible屬性,後面設備與驅動的probe主要是通過判斷此設備成員和設備驅動的id_table成員是否一致
status = i2c_check_addr_validity(client->addr, client->flags);
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
/* Check for address business */
status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev; //client設備與adapter設備的關係
client->dev.bus = &i2c_bus_type; //client設備的總線類型是i2c_bus_type,client與driver的probe就是通過i2c_bus_type的probe實現的
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
i2c_dev_set_name(adap, client);
status = device_register(&client->dev); //設備註冊
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
out_err_silent:
kfree(client);
return NULL;
}
(2) 掃描 __i2c_board_list,對已經添加到此list的i2c設備進行註冊。在device tree應用之前,i2c從設備通過調用i2c_register_board_info將自己添加到 __i2c_board_list,目前此中方法已很少使用。
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info)) //設備註冊
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
下面是在kernel的mach目錄下找到的一個以此種方式添加到list中的例子。
static struct i2c_board_info i2c_devs[] __initdata = {
{ I2C_BOARD_INFO("wm8753", 0x1A), },
{ I2C_BOARD_INFO("24c08", 0x50), },
};
static void __init smdk6400_machine_init(void)
{
i2c_register_board_info(0, i2c_devs, ARRAY_SIZE(i2c_devs));
}
至此,在控制器驅動中不僅對i2c_adapter設備進行了註冊,還對i2c_client設備進行了註冊,並建立了adapter和client之間的關係,而且它們都是i2c_bus_type總線類型的設備。
此處要強調一下, i2c控制器是platform device,它的驅動註冊是通過platform_bus, platform_device和platform_driver之間的關係進行probe的,與i2c_bus_type不要混淆。
3.3 總線傳輸
在controller驅動中還對i2c另一重要結構體進行了賦值,它是發送和接收數據的實現函數。
pi2c->adap.algo = &vendor_i2c_algo;
static const struct i2c_algorithm vendor_i2c_algo = {
.master_xfer = vendor_i2c_master_xfer,
.functionality = vendor_i2c_func,
};
static int
vendor_i2c_master_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg *msgs, int num)
{
int im = 0, ret = 0;
struct vendor_i2c *pi2c = i2c_adap->algo_data;
clk_enable(pi2c->clk);
for (im = 0; ret >= 0 && im != num; im++) {
ret = vendor_i2c_handle_msg(i2c_adap, &msgs[im], im == num - 1);
}
clk_disable(pi2c->clk);
return (ret >= 0) ? im : -1;
}
static int
vendor_i2c_handle_msg(struct i2c_adapter *i2c_adap,
struct i2c_msg *pmsg, int is_last_msg)
{
int rc;
struct vendor_i2c *pi2c = i2c_adap->algo_data;
rc = vendor_i2c_send_target_addr(pi2c, pmsg);
if ((pmsg->flags & I2C_M_RD))
return vendor_i2c_readbytes(pi2c, pmsg->buf, pmsg->len);
else
return vendor_i2c_writebytes(pi2c, pmsg->buf, pmsg->len,
is_last_msg);
}
vendor_i2c_send_target_addr, vendor_i2c_readbytes, vendor_i2c_writebytes等依賴於具體廠商的實現。
3.4 用戶層接口
此外,i2c-dev.c中定義了與應用層交互接口的ops,用戶可以直接打開/dev/i2c-0或其它設備節點,使用文件描述符進行讀寫等操作,不必關心底層實現。
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
讀寫操作最終都是通過i2c-core提供的i2c_transfer函數實現的,i2c_transfer又是通過調用adap->algo->master_xfer實現的,此處不再進行展開。
4 設備驅動
i2c設備驅動,即i2c master對應的slave設備的驅動,在前面我們已經提到controller驅動中已經對slave設備i2c_client進行了註冊,此處是對i2c_client設備對應的驅動i2c_driver進行註冊。
此例中的i2c_driver使用傳感器的驅動bma2x2_driver,註冊代碼如下。
#define SENSOR_NAME "bma2x2"
static const struct i2c_device_id bma2x2_id[] = {
{ SENSOR_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, bma2x2_id);
static const struct of_device_id bma2x2_of_match[] = {
{ .compatible = "BOSCH,bma2x2", },
{ }
};
MODULE_DEVICE_TABLE(of, bma2x2_of_match);
static struct i2c_driver bma2x2_driver = {
.driver = {
.owner = THIS_MODULE,
.name = SENSOR_NAME,
.of_match_table = bma2x2_of_match,
.pm = &bma2x2_pm_ops
},
.id_table = bma2x2_id,
.probe = bma2x2_probe,
.remove = bma2x2_remove,
.shutdown = bma2x2_shutdown,
};
static int __init BMA2X2_init(void)
{
return i2c_add_driver(&bma2x2_driver);
}
static void __exit BMA2X2_exit(void)
{
i2c_del_driver(&bma2x2_driver);
}
module_init(BMA2X2_init);
module_exit(BMA2X2_exit);
驅動註冊i2c_add_driver函數的調用過程中會調用dev->bus->probe,其中dev是通過遍歷所有dev設備得到的。當i2c_client設備註冊完成後,client->dev.bus = &i2c_bus_type,因此,此處調用的bus->probe就是i2c_bus_type.probe函數指針指向的函數。
i2c_add_driver
i2c_register_driver(THIS_MODULE, driver)
res = driver_register(&driver->driver);
ret = bus_add_driver(drv);
driver_attach(drv);
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach
driver_probe_device(drv, dev);
ret = really_probe(dev, drv);
dev->bus->probe(dev);
i2c_bus_type在i2c-core.c中定義如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n");
status = of_clk_set_defaults(dev->of_node, false);
if (status < 0)
return status;
status = dev_pm_domain_attach(&client->dev, true);
if (status != -EPROBE_DEFER) {
status = driver->probe(client, i2c_match_id(driver->id_table,
client)); //driver->probe的調用依賴於i2c_match_id的結果
if (status)
dev_pm_domain_detach(&client->dev, true);
}
return status;
}
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
i2c_match_id的實現就是在比對client->name和id->name。client->name在controller驅動中註冊i2c_client設備的過程中有過賦值,它來自於dt中slave設備的compatible屬性。id_name在設備驅動中定義,本例中就是宏定義SENSOR_NAME,即 “bma2x2”,與dt中slave中的compatible屬性逗號後面的字符串一致。因此driver->probe即 bma2x2_probe會被調用。
5 總結
理解linux i2c框架就要理清如下關係:
1. bus, device, device_driver的驅動模型。
2. controller和slave設備的關係。
3. i2c_client與i2c_driver的關係。
4. core層的價值。