博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
I.MX6 Linux I2C device& driver hacking
阅读量:5863 次
发布时间:2019-06-19

本文共 26647 字,大约阅读时间需要 88 分钟。

/******************************************************************************************* *                          I.MX6 Linux I2C device& driver hacking * 声明: *     1. 本文主要是对Linux I2C驱动进行代码跟踪,主要是为了能够对I2C驱动框架有个全面的了解; *     2. 本文源代码来自myzr_android4_2_2_1_1_0.tar.bz2; *     3. 如果你有兴趣,请尽量自己去对代码进行跟踪,这样自己会对I2C有一个框架结构上的理解; * *                                                2015-6-4 晴 深圳 南山平山村 曾剑锋 ******************************************************************************************/                     \\\\\\\\\\\\\\\\\-*- 目录 -*-/////                                       |   一、跟踪板级文件:                      |   二、跟踪mx6q_sabresd_i2c_data参数:                     |   三、跟踪imx6q_add_imx_i2c()函数:                     |   四、跟踪imx6q_imx_i2c_data参数:                     |   五、跟踪imx_add_imx_i2c()函数:                     |   六、跟踪mxc_i2c0_board_info参数:                     |   七、跟踪i2c_register_board_info()函数:                     |   八、I2C adapter(适配器)跟踪:                     |   九、I2C设备追踪:                     |   十、跟踪__process_new_driver参数:                     |   十一、跟踪i2c_for_each_dev()函数:                     |   十二、跟踪max6875_probe()函数:                     \\\\\\\\\\\\\\\\\\\\\\\///                                        一、跟踪板级文件:     1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c        ......        /*         * initialize __mach_desc_MX6Q_SABRESD data structure.         */        MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")            /* Maintainer: Freescale Semiconductor, Inc. */            .boot_params = MX6_PHYS_OFFSET + 0x100,            .fixup = fixup_mxc_board,            .map_io = mx6_map_io,            .init_irq = mx6_init_irq,            .init_machine = mx6_sabresd_board_init,   //跟踪板级初始化函数            .timer = &mx6_sabresd_timer,            .reserve = mx6q_sabresd_reserve,        MACHINE_END    2. cat arch/arm/mach-mx6/board-mx6q_sabresd.c        ......        /*!         * Board specific initialization.         */        static void __init mx6_sabresd_board_init(void)        {            ......            /**             * 接下来我们需要向以下4个方向去跟踪代码:             *     1. 跟踪mx6q_sabresd_i2c_data参数;             *     2. 跟踪imx6q_add_imx_i2c()函数;             *     3. 跟踪mxc_i2c0_board_info参数,在里面继续跟踪wm8962_config_data,             *         因为下面这两行代码修改了mxc_i2c0_board_info第一个元素;             *     4. 跟踪i2c_register_board_info()函数;             */            strcpy(mxc_i2c0_board_info[0].type, "wm8962");                mxc_i2c0_board_info[0].platform_data = &wm8962_config_data;  //跟踪wm8962_config_data            /**             * 这里相当于注册I2C控制器             */            imx6q_add_imx_i2c(0, &mx6q_sabresd_i2c_data);   //跟踪参数,函数            imx6q_add_imx_i2c(1, &mx6q_sabresd_i2c_data);            imx6q_add_imx_i2c(2, &mx6q_sabresd_i2c_data);            /**             * 这里相当于注册I2C设备             */            i2c_register_board_info(0, mxc_i2c0_board_info, //跟踪参数,函数                    ARRAY_SIZE(mxc_i2c0_board_info));            i2c_register_board_info(1, mxc_i2c1_board_info,                    ARRAY_SIZE(mxc_i2c1_board_info));            i2c_register_board_info(2, mxc_i2c2_board_info,                    ARRAY_SIZE(mxc_i2c2_board_info));            ......        }        ......二、跟踪mx6q_sabresd_i2c_data参数:    1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c        ......        static struct imxi2c_platform_data mx6q_sabresd_i2c_data = {  //跟踪结构体            .bitrate = 100000,        };        ......    2. cat arch/arm/plat-mxc/include/mach/i2c.h        ......        struct imxi2c_platform_data {            int (*init)(struct device *dev);            void (*exit)(struct device *dev);            int bitrate;        };        ......三、跟踪imx6q_add_imx_i2c()函数:    cat arch/arm/mach-mx6/devices-imx6q.h        ......        /**         * 这里需要跟踪2个方向:         *     1. 跟踪imx6q_imx_i2c_data参数;         *     2. 跟踪imx_add_imx_i2c()函数;         */        extern const struct imx_imx_i2c_data imx6q_imx_i2c_data[] __initconst;        #define imx6q_add_imx_i2c(id, pdata)    \            imx_add_imx_i2c(&imx6q_imx_i2c_data[id], pdata)   //跟踪结构体,函数        ......四、跟踪imx6q_imx_i2c_data参数:    1. cat arch/arm/plat-mxc/devices/platform-imx-i2c.c        ......        #ifdef CONFIG_SOC_IMX6Q        const struct imx_imx_i2c_data imx6q_imx_i2c_data[] __initconst = {        #define imx6q_imx_i2c_data_entry(_id, _hwid)                \            imx_imx_i2c_data_entry(MX6Q, _id, _hwid, SZ_4K)            imx6q_imx_i2c_data_entry(0, 1),     //跟踪这个宏            imx6q_imx_i2c_data_entry(1, 2),            imx6q_imx_i2c_data_entry(2, 3),        };        #endif /* ifdef CONFIG_SOC_IMX6Q */        ......    2. cat arch/arm/plat-mxc/include/mach/devices-common.h        ......        #include 
struct imx_imx_i2c_data { int id; resource_size_t iobase; resource_size_t iosize; resource_size_t irq; }; ...... 3. cat arch/arm/plat-mxc/devices/platform-imx-i2c.c ...... /** * 1. 如果传入参数是: * 1. soc = MX6Q; * 2. _id = 0; * 3. _hwid = 1; * 4. size = SZ_4K; * 2. .iobase = soc ## _I2C ## _hwid ## _BASE_ADDR合成结果: * .iobase = MX6Q_I2C1_BASE_ADDR = 0x021A0000 (看后面的跟踪代码推演计算) * 3. .irq = soc ## _INT_I2C ## _hwid合成结果: * .irq = MX6Q_INT_I2C1 = 68 */ #define imx_imx_i2c_data_entry_single(soc, _id, _hwid, _size) \ { \ .id = _id, \ .iobase = soc ## _I2C ## _hwid ## _BASE_ADDR, \ //跟踪合成后的宏 .iosize = _size, \ .irq = soc ## _INT_I2C ## _hwid, \ } #define imx_imx_i2c_data_entry(soc, _id, _hwid, _size) \ [_id] = imx_imx_i2c_data_entry_single(soc, _id, _hwid, _size) ...... 4. cat arch/arm/plat-mxc/include/mach/mx6.h ...... /** * 1. I2C1首地址推演计算: * MX6Q_I2C1_BASE_ADDR = (AIPS2_OFF_BASE_ADDR + 0x20000) * MX6Q_I2C1_BASE_ADDR = ((ATZ2_BASE_ADDR + 0x80000) + 0x20000) * MX6Q_I2C1_BASE_ADDR = ((AIPS2_ARB_BASE_ADDR + 0x80000) + 0x20000) * MX6Q_I2C1_BASE_ADDR = ((0x02100000 + 0x80000) + 0x20000) * MX6Q_I2C1_BASE_ADDR = (0x02100000 + 0x80000 + 0x20000) * MX6Q_I2C1_BASE_ADDR = (0x02100000 + 0x80000 + 0x20000) * MX6Q_I2C1_BASE_ADDR = 0x021A0000 (符合下面参考书给出的首地址) * 2. 参考书IMX6DQRM_revC.pdf给出的I2C1的首地址: * ------------------------------------------------------------- * | Start Address | End Address | Region | Allocation | Size | * +---------------+-------------+--------+-------------+------+ * | 021A_0000 | 021A_3FFF | AIPS-2 | I2C1 | 16KB | * ------------------------------------------------------------- */ #define MX6Q_I2C1_BASE_ADDR (AIPS2_OFF_BASE_ADDR + 0x20000) ...... /* ATZ#2- Off Platform */ #define AIPS2_OFF_BASE_ADDR (ATZ2_BASE_ADDR + 0x80000) ...... #define ATZ2_BASE_ADDR AIPS2_ARB_BASE_ADDR ...... #define AIPS2_ARB_BASE_ADDR 0x02100000 ...... 5. cat arch/arm/plat-mxc/include/mach/mx6.h ...... /** * 1. 参考书IMX6DQRM_revC.pdf给出的I2C1的中断号: * --------------------------------------------- * |IRQ | Interrupt | Interrupt Description | * | | Source | | * +----+------------+-------------------------+ * |68 | ECSPI1I2C1 | I2C1 interrupt request. | * --------------------------------------------- * 2. 非常精准的符合参好书 :) */ #define MX6Q_INT_I2C1 68 ......五、跟踪imx_add_imx_i2c()函数: 1. cat arch/arm/plat-mxc/devices/platform-imx-i2c.c ...... struct platform_device *__init imx_add_imx_i2c( const struct imx_imx_i2c_data *data, const struct imxi2c_platform_data *pdata) { //利用传入参数合成平台资源数据 struct resource res[] = { { .start = data->iobase, .end = data->iobase + data->iosize - 1, .flags = IORESOURCE_MEM, }, { .start = data->irq, .end = data->irq, .flags = IORESOURCE_IRQ, }, }; /** * 从这里可以知道设备匹配时候的名字,跟踪该函数 */ return imx_add_platform_device("imx-i2c", data->id, res, ARRAY_SIZE(res), pdata, sizeof(*pdata)); } ...... 2. cat arch/arm/plat-mxc/include/mach/devices-common.h ...... static inline struct platform_device *imx_add_platform_device( const char *name, int id, const struct resource *res, unsigned int num_resources, const void *data, size_t size_data) { //跟踪该函数 return imx_add_platform_device_dmamask( name, id, res, num_resources, data, size_data, 0); } ...... 3. cat arch/arm/plat-mxc/devices.c ...... struct platform_device *__init imx_add_platform_device_dmamask( const char *name, int id, const struct resource *res, unsigned int num_resources, const void *data, size_t size_data, u64 dmamask) { int ret = -ENOMEM; struct platform_device *pdev; pdev = platform_device_alloc(name, id); if (!pdev) goto err; ...... if (res) { ret = platform_device_add_resources(pdev, res, num_resources); if (ret) goto err; } if (data) { ret = platform_device_add_data(pdev, data, size_data); if (ret) goto err; } ret = platform_device_add(pdev); //设备注册 if (ret) { err: if (dmamask) kfree(pdev->dev.dma_mask); platform_device_put(pdev); return ERR_PTR(ret); } return pdev; } ......六、跟踪mxc_i2c0_board_info参数: 1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c ...... static struct i2c_board_info mxc_i2c0_board_info[] __initdata = { //跟踪结构体 /** * 这是板级初始化函数中的另一部分: * strcpy(mxc_i2c0_board_info[0].type, "wm8962"); * mxc_i2c0_board_info[0].platform_data = &wm8962_config_data; //跟踪目标wm8962_config_data * * 通过这里可以知道wm8962的I2C地址是:0x1a */ { I2C_BOARD_INFO("wm89**", 0x1a), }, { I2C_BOARD_INFO("ov564x", 0x3c), .platform_data = (void *)&camera_data, }, { I2C_BOARD_INFO("mma8451", 0x1d), .platform_data = (void *)&mma8451_position, }, { I2C_BOARD_INFO("isl1208", 0x6f), }, }; ...... 2. cat include/linux/i2c.h ...... 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; }; ...... 3. cat arch/arm/mach-mx6/board-mx6q_sabresd.c ...... static struct wm8962_pdata wm8962_config_data = { //跟踪结构体 .gpio_init = { [2] = WM8962_GPIO_FN_DMICCLK, [4] = 0x8000 | WM8962_GPIO_FN_DMICDAT, }, }; ...... 4. cat include/sound/wm8962.h ...... struct wm8962_pdata { int gpio_base; u32 gpio_init[WM8962_MAX_GPIO]; /* Setup for microphone detection, raw value to be written to * R48(0x30) - only microphone related bits will be updated. * Detection may be enabled here for use with signals brought * out on the GPIOs. */ u32 mic_cfg; bool irq_active_low; bool spk_mono; /* Speaker outputs tied together as mono */ }; ......七、跟踪i2c_register_board_info()函数: 1. cat drivers/i2c/i2c-boardinfo.c ...... int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) { int status; down_write(&__i2c_board_lock); /* dynamic bus numbers will be assigned after the last static one */ 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; } devinfo->busnum = busnum; devinfo->board_info = *info; list_add_tail(&devinfo->list, &__i2c_board_list); //跟踪__i2c_board_list } up_write(&__i2c_board_lock); return status; } ...... 2. cat drivers/i2c/i2c-boardinfo.c ...... LIST_HEAD(__i2c_board_list); 一个公用的I2C链表头 EXPORT_SYMBOL_GPL(__i2c_board_list); ......八、I2C adapter(适配器)跟踪: 1. cat drivers/i2c/busses/i2c-imx.c ...... /* This will be the driver name the kernel reports */ #define DRIVER_NAME "imx-i2c" //adapter和I2C控制器匹配的名字 /* Default value */ #define IMX_I2C_BIT_RATE 100000 /* 100kHz */ static struct platform_driver i2c_imx_driver = { .remove = __exit_p(i2c_imx_remove), .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, } }; static int __init i2c_adap_imx_init(void) { return platform_driver_probe(&i2c_imx_driver, i2c_imx_probe); //跟踪函数,参数 } subsys_initcall(i2c_adap_imx_init); static void __exit i2c_adap_imx_exit(void) { platform_driver_unregister(&i2c_imx_driver); } module_exit(i2c_adap_imx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Darius Augulis"); MODULE_DESCRIPTION("I2C adapter driver for IMX I2C bus"); //这里说明了这个驱动的作用 MODULE_ALIAS("platform:" DRIVER_NAME); 2. cat drivers/base/platform.c ...... int __init_or_module platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *)) { int retval, code; /* make sure driver won't have bind/unbind attributes */ drv->driver.suppress_bind_attrs = true; /* temporary section violation during probe() */ /** * 主要注意下面这行代码 */ drv->probe = probe; retval = code = platform_driver_register(drv); /* * Fixup that section violation, being paranoid about code scanning * the list of drivers in order to probe new devices. Check to see * if the probe was successful, and make sure any forced probes of * new devices fail. */ spin_lock(&drv->driver.bus->p->klist_drivers.k_lock); drv->probe = NULL; if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list)) retval = -ENODEV; drv->driver.probe = platform_drv_probe_fail; spin_unlock(&drv->driver.bus->p->klist_drivers.k_lock); if (code != retval) platform_driver_unregister(drv); return retval; } EXPORT_SYMBOL_GPL(platform_driver_probe); ...... 3. cat drivers/i2c/busses/i2c-imx.c ...... static int __init i2c_imx_probe(struct platform_device *pdev) { struct imx_i2c_struct *i2c_imx; struct resource *res; struct imxi2c_platform_data *pdata; void __iomem *base; resource_size_t res_size; int irq; int ret; dev_dbg(&pdev->dev, "<%s>\n", __func__); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "can't get device resources\n"); return -ENOENT; } irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "can't get irq number\n"); return -ENOENT; } pdata = pdev->dev.platform_data; if (pdata && pdata->init) { ret = pdata->init(&pdev->dev); if (ret) return ret; } res_size = resource_size(res); if (!request_mem_region(res->start, res_size, DRIVER_NAME)) { ret = -EBUSY; goto fail0; } base = ioremap(res->start, res_size); if (!base) { dev_err(&pdev->dev, "ioremap failed\n"); ret = -EIO; goto fail1; } i2c_imx = kzalloc(sizeof(struct imx_i2c_struct), GFP_KERNEL); if (!i2c_imx) { dev_err(&pdev->dev, "can't allocate interface\n"); ret = -ENOMEM; goto fail2; } /* Setup i2c_imx driver structure */ strcpy(i2c_imx->adapter.name, pdev->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->irq = irq; i2c_imx->base = base; i2c_imx->res = res; /* Get I2C clock */ i2c_imx->clk = clk_get(&pdev->dev, "i2c_clk"); if (IS_ERR(i2c_imx->clk)) { ret = PTR_ERR(i2c_imx->clk); dev_err(&pdev->dev, "can't get I2C clock\n"); goto fail3; } /* Request IRQ */ ret = request_irq(i2c_imx->irq, i2c_imx_isr, 0, pdev->name, i2c_imx); if (ret) { dev_err(&pdev->dev, "can't claim irq %d\n", i2c_imx->irq); goto fail4; } /* Init queue */ init_waitqueue_head(&i2c_imx->queue); /* Set up adapter data */ i2c_set_adapdata(&i2c_imx->adapter, i2c_imx); /* Set up clock divider */ if (pdata && pdata->bitrate) i2c_imx_set_clk(i2c_imx, pdata->bitrate); else i2c_imx_set_clk(i2c_imx, IMX_I2C_BIT_RATE); /* Set up chip registers to defaults */ writeb(0, i2c_imx->base + IMX_I2C_I2CR); writeb(0, i2c_imx->base + IMX_I2C_I2SR); /* Add I2C adapter */ ret = i2c_add_numbered_adapter(&i2c_imx->adapter); if (ret < 0) { dev_err(&pdev->dev, "registration failed\n"); goto fail5; } /* Set up platform driver data */ platform_set_drvdata(pdev, i2c_imx); dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", i2c_imx->irq); dev_dbg(&i2c_imx->adapter.dev, "device resources from 0x%x to 0x%x\n", i2c_imx->res->start, i2c_imx->res->end); dev_dbg(&i2c_imx->adapter.dev, "allocated %d bytes at 0x%x \n", res_size, i2c_imx->res->start); dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n", i2c_imx->adapter.name); dev_dbg(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n"); return 0; /* Return OK */ fail5: free_irq(i2c_imx->irq, i2c_imx); fail4: clk_put(i2c_imx->clk); fail3: kfree(i2c_imx); fail2: iounmap(base); fail1: release_mem_region(res->start, resource_size(res)); fail0: if (pdata && pdata->exit) pdata->exit(&pdev->dev); return ret; /* Return error number */ } ......九、I2C设备追踪: 1. cat drivers/misc/eeprom/max6875.c ...... static const struct i2c_device_id max6875_id[] = { { "max6875", 0 }, { } }; static struct i2c_driver max6875_driver = { .driver = { .name = "max6875", }, .probe = max6875_probe, //跟踪函数 .remove = max6875_remove, .id_table = max6875_id, }; static int __init max6875_init(void) { return i2c_add_driver(&max6875_driver); //跟踪函数 } static void __exit max6875_exit(void) { i2c_del_driver(&max6875_driver); } MODULE_AUTHOR("Ben Gardner
"); MODULE_DESCRIPTION("MAX6875 driver"); MODULE_LICENSE("GPL"); module_init(max6875_init); module_exit(max6875_exit); 2. cat include/linux/i2c.h ...... static inline int i2c_add_driver(struct i2c_driver *driver) { return i2c_register_driver(THIS_MODULE, driver); //跟踪函数 } ...... 3. cat drivers/i2c/i2c-core.c ...... int i2c_register_driver(struct module *owner, struct i2c_driver *driver) { ...... 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); //这里就跟踪到这里结束了 ...... /* Walk the adapters that are already present */ i2c_for_each_dev(driver, __process_new_driver); //跟踪这个函数 return 0; } EXPORT_SYMBOL(i2c_register_driver); ...... 4. cat drivers/i2c/i2c-core.c ...... struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm = &i2c_device_pm_ops, }; EXPORT_SYMBOL_GPL(i2c_bus_type); ...... 十、跟踪__process_new_driver参数: 1. cat drivers/i2c/i2c-core.c ...... static int __process_new_driver(struct device *dev, void *data) { if (dev->type != &i2c_adapter_type) return 0; //跟踪to_i2c_adapter参数,部队i2c_do_add_adapter()函数进行跟踪,到这里结束 return i2c_do_add_adapter(data, to_i2c_adapter(dev)); } ...... 2. cat include/linux/i2c.h ...... #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev) ......十一、跟踪i2c_for_each_dev()函数: 1. cat drivers/i2c/i2c-core.c ...... int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *)) { int res; mutex_lock(&core_lock); res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn); //跟踪函数 mutex_unlock(&core_lock); return res; } EXPORT_SYMBOL_GPL(i2c_for_each_dev); ...... 2. cat drivers/base/bus.c ...... /** * 这一部分只跟踪到这里,因为从函数名,你可以知道没必要再跟下去了 */ int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *)) { /** * * struct bus_type { * ...... * * struct subsys_private *p; * }; * * struct subsys_private { * ...... * struct klist klist_devices; * struct klist klist_drivers; * ...... * }; * * struct device { * ...... * struct device_private *p; * ...... * }; * * struct device_private { * ...... * struct klist_node knode_bus; * void *driver_data; * struct device *device; * }; */ struct klist_iter i; struct device *dev; ...... 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); klist_iter_exit(&i); return error; } EXPORT_SYMBOL_GPL(bus_for_each_dev); ......十二、跟踪max6875_probe()函数: cat drivers/misc/eeprom/max6875.c ...... static int max6875_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = client->adapter; struct max6875_data *data; int err; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA | I2C_FUNC_SMBUS_READ_BYTE)) return -ENODEV; /* Only bind to even addresses */ if (client->addr & 1) return -ENODEV; if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL))) return -ENOMEM; /* A fake client is created on the odd address */ data->fake_client = i2c_new_dummy(client->adapter, client->addr + 1); if (!data->fake_client) { err = -ENOMEM; goto exit_kfree; } /* Init real i2c_client */ i2c_set_clientdata(client, data); mutex_init(&data->update_lock); err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr); if (err) goto exit_remove_fake; return 0; exit_remove_fake: i2c_unregister_device(data->fake_client); exit_kfree: kfree(data); return err; }

 

转载于:https://www.cnblogs.com/zengjfgit/p/4553668.html

你可能感兴趣的文章
转 博弈类题目小结(hdu,poj,zoj)
查看>>
Team Project Specification–IP Domain search tool
查看>>
mk、cd、pwd、ls、touch、vi、cat、cp、mv的使用及命令快捷方式
查看>>
关于指针的传值与传址
查看>>
[转]JS获取URL传参方法
查看>>
HDU-1066 Last non-zero Digit in N!
查看>>
WEBSERVICE-AXIS2服务端代码
查看>>
第一章 C语言概述
查看>>
java中字符串的非空判断
查看>>
关于int main(int argc,char* argv[])详解
查看>>
linux-软件下载安装
查看>>
C# DateTime判断时间
查看>>
C# Combox控件绑定自定义数据
查看>>
java错题本
查看>>
apply()与call()的区别
查看>>
QT 读取txt文件的几种方法
查看>>
centos7下误执行chmod -R 777 /后的权限修复方法
查看>>
SIGSEGV 和 SIGBUS & gdb看汇编
查看>>
argparse
查看>>
CSS布局
查看>>