系列文章目录

Linux I2C子系统【1】-I2C驱动分析-设备树创建platform_device
Linux I2C子系统【2】-i2c-adapter 创建过程
Linux I2C子系统【3】- I2C controller DMA数据交互过程
Linux I2C子系统【4】-i2c-driver设备驱动源码分析(ap3216c)


前言

要解决以下问题

  1. 通过分析设备启动分析设备树,将所有设备以 struct platform_device 的方式加入进入了,platform_bus_type的设备链表中,那么
    • i2c_adapter是什么概念
    • i2c_client又是什么设备概念呢?
    • 如何从把platform_bus_typestruct platform_device转化成 i2c_adapteri2c_client 的呢?

1. linux 内核驱动加载顺序

  在u-boot 运行完之后,就开始进行kernel 加载。 那么这个kernel 是依据什么来加载的, 各个驱动的加载顺序是怎样的

  1. 从kernel start 到驱动加载的函数顺序

start_kernel()
---->rest_init()
-------> kernel_init()
---------> do_basic_setup()
------------>do_initcalls()
---------------> do_one_initcall(initcall_t fn)

  1. start_kernel() 到 do_basic_setup() 都是对系统及 cpu 等相关做初始化的动作
  2. 驱动的加载主要是在do_initcalls() 函数中实现, 关注该函数中如下部分
  3. 如何使用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链表上

  1. 在注册i2c_driver时,即:i2c_add_driver(struct i2c_driver*),会做两件事情:
    (1)将 i2c_driver->drv 挂接到此链表
    (2)遍历 klist_devices 链表,查找与 i2c_driver->id_table->name 相同的 i2c_client->name

总结

Logo

更多推荐