diff --git a/README.md b/README.md index 62c227a..850ba23 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,15 @@ It does not require a custom version of flashrom, just drag the compiled uf2 ont The default pin-out is: -| GPIO | Pico Pin | Function | -|------|----------|----------| -| 1 | 2 | CS | -| 2 | 4 | SCK | -| 3 | 5 | MOSI | -| 4 | 6 | MISO | +| GPIO | Pico Pin | Function | +|------|----------|----------------| +| 1 | 2 | CS_0 (default) | +| 2 | 4 | SCK | +| 3 | 5 | MOSI | +| 4 | 6 | MISO | +| 5 | 7 | CS_1 | +| 6 | 9 | CS_2 | +| 7 | 10 | CS_3 | ## Usage @@ -21,6 +24,11 @@ Dump a flashchip: flashrom -p serprog:dev=/dev/ttyACM0:115200,spispeed=12M -r foo.bin ``` +pico-serprog only switches the pins to output when requested by flashrom. This +means that you can leave your pico-serprog programmer attached to the flash; +you don't have to detach it before booting the board that you're programming. + + ## License The project is based on the spi_flash example by Raspberry Pi (Trading) Ltd. which is licensed under BSD-3-Clause. diff --git a/main.c b/main.c index f6b907c..18d5462 100644 --- a/main.c +++ b/main.c @@ -22,34 +22,61 @@ #define PIN_MISO 4 #define PIN_MOSI 3 #define PIN_SCK 2 -#define PIN_CS 1 +#define PIN_CS_0 1 +#define PIN_CS_1 5 +#define PIN_CS_2 6 +#define PIN_CS_3 7 #define BUS_SPI (1 << 3) #define S_SUPPORTED_BUS BUS_SPI #define S_CMD_MAP ( \ - (1 << S_CMD_NOP) | \ - (1 << S_CMD_Q_IFACE) | \ - (1 << S_CMD_Q_CMDMAP) | \ - (1 << S_CMD_Q_PGMNAME) | \ - (1 << S_CMD_Q_SERBUF) | \ - (1 << S_CMD_Q_BUSTYPE) | \ - (1 << S_CMD_SYNCNOP) | \ - (1 << S_CMD_O_SPIOP) | \ - (1 << S_CMD_S_BUSTYPE) | \ - (1 << S_CMD_S_SPI_FREQ)| \ - (1 << S_CMD_S_PIN_STATE) \ + (1 << S_CMD_NOP) | \ + (1 << S_CMD_Q_IFACE) | \ + (1 << S_CMD_Q_CMDMAP) | \ + (1 << S_CMD_Q_PGMNAME) | \ + (1 << S_CMD_Q_SERBUF) | \ + (1 << S_CMD_Q_BUSTYPE) | \ + (1 << S_CMD_SYNCNOP) | \ + (1 << S_CMD_O_SPIOP) | \ + (1 << S_CMD_S_BUSTYPE) | \ + (1 << S_CMD_S_SPI_FREQ) | \ + (1 << S_CMD_S_PIN_STATE) | \ + (1 << S_CMD_S_SPI_CS) | \ + (1 << S_CMD_S_SPI_MODE) | \ + (1 << S_CMD_S_CS_MODE) \ ) +enum spi_mode { + SPI_MODE_HALF_DUPLEX = 0, + SPI_MODE_FULL_DUPLEX = 1, + SPI_MODE_MAX = SPI_MODE_FULL_DUPLEX, +}; + +enum spi_mode current_spi_mode = SPI_MODE_HALF_DUPLEX; + +enum cs_mode { + CS_MODE_AUTO = 0, + CS_MODE_SELECTED = 1, + CS_MODE_DESELECTED = 2, + CS_MODE_MAX = CS_MODE_DESELECTED, +}; + +enum cs_mode current_cs_mode = CS_MODE_AUTO; + +uint active_cs_pin = 0; +#define NUM_CS_AVAILABLE 4 // Number of usable chip selects +uint8_t cs_pins[NUM_CS_AVAILABLE] = { PIN_CS_0, PIN_CS_1, PIN_CS_2, PIN_CS_3 }; + static uint32_t serprog_spi_init(uint32_t freq); static inline void cs_select(uint cs_pin) { asm volatile("nop \n nop \n nop"); // FIXME - gpio_put(cs_pin, 0); + gpio_put(cs_pins[cs_pin], 0); asm volatile("nop \n nop \n nop"); // FIXME } static inline void cs_deselect(uint cs_pin) { asm volatile("nop \n nop \n nop"); // FIXME - gpio_put(cs_pin, 1); + gpio_put(cs_pins[cs_pin], 1); asm volatile("nop \n nop \n nop"); // FIXME } @@ -79,7 +106,59 @@ void putu32(uint32_t d) { unsigned char write_buffer[4096]; +void apply_pin_state(const pio_spi_inst_t *spi, bool state) { + pio_spi_enable_outputs(spi->pio, spi->sm, state, PIN_SCK, PIN_MOSI, PIN_MISO); + for (int i = 0; i < NUM_CS_AVAILABLE; i++) { + gpio_set_dir(cs_pins[i], state? GPIO_OUT : GPIO_IN); + } +} + +void spi_half_duplex(const pio_spi_inst_t *spi, uint32_t wlen, uint32_t rlen) { + fread(write_buffer, 1, wlen, stdin); + pio_spi_write8_blocking(spi, write_buffer, wlen); + + putchar(S_ACK); + uint32_t chunk; + char buf[128]; + + for(uint32_t i = 0; i < rlen; i += chunk) { + chunk = MIN(rlen - i, sizeof(buf)); + pio_spi_read8_blocking(spi, buf, chunk); + fwrite(buf, 1, chunk, stdout); + fflush(stdout); + } +} + +void spi_full_duplex(const pio_spi_inst_t *spi, uint32_t wlen, uint32_t rlen) { + uint8_t buffer[128]; + + putchar(S_ACK); + + while (wlen || rlen) { + size_t len = MIN(rlen, wlen); + size_t chunk_size = MIN(sizeof(buffer), len); + memset(buffer, 0, chunk_size); + + if (wlen) { + size_t chunk = MIN(wlen, chunk_size); + fread(buffer, 1, chunk, stdin); + wlen -= chunk; + } + + pio_spi_write8_read8_blocking(spi, buffer, buffer, chunk_size); + + if (rlen) { + size_t chunk = MIN(rlen, chunk_size); + fwrite(buffer, 1, chunk, stdout); + rlen -= chunk; + } + } + fflush(stdout); +} + void process(const pio_spi_inst_t *spi, int command) { + static bool pin_state = false; + switch(command) { case S_CMD_NOP: putchar(S_ACK); @@ -131,22 +210,22 @@ void process(const pio_spi_inst_t *spi, int command) { uint32_t wlen = getu24(); uint32_t rlen = getu24(); - cs_select(PIN_CS); - fread(write_buffer, 1, wlen, stdin); - pio_spi_write8_blocking(spi, write_buffer, wlen); - - putchar(S_ACK); - uint32_t chunk; - char buf[128]; - - for(uint32_t i = 0; i < rlen; i += chunk) { - chunk = MIN(rlen - i, sizeof(buf)); - pio_spi_read8_blocking(spi, buf, chunk); - fwrite(buf, 1, chunk, stdout); - fflush(stdout); + if (current_cs_mode == CS_MODE_AUTO) { + cs_select(active_cs_pin); + } + switch (current_spi_mode) { + case SPI_MODE_HALF_DUPLEX: + spi_half_duplex(spi, wlen, rlen); + break; + case SPI_MODE_FULL_DUPLEX: + spi_full_duplex(spi, wlen, rlen); + break; + default: + break; + } + if (current_spi_mode == CS_MODE_AUTO) { + cs_deselect(active_cs_pin); } - - cs_deselect(PIN_CS); } break; case S_CMD_S_SPI_FREQ: @@ -155,16 +234,60 @@ void process(const pio_spi_inst_t *spi, int command) { if (freq >= 1) { putchar(S_ACK); putu32(serprog_spi_init(freq)); + apply_pin_state(spi, pin_state); } else { putchar(S_NAK); } } break; case S_CMD_S_PIN_STATE: - //TODO: - getchar(); + pin_state = !!getchar(); + apply_pin_state(spi, pin_state); putchar(S_ACK); break; + case S_CMD_S_SPI_CS: + { + uint8_t new_cs = getchar(); + if (new_cs < NUM_CS_AVAILABLE) { + active_cs_pin = new_cs; + putchar(S_ACK); + } else { + putchar(S_NAK); + } + break; + } + case S_CMD_S_SPI_MODE: + { + uint8_t spi_mode = getchar(); + if (spi_mode <= SPI_MODE_MAX) { + current_spi_mode = spi_mode; + putchar(S_ACK); + } else { + putchar(S_NAK); + } + break; + } + case S_CMD_S_CS_MODE: + { + uint8_t cs_mode = getchar(); + switch (cs_mode) { + case CS_MODE_AUTO: + case CS_MODE_DESELECTED: + cs_deselect(active_cs_pin); + current_cs_mode = cs_mode; + putchar(S_ACK); + break; + case CS_MODE_SELECTED: + cs_select(active_cs_pin); + current_cs_mode = cs_mode; + putchar(S_ACK); + break; + default: + putchar(S_NAK); + break; + } + break; + } default: putchar(S_NAK); } @@ -174,7 +297,6 @@ void process(const pio_spi_inst_t *spi, int command) { static const pio_spi_inst_t spi = { .pio = pio1, .sm = 0, - .cs_pin = PIN_CS }; static uint spi_offset; @@ -217,7 +339,10 @@ int main() { bi_decl(bi_1pin_with_name(PIN_MISO, "MISO")); bi_decl(bi_1pin_with_name(PIN_MOSI, "MOSI")); bi_decl(bi_1pin_with_name(PIN_SCK, "SCK")); - bi_decl(bi_1pin_with_name(PIN_CS, "CS#")); + bi_decl(bi_1pin_with_name(PIN_CS_0, "CS_0 (default)")); + bi_decl(bi_1pin_with_name(PIN_CS_1, "CS_1")); + bi_decl(bi_1pin_with_name(PIN_CS_2, "CS_2")); + bi_decl(bi_1pin_with_name(PIN_CS_3, "CS_3")); stdio_init_all(); @@ -225,9 +350,11 @@ int main() { // Initialize CS - gpio_init(PIN_CS); - gpio_put(PIN_CS, 1); - gpio_set_dir(PIN_CS, GPIO_OUT); + for (int i = 0; i < NUM_CS_AVAILABLE; i++) { + gpio_init(cs_pins[i]); + gpio_put(cs_pins[i], 1); + gpio_set_dir(cs_pins[i], GPIO_IN); // switch to output on S_CMD_S_PIN_STATE + } spi_offset = pio_add_program(spi.pio, &spi_cpha0_program); serprog_spi_init(1000000); // 1 MHz diff --git a/pio/spi.pio b/pio/spi.pio index 132c661..7dfc940 100644 --- a/pio/spi.pio +++ b/pio/spi.pio @@ -54,9 +54,9 @@ static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits, sm_config_set_in_shift(&c, false, true, n_bits); sm_config_set_clkdiv(&c, clkdiv); - // MOSI, SCK output are low, MISO is input + // MOSI, SCK output are low, MISO is input -- but we start in input mode pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi)); - pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); + pio_sm_set_pindirs_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); pio_gpio_init(pio, pin_mosi); pio_gpio_init(pio, pin_miso); pio_gpio_init(pio, pin_sck); @@ -70,6 +70,12 @@ static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits, pio_sm_init(pio, sm, prog_offs, &c); pio_sm_set_enabled(pio, sm, true); } + +static inline void pio_spi_enable_outputs(PIO pio, uint sm, bool output, uint pin_sck, uint pin_mosi, uint pin_miso) { + uint mask = output? (1u << pin_sck) | (1u << pin_mosi) : 0; + pio_sm_set_pindirs_with_mask(pio, sm, mask, (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); +} + %} ; SPI with Chip Select diff --git a/spi.h b/spi.h index b54aaea..9c05e69 100644 --- a/spi.h +++ b/spi.h @@ -23,3 +23,6 @@ #define S_CMD_O_SPIOP 0x13 /* Perform SPI operation. */ #define S_CMD_S_SPI_FREQ 0x14 /* Set SPI clock frequency */ #define S_CMD_S_PIN_STATE 0x15 /* Enable/disable output drivers */ +#define S_CMD_S_SPI_CS 0x16 /* Set SPI chip select to use */ +#define S_CMD_S_SPI_MODE 0x17 /* Sets the spi mode used by S_CMD_O_SPIOP */ +#define S_CMD_S_CS_MODE 0x18 /* Sets the way the CS is controlled */