| // 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 <fcntl.h> |
| #include <lib/component/incoming/cpp/service_client.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 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`). 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`). |
| 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 represented by devfs node index `4`: |
| $ i2cutil read 4 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 |
| |
| 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 { |
| |
| 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"); |
| } |
| } |
| |
| zx_status_t ping() { |
| constexpr char kI2cDevicePath[] = "/dev/class/i2c"; |
| |
| 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; |
| } |
| |
| } // 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) { |
| zx::result device = component::Connect<fuchsia_hardware_i2c::Device>(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; |
| } |
| |
| printTransactions(args->Transactions()); |
| } else if (args->Op() == i2cutil::I2cOp::Ping) { |
| return ping() == ZX_OK ? 0 : -1; |
| } else if (args->Op() == i2cutil::I2cOp::Help) { |
| usage(true); |
| } else { |
| fprintf(stderr, "i2cutil: Unknown command\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |