今天大体看了下Android的Motor驱动模块,这部分高通都做好了,拿来就用,基本不需要修改,所以一直没有看,有空看了下,特作记录。Vibrator是基于Linux的timed output驱动框架的,基本的结构就是timed_output_dev struct,其中涉及的知识还有定时器和消息队列。只介绍driver这一部分,更详细的内容可以搜索CONFIG_MSM_PMIC_VIBRATOR这个配置来了解。
一、设备结点
设备节点在调试驱动时非常有用,直接adb shell通过读写文件可以验证驱动是否OK。
- # echo ‘10000’> /sys/class/timed_output/vibrator/enable
- # cat /sys/class/timed_output/vibrator/enable
- 3290
- # echo ‘0’> /sys/class/timed_output/vibrator/enable
向enable文件写入成功,就立即震动,震动的持续时间即是写入的值,单位为ms,可以通过读enable文件来获得震动剩余的时间。
二、相关的文件
1.kernel/arch/arm/mach-msm/msm_vibrator.c
这是底层的驱动文件,编译到boot.img,几个主要的函数如下:
#define PMIC_VIBRATOR_LEVEL (3000) //设置震动强度,3000mv static struct work_struct work_vibrator; static int vibe_state; //记录motor的状态 static struct hrtimer vibe_timer; static DEFINE_MUTEX(vibe_mtx); static spinlock_t vibe_lock; static void update_vibrator(struct work_struct *work) { set_pmic_vibrator(vibe_state); } //star timer or not,schedule work.NOT control vibrator voltage! static void vibrator_enable(struct timed_output_dev *dev, int value) { unsigned long flags; spin_lock_irqsave(&vibe_lock, flags); hrtimer_cancel(&vibe_timer); if (value == 0) vibe_state = 0; else { value = (value > 15000 ? 15000 : value); vibe_state = 1; hrtimer_start(&vibe_timer, ktime_set(value / 1000, (value % 1000) * 1000000), HRTIMER_MODE_REL); } spin_unlock_irqrestore(&vibe_lock, flags); schedule_work(&work_vibrator);//执行这个函数会立即导致work_vibrator这个work_struct中的func函数被调用 } static int vibrator_get_time(struct timed_output_dev *dev) { if (hrtimer_active(&vibe_timer)) { ktime_t r = hrtimer_get_remaining(&vibe_timer); return r.tv.sec * 1000 + r.tv.nsec / 1000000; } else return 0; } //turn ON/OFF Motor static void set_pmic_vibrator(int on) { static struct msm_rpc_endpoint *vib_endpoint; struct set_vib_on_off_req { struct rpc_request_hdr hdr; uint32_t data; } req; if(mutex_lock_interruptible(&vibe_mtx)) return; if (!vib_endpoint) { vib_endpoint = msm_rpc_connect(PM_LIBPROG, PM_LIBVERS, 0); if (IS_ERR(vib_endpoint)) { printk(KERN_ERR "init vib rpc failed!\n"); vib_endpoint = 0; mutex_unlock(&vibe_mtx); return; } } if (on) req.data = cpu_to_be32(PMIC_VIBRATOR_LEVEL);//bigendian32,设置驱动马达的voltage else req.data = cpu_to_be32(0);//停止震动 //控制电压,使motor震动或关闭 msm_rpc_call(vib_endpoint, HTC_PROCEDURE_SET_VIB_ON_OFF, &req, sizeof(req), 5 * HZ); mutex_unlock(&vibe_mtx); } //定时器time out会调用这个函数,修改state,调度work static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) { vibe_state = 0; schedule_work(&work_vibrator);// return HRTIMER_NORESTART; }static struct timed_output_dev pmic_vibrator = { .name = "vibrator",//sysFS下文件夹的名字 /sys/class/timed_output/vibrator .get_time = vibrator_get_time, .enable = vibrator_enable, };void __init msm_init_pmic_vibrator(void) { INIT_WORK(&work_vibrator, update_vibrator);//仅仅是初始化work_vibrator这个结构体 spin_lock_init(&vibe_lock); vibe_state = 0; hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);//初始hrtimer vibe_timer.function = vibrator_timer_func;//给定时器超时callback函数赋值 timed_output_dev_register(&pmic_vibrator);//注册这个vibrator设备 } MODULE_DESCRIPTION("timed output pmic vibrator device"); MODULE_LICENSE("GPL");
写enable文件时调用vibrator_enable;读enable文件时调用vibrator_get_time。
msm_init_pmic_vibrator是模块入口, vibrator_enable是写enable文件的入口。
vibrator_enable中的处理非常有意思,这个函数“正常”的步骤应该是:设置voltage以开震动----->延迟需要震动的时间----->把volt设为0以关闭震动,然而上面的步骤却没有这样处理,而是启动了高精度定时器并在退出前通过schedule_work(&work_vibrator)调度work,调度work即是执行set_pmic_vibrator,这里通过RPC通信操作VIB_DRV_N pin,控制输出电压。即写enable文件时并没有对motor 电压的任何直接操作,而是"后续"进行的。为何采取这种策略,一种解释是:.enable的调用将形成一中持续时间的效果,但是调用本身不宜阻塞,因此实现就让vibrator_enable函数退出后通过定时器实现效果。我反正不太理解上面这个解释:-(~
白话一些,我的理解:
那种“正常”的步骤,最关键的是有个延时,而在函数中这种长时间的延时(震动可能很长时间如闹钟或来电)是不好的而且是很不好,于是策略改变,采用Timer +work,具体步骤如下:
引入关键的全局变量 vibe_state,只有在向enable文件写入不为0时才将 vibe_state置为1,同时启动定时器,并调度work;定时器超时的callback函数中又将 vibe_state置0并调度work。而调度work时的执行函数set_pmic_vibrator(int on)其传入参数即是vibe_state,会根据这一值来设置motor的pin电压为需要的值(开震动)或者0(关震动)。如此通过迂回的方式就避开了“在函数中延长很长时间”的问题,不会造成阻塞。
通过这个例子可以理解work将“工作推后延迟执行”的特点,TouchPanel中的work是将读点的工作延后到中断服务退出,不同的是TouchPanel中还要创建独立的工作队列(create_singlethread_workqueue),而不是像这个使用内核缺省的队列。没有搞清楚 msm_rpc_call这个函数如何使用的?!
2.hardware/libhardware_legacy/vibrator.c
这是硬件抽象层,其实就是对/sys/class/timed_output/vibrator/enable文件的写操作,提供给上层JNI的两个接口:
这个文件就3个函数,核心是sendit(),全文如下:
- int vibrator_on(int timeout_ms); // 开始振动
- int vibrator_off(); // 关闭振动
#define THE_DEVICE "/sys/class/timed_output/vibrator/enable" static int sendit(int timeout_ms) { int nwr, ret, fd; char value[20]; #ifdef QEMU_HARDWARE if (qemu_check()) { return qemu_control_command( "vibrator:%d", timeout_ms ); } #endif fd = open(THE_DEVICE, O_RDWR); if(fd < 0) return errno; nwr = sprintf(value, "%d\n", timeout_ms); ret = write(fd, value, nwr); close(fd); return (ret == nwr) ? 0 : -1; } int vibrator_on(int timeout_ms) { LOGE("============entry %:timeout_ms=%dms",__func__,timeout_ms); /* constant on, up to maximum allowed time */ return sendit(timeout_ms); } int vibrator_off() { LOGE("============entry %",__func__); return sendit(0); }备注:
1)如何单独编译这个文件?只要make libhardware_legacy。
至于如何知道这个vibrator.c被编译到那个so,可以通过Android.mk查看到LOCAL_MODULE:= libhardware_legacy
或者直接在out文件夹下搜索vibrator.o被放到了哪个目录下,这个目录名去掉“_intermediates"就是so库的名字了,这是确定C文件链到哪个库文件的小技巧:-)。如
$find ./out -name 'vibrator.o' -ok rm {} \;
//by yue
< rm ... ./out/target/product/msm7627_sku2/obj/SHARED_LIBRARIES/libhardware_legacy_intermediates/vibrator/vibrator.o > ? y
2)这是HAL层的东东,已经不能printk:-(,要打log,加入以下
//#define LOG_NDEBUG 0
//#define LOG_NIDEBUG 0
#define LOG_TAG "vibrator"
#include <utils/Log.h>
//end然后使用LOGE,可以在DDMS中查看输出log。
三、硬件控制
马达震动控制是很简单的,只要供给电压就震动,关建是如何控制震动强度及持续时间。我的平台上PM7540有专门的马达电压控制 pin VIB_DRV_N,只要控制这个pin脚的电压输出就可控制马达,电压的大小控制震动的强弱,电压大小可编程的范围[1200,3100],100steps,提供的API有三个:
pm_vib_mot_set_volt()//设置输出电压voltage的大小,单位mv
关闭震动就调用pm_vib_mot_set_volt(0),震动持续的时间是调用pm_vib_mot_set_volt(volt_lvl)和调用pm_vib_mot_set_volt(0)之间的间隔。
pm_vib_mot_set_polarity()
pm_vib_mot_set_mode()详细解释可以参考80-VB857-1_C_PM7500_PM7540_IC_ISOD.pdf
PS:开机震动的函数在AMSS端 products\76XX\secboot\oemsbl\oemsbl_gbc.c
void oemsbl_vibrator_power_on_procedure(void) { pm_err_flag_type pm_err=0; pm_err=pm_vib_mot_set_volt(VIBRATE_VOLT);//开始震动 /* if(PM_ERR_FLAG__SUCCESS==pm_err) sprintf(dbgMsg,"[vibrator:oemsbl_qrd.c] Turn on vibrator successfully.\n"); else sprintf(dbgMsg,"[vibrator:oemsbl_qrd.c] Fail to turn on vibrator. Error Code=%X\n",pm_err); OEM_DBG_MSG(dbgMsg); sprintf(dbgMsg,"[vibrator:oemsbl_qrd.c] Wait for %d micro-seconds.\n",VIBRATE_TIME); OEM_DBG_MSG(dbgMsg); */ clk_busy_wait(VIBRATE_TIME);//震动持续时间 pm_err=pm_vib_mot_set_volt(0);//结束震动 /* if(PM_ERR_FLAG__SUCCESS==pm_err) sprintf(dbgMsg,"[vibrator:oemsbl_qrd.c] Turn off vibrator successfully.\n"); else sprintf(dbgMsg,"[vibrator:oemsbl_qrd.c] Fail to turn off vibrator. Error Code=%X\n",pm_err); OEM_DBG_MSG(dbgMsg); */ }四、参考80-VD691-1 PM7540 POWER MANAGEMENT IC DEVICE SPECIFICATION.pdf
80-VB857-1_C_PM7500_PM7540_IC_ISOD.pdf
2.6 内核中的计时器和列表
Anroid振动器系统
Android开发平台振动器系统详解
《Android系统级深入开发--移植与调试》第23章 振动器系统