bitbang_spi: Implement multi-i/o
Add some optional functions to the bitbang_spi API that will allow
us to transfer multiple bits at once. With these, implement the
spi_send_multicommand() API, completely with all known transfer
modes.
Change-Id: I38346de37809118e13a059e90b9f3fff6456db15
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/82
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
diff --git a/bitbang_spi.c b/bitbang_spi.c
index 1c2c178..33ea050 100644
--- a/bitbang_spi.c
+++ b/bitbang_spi.c
@@ -20,6 +20,7 @@
#include "flash.h"
#include "programmer.h"
#include "spi.h"
+#include "spi_command.h"
#include "bitbang_spi.h"
struct bitbang_spi_master_data {
@@ -72,10 +73,27 @@
return master->get_miso(spi_data);
}
+static void bitbang_spi_idle_io(const struct bitbang_spi_master_data *bbs)
+{
+ if (bbs->mst->set_idle_io)
+ bbs->mst->set_idle_io(bbs->spi_data);
+}
+
+static void bitbang_spi_run_clock(const struct bitbang_spi_master_data *bbs, unsigned int cycles)
+{
+ for (; cycles > 0; --cycles) {
+ bbs->mst->set_sck(0, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ bbs->mst->set_sck(1, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ }
+}
+
static int bitbang_spi_send_command(const struct flashctx *flash,
unsigned int writecnt, unsigned int readcnt,
const unsigned char *writearr,
unsigned char *readarr);
+static int bitbang_spi_send_multicommand(const struct flashctx *, struct spi_command *);
static int bitbang_spi_shutdown(void *data);
static const struct spi_master spi_master_bitbang = {
@@ -83,7 +101,7 @@
.max_data_read = MAX_DATA_READ_UNLIMITED,
.max_data_write = MAX_DATA_WRITE_UNLIMITED,
.command = bitbang_spi_send_command,
- .multicommand = default_spi_send_multicommand,
+ .multicommand = bitbang_spi_send_multicommand,
.read = default_spi_read,
.write_256 = default_spi_write_256,
.shutdown = bitbang_spi_shutdown,
@@ -104,12 +122,23 @@
if (!master || !master->set_cs ||
!master->set_sck || !master->set_mosi || !master->get_miso ||
(master->request_bus && !master->release_bus) ||
- (!master->request_bus && master->release_bus)) {
+ (!master->request_bus && master->release_bus) ||
+ (master->set_sck_set_dual_io && !master->set_sck_get_dual_io) ||
+ (!master->set_sck_set_dual_io && master->set_sck_get_dual_io) ||
+ (master->set_sck_set_quad_io && !master->set_sck_get_quad_io) ||
+ (!master->set_sck_set_quad_io && master->set_sck_get_quad_io) ||
+ ((master->set_sck_set_dual_io || master->set_sck_set_quad_io) &&
+ !master->set_idle_io)) {
msg_perr("Incomplete SPI bitbang master setting!\n"
"Please report a bug at flashprog@flashprog.org\n");
return ERROR_FLASHPROG_BUG;
}
+ if (master->set_sck_set_dual_io)
+ mst.features |= SPI_MASTER_DUAL;
+ if (master->set_sck_set_quad_io)
+ mst.features |= SPI_MASTER_QUAD | SPI_MASTER_QPI;
+
struct bitbang_spi_master_data *data = calloc(1, sizeof(struct bitbang_spi_master_data));
if (!data) {
msg_perr("Out of memory!\n");
@@ -122,6 +151,7 @@
/* Only mess with the bus if we're sure nobody else uses it. */
bitbang_spi_request_bus(master, spi_data);
+ bitbang_spi_idle_io(data);
bitbang_spi_set_cs(master, 1, spi_data);
bitbang_spi_set_sck_set_mosi(master, 0, 0, spi_data);
/* FIXME: Release SPI bus here and request it again for each command or
@@ -149,6 +179,36 @@
return ret;
}
+static uint8_t bitbang_spi_read_dual(const struct bitbang_spi_master_data *const bbs)
+{
+ uint8_t ret = 0;
+ int i;
+
+ for (i = 6; i >= 0; i -= 2) {
+ bbs->mst->set_sck(0, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ ret <<= 2;
+ ret |= bbs->mst->set_sck_get_dual_io(1, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ }
+ return ret;
+}
+
+static uint8_t bitbang_spi_read_quad(const struct bitbang_spi_master_data *const bbs)
+{
+ uint8_t ret = 0;
+ int i;
+
+ for (i = 4; i >= 0; i -= 4) {
+ bbs->mst->set_sck(0, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ ret <<= 4;
+ ret |= bbs->mst->set_sck_get_quad_io(1, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ }
+ return ret;
+}
+
static void bitbang_spi_write_byte(const struct bitbang_spi_master *master, uint8_t val, void *spi_data)
{
int i;
@@ -161,6 +221,30 @@
}
}
+static void bitbang_spi_write_dual(const struct bitbang_spi_master_data *bbs, uint8_t val)
+{
+ int i;
+
+ for (i = 6; i >= 0; i -= 2) {
+ bbs->mst->set_sck_set_dual_io(0, (val >> i) & 3, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ bbs->mst->set_sck(1, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ }
+}
+
+static void bitbang_spi_write_quad(const struct bitbang_spi_master_data *bbs, uint8_t val)
+{
+ int i;
+
+ for (i = 4; i >= 0; i -= 4) {
+ bbs->mst->set_sck_set_quad_io(0, (val >> i) & 0xf, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ bbs->mst->set_sck(1, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ }
+}
+
static int bitbang_spi_send_command(const struct flashctx *flash,
unsigned int writecnt, unsigned int readcnt,
const unsigned char *writearr,
@@ -190,3 +274,86 @@
return 0;
}
+
+static int bitbang_spi_send_multicommand(const struct flashctx *flash, struct spi_command *cmds)
+{
+ const struct bitbang_spi_master_data *const bbs = flash->mst.spi->data;
+ int ret = 0;
+
+ bitbang_spi_request_bus(bbs->mst, bbs->spi_data);
+
+ for (; !spi_is_empty(cmds); ++cmds) {
+ size_t write_single = 0, write_dual = 0, write_quad = 0;
+ size_t read_single = 0, read_dual = 0, read_quad = 0;
+ unsigned int high_z_cycles;
+
+ switch (cmds->io_mode) {
+ case SINGLE_IO_1_1_1:
+ write_single = cmds->opcode_len + cmds->address_len + cmds->write_len;
+ high_z_cycles = 8 * cmds->high_z_len;
+ read_single = cmds->read_len;
+ break;
+ case DUAL_OUT_1_1_2:
+ write_single = cmds->opcode_len + cmds->address_len + cmds->write_len;
+ high_z_cycles = 4 * cmds->high_z_len;
+ read_dual = cmds->read_len;
+ break;
+ case DUAL_IO_1_2_2:
+ write_single = cmds->opcode_len;
+ write_dual = cmds->address_len + cmds->write_len;
+ high_z_cycles = 4 * cmds->high_z_len;
+ read_dual = cmds->read_len;
+ break;
+ case QUAD_OUT_1_1_4:
+ write_single = cmds->opcode_len + cmds->address_len + cmds->write_len;
+ high_z_cycles = 2 * cmds->high_z_len;
+ read_quad = cmds->read_len;
+ break;
+ case QUAD_IO_1_4_4:
+ write_single = cmds->opcode_len;
+ write_quad = cmds->address_len + cmds->write_len;
+ high_z_cycles = 2 * cmds->high_z_len;
+ read_quad = cmds->read_len;
+ break;
+ case QPI_4_4_4:
+ write_quad = cmds->opcode_len + cmds->address_len + cmds->write_len;
+ high_z_cycles = 2 * cmds->high_z_len;
+ read_quad = cmds->read_len;
+ break;
+ default:
+ return SPI_FLASHPROG_BUG;
+ }
+
+ bitbang_spi_set_cs(bbs->mst, 0, bbs->spi_data);
+
+ const unsigned char *out = cmds->writearr;
+ for (; write_single > 0; --write_single, ++out)
+ bitbang_spi_write_byte(bbs->mst, *out, bbs->spi_data);
+ for (; write_dual > 0; --write_dual, ++out)
+ bitbang_spi_write_dual(bbs, *out);
+ for (; write_quad > 0; --write_quad, ++out)
+ bitbang_spi_write_quad(bbs, *out);
+
+ if (high_z_cycles || read_dual || read_quad) {
+ bitbang_spi_idle_io(bbs);
+ bitbang_spi_run_clock(bbs, high_z_cycles);
+ }
+
+ unsigned char *in = cmds->readarr;
+ for (; read_quad > 0; --read_quad, ++in)
+ *in = bitbang_spi_read_quad(bbs);
+ for (; read_dual > 0; --read_dual, ++in)
+ *in = bitbang_spi_read_dual(bbs);
+ for (; read_single > 0; --read_single, ++in)
+ *in = bitbang_spi_read_byte(bbs->mst, bbs->spi_data);
+
+ bitbang_spi_set_sck(bbs->mst, 0, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ bitbang_spi_set_cs(bbs->mst, 1, bbs->spi_data);
+ programmer_delay(bbs->mst->half_period);
+ }
+
+ bitbang_spi_release_bus(bbs->mst, bbs->spi_data);
+
+ return ret;
+}
diff --git a/include/bitbang_spi.h b/include/bitbang_spi.h
index b321651..322d022 100644
--- a/include/bitbang_spi.h
+++ b/include/bitbang_spi.h
@@ -26,6 +26,12 @@
/* optional functions to optimize xfers */
void (*set_sck_set_mosi) (int sck, int mosi, void *data);
int (*set_sck_get_miso) (int sck, void *data);
+ /* optional functions for dual/quad i/o */
+ void (*set_sck_set_dual_io) (int sck, int io, void *data);
+ void (*set_sck_set_quad_io) (int sck, int io, void *data);
+ void (*set_idle_io) (void *data);
+ int (*set_sck_get_dual_io) (int sck, void *data);
+ int (*set_sck_get_quad_io) (int sck, void *data);
/* Length of half a clock period in usecs. */
unsigned int half_period;
};