嵌入式软件开发之------浅析linux驱动模型(五)I2C驱动

Linux代码版本:linux3.0
开发板环境: tiny4412
导读:i2c控制器作为platform_device挂接在platform总线上,在《嵌入式软件开发之------浅谈linux驱动模型(四)device和driver》以i2c控制器为例,分析了

s3c_device_i2c1和s3c24xx_i2c_driver的注册过程,总结下来就是下图:

每个i2c控制器都是一个i2c 总线,i2c控制器即挂接在platform总线上,也为i2c提供总线。platform总线有platform_device和platform_driver,相应的i2c总线也有对应的device和driver,只不过是i2c_client和i2c_driver.

一、i2c_adapter的注册及 i2c_client 的实例化

下面看i2c_client,有没有觉得和platform_device很像?都是封装了devcie后添加了部分各自特点的成员:

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;
};

unsigned short flags;

标志,用 I2C_CLIENT_TEN 表示 10 bit 地址,用 I2C_CLIENT_PEC 表示SMBus的错误数据检测

unsigned short addr;

器件的地址

char name[I2C_NAME_SIZE];

设备的名字,在 new_device 属性文件中,低7位代表地址

struct i2c_adapter *adapter; /* the adapter we sit on */
虽然是挂接在I2C总线上的,client->dev.bus = &i2c_bus_type,可是 总要 指向所在的i2c控制器吗

 

struct i2c_driver *driver; /* and our access routines */

 

对应的驱动

struct device dev; /* the device structure */

封装的device结构体

int irq; /* irq issued by device */

用来表示此设备产生的IRQ

struct list_head detected;

用于插入 i2c_driver.clients 的节点,在遍历 i2c_drive r探测到实际挂接在 i2c_adapter 但又未注册

的 i2c 设备时,实例化成 i2c_client 后以此成员插入 i2c_driver.clients。

看到i2c_client就不得不看i2c设备的注册,这个时候可能有人会说,大概和platform设备注册差不多吧,确实 差不多,可还是有点区别下面就以mma7660为例,

static struct i2c_board_info i2c_devs3[] __initdata = {
	{
		I2C_BOARD_INFO("mma7660", 0x4c),
		.platform_data = &mma7660_pdata,
	},
};

展开

static struct i2c_board_info i2c_devs3[] __initdata = {
	{
        .type = "mma7660", 
        .addr = 0x4c, 
		.platform_data = &mma7660_pdata,
	},
};

有没有发现什么不对?不是说i2c设备的结构体说 i2c_client ?咋变成了 i2c_board_info ?platform_device就是对应的啊。这里面肯定有蹊跷,i2c_board_info 最后肯定还得转成成 i2c_client,是的,这既是i2c设备的实例化,从定义的 i2c_board_info 组装成 i2c_client,这个过程将在代码中分析。接下来看一下 platform_device通过 platform_device_register来注册 ,那么 i2c_board_info 设备是通过i2c_register_board_info。

先看下 i2c_board_info 结构体

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;
	int		irq;
};

再看一个结构体,下面会用到

struct i2c_devinfo {
	struct list_head	list;
	int			busnum;
	struct i2c_board_info	board_info;
};

下面再看 i2c_register_board_info 实例

i2c_register_board_info(3, i2c_devs3, ARRAY_SIZE(i2c_devs3));
{
	int status;

	down_write(&__i2c_board_lock);

	/* dynamic bus numbers will be assigned after the last static one */
    /*__i2c_first_dynamic_bus_num总是要比最大的bus号大1,后面会用到*/
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

	for (status = 0; len; len--, info++) {
		struct i2c_devinfo	*devinfo;

		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}
        /*将i2c_board_info的信息赋值给 devinfo ,然后将devinfo插入 __i2c_board_list ,所有的i2c设备都会插入
        __i2c_board_list,所以一定要记住__i2c_board_list,
        devinfo->busnum  = 3
        devinfo->board_info = &i2c_devs3 */
        
		devinfo->busnum = busnum;
		devinfo->board_info = *info;
		list_add_tail(&devinfo->list, &__i2c_board_list);
	}

	up_write(&__i2c_board_lock);

	return status;
}

上面的知识将在下面的代码中用到,下面接着 分析 s3c24xx_i2c_probe,由于是在分析驱动框架,不是分析BSP,

硬件设备千变万化,看芯片手册写或者调试驱动是一个驱动工程师的基本功,所以对于硬件相关的设置将省略。

