blob: db5c2f1fdc5c7ced17bd8f3553cde1800d4e014b [file] [log] [blame]
// Copyright 2018 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 "cfm-device-monitor/apex-monitor/apex_manager.h"
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <fcntl.h>
#include <string>
#include <utility>
#include "cfm-device-monitor/apex-monitor/i2c_interface.h"
namespace {
const size_t kByteDataSize = 1;
const size_t kWordDataSize = 2;
// Thermal sensor CSRs.
const uint8_t kThermalSensorPageNumber = 0x00;
// Thermal sensor clock enable: bit 7 of 0xd0.
const uint8_t kThermalSensorClkEnAddr = 0xd0;
const uint8_t kThermalSensorClkEnBit = 0x7;
// Thermal sensor ports enable: last 3 bit of 0xd8.
const uint8_t kThermalSensorPortEnAddr = 0xd8;
const uint8_t kThermalSensorPortEnadBit = 0x2;
const uint8_t kThermalSensorPortEnvrBit = 0x1;
const uint8_t kThermalSensorPortEnbgBit = 0x0;
// Thermal sensor OMC controller enable: bit 0 of 0xdc.
const uint8_t kThermalSensorCtrlEnAddr = 0xdc;
const unsigned char kThermalSensorCtrlEnBit = 0x0;
// Temperature data: 10 bits starting at 0xde.
const uint8_t kThermalSensorTempAddr = 0xde;
const uint16_t kThermalSensorTempMask = 0x03ff;
// If temperature < -50C. It's more likely the thermal sensor is
// not configured properly.
const int kThermalLowestThreshold = -50;
const uint32_t kEnableThermalControllerSlpTimeUs = 1e5;
// IO expander.
const uint8_t kApexResetSetAddr[] = {0x02, 0x03};
// Reset bits are bit 03 and 15.
const uint8_t kApexResetSetBits[] = {0x03, 0x05};
// Wait 100ms before release reset signal.
const uint32_t kApexResetSlpTimeUs = 100e3;
// IO expander IO configuration registers.
const uint8_t kIoExpanderIoConfig0Addr = 0x06;
const uint8_t kIoExpanderIoConfig1Addr = 0x07;
const unsigned char kIoExpanderIoConfig0Value = 0x67;
const unsigned char kIoExpanderIoConfig1Value = 0xdc;
// Apex power/boot state bits.
const uint8_t kIoExpanderPowerStateAddr = 0x01;
const unsigned char kApexPowerStateBitMask[] = {0x40, 0x80};
const uint8_t kIoExpanderBootStateAddr[] = {0x00, 0x01};
const unsigned char kApexBootStateBitMask[] = {0x01, 0x04};
// Power control GPIOs.
const char kSysfsGpioPath[] = "/sys/class/gpio/";
constexpr int kApexPowerCtrlGpioId[] = {416, 417};
const char kGpioDirOutStr[] = "out";
const char kGpioValueHigh = '1';
const char kGpioValueLow = '0';
const size_t kGpioDirOutStrLen = 3;
const size_t kGpioValueLen = 1;
// Wait 1s for power cycle.
const uint32_t kApexPowerCycleSlpTimeUs = 1e6;
} // namespace
namespace apex_monitor {
ApexManager::ApexManager(const int& chip_id,
std::unique_ptr<BaseI2cInterface> apex_interface,
BaseI2cInterface* ioexp_interface)
: chip_id_(chip_id),
apex_i2c_interface_(std::move(apex_interface)),
io_expander_interface_(ioexp_interface) {}
ApexManager::~ApexManager() {}
std::unique_ptr<ApexManager> ApexManager::Create(
const int& chip_id, std::unique_ptr<BaseI2cInterface> apex_interface,
BaseI2cInterface* ioexp_interface) {
auto apex_manager = std::make_unique<ApexManager>(
chip_id, std::move(apex_interface), ioexp_interface);
if (!apex_manager->InitThermalSensor()) {
LOG(ERROR) << "Failed to init thermal sensor for chip " << chip_id;
return nullptr;
}
return apex_manager;
}
bool ApexManager::InitThermalSensor() {
// Enable thermal sensor clock.
if (!apex_i2c_interface_->SetBit(kThermalSensorClkEnAddr,
kThermalSensorClkEnBit,
kThermalSensorPageNumber)) {
LOG(ERROR) << "Failed to enable thermal sensor clock.";
return false;
}
// Enable thermal sensor input port: ENBG, ENVR, ENAD.
if (!(apex_i2c_interface_->SetBit(kThermalSensorPortEnAddr,
kThermalSensorPortEnadBit,
kThermalSensorPageNumber) &&
apex_i2c_interface_->SetBit(kThermalSensorPortEnAddr,
kThermalSensorPortEnvrBit,
kThermalSensorPageNumber) &&
apex_i2c_interface_->SetBit(kThermalSensorPortEnAddr,
kThermalSensorPortEnbgBit,
kThermalSensorPageNumber))) {
LOG(ERROR) << "Failed to enable thermal sensor input ports.";
return false;
}
usleep(kEnableThermalControllerSlpTimeUs);
// Enable OMC thermal sensor controller.
if (!apex_i2c_interface_->SetBit(kThermalSensorCtrlEnAddr,
kThermalSensorCtrlEnBit,
kThermalSensorPageNumber)) {
LOG(ERROR) << "Failed to enable thermal sensor controller.";
return false;
}
return true;
}
bool ApexManager::InitIoExpander(BaseI2cInterface* ioexp_interface) {
if (!ioexp_interface->I2cWriteReg(kIoExpanderIoConfig0Addr,
&kIoExpanderIoConfig0Value,
kByteDataSize)) {
LOG(ERROR) << "Failed to configure IO expander byte 0.";
return false;
}
if (!ioexp_interface->I2cWriteReg(kIoExpanderIoConfig1Addr,
&kIoExpanderIoConfig1Value,
kByteDataSize)) {
LOG(ERROR) << "Failed to configure IO expander byte 1.";
return false;
}
return true;
}
bool ApexManager::InitIoExpander() {
if (!InitIoExpander(io_expander_interface_)) {
LOG(ERROR) << "Failed to init IO expander.";
return false;
}
return true;
}
bool ApexManager::IoExpanderConfigGood() {
unsigned char config_data;
if (!io_expander_interface_->I2cReadReg(kIoExpanderIoConfig0Addr,
&config_data, kByteDataSize)) {
LOG(ERROR) << "Failed to read IO expander configuration byte 0.";
return false;
}
if (config_data != kIoExpanderIoConfig0Value) return false;
if (!io_expander_interface_->I2cReadReg(kIoExpanderIoConfig1Addr,
&config_data, kByteDataSize)) {
LOG(ERROR) << "Failed to read IO expander configuration byte 1.";
return false;
}
if (config_data != kIoExpanderIoConfig1Value) return false;
return true;
}
bool ApexManager::CheckStatusRegister(bool* power_good, bool* boot_fail) {
unsigned char read_data;
// Read power status.
if (!io_expander_interface_->I2cReadReg(kIoExpanderPowerStateAddr, &read_data,
kByteDataSize)) {
LOG(ERROR) << "Failed to read power status.";
return false;
}
*power_good = read_data & kApexPowerStateBitMask[chip_id_];
// If power_good is false, check boot status doesn't make any sense.
if (!power_good) {
*boot_fail = true;
return true;
}
// Read boot status. The register address of boot status and power status
// are the same for apex chip 1. So no need to re-read the register data
// in that case.
if (kIoExpanderBootStateAddr[chip_id_] != kIoExpanderPowerStateAddr) {
if (!io_expander_interface_->I2cReadReg(kIoExpanderBootStateAddr[chip_id_],
&read_data, kByteDataSize)) {
LOG(ERROR) << "Failed to read boot status.";
return false;
}
}
*boot_fail = read_data & kApexBootStateBitMask[chip_id_];
return true;
}
int ApexManager::ChipID() { return chip_id_; }
bool ApexManager::ReadChipTemp(int* temp) {
uint16_t temp_data;
if (!apex_i2c_interface_->I2cReadReg(
kThermalSensorTempAddr, reinterpret_cast<unsigned char*>(&temp_data),
kWordDataSize, kThermalSensorPageNumber)) {
LOG(ERROR) << "Failed to read temperature of Apex " << chip_id_;
return false;
}
TempCodeToCelsius(temp_data, temp);
// If the temprature is too low, it more likely that the thermal
// sensor configuration is wrong.
if (*temp < kThermalLowestThreshold) {
InitThermalSensor();
return true;
}
LOG(INFO) << "Apex chip " << chip_id_ << " temperature: " << *temp;
return true;
}
inline void ApexManager::TempCodeToCelsius(const uint16_t& temp_code,
int* temp) {
*temp = static_cast<int>(
(((662.0 - (temp_code & kThermalSensorTempMask)) / 4.0 + 0.5) * 10.0 +
0.5) /
10);
}
bool ApexManager::ResetChip() {
// First check IO expander IO configuration.
if (!IoExpanderConfigGood()) {
if (!InitIoExpander()) {
LOG(ERROR) << "Invalid IO expander IO direction configuration.";
return false;
}
}
// Assert reset signal.
if (!io_expander_interface_->ClearBit(kApexResetSetAddr[chip_id_],
kApexResetSetBits[chip_id_])) {
LOG(ERROR) << "Failed to assert reset signal for apex " << chip_id_;
return false;
}
usleep(kApexResetSlpTimeUs);
// Release reset signal.
if (!io_expander_interface_->SetBit(kApexResetSetAddr[chip_id_],
kApexResetSetBits[chip_id_])) {
LOG(ERROR) << "Failed to release reset signal for apex " << chip_id_;
return false;
}
// Re-init thermal sensor registers.
if (!InitThermalSensor()) {
LOG(ERROR) << "Failed to re-init thermal sensor.";
return false;
}
return true;
}
bool ApexManager::ExportPowerCtrlGpio() {
std::string chip_id_str = std::to_string(kApexPowerCtrlGpioId[chip_id_]);
base::FilePath gpio_path(kSysfsGpioPath + std::string("gpio") + chip_id_str);
if (base::PathExists(gpio_path)) {
VLOG(1) << "GPIO " << chip_id_str << " already exported.";
return true;
}
// Export target GPIO.
base::FilePath export_path(kSysfsGpioPath + std::string("export"));
base::ScopedFD export_fd(
open(export_path.value().c_str(), O_WRONLY | O_CLOEXEC));
if (!export_fd.is_valid()) {
LOG(ERROR) << "Failed to open " << export_path.value();
return false;
}
int r = write(export_fd.get(), chip_id_str.c_str(), chip_id_str.size());
if (r < 0 || !base::PathExists(gpio_path)) {
PLOG(ERROR) << "Failed to export GPIO " << chip_id_str;
return false;
}
r = base::WriteFile(gpio_path.Append("direction"), kGpioDirOutStr,
kGpioDirOutStrLen);
if (r < 0) {
PLOG(ERROR) << "Failed to set GPIO direction for GPIO"
<< kApexPowerCtrlGpioId[chip_id_];
return false;
}
return true;
}
bool ApexManager::PowerCycleChip() {
if (!ExportPowerCtrlGpio()) {
LOG(ERROR) << "Failed to export GPIOs to control apex power.";
return false;
}
base::FilePath gpio_path(kSysfsGpioPath + std::string("gpio") +
std::to_string(kApexPowerCtrlGpioId[chip_id_]));
// Power off chip.
int r =
base::WriteFile(gpio_path.Append("value"), &kGpioValueLow, kGpioValueLen);
if (r < 0) {
PLOG(ERROR) << "Failed to set GPIO " << kApexPowerCtrlGpioId[chip_id_]
<< " value to low.";
return false;
}
// Check IO expander IO configuration before toggling reset bit.
if (!IoExpanderConfigGood()) {
if (!InitIoExpander()) {
LOG(ERROR) << "Invalid IO expander IO direction configuration.";
return false;
}
}
// Assert reset signal.
if (!io_expander_interface_->ClearBit(kApexResetSetAddr[chip_id_],
kApexResetSetBits[chip_id_])) {
LOG(ERROR) << "Failed to set reset signal for apex " << chip_id_;
return false;
}
usleep(kApexPowerCycleSlpTimeUs);
// Power on chip.
r = base::WriteFile(gpio_path.Append("value"), &kGpioValueHigh,
kGpioValueLen);
if (r < 0) {
PLOG(ERROR) << "Failed to set GPIO " << kApexPowerCtrlGpioId[chip_id_]
<< " value to high.";
return false;
}
usleep(kApexResetSlpTimeUs);
// Release reset signal.
if (!io_expander_interface_->SetBit(kApexResetSetAddr[chip_id_],
kApexResetSetBits[chip_id_])) {
LOG(ERROR) << "Failed to release reset signal for apex " << chip_id_;
return false;
}
// Re-init thermal sensor.
if (!InitThermalSensor()) {
LOG(ERROR) << "Failed to re-init thermal sensor.";
return false;
}
return true;
}
} // namespace apex_monitor