1 /*
2  * Copyright (c) 2024 Astrolight
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <stdbool.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <zephyr/drivers/spi.h>
12 #include <zephyr/shell/shell.h>
13 #include <zephyr/sys/util.h>
14 
15 #define TXRX_ARGV_BYTES     (1)
16 #define CONF_ARGV_DEV       (1)
17 #define CONF_ARGV_FREQUENCY (2)
18 #define CONF_ARGV_SETTINGS  (3)
19 
20 /* Maximum bytes we can write and read at once */
21 #define MAX_SPI_BYTES MIN((CONFIG_SHELL_ARGC_MAX - TXRX_ARGV_BYTES), 32)
22 
23 static struct device *spi_device;
24 static struct spi_config config = {.frequency = 1000000,
25 				   .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8)};
26 
device_name_get(size_t idx,struct shell_static_entry * entry)27 static void device_name_get(size_t idx, struct shell_static_entry *entry)
28 {
29 	const struct device *dev = shell_device_lookup(idx, "spi");
30 
31 	entry->syntax = (dev != NULL) ? dev->name : NULL;
32 	entry->handler = NULL;
33 	entry->help = NULL;
34 	entry->subcmd = NULL;
35 }
36 
37 SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get);
38 
cmd_spi_transceive(const struct shell * ctx,size_t argc,char ** argv)39 static int cmd_spi_transceive(const struct shell *ctx, size_t argc, char **argv)
40 {
41 	uint8_t rx_buffer[MAX_SPI_BYTES] = {0};
42 	uint8_t tx_buffer[MAX_SPI_BYTES] = {0};
43 
44 	if (spi_device == NULL) {
45 		shell_error(ctx, "SPI device isn't configured. Use `spi conf`");
46 		return -ENODEV;
47 	}
48 
49 	int bytes_to_send = argc - TXRX_ARGV_BYTES;
50 
51 	for (int i = 0; i < bytes_to_send; i++) {
52 		tx_buffer[i] = strtol(argv[TXRX_ARGV_BYTES + i], NULL, 16);
53 	}
54 
55 	const struct spi_buf tx_buffers = {.buf = tx_buffer, .len = bytes_to_send};
56 	const struct spi_buf rx_buffers = {.buf = rx_buffer, .len = bytes_to_send};
57 
58 	const struct spi_buf_set tx_buf_set = {.buffers = &tx_buffers, .count = 1};
59 	const struct spi_buf_set rx_buf_set = {.buffers = &rx_buffers, .count = 1};
60 
61 	int ret = spi_transceive(spi_device, &config, &tx_buf_set, &rx_buf_set);
62 
63 	if (ret < 0) {
64 		shell_error(ctx, "spi_transceive returned %d", ret);
65 		return ret;
66 	}
67 
68 	shell_print(ctx, "TX:");
69 	shell_hexdump(ctx, tx_buffer, bytes_to_send);
70 
71 	shell_print(ctx, "RX:");
72 	shell_hexdump(ctx, rx_buffer, bytes_to_send);
73 
74 	return ret;
75 }
76 
cmd_spi_conf(const struct shell * ctx,size_t argc,char ** argv)77 static int cmd_spi_conf(const struct shell *ctx, size_t argc, char **argv)
78 {
79 	spi_operation_t operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER;
80 
81 	/* warning: initialization discards 'const' qualifier from pointer */
82 	/* target type */
83 	struct device *dev = (struct device *)shell_device_get_binding(argv[CONF_ARGV_DEV]);
84 
85 	if (dev == NULL) {
86 		shell_error(ctx, "device %s not found.", argv[CONF_ARGV_DEV]);
87 		return -ENODEV;
88 	}
89 
90 	uint32_t frequency = strtol(argv[CONF_ARGV_FREQUENCY], NULL, 10);
91 
92 	if (!IN_RANGE(frequency, 100 * 1000, 80 * 1000 * 1000)) {
93 		shell_error(ctx, "frequency must be between 100000  and 80000000");
94 		return -EINVAL;
95 	}
96 
97 	/* no settings */
98 	if (argc == (CONF_ARGV_FREQUENCY + 1)) {
99 		goto out;
100 	}
101 
102 	char *opts = argv[CONF_ARGV_SETTINGS];
103 	bool all_opts_is_valid = true;
104 
105 	while (*opts != '\0') {
106 		switch (*opts) {
107 		case 'o':
108 			operation |= SPI_MODE_CPOL;
109 			break;
110 		case 'h':
111 			operation |= SPI_MODE_CPHA;
112 			break;
113 		case 'l':
114 			operation |= SPI_TRANSFER_LSB;
115 			break;
116 		case 'T':
117 			operation |= SPI_FRAME_FORMAT_TI;
118 			break;
119 		default:
120 			all_opts_is_valid = false;
121 			shell_error(ctx, "invalid setting %c", *opts);
122 		}
123 		opts++;
124 	}
125 
126 	if (!all_opts_is_valid) {
127 		return -EINVAL;
128 	}
129 
130 out:
131 	config.frequency = frequency;
132 	config.operation = operation;
133 	spi_device = dev;
134 
135 	return 0;
136 }
137 
138 SHELL_STATIC_SUBCMD_SET_CREATE(sub_spi_cmds,
139 			       SHELL_CMD_ARG(conf, &dsub_device_name,
140 					     "Configure SPI\n"
141 					     "Usage: spi conf <device> <frequency> [<settings>]\n"
142 					     "<settings> - any sequence of letters:\n"
143 					     "o - SPI_MODE_CPOL\n"
144 					     "h - SPI_MODE_CPHA\n"
145 					     "l - SPI_TRANSFER_LSB\n"
146 					     "T - SPI_FRAME_FORMAT_TI\n"
147 					     "example: spi conf spi1 1000000 ol",
148 					     cmd_spi_conf, 3, 1),
149 			       SHELL_CMD_ARG(transceive, NULL,
150 					     "Transceive data to and from an SPI device\n"
151 					     "Usage: spi transceive <TX byte 1> [<TX byte 2> ...]",
152 					     cmd_spi_transceive, 2, MAX_SPI_BYTES - 1),
153 			       SHELL_SUBCMD_SET_END);
154 
155 SHELL_CMD_REGISTER(spi, &sub_spi_cmds, "SPI commands", NULL);
156