下面接着分析经过platform总线匹配后调用的 s3c24xx_i2c_probe

 
static int s3c24xx_i2c_probe(&s3c_device_i2c3)
{
	struct s3c24xx_i2c *i2c;
	struct s3c2410_platform_i2c *pdata;
	struct resource *res;
	int ret;

	......
    
    /*赋值 i2c->adap.name = "s3c2410-i2c",后面会用到*/
	strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
    /*i2c->adap进行复制,又看到了 THIS_MODULE 只有当此驱动编译成module的时候指向此模块
    ,如果没有编译成module则为NULL,绝大多数owner都是 THIS_MODULE */
	i2c->adap.owner   = THIS_MODULE;
    /*i2c->adap.algo,对应实际硬件的通信方法,控制硬件产生读写时序的函数,adapter是对硬件控制器的抽象,
    实际产生硬件通信时序的s3c24xx_i2c_algorithm*/
	i2c->adap.algo    = &s3c24xx_i2c_algorithm;
    /*通信失败重新尝试的次数*/
	i2c->adap.retries = 2;
    /*此adapter支持的硬件类型,公共也就是三种 I2C_CLASS_HWMON、I2C_CLASS_SPD 和 I2C_CLASS_DDC 
    相应的,i2c_client或者i2c_driver肯定也会有相应的class来和 i2c->adap.class 匹配 */
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	......
    
	/* setup info block for the i2c core */

	i2c->adap.algo_data = i2c;
    /*i2c->adap.dev.parent 指向 s3c_device_i2c1 ,
    意味着i2c->adap对应的目录会在/sys/devices/platform/s3c2440-i2c.3下*/
	i2c->adap.dev.parent = &pdev->dev;

	......
    
	/* Note, previous versions of the driver used i2c_add_adapter()
	 * to add the bus at any number. We now pass the bus number via
	 * the platform data, so if unset it will now default to always
	 * being bus 0.
	 */
    /*此adapter所对应的i2c总线号,这里是通过 paltform_device的私有结构体传递,实际还有另一个代表
    /sys/devices/platform/s3c2440-i2c.3
    i2c总线号的,还记得 s3c_device_i2c3.id = 3吗*/
	i2c->adap.nr = pdata->bus_num;
    /*注册adapter*/
	ret = i2c_add_numbered_adapter(&i2c->adap);
        {
        	int	id;
        	int	status;

        	if (adap->nr & ~MAX_ID_MASK)
        		return -EINVAL;
        /*下面涉及idr机制,简单的说idr机制就是将一个整数id和特定指针映射起来,然后可以通过id号
            找到对应的指针*/
        retry:
            /*为idr分配内存,通过idr获取id之前需要先分配内存*/
        	if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        		return -ENOMEM;

        	mutex_lock(&core_lock);
        	/* "above" here means "above or equal to", sigh;
        	 * we need the "equal to" result to force the result
        	 */
        	 /*分配id号并和adap指针关联,对于i2c adapter分配的id就是bus号,所以下面会有
            id和adap->nr的判断*/
        	status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
        	if (status == 0 && id != adap->nr) {
        		status = -EBUSY;
        		idr_remove(&i2c_adapter_idr, id);
        	}
        	mutex_unlock(&core_lock);
        	if (status == -EAGAIN)
        		goto retry;

        	if (status == 0)
                /*开始注册adapter*/
        		status = i2c_register_adapter(adap);
                        {
                        	int res = 0;

                        	/* Can't register until after driver model init */
                            /*i2c被初始化过才能注册adapter,显然注册 i2c_bus_type 就已经初始化过*/
                        	if (unlikely(WARN_ON(!i2c_bus_type.p))) {
                        		res = -EAGAIN;
                        		goto out_list;
                        	}

                        	/* Sanity checks */
                            /*前面已经初始化 adap->name[0] = "s3c2410-i2c" */
                        	if (unlikely(adap->name[0] == '\0')) {
                        		pr_err("i2c-core: Attempt to register an adapter with "
                        		       "no name!\n");
                        		return -EINVAL;
                        	}
                            /*上面已经初始化过 i2c->adap.algo    = &s3c24xx_i2c_algorithm */
                        	if (unlikely(!adap->algo)) {
                        		pr_err("i2c-core: Attempt to register adapter '%s' with "
                        		       "no algo!\n", adap->name);
                        		return -EINVAL;
                        	}

                        	rt_mutex_init(&adap->bus_lock);
                        	mutex_init(&adap->userspace_clients_lock);
                        	INIT_LIST_HEAD(&adap->userspace_clients);

                        	/* Set default timeout to 1 second if not already set */
                            /*上面没有初始化超时时间,所以默认设置为1s*/
                        	if (adap->timeout == 0)
                        		adap->timeout = HZ;
                            /*赋值 i2c->adap->dev->kobj->name = "i2c-3",意味着将来创建目录的名字为 i2c-3 */
                        	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
                            /*adapter也是一个device,类型为 i2c_adapter_type ,所以指向i2c_bus_type,其本身提供具体的i2c总线*/
                        	adap->dev.bus = &i2c_bus_type;
                        	adap->dev.type = &i2c_adapter_type;
                            /*下面即使device的注册过程*/
                        	res = device_register(&adap->dev);
                                {
                                	device_initialize(dev);
                                    {
                                        /*adapter也是一个device,所以其 kobject 属于 devices_kset,类型为 device_ktype*/
                                    	dev->kobj.kset = devices_kset;
                                    	kobject_init(&dev->kobj, &device_ktype);
                                    	INIT_LIST_HEAD(&dev->dma_pools);
                                    	mutex_init(&dev->mutex);
                                    	lockdep_set_novalidate_class(&dev->mutex);
                                    	spin_lock_init(&dev->devres_lock);
                                    	INIT_LIST_HEAD(&dev->devres_head);
                                    	device_pm_init(dev);
                                    	set_dev_node(dev, -1);
                                    }
                                	return device_add(dev);
                                            {
                                            	struct device *parent = NULL;
                                            	struct class_interface *class_intf;
                                            	int error = -EINVAL;

                                            	dev = get_device(dev);
                                            	if (!dev)
                                            		goto done;

                                            	if (!dev->p) {
                                            		error = device_private_init(dev);
                                                            {
                                                            	dev->p = kzalloc(sizeof(*dev->p), GFP_KERNEL);
                                                            	if (!dev->p)
                                                            		return -ENOMEM;
                                                            	dev->p->device = dev;
                                                            	klist_init(&dev->p->klist_children, klist_children_get,
                                                            		   klist_children_put);
                                                            	return 0;
                                                            }
                                            		if (error)
                                            			goto done;
                                            	}

                                            	/*
                                            	 * for statically allocated devices, which should all be converted
                                            	 * some day, we need to initialize the name. We prevent reading back
                                            	 * the name, and force the use of dev_name()
                                            	 */
                                            	 /* i2c->adap->dev->init_name未被赋值,也就是为NULL*/
                                            	if (dev->init_name) {
                                            		dev_set_name(dev, "%s", dev->init_name);
                                            		dev->init_name = NULL;
                                            	}
                                                /* i2c->adap->dev->kobj->name = "i2c-3" ,所以此条件不会被满足 */
                                            	if (!dev_name(dev)) {
                                            		error = -EINVAL;
                                            		goto name_error;
                                            	}

                                            	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
                                                /*前面初始化 i2c->adap.dev.parent = &pdev->dev */
                                            	parent = get_device(dev->parent);
                                                 /*device层面的parent已经建立,setup_parent要建立kobject 层面 的parent关系*/
                                            	setup_parent(dev, parent);

                                            	/* use parent numa_node */
                                                /*NUMA架构一般应用于大型多CPU的设备,嵌入式单个多核CPU使用SMP架构*/
                                            	if (parent)
                                            		set_dev_node(dev, dev_to_node(parent));

                                            	/* first, register with generic layer. */
                                            	/* we require the name to be set before, and pass NULL */
                                                /*又看见kobject_add,在sys/devices/platform/s3c2440-i2c.3创建i2c-3目录*/
                                            	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
                                            	if (error)
                                            		goto Error;

                                            	/* notify platform of device entry */
                                                /*暂时不知道具体用途,代码中极少给platform_notify赋值*/
                                            	if (platform_notify)
                                            		platform_notify(dev);
                                                /*在sys/devices/platform/s3c2440-i2c.1/i2c-3创建uevent文件*/
                                            	error = device_create_file(dev, &uevent_attr);
                                            	if (error)
                                            		goto attrError;
                                                /*此时 i2c->adap->dev 还没有设备号,所以执行不到,等到 device_creat 的时候再详细分析*/
                                            	if (MAJOR(dev->devt)) {
                                            		error = device_create_file(dev, &devt_attr);
                                            		if (error)
                                            			goto ueventattrError;

                                            		error = device_create_sys_dev_entry(dev);
                                            		if (error)
                                            			goto devtattrError;

                                            		devtmpfs_create_node(dev);
                                            	}
                                                
                                            	error = device_add_class_symlinks(dev);
                                                        {
                                                        	int error;
                                                            /*i2c->adap->dev->class未赋值,也就是为NULL,在此返回*/
                                                        	if (!dev->class)
                                                        		return 0;
                                                          
                                                        	error = sysfs_create_link(&dev->kobj,
                                                        				  &dev->class->p->subsys.kobj,
                                                        				  "subsystem");
                                                        	if (error)
                                                        		goto out;

                                                        	if (dev->parent && device_is_not_partition(dev)) {
                                                        		error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
                                                        					  "device");
                                                        		if (error)
                                                        			goto out_subsys;
                                                        	}

                                                            #ifdef CONFIG_BLOCK
                                                        	/* /sys/block has directories and does not need symlinks */
                                                        	if (sysfs_deprecated && dev->class == &block_class)
                                                        		return 0;
                                                            #endif

                                                        	/* link in the class directory pointing to the device */
                                                        	error = sysfs_create_link(&dev->class->p->subsys.kobj,
                                                        				  &dev->kobj, dev_name(dev));
                                                        	if (error)
                                                        		goto out_device;

                                                        	return 0;

                                                        out_device:
                                                        	sysfs_remove_link(&dev->kobj, "device");

                                                        out_subsys:
                                                        	sysfs_remove_link(&dev->kobj, "subsystem");
                                                        out:
                                                        	return error;
                                                        }
                                            	if (error)
                                            		goto SymlinkError;
                                                /*为 i2c->adap 创建属性文件*/
                                            	error = device_add_attrs(dev);
                                                        {
                                                        	struct class *class = dev->class;
                                                        	const struct device_type *type = dev->type;
                                                        	int error;
                                                            /*i2c->adap->dev->class未赋值,也就是为NULL,在此返回*/
                                                        	if (class) {
                                                        		error = device_add_attributes(dev, class->dev_attrs);
                                                                        {
                                                                        	int error = 0;
                                                                        	int i;

                                                                        	if (attrs) {
                                                                        		for (i = 0; attr_name(attrs[i]); i++) {
                                                                        			error = device_create_file(dev, &attrs[i]);
                                                                        			if (error)
                                                                        				break;
                                                                        		}
                                                                        		if (error)
                                                                        			while (--i >= 0)
                                                                        				device_remove_file(dev, &attrs[i]);
                                                                        	}
                                                                        	return error;
                                                                        }
                                                        		if (error)
                                                        			return error;
                                                        		error = device_add_bin_attributes(dev, class->dev_bin_attrs);
                                                        		if (error)
                                                        			goto err_remove_class_attrs;
                                                        	}
                                                            /*前面赋值 i2c->adap->dev->type = &i2c_adapter_type,其中
                                                            i2c_adapter_type->groups = i2c_adapter_attr_groups,
                                                            i2c_adapter_attr_groups = &i2c_adapter_attr_group,
                                                            i2c_adapter_attr_group->attr = i2c_adapter_attrs,
                                                            i2c_adapter_attrs[] = {
                                                            	&dev_attr_name.attr,
                                                            	&dev_attr_new_device.attr,
                                                            	&dev_attr_delete_device.attr,
                                                            	NULL
                                                            };

                                                            所以最终将在/sys/devices/platform/s3c2440-i2c.3/i2c-3下创建 name、new_device和delete_device的属性文件*/
                                                        	if (type) {
                                                        		error = device_add_groups(dev, type->groups);
                                                                        {
                                                                        	int error = 0;
                                                                        	int i;

                                                                        	if (groups) {
                                                                        		for (i = 0; groups[i]; i++) {
                                                                        			error = sysfs_create_group(&dev->kobj, groups[i]);
                                                                        			if (error) {
                                                                        				while (--i >= 0)
                                                                        					sysfs_remove_group(&dev->kobj,
                                                                        							   groups[i]);
                                                                        				break;
                                                                        			}
                                                                        		}
                                                                        	}
                                                                        	return error;
                                                                        }
                                                        		if (error)
                                                        			goto err_remove_class_bin_attrs;
                                                        	}
                                                            /*i2c->adap->dev->groups也是未初始化的*/
                                                        	error = device_add_groups(dev, dev->groups);
                                                        	if (error)
                                                        		goto err_remove_type_groups;

                                                        	return 0;

                                                         err_remove_type_groups:
                                                        	if (type)
                                                        		device_remove_groups(dev, type->groups);
                                                         err_remove_class_bin_attrs:
                                                        	if (class)
                                                        		device_remove_bin_attributes(dev, class->dev_bin_attrs);
                                                         err_remove_class_attrs:
                                                        	if (class)
                                                        		device_remove_attributes(dev, class->dev_attrs);

                                                        	return error;
                                                        }
                                            	if (error)
                                            		goto AttrsError; 
                                                /*i2c->adap 添加到 i2c_bus_type*/
                                            	error = bus_add_device(dev);
                                                        {
                                                        	struct bus_type *bus = bus_get(dev->bus);
                                                        	int error = 0;

                                                        	if (bus) {
                                                        		pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
                                                        		error = device_add_attrs(bus, dev);
                                                                        {
                                                                        	int error = 0;
                                                                        	int i;
                                                                            /*由于i2c_bus_type->dev_attrs = NULL,
                                                                            所以不会创建任何属性文件*/
                                                                        	if (!bus->dev_attrs)
                                                                        		return 0;

                                                                        	for (i = 0; attr_name(bus->dev_attrs[i]); i++) {
                                                                        		error = device_create_file(dev, &bus->dev_attrs[i]);
                                                                        		if (error) {
                                                                        			while (--i >= 0)
                                                                        				device_remove_file(dev, &bus->dev_attrs[i]);
                                                                        			break;
                                                                        		}
                                                                        	}
                                                                        	return error;
                                                                        }
                                                        		if (error)
                                                        			goto out_put;
                                                                /*在注册i2c_bus_type的时候
                                                                 priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj);
                                                                 下面就是要在/sys/bus/i2c/devices创建指向 /sys/devices/platform/s3c2440-i2c.3/i2c-3
                                                                 的链接,名字也为i2c-3*/
                                                        		error = sysfs_create_link(&bus->p->devices_kset->kobj,
                                                        						&dev->kobj, dev_name(dev));
                                                        		if (error)
                                                        			goto out_id;
                                                                /*在/sys/devices/platform/s3c2440-i2c.3/i2c-3创建指向/sys/bus/i2c/的链接,
                                                                名字为subsystem*/
                                                        		error = sysfs_create_link(&dev->kobj,
                                                        				&dev->bus->p->subsys.kobj, "subsystem");
                                                        		if (error)
                                                        			goto out_subsys;
                                                                /*将 i2c->adap 加入到i2c_bus_type->p->klist_devices链表中*/
                                                        		klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
                                                        	}
                                                        	return 0;

                                                        out_subsys:
                                                        	sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
                                                        out_id:
                                                        	device_remove_attrs(bus, dev);
                                                        out_put:
                                                        	bus_put(dev->bus);
                                                        	return error;
                                                        }
                                            	if (error)
                                            		goto BusError;
                                                /*电源管理相关,后面分析的电源管理机制的时候再详细分析*/
                                            	error = dpm_sysfs_add(dev);
                                            	if (error)
                                            		goto DPMError;
                                            	device_pm_add(dev);

                                            	/* Notify clients of device addition.  This call must come
                                            	 * after dpm_sysf_add() and before kobject_uevent().
                                            	 */
                                            	if (dev->bus)
                                            		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                            					     BUS_NOTIFY_ADD_DEVICE, dev);

                                            	kobject_uevent(&dev->kobj, KOBJ_ADD);
                                            	bus_probe_device(dev);
                                                {
                                                	struct bus_type *bus = dev->bus;
                                                	int ret;
                                                    /* i2c_bus_type 注册的时候初始化 i2c_bus_type->p->drivers_autoprobe = 1
                                                    /sys/bus/i2c 下的drivers_autoprobe,drivers_autoprobe被置1
                                                    则自动进行驱动匹配*/
                                                	if (bus && bus->p->drivers_autoprobe) {
                                                		ret = device_attach(dev);
                                                            {
                                                            	int ret = 0;

                                                            	device_lock(dev);
                                                                /*现在只注册 i2c->adap  , i2c->adap  ->dev->driver = NULL*/
                                                            	if (dev->driver) {
                                                            		if (klist_node_attached(&dev->p->knode_driver)) {
                                                            			ret = 1;
                                                            			goto out_unlock;
                                                            		}
                                                            		ret = device_bind_driver(dev);
                                                                        {
                                                                        	int ret;

                                                                        	ret = driver_sysfs_add(dev);
                                                                        	if (!ret)
                                                                        		driver_bound(dev);
                                                                        	return ret;
                                                                        }
                                                            		if (ret == 0)
                                                            			ret = 1;
                                                            		else {
                                                            			dev->driver = NULL;
                                                            			ret = 0;
                                                            		}
                                                            	} 
                                                                else {
                                                            		pm_runtime_get_noresume(dev);
                                                                    /*通过 __device_attach 函数逐个匹配 i2c_bus_type->p->klist_drivers成员
                                                                     i2c->adap 只是作为一个device注册,没有所谓的驱动,类似 platform_bus 一样,*/
                                                            		ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);

                                                            		pm_runtime_put_sync(dev);
                                                            	}
                                                            out_unlock:
                                                            	device_unlock(dev);
                                                            	return ret;
                                                            }
                                                		WARN_ON(ret < 0);
                                                	}
                                                }
                                                /*将 i2c->adap 加入 s3c_device_i2c3 的子设备链表*/
                                            	if (parent)
                                            		klist_add_tail(&dev->p->knode_parent,
                                            			       &parent->p->klist_children);
                                                
                                                /*将 i2c->adap 加入到class的设备链表中,目前 i2c->adap->dev->class = NULL */
                                            	if (dev->class) {
                                            		mutex_lock(&dev->class->p->class_mutex);
                                            		/* tie the class to the device */
                                            		klist_add_tail(&dev->knode_class,
                                            			       &dev->class->p->klist_devices);

                                            		/* notify any interfaces that the device is here */
                                                    /*暂时还没弄清class_interface的具体用途*/
                                            		list_for_each_entry(class_intf,
                                            				    &dev->class->p->class_interfaces, node)
                                            			if (class_intf->add_dev)
                                            				class_intf->add_dev(dev, class_intf);
                                            		mutex_unlock(&dev->class->p->class_mutex);
                                            	}
                                            done:
                                            	put_device(dev);
                                            	return error;
                                             DPMError:
                                            	bus_remove_device(dev);
                                             BusError:
                                            	device_remove_attrs(dev);
                                             AttrsError:
                                            	device_remove_class_symlinks(dev);
                                             SymlinkError:
                                            	if (MAJOR(dev->devt))
                                            		devtmpfs_delete_node(dev);
                                            	if (MAJOR(dev->devt))
                                            		device_remove_sys_dev_entry(dev);
                                             devtattrError:
                                            	if (MAJOR(dev->devt))
                                            		device_remove_file(dev, &devt_attr);
                                             ueventattrError:
                                            	device_remove_file(dev, &uevent_attr);
                                             attrError:
                                            	kobject_uevent(&dev->kobj, KOBJ_REMOVE);
                                            	kobject_del(&dev->kobj);
                                             Error:
                                            	cleanup_device_parent(dev);
                                            	if (parent)
                                            		put_device(parent);
                                            name_error:
                                            	kfree(dev->p);
                                            	dev->p = NULL;
                                            	goto done;
                                            }
                                }
                        	if (res)
                        		goto out_list;

                        	dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

                #ifdef CONFIG_I2C_COMPAT
                            /* i2c_adapter_compat_class = class_compat_register("i2c-adapter"); 
                                在 sys/calss/i2c-adapter 目录下创建链接*/
                        	res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,adap->dev.parent);
                                {
                                	int error;
                                    /* 在 sys/calss/i2c-adapter 目录下创建链接 指向 sys/devices/platform/s3c2440-i2c.3/i2c-3 的链接,名字仍为 i2c-3 */
                                	error = sysfs_create_link(cls->kobj, &dev->kobj, dev_name(dev));
                                            {
                                            	return sysfs_do_create_link(kobj, target, name, 1);
                                            }
                                	if (error)
                                		return error;

                                	/*
                                	 * Optionally add a "device" link (typically to the parent), as a
                                	 * class device would have one and we want to provide as much
                                	 * backwards compatibility as possible.
                                	 */
                                	if (device_link) {
                                        /*在 sys/devices/platform/s3c2440-i2c.3/i2c-3 目录下创建指向 sys/devices/platform/s3c2440-i2c.3 ,名字为device */
                                		error = sysfs_create_link(&dev->kobj, &device_link->kobj,
                                					  "device");
                                		if (error)
                                			sysfs_remove_link(cls->kobj, dev_name(dev));
                                	}

                                	return error;
                                }
                        	if (res)
                        		dev_warn(&adap->dev,
                        			 "Failed to create compatibility class link\n");
                #endif

                        	/* create pre-declared device nodes */
                            /* 重点来了, i2c_client 的实例化,由 i2c_board_info 生成 i2c_client */
                        	if (adap->nr < __i2c_first_dynamic_bus_num)
                        		i2c_scan_static_board_info(adap);
                                {
                                	struct i2c_devinfo	*devinfo;

                                	down_read(&__i2c_board_lock);
                                    /*还记得前面 i2c_register_board_info 把所有的 i2c_board_info 都注册到 __i2c_board_list ,
                                    下面就是遍历 __i2c_board_list ,查找 属于 i2c->adap 的设备 */
                                	list_for_each_entry(devinfo, &__i2c_board_list, list) {
                                		if (devinfo->busnum == adapter->nr
                                				&& !i2c_new_device(adapter,&devinfo->board_info))
                                				    {
                                				        /*每查到一个 属于 i2c->adap 的设备 ,声明一个 i2c_client ,这里就以前面注册的 i2c_devs3 为例*/
                                                    	struct i2c_client	*client;
                                                    	int			status;

                                                    	client = kzalloc(sizeof *client, GFP_KERNEL);
                                                    	if (!client)
                                                    		return NULL;
                                                        /*初始化该 i2c_client 所属的 adap */
                                                    	client->adapter = adap;
                                                        /* client->dev.platform_data = i2c_devs3->platform_data = &mma7660_pdata*/
                                                    	client->dev.platform_data = info->platform_data;

                                                    	if (info->archdata)
                                                    		client->dev.archdata = *info->archdata;
                                                       /*i2c_devs3->flags 未初始化,也就是为      0   */ 
                                                    	client->flags = info->flags;
                                                        /*client->addr = 0x4c*/
                                                    	client->addr = info->addr;
                                                        
                                                    	client->irq = info->irq;
                                                        /* client->name = "mma7660" */
                                                    	strlcpy(client->name, info->type, sizeof(client->name));

                                                    	/* Check for address validity */
                                                        /*检查 client->addr 的合法性 */
                                                    	status = i2c_check_client_addr_validity(client);
                                                    	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 */
                                                        /*检查 i2c->adap 是否注册过 相同 address 的 i2c 期间,一个 i2c 总线上的器件address 必须唯一*/
                                                    	status = i2c_check_addr_busy(adap, client->addr);
                                                                {
                                                                	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
                                                                                                {
                                                                                                    /*前面初始化 i2c->adap->dev->parent = s3c_device_i2c3->dev,所以 parent = NULL */
                                                                                                	struct device *parent = adapter->dev.parent;

                                                                                                	if (parent != NULL && parent->type == &i2c_adapter_type)
                                                                                                		return to_i2c_adapter(parent);
                                                                                                	else
                                                                                                		return NULL;
                                                                                                }
                                                                	int result = 0;
                                                                    /* parent = NULL */
                                                                	if (parent)
                                                                		result = i2c_check_mux_parents(parent, addr);
                                                                                {
                                                                                	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
                                                                                	int result;
                                                                                    
                                                                                	result = device_for_each_child(&adapter->dev, &addr,
                                                                                					__i2c_check_addr_busy);

                                                                                	if (!result && parent)
                                                                                		result = i2c_check_mux_parents(parent, addr);

                                                                                	return result;
                                                                                }

                                                                	if (!result)
                                                                		result = device_for_each_child(&adapter->dev, &addr,i2c_check_mux_children);
                                                                                {
                                                                                	struct klist_iter i;
                                                                                	struct device *child;
                                                                                	int error = 0;

                                                                                	if (!parent->p)
                                                                                		return 0;

                                                                                	klist_iter_init(&parent->p->klist_children, &i);
                                                                                	while ((child = next_device(&i)) && !error)
                                                                                		error = fn(child, data);   //fn = i2c_check_mux_children
                                                                                		        {
                                                                                                	int result;

                                                                                                	if (dev->type == &i2c_adapter_type)
                                                                                                		result = device_for_each_child(dev, addrp,
                                                                                                						i2c_check_mux_children);
                                                                                                	else
                                                                                                        /*此地址已经被注册过,则返回 -EBUSY */
                                                                                                		result = __i2c_check_addr_busy(dev, addrp);
                                                                                                                {
                                                                                                                	struct i2c_client	*client = i2c_verify_client(dev);
                                                                                                                	int			addr = *(int *)addrp;

                                                                                                                	if (client && client->addr == addr)
                                                                                                                		return -EBUSY;
                                                                                                                	return 0;
                                                                                                                }

                                                                                                	return result;
                                                                                                }
                                                                                	klist_iter_exit(&i);
                                                                                	return error;
                                                                                }

                                                                	return result;
                                                                }
                                                    	if (status)
                                                    		goto out_err;

                                                    	client->dev.parent = &client->adapter->dev;
                                                    	client->dev.bus = &i2c_bus_type;
                                                    	client->dev.type = &i2c_client_type;
                                                    	client->dev.of_node = info->of_node;
                                                        /* client->dev->kobj = "3-004c" */
                                                    	dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
                                                    		     client->addr);
                                                        /*将 client 注册到 /sys/devices/platform/s3c2440-i2c.3/i2c-3 目录下,并创建 3-004c 目录,同事创建
                                                                 uevent、name和 new_device 属性文件和一些链接文件等等 */
                                                    	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;
                                                    }  
                                			dev_err(&adapter->dev,
                                				"Can't create device at 0x%02x\n",
                                				devinfo->board_info.addr);
                                	}
                                	up_read(&__i2c_board_lock);
                                }

                        	/* Notify drivers */
                        	mutex_lock(&core_lock);
                            /*遍历已经注册 i2c_driver 和   i2c->adap    ,然后探测 i2c->adap 上i2c_driver支持的地址,
                            如果已经注册则跳过,未注册但有该 i2c 设备,则进行实例化*/  
                        	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
                            {
                            	struct klist_iter i;
                            	struct device_driver *drv;
                            	int error = 0;

                            	if (!bus)
                            		return -EINVAL;
                                   /*遍历 i2c_bus_type->p->klist_drivers ,然后调用 __process_new_adapter*/
                            	klist_iter_init_node(&bus->p->klist_drivers, &i,
                            			     start ? &start->p->knode_bus : NULL);
                            	while ((drv = next_driver(&i)) && !error)
                            		error = fn(drv, data);    //fn = __process_new_adapter
                            		{
                                    	return i2c_do_add_adapter(to_i2c_driver(d), data);
                                                {
                                                	/* Detect supported devices on that bus, and instantiate them */
                                                	i2c_detect(adap, driver);
                                                    {   /* driver 所支持i2c设备的 address 列表*/
                                                    	const unsigned short *address_list;
                                                        /* 声明一个临时的 i2c_client */
                                                    	struct i2c_client *temp_client;
                                                    	int i, err = 0;
                                                    	int adap_id = i2c_adapter_id(adapter);
                                                        /*如果 */
                                                    	address_list = driver->address_list;
                                                    	if (!driver->detect || !address_list)
                                                    		return 0;

                                                    	/* Stop here if the classes do not match */
                                                    	if (!(adapter->class & driver->class))
                                                    		return 0;

                                                    	/* Set up a temporary client to help detect callback */
                                                    	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];
                                                    		err = i2c_detect_address(temp_client, driver);
                                                                {
                                                                	struct i2c_board_info info;
                                                                	struct i2c_adapter *adapter = temp_client->adapter;
                                                                	int addr = temp_client->addr;
                                                                	int err;

                                                                	/* Make sure the address is valid */
                                                                	err = i2c_check_addr_validity(addr);
                                                                        {
                                                                        	/*
                                                                        	 * Reserved addresses per I2C specification:
                                                                        	 *  0x00       General call address / START byte
                                                                        	 *  0x01       CBUS address
                                                                        	 *  0x02       Reserved for different bus format
                                                                        	 *  0x03       Reserved for future purposes
                                                                        	 *  0x04-0x07  Hs-mode master code
                                                                        	 *  0x78-0x7b  10-bit slave addressing
                                                                        	 *  0x7c-0x7f  Reserved for future purposes
                                                                        	 */
                                                                        	if (addr < 0x08 || addr > 0x77)
                                                                        		return -EINVAL;
                                                                        	return 0;
                                                                        }
                                                                	if (err) {
                                                                		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
                                                                			 addr);
                                                                		return err;
                                                                	}

                                                                	/* Skip if already in use */
                                                                	if (i2c_check_addr_busy(adapter, addr))
                                                                        {
                                                                            /*前面初始化 i2c->adap->dev->parent = s3c_device_i2c3->dev,所以 parent = NULL */
                                                                        	struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
                                                                        	int result = 0;

                                                                        	if (parent)
                                                                        		result = i2c_check_mux_parents(parent, addr);

                                                                        	if (!result)
                                                                        		result = device_for_each_child(&adapter->dev, &addr,i2c_check_mux_children);
                                                                                        {
                                                                                        	struct klist_iter i;
                                                                                        	struct device *child;
                                                                                        	int error = 0;

                                                                                        	if (!parent->p)
                                                                                        		return 0;
                                                                                            /*遍历 查询 i2c->adap 下的设备,也就是 i2c  _client */
                                                                                        	klist_iter_init(&parent, &i);
                                                                                        	while ((child = next_device(&i)) && !error)
                                                                                         		error = fn(child, data);    //fn = i2c_check_mux_children
                                                                                         		        {
                                                                                                        	int result;
                                                                                                        	if (dev->type == &i2c_adapter_type)
                                                                                                        		result = device_for_each_child(dev, addrp,i2c_check_mux_children);
                                                                                                                        
                                                                                                        	else
                                                                                                        		result = __i2c_check_addr_busy(dev, addrp);
                                                                                                                        {
                                                                                                                        	struct i2c_client	*client = i2c_verify_client(dev);
                                                                                                                        	int			addr = *(int *)addrp;

                                                                                                                        	if (client && client->addr == addr)
                                                                                                                        		return -EBUSY;
                                                                                                                        	return 0;
                                                                                                                        }

                                                                                                        	return result;
                                                                                                        }
                                                                                        	klist_iter_exit(&i);
                                                                                        	return error;
                                                                                        }
                                                                        	return result;
                                                                        }
                                                                		return 0;

                                                                	/* Make sure there is something at this address */
                                                                	if (!i2c_default_probe(adapter, addr))
                                                                        {
                                                                        	int err;
                                                                        	union i2c_smbus_data dummy;

                                                                    #ifdef CONFIG_X86
                                                                        	if (addr == 0x73 && (adap->class & I2C_CLASS_HWMON)
                                                                        	 && i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE_DATA))
                                                                        		err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
                                                                        				     I2C_SMBUS_BYTE_DATA, &dummy);
                                                                        	else
                                                                    #endif
                                                                        	if (!((addr & ~0x07) == 0x30 || (addr & ~0x0f) == 0x50)
                                                                        	 && i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK))
                                                                        		err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_WRITE, 0,
                                                                        				     I2C_SMBUS_QUICK, NULL);
                                                                        	else if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE))
                                                                        		err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
                                                                        				     I2C_SMBUS_BYTE, &dummy);
                                                                        	else {
                                                                        		dev_warn(&adap->dev, "No suitable probing method supported\n");
                                                                        		err = -EOPNOTSUPP;
                                                                        	}

                                                                        	return err >= 0;
                                                                        }
                                                                		return 0;

                                                                	/* Finally call the custom detection function */
                                                                	memset(&info, 0, sizeof(struct i2c_board_info));
                                                                	info.addr = addr;
                                                                	err = driver->detect(temp_client, &info);
                                                                	if (err) {
                                                                		/* -ENODEV is returned if the detection fails. We catch it
                                                                		   here as this isn't an error. */
                                                                		return err == -ENODEV ? 0 : err;
                                                                	}

                                                                	/* Consistency check */
                                                                	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 succeeded, instantiate the device */
                                                                		dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",
                                                                			info.type, info.addr);
                                                                        /*驱动探测到 adapter 上有该驱动支持的设备但又未 注册 ,则给其实例化*/
                                                                		client = i2c_new_device(adapter, &info);
                                                                		if (client)
                                                                			list_add_tail(&client->detected, &driver->clients);
                                                                		else
                                                                			dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
                                                                				info.type, info.addr);
                                                                	}
                                                                	return 0;
                                                                }
                                                    		if (unlikely(err))
                                                    			break;
                                                    	}

                                                    	kfree(temp_client);
                                                    	return err;
                                                    }

                                                	/* Let legacy drivers scan this bus for matching devices */
                                                	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;
                                                }
                                    }
                            	klist_iter_exit(&i);
                            	return error;
                            }
                        	mutex_unlock(&core_lock);

                        	return 0;

                        out_list:
                        	mutex_lock(&core_lock);
                        	idr_remove(&i2c_adapter_idr, adap->nr);
                        	mutex_unlock(&core_lock);
                        	return res;
                        }
        	return status;
        }
    
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to add bus to i2c core\n");
		goto err_cpufreq;
	}

	platform_set_drvdata(pdev, i2c);

	dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
	clk_disable(i2c->clk);
	return 0;

 err_cpufreq:
	s3c24xx_i2c_deregister_cpufreq(i2c);

 err_irq:
	free_irq(i2c->irq, i2c);

 err_iomap:
	iounmap(i2c->regs);

 err_ioarea:
	release_resource(i2c->ioarea);
	kfree(i2c->ioarea);

 err_clk:
	clk_disable(i2c->clk);
	clk_put(i2c->clk);

 err_noclk:
	kfree(i2c);
	return ret;
}

