blob: 5d2193b8d90a07f2cf394b5889f6b87f5fde3031 [file] [log] [blame]
// Copyright 2020 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 "vs680-power.h"
#include <memory>
#include <ddk/debug.h>
#include <ddk/platform-defs.h>
#include <ddktl/protocol/composite.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <hwreg/i2c.h>
#include <soc/vs680/vs680-power.h>
#include "src/devices/power/drivers/vs680-power/vs680-power-bind.h"
namespace {
constexpr uint32_t kStepSizeMicroVolts = 10'000;
constexpr uint32_t kMinVoltageMicroVolts = 600'000;
constexpr uint32_t kMaxVoltageMicroVolts = 1'870'000;
// This is the voltage if VBOOT is set to 1, and depends on the feedback voltage divider. On the
// VS680 EVK board this is set to 0.8V.
constexpr uint32_t kDefaultVoltageMicroVolts = 800'000;
class VSel : public hwreg::I2cRegisterBase<VSel, uint8_t, sizeof(uint8_t)> {
public:
static auto Get() { return hwreg::I2cRegisterAddr<VSel>(0x00); }
DEF_BIT(7, vboot);
DEF_FIELD(6, 0, voltage);
auto& set_voltage_microvolts(uint32_t voltage_uv) {
set_vboot(0);
set_voltage(static_cast<uint8_t>((voltage_uv - kMinVoltageMicroVolts) / kStepSizeMicroVolts));
return *this;
}
uint32_t voltage_microvolts() const {
return vboot() ? kDefaultVoltageMicroVolts
: (voltage() * kStepSizeMicroVolts) + kMinVoltageMicroVolts;
}
};
class SysCntrlReg1 : public hwreg::I2cRegisterBase<SysCntrlReg1, uint8_t, sizeof(uint8_t)> {
public:
static constexpr uint8_t kGoBitResetThreshold = 50'000 / kStepSizeMicroVolts;
static auto Get() { return hwreg::I2cRegisterAddr<SysCntrlReg1>(0x01); }
// This bit must be set before changing the voltage, and will be cleared automatically unless the
// voltage change is within 50 mV.
DEF_BIT(6, go_bit);
};
} // namespace
namespace power {
zx_status_t Vs680Power::Create(void* ctx, zx_device_t* parent) {
ddk::CompositeProtocolClient composite(parent);
if (!composite.is_valid()) {
zxlogf(ERROR, "%s: Failed to get composite protocol", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
ddk::I2cProtocolClient pmic_i2c(composite, "i2c-pmic");
if (!pmic_i2c.is_valid()) {
zxlogf(ERROR, "%s: Failed to get I2C fragment", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
fbl::AllocChecker ac;
std::unique_ptr<Vs680Power> device(new (&ac) Vs680Power(parent, pmic_i2c));
if (!ac.check()) {
zxlogf(ERROR, "%s: Failed to allocate device memory", __FILE__);
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = device->DdkAdd("vs680-power", DEVICE_ADD_ALLOW_MULTI_COMPOSITE);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed: %d", __FILE__, status);
return status;
}
__UNUSED auto* _ = device.release();
return ZX_OK;
}
zx_status_t Vs680Power::PowerImplGetPowerDomainStatus(uint32_t index,
power_domain_status_t* out_status) {
if (index != vs680::kPowerDomainVCpu) {
return ZX_ERR_OUT_OF_RANGE;
}
// The VCPU domain is always enabled.
*out_status = POWER_DOMAIN_STATUS_ENABLED;
return ZX_OK;
}
zx_status_t Vs680Power::PowerImplEnablePowerDomain(uint32_t index) { return ZX_OK; }
zx_status_t Vs680Power::PowerImplDisablePowerDomain(uint32_t index) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t Vs680Power::PowerImplGetSupportedVoltageRange(uint32_t index, uint32_t* out_min,
uint32_t* out_max) {
if (index != vs680::kPowerDomainVCpu) {
return ZX_ERR_OUT_OF_RANGE;
}
*out_min = kMinVoltageMicroVolts;
*out_max = kMaxVoltageMicroVolts;
return ZX_OK;
}
zx_status_t Vs680Power::PowerImplRequestVoltage(uint32_t index, uint32_t voltage,
uint32_t* out_actual_voltage) {
if (index != vs680::kPowerDomainVCpu) {
return ZX_ERR_OUT_OF_RANGE;
}
if (voltage < kMinVoltageMicroVolts || voltage > kMaxVoltageMicroVolts) {
return ZX_ERR_OUT_OF_RANGE;
}
if ((voltage - kMinVoltageMicroVolts) % kStepSizeMicroVolts != 0) {
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AutoLock lock(&lock_);
auto syscntrl = SysCntrlReg1::Get().FromValue(0);
zx_status_t status = syscntrl.ReadFrom(pmic_i2c_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to read from SysCntrlReg1: %d", __FILE__, status);
return status;
}
if ((status = syscntrl.set_go_bit(1).WriteTo(pmic_i2c_)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to write to SysCntrlReg1: %d", __FILE__, status);
return status;
}
auto vsel = VSel::Get().FromValue(0);
if ((status = vsel.ReadFrom(pmic_i2c_)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to read from VSel: %d", __FILE__, status);
return status;
}
const uint8_t old_voltage_sel = vsel.voltage();
if ((status = vsel.set_voltage_microvolts(voltage).WriteTo(pmic_i2c_)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to write to VSel: %d", __FILE__, status);
return status;
}
const uint8_t voltage_sel_delta =
std::max(old_voltage_sel, vsel.voltage()) - std::min(old_voltage_sel, vsel.voltage());
if (voltage_sel_delta <= SysCntrlReg1::kGoBitResetThreshold) {
if ((status = syscntrl.ReadFrom(pmic_i2c_)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to read from SysCntrlReg1: %d", __FILE__, status);
return status;
}
if ((status = syscntrl.set_go_bit(0).WriteTo(pmic_i2c_)) != ZX_OK) {
zxlogf(ERROR, "%s: Failed to write to SysCntrlReg1: %d", __FILE__, status);
return status;
}
}
*out_actual_voltage = voltage;
return ZX_OK;
}
zx_status_t Vs680Power::PowerImplGetCurrentVoltage(uint32_t index, uint32_t* out_current_voltage) {
if (index != vs680::kPowerDomainVCpu) {
return ZX_ERR_OUT_OF_RANGE;
}
fbl::AutoLock lock(&lock_);
auto vsel = VSel::Get().FromValue(0);
zx_status_t status = vsel.ReadFrom(pmic_i2c_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Failed to read from VSel: %d", __FILE__, status);
return status;
}
*out_current_voltage = vsel.voltage_microvolts();
return ZX_OK;
}
zx_status_t Vs680Power::PowerImplWritePmicCtrlReg(uint32_t index, uint32_t reg_addr,
uint32_t value) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Vs680Power::PowerImplReadPmicCtrlReg(uint32_t index, uint32_t reg_addr,
uint32_t* out_value) {
return ZX_ERR_NOT_SUPPORTED;
}
} // namespace power
static constexpr zx_driver_ops_t vs680_power_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = power::Vs680Power::Create;
return ops;
}();
ZIRCON_DRIVER(vs680_power, vs680_power_driver_ops, "zircon", "0.1");