#include #include #include #include #include #include #include #include #include "rpi.h" #include "spi.h" enum ff_spi_quirks { // There is no separate "Write SR 2" command. Instead, // you must write SR2 after writing SR1 SQ_SR2_FROM_SR1 = (1 << 0), // Don't issue a "Write Enable" command prior to writing // a status register SQ_SKIP_SR_WEL = (1 << 1), // Security registers are shifted up by 4 bits SQ_SECURITY_NYBBLE_SHIFT = (1 << 2), }; struct ff_spi { enum spi_state state; enum spi_type type; enum spi_type desired_type; struct spi_id id; enum ff_spi_quirks quirks; int size_override; struct { int clk; int d0; int d1; int d2; int d3; int wp; int hold; int cs; int miso; int mosi; } pins; }; static void spi_get_id(struct ff_spi *spi); static void spi_set_state(struct ff_spi *spi, enum spi_state state) { if (spi->state == state) return; switch (state) { case SS_SINGLE: gpioSetMode(spi->pins.clk, PI_OUTPUT); // CLK gpioSetMode(spi->pins.cs, PI_OUTPUT); // CE0# gpioSetMode(spi->pins.mosi, PI_OUTPUT); // MOSI gpioSetMode(spi->pins.miso, PI_INPUT); // MISO gpioSetMode(spi->pins.hold, PI_OUTPUT); gpioSetMode(spi->pins.wp, PI_OUTPUT); break; case SS_DUAL_RX: gpioSetMode(spi->pins.clk, PI_OUTPUT); // CLK gpioSetMode(spi->pins.cs, PI_OUTPUT); // CE0# gpioSetMode(spi->pins.mosi, PI_INPUT); // MOSI gpioSetMode(spi->pins.miso, PI_INPUT); // MISO gpioSetMode(spi->pins.hold, PI_OUTPUT); gpioSetMode(spi->pins.wp, PI_OUTPUT); break; case SS_DUAL_TX: gpioSetMode(spi->pins.clk, PI_OUTPUT); // CLK gpioSetMode(spi->pins.cs, PI_OUTPUT); // CE0# gpioSetMode(spi->pins.mosi, PI_OUTPUT); // MOSI gpioSetMode(spi->pins.miso, PI_OUTPUT); // MISO gpioSetMode(spi->pins.hold, PI_OUTPUT); gpioSetMode(spi->pins.wp, PI_OUTPUT); break; case SS_QUAD_RX: gpioSetMode(spi->pins.clk, PI_OUTPUT); // CLK gpioSetMode(spi->pins.cs, PI_OUTPUT); // CE0# gpioSetMode(spi->pins.mosi, PI_INPUT); // MOSI gpioSetMode(spi->pins.miso, PI_INPUT); // MISO gpioSetMode(spi->pins.hold, PI_INPUT); gpioSetMode(spi->pins.wp, PI_INPUT); break; case SS_QUAD_TX: gpioSetMode(spi->pins.clk, PI_OUTPUT); // CLK gpioSetMode(spi->pins.cs, PI_OUTPUT); // CE0# gpioSetMode(spi->pins.mosi, PI_OUTPUT); // MOSI gpioSetMode(spi->pins.miso, PI_OUTPUT); // MISO gpioSetMode(spi->pins.hold, PI_OUTPUT); gpioSetMode(spi->pins.wp, PI_OUTPUT); break; case SS_HARDWARE: gpioSetMode(spi->pins.clk, PI_ALT0); // CLK gpioSetMode(spi->pins.cs, PI_ALT0); // CE0# gpioSetMode(spi->pins.mosi, PI_ALT0); // MOSI gpioSetMode(spi->pins.miso, PI_ALT0); // MISO gpioSetMode(spi->pins.hold, PI_OUTPUT); gpioSetMode(spi->pins.wp, PI_OUTPUT); break; default: fprintf(stderr, "Unrecognized spi state\n"); return; } spi->state = state; } void spiPause(struct ff_spi *spi) { (void)spi; // usleep(1); return; } void spiBegin(struct ff_spi *spi) { spi_set_state(spi, SS_SINGLE); if ((spi->type == ST_SINGLE) || (spi->type == ST_DUAL)) { gpioWrite(spi->pins.wp, 1); gpioWrite(spi->pins.hold, 1); } gpioWrite(spi->pins.cs, 0); } void spiEnd(struct ff_spi *spi) { (void)spi; gpioWrite(spi->pins.cs, 1); } static uint8_t spiXfer(struct ff_spi *spi, uint8_t out) { int bit; uint8_t in = 0; for (bit = 7; bit >= 0; bit--) { if (out & (1 << bit)) { gpioWrite(spi->pins.mosi, 1); } else { gpioWrite(spi->pins.mosi, 0); } gpioWrite(spi->pins.clk, 1); spiPause(spi); in |= ((!!gpioRead(spi->pins.miso)) << bit); gpioWrite(spi->pins.clk, 0); spiPause(spi); } return in; } static void spiSingleTx(struct ff_spi *spi, uint8_t out) { spi_set_state(spi, SS_SINGLE); spiXfer(spi, out); } static uint8_t spiSingleRx(struct ff_spi *spi) { spi_set_state(spi, SS_SINGLE); return spiXfer(spi, 0xff); } static void spiDualTx(struct ff_spi *spi, uint8_t out) { int bit; spi_set_state(spi, SS_DUAL_TX); for (bit = 7; bit >= 0; bit -= 2) { if (out & (1 << (bit - 1))) { gpioWrite(spi->pins.d0, 1); } else { gpioWrite(spi->pins.d0, 0); } if (out & (1 << (bit - 0))) { gpioWrite(spi->pins.d1, 1); } else { gpioWrite(spi->pins.d1, 0); } gpioWrite(spi->pins.clk, 1); spiPause(spi); gpioWrite(spi->pins.clk, 0); spiPause(spi); } } static void spiQuadTx(struct ff_spi *spi, uint8_t out) { int bit; spi_set_state(spi, SS_QUAD_TX); for (bit = 7; bit >= 0; bit -= 4) { if (out & (1 << (bit - 3))) { gpioWrite(spi->pins.d0, 1); } else { gpioWrite(spi->pins.d0, 0); } if (out & (1 << (bit - 2))) { gpioWrite(spi->pins.d1, 1); } else { gpioWrite(spi->pins.d1, 0); } if (out & (1 << (bit - 1))) { gpioWrite(spi->pins.d2, 1); } else { gpioWrite(spi->pins.d2, 0); } if (out & (1 << (bit - 0))) { gpioWrite(spi->pins.d3, 1); } else { gpioWrite(spi->pins.d3, 0); } gpioWrite(spi->pins.clk, 1); spiPause(spi); gpioWrite(spi->pins.clk, 0); spiPause(spi); } } static uint8_t spiDualRx(struct ff_spi *spi) { int bit; uint8_t in = 0; spi_set_state(spi, SS_QUAD_RX); for (bit = 7; bit >= 0; bit -= 2) { gpioWrite(spi->pins.clk, 1); spiPause(spi); in |= ((!!gpioRead(spi->pins.d0)) << (bit - 1)); in |= ((!!gpioRead(spi->pins.d1)) << (bit - 0)); gpioWrite(spi->pins.clk, 0); spiPause(spi); } return in; } static uint8_t spiQuadRx(struct ff_spi *spi) { int bit; uint8_t in = 0; spi_set_state(spi, SS_QUAD_RX); for (bit = 7; bit >= 0; bit -= 4) { gpioWrite(spi->pins.clk, 1); spiPause(spi); in |= ((!!gpioRead(spi->pins.d0)) << (bit - 3)); in |= ((!!gpioRead(spi->pins.d1)) << (bit - 2)); in |= ((!!gpioRead(spi->pins.d2)) << (bit - 1)); in |= ((!!gpioRead(spi->pins.d3)) << (bit - 0)); gpioWrite(spi->pins.clk, 0); spiPause(spi); } return in; } int spiTx(struct ff_spi *spi, uint8_t word) { switch (spi->type) { case ST_SINGLE: spiSingleTx(spi, word); break; case ST_DUAL: spiDualTx(spi, word); break; case ST_QUAD: case ST_QPI: spiQuadTx(spi, word); break; default: return -1; } return 0; } uint8_t spiRx(struct ff_spi *spi) { switch (spi->type) { case ST_SINGLE: return spiSingleRx(spi); case ST_DUAL: return spiDualRx(spi); case ST_QUAD: case ST_QPI: return spiQuadRx(spi); default: return 0xff; } } void spiCommand(struct ff_spi *spi, uint8_t cmd) { if (spi->type == ST_QPI) spiQuadTx(spi, cmd); else spiSingleTx(spi, cmd); } uint8_t spiCommandRx(struct ff_spi *spi) { if (spi->type == ST_QPI) return spiQuadRx(spi); else return spiSingleRx(spi); } uint8_t spiReadStatus(struct ff_spi *spi, uint8_t sr) { uint8_t val = 0xff; switch (sr) { case 1: spiBegin(spi); spiCommand(spi, 0x05); val = spiCommandRx(spi); spiEnd(spi); break; case 2: spiBegin(spi); spiCommand(spi, 0x35); val = spiCommandRx(spi); spiEnd(spi); break; case 3: spiBegin(spi); spiCommand(spi, 0x15); val = spiCommandRx(spi); spiEnd(spi); break; default: fprintf(stderr, "unrecognized status register: %d\n", sr); break; } return val; } void spiWriteSecurity(struct ff_spi *spi, uint8_t sr, uint8_t security[256]) { if (spi->quirks & SQ_SECURITY_NYBBLE_SHIFT) sr <<= 4; spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); // erase the register spiBegin(spi); spiCommand(spi, 0x44); spiCommand(spi, 0x00); // A23-16 spiCommand(spi, sr); // A15-8 spiCommand(spi, 0x00); // A0-7 spiEnd(spi); spi_get_id(spi); sleep(1); // write enable spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x42); spiCommand(spi, 0x00); // A23-16 spiCommand(spi, sr); // A15-8 spiCommand(spi, 0x00); // A0-7 int i; for (i = 0; i < 256; i++) spiCommand(spi, security[i]); spiEnd(spi); spi_get_id(spi); } void spiReadSecurity(struct ff_spi *spi, uint8_t sr, uint8_t security[256]) { if (spi->quirks & SQ_SECURITY_NYBBLE_SHIFT) sr <<= 4; spiBegin(spi); spiCommand(spi, 0x48); // Read security registers spiCommand(spi, 0x00); // A23-16 spiCommand(spi, sr); // A15-8 spiCommand(spi, 0x00); // A0-7 int i; for (i = 0; i < 256; i++) security[i] = spiCommandRx(spi); spiEnd(spi); } void spiWriteStatus(struct ff_spi *spi, uint8_t sr, uint8_t val) { switch (sr) { case 1: if (!(spi->quirks & SQ_SKIP_SR_WEL)) { spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); } spiBegin(spi); spiCommand(spi, 0x50); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x01); spiCommand(spi, val); spiEnd(spi); break; case 2: { uint8_t sr1 = 0x00; if (spi->quirks & SQ_SR2_FROM_SR1) sr1 = spiReadStatus(spi, 1); if (!(spi->quirks & SQ_SKIP_SR_WEL)) { spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); } spiBegin(spi); spiCommand(spi, 0x50); spiEnd(spi); spiBegin(spi); if (spi->quirks & SQ_SR2_FROM_SR1) { spiCommand(spi, 0x01); spiCommand(spi, sr1); spiCommand(spi, val); } else { spiCommand(spi, 0x31); spiCommand(spi, val); } spiEnd(spi); break; } case 3: if (!(spi->quirks & SQ_SKIP_SR_WEL)) { spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); } spiBegin(spi); spiCommand(spi, 0x50); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x11); spiCommand(spi, val); spiEnd(spi); break; default: fprintf(stderr, "unrecognized status register: %d\n", sr); break; } } struct spi_id spiId(struct ff_spi *spi) { return spi->id; } static void spi_decode_id(struct ff_spi *spi) { spi->id.manufacturer = "unknown"; spi->id.model = "unknown"; spi->id.capacity = "unknown"; spi->id.bytes = -1; // unknown if (spi->id.manufacturer_id == 0xef) { spi->id.manufacturer = "Winbond"; if ((spi->id.memory_type == 0x70) && (spi->id.memory_size == 0x18)) { spi->id.model = "W25Q128JV"; spi->id.capacity = "128 Mbit"; spi->id.bytes = 16 * 1024 * 1024; } } if (spi->id.manufacturer_id == 0x1f) { spi->id.manufacturer = "Adesto"; if ((spi->id.memory_type == 0x86) && (spi->id.memory_size == 0x01)) { spi->id.model = "AT25SF161"; spi->id.capacity = "16 Mbit"; spi->id.bytes = 1 * 1024 * 1024; } } return; } static void spi_get_id(struct ff_spi *spi) { memset(&spi->id, 0xff, sizeof(spi->id)); spiBegin(spi); spiCommand(spi, 0x90); // Read manufacturer ID spiCommand(spi, 0x00); // Dummy byte 1 spiCommand(spi, 0x00); // Dummy byte 2 spiCommand(spi, 0x00); // Dummy byte 3 spi->id.manufacturer_id = spiCommandRx(spi); spi->id.device_id = spiCommandRx(spi); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x9f); // Read device id spi->id._manufacturer_id = spiCommandRx(spi); spi->id.memory_type = spiCommandRx(spi); spi->id.memory_size = spiCommandRx(spi); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0xab); // Read electronic signature spiCommand(spi, 0x00); // Dummy byte 1 spiCommand(spi, 0x00); // Dummy byte 2 spiCommand(spi, 0x00); // Dummy byte 3 spi->id.signature = spiCommandRx(spi); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x4b); // Read unique ID spiCommand(spi, 0x00); // Dummy byte 1 spiCommand(spi, 0x00); // Dummy byte 2 spiCommand(spi, 0x00); // Dummy byte 3 spiCommand(spi, 0x00); // Dummy byte 4 spi->id.serial[0] = spiCommandRx(spi); spi->id.serial[1] = spiCommandRx(spi); spi->id.serial[2] = spiCommandRx(spi); spi->id.serial[3] = spiCommandRx(spi); spiEnd(spi); spi_decode_id(spi); return; } void spiOverrideSize(struct ff_spi *spi, uint32_t size) { spi->size_override = size; // If size is 0, re-read the capacity if (!size) spi_decode_id(spi); else spi->id.bytes = size; } int spiSetType(struct ff_spi *spi, enum spi_type type) { if (spi->type == type) return 0; switch (type) { case ST_SINGLE: if (spi->type == ST_QPI) { spiBegin(spi); spiCommand(spi, 0xff); // Exit QPI Mode spiEnd(spi); } spi->type = type; spi_set_state(spi, SS_SINGLE); break; case ST_DUAL: if (spi->type == ST_QPI) { spiBegin(spi); spiCommand(spi, 0xff); // Exit QPI Mode spiEnd(spi); } spi->type = type; spi_set_state(spi, SS_DUAL_TX); break; case ST_QUAD: if (spi->type == ST_QPI) { spiBegin(spi); spiCommand(spi, 0xff); // Exit QPI Mode spiEnd(spi); } // Enable QE bit spiWriteStatus(spi, 2, spiReadStatus(spi, 2) | (1 << 1)); spi->type = type; spi_set_state(spi, SS_QUAD_TX); break; case ST_QPI: // Enable QE bit spiWriteStatus(spi, 2, spiReadStatus(spi, 2) | (1 << 1)); spiBegin(spi); spiCommand(spi, 0x38); // Enter QPI Mode spiEnd(spi); spi->type = type; spi_set_state(spi, SS_QUAD_TX); break; default: fprintf(stderr, "Unrecognized SPI type: %d\n", type); return 1; } return 0; } int spiRead(struct ff_spi *spi, uint32_t addr, uint8_t *data, unsigned int count) { unsigned int i; spiBegin(spi); switch (spi->type) { case ST_SINGLE: case ST_QPI: spiCommand(spi, 0x0b); break; case ST_DUAL: spiCommand(spi, 0x3b); break; case ST_QUAD: spiCommand(spi, 0x6b); break; default: fprintf(stderr, "unrecognized spi mode\n"); spiEnd(spi); return 1; } spiCommand(spi, addr >> 16); spiCommand(spi, addr >> 8); spiCommand(spi, addr >> 0); spiCommand(spi, 0x00); for (i = 0; i < count; i++) { if ((i & 0x3fff) == 0) { printf("\rReading @ %06x / %06x", i, count); fflush(stdout); } data[i] = spiRx(spi); } printf("\rReading @ %06x / %06x Done\n", i, count); spiEnd(spi); return 0; } static int spi_wait_for_not_busy(struct ff_spi *spi) { uint8_t sr1; sr1 = spiReadStatus(spi, 1); do { sr1 = spiReadStatus(spi, 1); } while (sr1 & (1 << 0)); return 0; } void spiSwapTxRx(struct ff_spi *spi) { int tmp = spi->pins.mosi; spi->pins.mosi = spi->pins.miso; spi->pins.miso = tmp; spiSetType(spi, ST_SINGLE); spi->state = SS_UNCONFIGURED; spi_set_state(spi, SS_SINGLE); } int spiWrite(struct ff_spi *spi, uint32_t addr, const uint8_t *data, unsigned int count) { unsigned int i; if (addr & 0xff) { fprintf(stderr, "Error: Target address is not page-aligned to 256 bytes\n"); return 1; } // Erase all applicable blocks uint32_t erase_addr; for (erase_addr = 0; erase_addr < count; erase_addr += 32768) { printf("\rErasing @ %06x / %06x", erase_addr, count); fflush(stdout); spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x52); spiCommand(spi, erase_addr >> 16); spiCommand(spi, erase_addr >> 8); spiCommand(spi, erase_addr >> 0); spiEnd(spi); spi_wait_for_not_busy(spi); } printf(" Done\n"); uint8_t write_cmd; switch (spi->type) { case ST_DUAL: fprintf(stderr, "dual writes are broken -- need to temporarily set SINGLE mode\n"); return 1; case ST_SINGLE: case ST_QPI: write_cmd = 0x02; break; case ST_QUAD: write_cmd = 0x32; break; default: fprintf(stderr, "unrecognized spi mode\n"); return 1; } int total = count; while (count) { printf("\rProgramming @ %06x / %06x", addr, total); fflush(stdout); spiBegin(spi); spiCommand(spi, 0x06); spiEnd(spi); uint8_t sr1 = spiReadStatus(spi, 1); if (!(sr1 & (1 << 1))) fprintf(stderr, "error: write-enable latch (WEL) not set, write will probably fail\n"); spiBegin(spi); spiCommand(spi, write_cmd); spiCommand(spi, addr >> 16); spiCommand(spi, addr >> 8); spiCommand(spi, addr >> 0); for (i = 0; (i < count) && (i < 256); i++) spiTx(spi, *data++); spiEnd(spi); count -= i; addr += i; spi_wait_for_not_busy(spi); } printf("\rProgramming @ %06x / %06x", addr, total); printf(" Done\n"); return 0; } uint8_t spiReset(struct ff_spi *spi) { // XXX You should check the "Ready" bit before doing this! // Shift to QPI mode, then back to Single mode, to ensure // we're actually in Single mode. spiSetType(spi, ST_QPI); spiSetType(spi, ST_SINGLE); spiBegin(spi); spiCommand(spi, 0x66); // "Enable Reset" command spiEnd(spi); spiBegin(spi); spiCommand(spi, 0x99); // "Reset Device" command spiEnd(spi); usleep(30); spiBegin(spi); spiCommand(spi, 0xab); // "Resume from Deep Power-Down" command spiEnd(spi); return 0; } int spiInit(struct ff_spi *spi) { spi->state = SS_UNCONFIGURED; spi->type = ST_UNCONFIGURED; // Reset the SPI flash, which will return it to SPI mode even // if it's in QPI mode. spiReset(spi); spiSetType(spi, ST_SINGLE); // Have the SPI flash pay attention to us gpioWrite(spi->pins.hold, 1); // Disable WP gpioWrite(spi->pins.wp, 1); spi_get_id(spi); spi->quirks |= SQ_SR2_FROM_SR1; // if (spi->id.manufacturer_id == 0x1f) if (spi->id.manufacturer_id == 0xef) spi->quirks |= SQ_SKIP_SR_WEL | SQ_SECURITY_NYBBLE_SHIFT; return 0; } struct ff_spi *spiAlloc(void) { struct ff_spi *spi = (struct ff_spi *)malloc(sizeof(struct ff_spi)); memset(spi, 0, sizeof(*spi)); return spi; } void spiSetPin(struct ff_spi *spi, enum spi_pin pin, int val) { switch (pin) { case SP_MOSI: spi->pins.mosi = val; break; case SP_MISO: spi->pins.miso = val; break; case SP_HOLD: spi->pins.hold = val; break; case SP_WP: spi->pins.wp = val; break; case SP_CS: spi->pins.cs = val; break; case SP_CLK: spi->pins.clk = val; break; case SP_D0: spi->pins.d0 = val; break; case SP_D1: spi->pins.d1 = val; break; case SP_D2: spi->pins.d2 = val; break; case SP_D3: spi->pins.d3 = val; break; default: fprintf(stderr, "unrecognized pin: %d\n", pin); break; } } void spiHold(struct ff_spi *spi) { spiBegin(spi); spiCommand(spi, 0xb9); spiEnd(spi); } void spiUnhold(struct ff_spi *spi) { spiBegin(spi); spiCommand(spi, 0xab); spiEnd(spi); } void spiFree(struct ff_spi **spi) { if (!spi) return; if (!*spi) return; spi_set_state(*spi, SS_HARDWARE); free(*spi); *spi = NULL; }