赞
踩
i2c_adapter
的过程I2C单独控制的话,占用CPU的时间非常高,这样不利于系统高效运转,所以一般的芯片里面都会有I2C控制器。
I2C控制器里面一般会有很多的寄存器。比如:
controller_register
可以设置I2C控制器的频率tx_register
、rx_register
控制数据的传输status_register
控制I2C
的状态int_register
控制I2C
的中断tx_register
。后面的I2C控制器会自动帮忙做好,在SDA
上发送数据。tx_register
后,就不用管了。直到数据发送结束,会产生一个发送完成的中断I2C_IFDR
(频率寄存器)I2C_I2CR
(控制寄存器I2C_I2SR
(状态寄存器)I2C_I2DR
(数据I/O寄存器)I2C_IADR
(地址寄存器imx6ull:arch/arm/boot/dts/imx6ull.dtsi
i2c1: i2c@02221a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = <"fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x40000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled"; //在100ask_imx6ull-14x14.dts 把它改成了"okay"
};
Linux下的i2c框架,分为了3个子模块
I2C
总线驱动重点是 I2C
适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到
两个重要的数据结构:i2c_adapter
和 i2c_algorithm
,Linux 内核将 SOC 的 I2C 适配器(控制器)
抽象成 i2c_adapter
,i2c_adapter
结构体定义在 include/linux/i2c.h
文件中。
498 struct i2c_adapter { 499 struct module *owner; 500 unsigned int class; /* classes to allow probing for */ 501 const struct i2c_algorithm *algo; /* 总线访问算法 */ 502 void *algo_data; 503 504 /* data fields that are valid for all devices */ 505 struct rt_mutex bus_lock; 506 507 int timeout; /* in jiffies */ 508 int retries; 509 struct device dev; /* the adapter device */ 510 511 int nr; 512 char name[48]; 513 struct completion dev_released; 514 515 struct mutex userspace_clients_lock; 516 struct list_head userspace_clients; 517 518 struct i2c_bus_recovery_info *bus_recovery_info; 519 const struct i2c_adapter_quirks *quirks; 520 }; struct imx_i2c_struct { struct i2c_adapter adapter; struct clk *clk; void __iomem *base; wait_queue_head_t queue; unsigned long i2csr; unsigned int disable_delay; int stopped; unsigned int ifdr; /* IMX_I2C_IFDR */ unsigned int cur_clk; unsigned int bitrate; const struct imx_i2c_hwdata *hwdata; struct imx_i2c_dma *dma; };
紧接着上篇文章《Linux I2C子系统【1】-I2C驱动分析-设备树创建platform_device》中的“i2c platform_driver主机驱动匹配platform_device 的过程”
,.probe = i2c_imx_probe
被执行,进入到开始创建i2c_adapter
过程中。
static const struct of_device_id i2c_imx_dt_ids[] = { { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, }, { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, }, { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, }, { /* sentinel */ } }; static const struct imx_i2c_hwdata imx21_i2c_hwdata = { .devtype = IMX21_I2C, .regshift = IMX_I2C_REGSHIFT, .clk_div = imx_i2c_clk_div, .ndivs = ARRAY_SIZE(imx_i2c_clk_div), .i2sr_clr_opcode = I2SR_CLR_OPCODE_W0C, .i2cr_ien_opcode = I2CR_IEN_OPCODE_1, }; i2c-imx.c( static int i2c_imx_probe(struct platform_device *pdev) { struct imx_i2c_struct *i2c_imx; /*告知一个设备是否匹配了(i2c_imx_dt_ids)列表中的设备*/ /*of_id = { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },*/ const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids, &pdev->dev);/*include/linux/of_device.h*/ { #define of_match_device(matches, dev) \ __of_match_device(of_match_ptr(matches), (dev)) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0);/*drivers/base/platform.c*/ base = devm_ioremap_resource(&pdev->dev, res);/*lib/devres.c*/ if (IS_ERR(base)) return PTR_ERR(base); phy_addr = (dma_addr_t)res->start; /*i2c_imx->adapter创建出来了*/ i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL); i2c_imx->hwdata = of_id->data;/*赋值硬件信息,分频等信息*/ /* 填充 i2c_imx 驱动结构体 */ strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name)); i2c_imx->adapter.owner = THIS_MODULE; i2c_imx->adapter.algo = &i2c_imx_algo; /*算法通讯部分*/ i2c_imx->adapter.dev.parent = &pdev->dev; /*此分配器的所属设备的父设备是他自己*/ i2c_imx->adapter.nr = pdev->id; i2c_imx->adapter.dev.of_node = pdev->dev.of_node; /*此分配器所属设备的设备节点*/ i2c_imx->base = base; /*DMA 地址首地址*/ /* Init queue */ init_waitqueue_head(&i2c_imx->queue); /* Set up adapter data */ i2c_set_adapdata(&i2c_imx->adapter, i2c_imx); /* Set up clock divider *//*默认为100khz*/ i2c_imx->bitrate = IMX_I2C_BIT_RATE; ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &i2c_imx->bitrate);/*clock-frequency = <100000>;*/ /*注册到哪里?以怎么的数据结构注册链接*/ ret = i2c_add_numbered_adapter(&i2c_imx->adapter);/*注册adapter*/ } 在include/linux/of.h里面 查看arch/arm64/KCONFIG,可以知道CONFIG_OF默认是打开的,意为使用设备树 #ifdef CONFIG_OF ... #define of_match_ptr(_ptr) (_ptr) ... #else /* CONFIG_OF */ ... #define of_match_ptr(_ptr) NULL ... #endif /* CONFIG_OF */ /* * of_match_device - Tell if a struct device matches an of_device_id list * @ids: array of of device match structures to search in * @dev: the of device structure to match against * * Used by a driver to check whether an platform_device present in the * system is in its list of supported devices. */ /*这里有一句非常重要的描述“of_match_device - Tell if a struct device matches an of_device_id list”,意思就是:告知一个设备(设备结构体)是否匹配了of_device_id列表中的设备。 如果匹配表或者设备没有对应的节点,返回NULL,匹配失败。否则,继续执行非常重要的of_match_node函数。*/ const struct of_device_id *of_match_device(const struct of_device_id *matches, const struct device *dev) { if ((!matches) || (!dev->of_node)) return NULL; return of_match_node(matches, dev->of_node); } driver/of/base.c ( /*可以看出其匹配的方式有三种:name/type/compatible,这三种方式的最佳是通过score分数来判定的,匹配完成后返回score最大对应的matches*/ static const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node) { const struct of_device_id *best_match = NULL; int score, best_score = 0; if (!matches) return NULL; for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) { score = __of_device_is_compatible(node, matches->compatible, matches->type, matches->name); if (score > best_score) { best_match = matches; best_score = score; } ) return best_match; } ) drvres/clk/clk-drvres.c ( struct clk *devm_clk_get(struct device *dev, const char *id) { struct clk **ptr, *clk; ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); clk = clk_get(dev, id); if (!IS_ERR(clk)) { *ptr = clk; devres_add(dev, ptr); } else { devres_free(ptr); } return clk; } ) drvres/clk/clk-drvres.c( struct clk *clk_get(struct device *dev, const char *con_id) { const char *dev_id = dev ? dev_name(dev) : NULL; struct clk *clk; if (dev) { clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);/*通过名字获取时钟*/ if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER) return clk; } return clk_get_sys(dev_id, con_id); } ) static inline void i2c_set_adapdata(struct i2c_adapter *dev, void *data) { dev_set_drvdata(&dev->dev, data); static inline void dev_set_drvdata(struct device *dev, void *data) { /*i2c_imx->adapter->(struct device*)dev->driver_data = i2c_imx*/ /*i2c_adapter 所属设备中的设备驱动指向i2c_imx*/ dev->driver_data = data; } } drivers/i2c/i2c-core.c( int i2c_add_numbered_adapter(struct i2c_adapter *adap) { if (adap->nr == -1) /* -1 means dynamically assign bus id */ return i2c_add_adapter(adap); return __i2c_add_numbered_adapter(adap); } int i2c_add_adapter(struct i2c_adapter *adapter) { struct device *dev = &adapter->dev; int id; if (dev->of_node) { id = of_alias_get_id(dev->of_node, "i2c"); if (id >= 0) { adapter->nr = id; return __i2c_add_numbered_adapter(adapter); } } } /*i2c_add_numbered_adapter where nr is never -1*/ static int __i2c_add_numbered_adapter(struct i2c_adapter *adap) { return i2c_register_adapter(adap); } static int i2c_register_adapter(struct i2c_adapter *adap) { /*至此等待设备在i2c_bus_type匹配*/ INIT_LIST_HEAD(&adap->userspace_clients);/*初始化链表头*/ dev_set_name(&adap->dev, "i2c-%d", adap->nr);/*dev->kobj->name*/ adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; res = device_register(&adap->dev);/*注册入i2c_bus_type*/ /* create pre-declared device nodes */ of_i2c_register_devices(adap); } )
/了解device_register阅读以下文章/
linux设备模型六(device细节)
到这里我们着重分析一下,在注册i2c_adapter的时候如何发生匹配i2c_clent的?
主机驱动会根据i2c_adpter->dev->of_node来创建 i2c_client。
i2c_client 链表: i2c_bus_type->p->klist_devices
i2c_driver 链表:i2c_bus_type->p->klist_drivers
硬件i2c控制器硬件初始化完成,注册 adapter时,依据1.1.3中的dev->of_node 中信息生成i2c_client,并挂接在klist_devices链表上
在注册i2c_driver时,即:i2c_add_driver(struct i2c_driver*),会做两件事情:
(1)将 i2c_driver->drv 挂接到此链表
(2)遍历 klist_devices 链表,查找与 i2c_driver->id_table->name 相同的 i2c_client->name
--->static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
--->static int i2c_register_adapter(struct i2c_adapter *adap)
--->device_register(&adap->dev)
--->static void of_i2c_register_devices(struct i2c_adapter *adap)
-->for_each_available_child_of_node(parent, child)
-->struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,struct device_node *node)
-->struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
/*就是将i2c_adapter当作device注册进入i2cbus->p->klist_devices,将来供i2c_client匹配*/
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, }; --->device_register(&adap->dev) { device_add(struct device *dev) { pr_info("device: '%s',%p\n", dev_name(dev),dev); parent = get_device(dev->parent); pr_info("parent=%p",parent); bus_add_device(struct device *dev) { klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);/*i2c_bus_type->p->klist_devices*/ } /*probe drivers for a new device*/ bus_probe_device(struct device *dev)/*bus_probe_device(dev)*/ { ret = device_attach(dev); } } } drivers/base/dd.c( /** * device_attach - try to attach device to a driver. * @dev: device. * * Walk the list of drivers that the bus has and call * driver_probe_device() for each pair. If a compatible * pair is found, break out and return. * * Returns 1 if the device was bound to a driver; * 0 if no matching driver was found; * -ENODEV if the device is not registered. * * When called for a USB interface, @dev->parent lock must be held. */ int device_attach(struct device *dev) { int ret = 0; device_lock(dev); if (dev->driver) { /* driver已经放在device了(初始化device,时,手动添加的driver) */ if (klist_node_attached(&dev->p->knode_driver)) { ret = 1; goto out_unlock; } ret = device_bind_driver(dev); if (ret == 0) ret = 1; else { dev->driver = NULL; ret = 0; } } else { ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); { struct klist_iter i; struct device_driver *drv; int error = 0; if (!bus) return -EINVAL; klist_iter_init_node(&bus->p->klist_drivers, &i, start ? &start->p->knode_bus : NULL); pr_info("start match driver\n\n"); while ((drv = next_driver(&i)) && !error) error = fn(drv, data); pr_info("end match driver\n\n"); klist_iter_exit(&i); return error; } pm_request_idle(dev); } out_unlock: device_unlock(dev); return ret; } static int __device_attach(struct device_driver *drv, void *data) { struct device *dev = data; if (!driver_match_device(drv, dev)) return 0; return driver_probe_device(drv, dev); } ) drivers/base/base.h( static inline int driver_match_device(struct device_driver *drv, struct device *dev) { pr_info("drv->bus=%s",drv->bus->name); return drv->bus->match ? drv->bus->match(dev, drv) : 1; } ) drivers/i2c/i2c-core.c( static int i2c_device_match(struct device *dev, struct device_driver *drv) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; if (!client){ pr_info("%s,%p,11-faild to match\n\n",drv->name,dev); return 0; } /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) return 1; pr_info("faild to match\n\n"); /* Then ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; driver = to_i2c_driver(drv); /* match on an id table if there is one */ if (driver->id_table) return i2c_match_id(driver->id_table, client) != NULL; return 0; } /* 以下地址为/i2c_adapter->dev 的地址,在没有匹配到驱动时,退出。 然后第二个bus_add_device,end match driver代表开始注册i2c_client->dev,然后开始匹配驱动,那么时怎么一个流程呢? bus_add_device device: 'i2c-0',88525038(struct device i2c_bus)(跟platform_bus一样当作所有device的父设备) parent=880fb010 device: 'i2c-0',886a2400(i2c_adapter->dev) parent=88525038 start match driver dummy,88503438,11-faild to match pca953x,88503438,11-faild to match stmpe-i2c,88503438,11-faild to match mc13xxx,88503438,11-faild to match da9052,88503438,11-faild to match pfuze100-regulator,88503438,11-faild to match at24,88503438,11-faild to match egalax_ts,88503438,11-faild to match tsc2007,88503438,11-faild to match vtl_ts,88503438,11-faild to match mma8450,88503438,11-faild to match ov2640,88503438,11-faild to match ir-kbd-i2c,88503438,11-faild to match sgtl5000,88503438,11-faild to match tlv320aic23-codec,88503438,11-faild to match wm8960,88503438,11-faild to match wm8962,88503438,11-faild to match bus_add_device end match driver (devcie_register 必经之路) of_i2c: walking child nodes of_i2c_register_device device: '0-001e',886a2c20 parent=88525038 start match driver end match driver client [ap3216c] registered with bus id 0-001e i2c_scan_static_board_info start match driver end match driver */ )
&i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; /*2022/1/31/ tristanwang*/ ap3216c@1e{ compatible = "alientek,ap3216c"; reg = <0x1e>; }; // mag3110@0e { // compatible = "fsl,mag3110"; // reg = <0x0e>; // position = <2>; // }; };
drivers/i2c/i2c-core.c( static void of_i2c_register_devices(struct i2c_adapter *adap) { struct device_node *node; /* Only register child devices if the adapter has a node pointer set */ if (!adap->dev.of_node) return; dev_dbg(&adap->dev, "of_i2c: walking child nodes\n"); for_each_available_child_of_node(adap->dev.of_node, node) of_i2c_register_device(adap, node); { result = i2c_new_device(adap, &info); } } ) linuc/of.h( #define for_each_available_child_of_node(parent, child) \ for (child = of_get_next_available_child(parent, NULL); child != NULL; \ child = of_get_next_available_child(parent, child)) ) drivers/of/base.c( /*of_get_next_available_child - Find the next available child node*/ struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev) { struct device_node *next; unsigned long flags; if (!node) return NULL; raw_spin_lock_irqsave(&devtree_lock, flags); next = prev ? prev->sibling : node->child; for (; next; next = next->sibling) { if (!__of_device_is_available(next)) continue; if (of_node_get(next)) break; } of_node_put(prev); raw_spin_unlock_irqrestore(&devtree_lock, flags); return next; } /* __of_device_is_available - check if a device is available for use*/ static bool __of_device_is_available(const struct device_node *device) { const char *status; int statlen; if (!device) return false; status = __of_get_property(device, "status", &statlen); if (status == NULL) return true; if (statlen > 0) { if (!strcmp(status, "okay") || !strcmp(status, "ok")) return true; } return false; } ) drivers/i2c/i2c-core.c( /* of_i2c: register /soc/aips-bus@02100000/i2c@021a0000/ap3216c@1e of_i2c: register -2097126164 of_i2c_betocpup: register 30 = ie client->flags=0,client->irq=0 client->name=ap3216c */ static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap, struct device_node *node)/*node of client*/ { struct i2c_client *result; struct i2c_board_info info = {}; pr_info("of_i2c: register %s\n\n", node->full_name); const __be32 *addr = of_get_property(node, "reg", &len); pr_info("of_i2c: register %d\n\n", addr); info.of_node = of_node_get(node); info.addr = be32_to_cpup(addr); pr_info("of_i2c_betocpup: register %d\n\n", info.addr); result = i2c_new_device(adap, &info); return result; } /* i2c_new_device - instantiate an i2c device, A driver may be bound to this device when we return from this function*/ struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) { struct i2c_client *client = kzalloc(sizeof *client, GFP_KERNEL); client->adapter = adap; /*绑定adpter 与 client*/ client->dev.platform_data = info->platform_data;/*NULL*/ client->flags = info->flags; client->addr = info->addr;/*client_reg addr*/ client->irq = info->irq; strlcpy(client->name, info->type, sizeof(client->name)); pr_info("client->flags=%d,client->irq=%d\n\n",client->flags,client->irq); pr_info("client->name=%s\n\n",client->name); 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;/*node of client*/ client->dev.fwnode = info->fwnode; i2c_dev_set_name(adap, client); pr_info("%s\n\n",client->dev.kobj.name);/*1-001a*/ /*/sys/bus/i2c/devices/0-001e /sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e*/ status = device_register(&client->dev); return client; out_err_silent: kfree(client); return NULL; } static void i2c_dev_set_name(struct i2c_adapter *adap, struct i2c_client *client) { /* For 10-bit clients, add an arbitrary offset to avoid collisions */ dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), client->addr | ((client->flags & I2C_CLIENT_TEN) ? 0xa000 : 0)); } )
/*i2c_del_adapter - unregister I2C adapter*/ void i2c_del_adapter(struct i2c_adapter *adap) { struct i2c_adapter *found; struct i2c_client *client, *next; /* First make sure that this adapter was ever added */ mutex_lock(&core_lock); found = idr_find(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); if (found != adap) { pr_debug("i2c-core: attempting to delete unregistered " "adapter [%s]\n", adap->name); return; } acpi_i2c_remove_space_handler(adap); /* Tell drivers about this removal */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_removed_adapter);/*删除与adapter相匹配的client*/ mutex_unlock(&core_lock); /* Remove devices instantiated from sysfs */ mutex_lock_nested(&adap->userspace_clients_lock, i2c_adapter_depth(adap)); list_for_each_entry_safe(client, next, &adap->userspace_clients, detected) { dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name, client->addr); list_del(&client->detected); i2c_unregister_device(client); } mutex_unlock(&adap->userspace_clients_lock); /* Detach any active clients. This can't fail, thus we do not * check the returned value. This is a two-pass process, because * we can't remove the dummy devices during the first pass: they * could have been instantiated by real devices wishing to clean * them up properly, so we give them a chance to do that first. */ device_for_each_child(&adap->dev, NULL, __unregister_client); device_for_each_child(&adap->dev, NULL, __unregister_dummy); #ifdef CONFIG_I2C_COMPAT class_compat_remove_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); #endif /* device name is gone after device_unregister */ dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name); /* wait until all references to the device are gone * * FIXME: This is old code and should ideally be replaced by an * alternative which results in decoupling the lifetime of the struct * device from the i2c_adapter, like spi or netdev do. Any solution * should be throughly tested with DEBUG_KOBJECT_RELEASE enabled! */ init_completion(&adap->dev_released); device_unregister(&adap->dev); wait_for_completion(&adap->dev_released); /* free bus id */ mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); /* Clear the device structure in case this adapter is ever going to be added again */ memset(&adap->dev, 0, sizeof(adap->dev)); } drivers/base/bus.c ( int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *)) { struct klist_iter i; struct device_driver *drv; int error = 0; if (!bus) return -EINVAL; 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); klist_iter_exit(&i); return error; } ) static int __process_removed_adapter(struct device_driver *d, void *data) { i2c_do_del_adapter(to_i2c_driver(d), data); return 0; } static void i2c_do_del_adapter(struct i2c_driver *driver, struct i2c_adapter *adapter) { struct i2c_client *client, *_n; /* Remove the devices we created ourselves as the result of hardware * probing (using a driver's detect method) */ list_for_each_entry_safe(client, _n, &driver->clients, detected) { if (client->adapter == adapter) { dev_dbg(&adapter->dev, "Removing %s at 0x%x\n", client->name, client->addr); list_del(&client->detected); i2c_unregister_device(client); } } }
void device_unregister(struct device *dev) { pr_debug("device: '%s': %s\n", dev_name(dev), __func__); device_del(dev); { bus_remove_device(dev); } put_device(dev); } void bus_remove_device(struct device *dev) { struct bus_type *bus = dev->bus; struct subsys_interface *sif; if (!bus) return; mutex_lock(&bus->p->mutex); list_for_each_entry(sif, &bus->p->interfaces, node) if (sif->remove_dev) sif->remove_dev(dev, sif); mutex_unlock(&bus->p->mutex); sysfs_remove_link(&dev->kobj, "subsystem"); sysfs_remove_link(&dev->bus->p->devices_kset->kobj, dev_name(dev)); device_remove_attrs(dev->bus, dev); device_remove_groups(dev, dev->bus->dev_groups); if (klist_node_attached(&dev->p->knode_bus)) klist_del(&dev->p->knode_bus); pr_debug("bus: '%s': remove device %s\n", dev->bus->name, dev_name(dev)); device_release_driver(dev); bus_put(dev->bus); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。