From 24789fe33222fa734177242059caee0af6d74299 Mon Sep 17 00:00:00 2001 From: Chris Paulo Date: Wed, 2 Nov 2022 21:57:58 +0000 Subject: [PATCH] device/vibrator: Move vibrator HAL into device folder for l26 Moved L26 portion of vibrator hal from hardware/google/pixel to device/google. Bug: 198239103 Test: None Change-Id: I4dc77f5770929337905878c7ee6acbbfa489bc57 Signed-off-by: Chris Paulo --- vibrator/Android.bp | 52 + vibrator/OWNERS | 3 + vibrator/common/Android.bp | 37 + vibrator/common/HardwareBase.cpp | 136 ++ vibrator/common/HardwareBase.h | 221 +++ vibrator/common/utils.h | 173 +++ vibrator/cs40l26/Android.bp | 86 ++ vibrator/cs40l26/Hardware.h | 346 +++++ vibrator/cs40l26/TEST_MAPPING | 10 + vibrator/cs40l26/Vibrator.cpp | 1327 +++++++++++++++++ vibrator/cs40l26/Vibrator.h | 211 +++ ...droid.hardware.vibrator-service.cs40l26.rc | 47 + ...roid.hardware.vibrator-service.cs40l26.xml | 7 + vibrator/cs40l26/device.mk | 6 + vibrator/cs40l26/service.cpp | 55 + vibrator/cs40l26/tests/Android.bp | 35 + vibrator/cs40l26/tests/mocks.h | 80 + vibrator/cs40l26/tests/test-hwapi.cpp | 288 ++++ vibrator/cs40l26/tests/test-hwcal.cpp | 386 +++++ vibrator/cs40l26/tests/test-vibrator.cpp | 682 +++++++++ vibrator/cs40l26/tests/types.h | 33 + vibrator/cs40l26/tests/utils.h | 46 + 22 files changed, 4267 insertions(+) create mode 100644 vibrator/Android.bp create mode 100644 vibrator/OWNERS create mode 100644 vibrator/common/Android.bp create mode 100644 vibrator/common/HardwareBase.cpp create mode 100644 vibrator/common/HardwareBase.h create mode 100644 vibrator/common/utils.h create mode 100644 vibrator/cs40l26/Android.bp create mode 100644 vibrator/cs40l26/Hardware.h create mode 100644 vibrator/cs40l26/TEST_MAPPING create mode 100644 vibrator/cs40l26/Vibrator.cpp create mode 100644 vibrator/cs40l26/Vibrator.h create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml create mode 100644 vibrator/cs40l26/device.mk create mode 100644 vibrator/cs40l26/service.cpp create mode 100644 vibrator/cs40l26/tests/Android.bp create mode 100644 vibrator/cs40l26/tests/mocks.h create mode 100644 vibrator/cs40l26/tests/test-hwapi.cpp create mode 100644 vibrator/cs40l26/tests/test-hwcal.cpp create mode 100644 vibrator/cs40l26/tests/test-vibrator.cpp create mode 100644 vibrator/cs40l26/tests/types.h create mode 100644 vibrator/cs40l26/tests/utils.h diff --git a/vibrator/Android.bp b/vibrator/Android.bp new file mode 100644 index 0000000..dff6816 --- /dev/null +++ b/vibrator/Android.bp @@ -0,0 +1,52 @@ +// +// Copyright (C) 2019 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: "PixelVibratorDefaults", + relative_install_path: "hw", + static_libs: [ + "PixelVibratorCommon", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcutils", + "libhardware", + "liblog", + "libutils", + ], +} + +cc_defaults { + name: "PixelVibratorBinaryDefaults", + defaults: ["PixelVibratorDefaults"], + shared_libs: [ + "android.hardware.vibrator-V2-ndk", + ], +} + +cc_defaults { + name: "PixelVibratorTestDefaults", + defaults: ["PixelVibratorDefaults"], + static_libs: [ + "android.hardware.vibrator-V2-ndk", + ], + test_suites: ["device-tests"], + require_root: true, +} diff --git a/vibrator/OWNERS b/vibrator/OWNERS new file mode 100644 index 0000000..5d4a9c3 --- /dev/null +++ b/vibrator/OWNERS @@ -0,0 +1,3 @@ +chasewu@google.com +michaelwr@google.com +taikuo@google.com diff --git a/vibrator/common/Android.bp b/vibrator/common/Android.bp new file mode 100644 index 0000000..04fbc4d --- /dev/null +++ b/vibrator/common/Android.bp @@ -0,0 +1,37 @@ +// +// Copyright (C) 2019 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_library { + name: "PixelVibratorCommon", + srcs: [ + "HardwareBase.cpp", + ], + shared_libs: [ + "libbase", + "libcutils", + "liblog", + "libutils", + ], + cflags: [ + "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", + "-DLOG_TAG=\"android.hardware.vibrator@1.x-common\"", + ], + export_include_dirs: ["."], + vendor_available: true, +} diff --git a/vibrator/common/HardwareBase.cpp b/vibrator/common/HardwareBase.cpp new file mode 100644 index 0000000..8e07e99 --- /dev/null +++ b/vibrator/common/HardwareBase.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2019 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 "HardwareBase.h" + +#include +#include + +#include +#include + +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +HwApiBase::HwApiBase() { + mPathPrefix = std::getenv("HWAPI_PATH_PREFIX") ?: ""; + if (mPathPrefix.empty()) { + ALOGE("Failed get HWAPI path prefix!"); + } +} + +void HwApiBase::saveName(const std::string &name, const std::ios *stream) { + mNames[stream] = name; +} + +bool HwApiBase::has(const std::ios &stream) { + return !!stream; +} + +void HwApiBase::debug(int fd) { + dprintf(fd, "Kernel:\n"); + + for (auto &entry : utils::pathsFromEnv("HWAPI_DEBUG_PATHS", mPathPrefix)) { + auto &path = entry.first; + auto &stream = entry.second; + std::string line; + + dprintf(fd, " %s:\n", path.c_str()); + while (std::getline(stream, line)) { + dprintf(fd, " %s\n", line.c_str()); + } + } + + mRecordsMutex.lock(); + dprintf(fd, " Records:\n"); + for (auto &r : mRecords) { + if (r == nullptr) { + continue; + } + dprintf(fd, " %s\n", r->toString(mNames).c_str()); + } + mRecordsMutex.unlock(); +} + +HwCalBase::HwCalBase() { + std::ifstream calfile; + auto propertyPrefix = std::getenv("PROPERTY_PREFIX"); + + if (propertyPrefix != NULL) { + mPropertyPrefix = std::string(propertyPrefix); + } else { + ALOGE("Failed get property prefix!"); + } + + utils::fileFromEnv("CALIBRATION_FILEPATH", &calfile); + + for (std::string line; std::getline(calfile, line);) { + if (line.empty() || line[0] == '#') { + continue; + } + std::istringstream is_line(line); + std::string key, value; + if (std::getline(is_line, key, ':') && std::getline(is_line, value)) { + mCalData[utils::trim(key)] = utils::trim(value); + } + } +} + +void HwCalBase::debug(int fd) { + std::ifstream stream; + std::string path; + std::string line; + struct context { + HwCalBase *obj; + int fd; + } context{this, fd}; + + dprintf(fd, "Properties:\n"); + + property_list( + [](const char *key, const char *value, void *cookie) { + struct context *context = static_cast(cookie); + HwCalBase *obj = context->obj; + int fd = context->fd; + const std::string expect{obj->mPropertyPrefix}; + const std::string actual{key, std::min(strlen(key), expect.size())}; + if (actual == expect) { + dprintf(fd, " %s:\n", key); + dprintf(fd, " %s\n", value); + } + }, + &context); + + dprintf(fd, "\n"); + + dprintf(fd, "Persist:\n"); + + utils::fileFromEnv("CALIBRATION_FILEPATH", &stream, &path); + + dprintf(fd, " %s:\n", path.c_str()); + while (std::getline(stream, line)) { + dprintf(fd, " %s\n", line.c_str()); + } +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/common/HardwareBase.h b/vibrator/common/HardwareBase.h new file mode 100644 index 0000000..5b07040 --- /dev/null +++ b/vibrator/common/HardwareBase.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2019 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::android::base::unique_fd; + +class HwApiBase { + private: + using NamesMap = std::map; + + class RecordInterface { + public: + virtual std::string toString(const NamesMap &names) = 0; + virtual ~RecordInterface() {} + }; + template + class Record : public RecordInterface { + public: + Record(const char *func, const T &value, const std::ios *stream) + : mFunc(func), mValue(value), mStream(stream) {} + std::string toString(const NamesMap &names) override; + + private: + const char *mFunc; + const T mValue; + const std::ios *mStream; + }; + using Records = std::list>; + + static constexpr uint32_t RECORDS_SIZE = 32; + + public: + HwApiBase(); + void debug(int fd); + + protected: + void saveName(const std::string &name, const std::ios *stream); + template + void open(const std::string &name, T *stream); + bool has(const std::ios &stream); + template + bool get(T *value, std::istream *stream); + template + bool set(const T &value, std::ostream *stream); + template + bool poll(const T &value, std::istream *stream, const int32_t timeout = -1); + template + void record(const char *func, const T &value, const std::ios *stream); + + private: + std::string mPathPrefix; + NamesMap mNames; + Records mRecords{RECORDS_SIZE}; + std::mutex mRecordsMutex; + std::mutex mIoMutex; +}; + +#define HWAPI_RECORD(args...) HwApiBase::record(__FUNCTION__, ##args) + +template +void HwApiBase::open(const std::string &name, T *stream) { + saveName(name, stream); + utils::openNoCreate(mPathPrefix + name, stream); +} + +template +bool HwApiBase::get(T *value, std::istream *stream) { + ATRACE_NAME("HwApi::get"); + std::scoped_lock ioLock{mIoMutex}; + bool ret; + stream->seekg(0); + *stream >> *value; + if (!(ret = !!*stream)) { + ALOGE("Failed to read %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + } + stream->clear(); + HWAPI_RECORD(*value, stream); + return ret; +} + +template +bool HwApiBase::set(const T &value, std::ostream *stream) { + ATRACE_NAME("HwApi::set"); + using utils::operator<<; + std::scoped_lock ioLock{mIoMutex}; + bool ret; + *stream << value << std::endl; + if (!(ret = !!*stream)) { + ALOGE("Failed to write %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + stream->clear(); + } + HWAPI_RECORD(value, stream); + return ret; +} + +template +bool HwApiBase::poll(const T &value, std::istream *stream, const int32_t timeoutMs) { + ATRACE_NAME("HwApi::poll"); + 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 %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + return false; + } + + while ((ret = get(&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 +void HwApiBase::record(const char *func, const T &value, const std::ios *stream) { + std::lock_guard lock(mRecordsMutex); + mRecords.emplace_back(std::make_unique>(func, value, stream)); + mRecords.pop_front(); +} + +template +std::string HwApiBase::Record::toString(const NamesMap &names) { + using utils::operator<<; + std::stringstream ret; + + ret << mFunc << " '" << names.at(mStream) << "' = '" << mValue << "'"; + + return ret.str(); +} + +class HwCalBase { + public: + HwCalBase(); + void debug(int fd); + + protected: + template + bool getProperty(const char *key, T *value, const T defval); + template + bool getPersist(const char *key, T *value); + + private: + std::string mPropertyPrefix; + std::map mCalData; +}; + +template +bool HwCalBase::getProperty(const char *key, T *outval, const T defval) { + ATRACE_NAME("HwCal::getProperty"); + *outval = utils::getProperty(mPropertyPrefix + key, defval); + return true; +} + +template +bool HwCalBase::getPersist(const char *key, T *value) { + ATRACE_NAME("HwCal::getPersist"); + auto it = mCalData.find(key); + if (it == mCalData.end()) { + ALOGE("Missing %s config!", key); + return false; + } + std::stringstream stream{it->second}; + utils::unpack(stream, value); + if (!stream || !stream.eof()) { + ALOGE("Invalid %s config!", key); + return false; + } + return true; +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/common/utils.h b/vibrator/common/utils.h new file mode 100644 index 0000000..188554d --- /dev/null +++ b/vibrator/common/utils.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2019 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 +#include +#include +#include + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { +namespace utils { + +template +class Is_Iterable { + private: + template + static std::true_type test(typename U::iterator *u); + + template + static std::false_type test(U *u); + + public: + static const bool value = decltype(test(0))::value; +}; + +template +using Enable_If_Iterable = std::enable_if_t::value == B>; + +template +using Enable_If_Signed = std::enable_if_t, U>; + +template +using Enable_If_Unsigned = std::enable_if_t, U>; + +// override for default behavior of printing as a character +inline std::ostream &operator<<(std::ostream &stream, const int8_t value) { + return stream << +value; +} +// override for default behavior of printing as a character +inline std::ostream &operator<<(std::ostream &stream, const uint8_t value) { + return stream << +value; +} + +template +inline auto toUnderlying(const T value) { + return static_cast>(value); +} + +template +inline Enable_If_Iterable unpack(std::istream &stream, T *value) { + for (auto &entry : *value) { + stream >> entry; + } +} + +template +inline Enable_If_Iterable unpack(std::istream &stream, T *value) { + stream >> *value; +} + +template <> +inline void unpack(std::istream &stream, std::string *value) { + *value = std::string(std::istreambuf_iterator(stream), {}); + stream.setstate(std::istream::eofbit); +} + +template +inline Enable_If_Signed getProperty(const std::string &key, const T def) { + if (std::is_floating_point_v) { + float result; + std::string value = ::android::base::GetProperty(key, ""); + if (!value.empty() && ::android::base::ParseFloat(value, &result)) { + return result; + } + return def; + } else { + return ::android::base::GetIntProperty(key, def); + } +} + +template +inline Enable_If_Unsigned getProperty(const std::string &key, const T def) { + return ::android::base::GetUintProperty(key, def); +} + +template <> +inline bool getProperty(const std::string &key, const bool def) { + return ::android::base::GetBoolProperty(key, def); +} + +template +static void openNoCreate(const std::string &file, T *outStream) { + auto mode = std::is_base_of_v ? std::ios_base::out : std::ios_base::in; + + // Force 'in' mode to prevent file creation + outStream->open(file, mode | std::ios_base::in); + if (!*outStream) { + ALOGE("Failed to open %s (%d): %s", file.c_str(), errno, strerror(errno)); + } +} + +template +static void fileFromEnv(const char *env, T *outStream, std::string *outName = nullptr) { + auto file = std::getenv(env); + + if (file == nullptr) { + ALOGE("Failed get env %s", env); + return; + } + + if (outName != nullptr) { + *outName = std::string(file); + } + + openNoCreate(file, outStream); +} + +static ATTRIBUTE_UNUSED auto pathsFromEnv(const char *env, const std::string &prefix = "") { + std::map ret; + auto value = std::getenv(env); + + if (value == nullptr) { + return ret; + } + + std::istringstream paths{value}; + std::string path; + + while (paths >> path) { + ret[path].open(prefix + path); + } + + return ret; +} + +static ATTRIBUTE_UNUSED std::string trim(const std::string &str, + const std::string &whitespace = " \t") { + const auto str_begin = str.find_first_not_of(whitespace); + if (str_begin == std::string::npos) { + return ""; + } + + const auto str_end = str.find_last_not_of(whitespace); + const auto str_range = str_end - str_begin + 1; + + return str.substr(str_begin, str_range); +} + +} // namespace utils +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp new file mode 100644 index 0000000..53764de --- /dev/null +++ b/vibrator/cs40l26/Android.bp @@ -0,0 +1,86 @@ +// +// 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", + ], + include_dirs: [ + "external/tinyalsa/include", + ], + shared_libs: [ + "libcutils", + "libtinyalsa", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26TestDefaults", + defaults: [ + "PixelVibratorTestDefaults", + "android.hardware.vibrator-defaults.cs40l26", + ], + static_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libtinyalsa", + ], +} + +cc_library { + name: "android.hardware.vibrator-impl.cs40l26", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + srcs: ["Vibrator.cpp"], + 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"], + shared_libs: ["android.hardware.vibrator-impl.cs40l26"], + 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"], + shared_libs: ["android.hardware.vibrator-impl.cs40l26"], + cflags: ["-DVIBRATOR_NAME=\"dual\""], + proprietary: true, +} diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h new file mode 100644 index 0000000..ae052ba --- /dev/null +++ b/vibrator/cs40l26/Hardware.h @@ -0,0 +1,346 @@ +/* + * 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 + +#include "HardwareBase.h" +#include "Vibrator.h" + +#define PROC_SND_PCM "/proc/asound/pcm" +#define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback" + +static struct pcm_config haptic_nohost_config = { + .channels = 1, + .rate = 48000, + .period_size = 80, + .period_count = 2, + .format = PCM_FORMAT_S16_LE, +}; + +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 , the WAVEFORM_MAX_INDEX must not exceed 96. + * #define FF_GAIN 0x60 // 96 in decimal + * #define FF_MAX_EFFECTS FF_GAIN + */ + WAVEFORM_MAX_INDEX, +}; + +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); + open("default/f0_comp_enable", &mF0CompEnable); + open("default/redc_comp_enable", &mRedcCompEnable); + open("default/delay_before_stop_playback_us", &mMinOnOffInterval); + } + + 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 get(value, &mEffectCount); } + bool pollVibeState(uint32_t value, int32_t timeoutMs) override { + return poll(value, &mVibeState, timeoutMs); + } + bool hasOwtFreeSpace() override { return has(mOwtFreeSpace); } + bool getOwtFreeSpace(uint32_t *value) override { return get(value, &mOwtFreeSpace); } + bool setF0CompEnable(bool value) override { return set(value, &mF0CompEnable); } + bool setRedcCompEnable(bool value) override { return set(value, &mRedcCompEnable); } + bool setMinOnOffInterval(uint32_t value) override { return set(value, &mMinOnOffInterval); } + // TODO(b/234338136): Need to add the force feedback HW API test cases + bool setFFGain(int fd, uint16_t value) override { + struct input_event gain = { + .type = EV_FF, + .code = FF_GAIN, + .value = value, + }; + if (write(fd, (const void *)&gain, sizeof(gain)) != sizeof(gain)) { + return false; + } + return true; + } + bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) override { + if (((*effect).replay.length != timeoutMs) || (ioctl(fd, EVIOCSFF, effect) < 0)) { + ALOGE("setFFEffect fail"); + return false; + } else { + return true; + } + } + bool setFFPlay(int fd, int8_t index, bool value) override { + struct input_event play = { + .type = EV_FF, + .code = static_cast(index), + .value = value, + }; + if (write(fd, (const void *)&play, sizeof(play)) != sizeof(play)) { + return false; + } else { + return true; + } + } + bool getHapticAlsaDevice(int *card, int *device) override { + 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 setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) override { + 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; + } + bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect, + uint32_t *outEffectIndex, int *status) override { + (*effect).u.periodic.custom_len = numBytes / sizeof(uint16_t); + delete[] ((*effect).u.periodic.custom_data); + (*effect).u.periodic.custom_data = new int16_t[(*effect).u.periodic.custom_len]{0x0000}; + if ((*effect).u.periodic.custom_data == nullptr) { + ALOGE("Failed to allocate memory for custom data\n"); + *status = EX_NULL_POINTER; + return false; + } + memcpy((*effect).u.periodic.custom_data, owtData, numBytes); + + if ((*effect).id != -1) { + ALOGE("(*effect).id != -1"); + } + + /* Create a new OWT waveform to update the PWLE or composite effect. */ + (*effect).id = -1; + if (ioctl(fd, EVIOCSFF, effect) < 0) { + ALOGE("Failed to upload effect %d (%d): %s", *outEffectIndex, errno, strerror(errno)); + delete[] ((*effect).u.periodic.custom_data); + *status = EX_ILLEGAL_STATE; + return false; + } + + if ((*effect).id >= FF_MAX_EFFECTS || (*effect).id < 0) { + ALOGE("Invalid waveform index after upload OWT effect: %d", (*effect).id); + *status = EX_ILLEGAL_ARGUMENT; + return false; + } + *outEffectIndex = (*effect).id; + *status = 0; + return true; + } + bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector *effect) override { + uint32_t effectCountBefore, effectCountAfter, i, successFlush = 0; + + if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) { + ALOGE("Invalid waveform index for OWT erase: %d", effectIndex); + return false; + } + + if (effectIndex < WAVEFORM_MAX_INDEX) { + /* Normal situation. Only erase the effect which we just played. */ + if (ioctl(fd, EVIOCRMFF, effectIndex) < 0) { + ALOGE("Failed to erase effect %d (%d): %s", effectIndex, errno, strerror(errno)); + } + for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) { + if ((*effect)[i].id == effectIndex) { + (*effect)[i].id = -1; + break; + } + } + } else { + /* Flush all non-prestored effects of ff-core and driver. */ + getEffectCount(&effectCountBefore); + for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < FF_MAX_EFFECTS; i++) { + if (ioctl(fd, EVIOCRMFF, i) >= 0) { + successFlush++; + } + } + getEffectCount(&effectCountAfter); + ALOGW("Flushed effects: ff: %d; driver: %d -> %d; success: %d", effectIndex, + effectCountBefore, effectCountAfter, successFlush); + /* Reset all OWT effect index of HAL. */ + for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) { + (*effect)[i].id = -1; + } + } + return true; + } + + 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::ifstream mOwtFreeSpace; + std::ofstream mF0CompEnable; + std::ofstream mRedcCompEnable; + std::ofstream mMinOnOffInterval; +}; + +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 V_TICK_DEFAULT = {1, 100}; + static constexpr std::array V_CLICK_DEFAULT = {1, 100}; + static constexpr std::array 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 *value) override { + if (getPersist(TICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_TICK_DEFAULT; + return true; + } + bool getClickVolLevels(std::array *value) override { + if (getPersist(CLICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_CLICK_DEFAULT; + return true; + } + bool getLongVolLevels(std::array *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); + } + bool isF0CompEnabled() override { + bool value; + getProperty("f0.comp.enabled", &value, true); + return value; + } + bool isRedcCompEnabled() override { + bool value; + getProperty("redc.comp.enabled", &value, true); + return value; + } + void debug(int fd) override { HwCalBase::debug(fd); } +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/TEST_MAPPING b/vibrator/cs40l26/TEST_MAPPING new file mode 100644 index 0000000..1d8ff7a --- /dev/null +++ b/vibrator/cs40l26/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "VibratorHalCs40l26TestSuite", + "keywords": [ + "nextgen" + ] + } + ] +} diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp new file mode 100644 index 0000000..3af057c --- /dev/null +++ b/vibrator/cs40l26/Vibrator.cpp @@ -0,0 +1,1327 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) +#endif + +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 uint16_t FF_CUSTOM_DATA_LEN_MAX_PWLE = 2302; + +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 uint32_t MIN_ON_OFF_INTERVAL_US = 8500; // SVC initialization time +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 uint32_t WT_LEN_CALCD = 0x00800000; +static constexpr uint8_t PWLE_CHIRP_BIT = 0x8; // Dynamic/static frequency and voltage +static constexpr uint8_t PWLE_BRAKE_BIT = 0x4; +static constexpr uint8_t PWLE_AMP_REG_BIT = 0x2; + +static constexpr float PWLE_LEVEL_MIN = 0.0; +static constexpr float PWLE_LEVEL_MAX = 1.0; +static constexpr float CS40L26_PWLE_LEVEL_MIX = -1.0; +static constexpr float CS40L26_PWLE_LEVEL_MAX = 0.9995118; +static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.00; +static constexpr float PWLE_FREQUENCY_MIN_HZ = 1.00; +static constexpr float PWLE_FREQUENCY_MAX_HZ = 1000.00; +static constexpr float PWLE_BW_MAP_SIZE = + 1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ); + +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 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 , 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 defaultSupportedPrimitives = { + ndk::enum_range().begin(), ndk::enum_range().end()}; + +enum vibe_state { + VIBE_STATE_STOPPED = 0, + VIBE_STATE_HAPTIC, + VIBE_STATE_ASP, +}; + +std::mutex mActiveId_mutex; // protects mActiveId + +static int min(int x, int y) { + return x < y ? x : y; +} + +static int floatToUint16(float input, uint16_t *output, float scale, float min, float max) { + if (input < min || input > max) + return -ERANGE; + + *output = roundf(input * scale); + return 0; +} + +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(data), + .current = reinterpret_cast(data), + .max = reinterpret_cast(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); +} + +Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) + : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)), mAsyncHandle(std::async([] {})) { + int32_t longFrequencyShift; + std::string caldata{8, '0'}; + uint32_t calVer; + + const char *inputEventName = std::getenv("INPUT_EVENT_NAME"); + const char *inputEventPathName = std::getenv("INPUT_EVENT_PATH"); + if ((strstr(inputEventName, "cs40l26") != nullptr) || + (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) { + glob_t inputEventPaths; + int fd = -1; + int ret; + uint32_t val = 0; + char str[20] = {0x00}; + for (uint8_t retry = 0; retry < 10; retry++) { + ret = glob(inputEventPathName, 0, nullptr, &inputEventPaths); + if (ret) { + ALOGE("Fail to get input event paths (%d): %s", errno, strerror(errno)); + } else { + 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 && + strstr(str, inputEventName) != nullptr) { + mInputFd.reset(fd); + ALOGI("Control %s through %s", inputEventName, + inputEventPaths.gl_pathv[i]); + break; + } + close(fd); + } + } + } + + if (ret == 0) { + globfree(&inputEventPaths); + } + if (mInputFd.ok()) { + break; + } + + sleep(1); + ALOGW("Retry #%d to search in %zu input devices.", retry, inputEventPaths.gl_pathc); + } + + if (!mInputFd.ok()) { + ALOGE("Fail to get an input event with name %s", inputEventName); + } + } else { + ALOGE("The input name %s is not cs40l26_input or cs40l26_dual_input", inputEventName); + } + + 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; + for (effectIndex = 0; effectIndex < WAVEFORM_MAX_INDEX; effectIndex++) { + if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) { + /* Initialize physical waveforms. */ + mFfEffects[effectIndex] = { + .type = FF_PERIODIC, + .id = -1, + .replay.length = static_cast(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, + }; + // Bypass the waveform update due to different input name + if ((strstr(inputEventName, "cs40l26") != nullptr) || + (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) { + if (!mHwApi->setFFEffect( + mInputFd, &mFfEffects[effectIndex], + static_cast(mFfEffects[effectIndex].replay.length))) { + ALOGE("Failed upload effect %d (%d): %s", effectIndex, errno, strerror(errno)); + } + } + if (mFfEffects[effectIndex].id != effectIndex) { + ALOGW("Unexpected effect index: %d -> %d", effectIndex, mFfEffects[effectIndex].id); + } + } else { + /* Initiate placeholders for OWT effects. */ + 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 { + ALOGD("Unsupported calibration version: %u!", calVer); + } + + mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled()); + mHwApi->setRedcCompEnable(mHwCal->isRedcCompEnabled()); + + 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; + } + + mHwApi->setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US); +} + +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_GET_RESONANT_FREQUENCY | + IVibrator::CAP_GET_Q_FACTOR; + if (hasHapticAlsaDevice()) { + ret |= IVibrator::CAP_EXTERNAL_CONTROL; + } else { + ALOGE("No haptics ALSA device"); + } + 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"); + bool ret{true}; + const std::scoped_lock lock(mActiveId_mutex); + + if (mActiveId >= 0) { + /* Stop the active effect. */ + if (!mHwApi->setFFPlay(mInputFd, mActiveId, false)) { + ALOGE("Failed to stop effect %d (%d): %s", mActiveId, errno, strerror(errno)); + ret = false; + } + + if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) && + (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) { + ALOGE("Failed to clean up the composed effect %d", mActiveId); + ret = false; + } + } else { + ALOGV("Vibrator is already off"); + } + + mActiveId = -1; + setGlobalAmplitude(false); + if (mF0Offset) { + mHwApi->setF0Offset(0); + } + + if (ret) { + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } +} + +ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, + const std::shared_ptr &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); + if (mF0Offset) { + mHwApi->setF0Offset(mF0Offset); + } + return on(timeoutMs, index, nullptr /*ignored*/, callback); +} + +ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *_aidl_return) { + ATRACE_NAME("Vibrator::perform"); + return performEffect(effect, strength, callback, _aidl_return); +} + +ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector *_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 || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) { + if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) { + ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } else { + ALOGE("No haptics ALSA device"); + 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 *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 &composite, + const std::shared_ptr &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 = dspmem_chunk_bytes(ch); + + /* 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 == dspmem_chunk_bytes(ch)) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else { + return performEffect(WAVEFORM_MAX_INDEX /*ignored*/, VOLTAGE_SCALE_MAX /*ignored*/, ch, + callback); + } +} + +ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem_chunk *ch, + const std::shared_ptr &callback) { + ndk::ScopedAStatus status = ndk::ScopedAStatus::ok(); + + if (effectIndex >= FF_MAX_EFFECTS) { + ALOGE("Invalid waveform index %d", effectIndex); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) { + ALOGE("Previous vibration pending: prev: %d, curr: %d", mActiveId, effectIndex); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + if (ch) { + /* Upload OWT effect. */ + if (ch->head == nullptr) { + ALOGE("Invalid OWT bank"); + delete ch; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + bool isPwle = (*reinterpret_cast(ch->head) != 0x0000); + effectIndex = isPwle ? WAVEFORM_PWLE : WAVEFORM_COMPOSE; + + uint32_t freeBytes; + mHwApi->getOwtFreeSpace(&freeBytes); + if (dspmem_chunk_bytes(ch) > freeBytes) { + ALOGE("Invalid OWT length: Effect %d: %d > %d!", effectIndex, dspmem_chunk_bytes(ch), + freeBytes); + delete ch; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + int errorStatus; + if (!mHwApi->uploadOwtEffect(mInputFd, ch->head, dspmem_chunk_bytes(ch), + &mFfEffects[effectIndex], &effectIndex, &errorStatus)) { + delete ch; + ALOGE("Invalid uploadOwtEffect"); + return ndk::ScopedAStatus::fromExceptionCode(errorStatus); + } + delete ch; + + } else if (effectIndex == WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX || + effectIndex == WAVEFORM_LONG_VIBRATION_EFFECT_INDEX) { + /* Update duration for long/short vibration. */ + mFfEffects[effectIndex].replay.length = static_cast(timeoutMs); + if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex], + static_cast(timeoutMs))) { + ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + + const std::scoped_lock lock(mActiveId_mutex); + mActiveId = effectIndex; + /* Play the event now. */ + if (!mHwApi->setFFPlay(mInputFd, effectIndex, true)) { + ALOGE("Failed to play effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + 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); + if (!mHwApi->setFFGain(mInputFd, scale)) { + 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 * /*_aidl_return*/) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t /*id*/, Effect /*effect*/, + EffectStrength /*strength*/) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} +ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t /*id*/) { + 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(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(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 *_aidl_return) { + // TODO(b/170919640): complete implementation + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + std::vector 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 *supported) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *supported = { + Braking::NONE, + }; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +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 constructPwleSegment(dspmem_chunk *ch, uint16_t delay, uint16_t amplitude, + uint16_t frequency, uint8_t flags, uint32_t vbemfTarget = 0) { + dspmem_chunk_write(ch, 16, delay); + dspmem_chunk_write(ch, 12, amplitude); + dspmem_chunk_write(ch, 12, frequency); + /* feature flags to control the chirp, CLAB braking, back EMF amplitude regulation */ + dspmem_chunk_write(ch, 8, (flags | 1) << 4); + if (flags & PWLE_AMP_REG_BIT) { + dspmem_chunk_write(ch, 24, vbemfTarget); /* target back EMF voltage */ + } +} + +static int constructActiveSegment(dspmem_chunk *ch, int duration, float amplitude, float frequency, + bool chirp) { + uint16_t delay = 0; + uint16_t amp = 0; + uint16_t freq = 0; + uint8_t flags = 0x0; + if ((floatToUint16(duration, &delay, 4, 0.0f, COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) < 0) || + (floatToUint16(amplitude, &, 2048, CS40L26_PWLE_LEVEL_MIX, CS40L26_PWLE_LEVEL_MAX) < + 0) || + (floatToUint16(frequency, &freq, 4, PWLE_FREQUENCY_MIN_HZ, PWLE_FREQUENCY_MAX_HZ) < 0)) { + ALOGE("Invalid argument: %d, %f, %f", duration, amplitude, frequency); + return -ERANGE; + } + if (chirp) { + flags |= PWLE_CHIRP_BIT; + } + constructPwleSegment(ch, delay, amp, freq, flags, 0 /*ignored*/); + return 0; +} + +static int constructBrakingSegment(dspmem_chunk *ch, int duration, Braking brakingType) { + uint16_t delay = 0; + uint16_t freq = 0; + uint8_t flags = 0x00; + if (floatToUint16(duration, &delay, 4, 0.0f, COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) < 0) { + ALOGE("Invalid argument: %d", duration); + return -ERANGE; + } + floatToUint16(PWLE_FREQUENCY_MIN_HZ, &freq, 4, PWLE_FREQUENCY_MIN_HZ, PWLE_FREQUENCY_MAX_HZ); + if (static_cast::type>(brakingType)) { + flags |= PWLE_BRAKE_BIT; + } + + constructPwleSegment(ch, delay, 0 /*ignored*/, freq, flags, 0 /*ignored*/); + return 0; +} + +static void updateWLength(dspmem_chunk *ch, uint32_t totalDuration) { + totalDuration *= 8; /* Unit: 0.125 ms (since wlength played @ 8kHz). */ + totalDuration |= WT_LEN_CALCD; /* Bit 23 is for WT_LEN_CALCD; Bit 22 is for WT_INDEFINITE. */ + *(ch->head + 0) = (totalDuration >> 24) & 0xFF; + *(ch->head + 1) = (totalDuration >> 16) & 0xFF; + *(ch->head + 2) = (totalDuration >> 8) & 0xFF; + *(ch->head + 3) = totalDuration & 0xFF; +} + +static void updateNSection(dspmem_chunk *ch, int segmentIdx) { + *(ch->head + 7) |= (0xF0 & segmentIdx) >> 4; /* Bit 4 to 7 */ + *(ch->head + 9) |= (0x0F & segmentIdx) << 4; /* Bit 3 to 0 */ +} + +ndk::ScopedAStatus Vibrator::composePwle(const std::vector &composite, + const std::shared_ptr &callback) { + ATRACE_NAME("Vibrator::composePwle"); + int32_t capabilities; + + Vibrator::getCapabilities(&capabilities); + if ((capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) == 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + if (composite.empty() || composite.size() > COMPOSE_PWLE_SIZE_MAX_DEFAULT) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + std::vector supported; + Vibrator::getSupportedBraking(&supported); + bool isClabSupported = + std::find(supported.begin(), supported.end(), Braking::CLAB) != supported.end(); + + int segmentIdx = 0; + uint32_t totalDuration = 0; + float prevEndAmplitude; + float prevEndFrequency; + resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency); + auto ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_PWLE]{0x00}, + FF_CUSTOM_DATA_LEN_MAX_PWLE); + bool chirp = false; + + dspmem_chunk_write(ch, 24, 0x000000); /* Waveform length placeholder */ + dspmem_chunk_write(ch, 8, 0); /* Repeat */ + dspmem_chunk_write(ch, 12, 0); /* Wait time between repeats */ + dspmem_chunk_write(ch, 8, 0x00); /* nsections placeholder */ + + for (auto &e : composite) { + switch (e.getTag()) { + case PrimitivePwle::active: { + auto active = e.get(); + 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.startAmplitude > CS40L26_PWLE_LEVEL_MAX) { + active.startAmplitude = CS40L26_PWLE_LEVEL_MAX; + } + if (active.endAmplitude > CS40L26_PWLE_LEVEL_MAX) { + active.endAmplitude = CS40L26_PWLE_LEVEL_MAX; + } + + 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))) { + if (constructActiveSegment(ch, 0, active.startAmplitude, active.startFrequency, + false) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + } + + if (active.startFrequency != active.endFrequency) { + chirp = true; + } + if (constructActiveSegment(ch, active.duration, active.endAmplitude, + active.endFrequency, chirp) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + + prevEndAmplitude = active.endAmplitude; + prevEndFrequency = active.endFrequency; + totalDuration += active.duration; + chirp = false; + break; + } + case PrimitivePwle::braking: { + auto braking = e.get(); + if (braking.braking > Braking::CLAB) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else if (!isClabSupported && (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); + } + + if (constructBrakingSegment(ch, 0, braking.braking) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + + if (constructBrakingSegment(ch, braking.duration, braking.braking) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + + resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency); + totalDuration += braking.duration; + break; + } + } + + if (segmentIdx > COMPOSE_PWLE_SIZE_MAX_DEFAULT) { + ALOGE("Too many PrimitivePwle section!"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + dspmem_chunk_flush(ch); + + /* Update wlength */ + totalDuration += MAX_COLD_START_LATENCY_MS; + if (totalDuration > 0x7FFFF) { + ALOGE("Total duration is too long (%d)!", totalDuration); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + updateWLength(ch, totalDuration); + + /* Update nsections */ + updateNSection(ch, segmentIdx); + + return performEffect(WAVEFORM_MAX_INDEX /*ignored*/, VOLTAGE_SCALE_MAX /*ignored*/, ch, + callback); +} + +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(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::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. + if (!mConfigHapticAlsaDeviceDone) { + if (mHwApi->getHapticAlsaDevice(&mCard, &mDevice)) { + mHasHapticAlsaDevice = true; + mConfigHapticAlsaDeviceDone = true; + } else { + ALOGE("Haptic ALSA device not supported"); + } + } else { + ALOGD("Haptic ALSA device configuration done."); + } + return mHasHapticAlsaDevice; +} + +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::performEffect(Effect effect, EffectStrength strength, + const std::shared_ptr &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); + volLevel = VOLTAGE_SCALE_MAX; + break; + default: + status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + break; + } + if (!status.isOk()) { + goto exit; + } + + status = performEffect(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 &callback) { + setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX); + + return on(MAX_TIME_MS, effectIndex, ch, callback); +} + +void Vibrator::waitForComplete(std::shared_ptr &&callback) { + if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, POLLING_TIMEOUT)) { + ALOGW("Failed to get state \"Haptic\""); + } + mHwApi->pollVibeState(VIBE_STATE_STOPPED); + + const std::scoped_lock lock(mActiveId_mutex); + if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) && + (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) { + ALOGE("Failed to clean up the composed effect %d", mActiveId); + } + mActiveId = -1; + + 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 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 0000000..220c974 --- /dev/null +++ b/vibrator/cs40l26/Vibrator.h @@ -0,0 +1,211 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include + +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 timeout or vibrator reaches desired state + // (2 = ASP enabled, 1 = haptic enabled, 0 = disabled). + virtual bool pollVibeState(uint32_t value, int32_t timeoutMs = -1) = 0; + // Reports whether getOwtFreeSpace() is supported. + virtual bool hasOwtFreeSpace() = 0; + // Reports the available OWT bytes. + virtual bool getOwtFreeSpace(uint32_t *value) = 0; + // Enables/Disables F0 compensation enable status + virtual bool setF0CompEnable(bool value) = 0; + // Enables/Disables Redc compensation enable status + virtual bool setRedcCompEnable(bool value) = 0; + // Stores the minumun delay time between playback and stop effects. + virtual bool setMinOnOffInterval(uint32_t value) = 0; + // Indicates the number of 0.125-dB steps of attenuation to apply to + // waveforms triggered in response to vibration calls from the + // Android vibrator HAL. + virtual bool setFFGain(int fd, uint16_t value) = 0; + // Create/modify custom effects for all physical waveforms. + virtual bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) = 0; + // Activates/deactivates the effect index after setFFGain() and setFFEffect(). + virtual bool setFFPlay(int fd, int8_t index, bool value) = 0; + // Get the Alsa device for the audio coupled haptics effect + virtual bool getHapticAlsaDevice(int *card, int *device) = 0; + // Set haptics PCM amplifier before triggering audio haptics feature + virtual bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, + int device) = 0; + // Set OWT waveform for compose or compose PWLE request + virtual bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes, + struct ff_effect *effect, uint32_t *outEffectIndex, + int *status) = 0; + // Erase OWT waveform + virtual bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector *effect) = 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 *value) = 0; + virtual bool getClickVolLevels(std::array *value) = 0; + virtual bool getLongVolLevels(std::array *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; + // Checks if the f0 compensation feature needs to be enabled. + virtual bool isF0CompEnabled() = 0; + // Checks if the redc compensation feature needs to be enabled. + virtual bool isRedcCompEnabled() = 0; + // Emit diagnostic information to the given file. + virtual void debug(int fd) = 0; + }; + + public: + Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal); + + ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override; + ndk::ScopedAStatus off() override; + ndk::ScopedAStatus on(int32_t timeoutMs, + const std::shared_ptr &callback) override; + ndk::ScopedAStatus perform(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *_aidl_return) override; + ndk::ScopedAStatus getSupportedEffects(std::vector *_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 *supported) override; + ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive, + int32_t *durationMs) override; + ndk::ScopedAStatus compose(const std::vector &composite, + const std::shared_ptr &callback) override; + ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector *_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 *_aidl_return) override; + ndk::ScopedAStatus getPwlePrimitiveDurationMax(int32_t *durationMs) override; + ndk::ScopedAStatus getPwleCompositionSizeMax(int32_t *maxSize) override; + ndk::ScopedAStatus getSupportedBraking(std::vector *supported) override; + ndk::ScopedAStatus composePwle(const std::vector &composite, + const std::shared_ptr &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, struct dspmem_chunk *ch, + const std::shared_ptr &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 performEffect(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *outTimeMs); + ndk::ScopedAStatus performEffect(uint32_t effectIndex, uint32_t volLevel, + struct dspmem_chunk *ch, + const std::shared_ptr &callback); + ndk::ScopedAStatus setPwle(const std::string &pwleQueue); + bool isUnderExternalControl(); + void waitForComplete(std::shared_ptr &&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 mHwApi; + std::unique_ptr mHwCal; + uint32_t mF0Offset; + std::array mTickEffectVol; + std::array mClickEffectVol; + std::array mLongEffectVol; + std::vector mFfEffects; + std::vector mEffectDurations; + std::future mAsyncHandle; + ::android::base::unique_fd mInputFd; + int8_t mActiveId{-1}; + struct pcm *mHapticPcm; + int mCard; + int mDevice; + bool mHasHapticAlsaDevice{false}; + bool mIsUnderExternalControl; + float mLongEffectScale = 1.0; + bool mIsChirpEnabled; + uint32_t mSupportedPrimitivesBits = 0x0; + std::vector mSupportedPrimitives; + bool mConfigHapticAlsaDeviceDone{false}; +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl 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 0000000..0fcca56 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc @@ -0,0 +1,47 @@ +on 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 + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_comp_enable + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/redc_comp_enable + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/delay_before_stop_playback_us + + 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 INPUT_EVENT_PATH /dev/input/event* + 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 + default/f0_comp_enable + default/redc_comp_enable + default/delay_before_stop_playback_us + " + + 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 0000000..4db8f8c --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml @@ -0,0 +1,7 @@ + + + android.hardware.vibrator + 2 + IVibrator/default + + diff --git a/vibrator/cs40l26/device.mk b/vibrator/cs40l26/device.mk new file mode 100644 index 0000000..5a3dd4f --- /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 0000000..27173d9 --- /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 +#include +#include +#include +#include + +#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(std::make_unique(), + std::make_unique()); + 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 +} diff --git a/vibrator/cs40l26/tests/Android.bp b/vibrator/cs40l26/tests/Android.bp new file mode 100644 index 0000000..93c9a9f --- /dev/null +++ b/vibrator/cs40l26/tests/Android.bp @@ -0,0 +1,35 @@ +// +// Copyright (C) 2022 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_test { + name: "VibratorHalCs40l26TestSuite", + defaults: ["VibratorHalCs40l26TestDefaults"], + srcs: [ + "test-hwcal.cpp", + "test-hwapi.cpp", + "test-vibrator.cpp", + ], + static_libs: [ + "libc++fs", + "libgmock", + ], + shared_libs: [ + "libbase", + ], +} diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h new file mode 100644 index 0000000..21466a0 --- /dev/null +++ b/vibrator/cs40l26/tests/mocks.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 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. + */ +#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H +#define ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H + +#include + +#include "Vibrator.h" + +class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { + public: + MOCK_METHOD0(destructor, void()); + MOCK_METHOD1(setF0, bool(std::string value)); + MOCK_METHOD1(setF0Offset, bool(uint32_t value)); + MOCK_METHOD1(setRedc, bool(std::string value)); + MOCK_METHOD1(setQ, bool(std::string value)); + MOCK_METHOD1(getEffectCount, bool(uint32_t *value)); + MOCK_METHOD2(pollVibeState, bool(uint32_t value, int32_t timeoutMs)); + MOCK_METHOD0(hasOwtFreeSpace, bool()); + MOCK_METHOD1(getOwtFreeSpace, bool(uint32_t *value)); + MOCK_METHOD1(setF0CompEnable, bool(bool value)); + MOCK_METHOD1(setRedcCompEnable, bool(bool value)); + MOCK_METHOD1(setMinOnOffInterval, bool(uint32_t value)); + MOCK_METHOD2(setFFGain, bool(int fd, uint16_t value)); + MOCK_METHOD3(setFFEffect, bool(int fd, struct ff_effect *effect, uint16_t timeoutMs)); + MOCK_METHOD3(setFFPlay, bool(int fd, int8_t index, bool value)); + MOCK_METHOD2(getHapticAlsaDevice, bool(int *card, int *device)); + MOCK_METHOD4(setHapticPcmAmp, bool(struct pcm **haptic_pcm, bool enable, int card, int device)); + MOCK_METHOD6(uploadOwtEffect, + bool(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect, + uint32_t *outEffectIndex, int *status)); + MOCK_METHOD3(eraseOwtEffect, bool(int fd, int8_t effectIndex, std::vector *effect)); + MOCK_METHOD1(debug, void(int fd)); + + ~MockApi() override { destructor(); }; +}; + +class MockCal : public ::aidl::android::hardware::vibrator::Vibrator::HwCal { + public: + MOCK_METHOD0(destructor, void()); + MOCK_METHOD1(getVersion, bool(uint32_t *value)); + MOCK_METHOD1(getF0, bool(std::string &value)); + MOCK_METHOD1(getRedc, bool(std::string &value)); + MOCK_METHOD1(getQ, bool(std::string &value)); + MOCK_METHOD1(getLongFrequencyShift, bool(int32_t *value)); + MOCK_METHOD1(getTickVolLevels, bool(std::array *value)); + MOCK_METHOD1(getClickVolLevels, bool(std::array *value)); + MOCK_METHOD1(getLongVolLevels, bool(std::array *value)); + MOCK_METHOD0(isChirpEnabled, bool()); + MOCK_METHOD1(getSupportedPrimitives, bool(uint32_t *value)); + MOCK_METHOD0(isF0CompEnabled, bool()); + MOCK_METHOD0(isRedcCompEnabled, bool()); + MOCK_METHOD1(debug, void(int fd)); + + ~MockCal() override { destructor(); }; + // b/132668253: Workaround gMock Compilation Issue + bool getF0(std::string *value) { return getF0(*value); } + bool getRedc(std::string *value) { return getRedc(*value); } + bool getQ(std::string *value) { return getQ(*value); } +}; + +class MockVibratorCallback : public aidl::android::hardware::vibrator::BnVibratorCallback { + public: + MOCK_METHOD(ndk::ScopedAStatus, onComplete, ()); +}; + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H diff --git a/vibrator/cs40l26/tests/test-hwapi.cpp b/vibrator/cs40l26/tests/test-hwapi.cpp new file mode 100644 index 0000000..cc4d465 --- /dev/null +++ b/vibrator/cs40l26/tests/test-hwapi.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2022 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 +#include +#include + +#include +#include + +#include "Hardware.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::Test; +using ::testing::TestParamInfo; +using ::testing::ValuesIn; +using ::testing::WithParamInterface; + +class HwApiTest : public Test { + private: + static constexpr const char *FILE_NAMES[]{ + "calibration/f0_stored", + "default/f0_offset", + "calibration/redc_stored", + "calibration/q_stored", + "default/f0_comp_enable", + "default/redc_comp_enable", + "default/owt_free_space", + "default/num_waves", + "default/delay_before_stop_playback_us", + }; + + public: + void SetUp() override { + std::string prefix; + for (auto n : FILE_NAMES) { + auto name = std::filesystem::path(n); + auto path = std::filesystem::path(mFilesDir.path) / name; + fs_mkdirs(path.c_str(), S_IRWXU); + std::ofstream touch{path}; + mFileMap[name] = path; + } + prefix = std::filesystem::path(mFilesDir.path) / ""; + setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true); + mHwApi = std::make_unique(); + + for (auto n : FILE_NAMES) { + auto name = std::filesystem::path(n); + auto path = std::filesystem::path(mEmptyDir.path) / name; + } + prefix = std::filesystem::path(mEmptyDir.path) / ""; + setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true); + mNoApi = std::make_unique(); + } + + void TearDown() override { verifyContents(); } + + static auto ParamNameFixup(std::string str) { + std::replace(str.begin(), str.end(), '/', '_'); + return str; + } + + protected: + // Set expected file content for a test. + template + void expectContent(const std::string &name, const T &value) { + mExpectedContent[name] << value << std::endl; + } + + // Set actual file content for an input test. + template + void updateContent(const std::string &name, const T &value) { + std::ofstream(mFileMap[name]) << value << std::endl; + } + + template + void expectAndUpdateContent(const std::string &name, const T &value) { + expectContent(name, value); + updateContent(name, value); + } + + // Compare all file contents against expected contents. + void verifyContents() { + for (auto &a : mFileMap) { + std::ifstream file{a.second}; + std::string expect = mExpectedContent[a.first].str(); + std::string actual = std::string(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + EXPECT_EQ(expect, actual) << a.first; + } + } + + protected: + std::unique_ptr mHwApi; + std::unique_ptr mNoApi; + std::map mFileMap; + TemporaryDir mFilesDir; + TemporaryDir mEmptyDir; + std::map mExpectedContent; +}; + +template +class HwApiTypedTest : public HwApiTest, + public WithParamInterface>> { + public: + static auto PrintParam(const TestParamInfo &info) { + return ParamNameFixup(std::get<0>(info.param)); + } + static auto MakeParam(std::string name, std::function func) { + return std::make_tuple(name, func); + } +}; + +using HasTest = HwApiTypedTest; + +TEST_P(HasTest, success_returnsTrue) { + auto param = GetParam(); + auto func = std::get<1>(param); + + EXPECT_TRUE(func(*mHwApi)); +} + +TEST_P(HasTest, success_returnsFalse) { + auto param = GetParam(); + auto func = std::get<1>(param); + + EXPECT_FALSE(func(*mNoApi)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, HasTest, + ValuesIn({ + HasTest::MakeParam("default/owt_free_space", + &Vibrator::HwApi::hasOwtFreeSpace), + }), + HasTest::PrintParam); + +using GetUint32Test = HwApiTypedTest; + +TEST_P(GetUint32Test, success) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + uint32_t expect = std::rand(); + uint32_t actual = ~expect; + + expectAndUpdateContent(name, expect); + + EXPECT_TRUE(func(*mHwApi, &actual)); + EXPECT_EQ(expect, actual); +} + +TEST_P(GetUint32Test, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + uint32_t value; + + EXPECT_FALSE(func(*mNoApi, &value)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, GetUint32Test, + ValuesIn({ + GetUint32Test::MakeParam("default/num_waves", + &Vibrator::HwApi::getEffectCount), + GetUint32Test::MakeParam("default/owt_free_space", + &Vibrator::HwApi::getOwtFreeSpace), + }), + GetUint32Test::PrintParam); + +using SetBoolTest = HwApiTypedTest; + +TEST_P(SetBoolTest, success_returnsTrue) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + + expectContent(name, "1"); + + EXPECT_TRUE(func(*mHwApi, true)); +} + +TEST_P(SetBoolTest, success_returnsFalse) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + + expectContent(name, "0"); + + EXPECT_TRUE(func(*mHwApi, false)); +} + +TEST_P(SetBoolTest, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + + EXPECT_FALSE(func(*mNoApi, true)); + EXPECT_FALSE(func(*mNoApi, false)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, SetBoolTest, + ValuesIn({ + SetBoolTest::MakeParam("default/f0_comp_enable", + &Vibrator::HwApi::setF0CompEnable), + SetBoolTest::MakeParam("default/redc_comp_enable", + &Vibrator::HwApi::setRedcCompEnable), + }), + SetBoolTest::PrintParam); + +using SetUint32Test = HwApiTypedTest; + +TEST_P(SetUint32Test, success) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + uint32_t value = std::rand(); + + expectContent(name, value); + + EXPECT_TRUE(func(*mHwApi, value)); +} + +TEST_P(SetUint32Test, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + uint32_t value = std::rand(); + + EXPECT_FALSE(func(*mNoApi, value)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, SetUint32Test, + ValuesIn({ + SetUint32Test::MakeParam("default/f0_offset", + &Vibrator::HwApi::setF0Offset), + SetUint32Test::MakeParam("default/delay_before_stop_playback_us", + &Vibrator::HwApi::setMinOnOffInterval), + }), + SetUint32Test::PrintParam); + +using SetStringTest = HwApiTypedTest; + +TEST_P(SetStringTest, success) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + std::string value = TemporaryFile().path; + + expectContent(name, value); + + EXPECT_TRUE(func(*mHwApi, value)); +} + +TEST_P(SetStringTest, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + std::string value = TemporaryFile().path; + + EXPECT_FALSE(func(*mNoApi, value)); +} + +INSTANTIATE_TEST_CASE_P( + HwApiTests, SetStringTest, + ValuesIn({ + SetStringTest::MakeParam("calibration/f0_stored", &Vibrator::HwApi::setF0), + SetStringTest::MakeParam("calibration/redc_stored", &Vibrator::HwApi::setRedc), + SetStringTest::MakeParam("calibration/q_stored", &Vibrator::HwApi::setQ), + }), + SetStringTest::PrintParam); + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/tests/test-hwcal.cpp b/vibrator/cs40l26/tests/test-hwcal.cpp new file mode 100644 index 0000000..e482b6c --- /dev/null +++ b/vibrator/cs40l26/tests/test-hwcal.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2022 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 +#include + +#include + +#include "Hardware.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::Test; + +class HwCalTest : public Test { + protected: + static constexpr std::array V_TICK_DEFAULT = {1, 100}; + static constexpr std::array V_CLICK_DEFAULT = {1, 100}; + static constexpr std::array V_LONG_DEFAULT = {1, 100}; + + public: + void SetUp() override { setenv("CALIBRATION_FILEPATH", mCalFile.path, true); } + + private: + template + static void pack(std::ostream &stream, const T &value, std::string lpad, std::string rpad) { + stream << lpad << value << rpad; + } + + template ::size_type N> + static void pack(std::ostream &stream, const std::array &value, std::string lpad, + std::string rpad) { + for (auto &entry : value) { + pack(stream, entry, lpad, rpad); + } + } + + protected: + void createHwCal() { mHwCal = std::make_unique(); } + + template + void write(const std::string key, const T &value, std::string lpad = " ", + std::string rpad = "") { + std::ofstream calfile{mCalFile.path, std::ios_base::app}; + calfile << key << ":"; + pack(calfile, value, lpad, rpad); + calfile << std::endl; + } + + void unlink() { ::unlink(mCalFile.path); } + + protected: + std::unique_ptr mHwCal; + TemporaryFile mCalFile; +}; + +TEST_F(HwCalTest, f0_measured) { + uint32_t randInput = std::rand(); + std::string expect = std::to_string(randInput); + std::string actual = std::to_string(~randInput); + + write("f0_measured", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getF0(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, f0_missing) { + std::string actual; + + createHwCal(); + + EXPECT_FALSE(mHwCal->getF0(&actual)); +} + +TEST_F(HwCalTest, redc_measured) { + uint32_t randInput = std::rand(); + std::string expect = std::to_string(randInput); + std::string actual = std::to_string(~randInput); + + write("redc_measured", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getRedc(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, redc_missing) { + std::string actual; + + createHwCal(); + + EXPECT_FALSE(mHwCal->getRedc(&actual)); +} + +TEST_F(HwCalTest, q_measured) { + uint32_t randInput = std::rand(); + std::string expect = std::to_string(randInput); + std::string actual = std::to_string(~randInput); + + write("q_measured", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getQ(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, q_missing) { + std::string actual; + + createHwCal(); + + EXPECT_FALSE(mHwCal->getQ(&actual)); +} + +TEST_F(HwCalTest, v_levels) { + std::array expect; + std::array actual; + + // voltage for tick effects + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("v_tick", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + // voltage for click effects + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("v_click", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + // voltage for long effects + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("v_long", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_missing) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_short) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", std::array()); + write("v_click", std::array()); + write("v_long", std::array()); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_long) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", std::array()); + write("v_click", std::array()); + write("v_long", std::array()); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_nofile) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", actual); + write("v_click", actual); + write("v_long", actual); + unlink(); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, multiple) { + uint32_t randInput = std::rand(); + std::string f0Expect = std::to_string(randInput); + std::string f0Actual = std::to_string(~randInput); + randInput = std::rand(); + std::string redcExpect = std::to_string(randInput); + std::string redcActual = std::to_string(~randInput); + randInput = std::rand(); + std::string qExpect = std::to_string(randInput); + std::string qActual = std::to_string(~randInput); + std::array volTickExpect, volClickExpect, volLongExpect; + std::array volActual; + + std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("f0_measured", f0Expect); + write("redc_measured", redcExpect); + write("q_measured", qExpect); + write("v_tick", volTickExpect); + std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(), + [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_click", volClickExpect); + std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_long", volLongExpect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getF0(&f0Actual)); + EXPECT_EQ(f0Expect, f0Actual); + EXPECT_TRUE(mHwCal->getRedc(&redcActual)); + EXPECT_EQ(redcExpect, redcActual); + EXPECT_TRUE(mHwCal->getQ(&qActual)); + EXPECT_EQ(qExpect, qActual); + EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual)); + EXPECT_EQ(volTickExpect, volActual); + EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual)); + EXPECT_EQ(volClickExpect, volActual); + EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual)); + EXPECT_EQ(volLongExpect, volActual); +} + +TEST_F(HwCalTest, trimming) { + uint32_t randInput = std::rand(); + std::string f0Expect = std::to_string(randInput); + std::string f0Actual = std::to_string(~randInput); + randInput = std::rand(); + std::string redcExpect = std::to_string(randInput); + std::string redcActual = std::to_string(randInput); + randInput = std::rand(); + std::string qExpect = std::to_string(randInput); + std::string qActual = std::to_string(randInput); + std::array volTickExpect, volClickExpect, volLongExpect; + std::array volActual; + + std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("f0_measured", f0Expect, " \t", "\t "); + write("redc_measured", redcExpect, " \t", "\t "); + write("q_measured", qExpect, " \t", "\t "); + write("v_tick", volTickExpect, " \t", "\t "); + std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(), + [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_click", volClickExpect, " \t", "\t "); + std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_long", volLongExpect, " \t", "\t "); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getF0(&f0Actual)); + EXPECT_EQ(f0Expect, f0Actual); + EXPECT_TRUE(mHwCal->getRedc(&redcActual)); + EXPECT_EQ(redcExpect, redcActual); + EXPECT_TRUE(mHwCal->getQ(&qActual)); + EXPECT_EQ(qExpect, qActual); + EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual)); + EXPECT_EQ(volTickExpect, volActual); + EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual)); + EXPECT_EQ(volClickExpect, volActual); + EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual)); + EXPECT_EQ(volLongExpect, volActual); +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp new file mode 100644 index 0000000..a8bedd5 --- /dev/null +++ b/vibrator/cs40l26/tests/test-vibrator.cpp @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2022 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 +#include +#include +#include +#include +#include + +#include + +#include "Vibrator.h" +#include "mocks.h" +#include "types.h" +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Assign; +using ::testing::AtLeast; +using ::testing::AtMost; +using ::testing::Combine; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Exactly; +using ::testing::Expectation; +using ::testing::ExpectationSet; +using ::testing::Ge; +using ::testing::Mock; +using ::testing::MockFunction; +using ::testing::Range; +using ::testing::Return; +using ::testing::Sequence; +using ::testing::SetArgPointee; +using ::testing::SetArgReferee; +using ::testing::Test; +using ::testing::TestParamInfo; +using ::testing::ValuesIn; +using ::testing::WithParamInterface; + +// Forward Declarations + +static EffectQueue Queue(const QueueEffect &effect); +static EffectQueue Queue(const QueueDelay &delay); +template +static EffectQueue Queue(const T &first, const U &second, Args... rest); + +static EffectLevel Level(float intensity, float levelLow, float levelHigh); +static EffectScale Scale(float intensity, float levelLow, float levelHigh); + +// Constants With Arbitrary Values + +static constexpr uint32_t CAL_VERSION = 2; +static constexpr std::array V_TICK_DEFAULT = {1, 100}; +static constexpr std::array V_CLICK_DEFAULT{1, 100}; +static constexpr std::array V_LONG_DEFAULT{1, 100}; +static constexpr std::array EFFECT_DURATIONS{ + 0, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000}; + +// Constants With Prescribed Values + +static const std::map EFFECT_INDEX{ + {Effect::CLICK, 2}, + {Effect::TICK, 2}, + {Effect::HEAVY_CLICK, 2}, + {Effect::TEXTURE_TICK, 9}, +}; +static constexpr uint32_t MIN_ON_OFF_INTERVAL_US = 8500; +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 auto POLLING_TIMEOUT = 20; +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 , the WAVEFORM_MAX_INDEX must not exceed 96. + * #define FF_GAIN 0x60 // 96 in decimal + * #define FF_MAX_EFFECTS FF_GAIN + */ + WAVEFORM_MAX_INDEX, +}; + +static const EffectScale ON_GLOBAL_SCALE{levelToScale(V_LONG_DEFAULT[1])}; +static const EffectIndex ON_EFFECT_INDEX{0}; + +static const std::map EFFECT_SCALE{ + {{Effect::TICK, EffectStrength::LIGHT}, + Scale(0.5f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::TICK, EffectStrength::MEDIUM}, + Scale(0.5f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::TICK, EffectStrength::STRONG}, + Scale(0.5f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::CLICK, EffectStrength::LIGHT}, + Scale(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::CLICK, EffectStrength::MEDIUM}, + Scale(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::CLICK, EffectStrength::STRONG}, + Scale(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::HEAVY_CLICK, EffectStrength::LIGHT}, + Scale(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::HEAVY_CLICK, EffectStrength::MEDIUM}, + Scale(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::HEAVY_CLICK, EffectStrength::STRONG}, + Scale(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::TEXTURE_TICK, EffectStrength::LIGHT}, + Scale(0.5f * 0.5f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])}, + {{Effect::TEXTURE_TICK, EffectStrength::MEDIUM}, + Scale(0.5f * 0.7f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])}, + {{Effect::TEXTURE_TICK, EffectStrength::STRONG}, + Scale(0.5f * 1.0f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])}, +}; + +static const std::map EFFECT_QUEUE{ + {{Effect::DOUBLE_CLICK, EffectStrength::LIGHT}, + Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + 100, + QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})}, + {{Effect::DOUBLE_CLICK, EffectStrength::MEDIUM}, + Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + 100, + QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})}, + {{Effect::DOUBLE_CLICK, EffectStrength::STRONG}, + Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + 100, + QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})}, +}; + +EffectQueue Queue(const QueueEffect &effect) { + auto index = std::get<0>(effect); + auto level = std::get<1>(effect); + auto string = std::to_string(index) + "." + std::to_string(level); + auto duration = EFFECT_DURATIONS[index]; + return {string, duration}; +} + +EffectQueue Queue(const QueueDelay &delay) { + auto string = std::to_string(delay); + return {string, delay}; +} + +template +EffectQueue Queue(const T &first, const U &second, Args... rest) { + auto head = Queue(first); + auto tail = Queue(second, rest...); + auto string = std::get<0>(head) + "," + std::get<0>(tail); + auto duration = std::get<1>(head) + std::get<1>(tail); + return {string, duration}; +} + +static EffectLevel Level(float intensity, float levelLow, float levelHigh) { + return std::lround(intensity * (levelHigh - levelLow)) + levelLow; +} + +static EffectScale Scale(float intensity, float levelLow, float levelHigh) { + return levelToScale(Level(intensity, levelLow, levelHigh)); +} + +class VibratorTest : public Test { + public: + void SetUp() override { + setenv("INPUT_EVENT_NAME", "CS40L26TestSuite", true); + std::unique_ptr mockapi; + std::unique_ptr mockcal; + + createMock(&mockapi, &mockcal); + createVibrator(std::move(mockapi), std::move(mockcal)); + } + + void TearDown() override { deleteVibrator(); } + + protected: + void createMock(std::unique_ptr *mockapi, std::unique_ptr *mockcal) { + *mockapi = std::make_unique(); + *mockcal = std::make_unique(); + + mMockApi = mockapi->get(); + mMockCal = mockcal->get(); + + ON_CALL(*mMockApi, destructor()).WillByDefault(Assign(&mMockApi, nullptr)); + + ON_CALL(*mMockApi, setFFGain(_, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, setFFEffect(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, setFFPlay(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, pollVibeState(_, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, eraseOwtEffect(_, _, _)).WillByDefault(Return(true)); + + ON_CALL(*mMockApi, getOwtFreeSpace(_)) + .WillByDefault(DoAll(SetArgPointee<0>(11504), Return(true))); + + ON_CALL(*mMockCal, destructor()).WillByDefault(Assign(&mMockCal, nullptr)); + + ON_CALL(*mMockCal, getVersion(_)) + .WillByDefault(DoAll(SetArgPointee<0>(CAL_VERSION), Return(true))); + + ON_CALL(*mMockCal, getTickVolLevels(_)) + .WillByDefault(DoAll(SetArgPointee<0>(V_TICK_DEFAULT), Return(true))); + ON_CALL(*mMockCal, getClickVolLevels(_)) + .WillByDefault(DoAll(SetArgPointee<0>(V_CLICK_DEFAULT), Return(true))); + ON_CALL(*mMockCal, getLongVolLevels(_)) + .WillByDefault(DoAll(SetArgPointee<0>(V_LONG_DEFAULT), Return(true))); + + relaxMock(false); + } + + void createVibrator(std::unique_ptr mockapi, std::unique_ptr mockcal, + bool relaxed = true) { + if (relaxed) { + relaxMock(true); + } + mVibrator = ndk::SharedRefBase::make(std::move(mockapi), std::move(mockcal)); + if (relaxed) { + relaxMock(false); + } + } + + void deleteVibrator(bool relaxed = true) { + if (relaxed) { + relaxMock(true); + } + mVibrator.reset(); + } + + private: + void relaxMock(bool relax) { + auto times = relax ? AnyNumber() : Exactly(0); + + Mock::VerifyAndClearExpectations(mMockApi); + Mock::VerifyAndClearExpectations(mMockCal); + + EXPECT_CALL(*mMockApi, destructor()).Times(times); + EXPECT_CALL(*mMockApi, setF0(_)).Times(times); + EXPECT_CALL(*mMockApi, setF0Offset(_)).Times(times); + EXPECT_CALL(*mMockApi, setRedc(_)).Times(times); + EXPECT_CALL(*mMockApi, setQ(_)).Times(times); + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).Times(times); + EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).Times(times); + EXPECT_CALL(*mMockApi, setF0CompEnable(_)).Times(times); + EXPECT_CALL(*mMockApi, setRedcCompEnable(_)).Times(times); + EXPECT_CALL(*mMockApi, pollVibeState(_, _)).Times(times); + EXPECT_CALL(*mMockApi, setFFGain(_, _)).Times(times); + EXPECT_CALL(*mMockApi, setFFEffect(_, _, _)).Times(times); + EXPECT_CALL(*mMockApi, setFFPlay(_, _, _)).Times(times); + EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times); + + EXPECT_CALL(*mMockApi, debug(_)).Times(times); + + EXPECT_CALL(*mMockCal, destructor()).Times(times); + EXPECT_CALL(*mMockCal, getF0(_)).Times(times); + EXPECT_CALL(*mMockCal, getRedc(_)).Times(times); + EXPECT_CALL(*mMockCal, getQ(_)).Times(times); + EXPECT_CALL(*mMockCal, getTickVolLevels(_)).Times(times); + EXPECT_CALL(*mMockCal, getClickVolLevels(_)).Times(times); + EXPECT_CALL(*mMockCal, getLongVolLevels(_)).Times(times); + EXPECT_CALL(*mMockCal, isChirpEnabled()).Times(times); + EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).Times(times); + EXPECT_CALL(*mMockCal, isF0CompEnabled()).Times(times); + EXPECT_CALL(*mMockCal, isRedcCompEnabled()).Times(times); + EXPECT_CALL(*mMockCal, debug(_)).Times(times); + } + + protected: + MockApi *mMockApi; + MockCal *mMockCal; + std::shared_ptr mVibrator; + uint32_t mEffectIndex; +}; + +TEST_F(VibratorTest, Constructor) { + std::unique_ptr mockapi; + std::unique_ptr mockcal; + std::string f0Val = std::to_string(std::rand()); + std::string redcVal = std::to_string(std::rand()); + std::string qVal = std::to_string(std::rand()); + uint32_t calVer; + uint32_t supportedPrimitivesBits = 0x0; + Expectation volGet; + Sequence f0Seq, redcSeq, qSeq, supportedPrimitivesSeq; + + EXPECT_CALL(*mMockApi, destructor()).WillOnce(DoDefault()); + EXPECT_CALL(*mMockCal, destructor()).WillOnce(DoDefault()); + + deleteVibrator(false); + + createMock(&mockapi, &mockcal); + + EXPECT_CALL(*mMockCal, getF0(_)) + .InSequence(f0Seq) + .WillOnce(DoAll(SetArgReferee<0>(f0Val), Return(true))); + EXPECT_CALL(*mMockApi, setF0(f0Val)).InSequence(f0Seq).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, getRedc(_)) + .InSequence(redcSeq) + .WillOnce(DoAll(SetArgReferee<0>(redcVal), Return(true))); + EXPECT_CALL(*mMockApi, setRedc(redcVal)).InSequence(redcSeq).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, getQ(_)) + .InSequence(qSeq) + .WillOnce(DoAll(SetArgReferee<0>(qVal), Return(true))); + EXPECT_CALL(*mMockApi, setQ(qVal)).InSequence(qSeq).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).WillOnce(Return(true)); + + mMockCal->getVersion(&calVer); + if (calVer == 2) { + volGet = EXPECT_CALL(*mMockCal, getTickVolLevels(_)).WillOnce(DoDefault()); + volGet = EXPECT_CALL(*mMockCal, getClickVolLevels(_)).WillOnce(DoDefault()); + volGet = EXPECT_CALL(*mMockCal, getLongVolLevels(_)).WillOnce(DoDefault()); + } + + EXPECT_CALL(*mMockCal, isF0CompEnabled()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setF0CompEnable(true)).WillOnce(Return(true)); + EXPECT_CALL(*mMockCal, isRedcCompEnabled()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setRedcCompEnable(true)).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, isChirpEnabled()).WillOnce(Return(true)); + EXPECT_CALL(*mMockCal, getSupportedPrimitives(_)) + .InSequence(supportedPrimitivesSeq) + .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitivesBits), Return(true))); + + EXPECT_CALL(*mMockApi, setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US)).WillOnce(Return(true)); + createVibrator(std::move(mockapi), std::move(mockcal), false); +} + +TEST_F(VibratorTest, on) { + Sequence s1, s2; + uint16_t duration = std::rand() + 1; + + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, setFFEffect(_, _, duration + MAX_COLD_START_LATENCY_MS)) + .InSequence(s2) + .WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, setFFPlay(_, ON_EFFECT_INDEX, true)) + .InSequence(s1, s2) + .WillOnce(DoDefault()); + EXPECT_TRUE(mVibrator->on(duration, nullptr).isOk()); +} + +TEST_F(VibratorTest, off) { + Sequence s1; + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault()); + EXPECT_TRUE(mVibrator->off().isOk()); +} + +TEST_F(VibratorTest, supportsAmplitudeControl_supported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_GT(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0); +} + +TEST_F(VibratorTest, supportsExternalAmplitudeControl_unsupported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL, 0); +} + +TEST_F(VibratorTest, setAmplitude_supported) { + EffectAmplitude amplitude = static_cast(std::rand()) / RAND_MAX ?: 1.0f; + + EXPECT_CALL(*mMockApi, setFFGain(_, amplitudeToScale(amplitude))).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setAmplitude(amplitude).isOk()); +} + +TEST_F(VibratorTest, supportsExternalControl_supported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_GT(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0); +} + +TEST_F(VibratorTest, supportsExternalControl_unsupported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(false)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0); +} + +TEST_F(VibratorTest, setExternalControl_enable) { + Sequence s1, s2; + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _)) + .InSequence(s1, s2) + .WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setExternalControl(true).isOk()); +} + +TEST_F(VibratorTest, setExternalControl_disable) { + Sequence s1, s2, s3, s4; + + // The default mIsUnderExternalControl is false, so it needs to turn on the External Control + // to make mIsUnderExternalControl become true. + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)) + .InSequence(s1) + .InSequence(s1) + .WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _)).InSequence(s3).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setExternalControl(true).isOk()); + + EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(VOLTAGE_SCALE_MAX))) + .InSequence(s4) + .WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, false, _, _)) + .InSequence(s1, s2, s3, s4) + .WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setExternalControl(false).isOk()); +} + +class EffectsTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &info) { + auto param = info.param; + auto effect = std::get<0>(param); + auto strength = std::get<1>(param); + return toString(effect) + "_" + toString(strength); + } +}; + +TEST_P(EffectsTest, perform) { + auto param = GetParam(); + auto effect = std::get<0>(param); + auto strength = std::get<1>(param); + auto scale = EFFECT_SCALE.find(param); + auto queue = EFFECT_QUEUE.find(param); + EffectDuration duration; + auto callback = ndk::SharedRefBase::make(); + std::promise promise; + std::future future{promise.get_future()}; + auto complete = [&promise] { + promise.set_value(); + return ndk::ScopedAStatus::ok(); + }; + bool composeEffect; + + ExpectationSet eSetup; + Expectation eActivate, ePollHaptics, ePollStop, eEraseDone; + + if (scale != EFFECT_SCALE.end()) { + EffectIndex index = EFFECT_INDEX.at(effect); + duration = EFFECT_DURATIONS[index]; + + eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(scale->second))) + .WillOnce(DoDefault()); + eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, index, true)) + .After(eSetup) + .WillOnce(DoDefault()); + } else if (queue != EFFECT_QUEUE.end()) { + duration = std::get<1>(queue->second); + eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)) + .After(eSetup) + .WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)) + .After(eSetup) + .WillOnce(DoDefault()); + eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true)) + .After(eSetup) + .WillOnce(DoDefault()); + composeEffect = true; + } else { + duration = 0; + } + + if (duration) { + ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT)) + .After(eActivate) + .WillOnce(DoDefault()); + ePollStop = EXPECT_CALL(*mMockApi, pollVibeState(0, -1)) + .After(ePollHaptics) + .WillOnce(DoDefault()); + if (composeEffect) { + eEraseDone = EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _)) + .After(ePollStop) + .WillOnce(DoDefault()); + EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete); + } else { + EXPECT_CALL(*callback, onComplete()).After(ePollStop).WillOnce(complete); + } + } + + int32_t lengthMs; + ndk::ScopedAStatus status = mVibrator->perform(effect, strength, callback, &lengthMs); + if (status.isOk()) { + EXPECT_LE(duration, lengthMs); + } else { + EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode()); + EXPECT_EQ(0, lengthMs); + } + + if (duration) { + EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready); + } +} + +const std::vector kEffects{ndk::enum_range().begin(), + ndk::enum_range().end()}; +const std::vector kEffectStrengths{ndk::enum_range().begin(), + ndk::enum_range().end()}; + +INSTANTIATE_TEST_CASE_P(VibratorTests, EffectsTest, + Combine(ValuesIn(kEffects.begin(), kEffects.end()), + ValuesIn(kEffectStrengths.begin(), kEffectStrengths.end())), + EffectsTest::PrintParam); + +struct PrimitiveParam { + CompositePrimitive primitive; + EffectIndex index; +}; + +class PrimitiveTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &info) { + return toString(info.param.primitive); + } +}; + +const std::vector kPrimitiveParams = { + {CompositePrimitive::CLICK, 2}, {CompositePrimitive::THUD, 4}, + {CompositePrimitive::SPIN, 5}, {CompositePrimitive::QUICK_RISE, 6}, + {CompositePrimitive::SLOW_RISE, 7}, {CompositePrimitive::QUICK_FALL, 8}, + {CompositePrimitive::LIGHT_TICK, 9}, {CompositePrimitive::LOW_TICK, 10}, +}; + +TEST_P(PrimitiveTest, getPrimitiveDuration) { + auto param = GetParam(); + auto primitive = param.primitive; + auto index = param.index; + int32_t duration; + + EXPECT_EQ(EX_NONE, mVibrator->getPrimitiveDuration(primitive, &duration).getExceptionCode()); + EXPECT_EQ(EFFECT_DURATIONS[index], duration); +} + +INSTANTIATE_TEST_CASE_P(VibratorTests, PrimitiveTest, + ValuesIn(kPrimitiveParams.begin(), kPrimitiveParams.end()), + PrimitiveTest::PrintParam); + +struct ComposeParam { + std::string name; + std::vector composite; + EffectQueue queue; +}; + +class ComposeTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &info) { return info.param.name; } +}; + +TEST_P(ComposeTest, compose) { + auto param = GetParam(); + auto composite = param.composite; + auto queue = std::get<0>(param.queue); + ExpectationSet eSetup; + Expectation eActivate, ePollHaptics, ePollStop, eEraseDone; + auto callback = ndk::SharedRefBase::make(); + std::promise promise; + std::future future{promise.get_future()}; + auto complete = [&promise] { + promise.set_value(); + return ndk::ScopedAStatus::ok(); + }; + + eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)) + .After(eSetup) + .WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)) + .After(eSetup) + .WillOnce(DoDefault()); + eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true)) + .After(eSetup) + .WillOnce(DoDefault()); + + ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT)) + .After(eActivate) + .WillOnce(DoDefault()); + ePollStop = + EXPECT_CALL(*mMockApi, pollVibeState(0, -1)).After(ePollHaptics).WillOnce(DoDefault()); + eEraseDone = + EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _)).After(ePollStop).WillOnce(DoDefault()); + EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete); + + EXPECT_EQ(EX_NONE, mVibrator->compose(composite, callback).getExceptionCode()); + + EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready); +} + +const std::vector kComposeParams = { + {"click", + {{0, CompositePrimitive::CLICK, 1.0f}}, + Queue(QueueEffect(2, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"thud", + {{1, CompositePrimitive::THUD, 0.8f}}, + Queue(1, QueueEffect(4, Level(0.8f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"spin", + {{2, CompositePrimitive::SPIN, 0.6f}}, + Queue(2, QueueEffect(5, Level(0.6f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"quick_rise", + {{3, CompositePrimitive::QUICK_RISE, 0.4f}}, + Queue(3, QueueEffect(6, Level(0.4f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)}, + {"slow_rise", + {{4, CompositePrimitive::SLOW_RISE, 0.0f}}, + Queue(4, QueueEffect(7, Level(0.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"quick_fall", + {{5, CompositePrimitive::QUICK_FALL, 1.0f}}, + Queue(5, QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)}, + {"pop", + {{6, CompositePrimitive::SLOW_RISE, 1.0f}, {50, CompositePrimitive::THUD, 1.0f}}, + Queue(6, QueueEffect(7, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 50, + QueueEffect(4, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"snap", + {{7, CompositePrimitive::QUICK_RISE, 1.0f}, {0, CompositePrimitive::QUICK_FALL, 1.0f}}, + Queue(7, QueueEffect(6, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), + QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)}, +}; + +INSTANTIATE_TEST_CASE_P(VibratorTests, ComposeTest, + ValuesIn(kComposeParams.begin(), kComposeParams.end()), + ComposeTest::PrintParam); +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/tests/types.h b/vibrator/cs40l26/tests/types.h new file mode 100644 index 0000000..e05c648 --- /dev/null +++ b/vibrator/cs40l26/tests/types.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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. + */ +#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H +#define ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H + +#include + +using EffectIndex = uint16_t; +using EffectLevel = uint32_t; +using EffectAmplitude = float; +using EffectScale = uint16_t; +using EffectDuration = uint32_t; +using EffectQueue = std::tuple; +using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect, + ::aidl::android::hardware::vibrator::EffectStrength>; + +using QueueEffect = std::tuple; +using QueueDelay = uint32_t; + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H diff --git a/vibrator/cs40l26/tests/utils.h b/vibrator/cs40l26/tests/utils.h new file mode 100644 index 0000000..e7f6055 --- /dev/null +++ b/vibrator/cs40l26/tests/utils.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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. + */ +#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H +#define ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H + +#include + +#include "types.h" + +static inline EffectScale toScale(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); +} + +static inline EffectScale levelToScale(EffectLevel level) { + return toScale(level, 100); +} + +static inline EffectScale amplitudeToScale(EffectAmplitude amplitude) { + return toScale(amplitude, 1.0f); +} + +static inline uint32_t msToCycles(EffectDuration ms) { + return ms * 48; +} + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H