引言

目前,各个OEM其实为了降低成本,将会用各自的渠道去购买更加物美价廉的外设。
这样,其实促进了各个厂商的竞争,而开发更加简单适配的驱动程序将会成为厂商必选项。
那么,开发一个驱动,将会有哪些点需要关注,i2c读写,interrupt实现等内容将会是本文的关注点。
这里以max8925为例,来进行一个分析。

驱动注册

在每个驱动想要被系统识别,肯定是需要进行一个声明。

module_platform_driver(max8925_power_driver);MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Power supply driver for MAX8925");
MODULE_ALIAS("platform:max8925-power");

在上面的例子中,我们看到使用了module_platform_driver函数来进行注册。

/* module_platform_driver() - Helper macro for drivers that don't do* anything special in module init/exit.  This eliminates a lot of* boilerplate.  Each module may only use this macro once, and* calling it replaces module_init() and module_exit()*/
#define module_platform_driver(__platform_driver) \module_driver(__platform_driver, platform_driver_register, \platform_driver_unregister)

这里其实只是定义了一个宏,真正的使用是在module_driver中实现。

/*** module_driver() - Helper macro for drivers that don't do anything* special in module init/exit. This eliminates a lot of boilerplate.* Each module may only use this macro once, and calling it replaces* module_init() and module_exit().** @__driver: driver name* @__register: register function for this driver type* @__unregister: unregister function for this driver type* @...: Additional arguments to be passed to __register and __unregister.** Use this macro to construct bus specific macros for registering* drivers, and do not use it on its own.*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

由上面的定义可知,module_platform_driver宏的作用就是定义指定名称的平台设备驱动注册函数和平台设备驱动注销函数。
并且在函数体内分别通过 __register函数和 __unregister() 注册和注销该平台设备驱动。
在本例中,注册的就是 max8925_power_driver了。


driver结构体实现

在本例中,max8925_power_driver 的结构体实现如下:

static struct platform_driver max8925_power_driver = {.probe    = max8925_power_probe,.remove  = max8925_power_remove,.driver = {.name   = "max8925-power",},
};
变量 成员变量 作用
probe max8925_power_probe probe函数在设备驱动注册最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用 platform设备的probe函数完成驱动注册最后工作
remove max8925_power_remove (1)卸载驱动的时候,remove调用 (2)设备移除的时候,与设备关联的驱动需要移除,remove调用
.name max8925-power 驱动的名字为max8925-power

那么我们可以知道,当注册匹配时,就可以进入probe函数进行注册的完成。

probe函数的实现

该函数一般实现较长,我们先来看看代码片段。

static int max8925_power_probe(struct platform_device *pdev)
{struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);struct power_supply_config psy_cfg = {}; /* Only for ac and usb */struct max8925_power_pdata *pdata = NULL;struct max8925_power_info *info;int ret;pdata = max8925_power_dt_init(pdev);if (!pdata) {dev_err(&pdev->dev, "platform data isn't assigned to ""power supply\n");return -EINVAL;}info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info),GFP_KERNEL);if (!info)return -ENOMEM;info->chip = chip;info->gpm = chip->i2c;info->adc = chip->adc;platform_set_drvdata(pdev, info);psy_cfg.supplied_to = pdata->supplied_to;psy_cfg.num_supplicants = pdata->num_supplicants;info->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg);if (IS_ERR(info->ac)) {ret = PTR_ERR(info->ac);goto out;}info->ac->dev.parent = &pdev->dev;info->usb = power_supply_register(&pdev->dev, &usb_desc, &psy_cfg);if (IS_ERR(info->usb)) {ret = PTR_ERR(info->usb);goto out_unregister_ac;}info->usb->dev.parent = &pdev->dev;info->battery = power_supply_register(&pdev->dev, &battery_desc, NULL);if (IS_ERR(info->battery)) {ret = PTR_ERR(info->battery);goto out_unregister_usb;}info->battery->dev.parent = &pdev->dev;info->batt_detect = pdata->batt_detect;info->topoff_threshold = pdata->topoff_threshold;info->fast_charge = pdata->fast_charge;info->set_charger = pdata->set_charger;info->no_temp_support = pdata->no_temp_support;info->no_insert_detect = pdata->no_insert_detect;max8925_init_charger(chip, info);return 0;
out_unregister_usb:power_supply_unregister(info->usb);
out_unregister_ac:power_supply_unregister(info->ac);
out:return ret;
}

因为这段是完成注册,所以我们来分析几个除了初始化以外其中的关键点。

max8925_power_dt_init

该函数其实是去传了一个platform_devices,这个的作用是什么呢?

  • 在内核初始化时通过device_node转换为platform_device,这种是最新的实现方式,基于设备树,在内核初始化时将设备树中的节点转化为platform_device;
  • 使用platform_device_register注册platform_device;