上面的主要工作,就是初始化 i2c_adapter 的硬件,并且注册 i2c_adapter ,注册后将会 遍历 __i2c_board_list 查找挂接在此  i2c_adapter 的 i2c_board_info ,然后实例化 成  i2c_client ,然后 遍历 已经注册的 i2c_driver ,查找此 i2c_adapter  是否有 已经注册的 i2c_driver 支持的 但又未 注册的 i2c 器件,查到后实例化成 i2c_client 并注册。

整个过程下来,仍然会生成一些 属性文件 和一些链接文件,下面关注 new_device 和 delete_device 两个属性文件。

static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);

展开

struct device_attribute dev_attr_new_device = { 
	.attr = {
        .name = "new_device", 
        .mode = S_IWUSR 
    },	
	.show	= NULL,					
	.store	= i2c_sysfs_new_device,					
}

看store函数

static ssize_t i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{
	struct i2c_adapter *adap = to_i2c_adapter(dev);
	struct i2c_board_info info;
	struct i2c_client *client;
	char *blank, end;
	int res;

	memset(&info, 0, sizeof(struct i2c_board_info));

	blank = strchr(buf, ' ');
	if (!blank) {
		dev_err(dev, "%s: Missing parameters\n", "new_device");
		return -EINVAL;
	}
	if (blank - buf > I2C_NAME_SIZE - 1) {
		dev_err(dev, "%s: Invalid device name\n", "new_device");
		return -EINVAL;
	}
	memcpy(info.type, buf, blank - buf);

	/* Parse remaining parameters, reject extra parameters */
	res = sscanf(++blank, "%hi%c", &info.addr, &end);
	if (res < 1) {
		dev_err(dev, "%s: Can't parse I2C address\n", "new_device");
		return -EINVAL;
	}
	if (res > 1  && end != '\n') {
		dev_err(dev, "%s: Extra parameters\n", "new_device");
		return -EINVAL;
	}

	client = i2c_new_device(adap, &info);
	if (!client)
		return -EINVAL;

	/* Keep track of the added device */
	mutex_lock(&adap->userspace_clients_lock);
	list_add_tail(&client->detected, &adap->userspace_clients);
	mutex_unlock(&adap->userspace_clients_lock);
	dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
		 info.type, info.addr);

	return count;
}

