diff options
author | Xin Li <delphij@google.com> | 2022-02-14 17:31:17 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-02-14 17:31:17 +0000 |
commit | 8e0f3aaf0924ef1641e968869b69bddd9047fac0 (patch) | |
tree | a78cbc4595fe61b048e828fcfe8f02053279ada8 | |
parent | 6a53c9f84db035e2ff06c58ee697b75d207d44d8 (diff) | |
parent | bcf55fa84f13157dd3795a4e646aedae02c3e6af (diff) | |
download | pixel-sam_222710654.tar.gz |
Merge "Merge sc-v2-dev-plus-aosp-without-vendor@8084891" into stage-aosp-mastersam_222710654
48 files changed, 2676 insertions, 82 deletions
diff --git a/fastboot/Android.bp b/fastboot/Android.bp index c40b69f7..dd2f80da 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -26,13 +26,15 @@ cc_library { relative_install_path: "hw", export_include_dirs: ["include"], shared_libs: [ - "liblog", + "android.hardware.fastboot@1.0", + "android.hardware.fastboot@1.1", "libbase", + "libcutils", + "libext4_utils", + "libfs_mgr", "libhidlbase", + "liblog", "libutils", - "libcutils", - "android.hardware.fastboot@1.0", - "android.hardware.fastboot@1.1", ], static_libs: [ "libnos_for_fastboot", diff --git a/fastboot/Fastboot.cpp b/fastboot/Fastboot.cpp index 93435d69..922334b4 100644 --- a/fastboot/Fastboot.cpp +++ b/fastboot/Fastboot.cpp @@ -19,11 +19,19 @@ #include <string> #include <unordered_map> #include <vector> +#include <map> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> + +// FS headers +#include <ext4_utils/wipe.h> +#include <fs_mgr.h> +#include <fs_mgr/roots.h> + +// Nugget headers #include <app_nugget.h> #include <nos/NuggetClient.h> #include <nos/debug.h> @@ -122,7 +130,50 @@ Return<void> Fastboot::doOemCommand(const ::android::hardware::hidl_string& oemC return Void(); } +static android::fs_mgr::Fstab fstab; +enum WipeVolumeStatus { + WIPE_OK = 0, + VOL_FSTAB, + VOL_UNKNOWN, + VOL_MOUNTED, + VOL_BLK_DEV_OPEN, + WIPE_ERROR_MAX = 0xffffffff, +}; +std::map<enum WipeVolumeStatus, std::string> wipe_vol_ret_msg{ + {WIPE_OK, ""}, + {VOL_FSTAB, "Unknown FS table"}, + {VOL_UNKNOWN, "Unknown volume"}, + {VOL_MOUNTED, "Fail to unmount volume"}, + {VOL_BLK_DEV_OPEN, "Fail to open block device"}, + {WIPE_ERROR_MAX, "Unknown wipe error"}}; + +enum WipeVolumeStatus wipe_volume(const std::string &volume) { + if (!android::fs_mgr::ReadDefaultFstab(&fstab)) { + return VOL_FSTAB; + } + const fs_mgr::FstabEntry *v = android::fs_mgr::GetEntryForPath(&fstab, volume); + if (v == nullptr) { + return VOL_UNKNOWN; + } + if (android::fs_mgr::EnsurePathUnmounted(&fstab, volume) != true) { + return VOL_MOUNTED; + } + + int fd = open(v->blk_device.c_str(), O_WRONLY | O_CREAT, 0644); + if (fd == -1) { + return VOL_BLK_DEV_OPEN; + } + wipe_block_device(fd, get_block_device_size(fd)); + close(fd); + + return WIPE_OK; +} + Return<void> Fastboot::doOemSpecificErase(V1_1::IFastboot::doOemSpecificErase_cb _hidl_cb) { + // Erase metadata partition along with userdata partition. + // Keep erasing Titan M even if failing on this case. + auto wipe_status = wipe_volume("/metadata"); + // Connect to Titan M ::nos::NuggetClient client; client.Open(); @@ -136,16 +187,27 @@ Return<void> Fastboot::doOemSpecificErase(V1_1::IFastboot::doOemSpecificErase_cb std::vector<uint8_t> magic(sizeof(magicValue)); memcpy(magic.data(), &magicValue, sizeof(magicValue)); const uint8_t retry_count = 5; + uint32_t nugget_status; for(uint8_t i = 0; i < retry_count; i++) { - const uint32_t status - = client.CallApp(APP_ID_NUGGET, NUGGET_PARAM_NUKE_FROM_ORBIT, magic, nullptr); - if (status == APP_SUCCESS) { - _hidl_cb({ Status::SUCCESS, "" }); + nugget_status = client.CallApp(APP_ID_NUGGET, NUGGET_PARAM_NUKE_FROM_ORBIT, magic, nullptr); + if (nugget_status == APP_SUCCESS && wipe_status == WIPE_OK) { + _hidl_cb({Status::SUCCESS, wipe_vol_ret_msg[wipe_status]}); return Void(); } } - _hidl_cb({ Status::FAILURE_UNKNOWN, "Titan M user data wipe failed" }); + // Return exactly what happened + if (nugget_status != APP_SUCCESS && wipe_status != WIPE_OK) { + _hidl_cb({Status::FAILURE_UNKNOWN, "Fail on wiping metadata and Titan M user data"}); + } else if (nugget_status != APP_SUCCESS) { + _hidl_cb({Status::FAILURE_UNKNOWN, "Titan M user data wipe failed"}); + } else { + if (wipe_vol_ret_msg.find(wipe_status) != wipe_vol_ret_msg.end()) + _hidl_cb({Status::FAILURE_UNKNOWN, wipe_vol_ret_msg[wipe_status]}); + else // Should not reach here, but handle it anyway + _hidl_cb({Status::FAILURE_UNKNOWN, "Unknown failure"}); + } + return Void(); } diff --git a/mm/device_gki.mk b/mm/device_gki.mk index 5d068e6c..c0bff728 100644 --- a/mm/device_gki.mk +++ b/mm/device_gki.mk @@ -1,5 +1,7 @@ PRODUCT_COPY_FILES += \ - hardware/google/pixel/mm/pixel-mm-gki.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/pixel-mm-gki.rc + hardware/google/pixel/mm/pixel-mm-gki.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/pixel-mm-gki.rc \ + hardware/google/pixel/mm/fstab.zram.2g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.2g \ + hardware/google/pixel/mm/fstab.zram.3g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.3g ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT))) PRODUCT_PACKAGES += \ diff --git a/mm/fstab.zram.2g b/mm/fstab.zram.2g new file mode 100644 index 00000000..aa59346c --- /dev/null +++ b/mm/fstab.zram.2g @@ -0,0 +1 @@ +/dev/block/zram0 none swap defaults zramsize=2147483648,max_comp_streams=8,zram_backingdev_size=512M diff --git a/mm/fstab.zram.3g b/mm/fstab.zram.3g new file mode 100644 index 00000000..b087adf1 --- /dev/null +++ b/mm/fstab.zram.3g @@ -0,0 +1 @@ +/dev/block/zram0 none swap defaults zramsize=3221225472,max_comp_streams=8,zram_backingdev_size=512M diff --git a/mm/pixel-mm-gki.rc b/mm/pixel-mm-gki.rc index 0566ef46..e5ab7f75 100644 --- a/mm/pixel-mm-gki.rc +++ b/mm/pixel-mm-gki.rc @@ -19,5 +19,11 @@ on init # khugepaged tuning write /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs 60000 +on property:persist.vendor.zram.size=* + setprop vendor.zram.size ${persist.vendor.zram.size} + +on property:sys.boot_completed=1 + swapon_all /vendor/etc/fstab.zram.${vendor.zram.size} + on property:sys.boot_completed=1 chmod 444 /sys/kernel/debug/page_owner diff --git a/pixelstats/SysfsCollector.cpp b/pixelstats/SysfsCollector.cpp index f2fdeea8..f814119f 100644 --- a/pixelstats/SysfsCollector.cpp +++ b/pixelstats/SysfsCollector.cpp @@ -894,7 +894,6 @@ void SysfsCollector::logPerDay() { logSpeechDspStat(stats_client); logUFSLifetime(stats_client); logUFSErrorStats(stats_client); - logZramStats(stats_client); logSpeakerHealthStats(stats_client); mm_metrics_reporter_.logCmaStatus(stats_client); mm_metrics_reporter_.logPixelMmMetricsPerDay(stats_client); @@ -907,6 +906,7 @@ void SysfsCollector::logPerHour() { return; } mm_metrics_reporter_.logPixelMmMetricsPerHour(stats_client); + logZramStats(stats_client); if (kPowerMitigationStatsPath != nullptr && strlen(kPowerMitigationStatsPath) > 0) mitigation_stats_reporter_.logMitigationStatsPerHour(stats_client, kPowerMitigationStatsPath); diff --git a/pixelstats/UeventListener.cpp b/pixelstats/UeventListener.cpp index 05283013..f8dec476 100644 --- a/pixelstats/UeventListener.cpp +++ b/pixelstats/UeventListener.cpp @@ -82,6 +82,7 @@ constexpr int32_t VID_MASK = 0xffff; constexpr int32_t VID_GOOGLE = 0x18d1; constexpr int32_t PID_OFFSET = 2; constexpr int32_t PID_LENGTH = 4; +constexpr uint32_t PID_P30 = 0x4f05; bool UeventListener::ReadFileToInt(const std::string &path, int *val) { return ReadFileToInt(path.c_str(), val); @@ -449,13 +450,15 @@ void UeventListener::ReportTypeCPartnerId(const std::shared_ptr<IStats> &stats_c return; } - // Upload data only for chargers - if (((vid >> PRODUCT_TYPE_OFFSET) & PRODUCT_TYPE_MASK) != PRODUCT_TYPE_CHARGER) { + // Upload data only for Google VID + if ((VID_MASK & vid) != VID_GOOGLE) { return; } - // Upload data only for Google VID - if ((VID_MASK & vid) != VID_GOOGLE) { + // Upload data only for chargers unless for P30 PID where the product type + // isn't set to charger. + if ((((vid >> PRODUCT_TYPE_OFFSET) & PRODUCT_TYPE_MASK) != PRODUCT_TYPE_CHARGER) && + (pid != PID_P30)) { return; } @@ -541,7 +544,7 @@ bool UeventListener::ProcessUevent() { devpath = cp; } else if (!strncmp(cp, "SUBSYSTEM=", strlen("SUBSYSTEM="))) { subsystem = cp; - } else if (!strncmp(cp, "DEVTYPE=typec_partner", strlen("DEVTYPE=typec_partner"))) { + } else if (!strncmp(cp, kTypeCPartnerUevent.c_str(), kTypeCPartnerUevent.size())) { collect_partner_id = true; } else if (!strncmp(cp, "POWER_SUPPLY_NAME=wireless", strlen("POWER_SUPPLY_NAME=wireless"))) { @@ -589,6 +592,7 @@ UeventListener::UeventListener(const std::string audio_uevent, const std::string kBatterySSOCPath(ssoc_details_path), kUsbPortOverheatPath(overheat_path), kChargeMetricsPath(charge_metrics_path), + kTypeCPartnerUevent(typec_partner_uevent_default), kTypeCPartnerVidPath(typec_partner_vid_path), kTypeCPartnerPidPath(typec_partner_pid_path), kWirelessChargerPtmcUevent(""), @@ -605,6 +609,9 @@ UeventListener::UeventListener(const struct UeventPaths &uevents_paths) kChargeMetricsPath((uevents_paths.ChargeMetricsPath == nullptr) ? charge_metrics_path_default : uevents_paths.ChargeMetricsPath), + kTypeCPartnerUevent((uevents_paths.TypeCPartnerUevent == nullptr) + ? typec_partner_uevent_default + : uevents_paths.TypeCPartnerUevent), kTypeCPartnerVidPath((uevents_paths.TypeCPartnerVidPath == nullptr) ? typec_partner_vid_path_default : uevents_paths.TypeCPartnerVidPath), diff --git a/pixelstats/WirelessChargeStats.cpp b/pixelstats/WirelessChargeStats.cpp index 6fd34c4a..79d62089 100644 --- a/pixelstats/WirelessChargeStats.cpp +++ b/pixelstats/WirelessChargeStats.cpp @@ -43,6 +43,10 @@ int WirelessChargeStats::TranslateSysModeToAtomValue(const int sys_mode) { return PixelAtoms::ChargeStats::ADAPTER_TYPE_WPC_EPP; case 3: return PixelAtoms::ChargeStats::ADAPTER_TYPE_WPC_L7; + case 0xe0: + return PixelAtoms::ChargeStats::ADAPTER_TYPE_DL; + case 0xa0: + return PixelAtoms::ChargeStats::ADAPTER_TYPE_L7; default: return PixelAtoms::ChargeStats::ADAPTER_TYPE_WLC; } diff --git a/pixelstats/include/pixelstats/UeventListener.h b/pixelstats/include/pixelstats/UeventListener.h index cd701e08..6505b02f 100644 --- a/pixelstats/include/pixelstats/UeventListener.h +++ b/pixelstats/include/pixelstats/UeventListener.h @@ -48,6 +48,7 @@ class UeventListener { const char *const SsocDetailsPath; const char *const OverheatPath; const char *const ChargeMetricsPath; + const char *const TypeCPartnerUevent; const char *const TypeCPartnerVidPath; const char *const TypeCPartnerPidPath; const char *const WirelessChargerPtmcUevent; @@ -63,6 +64,7 @@ class UeventListener { "/sys/class/typec/port0-partner/identity/id_header"; constexpr static const char *const typec_partner_pid_path_default = "/sys/class/typec/port0-partner/identity/product"; + constexpr static const char *const typec_partner_uevent_default = "DEVTYPE=typec_partner"; UeventListener(const std::string audio_uevent, const std::string ssoc_details_path = "", const std::string overheat_path = overheat_path_default, @@ -99,6 +101,7 @@ class UeventListener { const std::string kBatterySSOCPath; const std::string kUsbPortOverheatPath; const std::string kChargeMetricsPath; + const std::string kTypeCPartnerUevent; const std::string kTypeCPartnerVidPath; const std::string kTypeCPartnerPidPath; const std::string kWirelessChargerPtmcUevent; diff --git a/power-libperfmgr/aidl/Power.cpp b/power-libperfmgr/aidl/Power.cpp index f232ff4d..86076719 100644 --- a/power-libperfmgr/aidl/Power.cpp +++ b/power-libperfmgr/aidl/Power.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL) #define LOG_TAG "powerhal-libperfmgr" #include "Power.h" @@ -26,10 +25,8 @@ #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include <cutils/properties.h> #include <utils/Log.h> -#include <utils/Trace.h> #include "PowerHintSession.h" #include "PowerSessionManager.h" @@ -97,7 +94,6 @@ Power::Power(std::shared_ptr<HintManager> hm, std::shared_ptr<DisplayLowPower> d ndk::ScopedAStatus Power::setMode(Mode type, bool enabled) { LOG(DEBUG) << "Power setMode: " << toString(type) << " to: " << enabled; - ATRACE_INT(toString(type).c_str(), enabled); PowerSessionManager::getInstance()->updateHintMode(toString(type), enabled); switch (type) { case Mode::LOW_POWER: @@ -196,7 +192,6 @@ ndk::ScopedAStatus Power::isModeSupported(Mode type, bool *_aidl_return) { ndk::ScopedAStatus Power::setBoost(Boost type, int32_t durationMs) { LOG(DEBUG) << "Power setBoost: " << toString(type) << " duration: " << durationMs; - ATRACE_INT(toString(type).c_str(), durationMs); switch (type) { case Boost::INTERACTION: if (mVRModeOn || mSustainedPerfModeOn) { diff --git a/power-libperfmgr/aidl/PowerExt.cpp b/power-libperfmgr/aidl/PowerExt.cpp index 7fded31b..9481e4c8 100644 --- a/power-libperfmgr/aidl/PowerExt.cpp +++ b/power-libperfmgr/aidl/PowerExt.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL) #define LOG_TAG "android.hardware.power-service.pixel.ext-libperfmgr" #include "PowerExt.h" @@ -29,7 +28,6 @@ #include <android-base/strings.h> #include <utils/Log.h> -#include <utils/Trace.h> namespace aidl { namespace google { @@ -40,7 +38,6 @@ namespace pixel { ndk::ScopedAStatus PowerExt::setMode(const std::string &mode, bool enabled) { LOG(DEBUG) << "PowerExt setMode: " << mode << " to: " << enabled; - ATRACE_INT(mode.c_str(), enabled); if (enabled) { mHintManager->DoHint(mode); @@ -61,7 +58,6 @@ ndk::ScopedAStatus PowerExt::isModeSupported(const std::string &mode, bool *_aid ndk::ScopedAStatus PowerExt::setBoost(const std::string &boost, int32_t durationMs) { LOG(DEBUG) << "PowerExt setBoost: " << boost << " duration: " << durationMs; - ATRACE_INT(boost.c_str(), durationMs); if (durationMs > 0) { mHintManager->DoHint(boost, std::chrono::milliseconds(durationMs)); diff --git a/power-libperfmgr/aidl/PowerHintSession.cpp b/power-libperfmgr/aidl/PowerHintSession.cpp index 7a39dab0..359fcc3c 100644 --- a/power-libperfmgr/aidl/PowerHintSession.cpp +++ b/power-libperfmgr/aidl/PowerHintSession.cpp @@ -95,8 +95,8 @@ static double getDoubleProperty(const char *prop, double value) { return value; } -static double sPidPOver = getDoubleProperty(kPowerHalAdpfPidPOver, 5.0); -static double sPidPUnder = getDoubleProperty(kPowerHalAdpfPidPUnder, 3.0); +static double sPidPOver = getDoubleProperty(kPowerHalAdpfPidPOver, 2.0); +static double sPidPUnder = getDoubleProperty(kPowerHalAdpfPidPUnder, 1.0); static double sPidI = getDoubleProperty(kPowerHalAdpfPidI, 0.001); static double sPidDOver = getDoubleProperty(kPowerHalAdpfPidDOver, 500.0); static double sPidDUnder = getDoubleProperty(kPowerHalAdpfPidDUnder, 0.0); @@ -113,12 +113,12 @@ static const int64_t sPidIHighLimit = static const int64_t sPidILowLimit = (sPidI == 0) ? 0 : static_cast<int64_t>(::android::base::GetIntProperty<int64_t>( - kPowerHalAdpfPidILowLimit, -120) / + kPowerHalAdpfPidILowLimit, -30) / sPidI); static const int32_t sUclampMinHighLimit = - ::android::base::GetUintProperty<uint32_t>(kPowerHalAdpfUclampMinHighLimit, 512); + ::android::base::GetUintProperty<uint32_t>(kPowerHalAdpfUclampMinHighLimit, 384); static const int32_t sUclampMinLowLimit = - ::android::base::GetUintProperty<uint32_t>(kPowerHalAdpfUclampMinLowLimit, 0); + ::android::base::GetUintProperty<uint32_t>(kPowerHalAdpfUclampMinLowLimit, 2); static const uint32_t sUclampMinGranularity = ::android::base::GetUintProperty<uint32_t>(kPowerHalAdpfUclampMinGranularity, 5); static const int64_t sStaleTimeFactor = @@ -146,6 +146,8 @@ PowerHintSession::PowerHintSession(int32_t tgid, int32_t uid, const std::vector< ATRACE_INT(sz.c_str(), (int64_t)mDescriptor->duration.count()); sz = StringPrintf("adpf.%s-active", idstr.c_str()); ATRACE_INT(sz.c_str(), mDescriptor->is_active.load()); + sz = StringPrintf("adpf.%s-stale", idstr.c_str()); + ATRACE_INT(sz.c_str(), isStale()); } PowerSessionManager::getInstance()->addPowerSession(this); // init boost @@ -286,12 +288,13 @@ ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } if (PowerHintMonitor::getInstance()->isRunning() && isStale()) { + mDescriptor->integral_error = std::max(sPidIInit, mDescriptor->integral_error); if (ATRACE_ENABLED()) { const std::string idstr = getIdString(); - std::string sz = StringPrintf("adpf.%s-stale", idstr.c_str()); + std::string sz = StringPrintf("adpf.%s-wakeup", idstr.c_str()); + ATRACE_INT(sz.c_str(), mDescriptor->integral_error); ATRACE_INT(sz.c_str(), 0); } - mDescriptor->integral_error = std::max(sPidIInit, mDescriptor->integral_error); } int64_t targetDurationNanos = (int64_t)mDescriptor->duration.count(); int64_t length = actualDurations.size(); @@ -325,6 +328,15 @@ ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( } mDescriptor->previous_error = error; } + if (ATRACE_ENABLED()) { + const std::string idstr = getIdString(); + std::string sz = StringPrintf("adpf.%s-err", idstr.c_str()); + ATRACE_INT(sz.c_str(), err_sum / (length - p_start)); + sz = StringPrintf("adpf.%s-integral", idstr.c_str()); + ATRACE_INT(sz.c_str(), mDescriptor->integral_error); + sz = StringPrintf("adpf.%s-derivative", idstr.c_str()); + ATRACE_INT(sz.c_str(), derivative_sum / dt / (length - d_start)); + } int64_t pOut = static_cast<int64_t>((err_sum > 0 ? sPidPOver : sPidPUnder) * err_sum / (length - p_start)); int64_t iOut = static_cast<int64_t>(sPidI * mDescriptor->integral_error); @@ -332,6 +344,7 @@ ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( derivative_sum / dt / (length - d_start)); int64_t output = pOut + iOut + dOut; + if (ATRACE_ENABLED()) { const std::string idstr = getIdString(); std::string sz = StringPrintf("adpf.%s-actl_last", idstr.c_str()); @@ -350,6 +363,10 @@ ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( ATRACE_INT(sz.c_str(), dOut); sz = StringPrintf("adpf.%s-pid.output", idstr.c_str()); ATRACE_INT(sz.c_str(), output); + sz = StringPrintf("adpf.%s-stale", idstr.c_str()); + ATRACE_INT(sz.c_str(), isStale()); + sz = StringPrintf("adpf.%s-pid.overtime", idstr.c_str()); + ATRACE_INT(sz.c_str(), err_sum > 0); } mDescriptor->update_count++; @@ -357,8 +374,7 @@ ndk::ScopedAStatus PowerHintSession::reportActualWorkDuration( /* apply to all the threads in the group */ if (output != 0) { - int next_min = - std::min(sUclampMinHighLimit, mDescriptor->current_min + static_cast<int>(output)); + int next_min = std::min(sUclampMinHighLimit, static_cast<int>(output)); next_min = std::max(sUclampMinLowLimit, next_min); if (std::abs(mDescriptor->current_min - next_min) > sUclampMinGranularity) { setUclamp(next_min); @@ -429,6 +445,11 @@ void PowerHintSession::StaleHandler::updateStaleTimer() { duration_cast<nanoseconds>(next - now).count(), this, NULL); mIsMonitoringStale.store(true); } + if (ATRACE_ENABLED()) { + const std::string idstr = mSession->getIdString(); + std::string sz = StringPrintf("adpf.%s-stale", idstr.c_str()); + ATRACE_INT(sz.c_str(), 0); + } } } diff --git a/power-libperfmgr/disp-power/InteractionHandler.cpp b/power-libperfmgr/disp-power/InteractionHandler.cpp index f6cd0e9a..bfdffa79 100644 --- a/power-libperfmgr/disp-power/InteractionHandler.cpp +++ b/power-libperfmgr/disp-power/InteractionHandler.cpp @@ -133,7 +133,6 @@ void InteractionHandler::PerfLock() { if (!mHintManager->DoHint("INTERACTION")) { ALOGE("%s: do hint INTERACTION failed", __func__); } - ATRACE_INT("interaction_lock", 1); } void InteractionHandler::PerfRel() { @@ -141,7 +140,6 @@ void InteractionHandler::PerfRel() { if (!mHintManager->EndHint("INTERACTION")) { ALOGE("%s: end hint INTERACTION failed", __func__); } - ATRACE_INT("interaction_lock", 0); } void InteractionHandler::Acquire(int32_t duration) { diff --git a/power-libperfmgr/libperfmgr/HintManager.cc b/power-libperfmgr/libperfmgr/HintManager.cc index b25ce45e..bbfce08c 100644 --- a/power-libperfmgr/libperfmgr/HintManager.cc +++ b/power-libperfmgr/libperfmgr/HintManager.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL) #define LOG_TAG "libperfmgr" #include "perfmgr/HintManager.h" @@ -23,6 +24,7 @@ #include <android-base/stringprintf.h> #include <json/reader.h> #include <json/value.h> +#include <utils/Trace.h> #include <inttypes.h> #include <algorithm> @@ -86,6 +88,8 @@ void HintManager::DoHintStatus(const std::string &hint_type, std::chrono::millis std::lock_guard<std::mutex> lock(actions_.at(hint_type).status->mutex); actions_.at(hint_type).status->stats.count.fetch_add(1); auto now = std::chrono::steady_clock::now(); + ATRACE_INT(hint_type.c_str(), (timeout_ms == kMilliSecondZero) ? std::numeric_limits<int>::max() + : timeout_ms.count()); if (now > actions_.at(hint_type).status->end_time) { actions_.at(hint_type).status->stats.duration_ms.fetch_add( std::chrono::duration_cast<std::chrono::milliseconds>( @@ -102,6 +106,7 @@ void HintManager::EndHintStatus(const std::string &hint_type) { std::lock_guard<std::mutex> lock(actions_.at(hint_type).status->mutex); // Update HintStats if the hint ends earlier than expected end_time auto now = std::chrono::steady_clock::now(); + ATRACE_INT(hint_type.c_str(), 0); if (now < actions_.at(hint_type).status->end_time) { actions_.at(hint_type).status->stats.duration_ms.fetch_add( std::chrono::duration_cast<std::chrono::milliseconds>( diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h index 747e5c44..8943c98c 100644 --- a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h +++ b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h @@ -41,7 +41,7 @@ struct HintStats { struct HintStatus { const std::chrono::milliseconds max_timeout; HintStatus() : max_timeout(std::chrono::milliseconds(0)) {} - HintStatus(std::chrono::milliseconds max_timeout) + explicit HintStatus(std::chrono::milliseconds max_timeout) : max_timeout(max_timeout), start_time(std::chrono::steady_clock::time_point::min()), end_time(std::chrono::steady_clock::time_point::min()) {} @@ -58,7 +58,7 @@ struct HintStatus { enum class HintActionType { Node, DoHint, EndHint, MaskHint }; struct HintAction { - HintAction(HintActionType t, std::string v) : type(t), value(v) {} + HintAction(HintActionType t, const std::string &v) : type(t), value(v) {} HintActionType type; std::string value; }; @@ -80,7 +80,7 @@ struct Hint { class HintManager { public: HintManager(sp<NodeLooperThread> nm, const std::unordered_map<std::string, Hint> &actions) - : nm_(std::move(nm)), actions_(std::move(actions)) {} + : nm_(std::move(nm)), actions_(actions) {} ~HintManager() { if (nm_.get() != nullptr) nm_->Stop(); } diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h b/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h index 698a8216..f3035887 100644 --- a/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h +++ b/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h @@ -36,8 +36,8 @@ using ReqTime = std::chrono::time_point<std::chrono::steady_clock>; // that hint. class RequestGroup { public: - RequestGroup(std::string request_value) // NOLINT(runtime/explicit) - : request_value_(std::move(request_value)) {} + RequestGroup(const std::string &request_value) // NOLINT(runtime/explicit) + : request_value_(request_value) {} // Remove expired request in the map and return true when request_map_ is // not empty, false when request_map_ is empty; also update expire_time with diff --git a/thermal/Thermal.cpp b/thermal/Thermal.cpp index b63ca2c2..ec7d3fab 100644 --- a/thermal/Thermal.cpp +++ b/thermal/Thermal.cpp @@ -263,10 +263,13 @@ void Thermal::sendThermalChangedCallback(const Temperature_2_0 &t) { [&](const CallbackSetting &c) { if (!c.is_filter_type || t.type == c.type) { Return<void> ret = c.callback->notifyThrottling(t); - return !ret.isOk(); + if (!ret.isOk()) { + LOG(ERROR) << "a Thermal callback is dead, removed from " + "callback list."; + return true; + } + return false; } - LOG(ERROR) - << "a Thermal callback is dead, removed from callback list."; return false; }), callbacks_.end()); diff --git a/vibrator/common/HardwareBase.h b/vibrator/common/HardwareBase.h index 7612fb35..214c0565 100644 --- a/vibrator/common/HardwareBase.h +++ b/vibrator/common/HardwareBase.h @@ -71,10 +71,14 @@ class HwApiBase { template <typename T> bool get(T *value, std::istream *stream); template <typename T> + bool getStr(T *value, std::istream *stream); + template <typename T> bool set(const T &value, std::ostream *stream); template <typename T> bool poll(const T &value, std::istream *stream); template <typename T> + bool pollStr(const T &value, std::istream *stream, const int32_t timeout = -1); + template <typename T> void record(const char *func, const T &value, const std::ios *stream); private: @@ -109,6 +113,24 @@ bool HwApiBase::get(T *value, std::istream *stream) { } template <typename T> +bool HwApiBase::getStr(T *value, std::istream *stream) { + ATRACE_NAME("HwApi::getStr"); + std::scoped_lock ioLock{mIoMutex}; + bool ret; + stream->seekg(0); + utils::unpack(*stream, value); + if (!(ret = !!*stream)) { + ALOGE("Failed to read %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + } + if (!(ret = stream->eof())) { + ALOGE("Invalid %s !", mNames[stream].c_str()); + } + stream->clear(); + HWAPI_RECORD(*value, stream); + return ret; +} + +template <typename T> bool HwApiBase::set(const T &value, std::ostream *stream) { ATRACE_NAME("HwApi::set"); using utils::operator<<; @@ -149,6 +171,41 @@ bool HwApiBase::poll(const T &value, std::istream *stream) { } template <typename T> +bool HwApiBase::pollStr(const T &value, std::istream *stream, const int32_t timeoutMs) { + ATRACE_NAME("HwApi::pollStr"); + auto path = mPathPrefix + mNames[stream]; + unique_fd fileFd{::open(path.c_str(), O_RDONLY)}; + unique_fd epollFd{epoll_create(1)}; + epoll_event event = { + .events = EPOLLPRI | EPOLLET, + }; + T actual; + bool ret; + int epollRet; + + if (timeoutMs < -1) { + ALOGE("Invalid polling timeout!"); + return false; + } + + if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fileFd, &event)) { + ALOGE("Failed to poll string %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + return false; + } + + while ((ret = getStr(&actual, stream)) && (actual != value)) { + epollRet = epoll_wait(epollFd, &event, 1, timeoutMs); + if (epollRet <= 0) { + ALOGE("Polling error or timeout! (%d)", epollRet); + return false; + } + } + + HWAPI_RECORD(value, stream); + return ret; +} + +template <typename T> void HwApiBase::record(const char *func, const T &value, const std::ios *stream) { std::lock_guard<std::mutex> lock(mRecordsMutex); mRecords.emplace_back(std::make_unique<Record<T>>(func, value, stream)); diff --git a/vibrator/cs40l25/Android.bp b/vibrator/cs40l25/Android.bp index 6db96f44..80436f73 100644 --- a/vibrator/cs40l25/Android.bp +++ b/vibrator/cs40l25/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2017 The Android Open Source Project +// Copyright (C) 2021 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. @@ -17,11 +17,46 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +soong_config_module_type { + name: "haptics_feature_cc_defaults", + module_type: "cc_defaults", + config_namespace: "haptics", + variables: [ + "actuator_model", + ], + properties: ["cflags"], +} + +soong_config_string_variable { + name: "actuator_model", + values: [ + "luxshare_ict_081545", + "luxshare_ict_lt_xlra1906d", + ], +} + +haptics_feature_cc_defaults { + name: "haptics_feature_defaults", + soong_config_variables: { + actuator_model: { + luxshare_ict_081545: { + cflags: [ + "-DLUXSHARE_ICT_081545", + ], + }, + luxshare_ict_lt_xlra1906d: { + cflags: [ + "-DLUXSHARE_ICT_LT_XLRA1906D", + ], + }, + }, + }, +} + cc_defaults { name: "android.hardware.vibrator-defaults.cs40l25", cflags: [ "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", - "-DLOG_TAG=\"android.hardware.vibrator-cs40l25\"", ], shared_libs: [ "libbinder", @@ -50,7 +85,10 @@ cc_defaults { cc_library_shared { name: "android.hardware.vibrator-impl.cs40l25", - defaults: ["VibratorHalCs40l25BinaryDefaults"], + defaults: [ + "VibratorHalCs40l25BinaryDefaults", + "haptics_feature_defaults", + ], srcs: ["Vibrator.cpp"], include_dirs: [ "external/tinyalsa/include", @@ -78,6 +116,9 @@ cc_binary { "libcutils", "libtinyalsa", ], + cflags: [ + "-DLOG_TAG=\"android.hardware.vibrator-cs40l25\"", + ], proprietary: true, } @@ -95,6 +136,50 @@ cc_binary { "libcutils", "libtinyalsa", ], - cflags: ["-DVIBRATOR_NAME=\"dual\""], + cflags: [ + "-DVIBRATOR_NAME=\"dual\"", + "-DLOG_TAG=\"android.hardware.vibrator-cs40l25-dual\"", + ], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l25-specific-revision", + defaults: ["VibratorHalCs40l25BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l25-specific-revision.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l25.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l25", + "libcutils", + "libtinyalsa", + ], + cflags: [ + "-DLOG_TAG=\"android.hardware.vibrator-cs40l25\"", + ], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l25-specific-revision-dual", + defaults: ["VibratorHalCs40l25BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l25-specific-revision-dual.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l25-dual.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l25", + "libcutils", + "libtinyalsa", + ], + cflags: [ + "-DVIBRATOR_NAME=\"dual\"", + "-DLOG_TAG=\"android.hardware.vibrator-cs40l25-dual\"", + ], proprietary: true, } diff --git a/vibrator/cs40l25/Hardware.h b/vibrator/cs40l25/Hardware.h index f74fe931..d1883e53 100644 --- a/vibrator/cs40l25/Hardware.h +++ b/vibrator/cs40l25/Hardware.h @@ -48,7 +48,6 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { open("device/clab_enable", &mClabEnable); open("device/available_pwle_segments", &mAvailablePwleSegments); open("device/pwle", &mPwle); - open("device/pwle_ramp_down", &mPwleRampDown); } bool setF0(uint32_t value) override { return set(value, &mF0); } @@ -77,7 +76,6 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { bool getAvailablePwleSegments(uint32_t *value) override { return get(value, &mAvailablePwleSegments); } bool hasPwle() override { return has(mPwle); } bool setPwle(std::string value) override { return set(value, &mPwle); } - bool setPwleRampDown(uint32_t value) override { return set(value, &mPwleRampDown); } void debug(int fd) override { HwApiBase::debug(fd); } private: @@ -103,7 +101,6 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { std::ofstream mClabEnable; std::ifstream mAvailablePwleSegments; std::ofstream mPwle; - std::ofstream mPwleRampDown; }; class HwCal : public Vibrator::HwCal, private HwCalBase { diff --git a/vibrator/cs40l25/Vibrator.cpp b/vibrator/cs40l25/Vibrator.cpp index 8110cb37..002fccf9 100644 --- a/vibrator/cs40l25/Vibrator.cpp +++ b/vibrator/cs40l25/Vibrator.cpp @@ -101,8 +101,6 @@ static constexpr float PWLE_FREQUENCY_MIN_HZ = 0.25; static constexpr float PWLE_FREQUENCY_MAX_HZ = 1023.75; static constexpr float PWLE_BW_MAP_SIZE = 1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ); -static constexpr float RAMP_DOWN_CONSTANT = 1048.576; -static constexpr float RAMP_DOWN_TIME_MS = 50.0; static struct pcm_config haptic_nohost_config = { .channels = 1, @@ -119,13 +117,31 @@ static uint8_t amplitudeToScale(float amplitude, float maximum) { // Discrete points of frequency:max_level pairs as recommended by the document // [R4O6] Max. Allowable Chirp Levels (go/r4o6-max-chirp-levels) around resonant frequency +#if defined(LUXSHARE_ICT_081545) static std::map<float, float> discretePwleMaxLevels = {{120.0, 0.4}, {130.0, 0.31}, {140.0, 0.14}, {145.0, 0.09}, {150.0, 0.15}, {160.0, 0.35}, {170.0, 0.4}}; +// Discrete points of frequency:max_level pairs as recommended by the document +// [P7] Max. Allowable Chirp Levels (go/p7-max-chirp-levels) around resonant frequency +#elif defined(LUXSHARE_ICT_LT_XLRA1906D) +static std::map<float, float> discretePwleMaxLevels = {{145.0, 0.38}, {150.0, 0.35}, {160.0, 0.35}, + {170.0, 0.15}, {180.0, 0.35}, {190.0, 0.35}, + {200.0, 0.38}}; +#else +static std::map<float, float> discretePwleMaxLevels = {}; +#endif // Initialize all limits to 0.4 according to the document [R4O6] Max. Allowable Chirp Levels // (go/r4o6-max-chirp-levels) +#if defined(LUXSHARE_ICT_081545) std::vector<float> pwleMaxLevelLimitMap(PWLE_BW_MAP_SIZE, 0.4); +// Initialize all limits to 0.38 according to the document [P7] Max. Allowable Chirp Levels +// (go/p7-max-chirp-levels) +#elif defined(LUXSHARE_ICT_LT_XLRA1906D) +std::vector<float> pwleMaxLevelLimitMap(PWLE_BW_MAP_SIZE, 0.38); +#else +std::vector<float> pwleMaxLevelLimitMap(PWLE_BW_MAP_SIZE, 1.0); +#endif void Vibrator::createPwleMaxLevelLimitMap() { int32_t capabilities; @@ -133,6 +149,17 @@ void Vibrator::createPwleMaxLevelLimitMap() { if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { std::map<float, float>::iterator itr0, itr1; + if (discretePwleMaxLevels.empty()) { + return; + } + if (discretePwleMaxLevels.size() == 1) { + itr0 = discretePwleMaxLevels.begin(); + float pwleMaxLevelLimitMapIdx = + (itr0->first - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ; + pwleMaxLevelLimitMap[pwleMaxLevelLimitMapIdx] = itr0->second; + return; + } + itr0 = discretePwleMaxLevels.begin(); itr1 = std::next(itr0, 1); @@ -142,7 +169,7 @@ void Vibrator::createPwleMaxLevelLimitMap() { float x1 = itr1->first; float y1 = itr1->second; float pwleMaxLevelLimitMapIdx = - (itr0->first - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ; + (itr0->first - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ; for (float xp = x0; xp < (x1 + PWLE_FREQUENCY_RESOLUTION_HZ); xp += PWLE_FREQUENCY_RESOLUTION_HZ) { @@ -235,7 +262,6 @@ Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal) createPwleMaxLevelLimitMap(); mIsUnderExternalControl = false; - setPwleRampDown(); } ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { @@ -1150,22 +1176,6 @@ fail: return false; } -void Vibrator::setPwleRampDown() { - // The formula for calculating the ramp down coefficient to be written into - // pwle_ramp_down is as follows: - // Crd = 1048.576 / Trd - // where Trd is the desired ramp down time in seconds - // pwle_ramp_down accepts only 24 bit integers values - - const float seconds = RAMP_DOWN_TIME_MS / 1000; - const auto ramp_down_coefficient = static_cast<uint32_t>(RAMP_DOWN_CONSTANT / seconds); - - if (!mHwApi->setPwleRampDown(ramp_down_coefficient)) { - ALOGE("Failed to write \"%d\" to pwle_ramp_down (%d): %s", ramp_down_coefficient, errno, - strerror(errno)); - } -} - } // namespace vibrator } // namespace hardware } // namespace android diff --git a/vibrator/cs40l25/Vibrator.h b/vibrator/cs40l25/Vibrator.h index 88f7abd8..08a115e4 100644 --- a/vibrator/cs40l25/Vibrator.h +++ b/vibrator/cs40l25/Vibrator.h @@ -101,9 +101,6 @@ class Vibrator : public BnVibrator { // Specifies piecewise-linear specifications to generate complex // waveforms. virtual bool setPwle(std::string value) = 0; - // Specifies the coefficient required for a ramp down when a waveform - // ends - virtual bool setPwleRampDown(uint32_t value) = 0; // Emit diagnostic information to the given file. virtual void debug(int fd) = 0; }; @@ -203,7 +200,6 @@ class Vibrator : public BnVibrator { bool hasHapticAlsaDevice(); bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device); void createPwleMaxLevelLimitMap(); - void setPwleRampDown(); std::unique_ptr<HwApi> mHwApi; std::unique_ptr<HwCal> mHwCal; diff --git a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-dual.rc b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-dual.rc index 7e9ff688..f9ed341c 100644 --- a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-dual.rc +++ b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-dual.rc @@ -13,6 +13,7 @@ on boot chown system system /sys/class/leds/vibrator_1/state chown system system /sys/class/leds/vibrator_1/device/asp_enable + chown system system /sys/class/leds/vibrator_1/device/available_pwle_segments chown system system /sys/class/leds/vibrator_1/device/clab_enable chown system system /sys/class/leds/vibrator_1/device/comp_enable chown system system /sys/class/leds/vibrator_1/device/cp_dig_scale @@ -35,6 +36,7 @@ on boot chown system system /sys/class/leds/vibrator_1/device/heartbeat chown system system /sys/class/leds/vibrator_1/device/hw_reset chown system system /sys/class/leds/vibrator_1/device/num_waves + chown system system /sys/class/leds/vibrator_1/device/pwle chown system system /sys/class/leds/vibrator_1/device/q_stored chown system system /sys/class/leds/vibrator_1/device/redc_comp_enable chown system system /sys/class/leds/vibrator_1/device/redc_stored @@ -56,6 +58,7 @@ service vendor.vibrator.cs40l25-dual /vendor/bin/hw/android.hardware.vibrator-se setenv HWAPI_PATH_PREFIX /sys/class/leds/vibrator_1/ setenv HWAPI_DEBUG_PATHS " device/asp_enable + device/available_pwle_segments device/clab_enable device/f0_stored device/f0_offset @@ -66,6 +69,7 @@ service vendor.vibrator.cs40l25-dual /vendor/bin/hw/android.hardware.vibrator-se device/gpio1_rise_index device/heartbeat device/num_waves + device/pwle device/q_stored device/redc_stored state diff --git a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-specific-revision-dual.rc b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-specific-revision-dual.rc new file mode 100644 index 00000000..8b7cd72f --- /dev/null +++ b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-specific-revision-dual.rc @@ -0,0 +1,78 @@ +on boot && property:ro.boot.revision=PROTO1.0 + wait /sys/class/leds/vibrator_1/device + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l25a_dual.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l25a_dual.cal + + chown system system /sys/class/leds/vibrator_1/activate + chown system system /sys/class/leds/vibrator_1/brightness + chown system system /sys/class/leds/vibrator_1/duration + chown system system /sys/class/leds/vibrator_1/state + + chown system system /sys/class/leds/vibrator_1/device/asp_enable + chown system system /sys/class/leds/vibrator_1/device/available_pwle_segments + chown system system /sys/class/leds/vibrator_1/device/clab_enable + chown system system /sys/class/leds/vibrator_1/device/comp_enable + chown system system /sys/class/leds/vibrator_1/device/cp_dig_scale + chown system system /sys/class/leds/vibrator_1/device/cp_trigger_duration + chown system system /sys/class/leds/vibrator_1/device/cp_trigger_index + chown system system /sys/class/leds/vibrator_1/device/cp_trigger_q_sub + chown system system /sys/class/leds/vibrator_1/device/cp_trigger_queue + chown system system /sys/class/leds/vibrator_1/device/dig_scale + chown system system /sys/class/leds/vibrator_1/device/exc_enable + chown system system /sys/class/leds/vibrator_1/device/f0_stored + chown system system /sys/class/leds/vibrator_1/device/f0_offset + chown system system /sys/class/leds/vibrator_1/device/fw_rev + chown system system /sys/class/leds/vibrator_1/device/gpio1_enable + chown system system /sys/class/leds/vibrator_1/device/gpio1_fall_dig_scale + chown system system /sys/class/leds/vibrator_1/device/gpio1_fall_index + chown system system /sys/class/leds/vibrator_1/device/gpio1_rise_dig_scale + chown system system /sys/class/leds/vibrator_1/device/gpio1_rise_index + chown system system /sys/class/leds/vibrator_1/device/gpio_event + chown system system /sys/class/leds/vibrator_1/device/gpio_trigger + chown system system /sys/class/leds/vibrator_1/device/heartbeat + chown system system /sys/class/leds/vibrator_1/device/hw_reset + chown system system /sys/class/leds/vibrator_1/device/num_waves + chown system system /sys/class/leds/vibrator_1/device/pwle + chown system system /sys/class/leds/vibrator_1/device/q_stored + chown system system /sys/class/leds/vibrator_1/device/redc_comp_enable + chown system system /sys/class/leds/vibrator_1/device/redc_stored + chown system system /sys/class/leds/vibrator_1/device/standby_timeout + chown system system /sys/class/leds/vibrator_1/device/vbatt_max + chown system system /sys/class/leds/vibrator_1/device/vbatt_min + chown system system /sys/class/leds/vibrator_1/device/vibe_state + + enable vendor.vibrator.cs40l25-dual + +service vendor.vibrator.cs40l25-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l25-specific-revision-dual + class hal + user system + group system + + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l25a_dual.cal + + setenv HWAPI_PATH_PREFIX /sys/class/leds/vibrator_1/ + setenv HWAPI_DEBUG_PATHS " + device/asp_enable + device/available_pwle_segments + device/clab_enable + device/f0_stored + device/f0_offset + device/fw_rev + device/gpio1_fall_dig_scale + device/gpio1_fall_index + device/gpio1_rise_dig_scale + device/gpio1_rise_index + device/heartbeat + device/num_waves + device/pwle + device/q_stored + device/redc_stored + state + " + + disabled diff --git a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-specific-revision.rc b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-specific-revision.rc new file mode 100644 index 00000000..29adb894 --- /dev/null +++ b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25-specific-revision.rc @@ -0,0 +1,75 @@ +on boot && property:ro.boot.revision=PROTO1.0 + wait /sys/class/leds/vibrator/device + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l25a.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l25a.cal + + chown system system /sys/class/leds/vibrator/activate + chown system system /sys/class/leds/vibrator/brightness + chown system system /sys/class/leds/vibrator/duration + chown system system /sys/class/leds/vibrator/state + + chown system system /sys/class/leds/vibrator/device/asp_enable + chown system system /sys/class/leds/vibrator/device/available_pwle_segments + chown system system /sys/class/leds/vibrator/device/clab_enable + chown system system /sys/class/leds/vibrator/device/comp_enable + chown system system /sys/class/leds/vibrator/device/cp_dig_scale + chown system system /sys/class/leds/vibrator/device/cp_trigger_duration + chown system system /sys/class/leds/vibrator/device/cp_trigger_index + chown system system /sys/class/leds/vibrator/device/cp_trigger_q_sub + chown system system /sys/class/leds/vibrator/device/cp_trigger_queue + chown system system /sys/class/leds/vibrator/device/dig_scale + chown system system /sys/class/leds/vibrator/device/exc_enable + chown system system /sys/class/leds/vibrator/device/f0_stored + chown system system /sys/class/leds/vibrator/device/f0_offset + chown system system /sys/class/leds/vibrator/device/fw_rev + chown system system /sys/class/leds/vibrator/device/gpio1_fall_dig_scale + chown system system /sys/class/leds/vibrator/device/gpio1_fall_index + chown system system /sys/class/leds/vibrator/device/gpio1_rise_dig_scale + chown system system /sys/class/leds/vibrator/device/gpio1_rise_index + chown system system /sys/class/leds/vibrator/device/heartbeat + chown system system /sys/class/leds/vibrator/device/hw_reset + chown system system /sys/class/leds/vibrator/device/num_waves + chown system system /sys/class/leds/vibrator/device/pwle + chown system system /sys/class/leds/vibrator/device/q_stored + chown system system /sys/class/leds/vibrator/device/redc_comp_enable + chown system system /sys/class/leds/vibrator/device/redc_stored + chown system system /sys/class/leds/vibrator/device/standby_timeout + chown system system /sys/class/leds/vibrator/device/vbatt_max + chown system system /sys/class/leds/vibrator/device/vbatt_min + chown system system /sys/class/leds/vibrator/device/vibe_state + + enable vendor.vibrator.cs40l25 + +service vendor.vibrator.cs40l25 /vendor/bin/hw/android.hardware.vibrator-service.cs40l25-specific-revision + class hal + user system + group system + + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l25a.cal + + setenv HWAPI_PATH_PREFIX /sys/class/leds/vibrator/ + setenv HWAPI_DEBUG_PATHS " + device/asp_enable + device/available_pwle_segments + device/clab_enable + device/f0_stored + device/f0_offset + device/fw_rev + device/gpio1_fall_dig_scale + device/gpio1_fall_index + device/gpio1_rise_dig_scale + device/gpio1_rise_index + device/heartbeat + device/num_waves + device/pwle + device/q_stored + device/redc_stored + state + " + + disabled diff --git a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc index f373d31f..3f28d81f 100644 --- a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc +++ b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc @@ -34,7 +34,6 @@ on boot chown system system /sys/class/leds/vibrator/device/hw_reset chown system system /sys/class/leds/vibrator/device/num_waves chown system system /sys/class/leds/vibrator/device/pwle - chown system system /sys/class/leds/vibrator/device/pwle_ramp_down chown system system /sys/class/leds/vibrator/device/q_stored chown system system /sys/class/leds/vibrator/device/redc_comp_enable chown system system /sys/class/leds/vibrator/device/redc_stored @@ -68,7 +67,6 @@ service vendor.vibrator.cs40l25 /vendor/bin/hw/android.hardware.vibrator-service device/heartbeat device/num_waves device/pwle - device/pwle_ramp_down device/q_stored device/redc_stored state diff --git a/vibrator/cs40l25/device-stereo-specific-revision.mk b/vibrator/cs40l25/device-stereo-specific-revision.mk new file mode 100644 index 00000000..485de0cb --- /dev/null +++ b/vibrator/cs40l25/device-stereo-specific-revision.mk @@ -0,0 +1,6 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l25-specific-revision \ + android.hardware.vibrator-service.cs40l25-specific-revision-dual \ + +BOARD_SEPOLICY_DIRS += \ + hardware/google/pixel-sepolicy/vibrator/cs40l25 \ diff --git a/vibrator/cs40l25/device-stereo.mk b/vibrator/cs40l25/device-stereo.mk index fe69b635..8a030974 100644 --- a/vibrator/cs40l25/device-stereo.mk +++ b/vibrator/cs40l25/device-stereo.mk @@ -3,5 +3,4 @@ PRODUCT_PACKAGES += \ android.hardware.vibrator-service.cs40l25-dual \ BOARD_SEPOLICY_DIRS += \ - hardware/google/pixel-sepolicy/vibrator/common \ hardware/google/pixel-sepolicy/vibrator/cs40l25 \ diff --git a/vibrator/cs40l25/tests/mocks.h b/vibrator/cs40l25/tests/mocks.h index 8f2c672e..2c69e27b 100644 --- a/vibrator/cs40l25/tests/mocks.h +++ b/vibrator/cs40l25/tests/mocks.h @@ -49,7 +49,6 @@ class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { MOCK_METHOD1(getAvailablePwleSegments, bool(uint32_t *value)); MOCK_METHOD0(hasPwle, bool()); MOCK_METHOD1(setPwle, bool(std::string value)); - MOCK_METHOD1(setPwleRampDown, bool(uint32_t value)); MOCK_METHOD1(debug, void(int fd)); ~MockApi() override { destructor(); }; diff --git a/vibrator/cs40l25/tests/test-hwapi.cpp b/vibrator/cs40l25/tests/test-hwapi.cpp index a339207b..7f2ae01b 100644 --- a/vibrator/cs40l25/tests/test-hwapi.cpp +++ b/vibrator/cs40l25/tests/test-hwapi.cpp @@ -55,7 +55,6 @@ class HwApiTest : public Test { "device/num_waves", "device/available_pwle_segments", "device/pwle", - "device/pwle_ramp_down", }; public: @@ -320,8 +319,6 @@ INSTANTIATE_TEST_CASE_P( &Vibrator::HwApi::setGpioRiseIndex), SetUint32Test::MakeParam("device/gpio1_rise_dig_scale", &Vibrator::HwApi::setGpioRiseScale), - SetUint32Test::MakeParam("device/pwle_ramp_down", - &Vibrator::HwApi::setPwleRampDown), }), SetUint32Test::PrintParam); diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp new file mode 100644 index 00000000..df78ae8e --- /dev/null +++ b/vibrator/cs40l26/Android.bp @@ -0,0 +1,170 @@ +// +// Copyright (C) 2021 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_defaults { + name: "android.hardware.vibrator-defaults.cs40l26", + cflags: [ + "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", + "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"", + ], + shared_libs: [ + "libbinder", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26BinaryDefaults", + defaults: [ + "PixelVibratorBinaryDefaults", + "android.hardware.vibrator-defaults.cs40l26", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26TestDefaults", + defaults: [ + "PixelVibratorTestDefaults", + "android.hardware.vibrator-defaults.cs40l26", + ], + shared_libs: ["android.hardware.vibrator-impl.cs40l26"], + include_dirs: [ + "external/tinyalsa/include", + ], +} + +cc_library_shared { + name: "android.hardware.vibrator-impl.cs40l26", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + srcs: ["Vibrator.cpp"], + include_dirs: [ + "external/tinyalsa/include", + ], + shared_libs: [ + "libcutils", + "libtinyalsa", + ], + export_include_dirs: ["."], + vendor_available: true, + visibility: [":__subpackages__"], +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libcutils", + "libtinyalsa", + ], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-dual", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libcutils", + "libtinyalsa", + ], + cflags: ["-DVIBRATOR_NAME=\"dual\""], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-evt", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26-evt.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libcutils", + "libtinyalsa", + ], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-evt-dual", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26-evt-dual.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libcutils", + "libtinyalsa", + ], + cflags: ["-DVIBRATOR_NAME=\"dual\""], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-dvt", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26-dvt.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libcutils", + "libtinyalsa", + ], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-dvt-dual", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26-dvt-dual.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"], + srcs: ["service.cpp"], + include_dirs: [ + "external/tinyalsa/include" + ], + shared_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libcutils", + "libtinyalsa", + ], + cflags: ["-DVIBRATOR_NAME=\"dual\""], + proprietary: true, +} diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h new file mode 100644 index 00000000..a555874e --- /dev/null +++ b/vibrator/cs40l26/Hardware.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 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. + */ +#pragma once + +#include "HardwareBase.h" +#include "Vibrator.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +class HwApi : public Vibrator::HwApi, private HwApiBase { + public: + HwApi() { + open("calibration/f0_stored", &mF0); + open("default/f0_offset", &mF0Offset); + open("calibration/redc_stored", &mRedc); + open("calibration/q_stored", &mQ); + open("default/vibe_state", &mVibeState); + open("default/num_waves", &mEffectCount); + open("default/owt_free_space", &mOwtFreeSpace); + } + + bool setF0(std::string value) override { return set(value, &mF0); } + bool setF0Offset(uint32_t value) override { return set(value, &mF0Offset); } + bool setRedc(std::string value) override { return set(value, &mRedc); } + bool setQ(std::string value) override { return set(value, &mQ); } + bool getEffectCount(uint32_t *value) override { return getStr(value, &mEffectCount); } + bool pollVibeState(std::string value, int32_t timeoutMs) override { + return pollStr(value, &mVibeState, timeoutMs); + } + bool setClabEnable(bool value) override { return set(value, &mClabEnable); } + bool getAvailablePwleSegments(uint32_t *value) override { + return getStr(value, &mAvailablePwleSegments); + } + bool setPwle(std::string value) override { return set(value, &mPwle); } + bool hasOwtFreeSpace() override { return has(mOwtFreeSpace); } + bool getOwtFreeSpace(uint32_t *value) override { return get(value, &mOwtFreeSpace); } + void debug(int fd) override { HwApiBase::debug(fd); } + + private: + std::ofstream mF0; + std::ofstream mF0Offset; + std::ofstream mRedc; + std::ofstream mQ; + std::ifstream mEffectCount; + std::ifstream mVibeState; + std::ofstream mClabEnable; + std::ifstream mAvailablePwleSegments; + std::ofstream mPwle; + std::ifstream mOwtFreeSpace; +}; + +class HwCal : public Vibrator::HwCal, private HwCalBase { + private: + static constexpr char VERSION[] = "version"; + static constexpr char F0_CONFIG[] = "f0_measured"; + static constexpr char REDC_CONFIG[] = "redc_measured"; + static constexpr char Q_CONFIG[] = "q_measured"; + static constexpr char TICK_VOLTAGES_CONFIG[] = "v_tick"; + static constexpr char CLICK_VOLTAGES_CONFIG[] = "v_click"; + static constexpr char LONG_VOLTAGES_CONFIG[] = "v_long"; + + static constexpr uint32_t VERSION_DEFAULT = 2; + static constexpr int32_t DEFAULT_FREQUENCY_SHIFT = 0; + static constexpr std::array<uint32_t, 2> V_TICK_DEFAULT = {1, 100}; + static constexpr std::array<uint32_t, 2> V_CTICK_DEFAULT = {1, 100}; + static constexpr std::array<uint32_t, 2> V_LONG_DEFAULT = {1, 100}; + + public: + HwCal() {} + + bool getVersion(uint32_t *value) override { + if (getPersist(VERSION, value)) { + return true; + } + *value = VERSION_DEFAULT; + return true; + } + bool getLongFrequencyShift(int32_t *value) override { + return getProperty("long.frequency.shift", value, DEFAULT_FREQUENCY_SHIFT); + } + bool getF0(std::string *value) override { return getPersist(F0_CONFIG, value); } + bool getRedc(std::string *value) override { return getPersist(REDC_CONFIG, value); } + bool getQ(std::string *value) override { return getPersist(Q_CONFIG, value); } + bool getTickVolLevels(std::array<uint32_t, 2> *value) override { + if (getPersist(TICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_TICK_DEFAULT; + return true; + } + bool getClickVolLevels(std::array<uint32_t, 2> *value) override { + if (getPersist(CLICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_CTICK_DEFAULT; + return true; + } + bool getLongVolLevels(std::array<uint32_t, 2> *value) override { + if (getPersist(LONG_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_LONG_DEFAULT; + return true; + } + bool isChirpEnabled() override { + bool value; + getProperty("chirp.enabled", &value, false); + return value; + } + bool getSupportedPrimitives(uint32_t *value) override { + return getProperty("supported_primitives", value, (uint32_t)0); + } + void debug(int fd) override { HwCalBase::debug(fd); } +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp new file mode 100644 index 00000000..9d7954e8 --- /dev/null +++ b/vibrator/cs40l26/Vibrator.cpp @@ -0,0 +1,1365 @@ +/* + * Copyright (C) 2021 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 <glob.h> +#include <hardware/hardware.h> +#include <hardware/vibrator.h> +#include <linux/input.h> +#include <log/log.h> +#include <stdio.h> +#include <utils/Trace.h> + +#include <cinttypes> +#include <cmath> +#include <fstream> +#include <iostream> +#include <sstream> + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) +#endif + +#define PROC_SND_PCM "/proc/asound/pcm" +#define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { +static constexpr uint8_t FF_CUSTOM_DATA_LEN = 2; +static constexpr uint16_t FF_CUSTOM_DATA_LEN_MAX_COMP = 2044; // (COMPOSE_SIZE_MAX + 1) * 8 + 4 + +static constexpr uint32_t WAVEFORM_DOUBLE_CLICK_SILENCE_MS = 100; + +static constexpr uint32_t WAVEFORM_LONG_VIBRATION_THRESHOLD_MS = 50; + +static constexpr uint8_t VOLTAGE_SCALE_MAX = 100; + +static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6; // I2C Transaction + DSP Return-From-Standby +static constexpr int8_t MAX_PAUSE_TIMING_ERROR_MS = 1; // ALERT Irq Handling +static constexpr uint32_t MAX_TIME_MS = UINT16_MAX; + +static constexpr auto ASYNC_COMPLETION_TIMEOUT = std::chrono::milliseconds(100); +static constexpr auto POLLING_TIMEOUT = 20; +static constexpr int32_t COMPOSE_DELAY_MAX_MS = 10000; + +/* nsections is 8 bits. Need to preserve 1 section for the first delay before the first effect. */ +static constexpr int32_t COMPOSE_SIZE_MAX = 254; +static constexpr int32_t COMPOSE_PWLE_SIZE_MAX_DEFAULT = 127; + +// Measured resonant frequency, f0_measured, is represented by Q10.14 fixed +// point format on cs40l26 devices. The expression to calculate f0 is: +// f0 = f0_measured / 2^Q14_BIT_SHIFT +// See the LRA Calibration Support documentation for more details. +static constexpr int32_t Q14_BIT_SHIFT = 14; + +// Measured Q factor, q_measured, is represented by Q8.16 fixed +// point format on cs40l26 devices. The expression to calculate q is: +// q = q_measured / 2^Q16_BIT_SHIFT +// See the LRA Calibration Support documentation for more details. +static constexpr int32_t Q16_BIT_SHIFT = 16; + +static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 16383; + +static constexpr float PWLE_LEVEL_MIN = 0.0; +static constexpr float PWLE_LEVEL_MAX = 1.0; +static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 0.25; +static constexpr float PWLE_FREQUENCY_MIN_HZ = 0.25; +static constexpr float PWLE_FREQUENCY_MAX_HZ = 1023.75; +static constexpr float PWLE_BW_MAP_SIZE = + 1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ); + +static struct pcm_config haptic_nohost_config = { + .channels = 1, + .rate = 48000, + .period_size = 80, + .period_count = 2, + .format = PCM_FORMAT_S16_LE, +}; + +static uint16_t amplitudeToScale(float amplitude, float maximum) { + float ratio = 100; /* Unit: % */ + if (maximum != 0) + ratio = amplitude / maximum * 100; + + if (maximum == 0 || ratio > 100) + ratio = 100; + + return std::round(ratio); +} + +enum class AlwaysOnId : uint32_t { + GPIO_RISE, + GPIO_FALL, +}; + +enum WaveformBankID : uint8_t { + RAM_WVFRM_BANK, + ROM_WVFRM_BANK, + OWT_WVFRM_BANK, +}; + +enum WaveformIndex : uint16_t { + /* Physical waveform */ + WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0, + WAVEFORM_RESERVED_INDEX_1 = 1, + WAVEFORM_CLICK_INDEX = 2, + WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3, + WAVEFORM_THUD_INDEX = 4, + WAVEFORM_SPIN_INDEX = 5, + WAVEFORM_QUICK_RISE_INDEX = 6, + WAVEFORM_SLOW_RISE_INDEX = 7, + WAVEFORM_QUICK_FALL_INDEX = 8, + WAVEFORM_LIGHT_TICK_INDEX = 9, + WAVEFORM_LOW_TICK_INDEX = 10, + WAVEFORM_RESERVED_MFG_1, + WAVEFORM_RESERVED_MFG_2, + WAVEFORM_RESERVED_MFG_3, + WAVEFORM_MAX_PHYSICAL_INDEX, + /* OWT waveform */ + WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX, + WAVEFORM_PWLE, + /* + * Refer to <linux/input.h>, the WAVEFORM_MAX_INDEX must not exceed 96. + * #define FF_GAIN 0x60 // 96 in decimal + * #define FF_MAX_EFFECTS FF_GAIN + */ + WAVEFORM_MAX_INDEX, +}; + +std::vector<CompositePrimitive> defaultSupportedPrimitives = { + ndk::enum_range<CompositePrimitive>().begin(), ndk::enum_range<CompositePrimitive>().end()}; + +static int min(int x, int y) { + return x < y ? x : y; +} + +struct dspmem_chunk { + uint8_t *head; + uint8_t *current; + uint8_t *max; + int bytes; + + uint32_t cache; + int cachebits; +}; + +static dspmem_chunk *dspmem_chunk_create(void *data, int size) { + auto ch = new dspmem_chunk{ + .head = reinterpret_cast<uint8_t *>(data), + .current = reinterpret_cast<uint8_t *>(data), + .max = reinterpret_cast<uint8_t *>(data) + size, + }; + + return ch; +} + +static bool dspmem_chunk_end(struct dspmem_chunk *ch) { + return ch->current == ch->max; +} + +static int dspmem_chunk_bytes(struct dspmem_chunk *ch) { + return ch->bytes; +} + +static int dspmem_chunk_write(struct dspmem_chunk *ch, int nbits, uint32_t val) { + int nwrite, i; + + nwrite = min(24 - ch->cachebits, nbits); + ch->cache <<= nwrite; + ch->cache |= val >> (nbits - nwrite); + ch->cachebits += nwrite; + nbits -= nwrite; + + if (ch->cachebits == 24) { + if (dspmem_chunk_end(ch)) + return -ENOSPC; + + ch->cache &= 0xFFFFFF; + for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8) + *ch->current++ = (ch->cache & 0xFF000000) >> 24; + + ch->bytes += sizeof(ch->cache); + ch->cachebits = 0; + } + + if (nbits) + return dspmem_chunk_write(ch, nbits, val); + + return 0; +} + +static int dspmem_chunk_flush(struct dspmem_chunk *ch) { + if (!ch->cachebits) + return 0; + + return dspmem_chunk_write(ch, 24 - ch->cachebits, 0); +} + +std::vector<struct ff_effect> mFfEffects; + +Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal) + : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)), mAsyncHandle(std::async([] {})) { + int32_t longFrequencyShift; + std::string caldata{8, '0'}; + uint32_t calVer; + + glob_t inputEventPaths; + if (glob("/dev/input/event*", 0, nullptr, &inputEventPaths)) { + ALOGE("Fail to get input event paths (%d): %s", errno, strerror(errno)); + return; + } + + int fd = -1; + uint32_t val = 0; + char str[20] = {0x00}; + const char *inputEventName = std::getenv("INPUT_EVENT_NAME"); + for (uint8_t retry = 0; retry < 3; retry++) { + for (int i = 0; i < inputEventPaths.gl_pathc; i++) { + fd = TEMP_FAILURE_RETRY(open(inputEventPaths.gl_pathv[i], O_RDWR)); + if (fd > 0) { + if (ioctl(fd, EVIOCGBIT(0, sizeof(val)), &val) > 0 && (val & (1 << EV_FF)) && + ioctl(fd, EVIOCGNAME(sizeof(str)), &str) > 0 && + strcmp(str, inputEventName) == 0) { + mInputFd.reset(fd); + ALOGI("Control %s through %s", inputEventName, inputEventPaths.gl_pathv[i]); + break; + } + close(fd); + } + } + if (mInputFd.ok()) { + break; + } + + sleep(1); + ALOGW("Retry to search the input"); + } + globfree(&inputEventPaths); + if (!mInputFd.ok()) { + ALOGE("Fail to get an input event with name %s", inputEventName); + return; + } + + /* + * Create custom effects for all physical waveforms. + * 1. Set the initial duration for the corresponding RAM waveform. + * 2. Write to the force feedback driver. If the waveform firmware is not loaded, + * retry at most 10 times and then abort the constructor. + * 3. Store the effect ID. + */ + uint8_t retry = 0; + mFfEffects.resize(WAVEFORM_MAX_INDEX); + mEffectDurations.resize(WAVEFORM_MAX_INDEX); + mEffectDurations = { + 1000, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000, + }; /* 11+3 waveforms. The duration must < UINT16_MAX */ + + uint8_t effectIndex = 0; + + for (effectIndex = 0; effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX; effectIndex++) { + mFfEffects[effectIndex] = { + .type = FF_PERIODIC, + .id = -1, + .replay.length = static_cast<uint16_t>(mEffectDurations[effectIndex]), + .u.periodic.waveform = FF_CUSTOM, + .u.periodic.custom_data = new int16_t[2]{RAM_WVFRM_BANK, effectIndex}, + .u.periodic.custom_len = FF_CUSTOM_DATA_LEN, + }; + + while (true) { + if (ioctl(mInputFd, EVIOCSFF, &mFfEffects[effectIndex]) < 0) { + ALOGE("Failed upload effect %d (%d): %s", effectIndex, errno, strerror(errno)); + + if (retry < 10) { + sleep(1); + ALOGW("Retry #%u", ++retry); + continue; + } else { + ALOGE("Retried but the initialization was failed!"); + return; + } + } + mFfEffects[effectIndex].id = effectIndex; + break; + } + } + if (effectIndex != WAVEFORM_MAX_PHYSICAL_INDEX) { + ALOGE("Incomplete effect initialization!"); + return; + } + + /* Initiate placeholders for OWT effects. */ + for (effectIndex = WAVEFORM_MAX_PHYSICAL_INDEX; effectIndex < WAVEFORM_MAX_INDEX; + effectIndex++) { + mFfEffects[effectIndex] = { + .type = FF_PERIODIC, + .id = -1, + .replay.length = 0, + .u.periodic.waveform = FF_CUSTOM, + .u.periodic.custom_data = nullptr, + .u.periodic.custom_len = 0, + }; + } + + if (mHwCal->getF0(&caldata)) { + mHwApi->setF0(caldata); + } + if (mHwCal->getRedc(&caldata)) { + mHwApi->setRedc(caldata); + } + if (mHwCal->getQ(&caldata)) { + mHwApi->setQ(caldata); + } + + mHwCal->getLongFrequencyShift(&longFrequencyShift); + if (longFrequencyShift > 0) { + mF0Offset = longFrequencyShift * std::pow(2, 14); + } else if (longFrequencyShift < 0) { + mF0Offset = std::pow(2, 24) - std::abs(longFrequencyShift) * std::pow(2, 14); + } else { + mF0Offset = 0; + } + + mHwCal->getVersion(&calVer); + if (calVer == 2) { + mHwCal->getTickVolLevels(&mTickEffectVol); + mHwCal->getClickVolLevels(&mClickEffectVol); + mHwCal->getLongVolLevels(&mLongEffectVol); + } else { + ALOGW("Unsupported calibration version!"); + } + + mIsUnderExternalControl = false; + + mIsChirpEnabled = mHwCal->isChirpEnabled(); + + mHwCal->getSupportedPrimitives(&mSupportedPrimitivesBits); + if (mSupportedPrimitivesBits > 0) { + for (auto e : defaultSupportedPrimitives) { + if (mSupportedPrimitivesBits & (1 << uint32_t(e))) { + mSupportedPrimitives.emplace_back(e); + } + } + } else { + for (auto e : defaultSupportedPrimitives) { + mSupportedPrimitivesBits |= (1 << uint32_t(e)); + } + mSupportedPrimitives = defaultSupportedPrimitives; + } +} + +ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { + ATRACE_NAME("Vibrator::getCapabilities"); + + int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK | + IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_ALWAYS_ON_CONTROL | + IVibrator::CAP_GET_RESONANT_FREQUENCY | IVibrator::CAP_GET_Q_FACTOR; + if (hasHapticAlsaDevice()) { + ret |= IVibrator::CAP_EXTERNAL_CONTROL; + } + if (mHwApi->hasOwtFreeSpace()) { + ret |= IVibrator::CAP_COMPOSE_EFFECTS; + + if (mIsChirpEnabled) { + ret |= IVibrator::CAP_FREQUENCY_CONTROL | IVibrator::CAP_COMPOSE_PWLE_EFFECTS; + } + } + *_aidl_return = ret; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::off() { + ATRACE_NAME("Vibrator::off"); + if (mActiveId < 0) { + return ndk::ScopedAStatus::ok(); + } + + struct input_event play = { + .type = EV_FF, + .code = static_cast<uint16_t>(mActiveId), + .value = 0, + }; + + if (write(mInputFd, (const void *)&play, sizeof(play)) != sizeof(play)) { + ALOGE("Failed to stop effect %d (%d): %s", play.code, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mActiveId = -1; + setGlobalAmplitude(false); + mHwApi->setF0Offset(0); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, + const std::shared_ptr<IVibratorCallback> &callback) { + ATRACE_NAME("Vibrator::on"); + if (timeoutMs > MAX_TIME_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const uint16_t index = (timeoutMs < WAVEFORM_LONG_VIBRATION_THRESHOLD_MS) + ? WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX + : WAVEFORM_LONG_VIBRATION_EFFECT_INDEX; + if (MAX_COLD_START_LATENCY_MS <= MAX_TIME_MS - timeoutMs) { + timeoutMs += MAX_COLD_START_LATENCY_MS; + } + setGlobalAmplitude(true); + mHwApi->setF0Offset(mF0Offset); + return on(timeoutMs, index, callback); +} + +ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength, + const std::shared_ptr<IVibratorCallback> &callback, + int32_t *_aidl_return) { + ATRACE_NAME("Vibrator::perform"); + return performEffect(effect, strength, callback, _aidl_return); +} + +ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect> *_aidl_return) { + *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK, + Effect::DOUBLE_CLICK}; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { + ATRACE_NAME("Vibrator::setAmplitude"); + if (amplitude <= 0.0f || amplitude > 1.0f) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + mLongEffectScale = amplitude; + if (!isUnderExternalControl()) { + return setGlobalAmplitude(true); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { + ATRACE_NAME("Vibrator::setExternalControl"); + setGlobalAmplitude(enabled); + + if (mHasHapticAlsaDevice) { + if (!enableHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) { + ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + + mIsUnderExternalControl = enabled; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t *maxDelayMs) { + ATRACE_NAME("Vibrator::getCompositionDelayMax"); + *maxDelayMs = COMPOSE_DELAY_MAX_MS; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t *maxSize) { + ATRACE_NAME("Vibrator::getCompositionSizeMax"); + *maxSize = COMPOSE_SIZE_MAX; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector<CompositePrimitive> *supported) { + *supported = mSupportedPrimitives; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive, + int32_t *durationMs) { + ndk::ScopedAStatus status; + uint32_t effectIndex; + if (primitive != CompositePrimitive::NOOP) { + status = getPrimitiveDetails(primitive, &effectIndex); + if (!status.isOk()) { + return status; + } + + *durationMs = mEffectDurations[effectIndex]; + } else { + *durationMs = 0; + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::compose(const std::vector<CompositeEffect> &composite, + const std::shared_ptr<IVibratorCallback> &callback) { + ATRACE_NAME("Vibrator::compose"); + uint16_t size; + uint16_t nextEffectDelay; + + auto ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_COMP]{0x00}, + FF_CUSTOM_DATA_LEN_MAX_COMP); + + if (composite.size() > COMPOSE_SIZE_MAX || composite.empty()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + /* Check if there is a wait before the first effect. */ + nextEffectDelay = composite.front().delayMs; + if (nextEffectDelay > COMPOSE_DELAY_MAX_MS || nextEffectDelay < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else if (nextEffectDelay > 0) { + size = composite.size() + 1; + } else { + size = composite.size(); + } + + dspmem_chunk_write(ch, 8, 0); /* Padding */ + dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & size)); /* nsections */ + dspmem_chunk_write(ch, 8, 0); /* repeat */ + uint8_t header_count = ch->bytes; + + /* Insert 1 section for a wait before the first effect. */ + if (nextEffectDelay) { + dspmem_chunk_write(ch, 32, 0); /* amplitude, index, repeat & flags */ + dspmem_chunk_write(ch, 16, (uint16_t)(0xFFFF & nextEffectDelay)); /* delay */ + } + + for (uint32_t i_curr = 0, i_next = 1; i_curr < composite.size(); i_curr++, i_next++) { + auto &e_curr = composite[i_curr]; + uint32_t effectIndex = 0; + uint32_t effectVolLevel = 0; + if (e_curr.scale < 0.0f || e_curr.scale > 1.0f) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (e_curr.primitive != CompositePrimitive::NOOP) { + ndk::ScopedAStatus status; + status = getPrimitiveDetails(e_curr.primitive, &effectIndex); + if (!status.isOk()) { + return status; + } + effectVolLevel = intensityToVolLevel(e_curr.scale, effectIndex); + } + + /* Fetch the next composite effect delay and fill into the current section */ + nextEffectDelay = 0; + if (i_next < composite.size()) { + auto &e_next = composite[i_next]; + int32_t delay = e_next.delayMs; + + if (delay > COMPOSE_DELAY_MAX_MS || delay < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + nextEffectDelay = delay; + } + + if (effectIndex == 0 && nextEffectDelay == 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & effectVolLevel)); /* amplitude */ + dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & effectIndex)); /* index */ + dspmem_chunk_write(ch, 8, 0); /* repeat */ + dspmem_chunk_write(ch, 8, 0); /* flags */ + dspmem_chunk_write(ch, 16, (uint16_t)(0xFFFF & nextEffectDelay)); /* delay */ + } + dspmem_chunk_flush(ch); + if (header_count == ch->bytes) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else { + return performEffect(0 /*ignored*/, 0 /*ignored*/, ch, callback); + } +} + +ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, + const std::shared_ptr<IVibratorCallback> &callback) { + if (effectIndex > WAVEFORM_MAX_INDEX) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) { + ALOGE("Previous vibration pending."); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + /* Update duration for long/short vibration. */ + if (effectIndex == WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX || + effectIndex == WAVEFORM_LONG_VIBRATION_EFFECT_INDEX) { + mFfEffects[effectIndex].replay.length = static_cast<uint16_t>(timeoutMs); + if (ioctl(mInputFd, EVIOCSFF, &mFfEffects[effectIndex]) < 0) { + ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + + /* Play the event now. */ + struct input_event play = { + .type = EV_FF, + .code = static_cast<uint16_t>(effectIndex), + .value = 1, + }; + if (write(mInputFd, (const void *)&play, sizeof(play)) != sizeof(play)) { + ALOGE("Failed to play effect %d (%d): %s", play.code, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mActiveId = play.code; + + mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) { + uint16_t scale = amplitudeToScale(amplitude, maximum); + struct input_event gain = { + .type = EV_FF, + .code = FF_GAIN, + .value = scale, + }; + if (write(mInputFd, (const void *)&gain, sizeof(gain)) != sizeof(gain)) { + ALOGE("Failed to set the gain to %u (%d): %s", scale, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) { + uint8_t amplitude = set ? roundf(mLongEffectScale * mLongEffectVol[1]) : VOLTAGE_SCALE_MAX; + if (!set) { + mLongEffectScale = 1.0; // Reset the scale for the later new effect. + } + return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX); +} + +ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) { + *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK}; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) { + ndk::ScopedAStatus status; + uint32_t effectIndex; + uint32_t timeMs; + uint32_t volLevel; + uint16_t scale; + status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel); + if (!status.isOk()) { + return status; + } + + scale = amplitudeToScale(volLevel, VOLTAGE_SCALE_MAX); + + switch (static_cast<AlwaysOnId>(id)) { + case AlwaysOnId::GPIO_RISE: + // mHwApi->setGpioRiseIndex(effectIndex); + // mHwApi->setGpioRiseScale(scale); + return ndk::ScopedAStatus::ok(); + case AlwaysOnId::GPIO_FALL: + // mHwApi->setGpioFallIndex(effectIndex); + // mHwApi->setGpioFallScale(scale); + return ndk::ScopedAStatus::ok(); + } + + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} +ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t id) { + switch (static_cast<AlwaysOnId>(id)) { + case AlwaysOnId::GPIO_RISE: + // mHwApi->setGpioRiseIndex(0); + return ndk::ScopedAStatus::ok(); + case AlwaysOnId::GPIO_FALL: + // mHwApi->setGpioFallIndex(0); + return ndk::ScopedAStatus::ok(); + } + + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus Vibrator::getResonantFrequency(float *resonantFreqHz) { + std::string caldata{8, '0'}; + if (!mHwCal->getF0(&caldata)) { + ALOGE("Failed to get resonant frequency (%d): %s", errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + *resonantFreqHz = static_cast<float>(std::stoul(caldata, nullptr, 16)) / (1 << Q14_BIT_SHIFT); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getQFactor(float *qFactor) { + std::string caldata{8, '0'}; + if (!mHwCal->getQ(&caldata)) { + ALOGE("Failed to get q factor (%d): %s", errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + *qFactor = static_cast<float>(std::stoul(caldata, nullptr, 16)) / (1 << Q16_BIT_SHIFT); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getFrequencyResolution(float *freqResolutionHz) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + *freqResolutionHz = PWLE_FREQUENCY_RESOLUTION_HZ; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getFrequencyMinimum(float *freqMinimumHz) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + *freqMinimumHz = PWLE_FREQUENCY_MIN_HZ; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getBandwidthAmplitudeMap(std::vector<float> *_aidl_return) { + // TODO(b/170919640): complete implementation + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + std::vector<float> bandwidthAmplitudeMap(PWLE_BW_MAP_SIZE, 1.0); + *_aidl_return = bandwidthAmplitudeMap; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getPwlePrimitiveDurationMax(int32_t *durationMs) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *durationMs = COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getPwleCompositionSizeMax(int32_t *maxSize) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *maxSize = COMPOSE_PWLE_SIZE_MAX_DEFAULT; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getSupportedBraking(std::vector<Braking> *supported) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *supported = { + Braking::NONE, + Braking::CLAB, + }; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::setPwle(const std::string &pwleQueue) { + if (!mHwApi->setPwle(pwleQueue)) { + ALOGE("Failed to write \"%s\" to pwle (%d): %s", pwleQueue.c_str(), errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + return ndk::ScopedAStatus::ok(); +} + +static void resetPreviousEndAmplitudeEndFrequency(float *prevEndAmplitude, + float *prevEndFrequency) { + const float reset = -1.0; + *prevEndAmplitude = reset; + *prevEndFrequency = reset; +} + +static void incrementIndex(int *index) { + *index += 1; +} + +static void constructActiveDefaults(std::ostringstream &pwleBuilder, const int &segmentIdx) { + pwleBuilder << ",C" << segmentIdx << ":1"; + pwleBuilder << ",B" << segmentIdx << ":0"; + pwleBuilder << ",AR" << segmentIdx << ":0"; + pwleBuilder << ",V" << segmentIdx << ":0"; +} + +static void constructActiveSegment(std::ostringstream &pwleBuilder, const int &segmentIdx, + int duration, float amplitude, float frequency) { + pwleBuilder << ",T" << segmentIdx << ":" << duration; + pwleBuilder << ",L" << segmentIdx << ":" << amplitude; + pwleBuilder << ",F" << segmentIdx << ":" << frequency; + constructActiveDefaults(pwleBuilder, segmentIdx); +} + +static void constructBrakingSegment(std::ostringstream &pwleBuilder, const int &segmentIdx, + int duration, Braking brakingType) { + pwleBuilder << ",T" << segmentIdx << ":" << duration; + pwleBuilder << ",L" << segmentIdx << ":" << 0; + pwleBuilder << ",F" << segmentIdx << ":" << PWLE_FREQUENCY_MIN_HZ; + pwleBuilder << ",C" << segmentIdx << ":0"; + pwleBuilder << ",B" << segmentIdx << ":" + << static_cast<std::underlying_type<Braking>::type>(brakingType); + pwleBuilder << ",AR" << segmentIdx << ":0"; + pwleBuilder << ",V" << segmentIdx << ":0"; +} + +ndk::ScopedAStatus Vibrator::composePwle(const std::vector<PrimitivePwle> &composite, + const std::shared_ptr<IVibratorCallback> &callback) { + ATRACE_NAME("Vibrator::composePwle"); + std::ostringstream pwleBuilder; + std::string pwleQueue; + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if ((capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) == 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + if (composite.size() <= 0 || composite.size() > compositionSizeMax) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + float prevEndAmplitude; + float prevEndFrequency; + resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency); + + int segmentIdx = 0; + uint32_t totalDuration = 0; + + pwleBuilder << "S:0,WF:4,RP:0,WT:0"; + + for (auto &e : composite) { + switch (e.getTag()) { + case PrimitivePwle::active: { + auto active = e.get<PrimitivePwle::active>(); + if (active.duration < 0 || + active.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (active.startAmplitude < PWLE_LEVEL_MIN || + active.startAmplitude > PWLE_LEVEL_MAX || + active.endAmplitude < PWLE_LEVEL_MIN || active.endAmplitude > PWLE_LEVEL_MAX) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (active.startFrequency < PWLE_FREQUENCY_MIN_HZ || + active.startFrequency > PWLE_FREQUENCY_MAX_HZ || + active.endFrequency < PWLE_FREQUENCY_MIN_HZ || + active.endFrequency > PWLE_FREQUENCY_MAX_HZ) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (!((active.startAmplitude == prevEndAmplitude) && + (active.startFrequency == prevEndFrequency))) { + constructActiveSegment(pwleBuilder, segmentIdx, 0, active.startAmplitude, + active.startFrequency); + incrementIndex(&segmentIdx); + } + + constructActiveSegment(pwleBuilder, segmentIdx, active.duration, + active.endAmplitude, active.endFrequency); + incrementIndex(&segmentIdx); + + prevEndAmplitude = active.endAmplitude; + prevEndFrequency = active.endFrequency; + totalDuration += active.duration; + break; + } + case PrimitivePwle::braking: { + auto braking = e.get<PrimitivePwle::braking>(); + if (braking.braking > Braking::CLAB) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (braking.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + constructBrakingSegment(pwleBuilder, segmentIdx, 0, braking.braking); + incrementIndex(&segmentIdx); + + constructBrakingSegment(pwleBuilder, segmentIdx, braking.duration, braking.braking); + incrementIndex(&segmentIdx); + + resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency); + totalDuration += braking.duration; + break; + } + } + } + + pwleQueue = pwleBuilder.str(); + ALOGD("composePwle queue: (%s)", pwleQueue.c_str()); + + ndk::ScopedAStatus status = setPwle(pwleQueue); + if (!status.isOk()) { + ALOGE("Failed to write pwle queue"); + return status; + } + + // mHwApi->setEffectIndex(WAVEFORM_UNSAVED_TRIGGER_QUEUE_INDEX); + + totalDuration += MAX_COLD_START_LATENCY_MS; + // mHwApi->setDuration(MAX_TIME_MS); + + // mHwApi->setActivate(1); + + mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback); + + return ndk::ScopedAStatus::ok(); +} + +bool Vibrator::isUnderExternalControl() { + return mIsUnderExternalControl; +} + +binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { + if (fd < 0) { + ALOGE("Called debug() with invalid fd."); + return STATUS_OK; + } + + (void)args; + (void)numArgs; + + dprintf(fd, "AIDL:\n"); + + dprintf(fd, " F0 Offset: %" PRIu32 "\n", mF0Offset); + + dprintf(fd, " Voltage Levels:\n"); + dprintf(fd, " Tick Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mTickEffectVol[0], + mTickEffectVol[1]); + dprintf(fd, " Click Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mClickEffectVol[0], + mClickEffectVol[1]); + dprintf(fd, " Long Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mLongEffectVol[0], + mLongEffectVol[1]); + + dprintf(fd, " FF effect:\n"); + dprintf(fd, " Physical waveform:\n"); + dprintf(fd, "\tId\tIndex\tt ->\tt'\n"); + for (uint8_t effectId = 0; effectId < WAVEFORM_MAX_PHYSICAL_INDEX; effectId++) { + dprintf(fd, "\t%d\t%d\t%d\t%d\n", mFfEffects[effectId].id, + mFfEffects[effectId].u.periodic.custom_data[1], mEffectDurations[effectId], + mFfEffects[effectId].replay.length); + } + dprintf(fd, " OWT waveform:\n"); + dprintf(fd, "\tId\tBytes\tData\n"); + for (uint8_t effectId = WAVEFORM_MAX_PHYSICAL_INDEX; effectId < WAVEFORM_MAX_INDEX; + effectId++) { + uint32_t numBytes = mFfEffects[effectId].u.periodic.custom_len * 2; + std::stringstream ss; + ss << " "; + for (int i = 0; i < numBytes; i++) { + ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex + << (uint16_t)(*( + reinterpret_cast<uint8_t *>(mFfEffects[effectId].u.periodic.custom_data) + + i)) + << " "; + } + dprintf(fd, "\t%d\t%d\t{%s}\n", mFfEffects[effectId].id, numBytes, ss.str().c_str()); + } + + dprintf(fd, "\n"); + dprintf(fd, "\n"); + + mHwApi->debug(fd); + + dprintf(fd, "\n"); + + mHwCal->debug(fd); + + fsync(fd); + return STATUS_OK; +} + +bool Vibrator::findHapticAlsaDevice(int *card, int *device) { + std::string line; + std::ifstream myfile(PROC_SND_PCM); + if (myfile.is_open()) { + while (getline(myfile, line)) { + if (line.find(HAPTIC_PCM_DEVICE_SYMBOL) != std::string::npos) { + std::stringstream ss(line); + std::string currentToken; + std::getline(ss, currentToken, ':'); + sscanf(currentToken.c_str(), "%d-%d", card, device); + return true; + } + } + myfile.close(); + } else { + ALOGE("Failed to read file: %s", PROC_SND_PCM); + } + return false; +} + +bool Vibrator::hasHapticAlsaDevice() { + // We need to call findHapticAlsaDevice once only. Calling in the + // constructor is too early in the boot process and the pcm file contents + // are empty. Hence we make the call here once only right before we need to. + static bool configHapticAlsaDeviceDone = false; + if (!configHapticAlsaDeviceDone) { + if (findHapticAlsaDevice(&mCard, &mDevice)) { + mHasHapticAlsaDevice = true; + configHapticAlsaDeviceDone = true; + } else { + ALOGE("Haptic ALSA device not supported"); + } + } + return mHasHapticAlsaDevice; +} + +bool Vibrator::enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) { + int ret = 0; + + if (enable) { + *haptic_pcm = pcm_open(card, device, PCM_OUT, &haptic_nohost_config); + if (!pcm_is_ready(*haptic_pcm)) { + ALOGE("cannot open pcm_out driver: %s", pcm_get_error(*haptic_pcm)); + goto fail; + } + + ret = pcm_prepare(*haptic_pcm); + if (ret < 0) { + ALOGE("cannot prepare haptic_pcm: %s", pcm_get_error(*haptic_pcm)); + goto fail; + } + + ret = pcm_start(*haptic_pcm); + if (ret < 0) { + ALOGE("cannot start haptic_pcm: %s", pcm_get_error(*haptic_pcm)); + goto fail; + } + + return true; + } else { + if (*haptic_pcm) { + pcm_close(*haptic_pcm); + *haptic_pcm = NULL; + } + return true; + } + +fail: + pcm_close(*haptic_pcm); + *haptic_pcm = NULL; + return false; +} + +ndk::ScopedAStatus Vibrator::getSimpleDetails(Effect effect, EffectStrength strength, + uint32_t *outEffectIndex, uint32_t *outTimeMs, + uint32_t *outVolLevel) { + uint32_t effectIndex; + uint32_t timeMs; + float intensity; + uint32_t volLevel; + switch (strength) { + case EffectStrength::LIGHT: + intensity = 0.5f; + break; + case EffectStrength::MEDIUM: + intensity = 0.7f; + break; + case EffectStrength::STRONG: + intensity = 1.0f; + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + switch (effect) { + case Effect::TEXTURE_TICK: + effectIndex = WAVEFORM_LIGHT_TICK_INDEX; + intensity *= 0.5f; + break; + case Effect::TICK: + effectIndex = WAVEFORM_CLICK_INDEX; + intensity *= 0.5f; + break; + case Effect::CLICK: + effectIndex = WAVEFORM_CLICK_INDEX; + intensity *= 0.7f; + break; + case Effect::HEAVY_CLICK: + effectIndex = WAVEFORM_CLICK_INDEX; + intensity *= 1.0f; + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + volLevel = intensityToVolLevel(intensity, effectIndex); + timeMs = mEffectDurations[effectIndex] + MAX_COLD_START_LATENCY_MS; + + *outEffectIndex = effectIndex; + *outTimeMs = timeMs; + *outVolLevel = volLevel; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompoundDetails(Effect effect, EffectStrength strength, + uint32_t *outTimeMs, dspmem_chunk *outCh) { + ndk::ScopedAStatus status; + uint32_t timeMs = 0; + uint32_t thisEffectIndex; + uint32_t thisTimeMs; + uint32_t thisVolLevel; + switch (effect) { + case Effect::DOUBLE_CLICK: + dspmem_chunk_write(outCh, 8, 0); /* Padding */ + dspmem_chunk_write(outCh, 8, 2); /* nsections */ + dspmem_chunk_write(outCh, 8, 0); /* repeat */ + + status = getSimpleDetails(Effect::CLICK, strength, &thisEffectIndex, &thisTimeMs, + &thisVolLevel); + if (!status.isOk()) { + return status; + } + timeMs += thisTimeMs; + + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisVolLevel)); /* amplitude */ + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisEffectIndex)); /* index */ + dspmem_chunk_write(outCh, 8, 0); /* repeat */ + dspmem_chunk_write(outCh, 8, 0); /* flags */ + dspmem_chunk_write(outCh, 16, + (uint16_t)(0xFFFF & WAVEFORM_DOUBLE_CLICK_SILENCE_MS)); /* delay */ + + timeMs += WAVEFORM_DOUBLE_CLICK_SILENCE_MS + MAX_PAUSE_TIMING_ERROR_MS; + + status = getSimpleDetails(Effect::HEAVY_CLICK, strength, &thisEffectIndex, &thisTimeMs, + &thisVolLevel); + if (!status.isOk()) { + return status; + } + timeMs += thisTimeMs; + + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisVolLevel)); /* amplitude */ + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisEffectIndex)); /* index */ + dspmem_chunk_write(outCh, 8, 0); /* repeat */ + dspmem_chunk_write(outCh, 8, 0); /* flags */ + dspmem_chunk_write(outCh, 16, 0); /* delay */ + dspmem_chunk_flush(outCh); + + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + *outTimeMs = timeMs; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPrimitiveDetails(CompositePrimitive primitive, + uint32_t *outEffectIndex) { + uint32_t effectIndex; + uint32_t primitiveBit = 1 << int32_t(primitive); + if ((primitiveBit & mSupportedPrimitivesBits) == 0x0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + switch (primitive) { + case CompositePrimitive::NOOP: + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + case CompositePrimitive::CLICK: + effectIndex = WAVEFORM_CLICK_INDEX; + break; + case CompositePrimitive::THUD: + effectIndex = WAVEFORM_THUD_INDEX; + break; + case CompositePrimitive::SPIN: + effectIndex = WAVEFORM_SPIN_INDEX; + break; + case CompositePrimitive::QUICK_RISE: + effectIndex = WAVEFORM_QUICK_RISE_INDEX; + break; + case CompositePrimitive::SLOW_RISE: + effectIndex = WAVEFORM_SLOW_RISE_INDEX; + break; + case CompositePrimitive::QUICK_FALL: + effectIndex = WAVEFORM_QUICK_FALL_INDEX; + break; + case CompositePrimitive::LIGHT_TICK: + effectIndex = WAVEFORM_LIGHT_TICK_INDEX; + break; + case CompositePrimitive::LOW_TICK: + effectIndex = WAVEFORM_LOW_TICK_INDEX; + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + *outEffectIndex = effectIndex; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::uploadOwtEffect(uint8_t *owtData, uint32_t numBytes, + uint32_t *outEffectIndex) { + if (owtData == nullptr) { + ALOGE("Invalid waveform bank"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + bool isPwle = (*reinterpret_cast<uint16_t *>(owtData) != 0x0000); + WaveformIndex targetId = isPwle ? WAVEFORM_PWLE : WAVEFORM_COMPOSE; + + /* Erase the created OWT waveform. */ + bool isCreated = (mFfEffects[targetId].id != -1); + if (isCreated && ioctl(mInputFd, EVIOCRMFF, mFfEffects[targetId].id) < 0) { + ALOGW("Failed to erase effect %d(%d) (%d): %s", targetId, mFfEffects[targetId].id, errno, + strerror(errno)); + mFfEffects[targetId].id = -1; + } + + uint32_t freeBytes; + mHwApi->getOwtFreeSpace(&freeBytes); + if (numBytes > freeBytes) { + ALOGE("Effect %d length: %d > %d!", targetId, numBytes, freeBytes); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + /* Create a new OWT waveform to update the PWLE or composite effect. */ + mFfEffects[targetId].id = -1; /* New Effect */ + mFfEffects[targetId].u.periodic.custom_len = numBytes / 2; /* # of 16-bit elements */ + delete[](mFfEffects[targetId].u.periodic.custom_data); + mFfEffects[targetId].u.periodic.custom_data = + new int16_t[mFfEffects[targetId].u.periodic.custom_len]{0x0000}; + if (mFfEffects[targetId].u.periodic.custom_data == nullptr) { + ALOGE("Failed to allocate memory for custom data\n"); + return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); + } + memcpy(mFfEffects[targetId].u.periodic.custom_data, owtData, numBytes); + + if (ioctl(mInputFd, EVIOCSFF, &mFfEffects[targetId]) < 0) { + ALOGE("Failed to upload effect %d (%d): %s", targetId, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + *outEffectIndex = mFfEffects[targetId].id; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength, + const std::shared_ptr<IVibratorCallback> &callback, + int32_t *outTimeMs) { + ndk::ScopedAStatus status; + uint32_t effectIndex; + uint32_t timeMs = 0; + uint32_t volLevel; + dspmem_chunk *ch = nullptr; + + switch (effect) { + case Effect::TEXTURE_TICK: + // fall-through + case Effect::TICK: + // fall-through + case Effect::CLICK: + // fall-through + case Effect::HEAVY_CLICK: + status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel); + break; + case Effect::DOUBLE_CLICK: + ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_COMP]{0x00}, + FF_CUSTOM_DATA_LEN_MAX_COMP); + status = getCompoundDetails(effect, strength, &timeMs, ch); + break; + default: + status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + break; + } + if (!status.isOk()) { + goto exit; + } + + status = performEffect(static_cast<uint16_t>(effectIndex), volLevel, ch, callback); + +exit: + *outTimeMs = timeMs; + return status; +} + +ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel, + dspmem_chunk *ch, + const std::shared_ptr<IVibratorCallback> &callback) { + if (ch) { + ndk::ScopedAStatus status = uploadOwtEffect(ch->head, dspmem_chunk_bytes(ch), &effectIndex); + delete ch; + if (!status.isOk()) { + return status; + } + setEffectAmplitude(VOLTAGE_SCALE_MAX, VOLTAGE_SCALE_MAX); + } else { + setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX); + } + + return on(MAX_TIME_MS, effectIndex, callback); +} + +void Vibrator::waitForComplete(std::shared_ptr<IVibratorCallback> &&callback) { + if (!mHwApi->pollVibeState("Vibe state: Haptic\n", POLLING_TIMEOUT)) { + ALOGE("Fail to get state \"Haptic\""); + } + mHwApi->pollVibeState("Vibe state: Stopped\n"); + + if (callback) { + auto ret = callback->onComplete(); + if (!ret.isOk()) { + ALOGE("Failed completion callback: %d", ret.getExceptionCode()); + } + } +} + +uint32_t Vibrator::intensityToVolLevel(float intensity, uint32_t effectIndex) { + uint32_t volLevel; + auto calc = [](float intst, std::array<uint32_t, 2> v) -> uint32_t { + return std::lround(intst * (v[1] - v[0])) + v[0]; + }; + + switch (effectIndex) { + case WAVEFORM_LIGHT_TICK_INDEX: + volLevel = calc(intensity, mTickEffectVol); + break; + case WAVEFORM_QUICK_RISE_INDEX: + // fall-through + case WAVEFORM_QUICK_FALL_INDEX: + volLevel = calc(intensity, mLongEffectVol); + break; + case WAVEFORM_CLICK_INDEX: + // fall-through + case WAVEFORM_THUD_INDEX: + // fall-through + case WAVEFORM_SPIN_INDEX: + // fall-through + case WAVEFORM_SLOW_RISE_INDEX: + // fall-through + default: + volLevel = calc(intensity, mClickEffectVol); + break; + } + return volLevel; +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h new file mode 100644 index 00000000..dc7c42b0 --- /dev/null +++ b/vibrator/cs40l26/Vibrator.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2021 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. + */ +#pragma once + +#include <aidl/android/hardware/vibrator/BnVibrator.h> +#include <android-base/unique_fd.h> +#include <tinyalsa/asoundlib.h> + +#include <array> +#include <fstream> +#include <future> + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +class Vibrator : public BnVibrator { + public: + // APIs for interfacing with the kernel driver. + class HwApi { + public: + virtual ~HwApi() = default; + // Stores the LRA resonant frequency to be used for PWLE playback + // and click compensation. + virtual bool setF0(std::string value) = 0; + // Stores the frequency offset for long vibrations. + virtual bool setF0Offset(uint32_t value) = 0; + // Stores the LRA series resistance to be used for click + // compensation. + virtual bool setRedc(std::string value) = 0; + // Stores the LRA Q factor to be used for Q-dependent waveform + // selection. + virtual bool setQ(std::string value) = 0; + // Reports the number of effect waveforms loaded in firmware. + virtual bool getEffectCount(uint32_t *value) = 0; + // Blocks until vibrator reaches desired state + // ("Vibe state: Haptic" means enabled). + // ("Vibe state: Stopped" means disabled). + virtual bool pollVibeState(std::string value, int32_t timeoutMs = -1) = 0; + // Enables/disables closed-loop active braking. + virtual bool setClabEnable(bool value) = 0; + // Reports the number of available PWLE segments. + virtual bool getAvailablePwleSegments(uint32_t *value) = 0; + // Specifies piecewise-linear specifications to generate complex + // waveforms. + virtual bool setPwle(std::string value) = 0; + // Reports whether getOwtFreeSpace() is supported. + virtual bool hasOwtFreeSpace() = 0; + // Reports the available OWT bytes. + virtual bool getOwtFreeSpace(uint32_t *value) = 0; + // Emit diagnostic information to the given file. + virtual void debug(int fd) = 0; + }; + + // APIs for obtaining calibration/configuration data from persistent memory. + class HwCal { + public: + virtual ~HwCal() = default; + // Obtain the calibration version + virtual bool getVersion(uint32_t *value) = 0; + // Obtains the LRA resonant frequency to be used for PWLE playback + // and click compensation. + virtual bool getF0(std::string *value) = 0; + // Obtains the LRA series resistance to be used for click + // compensation. + virtual bool getRedc(std::string *value) = 0; + // Obtains the LRA Q factor to be used for Q-dependent waveform + // selection. + virtual bool getQ(std::string *value) = 0; + // Obtains frequency shift for long vibrations. + virtual bool getLongFrequencyShift(int32_t *value) = 0; + // Obtains the v0/v1(min/max) voltage levels to be applied for + // tick/click/long in units of 1%. + virtual bool getTickVolLevels(std::array<uint32_t, 2> *value) = 0; + virtual bool getClickVolLevels(std::array<uint32_t, 2> *value) = 0; + virtual bool getLongVolLevels(std::array<uint32_t, 2> *value) = 0; + // Checks if the chirp feature is enabled. + virtual bool isChirpEnabled() = 0; + // Obtains the supported primitive effects. + virtual bool getSupportedPrimitives(uint32_t *value) = 0; + // Emit diagnostic information to the given file. + virtual void debug(int fd) = 0; + }; + + public: + Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal); + + ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override; + ndk::ScopedAStatus off() override; + ndk::ScopedAStatus on(int32_t timeoutMs, + const std::shared_ptr<IVibratorCallback> &callback) override; + ndk::ScopedAStatus perform(Effect effect, EffectStrength strength, + const std::shared_ptr<IVibratorCallback> &callback, + int32_t *_aidl_return) override; + ndk::ScopedAStatus getSupportedEffects(std::vector<Effect> *_aidl_return) override; + ndk::ScopedAStatus setAmplitude(float amplitude) override; + ndk::ScopedAStatus setExternalControl(bool enabled) override; + ndk::ScopedAStatus getCompositionDelayMax(int32_t *maxDelayMs); + ndk::ScopedAStatus getCompositionSizeMax(int32_t *maxSize); + ndk::ScopedAStatus getSupportedPrimitives(std::vector<CompositePrimitive> *supported) override; + ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive, + int32_t *durationMs) override; + ndk::ScopedAStatus compose(const std::vector<CompositeEffect> &composite, + const std::shared_ptr<IVibratorCallback> &callback) override; + ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) override; + ndk::ScopedAStatus alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) override; + ndk::ScopedAStatus alwaysOnDisable(int32_t id) override; + ndk::ScopedAStatus getResonantFrequency(float *resonantFreqHz) override; + ndk::ScopedAStatus getQFactor(float *qFactor) override; + ndk::ScopedAStatus getFrequencyResolution(float *freqResolutionHz) override; + ndk::ScopedAStatus getFrequencyMinimum(float *freqMinimumHz) override; + ndk::ScopedAStatus getBandwidthAmplitudeMap(std::vector<float> *_aidl_return) override; + ndk::ScopedAStatus getPwlePrimitiveDurationMax(int32_t *durationMs) override; + ndk::ScopedAStatus getPwleCompositionSizeMax(int32_t *maxSize) override; + ndk::ScopedAStatus getSupportedBraking(std::vector<Braking> *supported) override; + ndk::ScopedAStatus composePwle(const std::vector<PrimitivePwle> &composite, + const std::shared_ptr<IVibratorCallback> &callback) override; + + binder_status_t dump(int fd, const char **args, uint32_t numArgs) override; + + private: + ndk::ScopedAStatus on(uint32_t timeoutMs, uint32_t effectIndex, + const std::shared_ptr<IVibratorCallback> &callback); + // set 'amplitude' based on an arbitrary scale determined by 'maximum' + ndk::ScopedAStatus setEffectAmplitude(float amplitude, float maximum); + ndk::ScopedAStatus setGlobalAmplitude(bool set); + // 'simple' effects are those precompiled and loaded into the controller + ndk::ScopedAStatus getSimpleDetails(Effect effect, EffectStrength strength, + uint32_t *outEffectIndex, uint32_t *outTimeMs, + uint32_t *outVolLevel); + // 'compound' effects are those composed by stringing multiple 'simple' effects + ndk::ScopedAStatus getCompoundDetails(Effect effect, EffectStrength strength, + uint32_t *outTimeMs, struct dspmem_chunk *outCh); + ndk::ScopedAStatus getPrimitiveDetails(CompositePrimitive primitive, uint32_t *outEffectIndex); + ndk::ScopedAStatus uploadOwtEffect(uint8_t *owtData, uint32_t num_bytes, + uint32_t *outEffectIndex); + ndk::ScopedAStatus performEffect(Effect effect, EffectStrength strength, + const std::shared_ptr<IVibratorCallback> &callback, + int32_t *outTimeMs); + ndk::ScopedAStatus performEffect(uint32_t effectIndex, uint32_t volLevel, + struct dspmem_chunk *ch, + const std::shared_ptr<IVibratorCallback> &callback); + ndk::ScopedAStatus setPwle(const std::string &pwleQueue); + bool isUnderExternalControl(); + void waitForComplete(std::shared_ptr<IVibratorCallback> &&callback); + uint32_t intensityToVolLevel(float intensity, uint32_t effectIndex); + bool findHapticAlsaDevice(int *card, int *device); + bool hasHapticAlsaDevice(); + bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device); + + std::unique_ptr<HwApi> mHwApi; + std::unique_ptr<HwCal> mHwCal; + uint32_t mF0Offset; + std::array<uint32_t, 2> mTickEffectVol; + std::array<uint32_t, 2> mClickEffectVol; + std::array<uint32_t, 2> mLongEffectVol; + std::vector<uint32_t> mEffectDurations; + std::future<void> mAsyncHandle; + int32_t compositionSizeMax; + ::android::base::unique_fd mInputFd; + int8_t mActiveId{-1}; + struct pcm *mHapticPcm; + int mCard; + int mDevice; + bool mHasHapticAlsaDevice; + bool mIsUnderExternalControl; + float mLongEffectScale = 1.0; + bool mIsChirpEnabled; + uint32_t mSupportedPrimitivesBits = 0x0; + std::vector<CompositePrimitive> mSupportedPrimitives; +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc new file mode 100644 index 00000000..539a83e8 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc @@ -0,0 +1,40 @@ +on boot && property:vendor.all.modules.ready=1 + wait /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26_dual.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26_dual.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/owt_free_space + + enable vendor.vibrator.cs40l26-dual + +service vendor.vibrator.cs40l26-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-dual + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_dual_input + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a-dual/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.xml new file mode 100644 index 00000000..1bd3e7e8 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.xml @@ -0,0 +1,7 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.vibrator</name> + <version>2</version> + <fqname>IVibrator/dual</fqname> + </hal> +</manifest> diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dvt-dual.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dvt-dual.rc new file mode 100644 index 00000000..c897f061 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dvt-dual.rc @@ -0,0 +1,40 @@ +on boot && property:vendor.all.modules.ready=1 && property:ro.boot.revision=DVT1.0 + wait /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26_dual.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26_dual.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/owt_free_space + + enable vendor.vibrator.cs40l26-dual + +service vendor.vibrator.cs40l26-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-dvt-dual + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_dual_input + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a-dual/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dvt.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dvt.rc new file mode 100644 index 00000000..c591bc45 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dvt.rc @@ -0,0 +1,40 @@ +on boot && property:vendor.all.modules.ready=1 && property:ro.boot.revision=DVT1.0 + wait /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/owt_free_space + + enable vendor.vibrator.cs40l26 + +service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-dvt + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_input + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-evt-dual.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-evt-dual.rc new file mode 100644 index 00000000..4f7fe788 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-evt-dual.rc @@ -0,0 +1,40 @@ +on boot && property:vendor.all.modules.ready=1 && property:ro.boot.revision=EVT1.0 + wait /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26_dual.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26_dual.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a-dual/default/owt_free_space + + enable vendor.vibrator.cs40l26-evt-dual + +service vendor.vibrator.cs40l26-evt-dual /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-evt-dual + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_dual_input + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a-dual/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-evt.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-evt.rc new file mode 100644 index 00000000..d521ce90 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-evt.rc @@ -0,0 +1,40 @@ +on boot && property:vendor.all.modules.ready=1 && property:ro.boot.revision=EVT1.0 + wait /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/owt_free_space + + enable vendor.vibrator.cs40l26-evt + +service vendor.vibrator.cs40l26-evt /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-evt + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_input + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc new file mode 100644 index 00000000..dbb2c417 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc @@ -0,0 +1,40 @@ +on boot && property:vendor.all.modules.ready=1 + wait /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/owt_free_space + + enable vendor.vibrator.cs40l26 + +service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26 + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_input + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml new file mode 100644 index 00000000..4db8f8c5 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml @@ -0,0 +1,7 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.vibrator</name> + <version>2</version> + <fqname>IVibrator/default</fqname> + </hal> +</manifest> diff --git a/vibrator/cs40l26/device-stereo-dvt.mk b/vibrator/cs40l26/device-stereo-dvt.mk new file mode 100644 index 00000000..21da45d9 --- /dev/null +++ b/vibrator/cs40l26/device-stereo-dvt.mk @@ -0,0 +1,3 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26-dvt \ + android.hardware.vibrator-service.cs40l26-dvt-dual \ diff --git a/vibrator/cs40l26/device-stereo-evt.mk b/vibrator/cs40l26/device-stereo-evt.mk new file mode 100644 index 00000000..e04282c7 --- /dev/null +++ b/vibrator/cs40l26/device-stereo-evt.mk @@ -0,0 +1,3 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26-evt \ + android.hardware.vibrator-service.cs40l26-evt-dual \ diff --git a/vibrator/cs40l26/device-stereo.mk b/vibrator/cs40l26/device-stereo.mk new file mode 100644 index 00000000..44d2b92c --- /dev/null +++ b/vibrator/cs40l26/device-stereo.mk @@ -0,0 +1,6 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26 \ + android.hardware.vibrator-service.cs40l26-dual \ + +BOARD_SEPOLICY_DIRS += \ + hardware/google/pixel-sepolicy/vibrator/cs40l26 \ diff --git a/vibrator/cs40l26/device.mk b/vibrator/cs40l26/device.mk new file mode 100644 index 00000000..5a3dd4f8 --- /dev/null +++ b/vibrator/cs40l26/device.mk @@ -0,0 +1,6 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26 + +BOARD_SEPOLICY_DIRS += \ + hardware/google/pixel-sepolicy/vibrator/common \ + hardware/google/pixel-sepolicy/vibrator/cs40l26 diff --git a/vibrator/cs40l26/service.cpp b/vibrator/cs40l26/service.cpp new file mode 100644 index 00000000..27173d9f --- /dev/null +++ b/vibrator/cs40l26/service.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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 <android/binder_manager.h> +#include <android/binder_process.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <log/log.h> + +#include "Hardware.h" +#include "Vibrator.h" + +using ::aidl::android::hardware::vibrator::HwApi; +using ::aidl::android::hardware::vibrator::HwCal; +using ::aidl::android::hardware::vibrator::Vibrator; +using ::android::defaultServiceManager; +using ::android::ProcessState; +using ::android::sp; +using ::android::String16; + +#if !defined(VIBRATOR_NAME) +#define VIBRATOR_NAME "default" +#endif + +int main() { + auto svc = ndk::SharedRefBase::make<Vibrator>(std::make_unique<HwApi>(), + std::make_unique<HwCal>()); + const auto svcName = std::string() + svc->descriptor + "/" + VIBRATOR_NAME; + + ProcessState::initWithDriver("/dev/vndbinder"); + + auto svcBinder = svc->asBinder(); + binder_status_t status = AServiceManager_addService(svcBinder.get(), svcName.c_str()); + LOG_ALWAYS_FATAL_IF(status != STATUS_OK); + + ProcessState::self()->setThreadPoolMaxThreadCount(1); + ProcessState::self()->startThreadPool(); + + ABinderProcess_setThreadPoolMaxThreadCount(0); + ABinderProcess_joinThreadPool(); + + return EXIT_FAILURE; // should not reach +} |