blob: 16585790b1ae5828d514aa31e7804c15589a6972 [file] [log] [blame]
/*
* Copyright (c) 2024 Astrolight
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/util.h>
#include <zephyr/devicetree.h>
#define TXRX_ARGV_SPI_DEV (1)
#define TXRX_ARGV_BYTES (2)
#define CONF_ARGV_SPI_DEV (1)
#define CONF_ARGV_FREQUENCY (2)
#define CONF_ARGV_SETTINGS (3)
#define CS_ARGV_SPI_DEV (1)
#define CS_ARGV_GPIO_DEV (2)
#define CS_ARGV_GPIO_PIN (3)
#define CS_ARGV_GPIO_FLAGS (4)
/* Maximum bytes we can write and read at once */
#define MAX_SPI_BYTES MIN((CONFIG_SHELL_ARGC_MAX - TXRX_ARGV_BYTES), 32)
/* Runs the given fn only if the node_id belongs to a spi device, which is on an okay spi bus. */
#define RUN_FN_ON_SPI_DEVICE(node_id, fn) \
COND_CODE_1(DT_ON_BUS(node_id, spi), \
(COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_BUS(node_id)), \
(fn), ())), ())
/* Create specified number of empty structs, separated by ',' */
#define _EMPTY_STRUCT_INST(idx, list) {0}
#define CREATE_NUM_EMPTY_STRUCTS(num) LISTIFY(num, _EMPTY_STRUCT_INST, (,))
/* Struct representing either a spi bus or a spi device. */
struct spi_shell_device {
/* Device name. Either a spi bus or a spi device. */
const char *name;
struct spi_dt_spec spec;
};
/* Struct used to map a label to a name of the associated spi_shell_device. */
struct map {
/* Either nodelabel or device name of either spi bus or spi device. */
const char *label;
/* Device name. This is the same as the name of the associated spi_shell_device struct. */
const char *name;
};
#define INST_SPI_SHELL_DEVICE_AS_SPI_DEV(node_id) \
{ \
.name = DEVICE_DT_NAME(node_id), \
.spec = SPI_DT_SPEC_GET(node_id, 0, 0), \
},
#define INST_SPI_SHELL_DEVICE_AS_SPI_DEV_AS_SPI_BUS(dev) \
(struct spi_shell_device) \
{ \
.name = dev->name, \
.spec = { \
.bus = dev, \
.config = \
{ \
.frequency = 1000000, \
.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8), \
}, \
}, \
}
#define INST_MAP(nodelabel, node_id) \
{.label = STRINGIFY(nodelabel), .name = DEVICE_DT_NAME(node_id)},
#define INST_MAP_FROM_NODE_ID(node_id) \
{.label = DEVICE_DT_NAME(node_id), .name = DEVICE_DT_NAME(node_id)},
/* Instantiate spi_shell_device struct from node_id, if node_id is a spi device. */
#define INST_ALL_SPI_DEVICES_AS_SPI_SHELL_DEVICES(node_id) \
RUN_FN_ON_SPI_DEVICE(node_id, INST_SPI_SHELL_DEVICE_AS_SPI_DEV(node_id))
/* List of spi shell devices. At compile time we instantiate structs for all spi devices.
* Additional empty space is reserved for the spi buses, which are added in spi_buses_init at
* runtime.
*/
static struct spi_shell_device spi_shell_devices[] = {
DT_FOREACH_STATUS_OKAY_NODE(INST_ALL_SPI_DEVICES_AS_SPI_SHELL_DEVICES)
CREATE_NUM_EMPTY_STRUCTS(CONFIG_SPI_SHELL_MAX_DEVICE_SLOTS)};
static size_t num_spi_shell_devices =
ARRAY_SIZE(spi_shell_devices) - CONFIG_SPI_SHELL_MAX_DEVICE_SLOTS;
#define INST_MAPS_FROM_SPI_DEVICE_NODELABELS(node_id) \
RUN_FN_ON_SPI_DEVICE(node_id, DT_FOREACH_NODELABEL_VARGS(node_id, INST_MAP, node_id))
#define INST_MAPS_FROM_SPI_DEVICE_NODE_ID(node_id) \
RUN_FN_ON_SPI_DEVICE(node_id, INST_MAP_FROM_NODE_ID(node_id))
/* A list of maps. At compile time we create maps for all nodelabels and node_ids of spi devices.
* Additional empty space is reserved for the spi buses, which are added in spi_buses_init at
* runtime.
*/
static struct map maps[] = {
DT_FOREACH_STATUS_OKAY_NODE(INST_MAPS_FROM_SPI_DEVICE_NODELABELS)
DT_FOREACH_STATUS_OKAY_NODE(INST_MAPS_FROM_SPI_DEVICE_NODE_ID)
CREATE_NUM_EMPTY_STRUCTS(CONFIG_SPI_SHELL_MAX_DEVICE_SLOTS)};
static size_t num_maps = ARRAY_SIZE(maps) - CONFIG_SPI_SHELL_MAX_DEVICE_SLOTS;
static bool device_is_spi(const struct device *dev)
{
return DEVICE_API_IS(spi, dev);
}
static bool device_is_gpio(const struct device *dev)
{
return DEVICE_API_IS(gpio, dev);
}
/**
* @brief Initialize spi buses at runtime.
*
* Since Zephyr currently doesn't support getting a device for all spi buses at compile time in a
* generic way, we do it at runtime.
*
* For each spi bus device we:
* - add an entry to spi_shell_devices array
* - add an entry to maps array by its name
* - add an entry to maps array for each it's nodelabel
*/
static int spi_buses_init(void)
{
int idx = 0;
while (1) {
const struct device *dev = shell_device_filter(idx, device_is_spi);
idx++;
if (dev == NULL) {
break;
}
if (num_spi_shell_devices == ARRAY_SIZE(spi_shell_devices)) {
printk("ERROR: not enough space in spi_shell_devices array\n");
printk("Increase CONFIG_SPI_SHELL_MAX_DEVICE_SLOTS.\n");
break;
}
spi_shell_devices[num_spi_shell_devices++] =
INST_SPI_SHELL_DEVICE_AS_SPI_DEV_AS_SPI_BUS(dev);
maps[num_maps++] = (struct map){
.label = dev->name,
.name = dev->name,
};
#ifdef CONFIG_DEVICE_DT_METADATA
const struct device_dt_nodelabels *nl = device_get_dt_nodelabels(dev);
if (nl == NULL) {
/* No nodelabel for this device, so we can skip the rest. */
continue;
}
if (num_maps + nl->num_nodelabels > ARRAY_SIZE(maps)) {
printk("ERROR: not enough space in maps array\n");
printk("Increase CONFIG_SPI_SHELL_MAX_DEVICE_SLOTS.\n");
break;
}
for (size_t i = 0; i < nl->num_nodelabels; i++) {
maps[num_maps++] = (struct map){
.label = nl->nodelabels[i],
.name = dev->name,
};
}
#endif
}
if (num_spi_shell_devices == 0) {
printk("ERROR: no spi devices or spi buses are enabled, check devicetree.\n");
}
return 0;
}
/**
* @brief Find spi_dt_spec by label (either nodelabel or nodename).
*
* The label can belong to either a spi bus or a spi device. We first look up the name
* associated with the given label in the maps array. If the name is found, we then search
* the spi_shell_devices array for a matching name and return the corresponding spi_dt_spec.
*
* @param[in] label
*
* @return Pointer to spi_dt_spec if found, NULL otherwise.
*/
static struct spi_dt_spec *find_spec_by_label(const char *label)
{
const char *name = NULL;
static bool initialized;
if (!initialized) {
spi_buses_init();
initialized = true;
}
for (size_t i = 0; i < num_maps; i++) {
if (strcmp(label, maps[i].label) == 0) {
name = maps[i].name;
break;
}
}
if (name == NULL) {
return NULL;
}
for (size_t i = 0; i < num_spi_shell_devices; i++) {
if (strcmp(name, spi_shell_devices[i].name) == 0) {
return &spi_shell_devices[i].spec;
}
}
return NULL;
}
static void get_gpio_device_name(size_t idx, struct shell_static_entry *entry)
{
const struct device *dev = shell_device_filter(idx, device_is_gpio);
entry->syntax = (dev != NULL) ? dev->name : NULL;
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = NULL;
}
SHELL_DYNAMIC_CMD_CREATE(dsub_get_gpio_device_name, get_gpio_device_name);
static void get_spi_shell_device_name_and_set_gpio_dsub(size_t idx,
struct shell_static_entry *entry)
{
if (idx >= num_maps) {
entry->syntax = NULL;
return;
}
entry->syntax = maps[idx].label;
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = &dsub_get_gpio_device_name;
}
SHELL_DYNAMIC_CMD_CREATE(dsub_get_spi_shell_device_name_and_set_gpio_dsub,
get_spi_shell_device_name_and_set_gpio_dsub);
static void get_spi_shell_device_name(size_t idx, struct shell_static_entry *entry)
{
if (idx >= num_maps) {
entry->syntax = NULL;
return;
}
entry->syntax = maps[idx].label;
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = NULL;
}
SHELL_DYNAMIC_CMD_CREATE(dsub_get_spi_shell_device_name, get_spi_shell_device_name);
static int cmd_spi_transceive(const struct shell *ctx, size_t argc, char **argv)
{
uint8_t rx_buffer[MAX_SPI_BYTES] = {0};
uint8_t tx_buffer[MAX_SPI_BYTES] = {0};
struct spi_dt_spec *spec = find_spec_by_label(argv[TXRX_ARGV_SPI_DEV]);
if (spec == NULL) {
shell_error(ctx, "device %s not found.", argv[TXRX_ARGV_SPI_DEV]);
return -ENODEV;
}
int bytes_to_send = argc - TXRX_ARGV_BYTES;
for (int i = 0; i < bytes_to_send; i++) {
tx_buffer[i] = strtol(argv[TXRX_ARGV_BYTES + i], NULL, 16);
}
const struct spi_buf tx_buffers = {.buf = tx_buffer, .len = bytes_to_send};
const struct spi_buf rx_buffers = {.buf = rx_buffer, .len = bytes_to_send};
const struct spi_buf_set tx_buf_set = {.buffers = &tx_buffers, .count = 1};
const struct spi_buf_set rx_buf_set = {.buffers = &rx_buffers, .count = 1};
int ret = spi_transceive_dt(spec, &tx_buf_set, &rx_buf_set);
if (ret < 0) {
shell_error(ctx, "spi_transceive returned %d", ret);
return ret;
}
shell_print(ctx, "TX:");
shell_hexdump(ctx, tx_buffer, bytes_to_send);
shell_print(ctx, "RX:");
shell_hexdump(ctx, rx_buffer, bytes_to_send);
return ret;
}
static int cmd_spi_conf(const struct shell *ctx, size_t argc, char **argv)
{
spi_operation_t operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER;
struct spi_dt_spec *spec = find_spec_by_label(argv[CONF_ARGV_SPI_DEV]);
if (spec == NULL) {
shell_error(ctx, "device %s not found.", argv[CONF_ARGV_SPI_DEV]);
return -ENODEV;
}
uint32_t frequency = strtol(argv[CONF_ARGV_FREQUENCY], NULL, 10);
if (!IN_RANGE(frequency, 100 * 1000, 80 * 1000 * 1000)) {
shell_error(ctx, "frequency must be between 100000 and 80000000");
return -EINVAL;
}
/* no settings */
if (argc == (CONF_ARGV_FREQUENCY + 1)) {
goto out;
}
char *opts = argv[CONF_ARGV_SETTINGS];
bool all_opts_is_valid = true;
while (*opts != '\0') {
switch (*opts) {
case 'o':
operation |= SPI_MODE_CPOL;
break;
case 'h':
operation |= SPI_MODE_CPHA;
break;
case 'l':
operation |= SPI_TRANSFER_LSB;
break;
case 'T':
operation |= SPI_FRAME_FORMAT_TI;
break;
default:
all_opts_is_valid = false;
shell_error(ctx, "invalid setting %c", *opts);
}
opts++;
}
if (!all_opts_is_valid) {
return -EINVAL;
}
out:
spec->config.frequency = frequency;
spec->config.operation = operation;
return 0;
}
static int cmd_spi_conf_cs(const struct shell *ctx, size_t argc, char **argv)
{
struct spi_dt_spec *spec = find_spec_by_label(argv[CS_ARGV_SPI_DEV]);
if (spec == NULL) {
shell_error(ctx, "device %s not found.", argv[CS_ARGV_SPI_DEV]);
return -ENODEV;
}
struct device *dev = (struct device *)shell_device_get_binding(argv[CS_ARGV_GPIO_DEV]);
char *endptr = NULL;
if (dev == NULL) {
shell_error(ctx, "device %s not found.", argv[CS_ARGV_GPIO_DEV]);
return -ENODEV;
}
int pin = strtol(argv[CS_ARGV_GPIO_PIN], &endptr, 10);
if (endptr == argv[CS_ARGV_GPIO_PIN] || (pin < 0)) {
shell_error(ctx, "invalid pin number: %s", argv[CS_ARGV_GPIO_PIN]);
return -EINVAL;
}
spec->config.cs.gpio.port = dev;
spec->config.cs.gpio.pin = pin;
/* Include flags if provided */
if (argc == (CS_ARGV_GPIO_FLAGS + 1)) {
uint32_t flags = strtol(argv[CS_ARGV_GPIO_FLAGS], &endptr, 16);
if (endptr == argv[CS_ARGV_GPIO_FLAGS]) {
shell_error(ctx, "invalid gpio flags: %s", argv[CS_ARGV_GPIO_FLAGS]);
return -EINVAL;
}
spec->config.cs.gpio.dt_flags = flags;
}
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(
sub_spi_cmds,
SHELL_CMD_ARG(conf, &dsub_get_spi_shell_device_name,
"Configure SPI\n"
"Usage: spi conf <spi-device> <frequency> [<settings>]\n"
"<settings> - any sequence of letters:\n"
"o - SPI_MODE_CPOL\n"
"h - SPI_MODE_CPHA\n"
"l - SPI_TRANSFER_LSB\n"
"T - SPI_FRAME_FORMAT_TI\n"
"example: spi conf spi1 1000000 ol",
cmd_spi_conf, 3, 1),
SHELL_CMD_ARG(cs, &dsub_get_spi_shell_device_name_and_set_gpio_dsub,
"Assign CS GPIO to SPI device\n"
"Usage: spi cs <spi-device> <gpio-device> <pin> [<gpio flags>]\n"
"example: spi cs spi1 gpio1 3 0x01",
cmd_spi_conf_cs, 4, 1),
SHELL_CMD_ARG(transceive, &dsub_get_spi_shell_device_name,
"Transceive data to and from an SPI device\n"
"Usage: spi transceive <spi-device> <TX byte 1> [<TX byte 2> ...]\n"
"example: spi transceive spi1 0x00 0x01",
cmd_spi_transceive, 3, MAX_SPI_BYTES - 1),
SHELL_SUBCMD_SET_END);
SHELL_CMD_REGISTER(spi, &sub_spi_cmds, "SPI commands", NULL);