new_device 文件就是为用户空间提供 一种添加 i2c 设备的接口,在不管 系统没有注册 此设备的时候 ,可以尝试通过 new_device 写入 i2c 期间的名字 添加该设备。

static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);

展开

struct device_attribute dev_attr_delete_device = { 
	.attr = {
        .name = "delete_device", 
        .mode = S_IWUSR 
    },	
	.show	= NULL,					
	.store	= i2c_sysfs_delete_device,					
}

查看 store 函数

static ssize_t i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{
	struct i2c_adapter *adap = to_i2c_adapter(dev);
	struct i2c_client *client, *next;
	unsigned short addr;
	char end;
	int res;

	/* Parse parameters, reject extra parameters */
	res = sscanf(buf, "%hi%c", &addr, &end);
	if (res < 1) {
		dev_err(dev, "%s: Can't parse I2C address\n", "delete_device");
		return -EINVAL;
	}
	if (res > 1  && end != '\n') {
		dev_err(dev, "%s: Extra parameters\n", "delete_device");
		return -EINVAL;
	}

	/* Make sure the device was added through sysfs */
	res = -ENOENT;
	mutex_lock(&adap->userspace_clients_lock);
	list_for_each_entry_safe(client, next, &adap->userspace_clients,
				 detected) {
		if (client->addr == addr) {
			dev_info(dev, "%s: Deleting device %s at 0x%02hx\n",
				 "delete_device", client->name, client->addr);

			list_del(&client->detected);
			i2c_unregister_device(client);
			res = count;
			break;
		}
	}
	mutex_unlock(&adap->userspace_clients_lock);

	if (res < 0)
		dev_err(dev, "%s: Can't find device in list\n",
			"delete_device");
	return res;
}

