blob: f1cbb5c99d1e27c02754850c35fa70e5a4b2f6a1 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "ti-lp8556.h"
#include <lib/ddk/debug.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/i2c.h>
#include <lib/device-protocol/pdev.h>
#include <math.h>
#include <algorithm>
#include <ddktl/fidl.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include "src/ui/backlight/drivers/ti-lp8556/ti-lp8556-bind.h"
#include "ti-lp8556Metadata.h"
namespace ti {
// Refer to <internal>/vendor/amlogic/video-common/ambient_temp/lp8556.cc
// Lookup tables containing the slope and y-intercept for a linear equation used
// to fit the (power / |brightness_to_current_scalar_|) per vendor for
// brightness levels below |kMinTableBrightness|. The power can be calculated
// from these scalars by:
// (slope * brightness + intercept) * |brightness_to_current_scalar_|.
constexpr std::array<double,
static_cast<std::size_t>(Lp8556Device::PanelType::kNumTypes)>
kLowBrightnessSlopeTable = {
22.4, // PanelType::kBoe
22.2, // PanelType::kKd
};
constexpr std::array<double,
static_cast<std::size_t>(Lp8556Device::PanelType::kNumTypes)>
kLowBrightnessInterceptTable = {
1236.0, // PanelType::kBoe
1319.0, // PanelType::kKd
};
// Lookup tables for backlight driver voltage as a function of the backlight
// brightness. The index for each sub-table corresponds to a PanelType, and
// allows for the backlight voltage to vary with panel vendor. Starting from a
// brightness level of |kMinTableBrightness|, each index of each sub-table
// corresponds to a jump of |kBrightnessStep| in brightness up to the maximum
// value of |kMaxBrightnessSetting|.
constexpr std::array<std::array<double, kTableSize>,
static_cast<std::size_t>(Lp8556Device::PanelType::kNumTypes)>
kVoltageTable = {{
// PanelType::kBoe
{19.80, 19.80, 19.80, 19.80, 19.90, 20.00, 20.10, 20.20, 20.30, 20.40, 20.50, 20.53, 20.53,
20.53, 20.53, 20.53},
// PanelType::kKd
{19.67, 19.67, 19.67, 19.67, 19.77, 19.93, 20.03, 20.13, 20.20, 20.27, 20.37, 20.37, 20.37,
20.37, 20.37, 20.37},
}};
// Lookup table for backlight driver efficiency as a function of the backlight
// brightness. Starting from a brightness level of |kMinTableBrightness|, each
// index of the table corresponds to a jump of |kBrightnessStep| in brightness
// up to the maximum value of |kMaxBrightnessSetting|.
constexpr std::array<double, kTableSize> kEfficiencyTable = {
0.6680, 0.7784, 0.8240, 0.8484, 0.8634, 0.8723, 0.8807, 0.8860,
0.8889, 0.8915, 0.8953, 0.8983, 0.9003, 0.9034, 0.9049, 0.9060};
// The max current value in the table is determined by the value of the three
// max current bits within the Lp8556 CFG1 register. The value of these bits can
// be obtained from the max_current sysfs node exposed by the driver. The
// current values in the table are expressed in mA.
constexpr std::array<double, 8> kMaxCurrentTable = {5.0, 10.0, 15.0, 20.0, 23.0, 25.0, 30.0, 50.0};
void Lp8556Device::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
void Lp8556Device::DdkRelease() { delete this; }
zx_status_t Lp8556Device::GetBacklightState(bool* power, double* brightness) {
*power = power_;
*brightness = brightness_;
return ZX_OK;
}
zx_status_t Lp8556Device::SetBacklightState(bool power, double brightness) {
brightness = std::max(brightness, 0.0);
brightness = std::min(brightness, 1.0);
uint16_t brightness_reg_value = static_cast<uint16_t>(ceil(brightness * kBrightnessRegMaxValue));
if (brightness != brightness_) {
// LSB should be updated before MSB. Writing to MSB triggers the brightness change.
uint8_t buf[2];
buf[0] = kBacklightBrightnessLsbReg;
buf[1] = static_cast<uint8_t>(brightness_reg_value & kBrightnessLsbMask);
zx_status_t status = i2c_.WriteSync(buf, sizeof(buf));
if (status != ZX_OK) {
LOG_ERROR("Failed to set brightness LSB register\n");
return status;
}
uint8_t msb_reg_value;
status = i2c_.ReadSync(kBacklightBrightnessMsbReg, &msb_reg_value, 1);
if (status != ZX_OK) {
LOG_ERROR("Failed to get brightness MSB register\n");
return status;
}
// The low 4-bits contain the brightness MSB. Keep the remaining bits unchanged.
msb_reg_value &= static_cast<uint8_t>(~kBrightnessMsbByteMask);
msb_reg_value |=
(static_cast<uint8_t>((brightness_reg_value & kBrightnessMsbMask) >> kBrightnessMsbShift));
buf[0] = kBacklightBrightnessMsbReg;
buf[1] = msb_reg_value;
status = i2c_.WriteSync(buf, sizeof(buf));
if (status != ZX_OK) {
LOG_ERROR("Failed to set brightness MSB register\n");
return status;
}
auto persistent_brightness = BrightnessStickyReg::Get().ReadFrom(&mmio_);
persistent_brightness.set_brightness(brightness_reg_value & kBrightnessRegMask);
persistent_brightness.set_is_valid(1);
persistent_brightness.WriteTo(&mmio_);
}
if (power != power_) {
uint8_t buf[2];
buf[0] = kDeviceControlReg;
buf[1] = power ? kBacklightOn : kBacklightOff;
zx_status_t status = i2c_.WriteSync(buf, sizeof(buf));
if (status != ZX_OK) {
LOG_ERROR("Failed to set device control register\n");
return status;
}
if (power) {
for (size_t i = 0; i < metadata_.register_count; i += 2) {
if ((status = i2c_.WriteSync(&metadata_.registers[i], 2)) != ZX_OK) {
LOG_ERROR("Failed to set register 0x%02x: %d\n", metadata_.registers[i], status);
return status;
}
}
buf[0] = kCfg2Reg;
buf[1] = cfg2_;
status = i2c_.WriteSync(buf, sizeof(buf));
if (status != ZX_OK) {
LOG_ERROR("Failed to set cfg2 register\n");
return status;
}
}
}
// update internal values
power_ = power;
brightness_ = brightness;
power_property_.Set(power_);
brightness_property_.Set(brightness_);
backlight_power_ = GetBacklightPower(brightness_reg_value);
return ZX_OK;
}
void Lp8556Device::GetStateNormalized(GetStateNormalizedCompleter::Sync& completer) {
FidlBacklight::wire::State state = {};
auto status = GetBacklightState(&state.backlight_on, &state.brightness);
if (status == ZX_OK) {
completer.ReplySuccess(state);
} else {
completer.ReplyError(status);
}
}
void Lp8556Device::SetStateNormalized(FidlBacklight::wire::State state,
SetStateNormalizedCompleter::Sync& completer) {
auto status = SetBacklightState(state.backlight_on, state.brightness);
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void Lp8556Device::GetStateAbsolute(GetStateAbsoluteCompleter::Sync& completer) {
if (!max_absolute_brightness_nits_.has_value()) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
if (scale_ != calibrated_scale_) {
LOG_ERROR("Can't get absolute state with non-calibrated current scale\n");
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
FidlBacklight::wire::State state = {};
auto status = GetBacklightState(&state.backlight_on, &state.brightness);
if (status == ZX_OK) {
state.brightness *= max_absolute_brightness_nits_.value();
completer.ReplySuccess(state);
} else {
completer.ReplyError(status);
}
}
void Lp8556Device::SetStateAbsolute(FidlBacklight::wire::State state,
SetStateAbsoluteCompleter::Sync& completer) {
if (!max_absolute_brightness_nits_.has_value()) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
// Restore the calibrated current scale that the bootloader set. This and the maximum brightness
// are the only values we have that can be used to set the absolute brightness in nits.
auto status = SetCurrentScale(calibrated_scale_);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
status = SetBacklightState(state.backlight_on,
state.brightness / max_absolute_brightness_nits_.value());
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void Lp8556Device::GetMaxAbsoluteBrightness(GetMaxAbsoluteBrightnessCompleter::Sync& completer) {
if (max_absolute_brightness_nits_.has_value()) {
completer.ReplySuccess(max_absolute_brightness_nits_.value());
} else {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
}
void Lp8556Device::SetNormalizedBrightnessScale(
double scale, SetNormalizedBrightnessScaleCompleter::Sync& completer) {
scale = std::clamp(scale, 0.0, 1.0);
zx_status_t status = SetCurrentScale(static_cast<uint16_t>(scale * kBrightnessRegMaxValue));
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess();
}
}
void Lp8556Device::GetNormalizedBrightnessScale(
GetNormalizedBrightnessScaleCompleter::Sync& completer) {
completer.ReplySuccess(static_cast<double>(scale_) / kBrightnessRegMaxValue);
}
void Lp8556Device::GetPowerWatts(GetPowerWattsCompleter::Sync& completer) {
completer.ReplySuccess(backlight_power_);
}
zx_status_t Lp8556Device::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
if (fidl::WireTryDispatch<FidlBacklight::Device>(this, msg, &transaction) ==
::fidl::DispatchResult::kFound) {
return transaction.Status();
}
fidl::WireDispatch<FidlPowerSensor::Device>(this, msg, &transaction);
return transaction.Status();
}
zx_status_t Lp8556Device::Init() {
root_ = inspector_.GetRoot().CreateChild("ti-lp8556");
double brightness_nits = 0.0;
size_t actual;
zx_status_t status = device_get_metadata(parent(), DEVICE_METADATA_BACKLIGHT_MAX_BRIGHTNESS_NITS,
&brightness_nits, sizeof(brightness_nits), &actual);
if (status == ZX_OK && actual == sizeof(brightness_nits)) {
SetMaxAbsoluteBrightnessNits(brightness_nits);
}
status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, &metadata_, sizeof(metadata_),
&actual);
// Supplying this metadata is optional.
if (status == ZX_OK) {
if (metadata_.register_count % (2 * sizeof(uint8_t)) != 0) {
LOG_ERROR("Register metadata is invalid\n");
return ZX_ERR_INVALID_ARGS;
} else if (actual != sizeof(metadata_)) {
LOG_ERROR("Too many registers specified in metadata\n");
return ZX_ERR_OUT_OF_RANGE;
}
for (size_t i = 0; i < metadata_.register_count; i += 2) {
if ((status = i2c_.WriteSync(&metadata_.registers[i], 2)) != ZX_OK) {
LOG_ERROR("Failed to set register 0x%02x: %d\n", metadata_.registers[i], status);
return status;
}
}
}
auto persistent_brightness = BrightnessStickyReg::Get().ReadFrom(&mmio_);
if (persistent_brightness.is_valid()) {
double brightness =
static_cast<double>(persistent_brightness.brightness()) / kBrightnessRegMaxValue;
if ((status = SetBacklightState(brightness > 0, brightness)) != ZX_OK) {
LOG_ERROR("Could not set persistent brightness value: %f\n", brightness);
} else {
LOG_INFO("Successfully set persistent brightness value: %f\n", brightness);
}
persistent_brightness_property_ =
root_.CreateUint("persistent_brightness", persistent_brightness.brightness());
}
if ((i2c_.ReadSync(kCfg2Reg, &cfg2_, 1) != ZX_OK) || (cfg2_ == 0)) {
cfg2_ = kCfg2Default;
}
uint8_t buf[2];
if ((i2c_.ReadSync(kCurrentLsbReg, buf, sizeof(buf))) != ZX_OK) {
LOG_ERROR("Could not read current scale value: %d\n", status);
return status;
}
scale_ = static_cast<uint16_t>(buf[0] | (buf[1] << kBrightnessMsbShift)) & kBrightnessRegMask;
calibrated_scale_ = scale_;
brightness_property_ = root_.CreateDouble("brightness", brightness_);
scale_property_ = root_.CreateUint("scale", scale_);
calibrated_scale_property_ = root_.CreateUint("calibrated_scale", calibrated_scale_);
power_property_ = root_.CreateBool("power", power_);
// max_absolute_brightness_nits will be initialized in SetMaxAbsoluteBrightnessNits.
uint8_t max_current_idx;
i2c_.ReadSync(kCfgReg, &max_current_idx, sizeof(max_current_idx));
max_current_idx = (max_current_idx >> 4) & 0b111;
max_current_ = kMaxCurrentTable[max_current_idx];
return ZX_OK;
}
zx_status_t Lp8556Device::SetCurrentScale(uint16_t scale) {
scale &= kBrightnessRegMask;
if (scale == scale_) {
return ZX_OK;
}
uint8_t msb_reg_value;
zx_status_t status = i2c_.ReadSync(kCfgReg, &msb_reg_value, sizeof(msb_reg_value));
if (status != ZX_OK) {
LOG_ERROR("Failed to get current scale register: %d", status);
return status;
}
msb_reg_value &= ~kBrightnessMsbByteMask;
const uint8_t buf[] = {
kCurrentLsbReg,
static_cast<uint8_t>(scale & kBrightnessLsbMask),
static_cast<uint8_t>(msb_reg_value | (scale >> kBrightnessMsbShift)),
};
if ((status = i2c_.WriteSync(buf, sizeof(buf))) != ZX_OK) {
LOG_ERROR("Failed to set current scale register: %d", status);
return status;
}
scale_ = scale;
scale_property_.Set(scale);
return ZX_OK;
}
double Lp8556Device::GetBacklightPower(double backlight_brightness) {
// For brightness values less than |kMinTableBrightness|, estimate the power
// on a per-vendor basis from a linear equation derived from validation data.
if (backlight_brightness < kMinTableBrightness) {
std::size_t panel_type_index = static_cast<std::size_t>(GetPanelType());
double slope = kLowBrightnessSlopeTable[panel_type_index];
double intercept = kLowBrightnessInterceptTable[panel_type_index];
return (slope * backlight_brightness + intercept) * GetBrightnesstoCurrentScalar();
}
// For brightness values in the range [|kMinTableBrightness|,
// |kMaxBrightnessSetting|], use the voltage and efficiency lookup tables
// derived from validation data to estimate the power.
double backlight_voltage = GetBacklightVoltage(backlight_brightness, GetPanelType());
double current_amp = GetBrightnesstoCurrentScalar() * backlight_brightness;
double driver_efficiency = GetDriverEfficiency(backlight_brightness);
return backlight_voltage * current_amp / driver_efficiency;
}
double Lp8556Device::GetBrightnesstoCurrentScalar() {
double setpoint_current_setting = scale_ / kMaxCurrentSetting;
double max_current_amp = max_current_ / kMilliampPerAmp;
// The setpoint current refers to the backlight current for a single driver
// channel, assuming that the backlight brightness setting is at its max value
// of 4095 (100%).
double setpoint_current_amp = (setpoint_current_setting / kMaxCurrentSetting) * max_current_amp;
// The scalar returned is equal to:
// 6 Driver Channels * Setpoint Current per Channel / Max Brightness Setting
// When this value is multiplied by the backlight brightness setting, it
// yields the backlight current in Amps.
return kNumBacklightDriverChannels * setpoint_current_amp / kMaxBrightnessSetting;
}
double Lp8556Device::GetBacklightVoltage(double backlight_brightness, PanelType panel_type) {
std::size_t panel_type_index = static_cast<std::size_t>(panel_type);
// Backlight is at max brightness
if (backlight_brightness == kMaxBrightnessSetting) {
return kVoltageTable[panel_type_index].back();
}
// Backlight is at |kMinTableBrightness|
if (backlight_brightness == kMinTableBrightness) {
return kVoltageTable[panel_type_index].front();
}
double integral;
double fractional = modf(backlight_brightness / kBrightnessStep, &integral);
std::size_t table_index = static_cast<std::size_t>(integral) - 1;
if (table_index + 1 >= kVoltageTable[panel_type_index].size()) {
LOG_ERROR("Invalid backlight brightness: %f", backlight_brightness);
return kVoltageTable[panel_type_index].back();
}
double lower_voltage = kVoltageTable[panel_type_index][table_index];
double upper_voltage = kVoltageTable[panel_type_index][table_index + 1];
return (upper_voltage - lower_voltage) * fractional + lower_voltage;
}
double Lp8556Device::GetDriverEfficiency(double backlight_brightness) {
// Backlight is at max brightness
if (backlight_brightness == kMaxBrightnessSetting) {
return kEfficiencyTable.back();
}
// Backlight is at |kMinTableBrightness|
if (backlight_brightness == kMinTableBrightness) {
return kEfficiencyTable.front();
}
double integral;
double fractional = modf(backlight_brightness / kBrightnessStep, &integral);
std::size_t table_index = static_cast<std::size_t>(integral) - 1;
if (table_index + 1 >= kEfficiencyTable.size()) {
LOG_ERROR("Invalid backlight brightness: %f", backlight_brightness);
return kEfficiencyTable.back();
}
double lower_efficiency = kEfficiencyTable[table_index];
double upper_efficiency = kEfficiencyTable[table_index + 1];
return (upper_efficiency - lower_efficiency) * fractional + lower_efficiency;
}
Lp8556Device::PanelType Lp8556Device::GetPanelType() {
if (metadata_.panel_id == 0 || metadata_.panel_id == 1) {
return Lp8556Device::PanelType::kKd;
}
return Lp8556Device::PanelType::kBoe;
}
zx_status_t ti_lp8556_bind(void* ctx, zx_device_t* parent) {
// Get platform device protocol
auto pdev = ddk::PDev::FromFragment(parent);
if (!pdev.is_valid()) {
LOG_ERROR("Could not get PDEV protocol\n");
return ZX_ERR_NO_RESOURCES;
}
// Map MMIO
std::optional<ddk::MmioBuffer> mmio;
zx_status_t status = pdev.MapMmio(0, &mmio);
if (status != ZX_OK) {
LOG_ERROR("Could not map mmio %d\n", status);
return status;
}
// Obtain I2C protocol needed to control backlight
ddk::I2cChannel i2c(parent, "i2c");
if (!i2c.is_valid()) {
LOG_ERROR("Could not obtain I2C protocol\n");
return ZX_ERR_NO_RESOURCES;
}
fbl::AllocChecker ac;
auto dev =
fbl::make_unique_checked<ti::Lp8556Device>(&ac, parent, std::move(i2c), *std::move(mmio));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if ((status = dev->Init()) != ZX_OK) {
return status;
}
status = dev->DdkAdd(ddk::DeviceAddArgs("ti-lp8556").set_inspect_vmo(dev->InspectVmo()));
if (status != ZX_OK) {
LOG_ERROR("Could not add device\n");
return status;
}
// devmgr is now in charge of memory for dev
__UNUSED auto ptr = dev.release();
return status;
}
static constexpr zx_driver_ops_t ti_lp8556_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = ti_lp8556_bind;
return ops;
}();
} // namespace ti
ZIRCON_DRIVER(ti_lp8556, ti::ti_lp8556_driver_ops, "TI-LP8556", "0.1");