415 lines
10 KiB
C
415 lines
10 KiB
C
/*
|
|
* Copyright (c) 2018 Prevas A/S
|
|
* Copyright (c) 2022 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <zephyr/sys/slist.h>
|
|
#include <zephyr/drivers/smbus.h>
|
|
#include <zephyr/shell/shell.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
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 <shell command> <device> <peripheral address> <command byte>
|
|
*/
|
|
#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 <device> */
|
|
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 <device> <dev_addr> */
|
|
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 <device> <dev_addr> */
|
|
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 <device> <dev_addr> <value> */
|
|
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 <device> <dev_addr> <cmd> */
|
|
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 <device> <dev_addr> <cmd> <value> */
|
|
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 <device> <dev_addr> <cmd> */
|
|
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 <device> <dev_addr> <cmd> <value> */
|
|
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 <device> <dev_addr> <cmd> <bytes ... > */
|
|
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 <device> <dev_addr> <cmd> */
|
|
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 <device> <addr>",
|
|
cmd_smbus_quick, 3, 0),
|
|
SHELL_CMD_ARG(scan, &dsub_device_name,
|
|
"Scan SMBus peripheral devices command\n"
|
|
"Usage: scan <device>",
|
|
cmd_smbus_scan, 2, 0),
|
|
SHELL_CMD_ARG(byte_read, &dsub_device_name,
|
|
"SMBus: byte read command\n"
|
|
"Usage: byte_read <device> <addr>",
|
|
cmd_smbus_byte_read, 3, 0),
|
|
SHELL_CMD_ARG(byte_write, &dsub_device_name,
|
|
"SMBus: byte write command\n"
|
|
"Usage: byte_write <device> <addr> <value>",
|
|
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 <device> <addr> <cmd>",
|
|
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 <device> <addr> <cmd> <value>",
|
|
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 <device> <addr> <cmd>",
|
|
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 <device> <addr> <cmd> <value>",
|
|
cmd_smbus_word_data_write, 5, 0),
|
|
SHELL_CMD_ARG(block_write, &dsub_device_name,
|
|
"SMBus: Block Write command\n"
|
|
"Usage: block_write <device> <addr> <cmd> [<byte1>, ...]",
|
|
cmd_smbus_block_write, 4, 32),
|
|
SHELL_CMD_ARG(block_read, &dsub_device_name,
|
|
"SMBus: Block Read command\n"
|
|
"Usage: block_read <device> <addr> <cmd>",
|
|
cmd_smbus_block_read, 4, 0),
|
|
SHELL_SUBCMD_SET_END /* Array terminated. */
|
|
);
|
|
|
|
SHELL_CMD_REGISTER(smbus, &sub_smbus_cmds, "smbus commands", NULL);
|