Linux i2c驱动框架分析 (二)

编程入门 行业动态 更新时间:2024-10-27 01:32:40

Linux i2c驱动框架分析 (一)
Linux i2c驱动框架分析 (二)
Linux i2c驱动框架分析 (三)
通用i2c设备驱动分析

i2c core

i2c核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,理解其中的主要函数非常关键,因为 I2C 总线驱动和设备驱动之间赖于i2c核心作为纽带。i2c核心中提供的主要函数如下。
(1)增加/删除i2c_adapter


int i2c_add_adapter(struct i2c_adapter *adapter);
void i2c_del_adapter(struct i2c_adapter *adap);

(2)增加/删除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)

(3)增加/删除i2c_client

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);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));void i2c_unregister_device(struct i2c_client *client)

(4)i2c传输、发送和接收

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(const struct i2c_client *client, const char *buf,int count);
int i2c_master_recv(const struct i2c_client *client, char *buf,int count);

i2c_transfer()函数用于进行i2c适配器和i2c设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。

i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用 i2c_algorithm的master_xfer()函数真正驱动硬件流程。

在i2c_core.c中定义了一个总线类型i2c_bus_type:

struct bus_type i2c_bus_type = {.name		= "i2c",.match		= i2c_device_match,.probe		= i2c_device_probe,.remove		= i2c_device_remove,.shutdown	= i2c_device_shutdown,
};

这个i2c总线结构体管理着i2c设备与i2c驱动的匹配,删除等操作,i2c总线会调用i2c_device_match函数看i2c设备和i2c驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用i2c驱动的probe函数。

i2c设备与驱动

Linux i2c设备驱动的模块加载与卸载

i2c设备驱动的模块加载函数通用的方法是进行通过I2C核心的 i2c_add_driver()函数添加 i2c_driver的工作,而在模块卸载函数中需要做相反的工作:通过i2c核心的i2c_del_driver()函数删除i2c_driver。

Linux i2c设备实例化的多种方法

这里只是介绍其中的几种方法。

通过devicetree声明I2C设备
在i2c控制器的节点里声明子节点,每个子节点代表一个i2c设备,如下图所示:

显式实例化i2c设备
通过i2c_new_device()或者i2c_new_probed_device()函数,创建并注册i2c_client。

从用户空间实例化i2c设备
Example:

//名字为eeprom,地址为0x50的i2c设备注册进内核,挂接在i2c控制器0上
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-0/new_device

驱动与设备的匹配

i2c驱动与设备的匹配是在注册驱动或设备时进行匹配的,下面以注册i2c驱动为例,分析匹配过程的细节。

注册i2c驱动:

#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{int res;/* Can't register until after driver model init */if (WARN_ON(!is_registered))return -EAGAIN;/* add the driver to the list of i2c drivers in the driver core */driver->driver.owner = owner;//设置所属总线为i2c_bus_typedriver->driver.bus = &i2c_bus_type;INIT_LIST_HEAD(&driver->clients);//注册驱动res = driver_register(&driver->driver);if (res)return res;pr_debug("driver [%s] registered\n", driver->driver.name);/* 探测驱动的address_list列表中的设备地址是否存在,存在则注册设备 */i2c_for_each_dev(driver, __process_new_driver);return 0;
}

下面重点分析driver_register函数:

int driver_register(struct device_driver *drv)
{int ret;struct device_driver *other;BUG_ON(!drv->bus->p);//进行一些相关判断if ((drv->bus->probe && drv->probe) ||(drv->bus->remove && drv->remove) ||(drv->bus->shutdown && drv->shutdown))printk(KERN_WARNING "Driver '%s' needs updating - please use ""bus_type methods\n", drv->name);//查询是否含有同名驱动other = driver_find(drv->name, drv->bus);if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}/* 把驱动插入到bus的驱动链表,这里涉及设备驱动模型相关知识*  在/sys/bus/i2c/drivers/目录下创建该驱动的sysfs目录,*  同时遍历bus的设备链表调用__driver_attach函数*/ret = bus_add_driver(drv);if (ret)return ret;//增添属性相关ret = driver_add_groups(drv, drv->groups);if (ret) {bus_remove_driver(drv);return ret;}kobject_uevent(&drv->p->kobj, KOBJ_ADD);return ret;
}

__driver_attach:

static int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;int ret;/* 调用bus里的match函数进行匹配,对于i2c bus对应函数i2c_device_match* 匹配成功返回1*/ret = driver_match_device(drv, dev);if (ret == 0) {/* no match */return 0;} else if (ret == -EPROBE_DEFER) {dev_dbg(dev, "Device match requests probe deferral\n");driver_deferred_probe_add(dev);} else if (ret < 0) {dev_dbg(dev, "Bus failed to match device: %d", ret);return ret;} /* ret > 0 means positive match */......//判断该设备是否已匹配了驱动if (!dev->driver)//会调到bus里的probe函数,对于i2c bus对应函数为i2c_device_probe(如果不存在则调用驱动的probe函数)driver_probe_device(drv, dev);device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;
}