二、i2c_driver的注册

    上面的代码已经 实例化 i2c_client 并且注册,下面就该注册相应的 i2c_driver ,然后通过 i2c_bus_type 的 match 调用 到

i2c_driver->probe 即 mma7660_probe 。

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this, it will be removed in a
	 * near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	int (*detach_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;
};

 

unsigned int class;

此driver支持的i2c设备种类,用来和i2c_adapter中的class做对比,共三种类型I2C_CLASS_HWMON、I2C_CLASS_SPD 和 I2C_CLASS_DDC 

int (*attach_adapter)(struct i2c_adapter *) __deprecated;

i2c 总线增加设备时的回调函数,已经弃用

int (*detach_adapter)(struct i2c_adapter *) __deprecated;

i2c 总线移除设备时的回调函数,已经弃用

 

int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;

 

const struct i2c_device_id *id_table;

此driver兼容的设备列表,i2c_device_match 时会调用

int (*detect)(struct i2c_client *, struct i2c_board_info *);

发现设备时的回调函数,在代码中发现 未注册 设备时会调用

const unsigned short *address_list;

此驱动支持的地址列表,注册 i2c_adapter 或 i2c_driver 时会调用此 列表判断

 i2c_adapter 是否挂有此驱动兼容的设备

struct list_head clients;

