diff --git a/drivers/smbus/CMakeLists.txt b/drivers/smbus/CMakeLists.txt index a2d2e75e489..b4e9718daf9 100644 --- a/drivers/smbus/CMakeLists.txt +++ b/drivers/smbus/CMakeLists.txt @@ -2,5 +2,6 @@ zephyr_library() +zephyr_library_sources_ifdef(CONFIG_SMBUS_SHELL smbus_shell.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE smbus_handlers.c) diff --git a/drivers/smbus/Kconfig b/drivers/smbus/Kconfig index 808aa74845f..5e7efa85fb9 100644 --- a/drivers/smbus/Kconfig +++ b/drivers/smbus/Kconfig @@ -10,6 +10,13 @@ menuconfig SMBUS if SMBUS +config SMBUS_SHELL + bool "SMBus Shell" + default y + depends on SHELL + help + Enable SMBus Shell. + config SMBUS_STATS bool "SMBus device Stats" depends on STATS diff --git a/drivers/smbus/smbus_shell.c b/drivers/smbus/smbus_shell.c new file mode 100644 index 00000000000..9ecbefa495b --- /dev/null +++ b/drivers/smbus/smbus_shell.c @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2018 Prevas A/S + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(smbus_shell, CONFIG_LOG_DEFAULT_LEVEL); + +/** + * smbus_shell is a highly modified version from i2c_shell. Basically only scan + * logic remains from i2c_shell + */ + +/** + * Simplify argument parsing, smbus arguments always go in this order: + * smbus + */ +#define ARGV_DEV 1 +#define ARGV_ADDR 2 +#define ARGV_CMD 3 + +/** + * This sends SMBUS messages without any data (i.e. stop condition after + * sending just the address). If there is an ACK for the address, it + * is assumed there is a device present. + * + * WARNING: As there is no standard SMBUS detection command, this code + * uses arbitrary SMBus commands (namely SMBus quick write to probe for + * devices. + * This operation can confuse your SMBUS bus, cause data loss, and is + * known to corrupt the Atmel AT24RF08 EEPROM found on many IBM + * Thinkpad laptops. + * + * https://manpages.debian.org/buster/i2c-tools/i2cdetect.8.en.html + */ + +/* smbus scan */ +static int cmd_smbus_scan(const struct shell *sh, size_t argc, char **argv) +{ + const struct device *dev; + uint8_t cnt = 0, first = 0x04, last = 0x77; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + shell_print(sh, " 0 1 2 3 4 5 6 7 8 9 a b c d e f"); + for (uint8_t i = 0; i <= last; i += 16) { + shell_fprintf(sh, SHELL_NORMAL, "%02x: ", i); + + for (uint8_t j = 0; j < 16; j++) { + if (i + j < first || i + j > last) { + shell_fprintf(sh, SHELL_NORMAL, " "); + continue; + } + + if (smbus_quick(dev, i + j, SMBUS_MSG_WRITE) == 0) { + shell_fprintf(sh, SHELL_NORMAL, "%02x ", i + j); + ++cnt; + } else { + shell_fprintf(sh, SHELL_NORMAL, "-- "); + } + } + + shell_print(sh, ""); + } + + shell_print(sh, "%u devices found on %s", cnt, argv[ARGV_DEV]); + + return 0; +} + +/* smbus quick */ +static int cmd_smbus_quick(const struct shell *sh, size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + + ret = smbus_quick(dev, addr, SMBUS_MSG_WRITE); + if (ret < 0) { + shell_error(sh, "SMBus: Failed quick cmd, perip: 0x%02x", addr); + } + + return ret; +} + +/* smbus byte_read */ +static int cmd_smbus_byte_read(const struct shell *sh, size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr; + uint8_t out; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + + ret = smbus_byte_read(dev, addr, &out); + if (ret < 0) { + shell_error(sh, "SMBus: Failed to read from periph: 0x%02x", + addr); + return -EIO; + } + + shell_print(sh, "Output: 0x%x", out); + + return 0; +} + +/* smbus byte_write */ +static int cmd_smbus_byte_write(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr; + uint8_t value; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + /* First byte is command */ + value = strtol(argv[ARGV_CMD], NULL, 16); + + ret = smbus_byte_write(dev, addr, value); + if (ret < 0) { + shell_error(sh, "SMBus: Failed to write to periph: 0x%02x", + addr); + return -EIO; + } + + return 0; +} + +/* smbus byte_data_read */ +static int cmd_smbus_byte_data_read(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr, command; + uint8_t out; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + command = strtol(argv[ARGV_CMD], NULL, 16); + + ret = smbus_byte_data_read(dev, addr, command, &out); + if (ret < 0) { + shell_error(sh, "SMBus: Failed to read from periph: 0x%02x", + addr); + return -EIO; + } + + shell_print(sh, "Output: 0x%x", out); + + return 0; +} + +/* smbus byte_data_write */ +static int cmd_smbus_byte_data_write(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr, command; + uint8_t value; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + command = strtol(argv[ARGV_CMD], NULL, 16); + value = strtol(argv[4], NULL, 16); + + ret = smbus_byte_data_write(dev, addr, command, value); + if (ret < 0) { + shell_error(sh, "SMBus: Failed to write to periph: 0x%02x", + addr); + return -EIO; + } + + return 0; +} + +/* smbus word_data_read */ +static int cmd_smbus_word_data_read(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr, command; + uint16_t out; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + command = strtol(argv[ARGV_CMD], NULL, 16); + + ret = smbus_word_data_read(dev, addr, command, &out); + if (ret < 0) { + shell_error(sh, "SMBus: Failed to read from periph: 0x%02x", + addr); + return -EIO; + } + + shell_print(sh, "Output: 0x%04x", out); + + return 0; +} + +/* smbus word_data_write */ +static int cmd_smbus_word_data_write(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr, command; + uint16_t value; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + command = strtol(argv[ARGV_CMD], NULL, 16); + value = strtol(argv[4], NULL, 16); + + ret = smbus_word_data_write(dev, addr, command, value); + if (ret < 0) { + shell_error(sh, "SMBus: Failed to write to periph: 0x%02x", + addr); + return -EIO; + } + + return 0; +} + +/* smbus block_write */ +static int cmd_smbus_block_write(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr, command; + uint8_t count = argc - 4; + char **p = &argv[4]; /* start data bytes */ + uint8_t buf[32]; /* max block count */ + int ret; + + if (count == 0 || count > sizeof(buf)) { + return -EINVAL; + } + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + command = strtol(argv[ARGV_CMD], NULL, 16); + + for (int i = 0; i < count; i++) { + buf[i] = (uint8_t)strtoul(p[i], NULL, 16); + } + + LOG_HEXDUMP_DBG(buf, count, "Constructed block buffer"); + + ret = smbus_block_write(dev, addr, command, count, buf); + if (ret < 0) { + shell_error(sh, "Failed block write to periph: 0x%02x", + addr); + return ret; + } + + return 0; +} + +/* smbus block_read */ +static int cmd_smbus_block_read(const struct shell *sh, + size_t argc, char **argv) +{ + const struct device *dev; + uint8_t addr, command; + uint8_t buf[32]; /* max block count */ + uint8_t count; + int ret; + + dev = device_get_binding(argv[ARGV_DEV]); + if (!dev) { + shell_error(sh, "SMBus: Device %s not found", argv[ARGV_DEV]); + return -ENODEV; + } + + addr = strtol(argv[ARGV_ADDR], NULL, 16); + command = strtol(argv[ARGV_CMD], NULL, 16); + + ret = smbus_block_read(dev, addr, command, &count, buf); + if (ret < 0) { + shell_error(sh, "Failed block read from periph: 0x%02x", + addr); + return ret; + } + + if (count == 0 || count > sizeof(buf)) { + shell_error(sh, "Returned count %u", count); + return -ENODATA; + } + + shell_hexdump(sh, buf, count); + + return 0; +} + +/* Device name autocompletion support */ +static void device_name_get(size_t idx, struct shell_static_entry *entry) +{ + const struct device *dev = shell_device_lookup(idx, "smbus"); + + entry->syntax = (dev != NULL) ? dev->name : NULL; + entry->handler = NULL; + entry->help = NULL; + entry->subcmd = NULL; +} + +SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get); + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_smbus_cmds, + SHELL_CMD_ARG(quick, &dsub_device_name, + "SMBus Quick command\n" + "Usage: quick ", + cmd_smbus_quick, 3, 0), + SHELL_CMD_ARG(scan, &dsub_device_name, + "Scan SMBus peripheral devices command\n" + "Usage: scan ", + cmd_smbus_scan, 2, 0), + SHELL_CMD_ARG(byte_read, &dsub_device_name, + "SMBus: byte read command\n" + "Usage: byte_read ", + cmd_smbus_byte_read, 3, 0), + SHELL_CMD_ARG(byte_write, &dsub_device_name, + "SMBus: byte write command\n" + "Usage: byte_write ", + cmd_smbus_byte_write, 4, 0), + SHELL_CMD_ARG(byte_data_read, &dsub_device_name, + "SMBus: byte data read command\n" + "Usage: byte_data_read ", + cmd_smbus_byte_data_read, 4, 0), + SHELL_CMD_ARG(byte_data_write, &dsub_device_name, + "SMBus: byte data write command\n" + "Usage: byte_data_write ", + cmd_smbus_byte_data_write, 5, 0), + SHELL_CMD_ARG(word_data_read, &dsub_device_name, + "SMBus: word data read command\n" + "Usage: word_data_read ", + cmd_smbus_word_data_read, 4, 0), + SHELL_CMD_ARG(word_data_write, &dsub_device_name, + "SMBus: word data write command\n" + "Usage: word_data_write ", + cmd_smbus_word_data_write, 5, 0), + SHELL_CMD_ARG(block_write, &dsub_device_name, + "SMBus: Block Write command\n" + "Usage: block_write [, ...]", + cmd_smbus_block_write, 4, 32), + SHELL_CMD_ARG(block_read, &dsub_device_name, + "SMBus: Block Read command\n" + "Usage: block_read ", + cmd_smbus_block_read, 4, 0), + SHELL_SUBCMD_SET_END /* Array terminated. */ +); + +SHELL_CMD_REGISTER(smbus, &sub_smbus_cmds, "smbus commands", NULL);