static struct max8925_power_pdata *
max8925_power_dt_init(struct platform_device *pdev)
{struct device_node *nproot = pdev->dev.parent->of_node;struct device_node *np;int batt_detect;int topoff_threshold;int fast_charge;int no_temp_support;int no_insert_detect;struct max8925_power_pdata *pdata;if (!nproot)return pdev->dev.platform_data;np = of_get_child_by_name(nproot, "charger");if (!np) {dev_err(&pdev->dev, "failed to find charger node\n");return NULL;}pdata = devm_kzalloc(&pdev->dev,sizeof(struct max8925_power_pdata),GFP_KERNEL);if (!pdata)goto ret;of_property_read_u32(np, "topoff-threshold", &topoff_threshold);of_property_read_u32(np, "batt-detect", &batt_detect);of_property_read_u32(np, "fast-charge", &fast_charge);of_property_read_u32(np, "no-insert-detect", &no_insert_detect);of_property_read_u32(np, "no-temp-support", &no_temp_support);pdata->batt_detect = batt_detect;pdata->fast_charge = fast_charge;pdata->topoff_threshold = topoff_threshold;pdata->no_insert_detect = no_insert_detect;pdata->no_temp_support = no_temp_support;ret:of_node_put(np);return pdata;
}

这里的两个关键点:

  1. 在没有使用dts的kernel驱动中,会使用 pdev->dev.platform_data 来进行注册
  2. 在使用dts的的kernel驱动中,会去调用devm_kzalloc来获取注册。

代码实现分别为:

 if (!nproot)return pdev->dev.platform_data;
 pdata = devm_kzalloc(&pdev->dev,sizeof(struct max8925_power_pdata),GFP_KERNEL);

这也是现在驱动越写越容易适配和调试的原因,会在实现时就考虑到多种情况。
注册后,如果成功,那么接下来就是platform_set_drvdata这个常用的函数了。
函数实现为:

static inline void platform_set_drvdata(struct platform_device *pdev,void *data)
{dev_set_drvdata(&pdev->dev, data);
}

继续看下这个封装的调用:

static inline void dev_set_drvdata(struct device *dev, void *data)
{dev->driver_data = data;
}

就是把data的值赋值给了dev->driver_data,传进来的device其实是pdev,而pdev是总线设备,所以这些数据对整个驱动设备都是可见的。

因为我们试一个power设备,所以power_supply_register是必须要实现的。
定义一个struct power_supply 变量,并初始化必要字段后,调用power_supply_registe将其注册到kernel中。

 info->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg);

然后就是一些具体功能的init。

max8925_init_charger(chip, info);

而在这个函数中,我们可以看到注册了很多的中端。

static int max8925_init_charger(struct max8925_chip *chip,struct max8925_power_info *info)
{int ret;REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");if (!info->no_insert_detect) {REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");}if (!info->no_temp_support) {REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range");REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range");}REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high");REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low");REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset");REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done");REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff");REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire");info->usb_online = 0;info->bat_online = 0;/* check for power - can miss interrupt at boot time */if (start_measure(info, MEASURE_VCHG) * 2000 > 500000)info->ac_online = 1;elseinfo->ac_online = 0;ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);if (ret >= 0) {/** If battery detection is enabled, ID pin of battery is* connected to MBDET pin of MAX8925. It could be used to* detect battery presence.* Otherwise, we have to assume that battery is always on.*/if (info->batt_detect)info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1;elseinfo->bat_online = 1;if (ret & MAX8925_CHG_AC_RANGE_MASK)info->ac_online = 1;elseinfo->ac_online = 0;}/* disable charge */max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);/* set charging current in charge topoff mode */max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5,info->topoff_threshold << 5);/* set charing current in fast charge mode */max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge);return 0;
}

我们在这边就不一一分析,而是只关注中断申请函数,REQUEST_IRQ。

中断的申请

仍然是以本例中的代码,我们展开分析。
REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");
REQUEST_IRQ是一个宏定义,实现如下:

#define REQUEST_IRQ(_irq, _name)                 \
do {                                    \ret = request_threaded_irq(chip->irq_base + _irq, NULL,       \max8925_charger_handler,       \IRQF_ONESHOT, _name, info);        \if (ret)                           \dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \_irq, ret);                    \
} while (0)

request_threaded_irq申请的中断,handler不在中断上下文里执行。
而是在新创建的线程里执行。
这样,该handler非常像执行workqueue。
即拥有所有workqueue的特性,但是省掉了创建,初始化,调度workqueue的繁多步骤。

我们可以看到,在这个例子里面,注册的handler为max8925_charger_handler。

中断的实现

上面注册了max8925_charger_handler,那么当出现中断的时候,就会调用对应的handler。