遍历驱动发现的i2c设备将挂入此链表中,和i2c_client的detected对应

上面以 mma7660 为例实例化的 i2c_client,下面还以 mma7660 为例分析 i2c_driver 的注册

i2c_mma7660_driver的定义

static struct i2c_driver i2c_mma7660_driver = {
	.driver		= {
		.name	= MMA7660_NAME,
	},

	.probe		= mma7660_probe,
	.remove		= __devexit_p(mma7660_remove),
	.suspend	= mma7660_suspend,
	.resume		= mma7660_resume,
	.id_table	= mma7660_ids,
};

其中的 id_table 为

static const struct i2c_device_id mma7660_ids[] = {
	{ "mma7660", 0 },
	{ },
};

下面就看  i2c_mma7660_driver 的注册过程。

static int __init init_mma7660(void)
{
	int ret;

	ret = i2c_add_driver(&i2c_mma7660_driver);
        {
        	return i2c_register_driver(THIS_MODULE, driver);
                    {
                    	int res;

                    	/* Can't register until after driver model init */
                    	if (unlikely(WARN_ON(!i2c_bus_type.p)))
                    		return -EAGAIN;

                    	/* add the driver to the list of i2c drivers in the driver core */
                    	driver->driver.owner = owner;
                    	driver->driver.bus = &i2c_bus_type;

                    	/* When registration returns, the driver core
                    	 * will have called probe() for all matching-but-unbound devices.
                    	 */
                    	res = driver_register(&driver->driver);
                            {
                            	int ret;
                            	struct device_driver *other;

                            	BUG_ON(!drv->bus->p);
                                /* i2c_bus_type->probe = i2c_device_probe
                                i2c_bus_type->remove = i2c_device_remove
                                i2c_bus_type->shutdown = i2c_device_shutdown
                                i2c_mma7660_driver->driver->probe = NULL
                                i2c_mma7660_driver->driver->remove = NULL
                                i2c_mma7660_driver->driver->shutdown = NULL*/
                            	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);
                                /*通过name查找是否已经注册过此driver*/
                            	other = driver_find(drv->name, drv->bus);
                                        {
                                        	struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);
                                        	struct driver_private *priv;

                                        	if (k) {
                                        		priv = to_driver(k);
                                        		return priv->driver;
                                        	}
                                        	return NULL;
                                        }
                            	if (other) {
                            		put_driver(other);
                            		printk(KERN_ERR "Error: Driver '%s' is already registered, "
                            			"aborting...\n", drv->name);
                            		return -EBUSY;
                            	}

                            	ret = bus_add_driver(drv);
                                    {
                                    	struct bus_type *bus;
                                    	struct driver_private *priv;
                                    	int error = 0;
                                /*前面初始化 i2c_mma7660_driver->driver.bus = &i2c_bus_type*/
                                    	bus = bus_get(drv->bus);
                                    	if (!bus)
                                    		return -EINVAL;

                                    	pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

                                        /*为 i2c_mma7660_driver->driver申请私有结构体*/

                                    	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
                                    	if (!priv) {
                                    		error = -ENOMEM;
                                    		goto out_put_bus;
                                    	}
                                    	klist_init(&priv->klist_devices, NULL, NULL);
                                    	priv->driver = drv;
                                    	drv->p = priv;
                                        /*i2c_mma7660_driver->driver->priv->kobj.kset = i2c_bus_type->p->drivers_kset
                                        还记得sys/bus/i2c 下的drivers目录吗
                                        priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);*/
                                    	priv->kobj.kset = bus->p->drivers_kset;
                                        /*在sys/bus/i2c/drivers目录下创建 mma7660 的目录*/
                                    	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
                                    				     "%s", drv->name);
                                    	if (error)
                                    		goto out_unregister;
                                        /*又见到了drivers_autoprobe,还记得/sys/bus/i2c 下的drivers_autoprobe文件吗
                                        i2c_type->p->drivers_autoprobe = 1,自动匹配device和driver*/
                                    	if (drv->bus->p->drivers_autoprobe) {
                                    		error = driver_attach(drv);
                                                    {
                                                    	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
                                                               {
                                                                	struct klist_iter i;
                                                                	struct device *dev;
                                                                	int error = 0;

                                                                	if (!bus || !bus->p)
                                                                		return -EINVAL;
                                                                    /*device 注册的时候就是挂接到 i2c_bus_type->p->klist_devices
                                                                    下面就是遍历 i2c_bus_type 的device 链表,然后通过__driver_attach和driver
                                                                    做匹配*/

                                                                	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);  //fn = __driver_attach
                                                                                {
                                                                                	struct device_driver *drv = data;

                                                                                	/*
                                                                                	 * Lock device and try to bind to it. We drop the error
                                                                                	 * here and always return 0, because we need to keep trying
                                                                                	 * to bind to devices and some drivers will return an error
                                                                                	 * simply if it didn't support the device.
                                                                                	 *
                                                                                	 * driver_probe_device() will spit a warning if there
                                                                                	 * is an error.
                                                                                	 */

                                                                                	if (!driver_match_device(drv, dev))
                                                                                        {   /* drv->bus->match = i2c_device_match ,最终还是调用到 i2c 总线 的match函数
                                                                                            最终会匹配到 mma7660 的 i2c_client*/
                                                                                        	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
                                                                                        }
                                                                                		return 0;
                                                                                    /*dev = client->dev
                                                                                      client->dev.parent = &client->adapter->dev */

                                                                                	if (dev->parent)	/* Needed for USB */
                                                                                		device_lock(dev->parent);
                                                                                	device_lock(dev);
                                                                                    /*虽然device和driver都找到了,可还未将driver赋值给device呢*/
                                                                                	if (!dev->driver)
                                                                                		driver_probe_device(drv, dev);
                                                                                        {
                                                                                        	int ret = 0;

                                                                                        	if (!device_is_registered(dev))
                                                                                        		return -ENODEV;

                                                                                        	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
                                                                                        		 drv->bus->name, __func__, dev_name(dev), drv->name);

                                                                                        	pm_runtime_get_noresume(dev);
                                                                                        	pm_runtime_barrier(dev);
                                                                                        	ret = really_probe(dev, drv);
                                                                                                {
                                                                                                	int ret = 0;

                                                                                                	atomic_inc(&probe_count);
                                                                                                	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
                                                                                                		 drv->bus->name, __func__, drv->name, dev_name(dev));
                                                                                                	WARN_ON(!list_empty(&dev->devres_head));
                                                                                                    /*这里才将driver和device关联起来*/
                                                                                                	dev->driver = drv;
                                                                                                	if (driver_sysfs_add(dev)) {
                                                                                                        {
                                                                                                        	int ret;

                                                                                                        	if (dev->bus)
                                                                                                        		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                                                                                        					     BUS_NOTIFY_BIND_DRIVER, dev);
                                                                                                            /*sys/bus/i2c/drivers/mma7660创建指向 3-004c 的链接,
                                                                                                            名字仍然为 3-004c */
                                                                                                        	ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
                                                                                                        			  kobject_name(&dev->kobj));
                                                                                                        	if (ret == 0) {
                                                                                                                /*在 /sys/devices/platform/s3c2440-i2c.3/i2c-3/3-004c/ 下
                                                                                                                创建指向 /sys/bus/i2c/drivers/mma7660 的链接,名字为driver*/
                                                                                                        		ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
                                                                                                        					"driver");
                                                                                                        		if (ret)
                                                                                                        			sysfs_remove_link(&dev->driver->p->kobj,
                                                                                                        					kobject_name(&dev->kobj));
                                                                                                        	}
                                                                                                        	return ret;
                                                                                                        }
                                                                                                		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
                                                                                                			__func__, dev_name(dev));
                                                                                                		goto probe_failed;
                                                                                                	}
                                                                                                    /*i2c_bus_type->probe = i2c_device_probe ,所以调用
                                                                                                    i2c_device_probe ,最终调用 mma7660_probe*/
                                                                                                	if (dev->bus->probe) {
                                                                                                		ret = dev->bus->probe(dev);
                                                                                                		if (ret)
                                                                                                			goto probe_failed;
                                                                                                	} else if (drv->probe) {
                                                                                                		ret = drv->probe(dev);
                                                                                                		if (ret)
                                                                                                			goto probe_failed;
                                                                                                	}

                                                                                                	driver_bound(dev);
                                                                                                    {   /*前面 client->dev->p->knode_driver未初始化,也就是为NULL*/
                                                                                                    	if (klist_node_attached(&dev->p->knode_driver)) {
                                                                                                    		printk(KERN_WARNING "%s: device %s already bound\n",
                                                                                                    			__func__, kobject_name(&dev->kobj));
                                                                                                    		return;
                                                                                                    	}

                                                                                                    	pr_debug("driver: '%s': %s: bound to device '%s'\n", dev_name(dev),
                                                                                                    		 __func__, dev->driver->name);
                                                                                                        /*将device挂接在driver的设备链表中*/
                                                                                                    	klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

                                                                                                    	if (dev->bus)
                                                                                                    		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                                                                                    					     BUS_NOTIFY_BOUND_DRIVER, dev);
                                                                                                    }
                                                                                                	ret = 1;
                                                                                                	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
                                                                                                		 drv->bus->name, __func__, dev_name(dev), drv->name);
                                                                                                	goto done;

                                                                                                probe_failed:
                                                                                                	devres_release_all(dev);
                                                                                                	driver_sysfs_remove(dev);
                                                                                                	dev->driver = NULL;

                                                                                                	if (ret != -ENODEV && ret != -ENXIO) {
                                                                                                		/* driver matched but the probe failed */
                                                                                                		printk(KERN_WARNING
                                                                                                		       "%s: probe of %s failed with error %d\n",
                                                                                                		       drv->name, dev_name(dev), ret);
                                                                                                	}
                                                                                                	/*
                                                                                                	 * Ignore errors returned by ->probe so that the next driver can try
                                                                                                	 * its luck.
                                                                                                	 */
                                                                                                	ret = 0;
                                                                                                done:
                                                                                                	atomic_dec(&probe_count);
                                                                                                	wake_up(&probe_waitqueue);
                                                                                                	return ret;
                                                                                                }
                                                                                        	pm_runtime_put_sync(dev);

                                                                                        	return ret;
                                                                                        }
                                                                                	device_unlock(dev);
                                                                                	if (dev->parent)
                                                                                		device_unlock(dev->parent);

                                                                                	return 0;
                                                                                }
                                                                	klist_iter_exit(&i);
                                                                	return error;
                                                               }
                                                    }
                                    		if (error)
                                    			goto out_unregister;
                                    	}
                                        /*将 i2c_mma7660_driver 挂接在 i2c_bus_type 的driver链表中*/
                                    	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
                                    	module_add_driver(drv->owner, drv);
                                        /*在/sys/bus/i2c/drivers/mma7660 创建 uevent 文件*/
                                    	error = driver_create_file(drv, &driver_attr_uevent);
                                    	if (error) {
                                    		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
                                    			__func__, drv->name);
                                    	}
                                        /*为 i2c_mma7660_driver 创建默认的属性文件,可是i2c_bus_type->drv_attrs = NULL,所以此例中没有
                                        没有创建任何文件*/
                                    	error = driver_add_attrs(bus, drv);
                                                {
                                                	int error = 0;
                                                	int i;

                                                	if (bus->drv_attrs) {
                                                		for (i = 0; attr_name(bus->drv_attrs[i]); i++) {
                                                			error = driver_create_file(drv, &bus->drv_attrs[i]);
                                                			if (error)
                                                				goto err;
                                                		}
                                                	}
                                                done:
                                                	return error;
                                                err:
                                                	while (--i >= 0)
                                                		driver_remove_file(drv, &bus->drv_attrs[i]);
                                                	goto done;
                                                }
                                    	if (error) {
                                    		/* How the hell do we get out of this pickle? Give up */
                                    		printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
                                    			__func__, drv->name);
                                    	}
                                        /*i2c_mma7660_driver->drv->suppress_bind_attrs 未赋值,也就是为0*/
                                    	if (!drv->suppress_bind_attrs) {
                                    		error = add_bind_files(drv);
                                                    {
                                                    	int ret;
                                                        /*在/sys/bus/i2c/drivers/mma7660 目录下创建 ubind 文件*/
                                                    	ret = driver_create_file(drv, &driver_attr_unbind);
                                                    	if (ret == 0) {
                                                            /*在/sys/bus/i2c/drivers/mma7660目录下创建 bind 文件*/
                                                    		ret = driver_create_file(drv, &driver_attr_bind);
                                                    		if (ret)
                                                    			driver_remove_file(drv, &driver_attr_unbind);
                                                    	}
                                                    	return ret;
                                                    }
                                    		if (error) {
                                    			/* Ditto */
                                    			printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
                                    				__func__, drv->name);
                                    		}
                                    	}
                                        /*发送 KOBJ_ADD */
                                    	kobject_uevent(&priv->kobj, KOBJ_ADD);
                                    	return 0;

                                    out_unregister:
                                    	kobject_put(&priv->kobj);
                                    	kfree(drv->p);
                                    	drv->p = NULL;
                                    out_put_bus:
                                    	bus_put(bus);
                                    	return error;
                                    }
                            	if (ret)
                            		return ret;
                            	ret = driver_add_groups(drv, drv->groups);
                            	if (ret)
                            		bus_remove_driver(drv);
                            	return ret;
                            }
                    	if (res)
                    		return res;

                    	/* Drivers should switch to dev_pm_ops instead. */
                    	if (driver->suspend)
                    		pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
                    			driver->driver.name);
                    	if (driver->resume)
                    		pr_warn("i2c-core: driver [%s] using legacy resume method\n",
                    			driver->driver.name);

                    	pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

                    	INIT_LIST_HEAD(&driver->clients);
                    	/* Walk the adapters that are already present */
                    	i2c_for_each_dev(driver, __process_new_driver);
                        {
                        	int res;

                        	mutex_lock(&core_lock);
                        	res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
                                {
                                	struct klist_iter i;
                                	struct device *dev;
                                	int error = 0;

                                	if (!bus || !bus->p)
                                		return -EINVAL;

                                	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);    //fn = __process_new_driver
                                		        {
                                                	if (dev->type != &i2c_adapter_type)
                                                		return 0;
                                                	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
                                                            {
                                                            	/* Detect supported devices on that bus, and instantiate them */
                                                            	i2c_detect(adap, driver);
                                                                {   /* driver 所支持i2c设备的 address 列表*/
                                                                	const unsigned short *address_list;
                                                                    /* 声明一个临时的 i2c_client */
                                                                	struct i2c_client *temp_client;
                                                                	int i, err = 0;
                                                                	int adap_id = i2c_adapter_id(adapter);
                                                                    /* i2c_mma7660_driver->address_list 未赋值,也就是为 NULL 函数在此返回*/
                                                                	address_list = driver->address_list;
                                                                	if (!driver->detect || !address_list)
                                                                		return 0;

                                                                    ......
                                                                    
                                                                	kfree(temp_client);
                                                                	return err;
                                                                }

                                                            	/* Let legacy drivers scan this bus for matching devices */
                                                            	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;
                                                            }
                                                }
                                	klist_iter_exit(&i);
                                	return error;
                                }
                        	mutex_unlock(&core_lock);

                        	return res;
                        }

                    	return 0;
                    }
        }
	printk(KERN_INFO "MMA7660 sensor driver registered.\n");

	return ret;
}

