blob: ec7e114afcdd681b77acaf76b8a268529382049d [file] [log] [blame]
/*
* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "vendor.qti.vibrator"
#include <dirent.h>
#include <inttypes.h>
#include <linux/input.h>
#include <log/log.h>
#include <string.h>
#include <sys/ioctl.h>
#include <thread>
#include "include/Vibrator.h"
#ifdef USE_EFFECT_STREAM
#include "effect.h"
#endif
extern "C" {
#include "libsoc_helper.h"
}
namespace aidl {
namespace android {
namespace hardware {
namespace vibrator {
#define STRONG_MAGNITUDE 0x7fff
#define MEDIUM_MAGNITUDE 0x5fff
#define LIGHT_MAGNITUDE 0x3fff
#define INVALID_VALUE -1
#define CUSTOM_DATA_LEN 3
#define NAME_BUF_SIZE 32
#define VIB_LED 0x01
#define VIB_AW 0x02
#define VIB_ALL (VIB_LED|VIB_AW)
#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)%8)))
static const char LED_DEVICE[] = "/sys/class/leds/vibrator";
static const char AW_DEVICE[] = "/sys/class/leds/vibrator_1";
InputFFDevice::InputFFDevice()
{
DIR *dp;
struct dirent *dir;
uint8_t ffBitmask[FF_CNT / 8];
char devicename[PATH_MAX];
const char *INPUT_DIR = "/dev/input/";
char name[NAME_BUF_SIZE];
int fd, ret;
soc_info_v0_1_t soc = {MSM_CPU_UNKNOWN};
mVibraFd = INVALID_VALUE;
mSupportGain = false;
mSupportEffects = false;
mSupportExternalControl = false;
mCurrAppId = INVALID_VALUE;
mCurrMagnitude = 0x7fff;
mInExternalControl = false;
dp = opendir(INPUT_DIR);
if (!dp) {
ALOGE("open %s failed, errno = %d", INPUT_DIR, errno);
return;
}
memset(ffBitmask, 0, sizeof(ffBitmask));
while ((dir = readdir(dp)) != NULL){
if (dir->d_name[0] == '.' &&
(dir->d_name[1] == '\0' ||
(dir->d_name[1] == '.' && dir->d_name[2] == '\0')))
continue;
snprintf(devicename, PATH_MAX, "%s%s", INPUT_DIR, dir->d_name);
fd = TEMP_FAILURE_RETRY(open(devicename, O_RDWR));
if (fd < 0) {
ALOGE("open %s failed, errno = %d", devicename, errno);
continue;
}
ret = TEMP_FAILURE_RETRY(ioctl(fd, EVIOCGNAME(sizeof(name)), name));
if (ret == -1) {
ALOGE("get input device name %s failed, errno = %d\n", devicename, errno);
close(fd);
continue;
}
if (strcmp(name, "qcom-hv-haptics") && strcmp(name, "qti-haptics")) {
ALOGD("not a qcom/qti haptics device\n");
close(fd);
continue;
}
ALOGI("%s is detected at %s\n", name, devicename);
ret = TEMP_FAILURE_RETRY(ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffBitmask)), ffBitmask));
if (ret == -1) {
ALOGE("ioctl failed, errno = %d", errno);
close(fd);
continue;
}
if (test_bit(FF_CONSTANT, ffBitmask) ||
test_bit(FF_PERIODIC, ffBitmask)) {
mVibraFd = fd;
if (test_bit(FF_CUSTOM, ffBitmask))
mSupportEffects = true;
if (test_bit(FF_GAIN, ffBitmask))
mSupportGain = true;
get_soc_info(&soc);
ALOGD("msm CPU SoC ID: %d\n", soc.msm_cpu);
switch (soc.msm_cpu) {
case MSM_CPU_LAHAINA:
case APQ_CPU_LAHAINA:
case MSM_CPU_SHIMA:
case MSM_CPU_SM8325:
case APQ_CPU_SM8325P:
case MSM_CPU_YUPIK:
mSupportExternalControl = true;
break;
default:
mSupportExternalControl = false;
break;
}
break;
}
close(fd);
}
closedir(dp);
}
/** Play vibration
*
* @param effectId: ID of the predefined effect will be played. If effectId is valid
* (non-negative value), the timeoutMs value will be ignored, and the
* real playing length will be set in param@playLengtMs and returned
* to VibratorService. If effectId is invalid, value in param@timeoutMs
* will be used as the play length for playing a constant effect.
* @param timeoutMs: playing length, non-zero means playing, zero means stop playing.
* @param playLengthMs: the playing length in ms unit which will be returned to
* VibratorService if the request is playing a predefined effect.
* The custom_data in periodic is reused for returning the playLengthMs
* from kernel space to userspace if the pattern is defined in kernel
* driver. It's been defined with following format:
* <effect-ID, play-time-in-seconds, play-time-in-milliseconds>.
* The effect-ID is used for passing down the predefined effect to
* kernel driver, and the rest two parameters are used for returning
* back the real playing length from kernel driver.
*/
int InputFFDevice::play(int effectId, uint32_t timeoutMs, long *playLengthMs) {
struct ff_effect effect;
struct input_event play;
int16_t data[CUSTOM_DATA_LEN] = {0, 0, 0};
int ret;
#ifdef USE_EFFECT_STREAM
const struct effect_stream *stream;
#endif
/* For QMAA compliance, return OK even if vibrator device doesn't exist */
if (mVibraFd == INVALID_VALUE) {
if (playLengthMs != NULL)
*playLengthMs = 0;
return 0;
}
if (timeoutMs != 0) {
if (mCurrAppId != INVALID_VALUE) {
ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCRMFF, mCurrAppId));
if (ret == -1) {
ALOGE("ioctl EVIOCRMFF failed, errno = %d", -errno);
goto errout;
}
mCurrAppId = INVALID_VALUE;
}
memset(&effect, 0, sizeof(effect));
if (effectId != INVALID_VALUE) {
data[0] = effectId;
effect.type = FF_PERIODIC;
effect.u.periodic.waveform = FF_CUSTOM;
effect.u.periodic.magnitude = mCurrMagnitude;
effect.u.periodic.custom_data = data;
effect.u.periodic.custom_len = sizeof(int16_t) * CUSTOM_DATA_LEN;
#ifdef USE_EFFECT_STREAM
stream = get_effect_stream(effectId);
if (stream != NULL) {
effect.u.periodic.custom_data = (int16_t *)stream;
effect.u.periodic.custom_len = sizeof(*stream);
}
#endif
} else {
effect.type = FF_CONSTANT;
effect.u.constant.level = mCurrMagnitude;
effect.replay.length = timeoutMs;
}
effect.id = mCurrAppId;
effect.replay.delay = 0;
ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCSFF, &effect));
if (ret == -1) {
ALOGE("ioctl EVIOCSFF failed, errno = %d", -errno);
goto errout;
}
mCurrAppId = effect.id;
if (effectId != INVALID_VALUE && playLengthMs != NULL) {
*playLengthMs = data[1] * 1000 + data[2];
#ifdef USE_EFFECT_STREAM
if (stream != NULL && stream->play_rate_hz != 0)
*playLengthMs = ((stream->length * 1000) / stream->play_rate_hz) + 1;
#endif
}
play.value = 1;
play.type = EV_FF;
play.code = mCurrAppId;
play.time.tv_sec = 0;
play.time.tv_usec = 0;
ret = TEMP_FAILURE_RETRY(write(mVibraFd, (const void*)&play, sizeof(play)));
if (ret == -1) {
ALOGE("write failed, errno = %d\n", -errno);
ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCRMFF, mCurrAppId));
if (ret == -1)
ALOGE("ioctl EVIOCRMFF failed, errno = %d", -errno);
goto errout;
}
} else if (mCurrAppId != INVALID_VALUE) {
ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCRMFF, mCurrAppId));
if (ret == -1) {
ALOGE("ioctl EVIOCRMFF failed, errno = %d", -errno);
goto errout;
}
mCurrAppId = INVALID_VALUE;
}
return 0;
errout:
mCurrAppId = INVALID_VALUE;
return ret;
}
int InputFFDevice::on(int32_t timeoutMs) {
return play(INVALID_VALUE, timeoutMs, NULL);
}
int InputFFDevice::off() {
return play(INVALID_VALUE, 0, NULL);
}
int InputFFDevice::setAmplitude(uint8_t amplitude) {
int tmp, ret;
struct input_event ie;
/* For QMAA compliance, return OK even if vibrator device doesn't exist */
if (mVibraFd == INVALID_VALUE)
return 0;
tmp = amplitude * (STRONG_MAGNITUDE - LIGHT_MAGNITUDE) / 255;
tmp += LIGHT_MAGNITUDE;
ie.type = EV_FF;
ie.code = FF_GAIN;
ie.value = tmp;
ret = TEMP_FAILURE_RETRY(write(mVibraFd, &ie, sizeof(ie)));
if (ret == -1) {
ALOGE("write FF_GAIN failed, errno = %d", -errno);
return ret;
}
mCurrMagnitude = tmp;
return 0;
}
int InputFFDevice::playEffect(int effectId, EffectStrength es, long *playLengthMs) {
switch (es) {
case EffectStrength::LIGHT:
mCurrMagnitude = LIGHT_MAGNITUDE;
break;
case EffectStrength::MEDIUM:
mCurrMagnitude = MEDIUM_MAGNITUDE;
break;
case EffectStrength::STRONG:
mCurrMagnitude = STRONG_MAGNITUDE;
break;
default:
return -1;
}
return play(effectId, INVALID_VALUE, playLengthMs);
}
LedVibratorDevice::LedVibratorDevice() {
char devicename[PATH_MAX];
int fd;
mDetected = false;
vibrator_dev = VIB_ALL;
snprintf(devicename, sizeof(devicename), "%s/%s", LED_DEVICE, "activate");
fd = TEMP_FAILURE_RETRY(open(devicename, O_RDWR));
if (fd < 0) {
ALOGE("open %s failed, errno = %d", devicename, errno);
vibrator_dev &= ~ VIB_LED;
// return;
}
snprintf(devicename, sizeof(devicename), "%s/%s", AW_DEVICE, "activate");
fd = TEMP_FAILURE_RETRY(open(devicename, O_RDWR));
if (fd < 0) {
ALOGE("open %s failed, errno = %d", devicename, errno);
vibrator_dev &= ~ VIB_AW;
}
ALOGI("vibrator device = %d", vibrator_dev);
if(!vibrator_dev)
return;
mDetected = true;
}
int LedVibratorDevice::write_value(const char *file, const char *value) {
int fd;
int ret;
fd = TEMP_FAILURE_RETRY(open(file, O_WRONLY));
if (fd < 0) {
ALOGE("open %s failed, errno = %d", file, errno);
return -errno;
}
ret = TEMP_FAILURE_RETRY(write(fd, value, strlen(value) + 1));
if (ret == -1) {
ret = -errno;
} else if (ret != strlen(value) + 1) {
/* even though EAGAIN is an errno value that could be set
by write() in some cases, none of them apply here. So, this return
value can be clearly identified when debugging and suggests the
caller that it may try to call vibrator_on() again */
ret = -EAGAIN;
} else {
ret = 0;
}
errno = 0;
close(fd);
return ret;
}
int LedVibratorDevice::on(int32_t timeoutMs) {
char file[PATH_MAX];
char value[32];
int ret;
if(vibrator_dev & VIB_AW)
{
ALOGI("AwVibrator time = %d ",timeoutMs);
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "activate_mode");
ret = write_value(file, "0");
if (ret < 0)
goto error;
if (timeoutMs <= 50) {
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "seq");
ret = write_value(file, "0x00 0x00");
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "seq");
ret = write_value(file, "0x00 0x01");
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "loop");
ret = write_value(file, "0x00 0x00");
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "duration");
snprintf(value, sizeof(value), "%u\n", timeoutMs);
ret = write_value(file, value);
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "brightness");
ret = write_value(file, "1");
if (ret < 0)
goto error;
return 0;
} else {
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "index");
ret = write_value(file, "4");
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "duration");
snprintf(value, sizeof(value), "%u\n", timeoutMs);
ret = write_value(file, value);
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "activate");
ret = write_value(file, "1");
if (ret < 0)
goto error;
return 0;
}
}
snprintf(file, sizeof(file), "%s/%s", LED_DEVICE, "state");
ret = write_value(file, "1");
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", LED_DEVICE, "duration");
snprintf(value, sizeof(value), "%u\n", timeoutMs);
ret = write_value(file, value);
if (ret < 0)
goto error;
snprintf(file, sizeof(file), "%s/%s", LED_DEVICE, "activate");
ret = write_value(file, "1");
if (ret < 0)
goto error;
return 0;
error:
ALOGE("Failed to turn on vibrator ret: %d\n", ret);
return ret;
}
int LedVibratorDevice::off()
{
char file[PATH_MAX];
int ret;
ALOGI("LedVibrator device = %d ",vibrator_dev);
if(vibrator_dev & VIB_AW)
{
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "activate");
ret = write_value(file, "0");
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "brightness");
ret = write_value(file, "0");
if (ret < 0)
return ret;
snprintf(file, sizeof(file), "%s/%s", AW_DEVICE, "index");
ret = write_value(file, "1");
if (ret < 0)
return ret;
return ret;
}
snprintf(file, sizeof(file), "%s/%s", LED_DEVICE, "activate");
ret = write_value(file, "0");
return ret;
}
ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) {
*_aidl_return = IVibrator::CAP_ON_CALLBACK;
if (ledVib.mDetected) {
*_aidl_return |= IVibrator::CAP_PERFORM_CALLBACK;
ALOGD("QTI Vibrator reporting capabilities: %d", *_aidl_return);
return ndk::ScopedAStatus::ok();
}
if (ff.mSupportGain)
*_aidl_return |= IVibrator::CAP_AMPLITUDE_CONTROL;
if (ff.mSupportEffects)
*_aidl_return |= IVibrator::CAP_PERFORM_CALLBACK;
if (ff.mSupportExternalControl)
*_aidl_return |= IVibrator::CAP_EXTERNAL_CONTROL;
ALOGD("QTI Vibrator reporting capabilities: %d", *_aidl_return);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::off() {
int ret;
ALOGD("QTI Vibrator off");
if (ledVib.mDetected)
ret = ledVib.off();
else
ret = ff.off();
if (ret != 0)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
const std::shared_ptr<IVibratorCallback>& callback) {
int ret;
ALOGD("Vibrator on for timeoutMs: %d", timeoutMs);
if (ledVib.mDetected)
ret = ledVib.on(timeoutMs);
else
ret = ff.on(timeoutMs);
if (ret != 0)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC));
if (callback != nullptr) {
std::thread([=] {
ALOGD("Starting on on another thread");
usleep(timeoutMs * 1000);
ALOGD("Notifying on complete");
if (!callback->onComplete().isOk()) {
ALOGE("Failed to call onComplete");
}
}).detach();
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength es, const std::shared_ptr<IVibratorCallback>& callback, int32_t* _aidl_return) {
long playLengthMs;
int ret;
if (ledVib.mDetected)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
ALOGD("Vibrator perform effect %d", effect);
#ifdef TARGET_SUPPORTS_OFFLOAD
if (effect < Effect::CLICK || effect > Effect::RINGTONE_15)
#else
if (effect < Effect::CLICK || effect > Effect::HEAVY_CLICK)
#endif
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
if (es != EffectStrength::LIGHT && es != EffectStrength::MEDIUM && es != EffectStrength::STRONG)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
ret = ff.playEffect((static_cast<int>(effect)), es, &playLengthMs);
if (ret != 0)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC));
if (callback != nullptr) {
std::thread([=] {
ALOGD("Starting perform on another thread");
usleep(playLengthMs * 1000);
ALOGD("Notifying perform complete");
callback->onComplete();
}).detach();
}
*_aidl_return = playLengthMs;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect>* _aidl_return) {
if (ledVib.mDetected)
return ndk::ScopedAStatus::ok();
#ifdef TARGET_SUPPORTS_OFFLOAD
*_aidl_return = {Effect::CLICK, Effect::DOUBLE_CLICK, Effect::TICK, Effect::THUD,
Effect::POP, Effect::HEAVY_CLICK, Effect::RINGTONE_12,
Effect::RINGTONE_13, Effect::RINGTONE_14, Effect::RINGTONE_15};
#else
*_aidl_return = {Effect::CLICK, Effect::DOUBLE_CLICK, Effect::TICK, Effect::THUD,
Effect::POP, Effect::HEAVY_CLICK};
#endif
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) {
uint8_t tmp;
int ret;
if (ledVib.mDetected)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
ALOGD("Vibrator set amplitude: %f", amplitude);
if (amplitude <= 0.0f || amplitude > 1.0f)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
if (ff.mInExternalControl)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
tmp = (uint8_t)(amplitude * 0xff);
ret = ff.setAmplitude(tmp);
if (ret != 0)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) {
if (ledVib.mDetected)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
ALOGD("Vibrator set external control: %d", enabled);
if (!ff.mSupportExternalControl)
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
ff.mInExternalControl = enabled;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t* maxDelayMs __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t* maxSize __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector<CompositePrimitive>* supported __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive __unused,
int32_t* durationMs __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::compose(const std::vector<CompositeEffect>& composite __unused,
const std::shared_ptr<IVibratorCallback>& callback __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect>* _aidl_return __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t id __unused, Effect effect __unused,
EffectStrength strength __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t id __unused) {
return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION));
}
} // namespace vibrator
} // namespace hardware
} // namespace android
} // namespace aidl