分析i2c_device_match函数,看看具体怎么匹配的:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{struct i2c_client	*client = i2c_verify_client(dev);struct i2c_driver	*driver;if (!client)return 0;//设备树匹配 if (of_driver_match_device(dev, drv))return 1;/* Then ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;//id表匹配driver = to_i2c_driver(drv);/* match on an id table if there is one */if (driver->id_table) return i2c_match_id(driver->id_table, client) != NULL;return 0;
}

有多种匹配方式,这里分析一下id表匹配:

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) //比较namereturn id;id++;}return NULL;
}

如果匹配成功,driver_match_device函数返回1,之后会调到bus里的probe函数,对于i2c bus对应函数为i2c_device_probe:

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;......//调用驱动的probe函数status = driver->probe(client, i2c_match_id(driver->id_table, client));......
}

i2c驱动的探测功能

i2c驱动含有一个设备地址列表address_list:

struct i2c_driver {unsigned int class;......int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;
};

在注册i2c驱动时,通过适配器,探测address_list中对应地址的设备是否存在,如果存在则注册i2c设备。

在注册i2c驱动时,会遍历i2c bus上的适配器,调用__process_new_driver函数:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{int res;.....//可以得知i2c适配器也被当做一种设备插入到i2c bus的设备链表上i2c_for_each_dev(driver, __process_new_driver);return 0;
}

__process_new_driver:

static int __process_new_driver(struct device *dev, void *data)
{//判断是否为i2c适配器if (dev->type != &i2c_adapter_type)return 0;return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

i2c_do_add_adapter:

static int i2c_do_add_adapter(struct i2c_driver *driver,struct i2c_adapter *adap)
{/* 探测 */i2c_detect(adap, driver);/* 这是老式的探测方法,已不推荐使用 */if (driver->attach_adapter) {dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",driver->driver.name);dev_warn(&adap->dev,"Please use another way to instantiate your i2c_client\n");/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}return 0;
}

i2c_detect:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{const unsigned short *address_list;struct i2c_client *temp_client;int i, err = 0;int adap_id = i2c_adapter_id(adapter);address_list = driver->address_list;//如果驱动未提供detect函数和address_list,直接返回if (!driver->detect || !address_list)return 0;/* Warn that the adapter lost class based instantiation */if (adapter->class == I2C_CLASS_DEPRECATED) {dev_dbg(&adapter->dev,"This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. ""If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",driver->driver.name);return 0;}/* Stop here if the classes do not match */if (!(adapter->class & driver->class))return 0;/* 创建一个临时的i2c_client,用于帮助探测 */temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);if (!temp_client)return -ENOMEM;temp_client->adapter = adapter;for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {dev_dbg(&adapter->dev,"found normal entry for adapter %d, addr 0x%02x\n",adap_id, address_list[i]);temp_client->addr = address_list[i];//设置好临时的i2c_client后,探测设备地址err = i2c_detect_address(temp_client, driver);if (unlikely(err))break;}kfree(temp_client);return err;
}

i2c_detect_address:

static int i2c_detect_address(struct i2c_client *temp_client,struct i2c_driver *driver)
{struct i2c_board_info info;struct i2c_adapter *adapter = temp_client->adapter;int addr = temp_client->addr;int err;/* 检查addr是否有效 */err = i2c_check_7bit_addr_validity_strict(addr);if (err) {dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);return err;}/* 检查addr是否与挂在适配器上的设备的地址相同 */if (i2c_check_addr_busy(adapter, addr))return 0;/* 向地址为addr的设备发送信号,可是否有回应 */if (!i2c_default_probe(adapter, addr))return 0;/* Finally call the custom detection function */memset(&info, 0, sizeof(struct i2c_board_info));info.addr = addr;/* 程序执行到这里,说明存在地址为addr的设备,调用驱动的detect函数* 在驱动的detect函数中,要初始化i2c_board_info->type,即i2c设备的name*/err = driver->detect(temp_client, &info);if (err) {/* -ENODEV is returned if the detection fails. We catch ithere as this isn't an error. */return err == -ENODEV ? 0 : err;}/* 检查name是否初始化 */if (info.type[0] == '\0') {dev_err(&adapter->dev,"%s detection function provided no name for 0x%x\n",driver->driver.name, addr);} else {struct i2c_client *client;/* Detection sueeded, instantiate the device */if (adapter->class & I2C_CLASS_DEPRECATED)dev_warn(&adapter->dev,"This adapter will soon drop class based instantiation of devices. ""Please make sure client 0x%02x gets instantiated by other means. ""Check 'Documentation/i2c/instantiating-devices' for details.\n",info.addr);dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",info.type, info.addr);//增添新的i2c设备client = i2c_new_device(adapter, &info);if (client)//把i2c设备插入驱动的clients链表list_add_tail(&client->detected, &driver->clients);elsedev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",info.type, info.addr);}return 0;
}

更多推荐

框架,Linux,i2c

本文发布于:2023-05-20 23:54:36,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/156757.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:框架   Linux   i2c

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!