Vibrator motor驱动

     今天大体看了下Android的Motor驱动模块,这部分高通都做好了,拿来就用,基本不需要修改,所以一直没有看,有空看了下,特作记录。Vibrator是基于Linux的timed output驱动框架的,基本的结构就是timed_output_dev struct,其中涉及的知识还有定时器和消息队列。只介绍driver这一部分,更详细的内容可以搜索CONFIG_MSM_PMIC_VIBRATOR这个配置来了解。

一、设备结点

        设备节点在调试驱动时非常有用,直接adb shell通过读写文件可以验证驱动是否OK。

 
 
  1. # echo ‘10000’> /sys/class/timed_output/vibrator/enable  
  2. # cat /sys/class/timed_output/vibrator/enable  
  3. 3290  
  4. # 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的两个接口:


      
      
  1. int vibrator_on(int timeout_ms);            // 开始振动  
  2. 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,加入以下

//by yue
//#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章 振动器系统






























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值