blob: a8fe04f52251c3cc2be3ff02c38b9e8cd64e7160 [file] [log] [blame]
// Copyright 2021 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.
/*
* I2C device handler.
*/
#include "hps/hal/i2c.h"
#include <utility>
#include <vector>
#include <fcntl.h>
#include <stdint.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/files/file.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/numerics/safe_conversions.h>
namespace hps {
namespace {
/*
* Holds a file handle to the HPS device, which causes the kernel module
* to keep the sensor powered as long as the file handle exists.
*
* If the HPS kernel driver isn't loaded or the device node doesn't exist, we
* assume the hardware is always powered on and disable power management in
* hpsd.
*/
class WakeLockImpl : public WakeLock {
public:
explicit WakeLockImpl(const base::FilePath& power_control)
: power_file_(power_control,
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE) {
if (!power_file_.IsValid())
PLOG(ERROR) << "Unable to create wake lock: \"" << power_control << "\"";
}
~WakeLockImpl() override = default;
private:
base::File power_file_;
};
} // namespace
I2CDev::I2CDev(const std::string& bus,
uint8_t addr,
const base::FilePath& power_control)
: bus_(bus), power_control_(power_control), address_(addr), fd_(-1) {}
int I2CDev::Open() {
if (this->bus_.empty()) {
LOG(ERROR) << "Empty i2c path: \"" << this->bus_ << "\"";
return -1;
}
if (!power_control_.empty() && !base::PathExists(power_control_)) {
LOG(WARNING) << "Bad power control file, disabling power management: \""
<< power_control_ << "\"";
power_control_.clear();
}
this->fd_ = open(this->bus_.c_str(), O_RDWR);
if (this->fd_ < 0) {
PLOG(ERROR) << "Cannot open: \"" << this->bus_ << "\"";
}
return this->fd_;
}
bool I2CDev::ReadDevice(uint8_t cmd, uint8_t* data, size_t len) {
struct i2c_msg m[2];
m[0].addr = this->address_;
m[0].flags = 0;
m[0].len = sizeof(cmd);
m[0].buf = &cmd;
m[1].addr = this->address_;
m[1].flags = I2C_M_RD;
m[1].len = base::checked_cast<uint16_t>(len);
m[1].buf = data;
return this->Ioc(m, sizeof(m) / sizeof(m[0]));
}
bool I2CDev::WriteDevice(uint8_t cmd, const uint8_t* data, size_t len) {
struct i2c_msg m[1];
std::vector<uint8_t> buffer;
buffer.reserve(len + 1);
buffer.push_back(cmd);
buffer.insert(buffer.end(), data, data + len);
m[0].addr = this->address_;
m[0].flags = I2C_M_STOP;
m[0].len = base::checked_cast<uint16_t>(buffer.size());
m[0].buf = buffer.data();
return this->Ioc(m, sizeof(m) / sizeof(m[0]));
}
bool I2CDev::Ioc(struct i2c_msg* msg, size_t count) {
struct i2c_rdwr_ioctl_data ioblk;
ioblk.msgs = msg;
ioblk.nmsgs = static_cast<uint32_t>(count);
int ret = ioctl(this->fd_, I2C_RDWR, &ioblk);
if (ret < 0) {
VPLOG(3) << "i2c read/write failed";
}
return ret != -1;
}
std::unique_ptr<WakeLock> I2CDev::CreateWakeLock() {
if (!power_control_.empty())
return std::make_unique<WakeLockImpl>(power_control_);
return DevInterface::CreateWakeLock();
}
// Static factory method.
std::unique_ptr<DevInterface> I2CDev::Create(const std::string& bus,
uint8_t addr,
const std::string& power_control) {
// Use new so that private constructor can be accessed.
auto i2c_dev = std::unique_ptr<I2CDev>(
new I2CDev(bus, addr, base::FilePath(power_control)));
CHECK_GE(i2c_dev->Open(), 0);
return std::unique_ptr<DevInterface>(std::move(i2c_dev));
}
} // namespace hps