blob: dc40ada770d07719a06e0d405a4169a04f2db698 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "power_manager/powerd/system/internal_backlight.h"
#include <cmath>
#include <string>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <linux/fb.h>
#include "power_manager/common/clock.h"
#include "power_manager/common/util.h"
namespace power_manager {
namespace system {
namespace {
// When animating a brightness level transition, amount of time in milliseconds
// to wait between each update.
const int kTransitionIntervalMs = 20;
} // namespace
const char InternalBacklight::kBrightnessFilename[] = "brightness";
const char InternalBacklight::kMaxBrightnessFilename[] = "max_brightness";
const char InternalBacklight::kBlPowerFilename[] = "bl_power";
const char InternalBacklight::kScaleFilename[] = "scale";
InternalBacklight::InternalBacklight()
: clock_(new Clock),
max_brightness_level_(0),
current_brightness_level_(0),
brightness_scale_(BrightnessScale::kUnknown),
transition_start_level_(0),
transition_end_level_(0) {}
InternalBacklight::~InternalBacklight() {}
bool InternalBacklight::Init(const base::FilePath& base_path,
const std::string& pattern) {
base::FileEnumerator enumerator(base_path, false,
base::FileEnumerator::DIRECTORIES, pattern);
// Find the backlight interface with greatest granularity (highest max).
for (base::FilePath device_path = enumerator.Next(); !device_path.empty();
device_path = enumerator.Next()) {
if (device_path.BaseName().value()[0] == '.')
continue;
const base::FilePath max_brightness_path =
device_path.Append(kMaxBrightnessFilename);
if (!base::PathExists(max_brightness_path)) {
LOG(WARNING) << "Can't find " << max_brightness_path.value();
continue;
}
const base::FilePath brightness_path =
device_path.Append(kBrightnessFilename);
if (access(brightness_path.value().c_str(), R_OK | W_OK) != 0) {
LOG(WARNING) << "Can't write to " << brightness_path.value();
continue;
}
int64_t max_level = 0;
if (!util::ReadInt64File(max_brightness_path, &max_level))
continue;
if (max_level <= max_brightness_level_)
continue;
device_path_ = device_path;
brightness_path_ = brightness_path;
max_brightness_path_ = max_brightness_path;
max_brightness_level_ = max_level;
const base::FilePath power_path = device_path.Append(kBlPowerFilename);
if (base::PathExists(power_path))
bl_power_path_ = power_path;
const base::FilePath scale_path = device_path.Append(kScaleFilename);
if (base::PathExists(scale_path)) {
std::string scale;
util::ReadStringFile(scale_path, &scale);
if (scale == "linear")
brightness_scale_ = BrightnessScale::kLinear;
else if (scale == "non-linear")
brightness_scale_ = BrightnessScale::kNonLinear;
else
brightness_scale_ = BrightnessScale::kUnknown;
}
}
if (max_brightness_level_ <= 0)
return false;
util::ReadInt64File(brightness_path_, &current_brightness_level_);
return true;
}
bool InternalBacklight::TriggerTransitionTimeoutForTesting() {
CHECK(transition_timer_.IsRunning());
HandleTransitionTimeout();
return transition_timer_.IsRunning();
}
void InternalBacklight::AddObserver(BacklightObserver* observer) {}
void InternalBacklight::RemoveObserver(BacklightObserver* observer) {}
bool InternalBacklight::DeviceExists() {
return true;
}
int64_t InternalBacklight::GetMaxBrightnessLevel() {
return max_brightness_level_;
}
int64_t InternalBacklight::GetCurrentBrightnessLevel() {
return current_brightness_level_;
}
bool InternalBacklight::SetBrightnessLevel(int64_t level,
base::TimeDelta interval) {
if (brightness_path_.empty()) {
LOG(ERROR) << "Cannot find backlight brightness file.";
return false;
}
if (level == current_brightness_level_) {
CancelTransition();
return true;
}
if (interval.InMilliseconds() <= kTransitionIntervalMs) {
CancelTransition();
return WriteBrightness(level);
}
transition_start_time_ = clock_->GetCurrentTime();
transition_end_time_ = transition_start_time_ + interval;
transition_start_level_ = current_brightness_level_;
transition_end_level_ = level;
if (!transition_timer_.IsRunning()) {
transition_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(kTransitionIntervalMs),
this, &InternalBacklight::HandleTransitionTimeout);
transition_timer_start_time_ = transition_start_time_;
}
return true;
}
BacklightInterface::BrightnessScale InternalBacklight::GetBrightnessScale() {
return brightness_scale_;
}
bool InternalBacklight::TransitionInProgress() const {
return transition_timer_.IsRunning();
}
bool InternalBacklight::WriteBrightness(int64_t new_level) {
// If the backlight is about to be turned on, write FB_BLANK_UNBLANK
// to bl_power first.
if (current_brightness_level_ == 0 && !bl_power_path_.empty())
util::WriteInt64File(bl_power_path_, FB_BLANK_UNBLANK);
if (!util::WriteInt64File(brightness_path_, new_level))
return false;
current_brightness_level_ = new_level;
// If the backlight level just went to 0, write FB_BLANK_POWERDOWN
// to bl_power.
if (current_brightness_level_ == 0 && !bl_power_path_.empty())
util::WriteInt64File(bl_power_path_, FB_BLANK_POWERDOWN);
return true;
}
void InternalBacklight::HandleTransitionTimeout() {
base::TimeTicks now = clock_->GetCurrentTime();
int64_t new_level = 0;
if (now >= transition_end_time_) {
new_level = transition_end_level_;
transition_timer_.Stop();
} else {
double transition_fraction =
(now - transition_start_time_).InMillisecondsF() /
(transition_end_time_ - transition_start_time_).InMillisecondsF();
int64_t intermediate_amount =
lround(transition_fraction *
(transition_end_level_ - transition_start_level_));
new_level = transition_start_level_ + intermediate_amount;
}
if (new_level == current_brightness_level_)
return;
WriteBrightness(new_level);
}
void InternalBacklight::CancelTransition() {
transition_timer_.Stop();
transition_start_time_ = base::TimeTicks();
transition_end_time_ = base::TimeTicks();
transition_start_level_ = current_brightness_level_;
transition_end_level_ = current_brightness_level_;
}
} // namespace system
} // namespace power_manager