blob: 319e1288cbbd2c8494de5041eaceab69f1c2dcb1 [file] [edit]
/*
* Copyright (c) 2025 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Haptics shell commands.
*/
#include <stdlib.h>
#include <string.h>
#include <zephyr/drivers/haptics.h>
#include <zephyr/shell/shell.h>
#include <zephyr/shell/shell_string_conv.h>
#define HAPTICS_ARGS_MAX CONFIG_HAPTICS_SHELL_BUFFER_SIZE
#define HAPTICS_CALIBRATE_HELP SHELL_HELP("Run calibration", "<device> [<routine>]")
#define HAPTICS_GET_HELP SHELL_HELP("Get monitor reading", "<device> <monitor> <type>")
#define HAPTICS_LEVEL_HELP SHELL_HELP("Set output level", "<device> <source> [<idx>] <level>")
#define HAPTICS_SELECT_HELP SHELL_HELP("Select haptic source", "<device> <source> [<idx>]")
#define HAPTICS_SET_HELP SHELL_HELP("Turn monitor on/off", "<device> <monitor> <enable>")
#define HAPTICS_START_HELP SHELL_HELP("Start haptic output", "<device>")
#define HAPTICS_STOP_HELP SHELL_HELP("Stop haptic output", "<device>")
#define HAPTICS_STREAM_HELP SHELL_HELP("Stream samples", "<device> [<byte1> <byte2> ...]")
#define HAPTICS_MONITORS bemf, current, f0, re, ambient_temp, die_temp, vbat, vbst, vout
#define HAPTICS_MONITORS_ALL HAPTICS_MONITORS, all
#define HAPTICS_GET min, max, mean, single
#define HAPTICS_SET disable, enable
#define HAPTICS_SOURCES rom, ram, dai, analog, pwm, control_port, all
#define HAPTICS_ARGS_DEVICE 1
#define HAPTICS_ARGS_MONITOR 2
#define HAPTICS_ARGS_ROUTINE 2
#define HAPTICS_ARGS_SOURCE 2
#define HAPTICS_ARGS_STREAM_START 2
#define HAPTICS_ARGS_ID_OR_LEVEL 3
#define HAPTICS_ARGS_MONITOR_OPTION 3
#define HAPTICS_ARGS_LEVEL 4
static const char *const monitor_units[] = {
[HAPTICS_MONITOR_BEMF] = " V", [HAPTICS_MONITOR_CURRENT] = " A",
[HAPTICS_MONITOR_F0] = " Hz", [HAPTICS_MONITOR_RE] = " Ohms",
[HAPTICS_MONITOR_AMBIENT_TEMP] = " C", [HAPTICS_MONITOR_DIE_TEMP] = " C",
[HAPTICS_MONITOR_VBAT] = " V", [HAPTICS_MONITOR_VBST] = " V",
[HAPTICS_MONITOR_VOUT] = " V",
};
static int monitor_lookup(const char *const arg, enum haptics_monitor *const monitor)
{
int ret;
if (strcmp(arg, "bemf") == 0) {
*monitor = HAPTICS_MONITOR_BEMF;
} else if (strcmp(arg, "current") == 0) {
*monitor = HAPTICS_MONITOR_CURRENT;
} else if (strcmp(arg, "f0") == 0) {
*monitor = HAPTICS_MONITOR_F0;
} else if (strcmp(arg, "re") == 0) {
*monitor = HAPTICS_MONITOR_RE;
} else if (strcmp(arg, "ambient_temp") == 0) {
*monitor = HAPTICS_MONITOR_AMBIENT_TEMP;
} else if (strcmp(arg, "die_temp") == 0) {
*monitor = HAPTICS_MONITOR_DIE_TEMP;
} else if (strcmp(arg, "vout") == 0) {
*monitor = HAPTICS_MONITOR_VOUT;
} else if (strcmp(arg, "vbat") == 0) {
*monitor = HAPTICS_MONITOR_VBAT;
} else if (strcmp(arg, "vbst") == 0) {
*monitor = HAPTICS_MONITOR_VBST;
} else if (strcmp(arg, "all") == 0) {
*monitor = HAPTICS_MONITOR_ALL;
} else {
*monitor = shell_strtol(arg, 10, &ret);
if (ret < 0) {
return -EINVAL;
}
}
return 0;
}
static int source_lookup(const char *const arg, enum haptics_source *const src)
{
int ret;
if (strcmp(arg, "rom") == 0) {
*src = HAPTICS_SOURCE_ROM;
} else if (strcmp(arg, "ram") == 0) {
*src = HAPTICS_SOURCE_RAM;
} else if (strcmp(arg, "dai") == 0) {
*src = HAPTICS_SOURCE_DAI;
} else if (strcmp(arg, "analog") == 0) {
*src = HAPTICS_SOURCE_ANALOG;
} else if (strcmp(arg, "pwm") == 0) {
*src = HAPTICS_SOURCE_PWM;
} else if (strcmp(arg, "control_port") == 0) {
*src = HAPTICS_SOURCE_CONTROL_PORT;
} else if (strcmp(arg, "all") == 0) {
*src = HAPTICS_SOURCE_ALL;
} else {
*src = shell_strtol(arg, 10, &ret);
if (ret < 0) {
return -EINVAL;
}
}
return 0;
}
static int cmd_calibrate(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev;
uint32_t routine = 0;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
if (argc > HAPTICS_ARGS_ROUTINE) {
routine = shell_strtoul(argv[HAPTICS_ARGS_ROUTINE], 10, &ret);
if (ret < 0) {
shell_error(sh, "failed to parse calibration routine");
return ret;
}
}
ret = haptics_calibrate(dev, routine);
if (ret < 0) {
shell_error(sh, "failed to calibrate haptic driver (%d)", ret);
}
return ret;
}
static int cmd_get(const struct shell *sh, size_t argc, char **argv)
{
enum haptics_monitor monitor;
const struct device *dev;
struct sensor_value val;
int custom, ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
ret = monitor_lookup(argv[HAPTICS_ARGS_MONITOR], &monitor);
if (ret < 0) {
shell_error(sh, "invalid monitor '%s'", argv[HAPTICS_ARGS_MONITOR]);
return ret;
}
if (monitor == HAPTICS_MONITOR_ALL) {
shell_error(sh, "invalid monitor 'all' for haptics_monitor_get()");
return -EINVAL;
}
if (strcmp(argv[HAPTICS_ARGS_MONITOR_OPTION], "min") == 0) {
ret = haptics_monitor_get(dev, monitor, HAPTICS_MONITOR_TYPE_MIN, &val);
} else if (strcmp(argv[HAPTICS_ARGS_MONITOR_OPTION], "max") == 0) {
ret = haptics_monitor_get(dev, monitor, HAPTICS_MONITOR_TYPE_MAX, &val);
} else if (strcmp(argv[HAPTICS_ARGS_MONITOR_OPTION], "mean") == 0) {
ret = haptics_monitor_get(dev, monitor, HAPTICS_MONITOR_TYPE_MEAN, &val);
} else if (strcmp(argv[HAPTICS_ARGS_MONITOR_OPTION], "single") == 0) {
ret = haptics_monitor_get(dev, monitor, HAPTICS_MONITOR_TYPE_SINGLE, &val);
} else {
custom = shell_strtol(argv[HAPTICS_ARGS_MONITOR_OPTION], 10, &ret);
if (ret < 0) {
return -EINVAL;
}
ret = haptics_monitor_get(dev, monitor, custom, &val);
}
if (ret < 0) {
shell_error(sh, "failed to get sensor reading (%d)", ret);
return ret;
}
shell_print(sh, "%s%d.%.6d%s", (val.val1 < 0 || val.val2 < 0) ? "-" : "", abs(val.val1),
abs(val.val2),
(monitor < HAPTICS_MONITOR_PRIV_START) ? monitor_units[monitor] : "");
return 0;
}
static int cmd_level(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev;
union haptics_config cfg;
enum haptics_source src;
uint32_t level;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
ret = source_lookup(argv[HAPTICS_ARGS_SOURCE], &src);
if (ret < 0) {
shell_error(sh, "invalid source '%s'", argv[HAPTICS_ARGS_SOURCE]);
return ret;
}
if (argc > HAPTICS_ARGS_LEVEL) {
cfg.idx = shell_strtoul(argv[HAPTICS_ARGS_ID_OR_LEVEL], 10, &ret);
if (ret < 0) {
shell_error(sh, "failed to parse idx (%d)", ret);
return ret;
}
level = shell_strtoul(argv[HAPTICS_ARGS_LEVEL], 10, &ret);
} else {
level = shell_strtoul(argv[HAPTICS_ARGS_ID_OR_LEVEL], 10, &ret);
}
if (ret < 0) {
shell_error(sh, "failed to parse level (%d)", ret);
return ret;
}
ret = haptics_set_level(dev, src, (argc > HAPTICS_ARGS_LEVEL) ? &cfg : NULL, level);
if (ret < 0) {
shell_error(sh, "failed to set level (%d)", ret);
}
return ret;
}
static int cmd_select(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev;
union haptics_config cfg;
enum haptics_source src;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
ret = source_lookup(argv[HAPTICS_ARGS_SOURCE], &src);
if (ret < 0) {
shell_error(sh, "invalid source '%s'", argv[HAPTICS_ARGS_SOURCE]);
return ret;
}
if (argc > HAPTICS_ARGS_ID_OR_LEVEL) {
cfg.idx = shell_strtoul(argv[HAPTICS_ARGS_ID_OR_LEVEL], 10, &ret);
if (ret < 0) {
shell_error(sh, "failed to parse idx (%d)", ret);
return ret;
}
}
ret = haptics_select_source(dev, src, (argc > HAPTICS_ARGS_ID_OR_LEVEL) ? &cfg : NULL);
if (ret < 0) {
shell_error(sh, "failed to select playback source (%d)", ret);
}
return ret;
}
static int cmd_set(const struct shell *sh, size_t argc, char **argv)
{
enum haptics_monitor monitor;
const struct device *dev;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
ret = monitor_lookup(argv[HAPTICS_ARGS_MONITOR], &monitor);
if (ret < 0) {
shell_error(sh, "invalid monitor '%s'", argv[HAPTICS_ARGS_MONITOR]);
return ret;
}
if (strcmp(argv[HAPTICS_ARGS_MONITOR_OPTION], "disable") == 0) {
ret = haptics_monitor_set(dev, monitor, false);
} else if (strcmp(argv[HAPTICS_ARGS_MONITOR_OPTION], "enable") == 0) {
ret = haptics_monitor_set(dev, monitor, true);
} else {
shell_error(sh, "invalid monitor option '%s'", argv[HAPTICS_ARGS_MONITOR_OPTION]);
return -EINVAL;
}
if (ret < 0) {
shell_error(sh, "failed to configure monitor (%d)", ret);
}
return ret;
}
static int cmd_start(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
ret = haptics_start_output(dev);
if (ret < 0) {
shell_error(sh, "failed to start haptic output (%d)", ret);
}
return ret;
}
static int cmd_stop(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
ret = haptics_stop_output(dev);
if (ret < 0) {
shell_error(sh, "failed to stop haptic output (%d)", ret);
}
return ret;
}
static int cmd_stream(const struct shell *sh, size_t argc, char **argv)
{
uint8_t samples[HAPTICS_ARGS_MAX];
const struct device *dev;
uint32_t byte;
int ret;
dev = shell_device_get_binding(argv[HAPTICS_ARGS_DEVICE]);
if (dev == NULL) {
shell_error(sh, "haptic device not found");
return -EINVAL;
}
if (argc == HAPTICS_ARGS_STREAM_START) {
shell_error(sh, "invalid data stream, no bytes provided");
return -EINVAL;
}
for (int i = HAPTICS_ARGS_STREAM_START; i < argc; i++) {
byte = shell_strtoul(argv[i], 16, &ret);
if (ret < 0) {
shell_error(sh, "failed to parse byte (%d)", ret);
return ret;
}
if (byte > UINT8_MAX) {
shell_error(sh, "invalid input '0x%X', must not exceed %u", byte,
UINT8_MAX);
return -EINVAL;
}
samples[i - HAPTICS_ARGS_STREAM_START] = byte;
}
ret = haptics_stream_samples(dev, samples, argc - HAPTICS_ARGS_STREAM_START);
if (ret < 0) {
shell_error(sh, "failed to stream samples (%d)", ret);
}
return ret;
}
static bool device_is_haptics(const struct device *dev)
{
return DEVICE_API_IS(haptics, dev);
}
#define HAPTICS_CMD_ARG(_cmd, _subcmd) SHELL_CMD_ARG(_cmd, _subcmd, NULL, NULL, 1, 0)
#define HAPTICS_STATIC_SUBCMD_SET_CREATE(_name, _subcmd, ...) \
SHELL_STATIC_SUBCMD_SET_CREATE( \
_name, FOR_EACH_FIXED_ARG(HAPTICS_CMD_ARG, (,), _subcmd, __VA_ARGS__), \
SHELL_SUBCMD_SET_END)
#define HAPTICS_DYNAMIC_CMD_CREATE(_name, _subcmd) \
static void device_##_name(size_t idx, struct shell_static_entry *entry) \
{ \
const struct device *dev = shell_device_filter(idx, device_is_haptics); \
entry->syntax = (dev != NULL) ? dev->name : NULL; \
entry->handler = NULL; \
entry->help = NULL; \
entry->subcmd = _subcmd; \
} \
\
SHELL_DYNAMIC_CMD_CREATE(dsub_device_##_name, device_##_name)
/* Create static subcommand trees for tab auto-completion. */
HAPTICS_STATIC_SUBCMD_SET_CREATE(get, NULL, HAPTICS_GET);
HAPTICS_STATIC_SUBCMD_SET_CREATE(set, NULL, HAPTICS_SET);
HAPTICS_STATIC_SUBCMD_SET_CREATE(sources, NULL, HAPTICS_SOURCES);
HAPTICS_STATIC_SUBCMD_SET_CREATE(monitors_get, &get, HAPTICS_MONITORS);
HAPTICS_STATIC_SUBCMD_SET_CREATE(monitors_set, &set, HAPTICS_MONITORS_ALL);
/* Create dynamic commands to get haptic devices and traverse subcommand trees declared above. */
HAPTICS_DYNAMIC_CMD_CREATE(only, NULL);
HAPTICS_DYNAMIC_CMD_CREATE(get, &monitors_get);
HAPTICS_DYNAMIC_CMD_CREATE(set, &monitors_set);
HAPTICS_DYNAMIC_CMD_CREATE(source, &sources);
/* clang-format off */
SHELL_STATIC_SUBCMD_SET_CREATE(haptic_cmds,
SHELL_CMD_ARG(calibrate, &dsub_device_only, HAPTICS_CALIBRATE_HELP, cmd_calibrate, 2, 1),
SHELL_CMD_ARG(get, &dsub_device_get, HAPTICS_GET_HELP, cmd_get, 4, 0),
SHELL_CMD_ARG(level, &dsub_device_source, HAPTICS_LEVEL_HELP, cmd_level, 4, 1),
SHELL_CMD_ARG(select, &dsub_device_source, HAPTICS_SELECT_HELP, cmd_select, 3, 1),
SHELL_CMD_ARG(set, &dsub_device_set, HAPTICS_SET_HELP, cmd_set, 4, 0),
SHELL_CMD_ARG(start, &dsub_device_only, HAPTICS_START_HELP, cmd_start, 2, 0),
SHELL_CMD_ARG(stop, &dsub_device_only, HAPTICS_STOP_HELP, cmd_stop, 2, 0),
SHELL_CMD_ARG(stream, &dsub_device_only, HAPTICS_STREAM_HELP, cmd_stream, 2,
HAPTICS_ARGS_MAX),
SHELL_SUBCMD_SET_END);
/* clang-format on */
SHELL_CMD_REGISTER(haptics, &haptic_cmds, "Haptic shell commands", NULL);