| // 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 |