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 #define CS_ARGV_GPIO_DEV    (1)
21 #define CS_ARGV_GPIO_PIN    (2)
22 #define CS_ARGV_GPIO_FLAGS  (3)
23 
24 /* Maximum bytes we can write and read at once */
25 #define MAX_SPI_BYTES MIN((CONFIG_SHELL_ARGC_MAX - TXRX_ARGV_BYTES), 32)
26 
27 static struct device *spi_device;
28 static struct spi_config config = {.frequency = 1000000,
29 				   .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8)};
30 
device_is_spi(const struct device * dev)31 static bool device_is_spi(const struct device *dev)
32 {
33 	return DEVICE_API_IS(spi, dev);
34 }
35 
device_name_get(size_t idx,struct shell_static_entry * entry)36 static void device_name_get(size_t idx, struct shell_static_entry *entry)
37 {
38 	const struct device *dev = shell_device_filter(idx, device_is_spi);
39 
40 	entry->syntax = (dev != NULL) ? dev->name : NULL;
41 	entry->handler = NULL;
42 	entry->help = NULL;
43 	entry->subcmd = NULL;
44 }
45 
46 SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get);
47 
cmd_spi_transceive(const struct shell * ctx,size_t argc,char ** argv)48 static int cmd_spi_transceive(const struct shell *ctx, size_t argc, char **argv)
49 {
50 	uint8_t rx_buffer[MAX_SPI_BYTES] = {0};
51 	uint8_t tx_buffer[MAX_SPI_BYTES] = {0};
52 
53 	if (spi_device == NULL) {
54 		shell_error(ctx, "SPI device isn't configured. Use `spi conf`");
55 		return -ENODEV;
56 	}
57 
58 	int bytes_to_send = argc - TXRX_ARGV_BYTES;
59 
60 	for (int i = 0; i < bytes_to_send; i++) {
61 		tx_buffer[i] = strtol(argv[TXRX_ARGV_BYTES + i], NULL, 16);
62 	}
63 
64 	const struct spi_buf tx_buffers = {.buf = tx_buffer, .len = bytes_to_send};
65 	const struct spi_buf rx_buffers = {.buf = rx_buffer, .len = bytes_to_send};
66 
67 	const struct spi_buf_set tx_buf_set = {.buffers = &tx_buffers, .count = 1};
68 	const struct spi_buf_set rx_buf_set = {.buffers = &rx_buffers, .count = 1};
69 
70 	int ret = spi_transceive(spi_device, &config, &tx_buf_set, &rx_buf_set);
71 
72 	if (ret < 0) {
73 		shell_error(ctx, "spi_transceive returned %d", ret);
74 		return ret;
75 	}
76 
77 	shell_print(ctx, "TX:");
78 	shell_hexdump(ctx, tx_buffer, bytes_to_send);
79 
80 	shell_print(ctx, "RX:");
81 	shell_hexdump(ctx, rx_buffer, bytes_to_send);
82 
83 	return ret;
84 }
85 
cmd_spi_conf(const struct shell * ctx,size_t argc,char ** argv)86 static int cmd_spi_conf(const struct shell *ctx, size_t argc, char **argv)
87 {
88 	spi_operation_t operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER;
89 
90 	/* warning: initialization discards 'const' qualifier from pointer */
91 	/* target type */
92 	struct device *dev = (struct device *)shell_device_get_binding(argv[CONF_ARGV_DEV]);
93 
94 	if (dev == NULL) {
95 		shell_error(ctx, "device %s not found.", argv[CONF_ARGV_DEV]);
96 		return -ENODEV;
97 	}
98 
99 	uint32_t frequency = strtol(argv[CONF_ARGV_FREQUENCY], NULL, 10);
100 
101 	if (!IN_RANGE(frequency, 100 * 1000, 80 * 1000 * 1000)) {
102 		shell_error(ctx, "frequency must be between 100000  and 80000000");
103 		return -EINVAL;
104 	}
105 
106 	/* no settings */
107 	if (argc == (CONF_ARGV_FREQUENCY + 1)) {
108 		goto out;
109 	}
110 
111 	char *opts = argv[CONF_ARGV_SETTINGS];
112 	bool all_opts_is_valid = true;
113 
114 	while (*opts != '\0') {
115 		switch (*opts) {
116 		case 'o':
117 			operation |= SPI_MODE_CPOL;
118 			break;
119 		case 'h':
120 			operation |= SPI_MODE_CPHA;
121 			break;
122 		case 'l':
123 			operation |= SPI_TRANSFER_LSB;
124 			break;
125 		case 'T':
126 			operation |= SPI_FRAME_FORMAT_TI;
127 			break;
128 		default:
129 			all_opts_is_valid = false;
130 			shell_error(ctx, "invalid setting %c", *opts);
131 		}
132 		opts++;
133 	}
134 
135 	if (!all_opts_is_valid) {
136 		return -EINVAL;
137 	}
138 
139 out:
140 	config.frequency = frequency;
141 	config.operation = operation;
142 	spi_device = dev;
143 
144 	return 0;
145 }
146 
cmd_spi_conf_cs(const struct shell * ctx,size_t argc,char ** argv)147 static int cmd_spi_conf_cs(const struct shell *ctx, size_t argc, char **argv)
148 {
149 	struct device *dev = (struct device *)shell_device_get_binding(argv[CS_ARGV_GPIO_DEV]);
150 	char *endptr = NULL;
151 
152 	if (dev == NULL) {
153 		shell_error(ctx, "device %s not found.", argv[CS_ARGV_GPIO_DEV]);
154 		return -ENODEV;
155 	}
156 
157 	int pin = strtol(argv[CS_ARGV_GPIO_PIN], &endptr, 10);
158 
159 	if (endptr == argv[CS_ARGV_GPIO_PIN] || (pin < 0)) {
160 		shell_error(ctx, "invalid pin number: %s", argv[CS_ARGV_GPIO_PIN]);
161 		return -EINVAL;
162 	}
163 
164 	config.cs.gpio.port = dev;
165 	config.cs.gpio.pin = pin;
166 
167 	/* Include flags if provided */
168 	if (argc == (CS_ARGV_GPIO_FLAGS + 1)) {
169 		uint32_t flags = strtol(argv[CS_ARGV_GPIO_FLAGS], &endptr, 16);
170 
171 		if (endptr == argv[CS_ARGV_GPIO_FLAGS]) {
172 			shell_error(ctx, "invalid gpio flags: %s", argv[CS_ARGV_GPIO_FLAGS]);
173 			return -EINVAL;
174 		}
175 
176 		config.cs.gpio.dt_flags = flags;
177 	}
178 
179 	return 0;
180 }
181 
182 SHELL_STATIC_SUBCMD_SET_CREATE(sub_spi_cmds,
183 			       SHELL_CMD_ARG(conf, &dsub_device_name,
184 					     "Configure SPI\n"
185 					     "Usage: spi conf <device> <frequency> [<settings>]\n"
186 					     "<settings> - any sequence of letters:\n"
187 					     "o - SPI_MODE_CPOL\n"
188 					     "h - SPI_MODE_CPHA\n"
189 					     "l - SPI_TRANSFER_LSB\n"
190 					     "T - SPI_FRAME_FORMAT_TI\n"
191 					     "example: spi conf spi1 1000000 ol",
192 					     cmd_spi_conf, 3, 1),
193 			       SHELL_CMD_ARG(cs, &dsub_device_name,
194 					     "Assign CS GPIO to SPI device\n"
195 					     "Usage: spi cs <gpio-device> <pin> [<gpio flags>]"
196 					     "example: spi conf gpio1 3 0x01",
197 					     cmd_spi_conf_cs, 3, 1),
198 			       SHELL_CMD_ARG(transceive, NULL,
199 					     "Transceive data to and from an SPI device\n"
200 					     "Usage: spi transceive <TX byte 1> [<TX byte 2> ...]",
201 					     cmd_spi_transceive, 2, MAX_SPI_BYTES - 1),
202 			       SHELL_SUBCMD_SET_END);
203 
204 SHELL_CMD_REGISTER(spi, &sub_spi_cmds, "SPI commands", NULL);
205