从上面的代码中可以看出,其实和platform的那一套机制是一回事,驱动框架完成了大量的工作,然后实际驱动只需要注册device和driver,驱动框架就会调用到probe函数,开始真正的硬件初始化相关的工作。

三、i2c-dev

不是说 linux 秉承一切皆文件吗(网络设备除外),驱动不是应该完成open、close、read和write等函数吗?到目前为止不管platform还是i2c都没有这些信息。不要忘了,bus、device和driver驱动模型,只是驱动的一种框架,并没有完成任何实质性工作,真正的注册char、block还是网络设备都是开发者自己的事,驱动模型不是最终保证会调用到probe函数吗,对的,你可以在probe函数中完成实际的设备驱动注册,如 register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops),驱动模型只是套了个架子而已,实际驱动工作还得自己做。

对于实际的 i2c 器件,只是将 i2c 作为通信通道而已,再简单的说实际设备功能并不关心是通过spi还是 i2c 进行,比如驱动中的 read 函数,按顺序读写寄存器,并不关心 是 i2c还是spi甚至usb,只要进行数据传输达到目的就行。对于 i2c 器件一般会是input、rtc或者misc,文件系统的接口自然由 input、rtc或misc模型提供,当然你也可以专门为此期间写一个char类型的驱动,完成open、close、read和write等函数,但对于i2c部分,只是利用 i2c_client->adapter->i2c_algorithm->master_xfer和器件进行数据交互。

