diff --git a/hidl/power/Android.mk b/hidl/power/Android.mk new file mode 100644 index 00000000..6e5889bf --- /dev/null +++ b/hidl/power/Android.mk @@ -0,0 +1,47 @@ +# +# Copyright (C) 2020 The LineageOS 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + Power.cpp \ + service.cpp + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + hardware/samsung/hidl/light/include + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libhidlbase \ + libhidltransport \ + libutils \ + android.hardware.power@1.0 \ + vendor.lineage.power@1.0 + +LOCAL_STATIC_LIBRARIES := libc++fs + +LOCAL_MODULE := android.hardware.power@1.0-service.exynos +LOCAL_INIT_RC := android.hardware.power@1.0-service.exynos.rc +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_OWNER := samsung +LOCAL_VENDOR_MODULE := true + +include $(BUILD_EXECUTABLE) diff --git a/hidl/power/Power.cpp b/hidl/power/Power.cpp new file mode 100644 index 00000000..721dbda2 --- /dev/null +++ b/hidl/power/Power.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2020 The LineageOS 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. + */ + +#define LOG_TAG "android.hardware.power@1.0-service.exynos" + +#include "Power.h" +#include +#include +#include +#include +#include "samsung_lights.h" +#include "samsung_power.h" + +namespace android { +namespace hardware { +namespace power { +namespace V1_0 { +namespace implementation { + +/* + * Write value to path and close file. + */ +template +static void set(const std::string& path, const T& value) { + std::ofstream file(path); + file << value << std::endl; +} + +template +static T get(const std::string& path, const T& def) { + std::ifstream file(path); + T result; + + file >> result; + return file.fail() ? def : result; +} + +Return Power::setInteractive(bool interactive) { + if (!initialized) { + initialize(); + } + + if (!interactive) { + int32_t panel_brightness = get(PANEL_BRIGHTNESS_NODE, -1); + + if (panel_brightness > 0) { + LOG(VERBOSE) << "Moving to non-interactive state, but screen is still on," + << "not disabling input devices"; + return Void(); + } + } + + if (!sec_touchscreen.empty()) { + set(sec_touchscreen, interactive ? "1" : "0"); + } + + if (!sec_touchkey.empty()) { + if (!interactive) { + int button_state = get(sec_touchkey, -1); + + if (button_state < 0) { + LOG(ERROR) << "Failed to read touchkey state"; + goto out; + } + + /* + * If button_state is 0, the keys have been disabled by another component + * (for example lineagehw), which means we don't want them to be enabled when resuming + * from suspend. + */ + if (button_state == 0) { + touchkeys_blocked = true; + } + } + + if (!touchkeys_blocked) { + set(sec_touchkey, interactive ? "1" : "0"); + } + } + +out: + for (const std::string& interactivePath : cpuInteractivePaths) { + set(interactivePath + "/io_is_busy", interactive ? "1" : "0"); + } + + return Void(); +} + +Return Power::powerHint(PowerHint hint, int32_t data) { + if (!initialized) { + initialize(); + } + + /* Bail out if low-power mode is active */ + if (current_profile == PowerProfile::POWER_SAVE && hint != PowerHint::LOW_POWER && + hint != static_cast(LineagePowerHint::SET_PROFILE)) { + LOG(VERBOSE) << "PROFILE_POWER_SAVE active, ignoring hint " << static_cast(hint); + return Void(); + } + + switch (hint) { + case PowerHint::INTERACTION: + case PowerHint::LAUNCH: + sendBoostpulse(); + break; + case PowerHint::LOW_POWER: + setProfile(data ? PowerProfile::POWER_SAVE : PowerProfile::BALANCED); + break; + default: + if (hint == static_cast(LineagePowerHint::SET_PROFILE)) { + setProfile(static_cast(data)); + } else if (hint == static_cast(LineagePowerHint::CPU_BOOST)) { + sendBoost(data); + } else { + LOG(INFO) << "Unknown power hint: " << static_cast(hint); + } + break; + } + return Void(); +} + +Return Power::setFeature(Feature feature __unused, bool activate __unused) { + if (!initialized) { + initialize(); + } + +#ifdef TAP_TO_WAKE_NODE + if (feature == Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE) { + set(TAP_TO_WAKE_NODE, activate ? "1" : "0"); + } +#endif + + return Void(); +} + +Return Power::getPlatformLowPowerStats(getPlatformLowPowerStats_cb _hidl_cb) { + _hidl_cb({}, Status::SUCCESS); + return Void(); +} + +Return Power::getFeature(LineageFeature feature) { + switch (feature) { + case LineageFeature::SUPPORTED_PROFILES: + return static_cast(PowerProfile::MAX); + default: + return -1; + } +} + +void Power::initialize() { + findInputNodes(); + + current_profile = PowerProfile::BALANCED; + + for (const std::string& interactivePath : cpuInteractivePaths) { + hispeed_freqs.emplace_back(get(interactivePath + "/hispeed_freq", "")); + } + + for (const std::string& sysfsPath : cpuSysfsPaths) { + max_freqs.emplace_back(get(sysfsPath + "/cpufreq/scaling_max_freq", "")); + } + + initialized = true; +} + +void Power::findInputNodes() { + std::error_code ec; + for (auto& de : std::filesystem::directory_iterator("/sys/class/input/", ec)) { + /* we are only interested in the input devices that we can access */ + if (ec || de.path().string().find("/sys/class/input/input") == std::string::npos) { + continue; + } + + for (auto& de2 : std::filesystem::directory_iterator(de.path(), ec)) { + if (!ec && de2.path().string().find("/name") != std::string::npos) { + std::string content = get(de2.path(), ""); + if (content == "sec_touchkey") { + sec_touchkey = de.path().string().append("/enabled"); + LOG(INFO) << "found sec_touchkey: " << sec_touchkey; + } else if (content == "sec_touchscreen") { + sec_touchscreen = de.path().string().append("/enabled"); + LOG(INFO) << "found sec_touchscreen: " << sec_touchscreen; + } + } + } + } +} + +void Power::setProfile(PowerProfile profile) { + if (current_profile == profile) { + return; + } + + switch (profile) { + case PowerProfile::POWER_SAVE: + // Limit to hispeed freq + for (int i = 0; i < cpuSysfsPaths.size(); i++) { + if (hispeed_freqs.size() > i && !hispeed_freqs.at(i).empty()) { + set(cpuSysfsPaths.at(i) + "/cpufreq/scaling_max_freq", hispeed_freqs.at(i)); + } + } + break; + case PowerProfile::BALANCED: + case PowerProfile::HIGH_PERFORMANCE: + // Restore normal max freq + for (int i = 0; i < cpuSysfsPaths.size(); i++) { + if (max_freqs.size() > i && !max_freqs.at(i).empty()) { + set(cpuSysfsPaths.at(i) + "/cpufreq/scaling_max_freq", max_freqs.at(i)); + } + } + break; + default: + break; + } +} + +void Power::sendBoostpulse() { + // the boostpulse node is only valid for the LITTLE cluster + set(cpuInteractivePaths.front() + "/boostpulse", "1"); +} + +void Power::sendBoost(int duration_us) { + set(cpuInteractivePaths.front() + "/boost", "1"); + + usleep(duration_us); + + set(cpuInteractivePaths.front() + "/boost", "0"); +} + +} // namespace implementation +} // namespace V1_0 +} // namespace power +} // namespace hardware +} // namespace android diff --git a/hidl/power/Power.h b/hidl/power/Power.h new file mode 100644 index 00000000..2a8b8af2 --- /dev/null +++ b/hidl/power/Power.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 The LineageOS 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_POWER_V1_0_POWER_H +#define ANDROID_HARDWARE_POWER_V1_0_POWER_H + +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace power { +namespace V1_0 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; + +using ::vendor::lineage::power::V1_0::ILineagePower; +using ::vendor::lineage::power::V1_0::LineageFeature; +using ::vendor::lineage::power::V1_0::LineagePowerHint; + +// clang-format off +enum PowerProfile { + POWER_SAVE = 0, + BALANCED, + HIGH_PERFORMANCE, + MAX +}; +// clang-format on + +struct Power : public IPower, public ILineagePower { + Return setInteractive(bool interactive) override; + Return powerHint(PowerHint hint, int32_t data) override; + Return setFeature(Feature feature, bool activate) override; + Return getPlatformLowPowerStats(getPlatformLowPowerStats_cb _hidl_cb) override; + + Return getFeature(LineageFeature feature) override; + + private: + void initialize(); + void findInputNodes(); + void setProfile(PowerProfile profile); + void sendBoostpulse(); + void sendBoost(int duration_us); + + bool initialized; + bool touchkeys_blocked; + std::string sec_touchkey; + std::string sec_touchscreen; + PowerProfile current_profile; + std::vector hispeed_freqs; + std::vector max_freqs; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace power +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_POWER_V1_0_POWER_H diff --git a/hidl/power/android.hardware.power@1.0-service.exynos.rc b/hidl/power/android.hardware.power@1.0-service.exynos.rc new file mode 100644 index 00000000..12dc6654 --- /dev/null +++ b/hidl/power/android.hardware.power@1.0-service.exynos.rc @@ -0,0 +1,4 @@ +service vendor.power-hal-1-0 /vendor/bin/hw/android.hardware.power@1.0-service.exynos + class hal + user system + group system diff --git a/hidl/power/include/samsung_power.h b/hidl/power/include/samsung_power.h new file mode 100644 index 00000000..c85b650c --- /dev/null +++ b/hidl/power/include/samsung_power.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * Copyright (C) 2020 The LineageOS 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 SAMSUNG_POWER_H +#define SAMSUNG_POWER_H + +/* + * Board specific nodes + * + * If your kernel exposes these controls in another place, you can either + * symlink to the locations given here, or override this header in your + * device tree. + */ + +static const std::vector cpuSysfsPaths = { + "/sys/devices/system/cpu/cpu0", + "/sys/devices/system/cpu/cpu4" +}; + +static const std::vector cpuInteractivePaths = { + "/sys/devices/system/cpu/cpu0/cpufreq/interactive", + "/sys/devices/system/cpu/cpu4/cpufreq/interactive" +}; + +/* double tap to wake node */ +//#define TAP_TO_WAKE_NODE "/sys/class/sec/tsp/dt2w_enable" + +#endif // SAMSUNG_POWER_H diff --git a/hidl/power/service.cpp b/hidl/power/service.cpp new file mode 100644 index 00000000..db3f92d8 --- /dev/null +++ b/hidl/power/service.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The LineageOS 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. + */ + +#define LOG_TAG "android.hardware.power@1.0-service.exynos" + +#include +#include +#include + +#include "Power.h" + +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +using android::hardware::power::V1_0::IPower; +using android::hardware::power::V1_0::implementation::Power; + +using android::OK; +using android::sp; +using android::status_t; + +int main() { + sp power = new Power(); + status_t status = 0; + + configureRpcThreadpool(1, true); + + status = power->IPower::registerAsService(); + if (status != OK) { + LOG(ERROR) << "Could not register service (IPower) for Power HAL"; + goto shutdown; + } + + status = power->ILineagePower::registerAsService(); + if (status != OK) { + LOG(ERROR) << "Could not register service (ILineagePower) for Power HAL"; + goto shutdown; + } + + LOG(INFO) << "Power HAL service is Ready."; + joinRpcThreadpool(); + +shutdown: + // In normal operation, we don't expect the thread pool to shutdown + LOG(ERROR) << "Power HAL failed to join thread pool."; + return 1; +}