今天大体看了下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的两个接口:
- int vibrator_on(int timeout_ms); // 开始振动
- int vibrator_off(); // 关闭振动
这个文件就3个函数,核心是sendit(),全文如下:
#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 {} \;
< 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