blob: c23b2266ae8507c0b63ed97eeea5a0224f8e4ed7 [file] [log] [blame]
// Copyright 2022 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 <dirent.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/zx/channel.h>
#include <stdio.h>
#include <filesystem>
#include "args.h"
#include "i2cutil2.h"
// LINT.IfChange
constexpr char kUsageSummary[] = R"""(
Usage:
i2cutil read <device> <address> [<address>...]
i2cutil write <device> <address> [<address>...] <data> [<data>...]
i2cutil transact <device> (r <bytes>|w <address> [<address>...] [<data>...])...
i2cutil dump <device> <address> <count>
i2cutil list
i2cutil ping
i2cutil help
)""";
constexpr char kUsageDetails[] = R"""(
Commands:
read | r Read one byte from an I2C device. Use `i2cutil transact`
to read multiple bytes. <device> can be the full path of
a devfs node (example: `/dev/class/i2c/031`) or only the
devfs node's index (example: `31`) or it can simply be the
device's friendly named obtained via `i2cutil list`
(example: `pmic`). Use `i2cutil ping` to get devfs node
paths and indexes. <address> is the internal register of
<device> to read from. Use multiple <address> values to
access a multi-byte (little-endian) register address.
For example `i2cutil read 4 0x20 0x3D` to read the
register at `0x203D`.
write | w Write one or more bytes (<data>) to an I2C device. See the
`i2cutil read` description for explanations of <device>
and <address>.
transact | t Perform a transaction with multiple segments. Each segment
can be a write (`w`) or a read (`r`).
dump | d Reads and prints <count> registers from <device> starting
at the address indicated by <address>.
list | l List all the I2C devices available on the system.
ping | p Ping all I2C devices under devfs path `/dev/class/i2c` by
reading from each device's 0x00 address.
help | h Print this help text.
Examples:
Read one byte from the register at `0x20` of the I2C device represented by
devfs node index `4`:
$ i2cutil read 4 0x20
Read three bytes from the same I2C device and register as the last example:
$ i2cutil transact 4 w 0x20 r 3
Read one byte from the register at the multi-byte address `0x203D` of the
I2C device named "temp_sensor":
$ i2cutil read temp_sensor 0x20 0x3D
Same as the last example but represent the I2C device with a devfs node path:
$ i2cutil read /dev/class/i2c/004 0x20 0x3D
Write byte `0x12` to the register at `0x2C` of the I2C device represented by
devfs node index `3`:
$ i2cutil write 3 0x2C 0x12
Write byte `0x121B` to the same device and register as the last example:
$ i2cutil write 3 0x2C 0x12 0x1B
Write byte `0x1B to register `0x2C12` of a different device (note that
this is the exact same command as the last example; the meaning of the
arguments depends on the I2C device):
$ i2cutil write 3 0x2C 0x12 0x1B
Ping all I2C devices:
$ i2cutil ping
/dev/class/i2c/821: OK
/dev/class/i2c/822: OK
/dev/class/i2c/823: OK
/dev/class/i2c/824: OK
Error ZX_ERR_TIMED_OUT
/dev/class/i2c/825: ERROR
List all I2C devices:
$ i2cutil list
159: pmic
160: (ANONYMOUS)
161: temp_sensor
Print 3 registers from the device named `pmic` starting at address 0x10:
$ i2cutil dump pmic 0x10 3
0x10: 0x41
0x11: 0x00
0x12: 0x00
Notes:
Source code for `i2cutil`: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/devices/i2c/bin/i2cutil.cc
)""";
// LINT.ThenChange(//docs/reference/tools/hardware/i2cutil.md)
namespace {
constexpr char kI2cDevicePath[] = "/dev/class/i2c";
void usage(bool show_details) {
printf(kUsageSummary);
if (!show_details) {
printf("\nUse `i2cutil help` to see full help text\n");
} else {
printf(kUsageDetails);
}
}
void printBytes(const std::vector<uint8_t>& bytes) {
for (const uint8_t b : bytes) {
printf("0x%02x ", b);
}
}
void printTransactions(const std::vector<i2cutil::TransactionData>& transactions) {
for (const auto& transaction : transactions) {
if (transaction.type == i2cutil::TransactionType::Read) {
printf("Read: ");
} else if (transaction.type == i2cutil::TransactionType::Write) {
printf("Write: ");
}
printBytes(transaction.bytes);
printf("\n");
}
}
void printDump(const std::vector<i2cutil::TransactionData>& transactions) {
// An I2C dump will be a sequence of alternating writes and reads.
// {W R W R W R W R}: Each write represents the address of the register
// being read and the following read will represent the data read back from
// that register.
// Start by ensuring that the input conforms to this spec.
ZX_ASSERT(transactions.size() % 2 == 0);
for (size_t i = 0; i < transactions.size(); i++) {
if (i % 2 == 0) {
ZX_ASSERT(transactions[i].type == i2cutil::TransactionType::Write);
printf("0x%02x: ", transactions[i].bytes.front());
} else {
ZX_ASSERT(transactions[i].type == i2cutil::TransactionType::Read);
printf("0x%02x\n", transactions[i].bytes.front());
}
}
}
zx_status_t ping() {
for (auto& dev_path : std::filesystem::directory_iterator(kI2cDevicePath)) {
i2cutil::TransactionData wr;
wr.type = i2cutil::TransactionType::Write;
wr.bytes.push_back(0x00);
i2cutil::TransactionData rd;
rd.type = i2cutil::TransactionType::Read;
rd.count = 1;
std::vector<i2cutil::TransactionData> txns = {wr, rd};
zx::result device = component::Connect<fuchsia_hardware_i2c::Device>(dev_path.path().c_str());
if (device.is_error()) {
printf("%s: ERROR (%s)\n", dev_path.path().c_str(), device.status_string());
continue;
}
zx_status_t result = execute(std::move(device.value()), txns);
if (result != ZX_OK) {
printf("%s: ERROR (%s)\n", dev_path.path().c_str(), zx_status_get_string(result));
continue;
}
printf("%s: OK\n", dev_path.path().c_str());
}
return ZX_OK;
}
zx_status_t list() {
for (auto& dev_path : std::filesystem::directory_iterator(kI2cDevicePath)) {
zx::result device = component::Connect<fuchsia_hardware_i2c::Device>(dev_path.path().c_str());
if (device.is_error()) {
printf("%s: ERROR (%s)\n", dev_path.path().filename().c_str(), device.status_string());
continue;
}
// Try to get the name of the device.
const fidl::WireResult result = fidl::WireCall(device.value())->GetName();
if (!result.ok()) {
printf("%s: ERROR (%s)\n", dev_path.path().filename().c_str(), result.status_string());
continue;
}
const fit::result response = result.value();
if (response.is_error()) {
if (response.error_value() == ZX_ERR_NOT_SUPPORTED) {
// ZX_ERR_NOT_SUPPORTED is part of the i2c.fidl contract and means that
// the device did not return a friendly name.
printf("%s: (ANONYMOUS)\n", dev_path.path().filename().c_str());
} else {
printf("%s: ERROR (%s)\n", dev_path.path().filename().c_str(),
zx_status_get_string(response.error_value()));
}
continue;
}
const std::string name(response.value()->name.get());
printf("%s: %s\n", dev_path.path().filename().c_str(), name.c_str());
}
return ZX_OK;
}
zx::result<fidl::ClientEnd<fuchsia_hardware_i2c::Device>> get_device_by_name(
const std::string& target) {
for (auto& dev_path : std::filesystem::directory_iterator(kI2cDevicePath)) {
zx::result device = component::Connect<fuchsia_hardware_i2c::Device>(dev_path.path().c_str());
if (device.is_error()) {
continue;
}
const fidl::WireResult result = fidl::WireCall(device.value())->GetName();
if (!result.ok()) {
continue;
}
const fit::result response = result.value();
if (response.is_error()) {
continue;
}
const std::string name(response.value()->name.get());
if (name == target) {
return zx::ok(std::move(device.value()));
}
}
return zx::error(ZX_ERR_NOT_FOUND);
}
} // namespace
int main(int argc, const char* argv[]) {
auto args = i2cutil::Args::FromArgv(argc, argv);
if (args.is_error()) {
usage(false);
return -1;
}
if (args->Op() == i2cutil::I2cOp::Read || args->Op() == i2cutil::I2cOp::Write ||
args->Op() == i2cutil::I2cOp::Transact || args->Op() == i2cutil::I2cOp::Dump) {
zx::result<fidl::ClientEnd<fuchsia_hardware_i2c::Device>> device;
if (std::filesystem::exists(args->Path())) {
device = component::Connect<fuchsia_hardware_i2c::Device>(args->Path());
} else {
device = get_device_by_name(args->Path());
}
if (device.is_error()) {
fprintf(stderr, "i2cutil: Failed to connect to device: %s\n", device.status_string());
return -1;
}
zx_status_t result = execute(std::move(device.value()), args->Transactions());
if (result != ZX_OK) {
fprintf(stderr, "i2cutil: Failed to execute transactions, st = %s\n",
zx_status_get_string(result));
return -1;
}
if (args->Op() == i2cutil::I2cOp::Dump) {
printDump(args->Transactions());
} else {
printTransactions(args->Transactions());
}
} else if (args->Op() == i2cutil::I2cOp::Ping) {
return ping() == ZX_OK ? 0 : -1;
} else if (args->Op() == i2cutil::I2cOp::List) {
return list() == ZX_OK ? 0 : -1;
} else if (args->Op() == i2cutil::I2cOp::Help) {
usage(true);
} else {
fprintf(stderr, "i2cutil: Unknown command\n");
return -1;
}
return 0;
}