static irqreturn_t max8925_charger_handler(int irq, void *data)
{struct max8925_power_info *info = (struct max8925_power_info *)data;struct max8925_chip *chip = info->chip;switch (irq - chip->irq_base) {case MAX8925_IRQ_VCHG_DC_R:info->ac_online = 1;__set_charger(info, 1);dev_dbg(chip->dev, "Adapter inserted\n");break;case MAX8925_IRQ_VCHG_DC_F:info->ac_online = 0;__set_charger(info, 0);dev_dbg(chip->dev, "Adapter removed\n");break;case MAX8925_IRQ_VCHG_THM_OK_F:/* Battery is not ready yet */dev_dbg(chip->dev, "Battery temperature is out of range\n");case MAX8925_IRQ_VCHG_DC_OVP:dev_dbg(chip->dev, "Error detection\n");__set_charger(info, 0);break;case MAX8925_IRQ_VCHG_THM_OK_R:/* Battery is ready now */dev_dbg(chip->dev, "Battery temperature is in range\n");break;case MAX8925_IRQ_VCHG_SYSLOW_R:/* VSYS is low */dev_info(chip->dev, "Sys power is too low\n");break;case MAX8925_IRQ_VCHG_SYSLOW_F:dev_dbg(chip->dev, "Sys power is above low threshold\n");break;case MAX8925_IRQ_VCHG_DONE:__set_charger(info, 0);dev_dbg(chip->dev, "Charging is done\n");break;case MAX8925_IRQ_VCHG_TOPOFF:dev_dbg(chip->dev, "Charging in top-off mode\n");break;case MAX8925_IRQ_VCHG_TMR_FAULT:__set_charger(info, 0);dev_dbg(chip->dev, "Safe timer is expired\n");break;case MAX8925_IRQ_VCHG_RST:__set_charger(info, 0);dev_dbg(chip->dev, "Charger is reset\n");break;}return IRQ_HANDLED;
}

在这个函数中,我们可以看到会有不同的中断进行对应的处理。
刚才我们举例的REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");
其实也会有对应的case来完成。

 case MAX8925_IRQ_VCHG_DC_OVP:dev_dbg(chip->dev, "Error detection\n");__set_charger(info, 0);break;

以上,就是一个驱动的简单实现流程和分析。

Android power_supply驱动开发详解相关推荐

  1. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  2. linux设备驱动总结,《Linux设备驱动开发详解(第3版)》海量更新总结

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux ...

  3. linux设备驱动开发详解 第三版,《Linux设备驱动开发详解(第3版)》进展同步更新...

    2014.8.25 目前初步完成2-11章以及第22章 <Linux设备驱动的调试>,相对于第2版,这几章主要的变更. 本博实时更新<Linux设备驱动开发详解(第3版)>的最 ...

  4. 《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)进展同步更新

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 目前已经完成稿件. 2015年8月9日,china-pub开始上线预售: http://product.china-pub ...

  5. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  6. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

  7. 《Linux设备驱动开发详解》学习笔记一

    Linux设备驱动开发详解学习笔记<一> 书名:<Linux设备驱动开发详解>第二版 主机环境:Linux version 2.6.25-14.fc9.i686@Fedora ...

  8. 《Linux 设备驱动开发详解(第2版)》——1.4 Linux设备驱动

    本节书摘来自异步社区<Linux 设备驱动开发详解(第2版)>一书中的第1章,第1.1节,作者:宋宝华著,更多章节内容可以访问云栖社区"异步社区"公众号查看 1.4 L ...

  9. Linux设备驱动开发详解 第3版 (即 Linux设备驱动开发详解 基于最新的Linux 4 0内核 )前言

    Linux从未停歇脚步.Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核.做技术,从来没有终南捷径,拼的就是坐冷板凳的 ...

最新文章

  1. arcgis for android 学习 - (4) 了解mapView的一些方法和事件
  2. Android获取挂载U盘的属性
  3. DeepID2+:Deeply Learned Attributes for Crowded Scene Understanding
  4. python中index从列表中查_在Python中查找包含它的列表的项目的索引
  5. eclipse安装lombok后无法启动解决办法
  6. 博为峰JavaEE技术文章 —— Hibernate域模型(2)
  7. 前端开发 —— BOM
  8. 浅谈Solr和ElasticSearch建索引性能优化策略
  9. c++ getline 读不到东西_C++,使用getline一直读取不到文件中的内容
  10. 华为上机试题 c语言,华为上机考试题库2017 2017年全国计算机等级考试C语言上机考试题库 -1-20套.doc...
  11. buck电路matlab,matlab buck电路仿真
  12. 8086汇编学习小记-1
  13. 一步步实现windows版ijkplayer系列文章之一Windows10平台编译ffmpeg 4.0.2,生成ffplay
  14. 【每日算法Day 107】面试必考:良心推荐,一题三解,不看后悔一辈子
  15. 【5分钟paper】基于强化学习的策略搜索算法的自主直升机控制
  16. 背包问题-递归思想(C语言)
  17. 用于机器学习的数据库--UCI数据库
  18. poj 2387 最短路 spfa 实现
  19. Ceph:pg peering过程分析
  20. 【随记】无线网络能替代有线网络吗?

热门文章

  1. CF25A IQ test
  2. Unity DOTS 介绍
  3. 多角度透彻理解渐近表示法(大O表示法)
  4. MySQL高级篇——日志
  5. 基于CCD摄像头智能车分段PID控制算法设计
  6. 超像素论文(三)——AINet: Association Implantation for Superpixel Segmentation
  7. 图像处理技术及相应C++代码
  8. 第二证券|钠离子电池将迎来量产 22股净利有望高增长
  9. Android10下wifi连接的两种方式:点对点连接和外网连接
  10. Between Worlds 3 太阳与地球