/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "Vibrator.h" #include #include #include #include #include #include #include #include #include #include "utils.h" namespace android { namespace hardware { namespace vibrator { namespace V1_3 { namespace implementation { static constexpr int8_t MAX_RTP_INPUT = 127; static constexpr int8_t MIN_RTP_INPUT = 0; static constexpr char RTP_MODE[] = "rtp"; static constexpr char WAVEFORM_MODE[] = "waveform"; // Use effect #1 in the waveform library for CLICK effect static constexpr char WAVEFORM_CLICK_EFFECT_SEQ[] = "1 0"; // Use effect #2 in the waveform library for TICK effect static constexpr char WAVEFORM_TICK_EFFECT_SEQ[] = "2 0"; // Use effect #3 in the waveform library for DOUBLE_CLICK effect static constexpr char WAVEFORM_DOUBLE_CLICK_EFFECT_SEQ[] = "3 0"; // Use effect #4 in the waveform library for HEAVY_CLICK effect static constexpr char WAVEFORM_HEAVY_CLICK_EFFECT_SEQ[] = "4 0"; // UT team design those target G values static constexpr std::array EFFECT_TARGET_G = {0.15, 0.15, 0.27, 0.43, 0.57}; static constexpr std::array STEADY_TARGET_G = {1.2, 1.145, 0.905}; #define FLOAT_EPS 1e-6 // Temperature protection upper bound 10°C and lower bound 5°C static constexpr int32_t TEMP_UPPER_BOUND = 10000; static constexpr int32_t TEMP_LOWER_BOUND = 5000; // Steady vibration's voltage in lower bound guarantee static uint32_t STEADY_VOLTAGE_LOWER_BOUND = 90; // 1.8 Vpeak static std::uint32_t freqPeriodFormula(std::uint32_t in) { return 1000000000 / (24615 * in); } static std::uint32_t convertLevelsToOdClamp(float voltageLevel, uint32_t lraPeriod) { float odClamp; odClamp = voltageLevel / ((21.32 / 1000.0) * sqrt(1.0 - (static_cast(freqPeriodFormula(lraPeriod)) * 8.0 / 10000.0))); return round(odClamp); } static float targetGToVlevelsUnderLinearEquation(std::array inputCoeffs, float targetG) { // Implement linear equation to get voltage levels, f(x) = ax + b // 0 to 3.2 is our valid output float outPutVal = 0.0f; outPutVal = (targetG - inputCoeffs[1]) / inputCoeffs[0]; if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } else { return 0.0f; } } static float targetGToVlevelsUnderCubicEquation(std::array inputCoeffs, float targetG) { // Implement cubic equation to get voltage levels, f(x) = ax^3 + bx^2 + cx + d // 0 to 3.2 is our valid output float AA = 0.0f, BB = 0.0f, CC = 0.0f, Delta = 0.0f; float Y1 = 0.0f, Y2 = 0.0f, K = 0.0f, T = 0.0f, sita = 0.0f; float outPutVal = 0.0f; float oneHalf = 1.0 / 2.0, oneThird = 1.0 / 3.0; float cosSita = 0.0f, sinSitaSqrt3 = 0.0f, sqrtA = 0.0f; AA = inputCoeffs[1] * inputCoeffs[1] - 3.0 * inputCoeffs[0] * inputCoeffs[2]; BB = inputCoeffs[1] * inputCoeffs[2] - 9.0 * inputCoeffs[0] * (inputCoeffs[3] - targetG); CC = inputCoeffs[2] * inputCoeffs[2] - 3.0 * inputCoeffs[1] * (inputCoeffs[3] - targetG); Delta = BB * BB - 4.0 * AA * CC; // There are four discriminants in Shengjin formula. // https://zh.wikipedia.org/wiki/%E4%B8%89%E6%AC%A1%E6%96%B9%E7%A8%8B#%E7%9B%9B%E9%87%91%E5%85%AC%E5%BC%8F%E6%B3%95 if ((fabs(AA) <= FLOAT_EPS) && (fabs(BB) <= FLOAT_EPS)) { // Case 1: A = B = 0 outPutVal = -inputCoeffs[1] / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } return 0.0f; } else if (Delta > FLOAT_EPS) { // Case 2: Delta > 0 Y1 = AA * inputCoeffs[1] + 3.0 * inputCoeffs[0] * (-BB + pow(Delta, oneHalf)) / 2.0; Y2 = AA * inputCoeffs[1] + 3.0 * inputCoeffs[0] * (-BB - pow(Delta, oneHalf)) / 2.0; if ((Y1 < -FLOAT_EPS) && (Y2 > FLOAT_EPS)) { return (-inputCoeffs[1] + pow(-Y1, oneThird) - pow(Y2, oneThird)) / (3.0 * inputCoeffs[0]); } else if ((Y1 > FLOAT_EPS) && (Y2 < -FLOAT_EPS)) { return (-inputCoeffs[1] - pow(Y1, oneThird) + pow(-Y2, oneThird)) / (3.0 * inputCoeffs[0]); } else if ((Y1 < -FLOAT_EPS) && (Y2 < -FLOAT_EPS)) { return (-inputCoeffs[1] + pow(-Y1, oneThird) + pow(-Y2, oneThird)) / (3.0 * inputCoeffs[0]); } else { return (-inputCoeffs[1] - pow(Y1, oneThird) - pow(Y2, oneThird)) / (3.0 * inputCoeffs[0]); } return 0.0f; } else if (Delta < -FLOAT_EPS) { // Case 3: Delta < 0 T = (2 * AA * inputCoeffs[1] - 3 * inputCoeffs[0] * BB) / (2 * AA * sqrt(AA)); sita = acos(T); cosSita = cos(sita / 3); sinSitaSqrt3 = sqrt(3.0) * sin(sita / 3); sqrtA = sqrt(AA); outPutVal = (-inputCoeffs[1] - 2 * sqrtA * cosSita) / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } outPutVal = (-inputCoeffs[1] + sqrtA * (cosSita + sinSitaSqrt3)) / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } outPutVal = (-inputCoeffs[1] + sqrtA * (cosSita - sinSitaSqrt3)) / (3 * inputCoeffs[0]); if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } return 0.0f; } else if (Delta <= FLOAT_EPS) { // Case 4: Delta = 0 K = BB / AA; outPutVal = (-inputCoeffs[1] / inputCoeffs[0] + K); if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } outPutVal = (-K / 2); if ((outPutVal > FLOAT_EPS) && (outPutVal <= 3.2)) { return outPutVal; } return 0.0f; } else { // Exception handling return 0.0f; } } using utils::toUnderlying; using Status = ::android::hardware::vibrator::V1_0::Status; using EffectStrength = ::android::hardware::vibrator::V1_0::EffectStrength; Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)) { std::string autocal; uint32_t lraPeriod = 0, lpTrigSupport = 0; bool hasEffectCoeffs = false; std::array effectCoeffs = {0}; if (!mHwApi->setState(true)) { ALOGE("Failed to set state (%d): %s", errno, strerror(errno)); } if (mHwCal->getAutocal(&autocal)) { mHwApi->setAutocal(autocal); } mHwCal->getLraPeriod(&lraPeriod); mHwCal->getCloseLoopThreshold(&mCloseLoopThreshold); mHwCal->getDynamicConfig(&mDynamicConfig); if (mDynamicConfig) { uint8_t i = 0; float tempVolLevel = 0.0f; float tempAmpMax = 0.0f; uint32_t longFreqencyShift = 0; uint32_t shortVoltageMax = 0, longVoltageMax = 0; uint32_t shape = 0; mHwCal->getLongFrequencyShift(&longFreqencyShift); mHwCal->getShortVoltageMax(&shortVoltageMax); mHwCal->getLongVoltageMax(&longVoltageMax); hasEffectCoeffs = mHwCal->getEffectCoeffs(&effectCoeffs); for (i = 0; i < 5; i++) { if (hasEffectCoeffs) { // Use linear approach to get the target voltage levels if ((effectCoeffs[2] == 0) && (effectCoeffs[3] == 0)) { tempVolLevel = targetGToVlevelsUnderLinearEquation(effectCoeffs, EFFECT_TARGET_G[i]); mEffectTargetOdClamp[i] = convertLevelsToOdClamp(tempVolLevel, lraPeriod); } else { // Use cubic approach to get the target voltage levels tempVolLevel = targetGToVlevelsUnderCubicEquation(effectCoeffs, EFFECT_TARGET_G[i]); mEffectTargetOdClamp[i] = convertLevelsToOdClamp(tempVolLevel, lraPeriod); } } else { mEffectTargetOdClamp[i] = shortVoltageMax; } } // Add a boundary protection for level 5 only, since // some devices might not be able to reach the maximum target G if ((mEffectTargetOdClamp[4] <= 0) || (mEffectTargetOdClamp[4] > 161)) { mEffectTargetOdClamp[4] = shortVoltageMax; } mHwCal->getEffectShape(&shape); mEffectConfig.reset(new VibrationConfig({ .shape = (shape == UINT32_MAX) ? WaveShape::SINE : static_cast(shape), .odClamp = &mEffectTargetOdClamp[0], .olLraPeriod = lraPeriod, })); mSteadyTargetOdClamp = longVoltageMax; if ((mHwCal->getSteadyAmpMax(&tempAmpMax)) && (tempAmpMax > STEADY_TARGET_G[0])) { tempVolLevel = round((STEADY_TARGET_G[0] / tempAmpMax) * longVoltageMax); mSteadyTargetOdClamp = (tempVolLevel < STEADY_VOLTAGE_LOWER_BOUND) ? STEADY_VOLTAGE_LOWER_BOUND : tempVolLevel; } mHwCal->getSteadyShape(&shape); mSteadyConfig.reset(new VibrationConfig({ .shape = (shape == UINT32_MAX) ? WaveShape::SQUARE : static_cast(shape), .odClamp = &mSteadyTargetOdClamp, .olLraPeriod = lraPeriod, })); mSteadyOlLraPeriod = lraPeriod; // 1. Change long lra period to frequency // 2. Get frequency': subtract the frequency shift from the frequency // 3. Get final long lra period after put the frequency' to formula mSteadyOlLraPeriodShift = freqPeriodFormula(freqPeriodFormula(lraPeriod) - longFreqencyShift); } else { mHwApi->setOlLraPeriod(lraPeriod); } mHwCal->getClickDuration(&mClickDuration); mHwCal->getTickDuration(&mTickDuration); mHwCal->getDoubleClickDuration(&mDoubleClickDuration); mHwCal->getHeavyClickDuration(&mHeavyClickDuration); // This enables effect #1 from the waveform library to be triggered by SLPI // while the AP is in suspend mode // For default setting, we will enable this feature if that project did not // set the lptrigger config mHwCal->getTriggerEffectSupport(&lpTrigSupport); if (!mHwApi->setLpTriggerEffect(lpTrigSupport)) { ALOGW("Failed to set LP trigger mode (%d): %s", errno, strerror(errno)); } } Return Vibrator::on(uint32_t timeoutMs, const char mode[], const std::unique_ptr &config, const int8_t volOffset) { LoopControl loopMode = LoopControl::OPEN; // Open-loop mode is used for short click for over-drive // Close-loop mode is used for long notification for stability if (mode == RTP_MODE && timeoutMs > mCloseLoopThreshold) { loopMode = LoopControl::CLOSE; } mHwApi->setCtrlLoop(toUnderlying(loopMode)); if (!mHwApi->setDuration(timeoutMs)) { ALOGE("Failed to set duration (%d): %s", errno, strerror(errno)); return Status::UNKNOWN_ERROR; } mHwApi->setMode(mode); if (config != nullptr) { mHwApi->setLraWaveShape(toUnderlying(config->shape)); mHwApi->setOdClamp(config->odClamp[volOffset]); mHwApi->setOlLraPeriod(config->olLraPeriod); } if (!mHwApi->setActivate(1)) { ALOGE("Failed to activate (%d): %s", errno, strerror(errno)); return Status::UNKNOWN_ERROR; } return Status::OK; } // Methods from ::android::hardware::vibrator::V1_2::IVibrator follow. Return Vibrator::on(uint32_t timeoutMs) { ATRACE_NAME("Vibrator::on"); if (mDynamicConfig) { int usbTemp = 0; mHwApi->getUsbTemp(&usbTemp); if (usbTemp > TEMP_UPPER_BOUND) { mSteadyConfig->odClamp = &mSteadyTargetOdClamp; mSteadyConfig->olLraPeriod = mSteadyOlLraPeriod; } else if (usbTemp < TEMP_LOWER_BOUND) { mSteadyConfig->odClamp = &STEADY_VOLTAGE_LOWER_BOUND; mSteadyConfig->olLraPeriod = mSteadyOlLraPeriodShift; } } return on(timeoutMs, RTP_MODE, mSteadyConfig, 0); } Return Vibrator::off() { ATRACE_NAME("Vibrator::off"); if (!mHwApi->setActivate(0)) { ALOGE("Failed to turn vibrator off (%d): %s", errno, strerror(errno)); return Status::UNKNOWN_ERROR; } return Status::OK; } Return Vibrator::supportsAmplitudeControl() { ATRACE_NAME("Vibrator::supportsAmplitudeControl"); return (mHwApi->hasRtpInput() ? true : false); } Return Vibrator::setAmplitude(uint8_t amplitude) { ATRACE_NAME("Vibrator::setAmplitude"); if (amplitude == 0) { return Status::BAD_VALUE; } int32_t rtp_input = std::round((amplitude - 1) / 254.0 * (MAX_RTP_INPUT - MIN_RTP_INPUT) + MIN_RTP_INPUT); if (!mHwApi->setRtpInput(rtp_input)) { ALOGE("Failed to set amplitude (%d): %s", errno, strerror(errno)); return Status::UNKNOWN_ERROR; } return Status::OK; } // Methods from ::android::hardware::vibrator::V1_3::IVibrator follow. Return Vibrator::supportsExternalControl() { ATRACE_NAME("Vibrator::supportsExternalControl"); return false; } Return Vibrator::setExternalControl(bool enabled) { ATRACE_NAME("Vibrator::setExternalControl"); ALOGE("Not support in DRV2624 solution, %d", enabled); return Status::UNSUPPORTED_OPERATION; } // Methods from ::android.hidl.base::V1_0::IBase follow. Return Vibrator::debug(const hidl_handle &handle, const hidl_vec & /* options */) { if (handle == nullptr || handle->numFds < 1 || handle->data[0] < 0) { ALOGE("Called debug() with invalid fd."); return Void(); } int fd = handle->data[0]; dprintf(fd, "HIDL:\n"); dprintf(fd, " Close Loop Thresh: %" PRIu32 "\n", mCloseLoopThreshold); if (mSteadyConfig) { dprintf(fd, " Steady Shape: %" PRIu32 "\n", mSteadyConfig->shape); dprintf(fd, " Steady OD Clamp: %" PRIu32 "\n", mSteadyConfig->odClamp[0]); dprintf(fd, " Steady OL LRA Period: %" PRIu32 "\n", mSteadyConfig->olLraPeriod); } if (mEffectConfig) { dprintf(fd, " Effect Shape: %" PRIu32 "\n", mEffectConfig->shape); dprintf(fd, " Effect OD Clamp: %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 "\n", mEffectConfig->odClamp[0], mEffectConfig->odClamp[1], mEffectConfig->odClamp[2], mEffectConfig->odClamp[3], mEffectConfig->odClamp[4]); dprintf(fd, " Effect OL LRA Period: %" PRIu32 "\n", mEffectConfig->olLraPeriod); } dprintf(fd, " Click Duration: %" PRIu32 "\n", mClickDuration); dprintf(fd, " Tick Duration: %" PRIu32 "\n", mTickDuration); dprintf(fd, " Double Click Duration: %" PRIu32 "\n", mDoubleClickDuration); dprintf(fd, " Heavy Click Duration: %" PRIu32 "\n", mHeavyClickDuration); dprintf(fd, "\n"); mHwApi->debug(fd); dprintf(fd, "\n"); mHwCal->debug(fd); fsync(fd); return Void(); } Return Vibrator::perform(V1_0::Effect effect, EffectStrength strength, perform_cb _hidl_cb) { return performWrapper(effect, strength, _hidl_cb); } Return Vibrator::perform_1_1(V1_1::Effect_1_1 effect, EffectStrength strength, perform_cb _hidl_cb) { return performWrapper(effect, strength, _hidl_cb); } Return Vibrator::perform_1_2(V1_2::Effect effect, EffectStrength strength, perform_cb _hidl_cb) { return performWrapper(effect, strength, _hidl_cb); } Return Vibrator::perform_1_3(Effect effect, EffectStrength strength, perform_cb _hidl_cb) { return performWrapper(effect, strength, _hidl_cb); } template Return Vibrator::performWrapper(T effect, EffectStrength strength, perform_cb _hidl_cb) { ATRACE_NAME("Vibrator::performWrapper"); auto validEffectRange = hidl_enum_range(); if (effect < *validEffectRange.begin() || effect > *std::prev(validEffectRange.end())) { _hidl_cb(Status::UNSUPPORTED_OPERATION, 0); return Void(); } auto validStrengthRange = hidl_enum_range(); if (strength < *validStrengthRange.begin() || strength > *std::prev(validStrengthRange.end())) { _hidl_cb(Status::UNSUPPORTED_OPERATION, 0); return Void(); } return performEffect(static_cast(effect), strength, _hidl_cb); } Return Vibrator::performEffect(Effect effect, EffectStrength strength, perform_cb _hidl_cb) { Status status = Status::OK; uint32_t timeMS; int8_t volOffset; switch (strength) { case EffectStrength::LIGHT: volOffset = 0; break; case EffectStrength::MEDIUM: volOffset = 1; break; case EffectStrength::STRONG: volOffset = 1; break; default: status = Status::UNSUPPORTED_OPERATION; break; } switch (effect) { case Effect::TEXTURE_TICK: mHwApi->setSequencer(WAVEFORM_TICK_EFFECT_SEQ); timeMS = mTickDuration; volOffset = TEXTURE_TICK; break; case Effect::CLICK: mHwApi->setSequencer(WAVEFORM_CLICK_EFFECT_SEQ); timeMS = mClickDuration; volOffset += CLICK; break; case Effect::DOUBLE_CLICK: mHwApi->setSequencer(WAVEFORM_DOUBLE_CLICK_EFFECT_SEQ); timeMS = mDoubleClickDuration; volOffset += CLICK; break; case Effect::TICK: mHwApi->setSequencer(WAVEFORM_TICK_EFFECT_SEQ); timeMS = mTickDuration; volOffset += TICK; break; case Effect::HEAVY_CLICK: mHwApi->setSequencer(WAVEFORM_HEAVY_CLICK_EFFECT_SEQ); timeMS = mHeavyClickDuration; volOffset += HEAVY_CLICK; break; default: _hidl_cb(Status::UNSUPPORTED_OPERATION, 0); return Void(); } on(timeMS, WAVEFORM_MODE, mEffectConfig, volOffset); _hidl_cb(status, timeMS); return Void(); } } // namespace implementation } // namespace V1_3 } // namespace vibrator } // namespace hardware } // namespace android