I.MX6 Linux I2C device& driver hacking

/*******************************************************************************************
* 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()函数:
\\\\\\\\\\\\\\\\\\\\\\\/////////////////////// 一、跟踪板级文件:
. 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 . 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[].type, "wm8962");
mxc_i2c0_board_info[].platform_data = &wm8962_config_data; //跟踪wm8962_config_data /**
* 这里相当于注册I2C控制器
*/
imx6q_add_imx_i2c(, &mx6q_sabresd_i2c_data); //跟踪参数,函数
imx6q_add_imx_i2c(, &mx6q_sabresd_i2c_data);
imx6q_add_imx_i2c(, &mx6q_sabresd_i2c_data); /**
* 这里相当于注册I2C设备
*/
i2c_register_board_info(, mxc_i2c0_board_info, //跟踪参数,函数
ARRAY_SIZE(mxc_i2c0_board_info));
i2c_register_board_info(, mxc_i2c1_board_info,
ARRAY_SIZE(mxc_i2c1_board_info));
i2c_register_board_info(, mxc_i2c2_board_info,
ARRAY_SIZE(mxc_i2c2_board_info));
......
}
...... 二、跟踪mx6q_sabresd_i2c_data参数:
. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
......
static struct imxi2c_platform_data mx6q_sabresd_i2c_data = { //跟踪结构体
.bitrate = ,
};
...... . 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参数:
. 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(, ), //跟踪这个宏
imx6q_imx_i2c_data_entry(, ),
imx6q_imx_i2c_data_entry(, ),
};
#endif /* ifdef CONFIG_SOC_IMX6Q */
...... . cat arch/arm/plat-mxc/include/mach/devices-common.h
......
#include <mach/i2c.h>
struct imx_imx_i2c_data {
int id;
resource_size_t iobase;
resource_size_t iosize;
resource_size_t irq;
};
...... . 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)
...... . 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
...... . 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()函数:
. 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 - ,
.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));
}
...... . 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, );
}
...... . 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参数:
. 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),
},
};
...... . 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;
};
...... . cat arch/arm/mach-mx6/board-mx6q_sabresd.c
......
static struct wm8962_pdata wm8962_config_data = { //跟踪结构体
.gpio_init = {
[] = WM8962_GPIO_FN_DMICCLK,
[] = 0x8000 | WM8962_GPIO_FN_DMICDAT,
},
};
...... . 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()函数:
. 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 + ; for (status = ; 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;
}
...... . cat drivers/i2c/i2c-boardinfo.c
......
LIST_HEAD(__i2c_board_list); 一个公用的I2C链表头
EXPORT_SYMBOL_GPL(__i2c_board_list);
...... 八、I2C adapter(适配器)跟踪:
. 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); . 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 == && 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);
...... . 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, );
if (!res) {
dev_err(&pdev->dev, "can't get device resources\n");
return -ENOENT;
}
irq = platform_get_irq(pdev, );
if (irq < ) {
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, , 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(, i2c_imx->base + IMX_I2C_I2CR);
writeb(, i2c_imx->base + IMX_I2C_I2SR); /* Add I2C adapter */
ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
if (ret < ) {
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 ; /* 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设备追踪:
. cat drivers/misc/eeprom/max6875.c
......
static const struct i2c_device_id max6875_id[] = {
{ "max6875", },
{ }
}; 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 <bgardner@wabtec.com>");
MODULE_DESCRIPTION("MAX6875 driver");
MODULE_LICENSE("GPL"); module_init(max6875_init);
module_exit(max6875_exit); . cat include/linux/i2c.h
......
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver); //跟踪函数
}
...... . 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 ;
}
EXPORT_SYMBOL(i2c_register_driver);
...... . 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参数:
. cat drivers/i2c/i2c-core.c
......
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return ;
//跟踪to_i2c_adapter参数,部队i2c_do_add_adapter()函数进行跟踪,到这里结束
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
...... . cat include/linux/i2c.h
......
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
...... 十一、跟踪i2c_for_each_dev()函数:
. 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);
...... . 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 & )
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 + );
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 ; exit_remove_fake:
i2c_unregister_device(data->fake_client);
exit_kfree:
kfree(data);
return err;
}
上一篇:30种oracle常见的等待事件说明


下一篇:使用CSS/JS代码修改博客模板plus