Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand Down
199 changes: 163 additions & 36 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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);
}
Copy link
Contributor

@neuschaefer neuschaefer Apr 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is suboptimal performance-wise, because of the lack of chunking.

I gave optimization a try and came up with the follow (untested!), but it's not very concise:

void spi_full_duplex(const pio_spi_inst_t *spi, uint32_t wlen, uint32_t rlen) {
    size_t len = MAX(wlen, 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);
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this suggested change but was not able to test it yet with hardware.


void process(const pio_spi_inst_t *spi, int command) {
static bool pin_state = false;

switch(command) {
case S_CMD_NOP:
putchar(S_ACK);
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
Expand All @@ -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;

Expand Down Expand Up @@ -217,17 +339,22 @@ 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();

stdio_set_translate_crlf(&stdio_usb, false);


// 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
Expand Down
10 changes: 8 additions & 2 deletions pio/spi.pio
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions spi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */