QPNP (Qualcomm Plug Plug N Play) vibrator is a peripheral on Qualcomm PMICs. The PMIC is connected to MSM8909 via SPMI bus.
Its driver uses the timed-output framework to interface with the userspace.
1. MSM8909 Vibrator driver implementation and DTS file
The vibrator is connected on the VIB_DRV_N line of the QPNP PMIC, and the PMIC vibrator driver can be used to program the voltage from 1.2 V to 3.1 V in 100 mV step through VIB_DRV_N pin.
- status: default status is set to "disabled. Must be "okay"
- compatible: must be "qcom,qpnp-vibrator"
- label: name which describes the device
- reg: address of device
Optional Properties:
- qcom,vib-timeout-ms: timeout of vibrator, in ms. Default 15000 ms
- qcom,vib-vtg-level-mV: voltage level, in mV. Default 3100 mV
vibrator driver source
QPNP vibrator operates in two modes – manual and PWM (Pulse Width Modulation). PWM is supported
through one of dtest lines connected to the vibrator. Manual mode is used on MSM8909 platform.
qpnp_vibrator_prboe() under kernel/drivers/platform/msm/qpnp-vibrator.c
staticintqpnp_vibrator_probe(structspmi_device*spmi){structqpnp_vib*vib;structresource*vib_resource;intrc;vib=devm_kzalloc(&spmi->dev,sizeof(*vib),GFP_KERNEL);if(!vib)return-ENOMEM;vib->spmi=spmi;vib_resource=spmi_get_resource(spmi,0,IORESOURCE_MEM,0);if(!vib_resource){dev_err(&spmi->dev,"Unable to get vibrator base address\n");return-EINVAL;}vib->base=vib_resource->start;rc=qpnp_vib_parse_dt(vib);if(rc){dev_err(&spmi->dev,"DT parsing failed\n");returnrc;}rc=qpnp_vibrator_config(vib);if(rc){dev_err(&spmi->dev,"vib config failed\n");returnrc;}mutex_init(&vib->lock);INIT_WORK(&vib->work,qpnp_vib_update);hrtimer_init(&vib->vib_timer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);vib->vib_timer.function=qpnp_vib_timer_func;vib->timed_dev.name="vibrator";vib->timed_dev.get_time=qpnp_vib_get_time;vib->timed_dev.enable=qpnp_vib_enable;dev_set_drvdata(&spmi->dev,vib);rc=timed_output_dev_register(&vib->timed_dev);if(rc<0)returnrc;returnrc;}
Uses timed output framework
callback .enable and .get_time of struct timed_output_dev is hooked by qpnp_vib_enable() and qpnp_vib_get_time().
Implementation of qnp_vib_enable() and qpnp_vib_get_time()
Timed output is a class drvier to allow changing a state and restore is automatically after a specfied timeout.
This exposes a user space interface under /sys/class/timed_output/vibrator/enable used by vibrator code.
staticssize_tenable_show(structdevice*dev,structdevice_attribute*attr,char*buf){structtimed_output_dev*tdev=dev_get_drvdata(dev);intremaining=tdev->get_time(tdev);returnsprintf(buf,"%d\n",remaining);}staticssize_tenable_store(structdevice*dev,structdevice_attribute*attr,constchar*buf,size_tsize){structtimed_output_dev*tdev=dev_get_drvdata(dev);intvalue;if(sscanf(buf,"%d",&value)!=1)return-EINVAL;tdev->enable(tdev,value);returnsize;}inttimed_output_dev_register(structtimed_output_dev*tdev){intret;if(!tdev||!tdev->name||!tdev->enable||!tdev->get_time)return-EINVAL;ret=create_timed_output_class();if(ret<0)returnret;tdev->index=atomic_inc_return(&device_count);tdev->dev=device_create(timed_output_class,NULL,MKDEV(0,tdev->index),NULL,tdev->name);if(IS_ERR(tdev->dev))returnPTR_ERR(tdev->dev);ret=device_create_file(tdev->dev,&dev_attr_enable);if(ret<0)gotoerr_create_file;dev_set_drvdata(tdev->dev,tdev);tdev->state=0;return0;err_create_file:device_destroy(timed_output_class,MKDEV(0,tdev->index));pr_err("failed to register driver %s\n",tdev->name);returnret;}EXPORT_SYMBOL_GPL(timed_output_dev_register);
3. Android vibrator HAL
Its interface to linux device drivers call with the specified timeout in millisecond via vibrator_on().
#define THE_DEVICE "/sys/class/timed_output/vibrator/enable"staticintsendit(inttimeout_ms){intnwr,ret,fd;charvalue[20];#ifdef QEMU_HARDWAREif(qemu_check()){returnqemu_control_command("vibrator:%d",timeout_ms);}#endiffd=open(THE_DEVICE,O_RDWR);if(fd<0)returnerrno;nwr=sprintf(value,"%d\n",timeout_ms);ret=write(fd,value,nwr);close(fd);return(ret==nwr)?0:-1;}intvibrator_on(inttimeout_ms){/* constant on, up to maximum allowed time */returnsendit(timeout_ms);}intvibrator_off(){returnsendit(0);}
4. Native layer through JNI between HAL and vibrator service
Controls of vibrator service reaches via vibratorOn(), vibratorOff(), and vibratorExists().
In application layer before controling vibrator, applications have to get the access to the vibrator service.
The control goes into the vibrator service (Framework Layer) thru binder inteface.
For example, App. creates Vibrator object and start the vibration via startVibrationLocked(vib).
Inside startVibrationLocked():
If mTimeout != 0, then call the JNI function vibratorOn().
else, call the code, which handle the rhythmic vibration pattern, which intern call the vibratorOn() through VibrateThread().
// Lock held on mVibrationsprivatevoidstartVibrationLocked(finalVibrationvib){try{if(mLowPowerMode&&vib.mUsageHint!=AudioAttributes.USAGE_NOTIFICATION_RINGTONE){return;}intmode=mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,vib.mUsageHint,vib.mUid,vib.mOpPkg);if(mode==AppOpsManager.MODE_ALLOWED){mode=mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),AppOpsManager.OP_VIBRATE,vib.mUid,vib.mOpPkg);}if(mode!=AppOpsManager.MODE_ALLOWED){if(mode==AppOpsManager.MODE_ERRORED){Slog.w(TAG,"Would be an error: vibrate from uid "+vib.mUid);}mH.post(mVibrationRunnable);return;}}catch(RemoteExceptione){}if(vib.mTimeout!=0){doVibratorOn(vib.mTimeout,vib.mUid,vib.mUsageHint);mH.postDelayed(mVibrationRunnable,vib.mTimeout);}else{// mThread better be null here. doCancelVibrate should always be// called before startNextVibrationLocked or startVibrationLocked.mThread=newVibrateThread(vib);mThread.start();}}privatevoiddoVibratorOn(longmillis,intuid,intusageHint){synchronized(mInputDeviceVibrators){if(DEBUG){Slog.d(TAG,"Turning vibrator on for "+millis+" ms.");}try{mBatteryStatsService.noteVibratorOn(uid,millis);mCurVibUid=uid;}catch(RemoteExceptione){}finalintvibratorCount=mInputDeviceVibrators.size();if(vibratorCount!=0){finalAudioAttributesattributes=newAudioAttributes.Builder().setUsage(usageHint).build();for(inti=0;i<vibratorCount;i++){mInputDeviceVibrators.get(i).vibrate(millis,attributes);}}else{vibratorOn(millis);}}}