Kernel的IIC驱动分析
涉及到的文件:
drivers/i2c/i2c-core.c
drivers/i2c/i2c-dev.c
drivers/i2c/busses/i2c-imx.c
等等
在下面分析的代码中,不想关或者不重要的,我会省略掉。
1. 适配器设备的注册
在Linux内核启动的过程中,会调用到mx6_sabresd_board_init函数
static void __init mx6_sabresd_board_init(void) { 。。。省略。。。 /*在这里修改了i2c0适配器上设备的类型,同时添加了平台数据,后面会分析到*/ strcpy(mxc_i2c0_board_info[].type, "wm8962"); mxc_i2c0_board_info[].platform_data = &wm8962_config_data; /*第一步:注册适配器设备:详见下面分析*/ imx6q_add_imx_i2c(, &mx6q_sabresd_i2c_data); /*第二步:添加I2C从设备*/ i2c_register_board_info(, mxc_i2c0_board_info, ARRAY_SIZE(mxc_i2c0_board_info)); 。。。省略。。。 }
下面我们来分析第一步:注册适配器设备
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)
接着追到imx_add_imx_i2c,我们可以知道
1). 通过imx_add_platform_device ,我们知道I2C的适配器设备是放入了平台总线模型当中,并且名字为imx-i2c。
2). 第一个参数imx_imx_i2c_data是适配器的设备信息,包括I2C寄存器地址,中断号,资源res是通过data的重新赋值。它包括了I2C寄存器的基地址,中断号,总线编号。第二个参数
imxi2c_platform_data是存放在平台上的数据,下面我们来分析这两个参数。 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)); }
我们先分析第一个参数imx_add_platform_device:
我们往回查看上述函数的关键数据
imx_imx_i2c_data* data= imx6q_imx_i2c_data[] 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(, ), };
接着追imx_imx_i2c_data_entry
#define imx_imx_i2c_data_entry(soc, _id, _hwid, _size) \
[_id] = imx_imx_i2c_data_entry_single(soc, _id, _hwid, _size)
再接着追imx_imx_i2c_data_entry
#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, \ }
通过一层层的代入展开,可以推导出:
imx6q_imx_i2c_data[]= { .id = , .iobase = MX6Q_I2C1_BASE_ADDR, .iosize = _SZ_4K, .irq = MX6Q_INT_I2C1, }
第二个参数mx6q_sabresd_i2c_data是时钟系数:
static struct imxi2c_platform_data mx6q_sabresd_i2c_data = { .bitrate = , };
2. 适配器驱动的流程
从之前的代码,我们得知适配器设备在平台驱动模型中的名字为imx-i2c。
其代码在drivers/i2c/busses/i2c-imx.c
static struct platform_driver i2c_imx_driver = { .remove = __exit_p(i2c_imx_remove), .driver = { .name = DRIVER_NAME, /*imx-i2c*/ .owner = THIS_MODULE, } }; /*通过平台模型注册*/ static int __init i2c_adap_imx_init(void) { return platform_driver_probe(&i2c_imx_driver, i2c_imx_probe); }
当名字imx-i2c匹配上的时候,会自动调用探测函数i2c_imx_probe,其原理属于平台驱动模型。在这里面会对I2C进行初始化:设置始终,初始化I2C寄存器,设置中断等。
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__); /*获取来自适配器设备上的内存资源:I2C寄存器地址*/ 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; /* 设置I2C时钟 */ 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; } /* 设置中断 */ 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_waitqueue_head(&i2c_imx->queue); /* 设置适配器数据 */ i2c_set_adapdata(&i2c_imx->adapter, i2c_imx); /* 设置时钟分频 */ if (pdata && pdata->bitrate) i2c_imx_set_clk(i2c_imx, pdata->bitrate); else i2c_imx_set_clk(i2c_imx, IMX_I2C_BIT_RATE); /* 设置I2C的寄存器 */ writeb(, i2c_imx->base + IMX_I2C_I2CR); writeb(, i2c_imx->base + IMX_I2C_I2SR); /* 注册适配器驱动:这个我们后面会分析 */ ret = i2c_add_numbered_adapter(&i2c_imx->adapter); if (ret < ) { dev_err(&pdev->dev, "registration failed\n"); goto fail5; } /* 将i2c_imx数据放入平台总线中 */ 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功能,提供判断*/
static u32 i2c_imx_func(struct i2c_adapter *adapter) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static struct i2c_algorithm i2c_imx_algo = { .master_xfer = i2c_imx_xfer, /*具体的传输的实现*/ .functionality = i2c_imx_func, /*传输的方式*/ }; static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num) { unsigned int i, temp; int result; /*将该适配器上的数据取出来*/ struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter); dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); /* I2C起始位 */ result = i2c_imx_start(i2c_imx); if (result) goto fail0; /* 对数据进行读写 */ for (i = ; i < num; i++) { if (i) { dev_dbg(&i2c_imx->adapter.dev, "<%s> repeated start\n", __func__); temp = readb(i2c_imx->base + IMX_I2C_I2CR); temp |= I2CR_RSTA; writeb(temp, i2c_imx->base + IMX_I2C_I2CR); result = i2c_imx_bus_busy(i2c_imx, ); if (result) goto fail0; } dev_dbg(&i2c_imx->adapter.dev, "<%s> transfer message: %d\n", __func__, i); /* write/read data */ #ifdef CONFIG_I2C_DEBUG_BUS temp = readb(i2c_imx->base + IMX_I2C_I2CR); dev_dbg(&i2c_imx->adapter.dev, "<%s> CONTROL: IEN=%d, IIEN=%d, " "MSTA=%d, MTX=%d, TXAK=%d, RSTA=%d\n", __func__, (temp & I2CR_IEN ? : ), (temp & I2CR_IIEN ? : ), (temp & I2CR_MSTA ? : ), (temp & I2CR_MTX ? : ), (temp & I2CR_TXAK ? : ), (temp & I2CR_RSTA ? : )); temp = readb(i2c_imx->base + IMX_I2C_I2SR); dev_dbg(&i2c_imx->adapter.dev, "<%s> STATUS: ICF=%d, IAAS=%d, IBB=%d, " "IAL=%d, SRW=%d, IIF=%d, RXAK=%d\n", __func__, (temp & I2SR_ICF ? : ), (temp & I2SR_IAAS ? : ), (temp & I2SR_IBB ? : ), (temp & I2SR_IAL ? : ), (temp & I2SR_SRW ? : ), (temp & I2SR_IIF ? : ), (temp & I2SR_RXAK ? : )); #endif if (msgs[i].flags & I2C_M_RD) result = i2c_imx_read(i2c_imx, &msgs[i]); else result = i2c_imx_write(i2c_imx, &msgs[i]); if (result) goto fail0; } fail0: /* 停止位 */ i2c_imx_stop(i2c_imx); dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__, (result < ) ? "error" : "success msg", (result < ) ? result : num); return (result < ) ? result : num; }
这个函数里面得到所有函数就是最底层真正对硬件的操作了,比如:
static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) { unsigned int temp = ; struct imxi2c_platform_data *pdata; int result; dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); /* Currently on Arik/Rigel, the I2C clk is from IPG_PERCLK which is * sourced from IPG_CLK. In low bus freq mode, IPG_CLK is at 12MHz * and IPG_PERCLK is down to 4MHz. * Update I2C divider before set i2c clock. */ pdata = i2c_imx->adapter.dev.parent->platform_data; if (pdata && pdata->bitrate) i2c_imx_set_clk(i2c_imx, pdata->bitrate); else i2c_imx_set_clk(i2c_imx, IMX_I2C_BIT_RATE); clk_enable(i2c_imx->clk); writeb(i2c_imx->ifdr, i2c_imx->base + IMX_I2C_IFDR); /* Enable I2C controller */ writeb(, i2c_imx->base + IMX_I2C_I2SR); writeb(I2CR_IEN, i2c_imx->base + IMX_I2C_I2CR); /* Wait controller to be stable */ udelay(); /* Start I2C transaction */ temp = readb(i2c_imx->base + IMX_I2C_I2CR); temp |= I2CR_MSTA; writeb(temp, i2c_imx->base + IMX_I2C_I2CR); result = i2c_imx_bus_busy(i2c_imx, ); if (result) return result; i2c_imx->stopped = ; temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK; writeb(temp, i2c_imx->base + IMX_I2C_I2CR); return result; }
在i2c_add_numbered_adapter中会对最后的适配器驱动进行注册,在注册之前还会对I2C设备驱动进行注册。
int i2c_add_numbered_adapter(struct i2c_adapter *adap) { int id; int status; if (adap->nr & ~MAX_ID_MASK) return -EINVAL; retry: if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == ) return -ENOMEM; mutex_lock(&core_lock); /* "above" here means "above or equal to", sigh; * we need the "equal to" result to force the result */ status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id); if (status == && id != adap->nr) { status = -EBUSY; idr_remove(&i2c_adapter_idr, id); } mutex_unlock(&core_lock); if (status == -EAGAIN) goto retry; if (status == ) /*在这里真正注册I2C适配器*/ status = i2c_register_adapter(adap); return status; } static int i2c_register_adapter(struct i2c_adapter *adap) { int res = ; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) { res = -EAGAIN; goto out_list; } /* Sanity checks */ if (unlikely(adap->name[] == '\0')) { pr_err("i2c-core: Attempt to register an adapter with " "no name!\n"); return -EINVAL; } 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 */ if (adap->timeout == ) adap->timeout = HZ; dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; res = device_register(&adap->dev); /*注册*/ if (res) goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); if (res) dev_warn(&adap->dev, "Failed to create compatibility class link\n"); #endif /* 在这里对已经添加好的I2C设备进行注册 */ if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); /* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); return ; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; } static void i2c_scan_static_board_info(struct i2c_adapter *adapter) { struct i2c_devinfo *devinfo; down_read(&__i2c_board_lock); list_for_each_entry(devinfo, &__i2c_board_list, list) { if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter, &devinfo->board_info)) dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr); } up_read(&__i2c_board_lock); }
3. I2C从设备的添加
int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) { int status; down_write(&__i2c_board_lock); /* */ 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); } up_write(&__i2c_board_lock); return status; }
4. AT24对于I2C的读写
只研究I2C,所以只是看下I2C在AT24C02中大概是如何使用,其他代码都省略
static struct i2c_driver at24_driver = { .driver = { .name = "at24", .owner = THIS_MODULE, }, .probe = at24_probe, .remove = __devexit_p(at24_remove), .id_table = at24_ids, /*这是一个包含所有支持名字的列表,只要注册的设备里有其中一个名字匹配就匹配成功*/ }; static int __init at24_init(void) { 。。。省略。。。 /*注册从设备驱动*/ return i2c_add_driver(&at24_driver); } module_init(at24_init); static inline int i2c_add_driver(struct i2c_driver *driver) { return i2c_register_driver(THIS_MODULE, driver); }
从设备的注册函数如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *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; /*注册设备*/ res = driver_register(&driver->driver); 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); return ; } static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) { if (client->dev.platform_data) { chip = *(struct at24_platform_data *)client->dev.platform_data; } else { if (!id->driver_data) { err = -ENODEV; goto err_out; } /* Use I2C operations unless we're stuck with SMBus extensions. */ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { if (chip.flags & AT24_FLAG_ADDR16) { err = -EPFNOSUPPORT; goto err_out; } /*查看支持的模式*/ if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { use_smbus = I2C_SMBUS_I2C_BLOCK_DATA; } else if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_WORD_DATA)) { use_smbus = I2C_SMBUS_WORD_DATA; } else if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) { use_smbus = I2C_SMBUS_BYTE_DATA; } else { err = -EPFNOSUPPORT; goto err_out; } } if (chip.flags & AT24_FLAG_TAKE8ADDR) num_addresses = ; else num_addresses = DIV_ROUND_UP(chip.byte_len, (chip.flags & AT24_FLAG_ADDR16) ? : ); at24 = kzalloc(sizeof(struct at24_data) + num_addresses * sizeof(struct i2c_client *), GFP_KERNEL); if (!at24) { err = -ENOMEM; goto err_out; } mutex_init(&at24->lock); at24->use_smbus = use_smbus; at24->chip = chip; at24->num_addresses = num_addresses; /* * Export the EEPROM bytes through sysfs, since that's convenient. * By default, only root should see the data (maybe passwords etc) */ sysfs_bin_attr_init(&at24->bin); at24->bin.attr.name = "eeprom"; at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR; at24->bin.read = at24_bin_read; at24->bin.size = chip.byte_len; at24->macc.read = at24_macc_read; writable = !(chip.flags & AT24_FLAG_READONLY); if (writable) { if (!use_smbus || i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { unsigned write_max = chip.page_size; at24->macc.write = at24_macc_write; at24->bin.write = at24_bin_write; at24->bin.attr.mode |= S_IWUSR; if (write_max > io_limit) write_max = io_limit; if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX) write_max = I2C_SMBUS_BLOCK_MAX; at24->write_max = write_max; /* buffer (data + address at the beginning) */ at24->writebuf = kmalloc(write_max + , GFP_KERNEL); if (!at24->writebuf) { err = -ENOMEM; goto err_struct; } } else { dev_warn(&client->dev, "cannot write due to controller restrictions."); } } } static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { struct at24_data *at24; at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); return at24_read(at24, buf, off, count); }
然后一直往下追,在某部分功能会看到
static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf, unsigned offset, size_t count) { status = i2c_transfer(client->adapter, msg, ); }
drivers.c/i2c/i2c-core.c
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { unsigned long orig_jiffies; int ret, try; if (adap->algo->master_xfer) { if (in_atomic() || irqs_disabled()) { ret = i2c_trylock_adapter(adap); if (!ret) /* I2C activity is ongoing. */ return -EAGAIN; } else { i2c_lock_adapter(adap); } /* Retry automatically on arbitration loss */ orig_jiffies = jiffies; for (ret = , try = ; try <= adap->retries; try++) { ret = adap->algo->master_xfer(adap, msgs, num); if (ret != -EAGAIN) break; if (time_after(jiffies, orig_jiffies + adap->timeout)) break; } i2c_unlock_adapter(adap); return ret; } else { dev_dbg(&adap->dev, "I2C level transfers not supported\n"); return -EOPNOTSUPP; } }
在这里,我们看到了adap->algo->master_xfer,也就是底层最终的传输函数。
Kernel的IIC驱动分析的更多相关文章
- IIC驱动分析
IIC设备是一种通过IIC总线连接的设备,由于其简单性,被广泛引用于电子系统中.在现代电子系统中,有很多的IIC设备需要进行相互之间通信 IIC总线是由PHILIPS公司开发的两线式串行总线,用于连接 ...
- IIC接口下的24C02 驱动分析
本节来学习IIC接口下的24C02 驱动分析,本节学完后,再来学习Linux下如何使用IIC操作24C02 1.I2C通信介绍 它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多 ...
- 九、IIC驱动原理分析
学习目标:学习IIC驱动原理: 一.IIC总线协议 IIC串行总线包括一条数据线(SDA)和一条时钟线(SCL),支持“一主多从”和“多主机”模式:每个从机设备都有唯一的地址来识别. 图 1 IIC ...
- 【转】android电池(四):电池 电量计(MAX17040)驱动分析篇
关键词:android 电池 电量计 MAX17040 任务初始化宏 power_supply 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台: ...
- android电池(四):电池 电量计(MAX17040)驱动分析篇【转】
本文转载自:http://blog.csdn.net/xubin341719/article/details/8969369 电池电量计,库仑计,用max17040这颗电量IC去计量电池电量,这种方法 ...
- Mini2440 DM9000 驱动分析(一)
Mini2440 DM9000 驱动分析(一) 硬件特性 Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系: PW_RST 连接到复位按键,复位按键按下,低电平 ...
- RM-Linux驱动--Watch Dog Timer(看门狗)驱动分析
from:http://blog.csdn.net/geekcome/article/details/6595265 硬件平台:FL2440 内核版本:2.6.28 主机平台:Ubuntu 11,04 ...
- 【转】android电池(五):电池 充电IC(PM2301)驱动分析篇
关键词:android 电池 电量计 PL2301任务初始化宏 power_supply 中断线程化 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 ...
- android电池(五):电池 充电IC(PM2301)驱动分析篇【转】
本文转载自:http://blog.csdn.net/xubin341719/article/details/8970363 android充电这块,有的电源管理芯片内部包含充电管理,如s5pv210 ...
随机推荐
- 3:C#异步WaitAll的使用
编写界面如图: private async void button1_Click(object sender, EventArgs e) { #region 单个执行的异步,效率慢 HttpClien ...
- 【转】Leader-Follower线程模型
上图就是L/F多线程模型的状态变迁图,共6个关键点: (1)线程有3种状态:领导leading,处理processing,追随following (2)假设共N个线程,其中只有1个leading线程( ...
- Promise之你看得懂的Promise
本文由作者陈旭锋(任职网易考拉)授权网易云社区发布. Promise源码详解 学习知识要善于思考,思考,再思考. -- 爱因斯坦 1.回调地狱 曾几何时,我们的代码是这样的,为了拿到回调的结果,不得不 ...
- Impala源码之资源管理与资源隔离
本文由 网易云发布. 前言 Impala是一个MPP架构的查询系统,为了做到平台化服务,首先需要考虑就是如何做到资源隔离,多个产品之间尽可能小的甚至毫无影响.对于这种需求,最好的隔离方案无疑是物理机 ...
- python开发工具之分析
开发工具篇之工具分析 任务:开发python程序环境:编辑器+解释器 [原始开发python] 环境:安装python (提供python解释器,命令行shell窗口,简易python编译器,第三方库 ...
- Python3.5 学习二
模块/库: Python的强大在于丰富的各种库的存在. 用import方法导入的 分为标准库.第三方库 程序运行时会先从当前目录下寻找import的模块名的文件,如果没有,则去全局环境变量对应的路径 ...
- java学习笔记—第三方数据库连接池包1(29)
第一步:导入dbcp包 第二步:通过核心类连接数据 BasicDataSource它是javax.sql.DataSrouce的子类. 一个工具类:BasicDataSourceFactory. 手工 ...
- java中数组的插入
package com.hxzy.demo; import java.util.Arrays;import java.util.Scanner; public class Demo1 { public ...
- ZJOI2019 游记
Day -2 入住酒店,跟 Sooke 一个房间 酒店还是很好的呢 然后就是颓废 Sooke 和 zxy,ljx,lyt,xx 一起打 lol,我孤独的打着坦克和 MC 然后复习了一下板子 Day - ...
- LOJ#2190. 「SHOI2014」信号增幅仪(最小圆覆盖)
题面 传送门 题解 我连椭圆是个啥都不知道导致这么简单一道题我一点思路都没有-- 我们把坐标系旋转一下,让半长轴成为新的\(x\)轴,也就是说所有点都绕原点逆时针旋转\(360-a\)度,然后再把所有 ...