Skip to content

Commit 9045721

Browse files
committed
Add manual spi mode support.
Add new cmdline option, to be read by spidev transport, along with supporting code in spi transport. Will enable appliable spi mode, enabling the userspace driver to take advantage of high speed spi read & program. Does not poll SFDP to check for support, but will instead fail on hardware that does not support higher speeds. Signed-off-by: Michael Field <michaelfield@google.com>
1 parent dd9dbdc commit 9045721

4 files changed

Lines changed: 145 additions & 12 deletions

File tree

examples/htool.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,6 +1846,9 @@ static const struct htool_param GLOBAL_FLAGS[] = {
18461846
.default_value = "100",
18471847
.desc = "Interval duration (in microseconds) to wait before checking SPI "
18481848
"device status again when it indicates that the device is busy"},
1849+
// TODO(michaelfield) : b/346345769 - enable spidev mode auto-detection
1850+
{HTOOL_FLAG_VALUE, .name = "spidev_mode", .default_value = "single",
1851+
.desc = "SPI mode toggles (single|dual|quad)."},
18491852
{HTOOL_FLAG_VALUE, .name = "mtddev_path", .default_value = "",
18501853
.desc = "The full MTD path of the RoT mailbox; for example "
18511854
"'/dev/mtd0'. If unspecified, will attempt to detect "

examples/htool_spi.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ struct libhoth_device* htool_libhoth_spi_device(void) {
3232

3333
int rv;
3434
const char* spidev_path_str;
35+
const char* mode_str;
3536
uint32_t mailbox_location;
3637
bool atomic;
3738
uint32_t spidev_speed_hz;
3839
uint32_t spidev_device_busy_wait_timeout;
3940
uint32_t spidev_device_busy_wait_check_interval;
4041
rv = htool_get_param_string(htool_global_flags(), "spidev_path",
4142
&spidev_path_str) ||
43+
htool_get_param_string(htool_global_flags(), "spidev_mode", &mode_str) ||
4244
htool_get_param_u32(htool_global_flags(), "mailbox_location",
4345
&mailbox_location) ||
4446
htool_get_param_bool(htool_global_flags(), "spidev_atomic", &atomic) ||
@@ -54,6 +56,18 @@ struct libhoth_device* htool_libhoth_spi_device(void) {
5456
return NULL;
5557
}
5658

59+
enum libhoth_spi_mode operation_mode;
60+
if (!strcmp(mode_str, "single")) {
61+
operation_mode = LIBHOTH_SPI_MODE_SINGLE;
62+
} else if (!strcmp(mode_str, "dual")) {
63+
operation_mode = LIBHOTH_SPI_MODE_DUAL;
64+
} else if (!strcmp(mode_str, "quad")) {
65+
operation_mode = LIBHOTH_SPI_MODE_QUAD;
66+
} else {
67+
fprintf(stderr, "Invalid spidev mode: %s\n", mode_str);
68+
return NULL;
69+
}
70+
5771
if (strlen(spidev_path_str) <= 0) {
5872
fprintf(stderr, "Invalid spidev path: %s\n", spidev_path_str);
5973
return NULL;
@@ -78,6 +92,7 @@ struct libhoth_device* htool_libhoth_spi_device(void) {
7892
.mailbox = mailbox_location,
7993
.atomic = atomic,
8094
.speed = spidev_speed_hz,
95+
.operation_mode = operation_mode,
8196
.device_busy_wait_timeout = spidev_device_busy_wait_timeout,
8297
.device_busy_wait_check_interval = spidev_device_busy_wait_check_interval,
8398
.timeout_us = timeout_us,

transports/libhoth_spi.c

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,41 @@
3535

3636
#define DID_VID_ADDR 0xD40F00
3737

38+
static uint8_t mode_to_nbits(enum libhoth_spi_mode mode) {
39+
switch (mode) {
40+
case LIBHOTH_SPI_MODE_DUAL:
41+
return 2;
42+
case LIBHOTH_SPI_MODE_QUAD:
43+
return 4;
44+
case LIBHOTH_SPI_MODE_SINGLE:
45+
default:
46+
return 1;
47+
}
48+
}
49+
50+
static uint8_t mode_to_read_opcode(enum libhoth_spi_mode mode) {
51+
switch (mode) {
52+
case LIBHOTH_SPI_MODE_DUAL:
53+
return SPI_NOR_OPCODE_DUAL_READ;
54+
case LIBHOTH_SPI_MODE_QUAD:
55+
return SPI_NOR_OPCODE_QUAD_READ;
56+
case LIBHOTH_SPI_MODE_SINGLE:
57+
default:
58+
return SPI_NOR_OPCODE_SLOW_READ;
59+
}
60+
}
61+
62+
static uint8_t mode_to_write_opcode(enum libhoth_spi_mode mode) {
63+
switch (mode) {
64+
case LIBHOTH_SPI_MODE_QUAD:
65+
return SPI_NOR_OPCODE_QUAD_PAGE_PROGRAM;
66+
case LIBHOTH_SPI_MODE_SINGLE:
67+
case LIBHOTH_SPI_MODE_DUAL:
68+
default:
69+
return SPI_NOR_OPCODE_PAGE_PROGRAM;
70+
}
71+
}
72+
3873
static int spi_nor_address(uint8_t* buf, uint32_t address,
3974
bool address_mode_4b) {
4075
if (address_mode_4b) {
@@ -128,7 +163,8 @@ static int spi_nor_write_enable(const int fd) {
128163
return LIBHOTH_OK;
129164
}
130165

131-
static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address,
166+
static int spi_nor_write(int fd, bool address_mode_4b,
167+
enum libhoth_spi_mode mode, unsigned int address,
132168
const void* data, size_t data_len,
133169
uint32_t device_busy_wait_timeout,
134170
uint32_t device_busy_wait_check_interval) {
@@ -147,7 +183,7 @@ static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address,
147183
uint8_t rq_buf[5] = {0}; // 1 for command opcode, 4 (max) for address
148184

149185
// Page Program OPCODE + Address
150-
rq_buf[0] = SPI_NOR_OPCODE_PAGE_PROGRAM;
186+
rq_buf[0] = mode_to_write_opcode(mode);
151187
int address_len = spi_nor_address(&rq_buf[1], address, address_mode_4b);
152188
xfer[0] = (struct spi_ioc_transfer){
153189
.tx_buf = (unsigned long)rq_buf,
@@ -163,6 +199,11 @@ static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address,
163199
.len = chunk_send_size,
164200
};
165201

202+
if (mode != LIBHOTH_SPI_MODE_SINGLE) {
203+
uint8_t nbits = mode_to_nbits(mode);
204+
xfer[1].tx_nbits = nbits;
205+
}
206+
166207
status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
167208
if (status < 0) {
168209
return LIBHOTH_ERR_FAIL;
@@ -180,15 +221,16 @@ static int spi_nor_write(int fd, bool address_mode_4b, unsigned int address,
180221
return LIBHOTH_OK;
181222
}
182223

183-
static int spi_nor_read(int fd, bool address_mode_4b, unsigned int address,
224+
static int spi_nor_read(int fd, bool address_mode_4b,
225+
enum libhoth_spi_mode mode, unsigned int address,
184226
void* data, size_t data_len) {
185227
if (fd < 0 || !data || !data_len) return LIBHOTH_ERR_INVALID_PARAMETER;
186228

187229
uint8_t rd_request[5] = {0};
188230
struct spi_ioc_transfer xfer[2] = {0};
189231

190232
// Read OPCODE and mailbox address
191-
rd_request[0] = SPI_NOR_OPCODE_SLOW_READ;
233+
rd_request[0] = mode_to_read_opcode(mode);
192234
int address_len = spi_nor_address(&rd_request[1], address, address_mode_4b);
193235
xfer[0] = (struct spi_ioc_transfer){
194236
.tx_buf = (unsigned long)rd_request,
@@ -201,6 +243,11 @@ static int spi_nor_read(int fd, bool address_mode_4b, unsigned int address,
201243
.len = data_len,
202244
};
203245

246+
if (mode != LIBHOTH_SPI_MODE_SINGLE) {
247+
uint8_t nbits = mode_to_nbits(mode);
248+
xfer[1].rx_nbits = nbits;
249+
}
250+
204251
int status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
205252
if (status < 0) {
206253
return LIBHOTH_ERR_FAIL;
@@ -281,6 +328,7 @@ int libhoth_spi_open(const struct libhoth_spi_device_init_options* options,
281328
spi_dev->fd = fd;
282329
spi_dev->mailbox_address = options->mailbox;
283330
spi_dev->address_mode_4b = true;
331+
spi_dev->mode = options->operation_mode;
284332
spi_dev->device_busy_wait_timeout = options->device_busy_wait_timeout;
285333
spi_dev->device_busy_wait_check_interval =
286334
options->device_busy_wait_check_interval;
@@ -311,12 +359,52 @@ int libhoth_spi_open(const struct libhoth_spi_device_init_options* options,
311359
}
312360
}
313361

314-
if (options->mode) {
315-
const uint8_t mode = (uint8_t)options->mode;
316-
if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {
362+
uint32_t mode = options->mode;
363+
if (ioctl(fd, SPI_IOC_RD_MODE32, &spi_dev->original_mode) < 0) {
364+
status = LIBHOTH_ERR_FAIL;
365+
goto err_out;
366+
}
367+
368+
// TODO(michaelfield): Readback the SFDP and check that quadmode is
369+
// supported. If not, then we should fail unless the user explicitly
370+
// requested to force quadmode.
371+
372+
if (options->operation_mode == LIBHOTH_SPI_MODE_QUAD) {
373+
// Quadmode spi needs to use 32-bit mode flags
374+
mode |= (SPI_TX_QUAD | SPI_RX_QUAD);
375+
376+
if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) {
317377
status = LIBHOTH_ERR_FAIL;
318378
goto err_out;
319379
}
380+
} else if (options->operation_mode == LIBHOTH_SPI_MODE_DUAL) {
381+
// Dualmode spi needs to use 32-bit mode flags
382+
mode |= (SPI_TX_DUAL | SPI_RX_DUAL);
383+
384+
if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) {
385+
status = LIBHOTH_ERR_FAIL;
386+
goto err_out;
387+
}
388+
} else {
389+
// Set the mode anyways.
390+
// There is a failure mode wherein a bad mode setting will stick.
391+
// Even if mode is zero, we still want to write it.
392+
if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) {
393+
status = LIBHOTH_ERR_FAIL;
394+
goto err_out;
395+
}
396+
}
397+
398+
// read back the mode, and verify that it is what we expect.
399+
uint32_t read_mode;
400+
if (ioctl(fd, SPI_IOC_RD_MODE32, &read_mode) < 0) {
401+
status = LIBHOTH_ERR_FAIL;
402+
goto err_out;
403+
}
404+
405+
if (read_mode != mode) {
406+
status = LIBHOTH_ERR_FAIL;
407+
goto err_out;
320408
}
321409

322410
if (options->speed) {
@@ -353,7 +441,7 @@ int libhoth_spi_send_request(struct libhoth_device* dev, const void* request,
353441
struct libhoth_spi_device* spi_dev =
354442
(struct libhoth_spi_device*)dev->user_ctx;
355443

356-
return spi_nor_write(spi_dev->fd, spi_dev->address_mode_4b,
444+
return spi_nor_write(spi_dev->fd, spi_dev->address_mode_4b, spi_dev->mode,
357445
spi_dev->mailbox_address, request, request_size,
358446
spi_dev->device_busy_wait_timeout,
359447
spi_dev->device_busy_wait_check_interval);
@@ -377,7 +465,7 @@ int libhoth_spi_receive_response(struct libhoth_device* dev, void* response,
377465
(struct libhoth_spi_device*)dev->user_ctx;
378466

379467
// Read Header From Mailbox
380-
status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b,
468+
status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b, spi_dev->mode,
381469
spi_dev->mailbox_address, response,
382470
sizeof(struct hoth_host_response));
383471
if (status != LIBHOTH_OK) {
@@ -397,7 +485,7 @@ int libhoth_spi_receive_response(struct libhoth_device* dev, void* response,
397485
if (host_response.data_len > 0) {
398486
// Read remainder of data based on header length
399487
uint8_t* const data_start = (uint8_t*)response + total_bytes;
400-
status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b,
488+
status = spi_nor_read(spi_dev->fd, spi_dev->address_mode_4b, spi_dev->mode,
401489
spi_dev->mailbox_address + total_bytes, data_start,
402490
host_response.data_len);
403491
if (status != LIBHOTH_OK) {
@@ -467,7 +555,7 @@ int libhoth_spi_send_and_receive_response(struct libhoth_device* dev,
467555

468556
// Page Program OPCODE + Mailbox Address
469557
uint8_t pp_buf[5] = {0};
470-
pp_buf[0] = SPI_NOR_OPCODE_PAGE_PROGRAM;
558+
pp_buf[0] = mode_to_write_opcode(spi_dev->mode);
471559
int address_len = spi_nor_address(&pp_buf[1], address, address_mode_4b);
472560
xfer[1] = (struct spi_ioc_transfer){
473561
.tx_buf = (unsigned long)pp_buf,
@@ -481,11 +569,15 @@ int libhoth_spi_send_and_receive_response(struct libhoth_device* dev,
481569
.cs_change = 1,
482570
};
483571

572+
if (spi_dev->mode != LIBHOTH_SPI_MODE_SINGLE) {
573+
uint8_t nbits = mode_to_nbits(spi_dev->mode);
574+
xfer[2].tx_nbits = nbits;
575+
}
484576
// Wait for status register is handled by the spidev driver.
485577

486578
// Read opcode + Mailbox Address
487579
uint8_t rd_buf[5] = {0};
488-
rd_buf[0] = SPI_NOR_OPCODE_SLOW_READ;
580+
rd_buf[0] = mode_to_read_opcode(spi_dev->mode);
489581
address_len = spi_nor_address(&rd_buf[1], address, address_mode_4b);
490582
xfer[3] = (struct spi_ioc_transfer){
491583
.tx_buf = (unsigned long)rd_buf,
@@ -498,6 +590,11 @@ int libhoth_spi_send_and_receive_response(struct libhoth_device* dev,
498590
.len = max_response_size,
499591
};
500592

593+
if (spi_dev->mode != LIBHOTH_SPI_MODE_SINGLE) {
594+
uint8_t nbits = mode_to_nbits(spi_dev->mode);
595+
xfer[4].rx_nbits = nbits;
596+
}
597+
501598
int rc = LIBHOTH_OK;
502599
int status = ioctl(spi_dev->fd, SPI_IOC_MESSAGE(5), xfer);
503600
if (status < 0) {
@@ -564,6 +661,12 @@ int libhoth_spi_close(struct libhoth_device* dev) {
564661

565662
struct libhoth_spi_device* spi_dev =
566663
(struct libhoth_spi_device*)dev->user_ctx;
664+
// Mode settings can be sticky! Restore the original mode to ensure any bad
665+
// modesetting does not presist across processes.
666+
if (ioctl(spi_dev->fd, SPI_IOC_WR_MODE32, &spi_dev->original_mode) < 0) {
667+
// We can't do much here, but we should at least close the fd.
668+
perror("Failed to restore SPI mode");
669+
}
567670
close(spi_dev->fd);
568671
free(dev->user_ctx);
569672
return LIBHOTH_OK;

transports/libhoth_spi.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ extern "C" {
2525

2626
struct libhoth_device;
2727

28+
enum libhoth_spi_mode {
29+
LIBHOTH_SPI_MODE_SINGLE = 0,
30+
LIBHOTH_SPI_MODE_DUAL,
31+
LIBHOTH_SPI_MODE_QUAD,
32+
};
33+
2834
struct libhoth_spi_device_init_options {
2935
// The device filepath to open
3036
const char* path;
@@ -34,6 +40,7 @@ struct libhoth_spi_device_init_options {
3440
int mode;
3541
int speed;
3642
int atomic;
43+
enum libhoth_spi_mode operation_mode;
3744
uint32_t device_busy_wait_timeout;
3845
uint32_t device_busy_wait_check_interval;
3946
uint32_t timeout_us;
@@ -43,6 +50,8 @@ struct libhoth_spi_device {
4350
int fd;
4451
unsigned int mailbox_address;
4552
bool address_mode_4b;
53+
enum libhoth_spi_mode mode;
54+
uint32_t original_mode;
4655

4756
void* buffered_request;
4857
size_t buffered_request_size;
@@ -55,7 +64,10 @@ enum {
5564
SPI_NOR_OPCODE_READ_STATUS = 0x05,
5665
SPI_NOR_OPCODE_WRITE_ENABLE = 0x06,
5766
SPI_NOR_OPCODE_PAGE_PROGRAM = 0x02,
67+
SPI_NOR_OPCODE_QUAD_PAGE_PROGRAM = 0x38,
5868
SPI_NOR_OPCODE_SLOW_READ = 0x03,
69+
SPI_NOR_OPCODE_DUAL_READ = 0x3B,
70+
SPI_NOR_OPCODE_QUAD_READ = 0x6B,
5971
SPI_NOR_FLASH_PAGE_SIZE = 256, // in bytes
6072
};
6173

0 commit comments

Comments
 (0)