1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "Vibrator.h"
18
19 #include <android-base/properties.h>
20 #include <hardware/hardware.h>
21 #include <hardware/vibrator.h>
22 #include <log/log.h>
23 #include <stdio.h>
24 #include <utils/Trace.h>
25
26 #include <cinttypes>
27 #include <cmath>
28 #include <fstream>
29 #include <iostream>
30 #include <map>
31 #include <sstream>
32
33 #include "Stats.h"
34
35 #ifndef ARRAY_SIZE
36 #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
37 #endif
38
39 #define PROC_SND_PCM "/proc/asound/pcm"
40 #define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback"
41
42 namespace aidl {
43 namespace android {
44 namespace hardware {
45 namespace vibrator {
46
47 #ifdef HAPTIC_TRACE
48 #define HAPTICS_TRACE(...) ALOGD(__VA_ARGS__)
49 #else
50 #define HAPTICS_TRACE(...)
51 #endif
52
53 static constexpr uint32_t BASE_CONTINUOUS_EFFECT_OFFSET = 32768;
54
55 static constexpr uint32_t WAVEFORM_EFFECT_0_20_LEVEL = 0;
56 static constexpr uint32_t WAVEFORM_EFFECT_1_00_LEVEL = 4;
57 static constexpr uint32_t WAVEFORM_EFFECT_LEVEL_MINIMUM = 4;
58
59 static constexpr uint32_t WAVEFORM_DOUBLE_CLICK_SILENCE_MS = 100;
60
61 static constexpr uint32_t WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0;
62 static constexpr uint32_t WAVEFORM_LONG_VIBRATION_THRESHOLD_MS = 50;
63 static constexpr uint32_t WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3 + BASE_CONTINUOUS_EFFECT_OFFSET;
64
65 static constexpr uint32_t WAVEFORM_CLICK_INDEX = 2;
66 static constexpr uint32_t WAVEFORM_THUD_INDEX = 4;
67 static constexpr uint32_t WAVEFORM_SPIN_INDEX = 5;
68 static constexpr uint32_t WAVEFORM_QUICK_RISE_INDEX = 6;
69 static constexpr uint32_t WAVEFORM_SLOW_RISE_INDEX = 7;
70 static constexpr uint32_t WAVEFORM_QUICK_FALL_INDEX = 8;
71 static constexpr uint32_t WAVEFORM_LIGHT_TICK_INDEX = 9;
72 static constexpr uint32_t WAVEFORM_LOW_TICK_INDEX = 10;
73
74 static constexpr uint32_t WAVEFORM_UNSAVED_TRIGGER_QUEUE_INDEX = 65529;
75 static constexpr uint32_t WAVEFORM_TRIGGER_QUEUE_INDEX = 65534;
76 static constexpr uint32_t VOLTAGE_GLOBAL_SCALE_LEVEL = 5;
77 static constexpr uint8_t VOLTAGE_SCALE_MAX = 100;
78
79 static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6; // I2C Transaction + DSP Return-From-Standby
80 static constexpr int8_t MAX_PAUSE_TIMING_ERROR_MS = 1; // ALERT Irq Handling
81 static constexpr uint32_t MAX_TIME_MS = UINT32_MAX;
82
83 static constexpr float AMP_ATTENUATE_STEP_SIZE = 0.125f;
84 static constexpr float EFFECT_FREQUENCY_KHZ = 48.0f;
85
86 static constexpr auto ASYNC_COMPLETION_TIMEOUT = std::chrono::milliseconds(100);
87 static constexpr auto POLLING_TIMEOUT = 20;
88
89 static constexpr int32_t COMPOSE_DELAY_MAX_MS = 10000;
90 static constexpr int32_t COMPOSE_SIZE_MAX = 127;
91 static constexpr int32_t COMPOSE_PWLE_SIZE_LIMIT = 82;
92 static constexpr int32_t CS40L2X_PWLE_LENGTH_MAX = 4094;
93
94 // Measured resonant frequency, f0_measured, is represented by Q10.14 fixed
95 // point format on cs40l2x devices. The expression to calculate f0 is:
96 // f0 = f0_measured / 2^Q14_BIT_SHIFT
97 // See the LRA Calibration Support documentation for more details.
98 static constexpr int32_t Q14_BIT_SHIFT = 14;
99
100 // Measured Q factor, q_measured, is represented by Q8.16 fixed
101 // point format on cs40l2x devices. The expression to calculate q is:
102 // q = q_measured / 2^Q16_BIT_SHIFT
103 // See the LRA Calibration Support documentation for more details.
104 static constexpr int32_t Q16_BIT_SHIFT = 16;
105
106 // Measured ReDC, redc_measured, is represented by Q7.17 fixed
107 // point format on cs40l2x devices. The expression to calculate redc is:
108 // redc = redc_measured * 5.857 / 2^Q17_BIT_SHIFT
109 // See the LRA Calibration Support documentation for more details.
110 static constexpr int32_t Q17_BIT_SHIFT = 17;
111
112 static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 999;
113 static constexpr float PWLE_LEVEL_MIN = 0.0f;
114 static constexpr float PWLE_LEVEL_MAX = 1.0f;
115 static constexpr float CS40L2X_PWLE_LEVEL_MAX = 0.99f;
116 static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.0f;
117 static constexpr float PWLE_FREQUENCY_MIN_HZ = 30.0f;
118 static constexpr float RESONANT_FREQUENCY_DEFAULT = 145.0f;
119 static constexpr float PWLE_FREQUENCY_MAX_HZ = 300.0f;
120 static constexpr float PWLE_BW_MAP_SIZE =
121 1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ);
122 static constexpr float RAMP_DOWN_CONSTANT = 1048.576f;
123 static constexpr float RAMP_DOWN_TIME_MS = 0.0f;
124
125 static struct pcm_config haptic_nohost_config = {
126 .channels = 1,
127 .rate = 48000,
128 .period_size = 80,
129 .period_count = 2,
130 .format = PCM_FORMAT_S16_LE,
131 };
132
amplitudeToScale(float amplitude,float maximum)133 static uint8_t amplitudeToScale(float amplitude, float maximum) {
134 return std::round((-20 * std::log10(amplitude / static_cast<float>(maximum))) /
135 (AMP_ATTENUATE_STEP_SIZE));
136 }
137
138 // Discrete points of frequency:max_level pairs as recommended by the document
139 #if defined(LUXSHARE_ICT_081545)
140 static std::map<float, float> discretePwleMaxLevels = {{120.0, 0.4}, {130.0, 0.31}, {140.0, 0.14},
141 {145.0, 0.09}, {150.0, 0.15}, {160.0, 0.35},
142 {170.0, 0.4}};
143 // Discrete points of frequency:max_level pairs as recommended by the document
144 #elif defined(LUXSHARE_ICT_LT_XLRA1906D)
145 static std::map<float, float> discretePwleMaxLevels = {{145.0, 0.38}, {150.0, 0.35}, {160.0, 0.35},
146 {170.0, 0.15}, {180.0, 0.35}, {190.0, 0.35},
147 {200.0, 0.38}};
148 #else
149 static std::map<float, float> discretePwleMaxLevels = {};
150 #endif
151
152 // Initialize all limits to 0.4 according to the document Max. Allowable Chirp Levels
153 #if defined(LUXSHARE_ICT_081545)
154 std::vector<float> pwleMaxLevelLimitMap(PWLE_BW_MAP_SIZE, 0.4);
155 // Initialize all limits to 0.38 according to the document Max. Allowable Chirp Levels
156 #elif defined(LUXSHARE_ICT_LT_XLRA1906D)
157 std::vector<float> pwleMaxLevelLimitMap(PWLE_BW_MAP_SIZE, 0.38);
158 #else
159 std::vector<float> pwleMaxLevelLimitMap(PWLE_BW_MAP_SIZE, 1.0);
160 #endif
161
createPwleMaxLevelLimitMap()162 void Vibrator::createPwleMaxLevelLimitMap() {
163 HAPTICS_TRACE("createPwleMaxLevelLimitMap()");
164 int32_t capabilities;
165 Vibrator::getCapabilities(&capabilities);
166 if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
167 std::map<float, float>::iterator itr0, itr1;
168
169 if (discretePwleMaxLevels.empty()) {
170 return;
171 }
172 if (discretePwleMaxLevels.size() == 1) {
173 itr0 = discretePwleMaxLevels.begin();
174 float pwleMaxLevelLimitMapIdx =
175 (itr0->first - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ;
176 pwleMaxLevelLimitMap[pwleMaxLevelLimitMapIdx] = itr0->second;
177 return;
178 }
179
180 itr0 = discretePwleMaxLevels.begin();
181 itr1 = std::next(itr0, 1);
182
183 while (itr1 != discretePwleMaxLevels.end()) {
184 float x0 = itr0->first;
185 float y0 = itr0->second;
186 float x1 = itr1->first;
187 float y1 = itr1->second;
188 float pwleMaxLevelLimitMapIdx =
189 (itr0->first - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ;
190
191 // FixLater: avoid floating point loop counters
192 // NOLINTBEGIN(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
193 for (float xp = x0; xp < (x1 + PWLE_FREQUENCY_RESOLUTION_HZ);
194 xp += PWLE_FREQUENCY_RESOLUTION_HZ) {
195 // NOLINTEND(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
196 float yp = y0 + ((y1 - y0) / (x1 - x0)) * (xp - x0);
197
198 pwleMaxLevelLimitMap[pwleMaxLevelLimitMapIdx++] = yp;
199 }
200
201 itr0++;
202 itr1++;
203 }
204 }
205 }
206
207 enum class AlwaysOnId : uint32_t {
208 GPIO_RISE,
209 GPIO_FALL,
210 };
211
Vibrator(std::unique_ptr<HwApi> hwapi,std::unique_ptr<HwCal> hwcal,std::unique_ptr<StatsApi> statsapi)212 Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal,
213 std::unique_ptr<StatsApi> statsapi)
214 : mHwApi(std::move(hwapi)),
215 mHwCal(std::move(hwcal)),
216 mStatsApi(std::move(statsapi)),
217 mAsyncHandle(std::async([] {})) {
218 int32_t longFreqencyShift;
219 uint32_t calVer;
220 uint32_t caldata;
221 uint32_t effectCount;
222
223 if (!mHwApi->setState(true)) {
224 mStatsApi->logError(kHwApiError);
225 ALOGE("Failed to set state (%d): %s", errno, strerror(errno));
226 }
227
228 if (mHwCal->getF0(&caldata)) {
229 mHwApi->setF0(caldata);
230 mResonantFrequency = static_cast<float>(caldata) / (1 << Q14_BIT_SHIFT);
231 } else {
232 mStatsApi->logError(kHwApiError);
233 ALOGE("Failed to get resonant frequency (%d): %s, using default resonant HZ: %f", errno,
234 strerror(errno), RESONANT_FREQUENCY_DEFAULT);
235 mResonantFrequency = RESONANT_FREQUENCY_DEFAULT;
236 }
237 if (mHwCal->getRedc(&caldata)) {
238 mHwApi->setRedc(caldata);
239 mRedc = caldata;
240 }
241 if (mHwCal->getQ(&caldata)) {
242 mHwApi->setQ(caldata);
243 }
244
245 mHwCal->getLongFrequencyShift(&longFreqencyShift);
246 if (longFreqencyShift > 0) {
247 mF0Offset = longFreqencyShift * std::pow(2, 14);
248 } else if (longFreqencyShift < 0) {
249 mF0Offset = std::pow(2, 24) - std::abs(longFreqencyShift) * std::pow(2, 14);
250 } else {
251 mF0Offset = 0;
252 }
253
254 mHwCal->getVersion(&calVer);
255 if (calVer == 1) {
256 std::array<uint32_t, 6> volLevels;
257 mHwCal->getVolLevels(&volLevels);
258 /*
259 * Given voltage levels for two intensities, assuming a linear function,
260 * solve for 'f(0)' in 'v = f(i) = a + b * i' (i.e 'v0 - (v1 - v0) / ((i1 - i0) / i0)').
261 */
262 mClickEffectVol[0] = std::max(std::lround(volLevels[WAVEFORM_EFFECT_0_20_LEVEL] -
263 (volLevels[WAVEFORM_EFFECT_1_00_LEVEL] -
264 volLevels[WAVEFORM_EFFECT_0_20_LEVEL]) /
265 4.0f),
266 static_cast<long>(WAVEFORM_EFFECT_LEVEL_MINIMUM));
267 mClickEffectVol[1] = volLevels[WAVEFORM_EFFECT_1_00_LEVEL];
268 mTickEffectVol = mClickEffectVol;
269 mLongEffectVol[0] = 0;
270 mLongEffectVol[1] = volLevels[VOLTAGE_GLOBAL_SCALE_LEVEL];
271 } else {
272 mHwCal->getTickVolLevels(&mTickEffectVol);
273 mHwCal->getClickVolLevels(&mClickEffectVol);
274 mHwCal->getLongVolLevels(&mLongEffectVol);
275 }
276 HAPTICS_TRACE("Vibrator(hwapi, hwcal:%u)", calVer);
277
278 mHwApi->getEffectCount(&effectCount);
279 mEffectDurations.resize(effectCount);
280 for (size_t effectIndex = 0; effectIndex < effectCount; effectIndex++) {
281 mHwApi->setEffectIndex(effectIndex);
282 uint32_t effectDuration;
283 if (mHwApi->getEffectDuration(&effectDuration)) {
284 mEffectDurations[effectIndex] = std::ceil(effectDuration / EFFECT_FREQUENCY_KHZ);
285 }
286 }
287
288 mHwApi->setClabEnable(true);
289
290 if (!(getPwleCompositionSizeMax(&mCompositionSizeMax).isOk())) {
291 mStatsApi->logError(kInitError);
292 ALOGE("Failed to get pwle composition size max, using default size: %d",
293 COMPOSE_PWLE_SIZE_LIMIT);
294 mCompositionSizeMax = COMPOSE_PWLE_SIZE_LIMIT;
295 }
296
297 mIsChirpEnabled = mHwCal->isChirpEnabled();
298 createPwleMaxLevelLimitMap();
299 mGenerateBandwidthAmplitudeMapDone = false;
300 mBandwidthAmplitudeMap = generateBandwidthAmplitudeMap();
301 mIsUnderExternalControl = false;
302 setPwleRampDown();
303 }
304
getCapabilities(int32_t * _aidl_return)305 ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) {
306 HAPTICS_TRACE("getCapabilities(_aidl_return)");
307 ATRACE_NAME("Vibrator::getCapabilities");
308 int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
309 IVibrator::CAP_COMPOSE_EFFECTS | IVibrator::CAP_ALWAYS_ON_CONTROL |
310 IVibrator::CAP_GET_RESONANT_FREQUENCY | IVibrator::CAP_GET_Q_FACTOR;
311 if (mHwApi->hasEffectScale()) {
312 ret |= IVibrator::CAP_AMPLITUDE_CONTROL;
313 }
314 if (mHwApi->hasAspEnable() || hasHapticAlsaDevice()) {
315 ret |= IVibrator::CAP_EXTERNAL_CONTROL;
316 }
317 if (mHwApi->hasPwle() && mIsChirpEnabled) {
318 ret |= IVibrator::CAP_FREQUENCY_CONTROL | IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
319 }
320 *_aidl_return = ret;
321 return ndk::ScopedAStatus::ok();
322 }
323
off()324 ndk::ScopedAStatus Vibrator::off() {
325 HAPTICS_TRACE("off()");
326 ATRACE_NAME("Vibrator::off");
327 ALOGD("off");
328 setGlobalAmplitude(false);
329 mHwApi->setF0Offset(0);
330 if (!mHwApi->setActivate(0)) {
331 mStatsApi->logError(kHwApiError);
332 ALOGE("Failed to turn vibrator off (%d): %s", errno, strerror(errno));
333 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
334 }
335 mActiveId = -1;
336 return ndk::ScopedAStatus::ok();
337 }
338
on(int32_t timeoutMs,const std::shared_ptr<IVibratorCallback> & callback)339 ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
340 const std::shared_ptr<IVibratorCallback> &callback) {
341 HAPTICS_TRACE("on(timeoutMs:%u, callback)", timeoutMs);
342 ATRACE_NAME("Vibrator::on");
343 ALOGD("on");
344 mStatsApi->logLatencyStart(kWaveformEffectLatency);
345 const uint32_t index = timeoutMs < WAVEFORM_LONG_VIBRATION_THRESHOLD_MS
346 ? WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX
347 : WAVEFORM_LONG_VIBRATION_EFFECT_INDEX;
348 mStatsApi->logWaveform(index, timeoutMs);
349 if (MAX_COLD_START_LATENCY_MS <= UINT32_MAX - timeoutMs) {
350 timeoutMs += MAX_COLD_START_LATENCY_MS;
351 }
352 setGlobalAmplitude(true);
353 mHwApi->setF0Offset(mF0Offset);
354 return on(timeoutMs, index, callback);
355 }
356
perform(Effect effect,EffectStrength strength,const std::shared_ptr<IVibratorCallback> & callback,int32_t * _aidl_return)357 ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength,
358 const std::shared_ptr<IVibratorCallback> &callback,
359 int32_t *_aidl_return) {
360 HAPTICS_TRACE("perform(effect:%s, strength:%s, callback, _aidl_return)",
361 toString(effect).c_str(), toString(strength).c_str());
362 ATRACE_NAME("Vibrator::perform");
363 ALOGD("perform");
364
365 mStatsApi->logLatencyStart(kPrebakedEffectLatency);
366
367 return performEffect(effect, strength, callback, _aidl_return);
368 }
369
getSupportedEffects(std::vector<Effect> * _aidl_return)370 ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect> *_aidl_return) {
371 HAPTICS_TRACE("getSupportedEffects(_aidl_return)");
372 *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK,
373 Effect::DOUBLE_CLICK};
374 return ndk::ScopedAStatus::ok();
375 }
376
setAmplitude(float amplitude)377 ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) {
378 HAPTICS_TRACE("setAmplitude(amplitude:%f)", amplitude);
379 ATRACE_NAME("Vibrator::setAmplitude");
380 if (amplitude <= 0.0f || amplitude > 1.0f) {
381 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
382 }
383
384 if (!isUnderExternalControl()) {
385 return setEffectAmplitude(amplitude, 1.0);
386 } else {
387 mStatsApi->logError(kUnsupportedOpError);
388 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
389 }
390 }
391
setExternalControl(bool enabled)392 ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) {
393 HAPTICS_TRACE("setExternalControl(enabled:%u)", enabled);
394 ATRACE_NAME("Vibrator::setExternalControl");
395 setGlobalAmplitude(enabled);
396
397 if (isUnderExternalControl() == enabled) {
398 if (enabled) {
399 ALOGE("Restart the external process.");
400 if (mHasHapticAlsaDevice) {
401 if (!enableHapticPcmAmp(&mHapticPcm, !enabled, mCard, mDevice)) {
402 mStatsApi->logError(kAlsaFailError);
403 ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"),
404 mDevice);
405 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
406 }
407 }
408 if (mHwApi->hasAspEnable()) {
409 if (!mHwApi->setAspEnable(!enabled)) {
410 mStatsApi->logError(kHwApiError);
411 ALOGE("Failed to set external control (%d): %s", errno, strerror(errno));
412 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
413 }
414 }
415 } else {
416 ALOGE("The external control is already disabled.");
417 return ndk::ScopedAStatus::ok();
418 }
419 }
420 if (mHasHapticAlsaDevice) {
421 if (!enableHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) {
422 mStatsApi->logError(kAlsaFailError);
423 ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice);
424 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
425 }
426 }
427 if (mHwApi->hasAspEnable()) {
428 if (!mHwApi->setAspEnable(enabled)) {
429 mStatsApi->logError(kHwApiError);
430 ALOGE("Failed to set external control (%d): %s", errno, strerror(errno));
431 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
432 }
433 }
434
435 mIsUnderExternalControl = enabled;
436 return ndk::ScopedAStatus::ok();
437 }
438
getCompositionDelayMax(int32_t * maxDelayMs)439 ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t *maxDelayMs) {
440 HAPTICS_TRACE("getCompositionDelayMax(maxDelayMs)");
441 ATRACE_NAME("Vibrator::getCompositionDelayMax");
442 *maxDelayMs = COMPOSE_DELAY_MAX_MS;
443 return ndk::ScopedAStatus::ok();
444 }
445
getCompositionSizeMax(int32_t * maxSize)446 ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t *maxSize) {
447 HAPTICS_TRACE("getCompositionSizeMax(maxSize)");
448 ATRACE_NAME("Vibrator::getCompositionSizeMax");
449 *maxSize = COMPOSE_SIZE_MAX;
450 return ndk::ScopedAStatus::ok();
451 }
452
getSupportedPrimitives(std::vector<CompositePrimitive> * supported)453 ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector<CompositePrimitive> *supported) {
454 HAPTICS_TRACE("getSupportedPrimitives(supported)");
455 *supported = {
456 CompositePrimitive::NOOP, CompositePrimitive::CLICK,
457 CompositePrimitive::THUD, CompositePrimitive::SPIN,
458 CompositePrimitive::QUICK_RISE, CompositePrimitive::SLOW_RISE,
459 CompositePrimitive::QUICK_FALL, CompositePrimitive::LIGHT_TICK,
460 CompositePrimitive::LOW_TICK,
461 };
462 return ndk::ScopedAStatus::ok();
463 }
464
getPrimitiveDuration(CompositePrimitive primitive,int32_t * durationMs)465 ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive,
466 int32_t *durationMs) {
467 HAPTICS_TRACE("getPrimitiveDuration(primitive:%s, durationMs)", toString(primitive).c_str());
468 ndk::ScopedAStatus status;
469 uint32_t effectIndex;
470
471 if (primitive != CompositePrimitive::NOOP) {
472 status = getPrimitiveDetails(primitive, &effectIndex);
473 if (!status.isOk()) {
474 return status;
475 }
476
477 *durationMs = mEffectDurations[effectIndex];
478 } else {
479 *durationMs = 0;
480 }
481
482 return ndk::ScopedAStatus::ok();
483 }
484
compose(const std::vector<CompositeEffect> & composite,const std::shared_ptr<IVibratorCallback> & callback)485 ndk::ScopedAStatus Vibrator::compose(const std::vector<CompositeEffect> &composite,
486 const std::shared_ptr<IVibratorCallback> &callback) {
487 HAPTICS_TRACE("compose(composite, callback)");
488 ATRACE_NAME("Vibrator::compose");
489 ALOGD("compose");
490 std::ostringstream effectBuilder;
491 std::string effectQueue;
492
493 mStatsApi->logLatencyStart(kCompositionEffectLatency);
494
495 if (composite.size() > COMPOSE_SIZE_MAX) {
496 mStatsApi->logError(kBadCompositeError);
497 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
498 }
499 const std::scoped_lock<std::mutex> lock(mTotalDurationMutex);
500
501 // Reset the mTotalDuration
502 mTotalDuration = 0;
503 for (auto &e : composite) {
504 if (e.scale < 0.0f || e.scale > 1.0f) {
505 mStatsApi->logError(kBadCompositeError);
506 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
507 }
508
509 if (e.delayMs) {
510 if (e.delayMs > COMPOSE_DELAY_MAX_MS) {
511 mStatsApi->logError(kBadCompositeError);
512 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
513 }
514 effectBuilder << e.delayMs << ",";
515 mTotalDuration += e.delayMs;
516 }
517 if (e.primitive != CompositePrimitive::NOOP) {
518 ndk::ScopedAStatus status;
519 uint32_t effectIndex;
520
521 status = getPrimitiveDetails(e.primitive, &effectIndex);
522 mStatsApi->logPrimitive(effectIndex);
523 if (!status.isOk()) {
524 mStatsApi->logError(kBadCompositeError);
525 return status;
526 }
527
528 effectBuilder << effectIndex << "." << intensityToVolLevel(e.scale, effectIndex) << ",";
529 mTotalDuration += mEffectDurations[effectIndex];
530 }
531 }
532
533 if (effectBuilder.tellp() == 0) {
534 mStatsApi->logError(kComposeFailError);
535 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
536 }
537
538 effectBuilder << 0;
539
540 effectQueue = effectBuilder.str();
541
542 return performEffect(0 /*ignored*/, 0 /*ignored*/, &effectQueue, callback);
543 }
544
on(uint32_t timeoutMs,uint32_t effectIndex,const std::shared_ptr<IVibratorCallback> & callback)545 ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex,
546 const std::shared_ptr<IVibratorCallback> &callback) {
547 HAPTICS_TRACE("on(timeoutMs:%u, effectIndex:%u, callback)", timeoutMs, effectIndex);
548 if (isUnderExternalControl()) {
549 setExternalControl(false);
550 ALOGE("Device is under external control mode. Force to disable it to prevent chip hang "
551 "problem.");
552 }
553 if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) {
554 mStatsApi->logError(kAsyncFailError);
555 ALOGE("Previous vibration pending: prev: %d, curr: %d", mActiveId, effectIndex);
556 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
557 }
558
559 ALOGD("on");
560 mHwApi->setEffectIndex(effectIndex);
561 mHwApi->setDuration(timeoutMs);
562 mStatsApi->logLatencyEnd();
563 mHwApi->setActivate(1);
564 // Using the mToalDuration for composed effect.
565 // For composed effect, we set the UINT32_MAX to the duration sysfs node,
566 // but it not a practical way to use it to monitor the total duration time.
567 if (timeoutMs != UINT32_MAX) {
568 const std::scoped_lock<std::mutex> lock(mTotalDurationMutex);
569 mTotalDuration = timeoutMs;
570 }
571
572 mActiveId = effectIndex;
573
574 mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback);
575
576 return ndk::ScopedAStatus::ok();
577 }
578
setEffectAmplitude(float amplitude,float maximum)579 ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) {
580 HAPTICS_TRACE("setEffectAmplitude(amplitude:%f, maximum:%f)", amplitude, maximum);
581 int32_t scale = amplitudeToScale(amplitude, maximum);
582
583 if (!mHwApi->setEffectScale(scale)) {
584 mStatsApi->logError(kHwApiError);
585 ALOGE("Failed to set effect amplitude (%d): %s", errno, strerror(errno));
586 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
587 }
588
589 return ndk::ScopedAStatus::ok();
590 }
591
setGlobalAmplitude(bool set)592 ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) {
593 HAPTICS_TRACE("setGlobalAmplitude(set:%u)", set);
594 uint8_t amplitude = set ? mLongEffectVol[1] : VOLTAGE_SCALE_MAX;
595 int32_t scale = amplitudeToScale(amplitude, VOLTAGE_SCALE_MAX);
596
597 if (!mHwApi->setGlobalScale(scale)) {
598 mStatsApi->logError(kHwApiError);
599 ALOGE("Failed to set global amplitude (%d): %s", errno, strerror(errno));
600 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
601 }
602
603 return ndk::ScopedAStatus::ok();
604 }
605
getSupportedAlwaysOnEffects(std::vector<Effect> * _aidl_return)606 ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) {
607 HAPTICS_TRACE("getSupportedAlwaysOnEffects(_aidl_return)");
608 *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK};
609 return ndk::ScopedAStatus::ok();
610 }
611
alwaysOnEnable(int32_t id,Effect effect,EffectStrength strength)612 ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
613 HAPTICS_TRACE("alwaysOnEnable(id:%d, effect:%s, strength:%s)", id, toString(effect).c_str(),
614 toString(strength).c_str());
615 ndk::ScopedAStatus status;
616 uint32_t effectIndex;
617 uint32_t timeMs;
618 uint32_t volLevel;
619 uint32_t scale;
620
621 status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel);
622 if (!status.isOk()) {
623 return status;
624 }
625
626 scale = amplitudeToScale(volLevel, VOLTAGE_SCALE_MAX);
627
628 switch (static_cast<AlwaysOnId>(id)) {
629 case AlwaysOnId::GPIO_RISE:
630 mHwApi->setGpioRiseIndex(effectIndex);
631 mHwApi->setGpioRiseScale(scale);
632 return ndk::ScopedAStatus::ok();
633 case AlwaysOnId::GPIO_FALL:
634 mHwApi->setGpioFallIndex(effectIndex);
635 mHwApi->setGpioFallScale(scale);
636 return ndk::ScopedAStatus::ok();
637 }
638
639 mStatsApi->logError(kUnsupportedOpError);
640 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
641 }
642
alwaysOnDisable(int32_t id)643 ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t id) {
644 HAPTICS_TRACE("alwaysOnDisable(id: %d)", id);
645 switch (static_cast<AlwaysOnId>(id)) {
646 case AlwaysOnId::GPIO_RISE:
647 mHwApi->setGpioRiseIndex(0);
648 return ndk::ScopedAStatus::ok();
649 case AlwaysOnId::GPIO_FALL:
650 mHwApi->setGpioFallIndex(0);
651 return ndk::ScopedAStatus::ok();
652 }
653
654 mStatsApi->logError(kUnsupportedOpError);
655 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
656 }
657
getResonantFrequency(float * resonantFreqHz)658 ndk::ScopedAStatus Vibrator::getResonantFrequency(float *resonantFreqHz) {
659 HAPTICS_TRACE("getResonantFrequency(resonantFreqHz)");
660 *resonantFreqHz = mResonantFrequency;
661
662 return ndk::ScopedAStatus::ok();
663 }
664
getQFactor(float * qFactor)665 ndk::ScopedAStatus Vibrator::getQFactor(float *qFactor) {
666 HAPTICS_TRACE("getQFactor(qFactor)");
667 uint32_t caldata;
668 if (!mHwCal->getQ(&caldata)) {
669 mStatsApi->logError(kHwCalError);
670 ALOGE("Failed to get q factor (%d): %s", errno, strerror(errno));
671 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
672 }
673 *qFactor = static_cast<float>(caldata) / (1 << Q16_BIT_SHIFT);
674
675 return ndk::ScopedAStatus::ok();
676 }
677
getFrequencyResolution(float * freqResolutionHz)678 ndk::ScopedAStatus Vibrator::getFrequencyResolution(float *freqResolutionHz) {
679 HAPTICS_TRACE("getFrequencyResolution(freqResolutionHz)");
680 int32_t capabilities;
681 Vibrator::getCapabilities(&capabilities);
682 if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
683 *freqResolutionHz = PWLE_FREQUENCY_RESOLUTION_HZ;
684 return ndk::ScopedAStatus::ok();
685 } else {
686 mStatsApi->logError(kUnsupportedOpError);
687 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
688 }
689 }
690
getFrequencyMinimum(float * freqMinimumHz)691 ndk::ScopedAStatus Vibrator::getFrequencyMinimum(float *freqMinimumHz) {
692 HAPTICS_TRACE("getFrequencyMinimum(freqMinimumHz)");
693 int32_t capabilities;
694 Vibrator::getCapabilities(&capabilities);
695 if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
696 *freqMinimumHz = PWLE_FREQUENCY_MIN_HZ;
697 return ndk::ScopedAStatus::ok();
698 } else {
699 mStatsApi->logError(kUnsupportedOpError);
700 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
701 }
702 }
703
redcToFloat(uint32_t redcMeasured)704 static float redcToFloat(uint32_t redcMeasured) {
705 HAPTICS_TRACE("redcToFloat(redcMeasured: %u)", redcMeasured);
706 return redcMeasured * 5.857 / (1 << Q17_BIT_SHIFT);
707 }
708
generateBandwidthAmplitudeMap()709 std::vector<float> Vibrator::generateBandwidthAmplitudeMap() {
710 HAPTICS_TRACE("generateBandwidthAmplitudeMap()");
711 // Use constant Q Factor of 10 from HW's suggestion
712 const float qFactor = 10.0f;
713 const float blSys = 1.1f;
714 const float gravity = 9.81f;
715 const float maxVoltage = 12.3f;
716 float deviceMass = 0, locCoeff = 0;
717
718 mHwCal->getDeviceMass(&deviceMass);
719 mHwCal->getLocCoeff(&locCoeff);
720 if (!deviceMass || !locCoeff) {
721 mStatsApi->logError(kInitError);
722 ALOGE("Failed to get Device Mass: %f and Loc Coeff: %f", deviceMass, locCoeff);
723 return std::vector<float>();
724 }
725
726 // Resistance value need to be retrieved from calibration file
727 if (!mRedc) {
728 mStatsApi->logError(kInitError);
729 ALOGE("Failed to get redc");
730 return std::vector<float>();
731 }
732 const float rSys = redcToFloat(mRedc);
733
734 std::vector<float> bandwidthAmplitudeMap(PWLE_BW_MAP_SIZE, 1.0);
735
736 const float wnSys = mResonantFrequency * 2 * M_PI;
737
738 float frequencyHz = PWLE_FREQUENCY_MIN_HZ;
739 float frequencyRadians = 0.0f;
740 float vLevel = 0.4f;
741 float vSys = (mLongEffectVol[1] / 100.0) * maxVoltage * vLevel;
742 float maxAsys = 0;
743
744 for (int i = 0; i < PWLE_BW_MAP_SIZE; i++) {
745 frequencyRadians = frequencyHz * 2 * M_PI;
746 vLevel = pwleMaxLevelLimitMap[i];
747 vSys = (mLongEffectVol[1] / 100.0) * maxVoltage * vLevel;
748
749 float var1 = pow((pow(wnSys, 2) - pow(frequencyRadians, 2)), 2);
750 float var2 = pow((wnSys * frequencyRadians / qFactor), 2);
751
752 float psysAbs = sqrt(var1 + var2);
753 // The equation and all related details: b/170919640#comment5
754 float amplitudeSys = (vSys * blSys * locCoeff / rSys / deviceMass) *
755 pow(frequencyRadians, 2) / psysAbs / gravity;
756 // Record the maximum acceleration for the next for loop
757 if (amplitudeSys > maxAsys)
758 maxAsys = amplitudeSys;
759
760 bandwidthAmplitudeMap[i] = amplitudeSys;
761 frequencyHz += PWLE_FREQUENCY_RESOLUTION_HZ;
762 }
763 // Scaled the map between 0.00 and 1.00
764 if (maxAsys > 0) {
765 for (int j = 0; j < PWLE_BW_MAP_SIZE; j++) {
766 bandwidthAmplitudeMap[j] = std::floor((bandwidthAmplitudeMap[j] / maxAsys) * 100) / 100;
767 }
768 mGenerateBandwidthAmplitudeMapDone = true;
769 } else {
770 return std::vector<float>();
771 }
772
773 return bandwidthAmplitudeMap;
774 }
775
getBandwidthAmplitudeMap(std::vector<float> * _aidl_return)776 ndk::ScopedAStatus Vibrator::getBandwidthAmplitudeMap(std::vector<float> *_aidl_return) {
777 HAPTICS_TRACE("getBandwidthAmplitudeMap(_aidl_return)");
778 int32_t capabilities;
779 Vibrator::getCapabilities(&capabilities);
780 if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) {
781 if (!mGenerateBandwidthAmplitudeMapDone) {
782 mBandwidthAmplitudeMap = generateBandwidthAmplitudeMap();
783 }
784 *_aidl_return = mBandwidthAmplitudeMap;
785 return (!mBandwidthAmplitudeMap.empty())
786 ? ndk::ScopedAStatus::ok()
787 : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
788 } else {
789 mStatsApi->logError(kUnsupportedOpError);
790 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
791 }
792 }
793
getPwlePrimitiveDurationMax(int32_t * durationMs)794 ndk::ScopedAStatus Vibrator::getPwlePrimitiveDurationMax(int32_t *durationMs) {
795 HAPTICS_TRACE("getPwlePrimitiveDurationMax(durationMs)");
796 int32_t capabilities;
797 Vibrator::getCapabilities(&capabilities);
798 if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) {
799 *durationMs = COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS;
800 return ndk::ScopedAStatus::ok();
801 } else {
802 mStatsApi->logError(kUnsupportedOpError);
803 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
804 }
805 }
806
getPwleCompositionSizeMax(int32_t * maxSize)807 ndk::ScopedAStatus Vibrator::getPwleCompositionSizeMax(int32_t *maxSize) {
808 HAPTICS_TRACE("getPwleCompositionSizeMax(maxSize)");
809 int32_t capabilities;
810 Vibrator::getCapabilities(&capabilities);
811 if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) {
812 uint32_t segments;
813 if (!mHwApi->getAvailablePwleSegments(&segments)) {
814 mStatsApi->logError(kHwApiError);
815 ALOGE("Failed to get availablePwleSegments (%d): %s", errno, strerror(errno));
816 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
817 }
818 *maxSize = (segments > COMPOSE_PWLE_SIZE_LIMIT) ? COMPOSE_PWLE_SIZE_LIMIT : segments;
819 mCompositionSizeMax = *maxSize;
820 return ndk::ScopedAStatus::ok();
821 } else {
822 mStatsApi->logError(kUnsupportedOpError);
823 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
824 }
825 }
826
getSupportedBraking(std::vector<Braking> * supported)827 ndk::ScopedAStatus Vibrator::getSupportedBraking(std::vector<Braking> *supported) {
828 HAPTICS_TRACE("getSupportedBraking(supported)");
829 int32_t capabilities;
830 Vibrator::getCapabilities(&capabilities);
831 if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) {
832 *supported = {
833 Braking::NONE,
834 Braking::CLAB,
835 };
836 return ndk::ScopedAStatus::ok();
837 } else {
838 mStatsApi->logError(kUnsupportedOpError);
839 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
840 }
841 }
842
setPwle(const std::string & pwleQueue)843 ndk::ScopedAStatus Vibrator::setPwle(const std::string &pwleQueue) {
844 HAPTICS_TRACE("setPwle(pwleQueue:%s)", pwleQueue.c_str());
845 if (!mHwApi->setPwle(pwleQueue)) {
846 mStatsApi->logError(kHwApiError);
847 ALOGE("Failed to write \"%s\" to pwle (%d): %s", pwleQueue.c_str(), errno, strerror(errno));
848 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
849 }
850
851 return ndk::ScopedAStatus::ok();
852 }
853
incrementIndex(int * index)854 static void incrementIndex(int *index) {
855 *index += 1;
856 }
857
constructActiveDefaults(std::ostringstream & pwleBuilder,const int & segmentIdx)858 static void constructActiveDefaults(std::ostringstream &pwleBuilder, const int &segmentIdx) {
859 HAPTICS_TRACE("constructActiveDefaults(pwleBuilder, segmentIdx:%d)", segmentIdx);
860 pwleBuilder << ",C" << segmentIdx << ":1";
861 pwleBuilder << ",B" << segmentIdx << ":0";
862 pwleBuilder << ",AR" << segmentIdx << ":0";
863 pwleBuilder << ",V" << segmentIdx << ":0";
864 }
865
constructActiveSegment(std::ostringstream & pwleBuilder,const int & segmentIdx,int duration,float amplitude,float frequency)866 static void constructActiveSegment(std::ostringstream &pwleBuilder, const int &segmentIdx,
867 int duration, float amplitude, float frequency) {
868 HAPTICS_TRACE(
869 "constructActiveSegment(pwleBuilder, segmentIdx:%d, duration:%d, amplitude:%f, "
870 "frequency:%f)",
871 segmentIdx, duration, amplitude, frequency);
872 pwleBuilder << ",T" << segmentIdx << ":" << duration;
873 pwleBuilder << ",L" << segmentIdx << ":" << std::setprecision(1) << amplitude;
874 pwleBuilder << ",F" << segmentIdx << ":" << std::lroundf(frequency);
875 constructActiveDefaults(pwleBuilder, segmentIdx);
876 }
877
constructBrakingSegment(std::ostringstream & pwleBuilder,const int & segmentIdx,int duration,Braking brakingType,float frequency)878 static void constructBrakingSegment(std::ostringstream &pwleBuilder, const int &segmentIdx,
879 int duration, Braking brakingType, float frequency) {
880 HAPTICS_TRACE(
881 "constructActiveSegment(pwleBuilder, segmentIdx:%d, duration:%d, brakingType:%s, "
882 "frequency:%f)",
883 segmentIdx, duration, toString(brakingType).c_str(), frequency);
884 pwleBuilder << ",T" << segmentIdx << ":" << duration;
885 pwleBuilder << ",L" << segmentIdx << ":" << 0;
886 pwleBuilder << ",F" << segmentIdx << ":" << std::lroundf(frequency);
887 pwleBuilder << ",C" << segmentIdx << ":0";
888 pwleBuilder << ",B" << segmentIdx << ":"
889 << static_cast<std::underlying_type<Braking>::type>(brakingType);
890 pwleBuilder << ",AR" << segmentIdx << ":0";
891 pwleBuilder << ",V" << segmentIdx << ":0";
892 }
893
composePwle(const std::vector<PrimitivePwle> & composite,const std::shared_ptr<IVibratorCallback> & callback)894 ndk::ScopedAStatus Vibrator::composePwle(const std::vector<PrimitivePwle> &composite,
895 const std::shared_ptr<IVibratorCallback> &callback) {
896 HAPTICS_TRACE("composePwle(composite, callback)");
897 ATRACE_NAME("Vibrator::composePwle");
898 std::ostringstream pwleBuilder;
899 std::string pwleQueue;
900
901 mStatsApi->logLatencyStart(kPwleEffectLatency);
902
903 if (!mIsChirpEnabled) {
904 mStatsApi->logError(kUnsupportedOpError);
905 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
906 }
907
908 if (composite.size() <= 0 || composite.size() > mCompositionSizeMax) {
909 mStatsApi->logError(kBadCompositeError);
910 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
911 }
912
913 float prevEndAmplitude = 0;
914 float prevEndFrequency = mResonantFrequency;
915
916 int segmentIdx = 0;
917 uint32_t totalDuration = 0;
918
919 pwleBuilder << "S:0,WF:4,RP:0,WT:0";
920
921 for (auto &e : composite) {
922 switch (e.getTag()) {
923 case PrimitivePwle::active: {
924 auto active = e.get<PrimitivePwle::active>();
925 if (active.duration < 0 ||
926 active.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) {
927 mStatsApi->logError(kBadCompositeError);
928 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
929 }
930 if (active.startAmplitude < PWLE_LEVEL_MIN ||
931 active.startAmplitude > PWLE_LEVEL_MAX ||
932 active.endAmplitude < PWLE_LEVEL_MIN || active.endAmplitude > PWLE_LEVEL_MAX) {
933 mStatsApi->logError(kBadCompositeError);
934 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
935 }
936 if (active.startAmplitude > CS40L2X_PWLE_LEVEL_MAX) {
937 active.startAmplitude = CS40L2X_PWLE_LEVEL_MAX;
938 }
939 if (active.endAmplitude > CS40L2X_PWLE_LEVEL_MAX) {
940 active.endAmplitude = CS40L2X_PWLE_LEVEL_MAX;
941 }
942
943 if (active.startFrequency < PWLE_FREQUENCY_MIN_HZ ||
944 active.startFrequency > PWLE_FREQUENCY_MAX_HZ ||
945 active.endFrequency < PWLE_FREQUENCY_MIN_HZ ||
946 active.endFrequency > PWLE_FREQUENCY_MAX_HZ) {
947 mStatsApi->logError(kBadCompositeError);
948 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
949 }
950
951 // clip to the hard limit on input level from pwleMaxLevelLimitMap
952 float maxLevelLimit =
953 pwleMaxLevelLimitMap[active.startFrequency / PWLE_FREQUENCY_RESOLUTION_HZ - 1];
954 if (active.startAmplitude > maxLevelLimit) {
955 active.startAmplitude = maxLevelLimit;
956 }
957 maxLevelLimit =
958 pwleMaxLevelLimitMap[active.endFrequency / PWLE_FREQUENCY_RESOLUTION_HZ - 1];
959 if (active.endAmplitude > maxLevelLimit) {
960 active.endAmplitude = maxLevelLimit;
961 }
962
963 if (!((active.startAmplitude == prevEndAmplitude) &&
964 (active.startFrequency == prevEndFrequency))) {
965 constructActiveSegment(pwleBuilder, segmentIdx, 0, active.startAmplitude,
966 active.startFrequency);
967 incrementIndex(&segmentIdx);
968 }
969
970 constructActiveSegment(pwleBuilder, segmentIdx, active.duration,
971 active.endAmplitude, active.endFrequency);
972 incrementIndex(&segmentIdx);
973
974 prevEndAmplitude = active.endAmplitude;
975 prevEndFrequency = active.endFrequency;
976 totalDuration += active.duration;
977 break;
978 }
979 case PrimitivePwle::braking: {
980 auto braking = e.get<PrimitivePwle::braking>();
981 if (braking.braking > Braking::CLAB) {
982 mStatsApi->logError(kBadPrimitiveError);
983 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
984 }
985 if (braking.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) {
986 mStatsApi->logError(kBadPrimitiveError);
987 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
988 }
989
990 constructBrakingSegment(pwleBuilder, segmentIdx, braking.duration, braking.braking,
991 prevEndFrequency);
992 incrementIndex(&segmentIdx);
993
994 prevEndAmplitude = 0;
995 totalDuration += braking.duration;
996 break;
997 }
998 }
999 }
1000
1001 pwleQueue = pwleBuilder.str();
1002 ALOGD("composePwle queue: (%s)", pwleQueue.c_str());
1003
1004 if (pwleQueue.size() > CS40L2X_PWLE_LENGTH_MAX) {
1005 ALOGE("PWLE string too large(%u)", static_cast<uint32_t>(pwleQueue.size()));
1006 mStatsApi->logError(kPwleConstructionFailError);
1007 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
1008 } else {
1009 ALOGD("PWLE string : %u", static_cast<uint32_t>(pwleQueue.size()));
1010 ndk::ScopedAStatus status = setPwle(pwleQueue);
1011 if (!status.isOk()) {
1012 mStatsApi->logError(kPwleConstructionFailError);
1013 ALOGE("Failed to write pwle queue");
1014 return status;
1015 }
1016 }
1017 setEffectAmplitude(VOLTAGE_SCALE_MAX, VOLTAGE_SCALE_MAX);
1018 mHwApi->setEffectIndex(WAVEFORM_UNSAVED_TRIGGER_QUEUE_INDEX);
1019
1020 totalDuration += MAX_COLD_START_LATENCY_MS;
1021 mHwApi->setDuration(totalDuration);
1022 {
1023 const std::scoped_lock<std::mutex> lock(mTotalDurationMutex);
1024 mTotalDuration = totalDuration;
1025 }
1026
1027 mStatsApi->logLatencyEnd();
1028 mHwApi->setActivate(1);
1029
1030 mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback);
1031
1032 return ndk::ScopedAStatus::ok();
1033 }
1034
isUnderExternalControl()1035 bool Vibrator::isUnderExternalControl() {
1036 HAPTICS_TRACE("isUnderExternalControl()");
1037 return mIsUnderExternalControl;
1038 }
1039
dump(int fd,const char ** args,uint32_t numArgs)1040 binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) {
1041 HAPTICS_TRACE("dump(fd:%d, args, numArgs:%u)", fd, numArgs);
1042 if (fd < 0) {
1043 ALOGE("Called debug() with invalid fd.");
1044 return STATUS_OK;
1045 }
1046
1047 (void)args;
1048 (void)numArgs;
1049
1050 dprintf(fd, "AIDL:\n");
1051
1052 dprintf(fd, " F0 Offset: %" PRIu32 "\n", mF0Offset);
1053
1054 dprintf(fd, " Voltage Levels:\n");
1055 dprintf(fd, " Tick Effect Min: %" PRIu32 " Max: %" PRIu32 "\n",
1056 mTickEffectVol[0], mTickEffectVol[1]);
1057 dprintf(fd, " Click Effect Min: %" PRIu32 " Max: %" PRIu32 "\n",
1058 mClickEffectVol[0], mClickEffectVol[1]);
1059 dprintf(fd, " Long Effect Min: %" PRIu32 " Max: %" PRIu32 "\n",
1060 mLongEffectVol[0], mLongEffectVol[1]);
1061
1062 dprintf(fd, " Effect Durations:");
1063 for (auto d : mEffectDurations) {
1064 dprintf(fd, " %" PRIu32, d);
1065 }
1066 dprintf(fd, "\n");
1067
1068 dprintf(fd, "\n");
1069
1070 mHwApi->debug(fd);
1071
1072 dprintf(fd, "\n");
1073
1074 mHwCal->debug(fd);
1075
1076 dprintf(fd, "\n");
1077
1078 mStatsApi->debug(fd);
1079
1080 fsync(fd);
1081 return STATUS_OK;
1082 }
1083
getSimpleDetails(Effect effect,EffectStrength strength,uint32_t * outEffectIndex,uint32_t * outTimeMs,uint32_t * outVolLevel)1084 ndk::ScopedAStatus Vibrator::getSimpleDetails(Effect effect, EffectStrength strength,
1085 uint32_t *outEffectIndex, uint32_t *outTimeMs,
1086 uint32_t *outVolLevel) {
1087 HAPTICS_TRACE(
1088 "getSimpleDetails(effect:%s, strength:%s, outEffectIndex, outTimeMs"
1089 ", outVolLevel)",
1090 toString(effect).c_str(), toString(strength).c_str());
1091 uint32_t effectIndex;
1092 uint32_t timeMs;
1093 float intensity;
1094 uint32_t volLevel;
1095
1096 switch (strength) {
1097 case EffectStrength::LIGHT:
1098 intensity = 0.5f;
1099 break;
1100 case EffectStrength::MEDIUM:
1101 intensity = 0.7f;
1102 break;
1103 case EffectStrength::STRONG:
1104 intensity = 1.0f;
1105 break;
1106 default:
1107 mStatsApi->logError(kUnsupportedOpError);
1108 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
1109 }
1110
1111 switch (effect) {
1112 case Effect::TEXTURE_TICK:
1113 effectIndex = WAVEFORM_LIGHT_TICK_INDEX;
1114 intensity *= 0.5f;
1115 break;
1116 case Effect::TICK:
1117 effectIndex = WAVEFORM_CLICK_INDEX;
1118 intensity *= 0.5f;
1119 break;
1120 case Effect::CLICK:
1121 effectIndex = WAVEFORM_CLICK_INDEX;
1122 intensity *= 0.7f;
1123 break;
1124 case Effect::HEAVY_CLICK:
1125 effectIndex = WAVEFORM_CLICK_INDEX;
1126 intensity *= 1.0f;
1127 break;
1128 default:
1129 mStatsApi->logError(kUnsupportedOpError);
1130 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
1131 }
1132
1133 volLevel = intensityToVolLevel(intensity, effectIndex);
1134 timeMs = mEffectDurations[effectIndex] + MAX_COLD_START_LATENCY_MS;
1135 {
1136 const std::scoped_lock<std::mutex> lock(mTotalDurationMutex);
1137 mTotalDuration = timeMs;
1138 }
1139
1140 *outEffectIndex = effectIndex;
1141 *outTimeMs = timeMs;
1142 *outVolLevel = volLevel;
1143
1144 return ndk::ScopedAStatus::ok();
1145 }
1146
getCompoundDetails(Effect effect,EffectStrength strength,uint32_t * outTimeMs,uint32_t *,std::string * outEffectQueue)1147 ndk::ScopedAStatus Vibrator::getCompoundDetails(Effect effect, EffectStrength strength,
1148 uint32_t *outTimeMs, uint32_t * /*outVolLevel*/,
1149 std::string *outEffectQueue) {
1150 HAPTICS_TRACE(
1151 "getCompoundDetails(effect:%s, strength:%s, outTimeMs, outVolLevel, outEffectQueue)",
1152 toString(effect).c_str(), toString(strength).c_str());
1153 ndk::ScopedAStatus status;
1154 uint32_t timeMs;
1155 std::ostringstream effectBuilder;
1156 uint32_t thisEffectIndex;
1157 uint32_t thisTimeMs;
1158 uint32_t thisVolLevel;
1159
1160 switch (effect) {
1161 case Effect::DOUBLE_CLICK:
1162 timeMs = 0;
1163
1164 status = getSimpleDetails(Effect::CLICK, strength, &thisEffectIndex, &thisTimeMs,
1165 &thisVolLevel);
1166 if (!status.isOk()) {
1167 return status;
1168 }
1169 effectBuilder << thisEffectIndex << "." << thisVolLevel;
1170 timeMs += thisTimeMs;
1171
1172 effectBuilder << ",";
1173
1174 effectBuilder << WAVEFORM_DOUBLE_CLICK_SILENCE_MS;
1175 timeMs += WAVEFORM_DOUBLE_CLICK_SILENCE_MS + MAX_PAUSE_TIMING_ERROR_MS;
1176
1177 effectBuilder << ",";
1178
1179 status = getSimpleDetails(Effect::HEAVY_CLICK, strength, &thisEffectIndex, &thisTimeMs,
1180 &thisVolLevel);
1181 if (!status.isOk()) {
1182 return status;
1183 }
1184 effectBuilder << thisEffectIndex << "." << thisVolLevel;
1185 timeMs += thisTimeMs;
1186 {
1187 const std::scoped_lock<std::mutex> lock(mTotalDurationMutex);
1188 mTotalDuration = timeMs;
1189 }
1190
1191 break;
1192 default:
1193 mStatsApi->logError(kUnsupportedOpError);
1194 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
1195 }
1196
1197 *outTimeMs = timeMs;
1198 *outEffectQueue = effectBuilder.str();
1199
1200 return ndk::ScopedAStatus::ok();
1201 }
1202
getPrimitiveDetails(CompositePrimitive primitive,uint32_t * outEffectIndex)1203 ndk::ScopedAStatus Vibrator::getPrimitiveDetails(CompositePrimitive primitive,
1204 uint32_t *outEffectIndex) {
1205 HAPTICS_TRACE("getPrimitiveDetails(primitive:%s, outEffectIndex)", toString(primitive).c_str());
1206 uint32_t effectIndex;
1207
1208 switch (primitive) {
1209 case CompositePrimitive::NOOP:
1210 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
1211 case CompositePrimitive::CLICK:
1212 effectIndex = WAVEFORM_CLICK_INDEX;
1213 break;
1214 case CompositePrimitive::THUD:
1215 effectIndex = WAVEFORM_THUD_INDEX;
1216 break;
1217 case CompositePrimitive::SPIN:
1218 effectIndex = WAVEFORM_SPIN_INDEX;
1219 break;
1220 case CompositePrimitive::QUICK_RISE:
1221 effectIndex = WAVEFORM_QUICK_RISE_INDEX;
1222 break;
1223 case CompositePrimitive::SLOW_RISE:
1224 effectIndex = WAVEFORM_SLOW_RISE_INDEX;
1225 break;
1226 case CompositePrimitive::QUICK_FALL:
1227 effectIndex = WAVEFORM_QUICK_FALL_INDEX;
1228 break;
1229 case CompositePrimitive::LIGHT_TICK:
1230 effectIndex = WAVEFORM_LIGHT_TICK_INDEX;
1231 break;
1232 case CompositePrimitive::LOW_TICK:
1233 effectIndex = WAVEFORM_LOW_TICK_INDEX;
1234 break;
1235 default:
1236 mStatsApi->logError(kUnsupportedOpError);
1237 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
1238 }
1239
1240 *outEffectIndex = effectIndex;
1241
1242 return ndk::ScopedAStatus::ok();
1243 }
1244
setEffectQueue(const std::string & effectQueue)1245 ndk::ScopedAStatus Vibrator::setEffectQueue(const std::string &effectQueue) {
1246 HAPTICS_TRACE("setEffectQueue(effectQueue:%s)", effectQueue.c_str());
1247 if (!mHwApi->setEffectQueue(effectQueue)) {
1248 ALOGE("Failed to write \"%s\" to effect queue (%d): %s", effectQueue.c_str(), errno,
1249 strerror(errno));
1250 mStatsApi->logError(kHwApiError);
1251 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
1252 }
1253
1254 return ndk::ScopedAStatus::ok();
1255 }
1256
performEffect(Effect effect,EffectStrength strength,const std::shared_ptr<IVibratorCallback> & callback,int32_t * outTimeMs)1257 ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength,
1258 const std::shared_ptr<IVibratorCallback> &callback,
1259 int32_t *outTimeMs) {
1260 HAPTICS_TRACE("performEffect(effect:%s, strength:%s, callback, outTimeMs)",
1261 toString(effect).c_str(), toString(strength).c_str());
1262 ndk::ScopedAStatus status;
1263 uint32_t effectIndex;
1264 uint32_t timeMs = 0;
1265 uint32_t volLevel;
1266 std::string effectQueue;
1267
1268 switch (effect) {
1269 case Effect::TEXTURE_TICK:
1270 // fall-through
1271 case Effect::TICK:
1272 // fall-through
1273 case Effect::CLICK:
1274 // fall-through
1275 case Effect::HEAVY_CLICK:
1276 status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel);
1277 break;
1278 case Effect::DOUBLE_CLICK:
1279 status = getCompoundDetails(effect, strength, &timeMs, &volLevel, &effectQueue);
1280 break;
1281 default:
1282 mStatsApi->logError(kUnsupportedOpError);
1283 status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
1284 break;
1285 }
1286 if (!status.isOk()) {
1287 goto exit;
1288 }
1289
1290 status = performEffect(effectIndex, volLevel, &effectQueue, callback);
1291
1292 exit:
1293
1294 *outTimeMs = timeMs;
1295 return status;
1296 }
1297
performEffect(uint32_t effectIndex,uint32_t volLevel,const std::string * effectQueue,const std::shared_ptr<IVibratorCallback> & callback)1298 ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel,
1299 const std::string *effectQueue,
1300 const std::shared_ptr<IVibratorCallback> &callback) {
1301 HAPTICS_TRACE("performEffect(effectIndex:%u, volLevel:%u, effectQueue:%s, callback)",
1302 effectIndex, volLevel, effectQueue->c_str());
1303 if (effectQueue && !effectQueue->empty()) {
1304 ndk::ScopedAStatus status = setEffectQueue(*effectQueue);
1305 if (!status.isOk()) {
1306 return status;
1307 }
1308 setEffectAmplitude(VOLTAGE_SCALE_MAX, VOLTAGE_SCALE_MAX);
1309 effectIndex = WAVEFORM_TRIGGER_QUEUE_INDEX;
1310 } else {
1311 setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX);
1312 }
1313
1314 return on(MAX_TIME_MS, effectIndex, callback);
1315 }
1316
waitForComplete(std::shared_ptr<IVibratorCallback> && callback)1317 void Vibrator::waitForComplete(std::shared_ptr<IVibratorCallback> &&callback) {
1318 HAPTICS_TRACE("waitForComplete(callback)");
1319 ALOGD("waitForComplete");
1320 uint32_t duration;
1321 {
1322 const std::scoped_lock<std::mutex> lock(mTotalDurationMutex);
1323 duration = ((mTotalDuration + POLLING_TIMEOUT) < UINT32_MAX)
1324 ? mTotalDuration + POLLING_TIMEOUT
1325 : UINT32_MAX;
1326 }
1327 if (!mHwApi->pollVibeState(false, duration)) {
1328 ALOGE("Timeout(%u)! Fail to poll STOP state", duration);
1329 } else {
1330 ALOGD("waitForComplete: Get STOP! Set active to 0.");
1331 }
1332 mHwApi->setActivate(false);
1333
1334 if (callback) {
1335 auto ret = callback->onComplete();
1336 if (!ret.isOk()) {
1337 mStatsApi->logError(kAsyncFailError);
1338 ALOGE("Failed completion callback: %d", ret.getExceptionCode());
1339 }
1340 }
1341 }
1342
intensityToVolLevel(float intensity,uint32_t effectIndex)1343 uint32_t Vibrator::intensityToVolLevel(float intensity, uint32_t effectIndex) {
1344 HAPTICS_TRACE("intensityToVolLevel(intensity:%f, effectIndex:%u)", intensity, effectIndex);
1345 uint32_t volLevel;
1346 auto calc = [](float intst, std::array<uint32_t, 2> v) -> uint32_t {
1347 return std::lround(intst * (v[1] - v[0])) + v[0]; };
1348
1349 switch (effectIndex) {
1350 case WAVEFORM_LIGHT_TICK_INDEX:
1351 volLevel = calc(intensity, mTickEffectVol);
1352 break;
1353 case WAVEFORM_QUICK_RISE_INDEX:
1354 // fall-through
1355 case WAVEFORM_QUICK_FALL_INDEX:
1356 volLevel = calc(intensity, mLongEffectVol);
1357 break;
1358 case WAVEFORM_CLICK_INDEX:
1359 // fall-through
1360 case WAVEFORM_THUD_INDEX:
1361 // fall-through
1362 case WAVEFORM_SPIN_INDEX:
1363 // fall-through
1364 case WAVEFORM_SLOW_RISE_INDEX:
1365 // fall-through
1366 default:
1367 volLevel = calc(intensity, mClickEffectVol);
1368 break;
1369 }
1370
1371 return volLevel;
1372 }
1373
findHapticAlsaDevice(int * card,int * device)1374 bool Vibrator::findHapticAlsaDevice(int *card, int *device) {
1375 HAPTICS_TRACE("findHapticAlsaDevice(card, device)");
1376 std::string line;
1377 std::ifstream myfile(PROC_SND_PCM);
1378 if (myfile.is_open()) {
1379 while (getline(myfile, line)) {
1380 if (line.find(HAPTIC_PCM_DEVICE_SYMBOL) != std::string::npos) {
1381 std::stringstream ss(line);
1382 std::string currentToken;
1383 std::getline(ss, currentToken, ':');
1384 sscanf(currentToken.c_str(), "%d-%d", card, device);
1385 return true;
1386 }
1387 }
1388 myfile.close();
1389 } else {
1390 mStatsApi->logError(kAlsaFailError);
1391 ALOGE("Failed to read file: %s", PROC_SND_PCM);
1392 }
1393 return false;
1394 }
1395
hasHapticAlsaDevice()1396 bool Vibrator::hasHapticAlsaDevice() {
1397 HAPTICS_TRACE("hasHapticAlsaDevice()");
1398 // We need to call findHapticAlsaDevice once only. Calling in the
1399 // constructor is too early in the boot process and the pcm file contents
1400 // are empty. Hence we make the call here once only right before we need to.
1401 static bool configHapticAlsaDeviceDone = false;
1402 if (!configHapticAlsaDeviceDone) {
1403 if (findHapticAlsaDevice(&mCard, &mDevice)) {
1404 mHasHapticAlsaDevice = true;
1405 configHapticAlsaDeviceDone = true;
1406 } else {
1407 mStatsApi->logError(kAlsaFailError);
1408 ALOGE("Haptic ALSA device not supported");
1409 }
1410 }
1411 return mHasHapticAlsaDevice;
1412 }
1413
enableHapticPcmAmp(struct pcm ** haptic_pcm,bool enable,int card,int device)1414 bool Vibrator::enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) {
1415 HAPTICS_TRACE("enableHapticPcmAmp(pcm, enable:%u, card:%d, device:%d)", enable, card, device);
1416 int ret = 0;
1417
1418 if (enable) {
1419 *haptic_pcm = pcm_open(card, device, PCM_OUT, &haptic_nohost_config);
1420 if (!pcm_is_ready(*haptic_pcm)) {
1421 ALOGE("cannot open pcm_out driver: %s", pcm_get_error(*haptic_pcm));
1422 goto fail;
1423 }
1424
1425 ret = pcm_prepare(*haptic_pcm);
1426 if (ret < 0) {
1427 ALOGE("cannot prepare haptic_pcm: %s", pcm_get_error(*haptic_pcm));
1428 goto fail;
1429 }
1430
1431 ret = pcm_start(*haptic_pcm);
1432 if (ret < 0) {
1433 ALOGE("cannot start haptic_pcm: %s", pcm_get_error(*haptic_pcm));
1434 goto fail;
1435 }
1436
1437 return true;
1438 } else {
1439 if (*haptic_pcm) {
1440 pcm_close(*haptic_pcm);
1441 *haptic_pcm = NULL;
1442 }
1443 return true;
1444 }
1445
1446 fail:
1447 pcm_close(*haptic_pcm);
1448 *haptic_pcm = NULL;
1449 return false;
1450 }
1451
setPwleRampDown()1452 void Vibrator::setPwleRampDown() {
1453 HAPTICS_TRACE("setPwleRampDown()");
1454 // The formula for calculating the ramp down coefficient to be written into
1455 // pwle_ramp_down is as follows:
1456 // Crd = 1048.576 / Trd
1457 // where Trd is the desired ramp down time in seconds
1458 // pwle_ramp_down accepts only 24 bit integers values
1459
1460 if (RAMP_DOWN_TIME_MS != 0.0) {
1461 const float seconds = RAMP_DOWN_TIME_MS / 1000;
1462 const auto ramp_down_coefficient = static_cast<uint32_t>(RAMP_DOWN_CONSTANT / seconds);
1463 if (!mHwApi->setPwleRampDown(ramp_down_coefficient)) {
1464 mStatsApi->logError(kHwApiError);
1465 ALOGE("Failed to write \"%d\" to pwle_ramp_down (%d): %s", ramp_down_coefficient, errno,
1466 strerror(errno));
1467 }
1468 } else {
1469 // Turn off the low level PWLE Ramp Down feature
1470 if (!mHwApi->setPwleRampDown(0)) {
1471 mStatsApi->logError(kHwApiError);
1472 ALOGE("Failed to write 0 to pwle_ramp_down (%d): %s", errno, strerror(errno));
1473 }
1474 }
1475 }
1476
1477 } // namespace vibrator
1478 } // namespace hardware
1479 } // namespace android
1480 } // namespace aidl
1481