Linux I2C子系统【1】-I2C驱动分析-设备树创建platform_device
linux i2c子系统
系列文章目录
Linux I2C子系统【1】-I2C驱动分析-设备树创建platform_device
Linux I2C子系统【2】-i2c-adapter 创建过程
Linux I2C子系统【3】- I2C controller DMA数据交互过程
Linux I2C子系统【4】-i2c-driver设备驱动源码分析(ap3216c)
文章目录
前言
要解决以下问题
- 通过分析设备启动分析设备树,将所有设备以
struct platform_device
的方式加入进入了,platform_bus_type
的设备链表中,那么i2c_adapter
是什么概念i2c_client
又是什么设备概念呢?- 如何从把
platform_bus_type
的struct platform_device
转化成i2c_adapter
,i2c_client
的呢?
1. linux 内核驱动加载顺序
在u-boot 运行完之后,就开始进行kernel 加载。 那么这个kernel 是依据什么来加载的, 各个驱动的加载顺序是怎样的
- 从kernel start 到驱动加载的函数顺序
start_kernel()
---->rest_init()
-------> kernel_init()
---------> do_basic_setup()
------------>do_initcalls()
---------------> do_one_initcall(initcall_t fn)
- start_kernel() 到 do_basic_setup() 都是对系统及 cpu 等相关做初始化的动作
- 驱动的加载主要是在do_initcalls() 函数中实现, 关注该函数中如下部分
- 如何使用GCC的__ATTRIBUTE__((SECTION (SECT))) Linux内核启动就是以下相关段的描述函数执行的过程。
/*段中定义可在include/asm-generic/vmlinux.lds.h查找到输入段到输出段连接段相关定义*/
#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
*(.initcall##level##.init) \
*(.initcall##level##s.init) \
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
*(.initcallearly.init) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
VMLINUX_SYMBOL(__initcall_end) = .;
/*init/main.c*/
extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
static void __init do_initcall_level(int level)
{
initcall_t *fn;
strcpy(initcall_command_line, saved_command_line);
parse_args(initcall_level_names[level],
initcall_command_line, __start___param,
__stop___param - __start___param,
level, level,
&repair_env_string);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
这个for循环是按照level
来加载驱动, 系统将所有的驱动分成了多个level
, 对于同一级别的驱动,系统就按照编译顺序来逐个加载。调试发现 对于每一level的驱动,都是从__initcalln_start
开始, 加载完后,再加载下一level的这组驱动。
将 initcall_levels[level]
和 *fn
打印出来 , 发现它们的值都可以在 system.map
中找到。 其中*fn就是驱动中各个函数的地址。
2. i2c-adapter 的注册成platform_driver入口函数描述方法
在内核启动之初就会调用被subsys_initcall
修饰的函数。
在include/linux/init.h中声明了各种带有优先级的段
,subsuy_initcall
被放进.initcall3.init
段中
具体相关知识可查阅__attribute__之section详解
/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
* Keep main.c:initcall_level_names[] in sync.
*/
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn; \
LTO_REFERENCE_INITCALL(__initcall_##fn##id)
/*driver/i2c/busses/i2c-imx.c*/
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);
3. 将I2C设备树创建成platform_device
.IMX6U 的 I2C 适配器驱动是个标准的 platform
驱动,那么内核是如何通过设备树来创建i2c设备信息platform_device
的?
.在linux驱动里面,使用了大量的platform设备和驱动,我们知道platform_device
的创建主要有两种方式,
(1)在内核初始化时通过device_node
转换为platform_device
,这种是最新的实现方式,基于设备树,在内核初始化时将设备树中的节点转化为platform_device
;
(2)使用platform_device_register
注册platform_device
;
在本文里面将结合kernel-5.4的源码来介绍根据设备树来创建platform device
的过程
---> of_platform_populate
---> of_platform_bus_create
---> of_platform_device_create_pdata
---> of_device_alloc
---> of_irq_to_resource_table
---> of_irq_to_resource
---> irq_of_parse_and_map
---> of_irq_parse_one
---> irq_create_of_mapping
---> irq_create_fwspec_mapping
---> irq_domain_translate // 解析参数
---> s3c24xx_irq_xlate_of
---> irq_create_mapping // 创建hwirq到virq的映射
1. DT_MACHINE_START将machine_desc放进.arch.info.init段中,在arch/arm/kernel/vmlinux.lds.S中可以查到相对应的连接段。
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}
2. 用户可在文件中描述字节的硬件初始化信息
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
#endif
static const char *imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
NULL,
};
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
3. struct machine_desc 机器描述结构体
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
phys_addr_t dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
enum reboot_mode reboot_mode; /* default restart mode */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
void (*init_meminfo)(void);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
void (*restart)(enum reboot_mode, const char *);
};
5. linue内核启动解析设备树验证是否支持硬件板子
init/main.c(
asmlinkage __visible void __init start_kernel(void){
setup_arch(&command_line);
}
)
arch/arm/kernel/setup.c(
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);
unflatten_and_copy_device_tree();
{
unflatten_device_tree();/*展平设备树*/
}
}
)
arch/arm/kernel/devtree.c(
static const void * __init arch_get_next_mach(const char *const **match)
{
static const struct machine_desc *mdesc = __arch_info_begin;
const struct machine_desc *m = mdesc;
mdesc++;
*match = m->dt_compat;
return m;
}
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)(__atags_pointer=0x83000000)设备树地址
{/*开始设置平台设备书*/
of_flat_dt_match_machine(mdesc_best, arch_get_next_mach)
{/*通过机器匹配表迭代找到最适合机器的匹配兼容字符串在FDT*/
dt_root = of_get_flat_dt_root();
/*查找板子的描述字符串“"fsl,imx6ull"”
和设备树中的“compatible”比较*/
/*
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
}
*/
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
}
}
)
6. 根据1.1.2可知arch_initcall优先级为3,高于module_init第6等级,会在1.1.2中描述的循环创建platform_device
arch/arm/kernel/setup.c(
arch_initcall(customize_machine);
static int __init customize_machine(void)
{
/ *
*定制平台设备,或添加新设备
*在基于DT的机器上,我们回到填充
*机器,如果没有提供回调函数,
否则我们总是需要一个init_machine回调函数。
*/
/*这里的struct machine_desc machine_desc 是通过machine_desc = mdesc = setup_machine_fdt(__atags_pointer);
找到的板级信息
*/
if (machine_desc->init_machine){
printk("\r\n\r\nmachine_desc->init_machine();\r\n\rn");
machine_desc->init_machine();/*用户自定义初始化cpu并解析设备树生成platform_device*/
}
}
)
5.根据设备树创建platform_device
接下来我们从内核的入口函数start_kernel开始看一下,platform device是如何通过设备树来创建的
start_kernel
-----setup_arch(匹配设备树"compatible")
----unflatten_device_tree(); /*从二进制文件中展平设备树*/
-----rest_init
----kernel_init
----kernel_init_freeable
-----do_basic_setup
-----do_initcalls
----do_initcall_level
----do_one_initcall(开始解析设备树注册设备)
接下来,我们将继续跟一下关键函数在of_platform_populate
dirvers/of/platform.c (
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
)
arch/arm/mach-imx/mach-imx6ul.c(
static void __init imx6ul_init_machine(void)
{
printk("\r\n\r\nimx6ul_init_machine\r\n\r\n");
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}
)
drivers/of/platfrom/c(
/**
* of_platform_populate() -从设备树数据填充platform_devices
* @root:探查的第一级的父级,或者NULL表示树的根
* @matches:匹配表,NULL使用默认值
* @lookup: auxdata表,用于匹配id和platform_data与设备节点
* @parent: parent来钩子设备,NULL表示顶级
*
*类似于of_platform_bus_probe(),该函数遍历设备树
*并从节点创建设备。 它的不同之处在于它遵循现代
*要求所有设备节点具有'compatible'属性的约定,
*,它适用于创建作为根的子设备
* node (of_platform_bus_probe)将只创建根节点的子节点
*由@matches参数选择)。
*新的主板支持应该使用这个功能,而不是
* of_platform_bus_probe()。
*成功时返回0,失败时返回< 0。
*/
int of_platform_populate(struct device_node *root,const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,struct device *parent)
{
struct device_node *child;
root = root ? of_node_get(root) : of_find_node_by_path("/");//获取root设备节点
pr_info("root_name=%s\n\n",root->name);
for_each_child_of_node(root, child) {//遍历所有root节点下的子节点
pr_info("child_name=%s\n\n",child->name);
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
}
/*
/ # ls /sys/firmware/devicetree/base/
#address-cells interrupt-controller@00a01000
#size-cells key
aliases memory
alphaled model
backlight name
beep pxp_v4l2
chosen regulators
clocks reserved-memory
compatible soc
cpus sound
gpio_keys spi4
gpioled
root_name=
child_name=chosen
child_name=aliases
child_name=memory
child_name=cpus
child_name=interrupt-controller
child_name=clocks
child_name=soc
child_name=reserved-memory
child_name=backlight
child_name=pxp_v4l2
child_name=regulators
child_name=sound
child_name=spi4
child_name=alphaled
child_name=gpioled
child_name=beep
child_name=key
child_name=gpio_keys·
*/
/**
* of_platform_bus_create() -为节点及其子节点创建一个设备。
要实例化的总线的设备节点
* @matches:匹配总线节点表
* @lookup: auxdata表,用于匹配id和platform_data与设备节点
* @parent: parent表示新设备,NULL表示顶级设备。
* @strict:要求兼容属性
*为提供的device_node创建一个platform_device(可选)
递归地为所有子节点创建设备。
*/
static int of_platform_bus_create(struct device_node *bus,const struct of_device_id *matches,
const struct of_dev_auxdata *lookup, struct device *parent, bool strict)
{
struct platform_device *dev;
void *platform_data = NULL;
const char *bus_id = NULL;
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}
//创建platform data,同时会去创建platfrom device,以及相应的sys下的属性节点
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
printk("\r\n\r\nfrombus=: %s\r\n\r\n ", bus->full_name);
for_each_child_of_node(bus, child) {//遍历子节点,然后再去为子节点去创建相应的platform设备
printk("\r\n\r\ncreate child: %s\r\n\r\n ", child->full_name);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);//递归调用
}
}
/**
* of_platform_device_create_pdata—分配,初始化和注册一个of_device
* @np:创建设备的节点指针
* @bus_id:指定设备名
* @platform_data:用来填充platform_data指针的指针
* @parent: Linux设备型号的父设备。
*返回一个指针指向已创建的平台设备,如果没有,则返回NULL
*注册。 不可用的设备将不会被注册。
*/
static struct platform_device *of_platform_device_create_pdata(struct device_node *np, const char *bus_id,
void *platform_data,struct device *parent)
{
struct platform_device *dev;
dev = of_device_alloc(np, bus_id, parent);//分配一个platfrom device
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;//初始化bus
dev->dev.platform_data = platform_data;//初始化platform data
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) { //这里是最关键的,将设备挂在对应的bus总线管理的设备链表上,同时会试着去probe对应的driver
}
}
)
drivers/of/platform.c(
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
/* count the io and irq resources */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);
/* Populate the resource table */
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %s\n",
np->name);
}
dev->dev.of_node = of_node_get(np);
dev->dev.fwnode = &np->fwnode;
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
)
driver/of/dynamic.c (
struct device_node *of_node_get(struct device_node *node)
{
if (node)
kobject_get(&node->kobj);
return node;
}
)
driver/of/address.c(
/*of_address_to_resource是从设备树里面提取资源值,但是本质上就是将 reg 属性值,然后将其转换为 resource 结构体类型,函数原型如下所示:*/
int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
{
const __be32 *addrp;
u64 size;
unsigned int flags;
const char *name = NULL;
addrp = of_get_address(dev, index, &size, &flags);/*//查找设备的地址,返回值为指向设备地址属性的指针,以及地址空间大小和标志*/
if (addrp == NULL)
return -EINVAL;
/* Get optional "reg-names" property to add a name to a resource */
of_property_read_string_index(dev, "reg-names", index, &name);
return __of_address_to_resource(dev, addrp, size, flags, name, r);
/*[地址映射分析](http://blog.chinaunix.net/uid-26675482-id-3563606.html)*/
/*接下来详细的分析,暂时到这里,太深了,出不来了*/
}
)
platform.c(
struct platform_device *of_find_device_by_node(struct device_node *np)
{
struct device *dev;
dev = bus_find_device(&platform_bus_type, NULL, np, of_dev_node_match);
return dev ? to_platform_device(dev) : NULL;
}
)
drivers/of/device.c(
int of_device_add(struct platform_device *ofdev)
{
/* name和id必须设置,这样平台总线不会得到 *对匹配感到困惑 */
ofdev->name = dev_name(&ofdev->dev);
ofdev->id = -1;;
return device_add(&ofdev->dev);//添加设备,将设备加入到Linux设备模型,它的内部将找到它的bus,然后让它的bus给它试图找到它的driver
}
)
device_add
具体分析请查看
linux设备模型六(device细节)
Linux platform子系统【1】-PLATFORM(平台)总线详解
3. i2c platform_driver主机驱动匹配platform_device 的过程
.IMX6U 的 I2C 适配器驱动是个标准的 platform 驱动,由此可以看出,虽然 I2C 总线为别的设备提供了一种总线驱动框架,但是 I2C 适配器却是 platform驱动。
/*driver/i2c/busses/i2c-imx.c*/
static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = i2c_imx_dt_ids,
.pm = IMX_I2C_PM,
},
.id_table = imx_i2c_devtype,
};
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
drivers/base/platform.c(
int __platform_driver_register(struct platform_driver *drv,struct module *owner)
{
drivers/base/driver.c(
driver_register(&drv->driver);
drivers/base/bus.c(
int bus_add_driver(struct device_driver *drv){
/*将驱动注册到i2cbus的klist_drivers中*/
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
driver/base/dd.c(
error = driver_attach(drv){/*在bus上匹配compatible的设备*/
drivers/base/bus.c(/*在bus->p->klist_devices匹配compatible的设备*/
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach){
klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data){/*循环匹配设备*/
driver/base/dd.c{
static int __driver_attach(struct device *dev, void *data){
driver_match_device(drv, dev);/*id_table/compatible匹配*/
{
static int platform_match(struct device *dev, struct device_driver *drv)
{
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
/*const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)*/
return of_match_device(drv->of_match_table, dev) != NULL;(
{
return of_match_node(matches, dev->of_node);
}
)
}
}
}
driver_probe_device(drv, dev){
drivers/base/platform.c(
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
ret = drv->probe(dev){
/*正式进入static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe,*/
/*正式开始制作i2c-adapter*/
}
}
)
}
}
}
}
klist_iter_exit(&i);
}
)
}
)
}
}
)
)
}
)
static const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;
if (!matches)
return NULL;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}
return best_match;
}
/*设备匹配驱动
drv->bus=platform
of_prop_next_string=fsl,imx6ul-i2c(device),compat=fsl,imx1-i2c(driver)
of_prop_next_string=fsl,imx21-i2c(device),compat=fsl,imx1-i2c(driver)
of_prop_next_string=fsl,imx6ul-i2c(device),compat=fsl,imx21-i2c(driver)
of_prop_next_string=fsl,imx21-i2c(device),compat=fsl,imx21-i2c(driver)
of_prop_next_string=fsl,imx6ul-i2c(device),compat=fsl,vf610-i2c(driver)
of_prop_next_string=fsl,imx21-i2c(device),compat=fsl,vf610-i2c(driver)
*/
/*
i2c1: i2c@021a0000 {(struct device_node *device)
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
};
(compat)
static const struct of_device_id i2c_imx_dt_ids[] = {
{.compatible = "fsl,imx1-i2c",.data = &imx1_i2c_hwdata,},
{.compatible = "fsl,imx21-i2c",.data = &imx21_i2c_hwdata,},
{.compatible = "fsl,vf610-i2c",.data = &vf610_i2c_hwdata,},
};
*/
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
pr_info("of_prop_next_string=%s,compat=%s\n\n",cp,compat);
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
/* Matching type is better than matching name */
if (type && type[0]) {
if (!device->type || of_node_cmp(type, device->type))
return 0;
score += 2;
}
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!device->name || of_node_cmp(name, device->name))
return 0;
score++;
}
return score;
}
1.1.3 i2c-adapter 与 i2c-client 创建过程
主机驱动会根据1.1.3中的dev->of_node来创建 i2c_client。
i2c_client 链表: i2c_bus_type->p->klist_devices
i2c_driver 链表:i2c_bus_type->p->klist_drivers
硬件i2c控制器硬件初始化完成,注册 adapter时,依据1.1.3中的dev->of_node 中信息生成i2c_client,并挂接在klist_devices链表上
- 在注册i2c_driver时,即:i2c_add_driver(struct i2c_driver*),会做两件事情:
(1)将 i2c_driver->drv 挂接到此链表
(2)遍历 klist_devices 链表,查找与 i2c_driver->id_table->name 相同的 i2c_client->name
总结
更多推荐
所有评论(0)