下面看一下i2c-dev,linux提供的 i2c-dev 并不是实际存在具体的一个i2c器件,而是代表一个i2c总线的设备,可以通过 i2c-dev 提供的 read、write和ioctl来访问总线上的 i2c 器件(根据指定的器件地址)。由于不同的 i2c 器件的 read 和 write 实现各不相同,所以linux提供的 i2c-dev 并不是很通用。

下面简单看一下i2c-dev的注册过程

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,
};
static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");
        /*注册为char型设备,主设备号为 I2C_MAJOR(123)(终于见到设备号了) */
	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
        
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);
    
	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

其中 i2cdev_attach_adapter中会创建设备文件,也就是在/dev/目录下创建 i2c- %d的文件,如 i2c-3 就代表第三个i2c 上的设备或者说i2c_adapter,可通过/dev/i2c-3 来访问该总线上设备(如open、close、read和write等)。

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	......
    
	/* register this i2c device with the driver core */
    /*创建设备文件,这回 是带 设备号的,所以也会在 /sys/dev/目录下创建文件,如i2c-3*/
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
	......
	return res;
}

上面就是i2c驱动框架的大致内容了,整体来说还是比较复杂的,不过从代码一点一点分析,还是可以理解的,但是鉴于个人的水平有限,可能会出现理解错误的地方,欢迎交流。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值